diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 59f45cde3d..743a9c90e1 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -80,16 +80,11 @@ ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
     // Version is intentionally skipped in shader generation, it's added by the lazy compilation.
     const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
 
-    std::string out = out += "// Shader Unique Id: GS" + id + '\n';
+    std::string out = "// Shader Unique Id: GS" + id + '\n';
     out += "#extension GL_ARB_separate_shader_objects : enable\n";
     out += GetCommonDeclarations();
 
-    ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
-    ProgramResult program =
-        Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry");
-
-    out += R"(
-out gl_PerVertex {
+    out += R"(out gl_PerVertex {
     vec4 gl_Position;
 };
 
@@ -103,9 +98,12 @@ layout (std140) uniform gs_config {
 };
 )";
 
+    ShaderIR program_ir(setup.program.code, PROGRAM_OFFSET);
+    ProgramResult program =
+        Decompile(program_ir, Maxwell3D::Regs::ShaderStage::Geometry, "geometry");
     out += program.first;
 
-    out = R"(
+    out += R"(
 void main() {
     execute_geometry();
 };)";
diff --git a/src/video_core/shader/decode/other.cpp b/src/video_core/shader/decode/other.cpp
index 9630ef831f..1918762b8d 100644
--- a/src/video_core/shader/decode/other.cpp
+++ b/src/video_core/shader/decode/other.cpp
@@ -12,6 +12,7 @@ namespace VideoCommon::Shader {
 using Tegra::Shader::ConditionCode;
 using Tegra::Shader::Instruction;
 using Tegra::Shader::OpCode;
+using Tegra::Shader::Register;
 
 u32 ShaderIR::DecodeOther(BasicBlock& bb, u32 pc) {
     const Instruction instr = {program_code[pc]};
@@ -140,6 +141,30 @@ u32 ShaderIR::DecodeOther(BasicBlock& bb, u32 pc) {
         SetRegister(bb, instr.gpr0, value);
         break;
     }
+    case OpCode::Id::OUT_R: {
+        UNIMPLEMENTED_IF_MSG(instr.gpr20.Value() != Register::ZeroIndex,
+                             "Stream buffer is not supported");
+
+        if (instr.out.emit) {
+            // gpr0 is used to store the next address and gpr8 contains the address to emit.
+            // Hardware uses pointers here but we just ignore it
+            bb.push_back(Operation(OperationCode::EmitVertex));
+            SetRegister(bb, instr.gpr0, Immediate(0));
+        }
+        if (instr.out.cut) {
+            bb.push_back(Operation(OperationCode::EndPrimitive));
+        }
+        break;
+    }
+    case OpCode::Id::ISBERD: {
+        UNIMPLEMENTED_IF(instr.isberd.o != 0);
+        UNIMPLEMENTED_IF(instr.isberd.skew != 0);
+        UNIMPLEMENTED_IF(instr.isberd.shift != Tegra::Shader::IsberdShift::None);
+        UNIMPLEMENTED_IF(instr.isberd.mode != Tegra::Shader::IsberdMode::None);
+        LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete");
+        SetRegister(bb, instr.gpr0, GetRegister(instr.gpr8));
+        break;
+    }
     case OpCode::Id::DEPBAR: {
         LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed");
         break;
diff --git a/src/video_core/shader/glsl_decompiler.cpp b/src/video_core/shader/glsl_decompiler.cpp
index 2e9323df7a..9b443e61a4 100644
--- a/src/video_core/shader/glsl_decompiler.cpp
+++ b/src/video_core/shader/glsl_decompiler.cpp
@@ -89,6 +89,22 @@ static std::string GetSwizzle(u32 elem) {
     return swizzle;
 }
 
+/// Translate topology
+static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
+    switch (topology) {
+    case Tegra::Shader::OutputTopology::PointList:
+        return "points";
+    case Tegra::Shader::OutputTopology::LineStrip:
+        return "line_strip";
+    case Tegra::Shader::OutputTopology::TriangleStrip:
+        return "triangle_strip";
+    default:
+        UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology));
+        return "points";
+    }
+}
+
+/// Returns true if an object has to be treated as precise
 static bool IsPrecise(Operation operand) {
     const auto& meta = operand.GetMeta();
 
@@ -115,6 +131,7 @@ public:
 
     void Decompile() {
         DeclareVertex();
+        DeclareGeometry();
         DeclareRegisters();
         DeclarePredicates();
         DeclareLocalMemory();
@@ -212,6 +229,16 @@ private:
         code.AddNewLine();
     }
 
+    void DeclareGeometry() {
+        if (stage != ShaderStage::Geometry)
+            return;
+
+        const auto topology = GetTopologyName(header.common3.output_topology);
+        const auto max_vertices = std::to_string(header.common4.max_output_vertices);
+        code.AddLine("layout (" + topology + ", max_vertices = " + max_vertices + ") out;");
+        code.AddNewLine();
+    }
+
     void DeclareRegisters() {
         const auto& registers = ir.GetRegisters();
         for (const u32 gpr : registers) {
@@ -419,9 +446,24 @@ private:
             const auto attribute = abuf->GetIndex();
             const auto element = abuf->GetElement();
 
+            const auto GeometryPass = [&](const std::string& name) {
+                if (stage == ShaderStage::Geometry && abuf->GetBuffer()) {
+                    // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
+                    // set an 0x80000000 index for those and the shader fails to build. Find out why
+                    // this happens and what's its intent.
+                    return "gs_" + name + "[ftou(" + Visit(abuf->GetBuffer()) +
+                           ") % MAX_VERTEX_INPUT]";
+                }
+                return name;
+            };
+
             switch (attribute) {
             case Attribute::Index::Position:
-                return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element);
+                if (stage != ShaderStage::Fragment) {
+                    return GeometryPass("position") + GetSwizzle(element);
+                } else {
+                    return element == 3 ? "1.0f" : "gl_FragCoord" + GetSwizzle(element);
+                }
             case Attribute::Index::PointCoord:
                 switch (element) {
                 case 0:
@@ -460,7 +502,7 @@ private:
             default:
                 if (attribute >= Attribute::Index::Attribute_0 &&
                     attribute <= Attribute::Index::Attribute_31) {
-                    return GetInputAttribute(attribute) + GetSwizzle(abuf->GetElement());
+                    return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element);
                 }
                 break;
             }
@@ -1226,6 +1268,27 @@ private:
         return {};
     }
 
+    std::string EmitVertex(Operation operation) {
+        ASSERT_MSG(stage == ShaderStage::Geometry,
+                   "EmitVertex is expected to be used in a geometry shader.");
+
+        // If a geometry shader is attached, it will always flip (it's the last stage before
+        // fragment). For more info about flipping, refer to gl_shader_gen.cpp.
+        code.AddLine("position.xy *= viewport_flip.xy;");
+        code.AddLine("gl_Position = position;");
+        code.AddLine("position.w = 1.0;");
+        code.AddLine("EmitVertex();");
+        return {};
+    }
+
+    std::string EndPrimitive(Operation operation) {
+        ASSERT_MSG(stage == ShaderStage::Geometry,
+                   "EndPrimitive is expected to be used in a geometry shader.");
+
+        code.AddLine("EndPrimitive();");
+        return {};
+    }
+
     std::string YNegate(Operation operation) {
         // Config pack's third value is Y_NEGATE's state.
         return "uintBitsToFloat(config_pack[2])";
@@ -1361,6 +1424,9 @@ private:
         &Exit,
         &Kil,
 
+        &EmitVertex,
+        &EndPrimitive,
+
         &YNegate,
     };
 
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index 0ea2df6bd6..5676d32a9e 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -172,6 +172,9 @@ enum class OperationCode {
     Exit, /// () -> void
     Kil,  /// () -> void
 
+    EmitVertex,   /// () -> void
+    EndPrimitive, /// () -> void
+
     YNegate, /// () -> float
 
     Amount,