From 7dafa96ab59892b7f1fbffdb61e4326e6443955f Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 24 Jun 2021 02:41:09 -0300
Subject: shader: Rework varyings and implement passthrough geometry shaders

Put all varyings into a single std::bitset with helpers to access it.

Implement passthrough geometry shaders using host's.
---
 .../backend/glasm/emit_context.cpp                 | 15 ++--
 src/shader_recompiler/backend/glasm/emit_glasm.cpp |  6 +-
 .../backend/glasm/emit_glasm_context_get_set.cpp   |  6 +-
 .../backend/glsl/emit_context.cpp                  | 58 +++++++------
 src/shader_recompiler/backend/glsl/emit_glsl.cpp   |  2 +-
 .../backend/glsl/emit_glsl_context_get_set.cpp     |  2 +-
 .../backend/glsl/emit_glsl_special.cpp             |  4 +-
 .../backend/spirv/emit_context.cpp                 | 97 ++++++++++++----------
 src/shader_recompiler/backend/spirv/emit_context.h |  2 +-
 src/shader_recompiler/backend/spirv/emit_spirv.cpp | 19 +++--
 .../backend/spirv/emit_spirv_context_get_set.cpp   |  2 +-
 11 files changed, 116 insertions(+), 97 deletions(-)

(limited to 'src/shader_recompiler/backend')

diff --git a/src/shader_recompiler/backend/glasm/emit_context.cpp b/src/shader_recompiler/backend/glasm/emit_context.cpp
index 21e14867c2..80dad9ff34 100644
--- a/src/shader_recompiler/backend/glasm/emit_context.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_context.cpp
@@ -83,14 +83,13 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
         break;
     }
     const std::string_view attr_stage{stage == Stage::Fragment ? "fragment" : "vertex"};
-    for (size_t index = 0; index < info.input_generics.size(); ++index) {
-        const auto& generic{info.input_generics[index]};
-        if (generic.used) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (info.loads.Generic(index)) {
             Add("{}ATTRIB in_attr{}[]={{{}.attrib[{}..{}]}};",
-                InterpDecorator(generic.interpolation), index, attr_stage, index, index);
+                InterpDecorator(info.interpolation[index]), index, attr_stage, index, index);
         }
     }
-    if (IsInputArray(stage) && info.loads_position) {
+    if (IsInputArray(stage) && info.loads.AnyComponent(IR::Attribute::PositionX)) {
         Add("ATTRIB vertex_position=vertex.position;");
     }
     if (info.uses_invocation_id) {
@@ -102,7 +101,7 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
     if (info.stores_tess_level_inner) {
         Add("OUTPUT result_patch_tessinner[]={{result.patch.tessinner[0..1]}};");
     }
-    if (info.stores_clip_distance) {
+    if (info.stores.ClipDistances()) {
         Add("OUTPUT result_clip[]={{result.clip[0..7]}};");
     }
     for (size_t index = 0; index < info.uses_patches.size(); ++index) {
@@ -124,8 +123,8 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
             Add("OUTPUT frag_color{}=result.color[{}];", index, index);
         }
     }
-    for (size_t index = 0; index < info.stores_generics.size(); ++index) {
-        if (info.stores_generics[index]) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (info.stores.Generic(index)) {
             Add("OUTPUT out_attr{}[]={{result.attrib[{}..{}]}};", index, index, index);
         }
     }
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 79314f1301..2b96977b36 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -296,8 +296,10 @@ void SetupOptions(const IR::Program& program, const Profile& profile,
     if (info.uses_sparse_residency) {
         header += "OPTION EXT_sparse_texture2;";
     }
-    if (((info.stores_viewport_index || info.stores_layer) && stage != Stage::Geometry) ||
-        info.stores_viewport_mask) {
+    const bool stores_viewport_layer{info.stores[IR::Attribute::ViewportIndex] ||
+                                     info.stores[IR::Attribute::Layer]};
+    if ((stage != Stage::Geometry && stores_viewport_layer) ||
+        info.stores[IR::Attribute::ViewportMask]) {
         if (profile.support_viewport_index_layer_non_geometry) {
             header += "OPTION NV_viewport_array2;";
         }
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 bc195d248c..02c9dc6d79 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
@@ -261,7 +261,7 @@ void EmitGetAttributeIndexed(EmitContext& ctx, IR::Inst& inst, ScalarS32 offset,
                                 fmt::format("{}.z", value), fmt::format("{}.w", value)};
         read(compare_index, values);
     }};
-    if (ctx.info.loads_position) {
+    if (ctx.info.loads.AnyComponent(IR::Attribute::PositionX)) {
         const u32 index{static_cast<u32>(IR::Attribute::PositionX)};
         if (IsInputArray(ctx.stage)) {
             read_swizzled(index, fmt::format("vertex_position{}", VertexIndex(ctx, vertex)));
@@ -269,8 +269,8 @@ void EmitGetAttributeIndexed(EmitContext& ctx, IR::Inst& inst, ScalarS32 offset,
             read_swizzled(index, fmt::format("{}.position", ctx.attrib_name));
         }
     }
-    for (u32 index = 0; index < ctx.info.input_generics.size(); ++index) {
-        if (!ctx.info.input_generics[index].used) {
+    for (u32 index = 0; index < static_cast<u32>(IR::NUM_GENERICS); ++index) {
+        if (!ctx.info.loads.Generic(index)) {
             continue;
         }
         read_swizzled(index, fmt::format("in_attr{}{}[0]", index, VertexIndex(ctx, vertex)));
diff --git a/src/shader_recompiler/backend/glsl/emit_context.cpp b/src/shader_recompiler/backend/glsl/emit_context.cpp
index 14c0095359..0d7f7bc3ba 100644
--- a/src/shader_recompiler/backend/glsl/emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_context.cpp
@@ -212,22 +212,22 @@ std::string_view OutputPrimitive(OutputTopology topology) {
 }
 
 void SetupLegacyOutPerVertex(EmitContext& ctx, std::string& header) {
-    if (!ctx.info.stores_legacy_varyings) {
+    if (!ctx.info.stores.Legacy()) {
         return;
     }
-    if (ctx.info.stores_fixed_fnc_textures) {
+    if (ctx.info.stores.FixedFunctionTexture()) {
         header += "vec4 gl_TexCoord[8];";
     }
-    if (ctx.info.stores_color_front_diffuse) {
+    if (ctx.info.stores.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) {
         header += "vec4 gl_FrontColor;";
     }
-    if (ctx.info.stores_color_front_specular) {
+    if (ctx.info.stores.AnyComponent(IR::Attribute::ColorFrontSpecularR)) {
         header += "vec4 gl_FrontSecondaryColor;";
     }
-    if (ctx.info.stores_color_back_diffuse) {
+    if (ctx.info.stores.AnyComponent(IR::Attribute::ColorBackDiffuseR)) {
         header += "vec4 gl_BackColor;";
     }
-    if (ctx.info.stores_color_back_specular) {
+    if (ctx.info.stores.AnyComponent(IR::Attribute::ColorBackSpecularR)) {
         header += "vec4 gl_BackSecondaryColor;";
     }
 }
@@ -237,32 +237,32 @@ void SetupOutPerVertex(EmitContext& ctx, std::string& header) {
         return;
     }
     header += "out gl_PerVertex{vec4 gl_Position;";
-    if (ctx.info.stores_point_size) {
+    if (ctx.info.stores[IR::Attribute::PointSize]) {
         header += "float gl_PointSize;";
     }
-    if (ctx.info.stores_clip_distance) {
+    if (ctx.info.stores.ClipDistances()) {
         header += "float gl_ClipDistance[];";
     }
-    if (ctx.info.stores_viewport_index && ctx.profile.support_viewport_index_layer_non_geometry &&
-        ctx.stage != Stage::Geometry) {
+    if (ctx.info.stores[IR::Attribute::ViewportIndex] &&
+        ctx.profile.support_viewport_index_layer_non_geometry && ctx.stage != Stage::Geometry) {
         header += "int gl_ViewportIndex;";
     }
     SetupLegacyOutPerVertex(ctx, header);
     header += "};";
-    if (ctx.info.stores_viewport_index && ctx.stage == Stage::Geometry) {
+    if (ctx.info.stores[IR::Attribute::ViewportIndex] && ctx.stage == Stage::Geometry) {
         header += "out int gl_ViewportIndex;";
     }
 }
 
 void SetupLegacyInPerFragment(EmitContext& ctx, std::string& header) {
-    if (!ctx.info.loads_legacy_varyings) {
+    if (!ctx.info.loads.Legacy()) {
         return;
     }
     header += "in gl_PerFragment{";
-    if (ctx.info.loads_fixed_fnc_textures) {
+    if (ctx.info.loads.FixedFunctionTexture()) {
         header += "vec4 gl_TexCoord[8];";
     }
-    if (ctx.info.loads_color_front_diffuse) {
+    if (ctx.info.loads.AnyComponent(IR::Attribute::ColorFrontDiffuseR)) {
         header += "vec4 gl_Color;";
     }
     header += "};";
@@ -325,14 +325,13 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
     SetupOutPerVertex(*this, header);
     SetupLegacyInPerFragment(*this, header);
 
-    for (size_t index = 0; index < info.input_generics.size(); ++index) {
-        const auto& generic{info.input_generics[index]};
-        if (!generic.used || !runtime_info.previous_stage_stores_generic[index]) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (!info.loads.Generic(index) || !runtime_info.previous_stage_stores.Generic(index)) {
             continue;
         }
-        header +=
-            fmt::format("layout(location={}){}in vec4 in_attr{}{};", index,
-                        InterpDecorator(generic.interpolation), index, InputArrayDecorator(stage));
+        header += fmt::format("layout(location={}){}in vec4 in_attr{}{};", index,
+                              InterpDecorator(info.interpolation[index]), index,
+                              InputArrayDecorator(stage));
     }
     for (size_t index = 0; index < info.uses_patches.size(); ++index) {
         if (!info.uses_patches[index]) {
@@ -349,11 +348,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
             header += fmt::format("layout(location={})out vec4 frag_color{};", index, index);
         }
     }
-    for (size_t index = 0; index < info.stores_generics.size(); ++index) {
-        if (!info.stores_generics[index]) {
-            continue;
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (info.stores.Generic(index)) {
+            DefineGenericOutput(index, program.invocations);
         }
-        DefineGenericOutput(index, program.invocations);
     }
     DefineConstantBuffers(bindings);
     DefineStorageBuffers(bindings);
@@ -398,14 +396,14 @@ void EmitContext::SetupExtensions() {
             header += "#extension GL_NV_shader_thread_shuffle : enable\n";
         }
     }
-    if ((info.stores_viewport_index || info.stores_layer) &&
+    if ((info.stores[IR::Attribute::ViewportIndex] || info.stores[IR::Attribute::Layer]) &&
         profile.support_viewport_index_layer_non_geometry && stage != Stage::Geometry) {
         header += "#extension GL_ARB_shader_viewport_layer_array : enable\n";
     }
     if (info.uses_sparse_residency && profile.support_gl_sparse_textures) {
         header += "#extension GL_ARB_sparse_texture2 : enable\n";
     }
-    if (info.stores_viewport_mask && profile.support_viewport_mask) {
+    if (info.stores[IR::Attribute::ViewportMask] && profile.support_viewport_mask) {
         header += "#extension GL_NV_viewport_array2 : enable\n";
     }
     if (info.uses_typeless_image_reads) {
@@ -535,20 +533,20 @@ void EmitContext::DefineHelperFunctions() {
             fmt::format("float IndexedAttrLoad(int offset{}){{int base_index=offset>>2;uint "
                         "masked_index=uint(base_index)&3u;switch(base_index>>2){{",
                         vertex_arg)};
-        if (info.loads_position) {
+        if (info.loads.AnyComponent(IR::Attribute::PositionX)) {
             const auto position_idx{is_array ? "gl_in[vertex]." : ""};
             func += fmt::format("case {}:return {}{}[masked_index];",
                                 static_cast<u32>(IR::Attribute::PositionX) >> 2, position_idx,
                                 position_name);
         }
         const u32 base_attribute_value = static_cast<u32>(IR::Attribute::Generic0X) >> 2;
-        for (u32 i = 0; i < info.input_generics.size(); ++i) {
-            if (!info.input_generics[i].used) {
+        for (u32 index = 0; index < IR::NUM_GENERICS; ++index) {
+            if (!info.loads.Generic(index)) {
                 continue;
             }
             const auto vertex_idx{is_array ? "[vertex]" : ""};
             func += fmt::format("case {}:return in_attr{}{}[masked_index];",
-                                base_attribute_value + i, i, vertex_idx);
+                                base_attribute_value + index, index, vertex_idx);
         }
         func += "default: return 0.0;}}";
         header += func;
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index 32c4f1da21..8deaf5760f 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -171,7 +171,7 @@ void EmitCode(EmitContext& ctx, const IR::Program& program) {
 }
 
 std::string GlslVersionSpecifier(const EmitContext& ctx) {
-    if (ctx.uses_y_direction || ctx.info.stores_legacy_varyings || ctx.info.loads_legacy_varyings) {
+    if (ctx.uses_y_direction || ctx.info.stores.Legacy() || ctx.info.loads.Legacy()) {
         return " compatibility";
     }
     return "";
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 3d2ba2eeec..16e2a8502e 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
@@ -179,7 +179,7 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr,
     const char swizzle{"xyzw"[element]};
     if (IR::IsGeneric(attr)) {
         const u32 index{IR::GenericAttributeIndex(attr)};
-        if (!ctx.runtime_info.previous_stage_stores_generic[index]) {
+        if (!ctx.runtime_info.previous_stage_stores.Generic(index)) {
             ctx.AddF32("{}=0.f;", inst, attr);
             return;
         }
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
index 6420aaa219..298881c7bb 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_special.cpp
@@ -20,8 +20,8 @@ void InitializeOutputVaryings(EmitContext& ctx) {
     if (ctx.stage == Stage::VertexB || ctx.stage == Stage::Geometry) {
         ctx.Add("gl_Position=vec4(0,0,0,1);");
     }
-    for (size_t index = 0; index < ctx.info.stores_generics.size(); ++index) {
-        if (!ctx.info.stores_generics[index]) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (!ctx.info.stores.Generic(index)) {
             continue;
         }
         const auto& info_array{ctx.output_generics.at(index)};
diff --git a/src/shader_recompiler/backend/spirv/emit_context.cpp b/src/shader_recompiler/backend/spirv/emit_context.cpp
index 4c6501129e..af4fb0c69b 100644
--- a/src/shader_recompiler/backend/spirv/emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_context.cpp
@@ -557,7 +557,7 @@ void EmitContext::DefineCommonConstants() {
 }
 
 void EmitContext::DefineInterfaces(const IR::Program& program) {
-    DefineInputs(program.info);
+    DefineInputs(program);
     DefineOutputs(program);
 }
 
@@ -693,16 +693,16 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
         const Id compare_index{OpShiftRightArithmetic(U32[1], base_index, Const(2U))};
         std::vector<Sirit::Literal> literals;
         std::vector<Id> labels;
-        if (info.loads_position) {
+        if (info.loads.AnyComponent(IR::Attribute::PositionX)) {
             literals.push_back(static_cast<u32>(IR::Attribute::PositionX) >> 2);
             labels.push_back(OpLabel());
         }
         const u32 base_attribute_value = static_cast<u32>(IR::Attribute::Generic0X) >> 2;
-        for (u32 i = 0; i < info.input_generics.size(); ++i) {
-            if (!info.input_generics[i].used) {
+        for (u32 index = 0; index < static_cast<u32>(IR::NUM_GENERICS); ++index) {
+            if (!info.loads.Generic(index)) {
                 continue;
             }
-            literals.push_back(base_attribute_value + i);
+            literals.push_back(base_attribute_value + index);
             labels.push_back(OpLabel());
         }
         OpSelectionMerge(end_block, spv::SelectionControlMask::MaskNone);
@@ -710,7 +710,7 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
         AddLabel(default_label);
         OpReturnValue(Const(0.0f));
         size_t label_index{0};
-        if (info.loads_position) {
+        if (info.loads.AnyComponent(IR::Attribute::PositionX)) {
             AddLabel(labels[label_index]);
             const Id pointer{is_array
                                  ? OpAccessChain(input_f32, input_position, vertex, masked_index)
@@ -719,18 +719,18 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
             OpReturnValue(result);
             ++label_index;
         }
-        for (size_t i = 0; i < info.input_generics.size(); i++) {
-            if (!info.input_generics[i].used) {
+        for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+            if (!info.loads.Generic(index)) {
                 continue;
             }
             AddLabel(labels[label_index]);
-            const auto type{AttrTypes(*this, static_cast<u32>(i))};
+            const auto type{AttrTypes(*this, static_cast<u32>(index))};
             if (!type) {
                 OpReturnValue(Const(0.0f));
                 ++label_index;
                 continue;
             }
-            const Id generic_id{input_generics.at(i)};
+            const Id generic_id{input_generics.at(index)};
             const Id pointer{is_array
                                  ? OpAccessChain(type->pointer, generic_id, vertex, masked_index)
                                  : OpAccessChain(type->pointer, generic_id, masked_index)};
@@ -758,19 +758,19 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
         const Id compare_index{OpShiftRightArithmetic(U32[1], base_index, Const(2U))};
         std::vector<Sirit::Literal> literals;
         std::vector<Id> labels;
-        if (info.stores_position) {
+        if (info.stores.AnyComponent(IR::Attribute::PositionX)) {
             literals.push_back(static_cast<u32>(IR::Attribute::PositionX) >> 2);
             labels.push_back(OpLabel());
         }
         const u32 base_attribute_value = static_cast<u32>(IR::Attribute::Generic0X) >> 2;
-        for (size_t i = 0; i < info.stores_generics.size(); i++) {
-            if (!info.stores_generics[i]) {
+        for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+            if (!info.stores.Generic(index)) {
                 continue;
             }
-            literals.push_back(base_attribute_value + static_cast<u32>(i));
+            literals.push_back(base_attribute_value + static_cast<u32>(index));
             labels.push_back(OpLabel());
         }
-        if (info.stores_clip_distance) {
+        if (info.stores.ClipDistances()) {
             literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance0) >> 2);
             labels.push_back(OpLabel());
             literals.push_back(static_cast<u32>(IR::Attribute::ClipDistance4) >> 2);
@@ -781,28 +781,28 @@ void EmitContext::DefineAttributeMemAccess(const Info& info) {
         AddLabel(default_label);
         OpReturn();
         size_t label_index{0};
-        if (info.stores_position) {
+        if (info.stores.AnyComponent(IR::Attribute::PositionX)) {
             AddLabel(labels[label_index]);
             const Id pointer{OpAccessChain(output_f32, output_position, masked_index)};
             OpStore(pointer, store_value);
             OpReturn();
             ++label_index;
         }
-        for (size_t i = 0; i < info.stores_generics.size(); ++i) {
-            if (!info.stores_generics[i]) {
+        for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+            if (!info.stores.Generic(index)) {
                 continue;
             }
-            if (output_generics[i][0].num_components != 4) {
+            if (output_generics[index][0].num_components != 4) {
                 throw NotImplementedException("Physical stores and transform feedbacks");
             }
             AddLabel(labels[label_index]);
-            const Id generic_id{output_generics[i][0].id};
+            const Id generic_id{output_generics[index][0].id};
             const Id pointer{OpAccessChain(output_f32, generic_id, masked_index)};
             OpStore(pointer, store_value);
             OpReturn();
             ++label_index;
         }
-        if (info.stores_clip_distance) {
+        if (info.stores.ClipDistances()) {
             AddLabel(labels[label_index]);
             const Id pointer{OpAccessChain(output_f32, clip_distances, masked_index)};
             OpStore(pointer, store_value);
@@ -1146,7 +1146,10 @@ void EmitContext::DefineImages(const Info& info, u32& binding) {
     }
 }
 
-void EmitContext::DefineInputs(const Info& info) {
+void EmitContext::DefineInputs(const IR::Program& program) {
+    const Info& info{program.info};
+    const VaryingState loads{info.loads.mask | info.passthrough.mask};
+
     if (info.uses_workgroup_id) {
         workgroup_id = DefineInput(*this, U32[3], false, spv::BuiltIn::WorkgroupId);
     }
@@ -1183,15 +1186,20 @@ void EmitContext::DefineInputs(const Info& info) {
         fswzadd_lut_b =
             ConstantComposite(F32[4], f32_minus_one, f32_minus_one, f32_one, f32_minus_one);
     }
-    if (info.loads_primitive_id) {
+    if (loads[IR::Attribute::PrimitiveId]) {
         primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId);
     }
-    if (info.loads_position) {
+    if (loads.AnyComponent(IR::Attribute::PositionX)) {
         const bool is_fragment{stage != Stage::Fragment};
         const spv::BuiltIn built_in{is_fragment ? spv::BuiltIn::Position : spv::BuiltIn::FragCoord};
         input_position = DefineInput(*this, F32[4], true, built_in);
+        if (profile.support_geometry_shader_passthrough) {
+            if (info.passthrough.AnyComponent(IR::Attribute::PositionX)) {
+                Decorate(input_position, spv::Decoration::PassthroughNV);
+            }
+        }
     }
-    if (info.loads_instance_id) {
+    if (loads[IR::Attribute::InstanceId]) {
         if (profile.support_vertex_instance_id) {
             instance_id = DefineInput(*this, U32[1], true, spv::BuiltIn::InstanceId);
         } else {
@@ -1199,7 +1207,7 @@ void EmitContext::DefineInputs(const Info& info) {
             base_instance = DefineInput(*this, U32[1], true, spv::BuiltIn::BaseInstance);
         }
     }
-    if (info.loads_vertex_id) {
+    if (loads[IR::Attribute::VertexId]) {
         if (profile.support_vertex_instance_id) {
             vertex_id = DefineInput(*this, U32[1], true, spv::BuiltIn::VertexId);
         } else {
@@ -1207,24 +1215,24 @@ void EmitContext::DefineInputs(const Info& info) {
             base_vertex = DefineInput(*this, U32[1], true, spv::BuiltIn::BaseVertex);
         }
     }
-    if (info.loads_front_face) {
+    if (loads[IR::Attribute::FrontFace]) {
         front_face = DefineInput(*this, U1, true, spv::BuiltIn::FrontFacing);
     }
-    if (info.loads_point_coord) {
+    if (loads[IR::Attribute::PointSpriteS] || loads[IR::Attribute::PointSpriteT]) {
         point_coord = DefineInput(*this, F32[2], true, spv::BuiltIn::PointCoord);
     }
-    if (info.loads_tess_coord) {
+    if (loads[IR::Attribute::TessellationEvaluationPointU] ||
+        loads[IR::Attribute::TessellationEvaluationPointV]) {
         tess_coord = DefineInput(*this, F32[3], false, spv::BuiltIn::TessCoord);
     }
-    for (size_t index = 0; index < info.input_generics.size(); ++index) {
-        if (!runtime_info.previous_stage_stores_generic[index]) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        const AttributeType input_type{runtime_info.generic_input_types[index]};
+        if (!runtime_info.previous_stage_stores.Generic(index)) {
             continue;
         }
-        const InputVarying generic{info.input_generics[index]};
-        if (!generic.used) {
+        if (!loads.Generic(index)) {
             continue;
         }
-        const AttributeType input_type{runtime_info.generic_input_types[index]};
         if (input_type == AttributeType::Disabled) {
             continue;
         }
@@ -1234,10 +1242,13 @@ void EmitContext::DefineInputs(const Info& info) {
         Name(id, fmt::format("in_attr{}", index));
         input_generics[index] = id;
 
+        if (info.passthrough.Generic(index) && profile.support_geometry_shader_passthrough) {
+            Decorate(id, spv::Decoration::PassthroughNV);
+        }
         if (stage != Stage::Fragment) {
             continue;
         }
-        switch (generic.interpolation) {
+        switch (info.interpolation[index]) {
         case Interpolation::Smooth:
             // Default
             // Decorate(id, spv::Decoration::Smooth);
@@ -1266,42 +1277,42 @@ void EmitContext::DefineInputs(const Info& info) {
 void EmitContext::DefineOutputs(const IR::Program& program) {
     const Info& info{program.info};
     const std::optional<u32> invocations{program.invocations};
-    if (info.stores_position || stage == Stage::VertexB) {
+    if (info.stores.AnyComponent(IR::Attribute::PositionX) || stage == Stage::VertexB) {
         output_position = DefineOutput(*this, F32[4], invocations, spv::BuiltIn::Position);
     }
-    if (info.stores_point_size || runtime_info.fixed_state_point_size) {
+    if (info.stores[IR::Attribute::PointSize] || runtime_info.fixed_state_point_size) {
         if (stage == Stage::Fragment) {
             throw NotImplementedException("Storing PointSize in fragment stage");
         }
         output_point_size = DefineOutput(*this, F32[1], invocations, spv::BuiltIn::PointSize);
     }
-    if (info.stores_clip_distance) {
+    if (info.stores.ClipDistances()) {
         if (stage == Stage::Fragment) {
             throw NotImplementedException("Storing ClipDistance in fragment stage");
         }
         const Id type{TypeArray(F32[1], Const(8U))};
         clip_distances = DefineOutput(*this, type, invocations, spv::BuiltIn::ClipDistance);
     }
-    if (info.stores_layer &&
+    if (info.stores[IR::Attribute::Layer] &&
         (profile.support_viewport_index_layer_non_geometry || stage == Stage::Geometry)) {
         if (stage == Stage::Fragment) {
             throw NotImplementedException("Storing Layer in fragment stage");
         }
         layer = DefineOutput(*this, U32[1], invocations, spv::BuiltIn::Layer);
     }
-    if (info.stores_viewport_index &&
+    if (info.stores[IR::Attribute::ViewportIndex] &&
         (profile.support_viewport_index_layer_non_geometry || stage == Stage::Geometry)) {
         if (stage == Stage::Fragment) {
             throw NotImplementedException("Storing ViewportIndex in fragment stage");
         }
         viewport_index = DefineOutput(*this, U32[1], invocations, spv::BuiltIn::ViewportIndex);
     }
-    if (info.stores_viewport_mask && profile.support_viewport_mask) {
+    if (info.stores[IR::Attribute::ViewportMask] && profile.support_viewport_mask) {
         viewport_mask = DefineOutput(*this, TypeArray(U32[1], Const(1u)), std::nullopt,
                                      spv::BuiltIn::ViewportMaskNV);
     }
-    for (size_t index = 0; index < info.stores_generics.size(); ++index) {
-        if (info.stores_generics[index]) {
+    for (size_t index = 0; index < IR::NUM_GENERICS; ++index) {
+        if (info.stores.Generic(index)) {
             DefineGenericOutput(*this, index, invocations);
         }
     }
diff --git a/src/shader_recompiler/backend/spirv/emit_context.h b/src/shader_recompiler/backend/spirv/emit_context.h
index 527685fb81..e277bc3580 100644
--- a/src/shader_recompiler/backend/spirv/emit_context.h
+++ b/src/shader_recompiler/backend/spirv/emit_context.h
@@ -300,7 +300,7 @@ private:
     void DefineAttributeMemAccess(const Info& info);
     void DefineGlobalMemoryFunctions(const Info& info);
 
-    void DefineInputs(const Info& info);
+    void DefineInputs(const IR::Program& program);
     void DefineOutputs(const IR::Program& program);
 };
 
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
index 278c262f8f..ddb86d0701 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp
@@ -281,11 +281,19 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
             ctx.AddExecutionMode(main, spv::ExecutionMode::OutputTriangleStrip);
             break;
         }
-        if (program.info.stores_point_size) {
+        if (program.info.stores[IR::Attribute::PointSize]) {
             ctx.AddCapability(spv::Capability::GeometryPointSize);
         }
         ctx.AddExecutionMode(main, spv::ExecutionMode::OutputVertices, program.output_vertices);
         ctx.AddExecutionMode(main, spv::ExecutionMode::Invocations, program.invocations);
+        if (program.is_geometry_passthrough) {
+            if (ctx.profile.support_geometry_shader_passthrough) {
+                ctx.AddExtension("SPV_NV_geometry_shader_passthrough");
+                ctx.AddCapability(spv::Capability::GeometryShaderPassthroughNV);
+            } else {
+                LOG_WARNING(Shader_SPIRV, "Geometry shader passthrough used with no support");
+            }
+        }
         break;
     case Stage::Fragment:
         execution_model = spv::ExecutionModel::Fragment;
@@ -377,20 +385,21 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
         ctx.AddExtension("SPV_EXT_demote_to_helper_invocation");
         ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT);
     }
-    if (info.stores_viewport_index) {
+    if (info.stores[IR::Attribute::ViewportIndex]) {
         ctx.AddCapability(spv::Capability::MultiViewport);
     }
-    if (info.stores_viewport_mask && profile.support_viewport_mask) {
+    if (info.stores[IR::Attribute::ViewportMask] && profile.support_viewport_mask) {
         ctx.AddExtension("SPV_NV_viewport_array2");
         ctx.AddCapability(spv::Capability::ShaderViewportMaskNV);
     }
-    if (info.stores_layer || info.stores_viewport_index) {
+    if (info.stores[IR::Attribute::Layer] || info.stores[IR::Attribute::ViewportIndex]) {
         if (profile.support_viewport_index_layer_non_geometry && ctx.stage != Stage::Geometry) {
             ctx.AddExtension("SPV_EXT_shader_viewport_index_layer");
             ctx.AddCapability(spv::Capability::ShaderViewportIndexLayerEXT);
         }
     }
-    if (!profile.support_vertex_instance_id && (info.loads_instance_id || info.loads_vertex_id)) {
+    if (!profile.support_vertex_instance_id &&
+        (info.loads[IR::Attribute::InstanceId] || info.loads[IR::Attribute::VertexId])) {
         ctx.AddExtension("SPV_KHR_shader_draw_parameters");
         ctx.AddCapability(spv::Capability::DrawParameters);
     }
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 85bd723895..77fbb2b2fd 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
@@ -298,7 +298,7 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
     if (IR::IsGeneric(attr)) {
         const u32 index{IR::GenericAttributeIndex(attr)};
         const std::optional<AttrInfo> type{AttrTypes(ctx, index)};
-        if (!type || !ctx.runtime_info.previous_stage_stores_generic[index]) {
+        if (!type || !ctx.runtime_info.previous_stage_stores.Generic(index)) {
             // Attribute is disabled
             return ctx.Const(0.0f);
         }
-- 
cgit v1.2.3-70-g09d2