diff options
author | FernandoS27 <fsahmkow27@gmail.com> | 2021-04-04 06:47:14 +0200 |
---|---|---|
committer | ameerj <52414509+ameerj@users.noreply.github.com> | 2021-07-23 03:51:26 +0200 |
commit | 1d51803169f72f79e19995072fb9e8a371dbdcbf (patch) | |
tree | eb668d924985e4b0bd599a695a0a2eee30fd0090 /src/shader_recompiler | |
parent | shader: Implement AL2P (diff) | |
download | yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar.gz yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar.bz2 yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar.lz yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar.xz yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.tar.zst yuzu-1d51803169f72f79e19995072fb9e8a371dbdcbf.zip |
Diffstat (limited to 'src/shader_recompiler')
12 files changed, 279 insertions, 35 deletions
diff --git a/src/shader_recompiler/backend/spirv/emit_context.cpp b/src/shader_recompiler/backend/spirv/emit_context.cpp index 002b305dc..eadecb064 100644 --- a/src/shader_recompiler/backend/spirv/emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/emit_context.cpp @@ -82,6 +82,28 @@ Id GetAttributeType(EmitContext& ctx, AttributeType type) { } throw InvalidArgument("Invalid attribute type {}", type); } + +struct AttrInfo { + Id pointer; + Id id; + bool needs_cast; +}; + +std::optional<AttrInfo> AttrTypes(EmitContext& ctx, u32 index) { + const AttributeType type{ctx.profile.generic_input_types.at(index)}; + switch (type) { + case AttributeType::Float: + return AttrInfo{ctx.input_f32, ctx.F32[1], false}; + case AttributeType::UnsignedInt: + return AttrInfo{ctx.input_u32, ctx.U32[1], true}; + case AttributeType::SignedInt: + return AttrInfo{ctx.input_s32, ctx.TypeInt(32, true), true}; + case AttributeType::Disabled: + return std::nullopt; + } + throw InvalidArgument("Invalid attribute type {}", type); +} + } // Anonymous namespace void VectorTypes::Define(Sirit::Module& sirit_ctx, Id base_type, std::string_view name) { @@ -107,6 +129,7 @@ EmitContext::EmitContext(const Profile& profile_, IR::Program& program, u32& bin DefineConstantBuffers(program.info, binding); DefineStorageBuffers(program.info, binding); DefineTextures(program.info, binding); + DefineAttributeMemAccess(program.info); DefineLabels(program); } @@ -290,6 +313,107 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) { } } +void EmitContext::DefineAttributeMemAccess(const Info& info) { + const auto make_load{[&]() { + const Id end_block{OpLabel()}; + const Id default_label{OpLabel()}; + + const Id func_type_load{TypeFunction(F32[1], U32[1])}; + const Id func{OpFunction(F32[1], spv::FunctionControlMask::MaskNone, func_type_load)}; + const Id offset{OpFunctionParameter(U32[1])}; + AddLabel(); + const Id base_index{OpShiftRightLogical(U32[1], offset, Constant(U32[1], 2U))}; + const Id masked_index{OpBitwiseAnd(U32[1], base_index, Constant(U32[1], 3U))}; + const Id compare_index{OpShiftRightLogical(U32[1], base_index, Constant(U32[1], 2U))}; + std::vector<Sirit::Literal> literals; + std::vector<Id> labels; + 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) { + continue; + } + literals.push_back(base_attribute_value + i); + labels.push_back(OpLabel()); + } + OpSelectionMerge(end_block, spv::SelectionControlMask::MaskNone); + OpSwitch(compare_index, default_label, literals, labels); + AddLabel(default_label); + OpReturnValue(Constant(F32[1], 0.0f)); + size_t label_index = 0; + for (u32 i = 0; i < info.input_generics.size(); i++) { + if (!info.input_generics[i].used) { + continue; + } + AddLabel(labels[label_index]); + const auto type{AttrTypes(*this, i)}; + if (!type) { + OpReturnValue(Constant(F32[1], 0.0f)); + label_index++; + continue; + } + const Id generic_id{input_generics.at(i)}; + const Id pointer{OpAccessChain(type->pointer, generic_id, masked_index)}; + const Id value{OpLoad(type->id, pointer)}; + const Id result{type->needs_cast ? OpBitcast(F32[1], value) : value}; + OpReturnValue(result); + label_index++; + } + AddLabel(end_block); + OpUnreachable(); + OpFunctionEnd(); + return func; + }}; + const auto make_store{[&]() { + const Id end_block{OpLabel()}; + const Id default_label{OpLabel()}; + + const Id func_type_store{TypeFunction(void_id, U32[1], F32[1])}; + const Id func{OpFunction(void_id, spv::FunctionControlMask::MaskNone, func_type_store)}; + const Id offset{OpFunctionParameter(U32[1])}; + const Id store_value{OpFunctionParameter(F32[1])}; + AddLabel(); + const Id base_index{OpShiftRightLogical(U32[1], offset, Constant(U32[1], 2U))}; + const Id masked_index{OpBitwiseAnd(U32[1], base_index, Constant(U32[1], 3U))}; + const Id compare_index{OpShiftRightLogical(U32[1], base_index, Constant(U32[1], 2U))}; + std::vector<Sirit::Literal> literals; + std::vector<Id> labels; + const u32 base_attribute_value = static_cast<u32>(IR::Attribute::Generic0X) >> 2; + for (u32 i = 0; i < info.stores_generics.size(); i++) { + if (!info.stores_generics[i]) { + continue; + } + literals.push_back(base_attribute_value + i); + labels.push_back(OpLabel()); + } + OpSelectionMerge(end_block, spv::SelectionControlMask::MaskNone); + OpSwitch(compare_index, default_label, literals, labels); + AddLabel(default_label); + OpReturn(); + size_t label_index = 0; + for (u32 i = 0; i < info.stores_generics.size(); i++) { + if (!info.stores_generics[i]) { + continue; + } + AddLabel(labels[label_index]); + const Id generic_id{output_generics.at(i)}; + const Id pointer{OpAccessChain(output_f32, generic_id, masked_index)}; + OpStore(pointer, store_value); + OpReturn(); + label_index++; + } + AddLabel(end_block); + OpUnreachable(); + OpFunctionEnd(); + return func; + }}; + if (info.loads_indexed_attributes) { + indexed_load_func = make_load(); + } + if (info.stores_indexed_attributes) { + indexed_store_func = make_store(); + } +} + void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) { if (info.constant_buffer_descriptors.empty()) { return; diff --git a/src/shader_recompiler/backend/spirv/emit_context.h b/src/shader_recompiler/backend/spirv/emit_context.h index 03c5a6aba..7a2ac0511 100644 --- a/src/shader_recompiler/backend/spirv/emit_context.h +++ b/src/shader_recompiler/backend/spirv/emit_context.h @@ -116,6 +116,9 @@ public: Id fswzadd_lut_a{}; Id fswzadd_lut_b{}; + Id indexed_load_func{}; + Id indexed_store_func{}; + Id local_memory{}; Id shared_memory_u8{}; @@ -148,6 +151,7 @@ private: void DefineConstantBuffers(const Info& info, u32& binding); void DefineStorageBuffers(const Info& info, u32& binding); void DefineTextures(const Info& info, u32& binding); + void DefineAttributeMemAccess(const Info& info); void DefineLabels(IR::Program& program); void DefineConstantBuffers(const Info& info, Id UniformDefinitions::*member_type, u32 binding, diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h index 712c5e61f..08460c94e 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv.h @@ -51,8 +51,8 @@ Id EmitGetCbufF32(EmitContext& ctx, const IR::Value& binding, const IR::Value& o Id EmitGetCbufU32x2(EmitContext& ctx, const IR::Value& binding, const IR::Value& offset); Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr); void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value); -void EmitGetAttributeIndexed(EmitContext& ctx); -void EmitSetAttributeIndexed(EmitContext& ctx); +Id EmitGetAttributeIndexed(EmitContext& ctx, Id offset); +void EmitSetAttributeIndexed(EmitContext& ctx, Id offset, Id value); void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value); void EmitSetFragDepth(EmitContext& ctx, Id value); void EmitGetZFlag(EmitContext& ctx); 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 1bfc60294..a60eca815 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 @@ -216,12 +216,12 @@ void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value) { ctx.OpStore(*output, value); } -void EmitGetAttributeIndexed(EmitContext&) { - throw NotImplementedException("SPIR-V Instruction"); +Id EmitGetAttributeIndexed(EmitContext& ctx, Id offset) { + return ctx.OpFunctionCall(ctx.F32[1], ctx.indexed_load_func, offset); } -void EmitSetAttributeIndexed(EmitContext&) { - throw NotImplementedException("SPIR-V Instruction"); +void EmitSetAttributeIndexed(EmitContext& ctx, Id offset, Id value) { + ctx.OpFunctionCall(ctx.void_id, ctx.indexed_store_func, offset, value); } void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) { diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp index ed1e0dd3b..e4e9b260c 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp +++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp @@ -307,6 +307,14 @@ void IREmitter::SetAttribute(IR::Attribute attribute, const F32& value) { Inst(Opcode::SetAttribute, attribute, value); } +F32 IREmitter::GetAttributeIndexed(IR::U32 phys_address) { + return Inst<F32>(Opcode::GetAttributeIndexed, phys_address); +} + +void IREmitter::SetAttributeIndexed(IR::U32 phys_address, const F32& value) { + Inst(Opcode::SetAttributeIndexed, phys_address, value); +} + void IREmitter::SetFragColor(u32 index, u32 component, const F32& value) { Inst(Opcode::SetFragColor, Imm32(index), Imm32(component), value); } diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h index 42756af43..afa8bd924 100644 --- a/src/shader_recompiler/frontend/ir/ir_emitter.h +++ b/src/shader_recompiler/frontend/ir/ir_emitter.h @@ -76,6 +76,9 @@ public: [[nodiscard]] F32 GetAttribute(IR::Attribute attribute); void SetAttribute(IR::Attribute attribute, const F32& value); + [[nodiscard]] F32 GetAttributeIndexed(IR::U32 phys_address); + void SetAttributeIndexed(IR::U32 phys_address, const F32& value); + void SetFragColor(u32 index, u32 component, const F32& value); void SetFragDepth(const F32& value); diff --git a/src/shader_recompiler/frontend/maxwell/program.cpp b/src/shader_recompiler/frontend/maxwell/program.cpp index 58caa35a1..aaf2a74a7 100644 --- a/src/shader_recompiler/frontend/maxwell/program.cpp +++ b/src/shader_recompiler/frontend/maxwell/program.cpp @@ -87,7 +87,7 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo Optimization::DeadCodeEliminationPass(program); Optimization::IdentityRemovalPass(program); Optimization::VerificationPass(program); - Optimization::CollectShaderInfoPass(program); + Optimization::CollectShaderInfoPass(env, program); CollectInterpolationInfo(env, program); return program; } diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp index 54bc1e34c..0d248c020 100644 --- a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp +++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp @@ -31,7 +31,7 @@ enum class SampleMode : u64 { Offset, }; -int NumElements(Size size) { +u32 NumElements(Size size) { switch (size) { case Size::B32: return 1; @@ -65,15 +65,21 @@ void TranslatorVisitor::ALD(u64 insn) { if (ald.patch != 0) { throw NotImplementedException("P"); } - if (ald.index_reg != IR::Reg::RZ) { - throw NotImplementedException("Indexed"); - } const u64 offset{ald.absolute_offset.Value()}; if (offset % 4 != 0) { throw NotImplementedException("Unaligned absolute offset {}", offset); } - const int num_elements{NumElements(ald.size)}; - for (int element = 0; element < num_elements; ++element) { + const u32 num_elements{NumElements(ald.size)}; + if (ald.index_reg != IR::Reg::RZ) { + const IR::U32 index_value = X(ald.index_reg); + for (u32 element = 0; element < num_elements; ++element) { + const IR::U32 final_offset = + element == 0 ? index_value : IR::U32{ir.IAdd(index_value, ir.Imm32(element * 4U))}; + F(ald.dest_reg + element, ir.GetAttributeIndexed(final_offset)); + } + return; + } + for (u32 element = 0; element < num_elements; ++element) { F(ald.dest_reg + element, ir.GetAttribute(IR::Attribute{offset / 4 + element})); } } @@ -103,8 +109,17 @@ void TranslatorVisitor::AST(u64 insn) { if (offset % 4 != 0) { throw NotImplementedException("Unaligned absolute offset {}", offset); } - const int num_elements{NumElements(ast.size)}; - for (int element = 0; element < num_elements; ++element) { + const u32 num_elements{NumElements(ast.size)}; + if (ast.index_reg != IR::Reg::RZ) { + const IR::U32 index_value = X(ast.index_reg); + for (u32 element = 0; element < num_elements; ++element) { + const IR::U32 final_offset = + element == 0 ? index_value : IR::U32{ir.IAdd(index_value, ir.Imm32(element * 4U))}; + ir.SetAttributeIndexed(final_offset, F(ast.src_reg + element)); + } + return; + } + for (u32 element = 0; element < num_elements; ++element) { ir.SetAttribute(IR::Attribute{offset / 4 + element}, F(ast.src_reg + element)); } } @@ -134,12 +149,9 @@ void TranslatorVisitor::IPA(u64 insn) { // gl_FragColor = colors[idx]; // } const bool is_indexed{ipa.idx != 0 && ipa.index_reg != IR::Reg::RZ}; - if (is_indexed) { - throw NotImplementedException("IDX"); - } - const IR::Attribute attribute{ipa.attribute}; - IR::F32 value{ir.GetAttribute(attribute)}; + IR::F32 value{is_indexed ? ir.GetAttributeIndexed(X(ipa.index_reg)) + : ir.GetAttribute(attribute)}; if (IR::IsGeneric(attribute)) { const ProgramHeader& sph{env.SPH()}; const u32 attr_index{IR::GenericAttributeIndex(attribute)}; 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 0f870535b..dbe9f1f40 100644 --- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp +++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "shader_recompiler/environment.h" #include "shader_recompiler/frontend/ir/microinstruction.h" #include "shader_recompiler/frontend/ir/modifiers.h" #include "shader_recompiler/frontend/ir/program.h" @@ -323,6 +324,12 @@ void VisitUsages(Info& info, IR::Inst& inst) { case IR::Opcode::SetAttribute: SetAttribute(info, inst.Arg(0).Attribute()); break; + case IR::Opcode::GetAttributeIndexed: + info.loads_indexed_attributes = true; + break; + case IR::Opcode::SetAttributeIndexed: + info.stores_indexed_attributes = true; + break; case IR::Opcode::SetFragColor: info.stores_frag_color[inst.Arg(0).U32()] = true; break; @@ -502,15 +509,42 @@ void Visit(Info& info, IR::Inst& inst) { VisitUsages(info, inst); VisitFpModifiers(info, inst); } + +void GatherInfoFromHeader(Environment& env, Info& info) { + auto stage = env.ShaderStage(); + if (stage == Stage::Compute) { + return; + } + const auto& header = env.SPH(); + if (stage == Stage::Fragment) { + for (size_t i = 0; i < info.input_generics.size(); i++) { + info.input_generics[i].used = + info.input_generics[i].used || header.ps.IsGenericVectorActive(i); + } + return; + } + for (size_t i = 0; i < info.input_generics.size(); i++) { + info.input_generics[i].used = + info.input_generics[i].used || header.vtg.IsInputGenericVectorActive(i); + } + for (size_t i = 0; i < info.stores_generics.size(); i++) { + info.stores_generics[i] = + info.stores_generics[i] || header.vtg.IsOutputGenericVectorActive(i); + } + info.stores_clip_distance = + info.stores_clip_distance || header.vtg.omap_systemc.clip_distances != 0; +} + } // Anonymous namespace -void CollectShaderInfoPass(IR::Program& program) { +void CollectShaderInfoPass(Environment& env, IR::Program& program) { Info& info{program.info}; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { Visit(info, inst); } } + GatherInfoFromHeader(env, info); } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h index 5c1fc166c..186104713 100644 --- a/src/shader_recompiler/ir_opt/passes.h +++ b/src/shader_recompiler/ir_opt/passes.h @@ -12,7 +12,7 @@ namespace Shader::Optimization { -void CollectShaderInfoPass(IR::Program& program); +void CollectShaderInfoPass(Environment& env, IR::Program& program); void ConstantPropagationPass(IR::Program& program); void DeadCodeEliminationPass(IR::Program& program); void GlobalMemoryToStorageBufferPass(IR::Program& program); diff --git a/src/shader_recompiler/program_header.h b/src/shader_recompiler/program_header.h index 1544bfa42..ce65fc1a4 100644 --- a/src/shader_recompiler/program_header.h +++ b/src/shader_recompiler/program_header.h @@ -68,10 +68,24 @@ struct ProgramHeader { union { struct { - INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA - INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB - INSERT_PADDING_BYTES_NOINIT(16); // ImapGenericVector[32] - INSERT_PADDING_BYTES_NOINIT(2); // ImapColor + INSERT_PADDING_BYTES_NOINIT(3); // ImapSystemValuesA + INSERT_PADDING_BYTES_NOINIT(1); // ImapSystemValuesB + + union { + BitField<0, 1, u8> x; + BitField<1, 1, u8> y; + BitField<2, 1, u8> z; + BitField<3, 1, u8> w; + BitField<4, 1, u8> x2; + BitField<5, 1, u8> y2; + BitField<6, 1, u8> z2; + BitField<7, 1, u8> w2; + BitField<0, 4, u8> first; + BitField<4, 4, u8> second; + u8 raw; + } imap_generic_vector[16]; + + INSERT_PADDING_BYTES_NOINIT(2); // ImapColor union { BitField<0, 8, u16> clip_distances; BitField<8, 1, u16> point_sprite_s; @@ -82,15 +96,54 @@ struct ProgramHeader { BitField<14, 1, u16> instance_id; BitField<15, 1, u16> vertex_id; }; - INSERT_PADDING_BYTES_NOINIT(5); // ImapFixedFncTexture[10] - INSERT_PADDING_BYTES_NOINIT(1); // ImapReserved - INSERT_PADDING_BYTES_NOINIT(3); // OmapSystemValuesA - INSERT_PADDING_BYTES_NOINIT(1); // OmapSystemValuesB - INSERT_PADDING_BYTES_NOINIT(16); // OmapGenericVector[32] - INSERT_PADDING_BYTES_NOINIT(2); // OmapColor - INSERT_PADDING_BYTES_NOINIT(2); // OmapSystemValuesC - INSERT_PADDING_BYTES_NOINIT(5); // OmapFixedFncTexture[10] - INSERT_PADDING_BYTES_NOINIT(1); // OmapReserved + INSERT_PADDING_BYTES_NOINIT(5); // ImapFixedFncTexture[10] + INSERT_PADDING_BYTES_NOINIT(1); // ImapReserved + INSERT_PADDING_BYTES_NOINIT(3); // OmapSystemValuesA + INSERT_PADDING_BYTES_NOINIT(1); // OmapSystemValuesB + + union { + BitField<0, 1, u8> x; + BitField<1, 1, u8> y; + BitField<2, 1, u8> z; + BitField<3, 1, u8> w; + BitField<4, 1, u8> x2; + BitField<5, 1, u8> y2; + BitField<6, 1, u8> z2; + BitField<7, 1, u8> w2; + BitField<0, 4, u8> first; + BitField<4, 4, u8> second; + u8 raw; + } omap_generic_vector[16]; + + INSERT_PADDING_BYTES_NOINIT(2); // OmapColor + + union { + BitField<0, 8, u16> clip_distances; + BitField<8, 1, u16> point_sprite_s; + BitField<9, 1, u16> point_sprite_t; + BitField<10, 1, u16> fog_coordinate; + BitField<12, 1, u16> tessellation_eval_point_u; + BitField<13, 1, u16> tessellation_eval_point_v; + BitField<14, 1, u16> instance_id; + BitField<15, 1, u16> vertex_id; + } omap_systemc; + + INSERT_PADDING_BYTES_NOINIT(5); // OmapFixedFncTexture[10] + INSERT_PADDING_BYTES_NOINIT(1); // OmapReserved + + [[nodiscard]] bool IsInputGenericVectorActive(size_t index) const { + if ((index & 1) == 0) { + return imap_generic_vector[index >> 1].first != 0; + } + return imap_generic_vector[index >> 1].second != 0; + } + + [[nodiscard]] bool IsOutputGenericVectorActive(size_t index) const { + if ((index & 1) == 0) { + return omap_generic_vector[index >> 1].first != 0; + } + return omap_generic_vector[index >> 1].second != 0; + } } vtg; struct { @@ -128,6 +181,10 @@ struct ProgramHeader { const auto& vector{imap_generic_vector[attribute]}; return {vector.x, vector.y, vector.z, vector.w}; } + + [[nodiscard]] bool IsGenericVectorActive(size_t index) const { + return imap_generic_vector[index].raw != 0; + } } ps; std::array<u32, 0xf> raw; diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index 9551a124f..41bb5b9a1 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h @@ -76,6 +76,7 @@ struct Info { bool loads_vertex_id{}; bool loads_front_face{}; bool loads_point_coord{}; + bool loads_indexed_attributes{}; std::array<bool, 8> stores_frag_color{}; bool stores_frag_depth{}; @@ -84,6 +85,7 @@ struct Info { bool stores_point_size{}; bool stores_clip_distance{}; bool stores_viewport_index{}; + bool stores_indexed_attributes{}; bool uses_fp16{}; bool uses_fp64{}; |