diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index cffa4c9523..82f47d8a90 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(SRCS
             command_processor.cpp
             debug_utils/debug_utils.cpp
+            geometry_pipeline.cpp
             pica.cpp
             primitive_assembly.cpp
             regs.cpp
@@ -29,6 +30,7 @@ set(SRCS
 set(HEADERS
             command_processor.h
             debug_utils/debug_utils.h
+            geometry_pipeline.h
             gpu_debugger.h
             pica.h
             pica_state.h
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index f98ca33025..fb65a3a0a7 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -161,6 +161,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
     case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index):
         g_state.immediate.current_attribute = 0;
+        g_state.immediate.reset_geometry_pipeline = true;
         default_attr_counter = 0;
         break;
 
@@ -234,16 +235,14 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
                     shader_engine->Run(g_state.vs, shader_unit);
                     shader_unit.WriteOutput(regs.vs, output);
 
-                    // Send to renderer
-                    using Pica::Shader::OutputVertex;
-                    auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
-                                          const OutputVertex& v2) {
-                        VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
-                    };
-
-                    g_state.primitive_assembler.SubmitVertex(
-                        Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output),
-                        AddTriangle);
+                    // Send to geometry pipeline
+                    if (g_state.immediate.reset_geometry_pipeline) {
+                        g_state.geometry_pipeline.Reconfigure();
+                        g_state.immediate.reset_geometry_pipeline = false;
+                    }
+                    ASSERT(!g_state.geometry_pipeline.NeedIndexInput());
+                    g_state.geometry_pipeline.Setup(shader_engine);
+                    g_state.geometry_pipeline.SubmitVertex(output);
                 }
             }
         }
@@ -321,8 +320,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
         // The size has been tuned for optimal balance between hit-rate and the cost of lookup
         const size_t VERTEX_CACHE_SIZE = 32;
         std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
-        std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
-        Shader::OutputVertex output_vertex;
+        std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache;
+        Shader::AttributeBuffer vs_output;
 
         unsigned int vertex_cache_pos = 0;
         vertex_cache_ids.fill(-1);
@@ -332,6 +331,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
         shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
 
+        g_state.geometry_pipeline.Reconfigure();
+        g_state.geometry_pipeline.Setup(shader_engine);
+        if (g_state.geometry_pipeline.NeedIndexInput())
+            ASSERT(is_indexed);
+
         for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) {
             // Indexed rendering doesn't use the start offset
             unsigned int vertex =
@@ -345,6 +349,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
             bool vertex_cache_hit = false;
 
             if (is_indexed) {
+                if (g_state.geometry_pipeline.NeedIndexInput()) {
+                    g_state.geometry_pipeline.SubmitIndex(vertex);
+                    continue;
+                }
+
                 if (g_debug_context && Pica::g_debug_context->recorder) {
                     int size = index_u16 ? 2 : 1;
                     memory_accesses.AddAccess(base_address + index_info.offset + size * index,
@@ -353,7 +362,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
                 for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
                     if (vertex == vertex_cache_ids[i]) {
-                        output_vertex = vertex_cache[i];
+                        vs_output = vertex_cache[i];
                         vertex_cache_hit = true;
                         break;
                     }
@@ -362,7 +371,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
 
             if (!vertex_cache_hit) {
                 // Initialize data for the current vertex
-                Shader::AttributeBuffer input, output{};
+                Shader::AttributeBuffer input;
                 loader.LoadVertex(base_address, index, vertex, input, memory_accesses);
 
                 // Send to vertex shader
@@ -371,26 +380,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
                                              (void*)&input);
                 shader_unit.LoadInput(regs.vs, input);
                 shader_engine->Run(g_state.vs, shader_unit);
-                shader_unit.WriteOutput(regs.vs, output);
-
-                // Retrieve vertex from register data
-                output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output);
+                shader_unit.WriteOutput(regs.vs, vs_output);
 
                 if (is_indexed) {
-                    vertex_cache[vertex_cache_pos] = output_vertex;
+                    vertex_cache[vertex_cache_pos] = vs_output;
                     vertex_cache_ids[vertex_cache_pos] = vertex;
                     vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
                 }
             }
 
-            // Send to renderer
-            using Pica::Shader::OutputVertex;
-            auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
-                                  const OutputVertex& v2) {
-                VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
-            };
-
-            primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
+            // Send to geometry pipeline
+            g_state.geometry_pipeline.SubmitVertex(vs_output);
         }
 
         for (auto& range : memory_accesses.ranges) {
diff --git a/src/video_core/geometry_pipeline.cpp b/src/video_core/geometry_pipeline.cpp
new file mode 100644
index 0000000000..b146e2ecb1
--- /dev/null
+++ b/src/video_core/geometry_pipeline.cpp
@@ -0,0 +1,274 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/geometry_pipeline.h"
+#include "video_core/pica_state.h"
+#include "video_core/regs.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
+
+namespace Pica {
+
+/// An attribute buffering interface for different pipeline modes
+class GeometryPipelineBackend {
+public:
+    virtual ~GeometryPipelineBackend() = default;
+
+    /// Checks if there is no incomplete data transfer
+    virtual bool IsEmpty() const = 0;
+
+    /// Checks if the pipeline needs a direct input from index buffer
+    virtual bool NeedIndexInput() const = 0;
+
+    /// Submits an index from index buffer
+    virtual void SubmitIndex(unsigned int val) = 0;
+
+    /**
+     * Submits vertex attributes
+     * @param input attributes of a vertex output from vertex shader
+     * @return if the buffer is full and the geometry shader should be invoked
+     */
+    virtual bool SubmitVertex(const Shader::AttributeBuffer& input) = 0;
+};
+
+// In the Point mode, vertex attributes are sent to the input registers in the geometry shader unit.
+// The size of vertex shader outputs and geometry shader inputs are constants. Geometry shader is
+// invoked upon inputs buffer filled up by vertex shader outputs. For example, if we have a geometry
+// shader that takes 6 inputs, and the vertex shader outputs 2 attributes, it would take 3 vertices
+// for one geometry shader invocation.
+// TODO: what happens when the input size is not divisible by the output size?
+class GeometryPipeline_Point : public GeometryPipelineBackend {
+public:
+    GeometryPipeline_Point(const Regs& regs, Shader::GSUnitState& unit) : regs(regs), unit(unit) {
+        ASSERT(regs.pipeline.variable_primitive == 0);
+        ASSERT(regs.gs.input_to_uniform == 0);
+        vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+        size_t gs_input_num = regs.gs.max_input_attribute_index + 1;
+        ASSERT(gs_input_num % vs_output_num == 0);
+        buffer_cur = attribute_buffer.attr;
+        buffer_end = attribute_buffer.attr + gs_input_num;
+    }
+
+    bool IsEmpty() const override {
+        return buffer_cur == attribute_buffer.attr;
+    }
+
+    bool NeedIndexInput() const override {
+        return false;
+    }
+
+    void SubmitIndex(unsigned int val) override {
+        UNREACHABLE();
+    }
+
+    bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+        buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+        if (buffer_cur == buffer_end) {
+            buffer_cur = attribute_buffer.attr;
+            unit.LoadInput(regs.gs, attribute_buffer);
+            return true;
+        }
+        return false;
+    }
+
+private:
+    const Regs& regs;
+    Shader::GSUnitState& unit;
+    Shader::AttributeBuffer attribute_buffer;
+    Math::Vec4<float24>* buffer_cur;
+    Math::Vec4<float24>* buffer_end;
+    unsigned int vs_output_num;
+};
+
+// In VariablePrimitive mode, vertex attributes are buffered into the uniform registers in the
+// geometry shader unit. The number of vertex is variable, which is specified by the first index
+// value in the batch. This mode is usually used for subdivision.
+class GeometryPipeline_VariablePrimitive : public GeometryPipelineBackend {
+public:
+    GeometryPipeline_VariablePrimitive(const Regs& regs, Shader::ShaderSetup& setup)
+        : regs(regs), setup(setup) {
+        ASSERT(regs.pipeline.variable_primitive == 1);
+        ASSERT(regs.gs.input_to_uniform == 1);
+        vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+    }
+
+    bool IsEmpty() const override {
+        return need_index;
+    }
+
+    bool NeedIndexInput() const override {
+        return need_index;
+    }
+
+    void SubmitIndex(unsigned int val) override {
+        DEBUG_ASSERT(need_index);
+
+        // The number of vertex input is put to the uniform register
+        float24 vertex_num = float24::FromFloat32(val);
+        setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num);
+
+        // The second uniform register and so on are used for receiving input vertices
+        buffer_cur = setup.uniforms.f + 1;
+
+        main_vertex_num = regs.pipeline.variable_vertex_main_num_minus_1 + 1;
+        total_vertex_num = val;
+        need_index = false;
+    }
+
+    bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+        DEBUG_ASSERT(!need_index);
+        if (main_vertex_num != 0) {
+            // For main vertices, receive all attributes
+            buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+            --main_vertex_num;
+        } else {
+            // For other vertices, only receive the first attribute (usually the position)
+            *(buffer_cur++) = input.attr[0];
+        }
+        --total_vertex_num;
+
+        if (total_vertex_num == 0) {
+            need_index = true;
+            return true;
+        }
+
+        return false;
+    }
+
+private:
+    bool need_index = true;
+    const Regs& regs;
+    Shader::ShaderSetup& setup;
+    unsigned int main_vertex_num;
+    unsigned int total_vertex_num;
+    Math::Vec4<float24>* buffer_cur;
+    unsigned int vs_output_num;
+};
+
+// In FixedPrimitive mode, vertex attributes are buffered into the uniform registers in the geometry
+// shader unit. The number of vertex per shader invocation is constant. This is usually used for
+// particle system.
+class GeometryPipeline_FixedPrimitive : public GeometryPipelineBackend {
+public:
+    GeometryPipeline_FixedPrimitive(const Regs& regs, Shader::ShaderSetup& setup)
+        : regs(regs), setup(setup) {
+        ASSERT(regs.pipeline.variable_primitive == 0);
+        ASSERT(regs.gs.input_to_uniform == 1);
+        vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+        ASSERT(vs_output_num == regs.pipeline.gs_config.stride_minus_1 + 1);
+        size_t vertex_num = regs.pipeline.gs_config.fixed_vertex_num_minus_1 + 1;
+        buffer_cur = buffer_begin = setup.uniforms.f + regs.pipeline.gs_config.start_index;
+        buffer_end = buffer_begin + vs_output_num * vertex_num;
+    }
+
+    bool IsEmpty() const override {
+        return buffer_cur == buffer_begin;
+    }
+
+    bool NeedIndexInput() const override {
+        return false;
+    }
+
+    void SubmitIndex(unsigned int val) override {
+        UNREACHABLE();
+    }
+
+    bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+        buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+        if (buffer_cur == buffer_end) {
+            buffer_cur = buffer_begin;
+            return true;
+        }
+        return false;
+    }
+
+private:
+    const Regs& regs;
+    Shader::ShaderSetup& setup;
+    Math::Vec4<float24>* buffer_begin;
+    Math::Vec4<float24>* buffer_cur;
+    Math::Vec4<float24>* buffer_end;
+    unsigned int vs_output_num;
+};
+
+GeometryPipeline::GeometryPipeline(State& state) : state(state) {}
+
+GeometryPipeline::~GeometryPipeline() = default;
+
+void GeometryPipeline::SetVertexHandler(Shader::VertexHandler vertex_handler) {
+    this->vertex_handler = vertex_handler;
+}
+
+void GeometryPipeline::Setup(Shader::ShaderEngine* shader_engine) {
+    if (!backend)
+        return;
+
+    this->shader_engine = shader_engine;
+    shader_engine->SetupBatch(state.gs, state.regs.gs.main_offset);
+}
+
+void GeometryPipeline::Reconfigure() {
+    ASSERT(!backend || backend->IsEmpty());
+
+    if (state.regs.pipeline.use_gs == PipelineRegs::UseGS::No) {
+        backend = nullptr;
+        return;
+    }
+
+    ASSERT(state.regs.pipeline.use_gs == PipelineRegs::UseGS::Yes);
+
+    // The following assumes that when geometry shader is in use, the shader unit 3 is configured as
+    // a geometry shader unit.
+    // TODO: what happens if this is not true?
+    ASSERT(state.regs.pipeline.gs_unit_exclusive_configuration == 1);
+    ASSERT(state.regs.gs.shader_mode == ShaderRegs::ShaderMode::GS);
+
+    state.gs_unit.ConfigOutput(state.regs.gs);
+
+    ASSERT(state.regs.pipeline.vs_outmap_total_minus_1_a ==
+           state.regs.pipeline.vs_outmap_total_minus_1_b);
+
+    switch (state.regs.pipeline.gs_config.mode) {
+    case PipelineRegs::GSMode::Point:
+        backend = std::make_unique<GeometryPipeline_Point>(state.regs, state.gs_unit);
+        break;
+    case PipelineRegs::GSMode::VariablePrimitive:
+        backend = std::make_unique<GeometryPipeline_VariablePrimitive>(state.regs, state.gs);
+        break;
+    case PipelineRegs::GSMode::FixedPrimitive:
+        backend = std::make_unique<GeometryPipeline_FixedPrimitive>(state.regs, state.gs);
+        break;
+    default:
+        UNREACHABLE();
+    }
+}
+
+bool GeometryPipeline::NeedIndexInput() const {
+    if (!backend)
+        return false;
+    return backend->NeedIndexInput();
+}
+
+void GeometryPipeline::SubmitIndex(unsigned int val) {
+    backend->SubmitIndex(val);
+}
+
+void GeometryPipeline::SubmitVertex(const Shader::AttributeBuffer& input) {
+    if (!backend) {
+        // No backend means the geometry shader is disabled, so we send the vertex shader output
+        // directly to the primitive assembler.
+        vertex_handler(input);
+    } else {
+        if (backend->SubmitVertex(input)) {
+            shader_engine->Run(state.gs, state.gs_unit);
+
+            // The uniform b15 is set to true after every geometry shader invocation. This is useful
+            // for the shader to know if this is the first invocation in a batch, if the program set
+            // b15 to false first.
+            state.gs.uniforms.b[15] = true;
+        }
+    }
+}
+
+} // namespace Pica
diff --git a/src/video_core/geometry_pipeline.h b/src/video_core/geometry_pipeline.h
new file mode 100644
index 0000000000..91fdd31923
--- /dev/null
+++ b/src/video_core/geometry_pipeline.h
@@ -0,0 +1,49 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "video_core/shader/shader.h"
+
+namespace Pica {
+
+struct State;
+
+class GeometryPipelineBackend;
+
+/// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler
+class GeometryPipeline {
+public:
+    explicit GeometryPipeline(State& state);
+    ~GeometryPipeline();
+
+    /// Sets the handler for receiving vertex outputs from vertex shader
+    void SetVertexHandler(Shader::VertexHandler vertex_handler);
+
+    /**
+     * Setup the geometry shader unit if it is in use
+     * @param shader_engine the shader engine for the geometry shader to run
+     */
+    void Setup(Shader::ShaderEngine* shader_engine);
+
+    /// Reconfigures the pipeline according to current register settings
+    void Reconfigure();
+
+    /// Checks if the pipeline needs a direct input from index buffer
+    bool NeedIndexInput() const;
+
+    /// Submits an index from index buffer. Call this only when NeedIndexInput returns true
+    void SubmitIndex(unsigned int val);
+
+    /// Submits vertex attributes output from vertex shader
+    void SubmitVertex(const Shader::AttributeBuffer& input);
+
+private:
+    Shader::VertexHandler vertex_handler;
+    Shader::ShaderEngine* shader_engine;
+    std::unique_ptr<GeometryPipelineBackend> backend;
+    State& state;
+};
+} // namespace Pica
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp
index b95148a6a4..218e06883d 100644
--- a/src/video_core/pica.cpp
+++ b/src/video_core/pica.cpp
@@ -3,9 +3,11 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "video_core/geometry_pipeline.h"
 #include "video_core/pica.h"
 #include "video_core/pica_state.h"
-#include "video_core/regs_pipeline.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
 
 namespace Pica {
 
@@ -24,6 +26,23 @@ void Zero(T& o) {
     memset(&o, 0, sizeof(o));
 }
 
+State::State() : geometry_pipeline(*this) {
+    auto SubmitVertex = [this](const Shader::AttributeBuffer& vertex) {
+        using Pica::Shader::OutputVertex;
+        auto AddTriangle = [this](const OutputVertex& v0, const OutputVertex& v1,
+                                  const OutputVertex& v2) {
+            VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
+        };
+        primitive_assembler.SubmitVertex(
+            Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, vertex), AddTriangle);
+    };
+
+    auto SetWinding = [this]() { primitive_assembler.SetWinding(); };
+
+    g_state.gs_unit.SetVertexHandler(SubmitVertex, SetWinding);
+    g_state.geometry_pipeline.SetVertexHandler(SubmitVertex);
+}
+
 void State::Reset() {
     Zero(regs);
     Zero(vs);
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index 864a2c9e60..c6634a0bc3 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -8,6 +8,7 @@
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/vector_math.h"
+#include "video_core/geometry_pipeline.h"
 #include "video_core/primitive_assembly.h"
 #include "video_core/regs.h"
 #include "video_core/shader/shader.h"
@@ -16,6 +17,7 @@ namespace Pica {
 
 /// Struct used to describe current Pica state
 struct State {
+    State();
     void Reset();
 
     /// Pica registers
@@ -137,8 +139,17 @@ struct State {
         Shader::AttributeBuffer input_vertex;
         // Index of the next attribute to be loaded into `input_vertex`.
         u32 current_attribute = 0;
+        // Indicates the immediate mode just started and the geometry pipeline needs to reconfigure
+        bool reset_geometry_pipeline = true;
     } immediate;
 
+    // the geometry shader needs to be kept in the global state because some shaders relie on
+    // preserved register value across shader invocation.
+    // TODO: also bring the three vertex shader units here and implement the shader scheduler.
+    Shader::GSUnitState gs_unit;
+
+    GeometryPipeline geometry_pipeline;
+
     // This is constructed with a dummy triangle topology
     PrimitiveAssembler<Shader::OutputVertex> primitive_assembler;
 };
diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp
index acd2ac5e23..9c3dd4cabd 100644
--- a/src/video_core/primitive_assembly.cpp
+++ b/src/video_core/primitive_assembly.cpp
@@ -17,15 +17,18 @@ template <typename VertexType>
 void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
                                                   TriangleHandler triangle_handler) {
     switch (topology) {
-    // TODO: Figure out what's different with TriangleTopology::Shader.
     case PipelineRegs::TriangleTopology::List:
     case PipelineRegs::TriangleTopology::Shader:
         if (buffer_index < 2) {
             buffer[buffer_index++] = vtx;
         } else {
             buffer_index = 0;
-
-            triangle_handler(buffer[0], buffer[1], vtx);
+            if (topology == PipelineRegs::TriangleTopology::Shader && winding) {
+                triangle_handler(buffer[1], buffer[0], vtx);
+                winding = false;
+            } else {
+                triangle_handler(buffer[0], buffer[1], vtx);
+            }
         }
         break;
 
@@ -50,10 +53,16 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
     }
 }
 
+template <typename VertexType>
+void PrimitiveAssembler<VertexType>::SetWinding() {
+    winding = true;
+}
+
 template <typename VertexType>
 void PrimitiveAssembler<VertexType>::Reset() {
     buffer_index = 0;
     strip_ready = false;
+    winding = false;
 }
 
 template <typename VertexType>
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h
index e8eccdf276..12de8e3b96 100644
--- a/src/video_core/primitive_assembly.h
+++ b/src/video_core/primitive_assembly.h
@@ -29,6 +29,12 @@ struct PrimitiveAssembler {
      */
     void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler);
 
+    /**
+     * Invert the vertex order of the next triangle. Called by geometry shader emitter.
+     * This only takes effect for TriangleTopology::Shader.
+     */
+    void SetWinding();
+
     /**
      * Resets the internal state of the PrimitiveAssembler.
      */
@@ -45,6 +51,7 @@ private:
     int buffer_index;
     VertexType buffer[2];
     bool strip_ready = false;
+    bool winding = false;
 };
 
 } // namespace
diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h
index 8b63692979..e78c3e3317 100644
--- a/src/video_core/regs_pipeline.h
+++ b/src/video_core/regs_pipeline.h
@@ -147,7 +147,15 @@ struct PipelineRegs {
     // Number of vertices to render
     u32 num_vertices;
 
-    INSERT_PADDING_WORDS(0x1);
+    enum class UseGS : u32 {
+        No = 0,
+        Yes = 2,
+    };
+
+    union {
+        BitField<0, 2, UseGS> use_gs;
+        BitField<31, 1, u32> variable_primitive;
+    };
 
     // The index of the first vertex to render
     u32 vertex_offset;
@@ -218,7 +226,29 @@ struct PipelineRegs {
 
     GPUMode gpu_mode;
 
-    INSERT_PADDING_WORDS(0x18);
+    INSERT_PADDING_WORDS(0x4);
+    BitField<0, 4, u32> vs_outmap_total_minus_1_a;
+    INSERT_PADDING_WORDS(0x6);
+    BitField<0, 4, u32> vs_outmap_total_minus_1_b;
+
+    enum class GSMode : u32 {
+        Point = 0,
+        VariablePrimitive = 1,
+        FixedPrimitive = 2,
+    };
+
+    union {
+        BitField<0, 8, GSMode> mode;
+        BitField<8, 4, u32> fixed_vertex_num_minus_1;
+        BitField<12, 4, u32> stride_minus_1;
+        BitField<16, 4, u32> start_index;
+    } gs_config;
+
+    INSERT_PADDING_WORDS(0x1);
+
+    u32 variable_vertex_main_num_minus_1;
+
+    INSERT_PADDING_WORDS(0x9);
 
     enum class TriangleTopology : u32 {
         List = 0,
diff --git a/src/video_core/regs_shader.h b/src/video_core/regs_shader.h
index ddb1ee4515..c15d4d1623 100644
--- a/src/video_core/regs_shader.h
+++ b/src/video_core/regs_shader.h
@@ -24,9 +24,16 @@ struct ShaderRegs {
 
     INSERT_PADDING_WORDS(0x4);
 
+    enum ShaderMode {
+        GS = 0x08,
+        VS = 0xA0,
+    };
+
     union {
         // Number of input attributes to shader unit - 1
         BitField<0, 4, u32> max_input_attribute_index;
+        BitField<8, 8, u32> input_to_uniform;
+        BitField<24, 8, ShaderMode> shader_mode;
     };
 
     // Offset to shader program entry point (in words)
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp
index 67ed19ba8d..e9063e6164 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -21,7 +21,8 @@ namespace Pica {
 
 namespace Shader {
 
-OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) {
+OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
+                                               const AttributeBuffer& input) {
     // Setup output data
     union {
         OutputVertex ret{};
@@ -82,6 +83,44 @@ void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) {
     }
 }
 
+UnitState::UnitState(GSEmitter* emitter) : emitter_ptr(emitter) {}
+
+GSEmitter::GSEmitter() {
+    handlers = new Handlers;
+}
+
+GSEmitter::~GSEmitter() {
+    delete handlers;
+}
+
+void GSEmitter::Emit(Math::Vec4<float24> (&vertex)[16]) {
+    ASSERT(vertex_id < 3);
+    std::copy(std::begin(vertex), std::end(vertex), buffer[vertex_id].begin());
+    if (prim_emit) {
+        if (winding)
+            handlers->winding_setter();
+        for (size_t i = 0; i < buffer.size(); ++i) {
+            AttributeBuffer output;
+            unsigned int output_i = 0;
+            for (unsigned int reg : Common::BitSet<u32>(output_mask)) {
+                output.attr[output_i++] = buffer[i][reg];
+            }
+            handlers->vertex_handler(output);
+        }
+    }
+}
+
+GSUnitState::GSUnitState() : UnitState(&emitter) {}
+
+void GSUnitState::SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter) {
+    emitter.handlers->vertex_handler = std::move(vertex_handler);
+    emitter.handlers->winding_setter = std::move(winding_setter);
+}
+
+void GSUnitState::ConfigOutput(const ShaderRegs& config) {
+    emitter.output_mask = config.output_mask;
+}
+
 MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
 
 #ifdef ARCHITECTURE_x86_64
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index e156f6aef0..a3789da012 100644
--- a/src/video_core/shader/shader.h
+++ b/src/video_core/shader/shader.h
@@ -6,6 +6,7 @@
 
 #include <array>
 #include <cstddef>
+#include <functional>
 #include <type_traits>
 #include <nihstro/shader_bytecode.h>
 #include "common/assert.h"
@@ -31,6 +32,12 @@ struct AttributeBuffer {
     alignas(16) Math::Vec4<float24> attr[16];
 };
 
+/// Handler type for receiving vertex outputs from vertex shader or geometry shader
+using VertexHandler = std::function<void(const AttributeBuffer&)>;
+
+/// Handler type for signaling to invert the vertex order of the next triangle
+using WindingSetter = std::function<void()>;
+
 struct OutputVertex {
     Math::Vec4<float24> pos;
     Math::Vec4<float24> quat;
@@ -43,7 +50,8 @@ struct OutputVertex {
     INSERT_PADDING_WORDS(1);
     Math::Vec2<float24> tc2;
 
-    static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output);
+    static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
+                                            const AttributeBuffer& output);
 };
 #define ASSERT_POS(var, pos)                                                                       \
     static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong "       \
@@ -60,6 +68,29 @@ ASSERT_POS(tc2, RasterizerRegs::VSOutputAttributes::TEXCOORD2_U);
 static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
 static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size");
 
+/**
+ * This structure contains state information for primitive emitting in geometry shader.
+ */
+struct GSEmitter {
+    std::array<std::array<Math::Vec4<float24>, 16>, 3> buffer;
+    u8 vertex_id;
+    bool prim_emit;
+    bool winding;
+    u32 output_mask;
+
+    // Function objects are hidden behind a raw pointer to make the structure standard layout type,
+    // for JIT to use offsetof to access other members.
+    struct Handlers {
+        VertexHandler vertex_handler;
+        WindingSetter winding_setter;
+    } * handlers;
+
+    GSEmitter();
+    ~GSEmitter();
+    void Emit(Math::Vec4<float24> (&vertex)[16]);
+};
+static_assert(std::is_standard_layout<GSEmitter>::value, "GSEmitter is not standard layout type");
+
 /**
  * This structure contains the state information that needs to be unique for a shader unit. The 3DS
  * has four shader units that process shaders in parallel. At the present, Citra only implements a
@@ -67,6 +98,7 @@ static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has inva
  * here will make it easier for us to parallelize the shader processing later.
  */
 struct UnitState {
+    explicit UnitState(GSEmitter* emitter = nullptr);
     struct Registers {
         // The registers are accessed by the shader JIT using SSE instructions, and are therefore
         // required to be 16-byte aligned.
@@ -82,6 +114,8 @@ struct UnitState {
     // TODO: How many bits do these actually have?
     s32 address_registers[3];
 
+    GSEmitter* emitter_ptr;
+
     static size_t InputOffset(const SourceRegister& reg) {
         switch (reg.GetRegisterType()) {
         case RegisterType::Input:
@@ -125,6 +159,19 @@ struct UnitState {
     void WriteOutput(const ShaderRegs& config, AttributeBuffer& output);
 };
 
+/**
+ * This is an extended shader unit state that represents the special unit that can run both vertex
+ * shader and geometry shader. It contains an additional primitive emitter and utilities for
+ * geometry shader.
+ */
+struct GSUnitState : public UnitState {
+    GSUnitState();
+    void SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter);
+    void ConfigOutput(const ShaderRegs& config);
+
+    GSEmitter emitter;
+};
+
 struct ShaderSetup {
     struct {
         // The float uniforms are accessed by the shader JIT using SSE instructions, and are
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp
index 206c0978ad..9d4da4904a 100644
--- a/src/video_core/shader/shader_interpreter.cpp
+++ b/src/video_core/shader/shader_interpreter.cpp
@@ -636,6 +636,22 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData
                 break;
             }
 
+            case OpCode::Id::EMIT: {
+                GSEmitter* emitter = state.emitter_ptr;
+                ASSERT_MSG(emitter, "Execute EMIT on VS");
+                emitter->Emit(state.registers.output);
+                break;
+            }
+
+            case OpCode::Id::SETEMIT: {
+                GSEmitter* emitter = state.emitter_ptr;
+                ASSERT_MSG(emitter, "Execute SETEMIT on VS");
+                emitter->vertex_id = instr.setemit.vertex_id;
+                emitter->prim_emit = instr.setemit.prim_emit != 0;
+                emitter->winding = instr.setemit.winding != 0;
+                break;
+            }
+
             default:
                 LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x",
                           (int)instr.opcode.Value().EffectiveOpCode(),
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp
index 42a57aab19..1b31623bd5 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.cpp
+++ b/src/video_core/shader/shader_jit_x64_compiler.cpp
@@ -75,8 +75,8 @@ const JitFunction instr_table[64] = {
     &JitShader::Compile_IF,    // ifu
     &JitShader::Compile_IF,    // ifc
     &JitShader::Compile_LOOP,  // loop
-    nullptr,                   // emit
-    nullptr,                   // sete
+    &JitShader::Compile_EMIT,  // emit
+    &JitShader::Compile_SETE,  // sete
     &JitShader::Compile_JMP,   // jmpc
     &JitShader::Compile_JMP,   // jmpu
     &JitShader::Compile_CMP,   // cmp
@@ -772,6 +772,51 @@ void JitShader::Compile_JMP(Instruction instr) {
     }
 }
 
+static void Emit(GSEmitter* emitter, Math::Vec4<float24> (*output)[16]) {
+    emitter->Emit(*output);
+}
+
+void JitShader::Compile_EMIT(Instruction instr) {
+    Label have_emitter, end;
+    mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
+    test(rax, rax);
+    jnz(have_emitter);
+
+    ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute EMIT on VS"));
+    CallFarFunction(*this, LogCritical);
+    ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    jmp(end);
+
+    L(have_emitter);
+    ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    mov(ABI_PARAM1, rax);
+    mov(ABI_PARAM2, STATE);
+    add(ABI_PARAM2, static_cast<Xbyak::uint32>(offsetof(UnitState, registers.output)));
+    CallFarFunction(*this, Emit);
+    ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    L(end);
+}
+
+void JitShader::Compile_SETE(Instruction instr) {
+    Label have_emitter, end;
+    mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
+    test(rax, rax);
+    jnz(have_emitter);
+
+    ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute SETEMIT on VS"));
+    CallFarFunction(*this, LogCritical);
+    ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+    jmp(end);
+
+    L(have_emitter);
+    mov(byte[rax + offsetof(GSEmitter, vertex_id)], instr.setemit.vertex_id);
+    mov(byte[rax + offsetof(GSEmitter, prim_emit)], instr.setemit.prim_emit);
+    mov(byte[rax + offsetof(GSEmitter, winding)], instr.setemit.winding);
+    L(end);
+}
+
 void JitShader::Compile_Block(unsigned end) {
     while (program_counter < end) {
         Compile_NextInstr();
diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h
index 31af0ca484..4aee56b1d5 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.h
+++ b/src/video_core/shader/shader_jit_x64_compiler.h
@@ -66,6 +66,8 @@ public:
     void Compile_JMP(Instruction instr);
     void Compile_CMP(Instruction instr);
     void Compile_MAD(Instruction instr);
+    void Compile_EMIT(Instruction instr);
+    void Compile_SETE(Instruction instr);
 
 private:
     void Compile_Block(unsigned end);