diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index b1f137b9c8b77adee111d2532fa0db0b0cc8ffc5..550ab114868b0c2ce9ef234165e58bcb17a5f02f 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -314,6 +314,15 @@ enum class TextureMiscMode : u64 {
     PTP,
 };
 
+enum class IsberdMode : u64 {
+    None = 0,
+    Patch = 1,
+    Prim = 2,
+    Attr = 3,
+};
+
+enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 };
+
 enum class IpaInterpMode : u64 {
     Linear = 0,
     Perspective = 1,
@@ -340,6 +349,87 @@ struct IpaMode {
     }
 };
 
+enum class SystemVariable : u64 {
+    LaneId = 0x00,
+    VirtCfg = 0x02,
+    VirtId = 0x03,
+    Pm0 = 0x04,
+    Pm1 = 0x05,
+    Pm2 = 0x06,
+    Pm3 = 0x07,
+    Pm4 = 0x08,
+    Pm5 = 0x09,
+    Pm6 = 0x0a,
+    Pm7 = 0x0b,
+    OrderingTicket = 0x0f,
+    PrimType = 0x10,
+    InvocationId = 0x11,
+    Ydirection = 0x12,
+    ThreadKill = 0x13,
+    ShaderType = 0x14,
+    DirectBeWriteAddressLow = 0x15,
+    DirectBeWriteAddressHigh = 0x16,
+    DirectBeWriteEnabled = 0x17,
+    MachineId0 = 0x18,
+    MachineId1 = 0x19,
+    MachineId2 = 0x1a,
+    MachineId3 = 0x1b,
+    Affinity = 0x1c,
+    InvocationInfo = 0x1d,
+    WscaleFactorXY = 0x1e,
+    WscaleFactorZ = 0x1f,
+    Tid = 0x20,
+    TidX = 0x21,
+    TidY = 0x22,
+    TidZ = 0x23,
+    CtaParam = 0x24,
+    CtaIdX = 0x25,
+    CtaIdY = 0x26,
+    CtaIdZ = 0x27,
+    NtId = 0x28,
+    CirQueueIncrMinusOne = 0x29,
+    Nlatc = 0x2a,
+    SmSpaVersion = 0x2c,
+    MultiPassShaderInfo = 0x2d,
+    LwinHi = 0x2e,
+    SwinHi = 0x2f,
+    SwinLo = 0x30,
+    SwinSz = 0x31,
+    SmemSz = 0x32,
+    SmemBanks = 0x33,
+    LwinLo = 0x34,
+    LwinSz = 0x35,
+    LmemLosz = 0x36,
+    LmemHioff = 0x37,
+    EqMask = 0x38,
+    LtMask = 0x39,
+    LeMask = 0x3a,
+    GtMask = 0x3b,
+    GeMask = 0x3c,
+    RegAlloc = 0x3d,
+    CtxAddr = 0x3e,      // .fmask = F_SM50
+    BarrierAlloc = 0x3e, // .fmask = F_SM60
+    GlobalErrorStatus = 0x40,
+    WarpErrorStatus = 0x42,
+    WarpErrorStatusClear = 0x43,
+    PmHi0 = 0x48,
+    PmHi1 = 0x49,
+    PmHi2 = 0x4a,
+    PmHi3 = 0x4b,
+    PmHi4 = 0x4c,
+    PmHi5 = 0x4d,
+    PmHi6 = 0x4e,
+    PmHi7 = 0x4f,
+    ClockLo = 0x50,
+    ClockHi = 0x51,
+    GlobalTimerLo = 0x52,
+    GlobalTimerHi = 0x53,
+    HwTaskId = 0x60,
+    CircularQueueEntryIndex = 0x61,
+    CircularQueueEntryAddressLow = 0x62,
+    CircularQueueEntryAddressHigh = 0x63,
+};
+
 union Instruction {
     Instruction& operator=(const Instruction& instr) {
         value = instr.value;
@@ -914,6 +1004,18 @@ union Instruction {
         }
     } bra;
 
+    union {
+        BitField<39, 1, u64> emit; // EmitVertex
+        BitField<40, 1, u64> cut;  // EndPrimitive
+    } out;
+
+    union {
+        BitField<31, 1, u64> skew;
+        BitField<32, 1, u64> o;
+        BitField<33, 2, IsberdMode> mode;
+        BitField<47, 2, IsberdShift> shift;
+    } isberd;
+
     union {
         BitField<20, 16, u64> imm20_16;
         BitField<36, 1, u64> product_shift_left;
@@ -936,6 +1038,10 @@ union Instruction {
         BitField<36, 5, u64> index;
     } cbuf36;
 
+    // Unsure about the size of this one.
+    // It's always used with a gpr0, so any size should be fine.
+    BitField<20, 8, SystemVariable> sys20;
+
     BitField<47, 1, u64> generates_cc;
     BitField<61, 1, u64> is_b_imm;
     BitField<60, 1, u64> is_b_gpr;
@@ -975,6 +1081,8 @@ public:
         TMML,   // Texture Mip Map Level
         EXIT,
         IPA,
+        OUT_R, // Emit vertex/primitive
+        ISBERD,
         FFMA_IMM, // Fused Multiply and Add
         FFMA_CR,
         FFMA_RC,
@@ -1034,6 +1142,7 @@ public:
         MOV_C,
         MOV_R,
         MOV_IMM,
+        MOV_SYS,
         MOV32_IMM,
         SHL_C,
         SHL_R,
@@ -1209,6 +1318,8 @@ private:
             INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
             INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
             INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
+            INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
+            INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
             INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
             INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
             INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
@@ -1255,6 +1366,7 @@ private:
             INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
             INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
             INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
+            INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"),
             INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"),
             INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
             INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 209bdf18148d716cf92a69cbb81b99a43f5de471..b7215448cad2d674c2b41424d1657d9e39fd7c77 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -255,7 +255,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
     return params;
 }
 
-void RasterizerOpenGL::SetupShaders() {
+void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
     MICROPROFILE_SCOPE(OpenGL_Shader);
     const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
 
@@ -270,6 +270,11 @@ void RasterizerOpenGL::SetupShaders() {
 
         // Skip stages that are not enabled
         if (!gpu.regs.IsShaderConfigEnabled(index)) {
+            switch (program) {
+            case Maxwell::ShaderProgram::Geometry:
+                shader_program_manager->UseTrivialGeometryShader();
+                break;
+            }
             continue;
         }
 
@@ -288,11 +293,18 @@ void RasterizerOpenGL::SetupShaders() {
         switch (program) {
         case Maxwell::ShaderProgram::VertexA:
         case Maxwell::ShaderProgram::VertexB: {
-            shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle());
+            shader_program_manager->UseProgrammableVertexShader(
+                shader->GetProgramHandle(primitive_mode));
+            break;
+        }
+        case Maxwell::ShaderProgram::Geometry: {
+            shader_program_manager->UseProgrammableGeometryShader(
+                shader->GetProgramHandle(primitive_mode));
             break;
         }
         case Maxwell::ShaderProgram::Fragment: {
-            shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle());
+            shader_program_manager->UseProgrammableFragmentShader(
+                shader->GetProgramHandle(primitive_mode));
             break;
         }
         default:
@@ -302,12 +314,13 @@ void RasterizerOpenGL::SetupShaders() {
         }
 
         // Configure the const buffers for this shader stage.
-        current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage),
-                                                          shader, current_constbuffer_bindpoint);
+        current_constbuffer_bindpoint =
+            SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode,
+                              current_constbuffer_bindpoint);
 
         // Configure the textures for this shader stage.
         current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
-                                                  current_texture_bindpoint);
+                                                  primitive_mode, current_texture_bindpoint);
 
         // When VertexA is enabled, we have dual vertex shaders
         if (program == Maxwell::ShaderProgram::VertexA) {
@@ -317,8 +330,6 @@ void RasterizerOpenGL::SetupShaders() {
     }
 
     state.Apply();
-
-    shader_program_manager->UseTrivialGeometryShader();
 }
 
 std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
@@ -580,7 +591,7 @@ void RasterizerOpenGL::DrawArrays() {
 
     SetupVertexArrays();
     DrawParameters params = SetupDraw();
-    SetupShaders();
+    SetupShaders(params.primitive_mode);
 
     buffer_cache.Unmap();
 
@@ -719,7 +730,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
 }
 
 u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader,
-                                        u32 current_bindpoint) {
+                                        GLenum primitive_mode, u32 current_bindpoint) {
     MICROPROFILE_SCOPE(OpenGL_UBO);
     const auto& gpu = Core::System::GetInstance().GPU();
     const auto& maxwell3d = gpu.Maxwell3D();
@@ -771,7 +782,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
             buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
 
         // Now configure the bindpoint of the buffer inside the shader
-        glUniformBlockBinding(shader->GetProgramHandle(),
+        glUniformBlockBinding(shader->GetProgramHandle(primitive_mode),
                               shader->GetProgramResourceIndex(used_buffer),
                               current_bindpoint + bindpoint);
 
@@ -787,7 +798,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
     return current_bindpoint + static_cast<u32>(entries.size());
 }
 
-u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
+u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
+                                    GLenum primitive_mode, u32 current_unit) {
     MICROPROFILE_SCOPE(OpenGL_Texture);
     const auto& gpu = Core::System::GetInstance().GPU();
     const auto& maxwell3d = gpu.Maxwell3D();
@@ -802,8 +814,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
 
         // Bind the uniform to the sampler.
 
-        glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry),
-                           current_bindpoint);
+        glProgramUniform1i(shader->GetProgramHandle(primitive_mode),
+                           shader->GetUniformLocation(entry), current_bindpoint);
 
         const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 0dab2018bbaa87c8f36fe77437fa989a16254a04..8de831468ff08d181572b89d6047bc29b418d60e 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -120,7 +120,7 @@ private:
      * @returns The next available bindpoint for use in the next shader stage.
      */
     u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
-                          u32 current_bindpoint);
+                          GLenum primitive_mode, u32 current_bindpoint);
 
     /*
      * Configures the current textures to use for the draw command.
@@ -130,7 +130,7 @@ private:
      * @returns The next available bindpoint for use in the next shader stage.
      */
     u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
-                      u32 current_unit);
+                      GLenum primitive_mode, u32 current_unit);
 
     /// Syncs the viewport to match the guest state
     void SyncViewport();
@@ -207,7 +207,7 @@ private:
 
     DrawParameters SetupDraw();
 
-    void SetupShaders();
+    void SetupShaders(GLenum primitive_mode);
 
     enum class AccelDraw { Disabled, Arrays, Indexed };
     AccelDraw accelerate_draw = AccelDraw::Disabled;
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 7cd8f91e48d164b532d28349ced1b8eff3c50551..1a03a677f9a6cb7880b98d466a0bcb4a920efe94 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -68,6 +68,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
         program_result = GLShader::GenerateVertexShader(setup);
         gl_type = GL_VERTEX_SHADER;
         break;
+    case Maxwell::ShaderProgram::Geometry:
+        program_result = GLShader::GenerateGeometryShader(setup);
+        gl_type = GL_GEOMETRY_SHADER;
+        break;
     case Maxwell::ShaderProgram::Fragment:
         program_result = GLShader::GenerateFragmentShader(setup);
         gl_type = GL_FRAGMENT_SHADER;
@@ -80,11 +84,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
 
     entries = program_result.second;
 
-    OGLShader shader;
-    shader.Create(program_result.first.c_str(), gl_type);
-    program.Create(true, shader.handle);
-    SetShaderUniformBlockBindings(program.handle);
-    VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
+    if (program_type != Maxwell::ShaderProgram::Geometry) {
+        OGLShader shader;
+        shader.Create(program_result.first.c_str(), gl_type);
+        program.Create(true, shader.handle);
+        SetShaderUniformBlockBindings(program.handle);
+        VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
+    } else {
+        // Store shader's code to lazily build it on draw
+        geometry_programs.code = program_result.first;
+    }
 }
 
 GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
@@ -110,6 +119,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
     return search->second;
 }
 
+GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
+                                         const std::string& glsl_topology,
+                                         const std::string& debug_name) {
+    if (target_program.handle != 0) {
+        return target_program.handle;
+    }
+    const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"};
+    OGLShader shader;
+    shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
+    target_program.Create(true, shader.handle);
+    SetShaderUniformBlockBindings(target_program.handle);
+    VideoCore::LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
+    return target_program.handle;
+};
+
 Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
     const VAddr program_addr{GetShaderAddress(program)};
 
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 9bafe43a983e381dec6d809d30cf33e964a6199a..7bb287f568e922d3e6e756e52d283b009e08fab2 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -7,6 +7,7 @@
 #include <map>
 #include <memory>
 
+#include "common/assert.h"
 #include "common/common_types.h"
 #include "video_core/rasterizer_cache.h"
 #include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -38,8 +39,31 @@ public:
     }
 
     /// Gets the GL program handle for the shader
-    GLuint GetProgramHandle() const {
-        return program.handle;
+    GLuint GetProgramHandle(GLenum primitive_mode) {
+        if (program_type != Maxwell::ShaderProgram::Geometry) {
+            return program.handle;
+        }
+        switch (primitive_mode) {
+        case GL_POINTS:
+            return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints");
+        case GL_LINES:
+        case GL_LINE_STRIP:
+            return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines");
+        case GL_LINES_ADJACENCY:
+        case GL_LINE_STRIP_ADJACENCY:
+            return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency",
+                                       "ShaderLinesAdjacency");
+        case GL_TRIANGLES:
+        case GL_TRIANGLE_STRIP:
+        case GL_TRIANGLE_FAN:
+            return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles");
+        case GL_TRIANGLES_ADJACENCY:
+        case GL_TRIANGLE_STRIP_ADJACENCY:
+            return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency",
+                                       "ShaderLines");
+        default:
+            UNREACHABLE_MSG("Unknown primitive mode.");
+        }
     }
 
     /// Gets the GL program resource location for the specified resource, caching as needed
@@ -49,12 +73,30 @@ public:
     GLint GetUniformLocation(const GLShader::SamplerEntry& sampler);
 
 private:
+    /// Generates a geometry shader or returns one that already exists.
+    GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
+                               const std::string& debug_name);
+
     VAddr addr;
     Maxwell::ShaderProgram program_type;
     GLShader::ShaderSetup setup;
     GLShader::ShaderEntries entries;
+
+    // Non-geometry program.
     OGLProgram program;
 
+    // Geometry programs. These are needed because GLSL needs an input topology but it's not
+    // declared by the hardware. Workaround this issue by generating a different shader per input
+    // topology class.
+    struct {
+        std::string code;
+        OGLProgram points;
+        OGLProgram lines;
+        OGLProgram lines_adjacency;
+        OGLProgram triangles;
+        OGLProgram triangles_adjacency;
+    } geometry_programs;
+
     std::map<u32, GLuint> resource_cache;
     std::map<u32, GLint> uniform_cache;
 };
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 85c668ca13567cd09e5b164b1b41687dd3af4af0..c82a0dcfa53ea5fc84c15a926adec9c8cf8a082f 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -7,6 +7,7 @@
 #include <string>
 #include <string_view>
 
+#include <boost/optional.hpp>
 #include <fmt/format.h>
 
 #include "common/assert.h"
@@ -29,11 +30,32 @@ using Tegra::Shader::SubOp;
 constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
 constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header);
 
+enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
+
+constexpr u32 MAX_GEOMETRY_BUFFERS = 6;
+constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested
+
 class DecompileFail : public std::runtime_error {
 public:
     using std::runtime_error::runtime_error;
 };
 
+/// 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:
+        LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology));
+        UNREACHABLE();
+        return "points";
+    }
+}
+
 /// Describes the behaviour of code path of a given entry point and a return point.
 enum class ExitMethod {
     Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
@@ -253,8 +275,9 @@ enum class InternalFlag : u64 {
 class GLSLRegisterManager {
 public:
     GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
-                        const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
-        : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
+                        const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
+                        const Tegra::Shader::Header& header)
+        : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} {
         BuildRegisterList();
         BuildInputList();
     }
@@ -358,11 +381,13 @@ public:
      * @param reg The destination register to use.
      * @param elem The element to use for the operation.
      * @param attribute The input attribute to use as the source value.
+     * @param vertex The register that decides which vertex to read from (used in GS).
      */
     void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
-                                    const Tegra::Shader::IpaMode& input_mode) {
+                                    const Tegra::Shader::IpaMode& input_mode,
+                                    boost::optional<Register> vertex = {}) {
         const std::string dest = GetRegisterAsFloat(reg);
-        const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
+        const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem);
         shader.AddLine(dest + " = " + src + ';');
     }
 
@@ -391,16 +416,29 @@ public:
      * are stored as floats, so this may require conversion.
      * @param attribute The destination output attribute.
      * @param elem The element to use for the operation.
-     * @param reg The register to use as the source value.
+     * @param val_reg The register to use as the source value.
+     * @param buf_reg The register that tells which buffer to write to (used in geometry shaders).
      */
-    void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
+    void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg,
+                                      const Register& buf_reg) {
         const std::string dest = GetOutputAttribute(attribute);
-        const std::string src = GetRegisterAsFloat(reg);
+        const std::string src = GetRegisterAsFloat(val_reg);
 
         if (!dest.empty()) {
             // Can happen with unknown/unimplemented output attributes, in which case we ignore the
             // instruction for now.
-            shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
+            if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
+                // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry
+                // shader. These instructions use a dirty register as buffer index. To avoid some
+                // drivers from complaining for the out of boundary writes, guard them.
+                const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " +
+                                            std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'};
+                shader.AddLine("amem[" + buf_index + "][" +
+                               std::to_string(static_cast<u32>(attribute)) + ']' +
+                               GetSwizzle(elem) + " = " + src + ';');
+            } else {
+                shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
+            }
         }
     }
 
@@ -441,41 +479,123 @@ public:
         }
     }
 
-    /// Add declarations for registers
+    /// Add declarations.
     void GenerateDeclarations(const std::string& suffix) {
+        GenerateRegisters(suffix);
+        GenerateInternalFlags();
+        GenerateInputAttrs();
+        GenerateOutputAttrs();
+        GenerateConstBuffers();
+        GenerateSamplers();
+        GenerateGeometry();
+    }
+
+    /// Returns a list of constant buffer declarations.
+    std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
+        std::vector<ConstBufferEntry> result;
+        std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
+                     std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
+        return result;
+    }
+
+    /// Returns a list of samplers used in the shader.
+    const std::vector<SamplerEntry>& GetSamplers() const {
+        return used_samplers;
+    }
+
+    /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
+    /// necessary.
+    std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
+                              bool is_array, bool is_shadow) {
+        const auto offset = static_cast<std::size_t>(sampler.index.Value());
+
+        // If this sampler has already been used, return the existing mapping.
+        const auto itr =
+            std::find_if(used_samplers.begin(), used_samplers.end(),
+                         [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
+
+        if (itr != used_samplers.end()) {
+            ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
+                   itr->IsShadow() == is_shadow);
+            return itr->GetName();
+        }
+
+        // Otherwise create a new mapping for this sampler
+        const std::size_t next_index = used_samplers.size();
+        const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow};
+        used_samplers.emplace_back(entry);
+        return entry.GetName();
+    }
+
+private:
+    /// Generates declarations for registers.
+    void GenerateRegisters(const std::string& suffix) {
         for (const auto& reg : regs) {
             declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() +
                                  std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;");
         }
         declarations.AddNewLine();
+    }
 
+    /// Generates declarations for internal flags.
+    void GenerateInternalFlags() {
         for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
             const InternalFlag code = static_cast<InternalFlag>(ii);
             declarations.AddLine("bool " + GetInternalFlag(code) + " = false;");
         }
         declarations.AddNewLine();
+    }
+
+    /// Generates declarations for input attributes.
+    void GenerateInputAttrs() {
+        if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
+            const std::string attr =
+                stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position";
+            declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
+                                 ") in vec4 " + attr + ';');
+        }
 
         for (const auto element : declr_input_attribute) {
             // TODO(bunnei): Use proper number of elements for these
             u32 idx =
                 static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
-            declarations.AddLine("layout(location = " + std::to_string(idx) + ")" +
-                                 GetInputFlags(element.first) + "in vec4 " +
-                                 GetInputAttribute(element.first, element.second) + ';');
+            if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
+                // If inputs are varyings, add an offset
+                idx += GENERIC_VARYING_START_LOCATION;
+            }
+
+            std::string attr{GetInputAttribute(element.first, element.second)};
+            if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
+                attr = "gs_" + attr + "[]";
+            }
+            declarations.AddLine("layout (location = " + std::to_string(idx) + ") " +
+                                 GetInputFlags(element.first) + "in vec4 " + attr + ';');
         }
+
         declarations.AddNewLine();
+    }
 
+    /// Generates declarations for output attributes.
+    void GenerateOutputAttrs() {
+        if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
+            declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) +
+                                 ") out vec4 position;");
+        }
         for (const auto& index : declr_output_attribute) {
             // TODO(bunnei): Use proper number of elements for these
-            declarations.AddLine("layout(location = " +
-                                 std::to_string(static_cast<u32>(index) -
-                                                static_cast<u32>(Attribute::Index::Attribute_0)) +
-                                 ") out vec4 " + GetOutputAttribute(index) + ';');
+            const u32 idx = static_cast<u32>(index) -
+                            static_cast<u32>(Attribute::Index::Attribute_0) +
+                            GENERIC_VARYING_START_LOCATION;
+            declarations.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " +
+                                 GetOutputAttribute(index) + ';');
         }
         declarations.AddNewLine();
+    }
 
+    /// Generates declarations for constant buffers.
+    void GenerateConstBuffers() {
         for (const auto& entry : GetConstBuffersDeclarations()) {
-            declarations.AddLine("layout(std140) uniform " + entry.GetName());
+            declarations.AddLine("layout (std140) uniform " + entry.GetName());
             declarations.AddLine('{');
             declarations.AddLine("    vec4 c" + std::to_string(entry.GetIndex()) +
                                  "[MAX_CONSTBUFFER_ELEMENTS];");
@@ -483,7 +603,10 @@ public:
             declarations.AddNewLine();
         }
         declarations.AddNewLine();
+    }
 
+    /// Generates declarations for samplers.
+    void GenerateSamplers() {
         const auto& samplers = GetSamplers();
         for (const auto& sampler : samplers) {
             declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
@@ -492,44 +615,42 @@ public:
         declarations.AddNewLine();
     }
 
-    /// Returns a list of constant buffer declarations
-    std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
-        std::vector<ConstBufferEntry> result;
-        std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
-                     std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
-        return result;
-    }
-
-    /// Returns a list of samplers used in the shader
-    const std::vector<SamplerEntry>& GetSamplers() const {
-        return used_samplers;
-    }
-
-    /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
-    /// necessary.
-    std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
-                              bool is_array, bool is_shadow) {
-        const std::size_t offset = static_cast<std::size_t>(sampler.index.Value());
+    /// Generates declarations used for geometry shaders.
+    void GenerateGeometry() {
+        if (stage != Maxwell3D::Regs::ShaderStage::Geometry)
+            return;
 
-        // If this sampler has already been used, return the existing mapping.
-        const auto itr =
-            std::find_if(used_samplers.begin(), used_samplers.end(),
-                         [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
+        declarations.AddLine(
+            "layout (" + GetTopologyName(header.common3.output_topology) +
+            ", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;");
+        declarations.AddNewLine();
 
-        if (itr != used_samplers.end()) {
-            ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
-                   itr->IsShadow() == is_shadow);
-            return itr->GetName();
-        }
+        declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" +
+                             std::to_string(MAX_ATTRIBUTES) + "];");
+        declarations.AddNewLine();
 
-        // Otherwise create a new mapping for this sampler
-        const std::size_t next_index = used_samplers.size();
-        const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow};
-        used_samplers.emplace_back(entry);
-        return entry.GetName();
+        constexpr char buffer[] = "amem[output_buffer]";
+        declarations.AddLine("void emit_vertex(uint output_buffer) {");
+        ++declarations.scope;
+        for (const auto element : declr_output_attribute) {
+            declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' +
+                                 std::to_string(static_cast<u32>(element)) + "];");
+        }
+
+        declarations.AddLine("position = " + std::string(buffer) + '[' +
+                             std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];");
+
+        // 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.
+        declarations.AddLine("position.xy *= viewport_flip.xy;");
+        declarations.AddLine("gl_Position = position;");
+        declarations.AddLine("position.w = 1.0;");
+        declarations.AddLine("EmitVertex();");
+        --declarations.scope;
+        declarations.AddLine('}');
+        declarations.AddNewLine();
     }
 
-private:
     /// Generates code representing a temporary (GPR) register.
     std::string GetRegister(const Register& reg, unsigned elem) {
         if (reg == Register::ZeroIndex) {
@@ -586,11 +707,19 @@ private:
 
     /// Generates code representing an input attribute register.
     std::string GetInputAttribute(Attribute::Index attribute,
-                                  const Tegra::Shader::IpaMode& input_mode) {
+                                  const Tegra::Shader::IpaMode& input_mode,
+                                  boost::optional<Register> vertex = {}) {
+        auto GeometryPass = [&](const std::string& name) {
+            if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) {
+                return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']';
+            }
+            return name;
+        };
+
         switch (attribute) {
         case Attribute::Index::Position:
             if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
-                return "position";
+                return GeometryPass("position");
             } else {
                 return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)";
             }
@@ -619,7 +748,7 @@ private:
                         UNREACHABLE();
                     }
                 }
-                return "input_attribute_" + std::to_string(index);
+                return GeometryPass("input_attribute_" + std::to_string(index));
             }
 
             LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute));
@@ -672,7 +801,7 @@ private:
         return out;
     }
 
-    /// Generates code representing an output attribute register.
+    /// Generates code representing the declaration name of an output attribute register.
     std::string GetOutputAttribute(Attribute::Index attribute) {
         switch (attribute) {
         case Attribute::Index::Position:
@@ -708,6 +837,7 @@ private:
     std::vector<SamplerEntry> used_samplers;
     const Maxwell3D::Regs::ShaderStage& stage;
     const std::string& suffix;
+    const Tegra::Shader::Header& header;
 };
 
 class GLSLGenerator {
@@ -1103,8 +1233,8 @@ private:
             return offset + 1;
         }
 
-        shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" +
-                       std::to_string(instr.value) + ')');
+        shader.AddLine(
+            fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value));
 
         using Tegra::Shader::Pred;
         ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
@@ -1826,7 +1956,7 @@ private:
                 const auto LoadNextElement = [&](u32 reg_offset) {
                     regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element,
                                                     static_cast<Attribute::Index>(next_index),
-                                                    input_mode);
+                                                    input_mode, instr.gpr39.Value());
 
                     // Load the next attribute element into the following register. If the element
                     // to load goes beyond the vec4 size, load the first element of the next
@@ -1890,8 +2020,8 @@ private:
 
                 const auto StoreNextElement = [&](u32 reg_offset) {
                     regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index),
-                                                      next_element,
-                                                      instr.gpr0.Value() + reg_offset);
+                                                      next_element, instr.gpr0.Value() + reg_offset,
+                                                      instr.gpr39.Value());
 
                     // Load the next attribute element into the following register. If the element
                     // to load goes beyond the vec4 size, load the first element of the next
@@ -2734,6 +2864,52 @@ private:
 
                 break;
             }
+            case OpCode::Id::OUT_R: {
+                ASSERT(instr.gpr20.Value() == Register::ZeroIndex);
+                ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
+                           "OUT is expected to be used in a geometry shader.");
+
+                if (instr.out.emit) {
+                    // gpr0 is used to store the next address. Hardware returns a pointer but
+                    // we just return the next index with a cyclic cap.
+                    const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)};
+                    const std::string next = "((" + current + " + 1" + ") % " +
+                                             std::to_string(MAX_GEOMETRY_BUFFERS) + ')';
+                    shader.AddLine("emit_vertex(" + current + ");");
+                    regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1);
+                }
+                if (instr.out.cut) {
+                    shader.AddLine("EndPrimitive();");
+                }
+
+                break;
+            }
+            case OpCode::Id::MOV_SYS: {
+                switch (instr.sys20) {
+                case Tegra::Shader::SystemVariable::InvocationInfo: {
+                    LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
+                    regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1);
+                    break;
+                }
+                default: {
+                    LOG_CRITICAL(HW_GPU, "Unhandled system move: {}",
+                                 static_cast<u32>(instr.sys20.Value()));
+                    UNREACHABLE();
+                }
+                }
+                break;
+            }
+            case OpCode::Id::ISBERD: {
+                ASSERT(instr.isberd.o == 0);
+                ASSERT(instr.isberd.skew == 0);
+                ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None);
+                ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None);
+                ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
+                           "ISBERD is expected to be used in a geometry shader.");
+                LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete");
+                regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1);
+                break;
+            }
             case OpCode::Id::BRA: {
                 ASSERT_MSG(instr.bra.constant_buffer == 0,
                            "BRA with constant buffers are not implemented");
@@ -2907,7 +3083,7 @@ private:
 
     ShaderWriter shader;
     ShaderWriter declarations;
-    GLSLRegisterManager regs{shader, declarations, stage, suffix};
+    GLSLRegisterManager regs{shader, declarations, stage, suffix, header};
 
     // Declarations
     std::set<std::string> declr_predicates;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index b0466c18fbd91144e4830d7a747fe2b8b91d796e..1e5eb32df0a943ce50d91e8af2fcd9eb8d4b2ad5 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
     std::string out = "#version 430 core\n";
     out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
     out += Decompiler::GetCommonDeclarations();
-    out += "bool exec_vertex();\n";
+
+    out += R"(
+out gl_PerVertex {
+    vec4 gl_Position;
+};
+
+layout(std140) uniform vs_config {
+    vec4 viewport_flip;
+    uvec4 instance_id;
+    uvec4 flip_stage;
+};
+)";
 
     if (setup.IsDualProgram()) {
         out += "bool exec_vertex_b();\n";
@@ -28,18 +39,17 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
                                      Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
             .get_value_or({});
 
-    out += R"(
-
-out gl_PerVertex {
-    vec4 gl_Position;
-};
+    out += program.first;
 
-out vec4 position;
+    if (setup.IsDualProgram()) {
+        ProgramResult program_b =
+            Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
+                                         Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
+                .get_value_or({});
+        out += program_b.first;
+    }
 
-layout (std140) uniform vs_config {
-    vec4 viewport_flip;
-    uvec4 instance_id;
-};
+    out += R"(
 
 void main() {
     position = vec4(0.0, 0.0, 0.0, 0.0);
@@ -52,27 +62,52 @@ void main() {
 
     out += R"(
 
-    // Viewport can be flipped, which is unsupported by glViewport
-    position.xy *= viewport_flip.xy;
+    // Check if the flip stage is VertexB
+    if (flip_stage[0] == 1) {
+        // Viewport can be flipped, which is unsupported by glViewport
+        position.xy *= viewport_flip.xy;
+    }
     gl_Position = position;
 
     // TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0
     // For now, this is here to bring order in lieu of proper emulation
-    position.w = 1.0;
+    if (flip_stage[0] == 1) {
+        position.w = 1.0;
+    }
 }
 
 )";
 
-    out += program.first;
+    return {out, program.second};
+}
 
-    if (setup.IsDualProgram()) {
-        ProgramResult program_b =
-            Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
-                                         Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
-                .get_value_or({});
-        out += program_b.first;
-    }
+ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
+    std::string out = "#version 430 core\n";
+    out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+    out += Decompiler::GetCommonDeclarations();
+    out += "bool exec_geometry();\n";
+
+    ProgramResult program =
+        Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
+                                     Maxwell3D::Regs::ShaderStage::Geometry, "geometry")
+            .get_value_or({});
+    out += R"(
+out gl_PerVertex {
+    vec4 gl_Position;
+};
 
+layout (std140) uniform gs_config {
+    vec4 viewport_flip;
+    uvec4 instance_id;
+    uvec4 flip_stage;
+};
+
+void main() {
+    exec_geometry();
+}
+
+)";
+    out += program.first;
     return {out, program.second};
 }
 
@@ -87,7 +122,6 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
                                      Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
             .get_value_or({});
     out += R"(
-in vec4 position;
 layout(location = 0) out vec4 FragColor0;
 layout(location = 1) out vec4 FragColor1;
 layout(location = 2) out vec4 FragColor2;
@@ -100,6 +134,7 @@ layout(location = 7) out vec4 FragColor7;
 layout (std140) uniform fs_config {
     vec4 viewport_flip;
     uvec4 instance_id;
+    uvec4 flip_stage;
 };
 
 void main() {
@@ -110,5 +145,4 @@ void main() {
     out += program.first;
     return {out, program.second};
 }
-
-} // namespace OpenGL::GLShader
+} // namespace OpenGL::GLShader
\ No newline at end of file
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index e56f39e78805f1a33e3da5e798487edb2df1be52..79596087a622fdf06bca5d93100a194ea525a160 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -195,6 +195,12 @@ private:
  */
 ProgramResult GenerateVertexShader(const ShaderSetup& setup);
 
+/**
+ * Generates the GLSL geometry shader program source code for the given GS program
+ * @returns String of the shader source code
+ */
+ProgramResult GenerateGeometryShader(const ShaderSetup& setup);
+
 /**
  * Generates the GLSL fragment shader program source code for the given FS program
  * @returns String of the shader source code
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 022d32a86e175625f39ee7c741ae38d8ca455692..010857ec62a8ec94a60ccb3dacda46c6ab7e1518 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -18,6 +18,14 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
 
     // We only assign the instance to the first component of the vector, the rest is just padding.
     instance_id[0] = state.current_instance;
+
+    // Assign in which stage the position has to be flipped
+    // (the last stage before the fragment shader).
+    if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) {
+        flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
+    } else {
+        flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
+    }
 }
 
 } // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 3de15ba9bb3690b1e6861b3a3b788be7b9070029..b3a191cf2c6032fe9709b9e79cb9597ade7f587a 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -21,8 +21,9 @@ struct MaxwellUniformData {
     void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage);
     alignas(16) GLvec4 viewport_flip;
     alignas(16) GLuvec4 instance_id;
+    alignas(16) GLuvec4 flip_stage;
 };
-static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
+static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect");
 static_assert(sizeof(MaxwellUniformData) < 16384,
               "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
 
@@ -36,6 +37,10 @@ public:
         vs = program;
     }
 
+    void UseProgrammableGeometryShader(GLuint program) {
+        gs = program;
+    }
+
     void UseProgrammableFragmentShader(GLuint program) {
         fs = program;
     }
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index 681919ae3ff21f707beaac9eb6145ad6c45b41b8..237cc13070ede00f31a6e2f8ba7d307b7a563fc3 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -169,16 +169,20 @@ static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr,
     const std::string nice_addr = fmt::format("0x{:016x}", addr);
     std::string object_label;
 
-    switch (identifier) {
-    case GL_TEXTURE:
-        object_label = extra_info + "@" + nice_addr;
-        break;
-    case GL_PROGRAM:
-        object_label = "ShaderProgram@" + nice_addr;
-        break;
-    default:
-        object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
-        break;
+    if (extra_info.empty()) {
+        switch (identifier) {
+        case GL_TEXTURE:
+            object_label = "Texture@" + nice_addr;
+            break;
+        case GL_PROGRAM:
+            object_label = "Shader@" + nice_addr;
+            break;
+        default:
+            object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
+            break;
+        }
+    } else {
+        object_label = extra_info + '@' + nice_addr;
     }
     glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
 }