diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index 0a7d42dda2..d6562c842d 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -379,6 +379,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
     ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst);
 }
 
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
+    switch (ctx.stage) {
+    case Stage::TessellationControl:
+    case Stage::TessellationEval:
+        ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst);
+        break;
+    default:
+        LOG_WARNING(Shader, "(STUBBED) called");
+        ctx.Add("MOV.S {}.x,0x00ff0000;", inst);
+    }
+}
+
 void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {
     ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst);
 }
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
index d645fd532a..eaaf9ba39b 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
@@ -69,6 +69,7 @@ void EmitSetOFlag(EmitContext& ctx);
 void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
 void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
 void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
 void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
 void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
 void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
index 89603c1c45..333a91cc58 100644
--- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
+++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
@@ -95,6 +95,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
     if (info.uses_invocation_id) {
         Add("ATTRIB primitive_invocation=primitive.invocation;");
     }
+    if (info.uses_invocation_info &&
+        (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) {
+        Add("ATTRIB primitive_vertexcount = primitive.vertexcount;");
+    }
     if (info.stores_tess_level_outer) {
         Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};");
     }
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index d7c845469f..c1671c37be 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -399,6 +399,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
     ctx.AddU32("{}=uint(gl_InvocationID);", inst);
 }
 
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
+    switch (ctx.stage) {
+    case Stage::TessellationControl:
+    case Stage::TessellationEval:
+        ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst);
+        break;
+    default:
+        LOG_WARNING(Shader, "(STUBBED) called");
+        ctx.AddU32("{}=uint(0x00ff0000);", inst);
+    }
+}
+
 void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {
     ctx.AddU32("{}=uint(gl_SampleID);", inst);
 }
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
index 96e683b5e0..4151c89deb 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
@@ -83,6 +83,7 @@ void EmitSetOFlag(EmitContext& ctx);
 void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
 void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
 void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
 void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
 void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
 void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index a4751b42d0..5b3b5d1f3d 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -512,6 +512,18 @@ Id EmitInvocationId(EmitContext& ctx) {
     return ctx.OpLoad(ctx.U32[1], ctx.invocation_id);
 }
 
+Id EmitInvocationInfo(EmitContext& ctx) {
+    switch (ctx.stage) {
+    case Stage::TessellationControl:
+    case Stage::TessellationEval:
+        return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.patch_vertices_in),
+                                      ctx.Const(16u));
+    default:
+        LOG_WARNING(Shader, "(STUBBED) called");
+        return ctx.Const(0x00ff0000u);
+    }
+}
+
 Id EmitSampleId(EmitContext& ctx) {
     return ctx.OpLoad(ctx.U32[1], ctx.sample_id);
 }
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 7070c8fda7..e31cdc5e8c 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -72,6 +72,7 @@ void EmitSetOFlag(EmitContext& ctx);
 Id EmitWorkgroupId(EmitContext& ctx);
 Id EmitLocalInvocationId(EmitContext& ctx);
 Id EmitInvocationId(EmitContext& ctx);
+Id EmitInvocationInfo(EmitContext& ctx);
 Id EmitSampleId(EmitContext& ctx);
 Id EmitIsHelperInvocation(EmitContext& ctx);
 Id EmitYDirection(EmitContext& ctx);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index c26ad8f935..0bfc2dd89b 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1325,6 +1325,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {
     if (info.uses_invocation_id) {
         invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId);
     }
+    if (info.uses_invocation_info &&
+        (stage == Shader::Stage::TessellationControl || stage == Shader::Stage::TessellationEval)) {
+        patch_vertices_in = DefineInput(*this, U32[1], false, spv::BuiltIn::PatchVertices);
+    }
     if (info.uses_sample_id) {
         sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId);
     }
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index c86e509113..dde45b4bc4 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -204,6 +204,7 @@ public:
     Id workgroup_id{};
     Id local_invocation_id{};
     Id invocation_id{};
+    Id patch_vertices_in{};
     Id sample_id{};
     Id is_helper_invocation{};
     Id subgroup_local_invocation_id{};
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index d4425f06d9..0cdac0effd 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -362,6 +362,10 @@ U32 IREmitter::InvocationId() {
     return Inst<U32>(Opcode::InvocationId);
 }
 
+U32 IREmitter::InvocationInfo() {
+    return Inst<U32>(Opcode::InvocationInfo);
+}
+
 U32 IREmitter::SampleId() {
     return Inst<U32>(Opcode::SampleId);
 }
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index f163c18d94..2df992feb6 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -97,6 +97,7 @@ public:
     [[nodiscard]] U32 LocalInvocationIdZ();
 
     [[nodiscard]] U32 InvocationId();
+    [[nodiscard]] U32 InvocationInfo();
     [[nodiscard]] U32 SampleId();
     [[nodiscard]] U1 IsHelperInvocation();
     [[nodiscard]] F32 YDirection();
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc
index 88aa077ee8..1fe3749cc9 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.inc
+++ b/src/shader_recompiler/frontend/ir/opcodes.inc
@@ -59,6 +59,7 @@ OPCODE(SetOFlag,                                            Void,           U1,
 OPCODE(WorkgroupId,                                         U32x3,                                                                                          )
 OPCODE(LocalInvocationId,                                   U32x3,                                                                                          )
 OPCODE(InvocationId,                                        U32,                                                                                            )
+OPCODE(InvocationInfo,                                      U32,                                                                                            )
 OPCODE(SampleId,                                            U32,                                                                                            )
 OPCODE(IsHelperInvocation,                                  U1,                                                                                             )
 OPCODE(YDirection,                                          F32,                                                                                            )
diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h
index 1e37c8eb6b..5077e56c2c 100644
--- a/src/shader_recompiler/frontend/ir/patch.h
+++ b/src/shader_recompiler/frontend/ir/patch.h
@@ -14,8 +14,6 @@ enum class Patch : u64 {
     TessellationLodBottom,
     TessellationLodInteriorU,
     TessellationLodInteriorV,
-    ComponentPadding0,
-    ComponentPadding1,
     Component0,
     Component1,
     Component2,
@@ -137,7 +135,7 @@ enum class Patch : u64 {
     Component118,
     Component119,
 };
-static_assert(static_cast<u64>(Patch::Component119) == 127);
+static_assert(static_cast<u64>(Patch::Component119) == 125);
 
 [[nodiscard]] bool IsGeneric(Patch patch) noexcept;
 
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
index 52be12f9c5..753c620984 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
@@ -117,8 +117,7 @@ enum class SpecialRegister : u64 {
     case SpecialRegister::SR_THREAD_KILL:
         return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))};
     case SpecialRegister::SR_INVOCATION_INFO:
-        LOG_WARNING(Shader, "(STUBBED) SR_INVOCATION_INFO");
-        return ir.Imm32(0x00ff'0000);
+        return ir.InvocationInfo();
     case SpecialRegister::SR_TID: {
         const IR::Value tid{ir.LocalInvocationId()};
         return ir.BitFieldInsert(ir.BitFieldInsert(IR::U32{ir.CompositeExtract(tid, 0)},
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
index 7cff8ecdc4..5a41952175 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -468,6 +468,9 @@ void VisitUsages(Info& info, IR::Inst& inst) {
     case IR::Opcode::InvocationId:
         info.uses_invocation_id = true;
         break;
+    case IR::Opcode::InvocationInfo:
+        info.uses_invocation_info = true;
+        break;
     case IR::Opcode::SampleId:
         info.uses_sample_id = true;
         break;
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index f31e1f821f..ee6252bb51 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -127,6 +127,7 @@ struct Info {
     bool uses_workgroup_id{};
     bool uses_local_invocation_id{};
     bool uses_invocation_id{};
+    bool uses_invocation_info{};
     bool uses_sample_id{};
     bool uses_is_helper_invocation{};
     bool uses_subgroup_invocation_id{};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 4221c27747..3fe04a115d 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -76,7 +76,8 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
         }
         break;
     case Shader::Stage::TessellationEval:
-        info.tess_clockwise = key.tessellation_clockwise != 0;
+        // Flip the face, as OpenGL's drawing is flipped.
+        info.tess_clockwise = key.tessellation_clockwise == 0;
         info.tess_primitive = [&key] {
             switch (key.tessellation_primitive) {
             case Maxwell::Tessellation::DomainType::Isolines:
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index e216b90d97..d4b0a542ae 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -166,6 +166,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
         }
         break;
     case Shader::Stage::TessellationEval:
+        info.tess_clockwise = key.state.tessellation_clockwise != 0;
         info.tess_primitive = [&key] {
             const u32 raw{key.state.tessellation_primitive.Value()};
             switch (static_cast<Maxwell::Tessellation::DomainType>(raw)) {