diff options
| author | 2019-07-05 13:39:13 -0400 | |
|---|---|---|
| committer | 2019-07-05 13:39:13 -0400 | |
| commit | 772c86a260eb446b0fe4232b0a50666511bef25c (patch) | |
| tree | 013d92268c06454c93565c83eff2b79b56a00839 /src/video_core/shader | |
| parent | Merge pull request #2669 from FearlessTobi/move-cpujit-setting (diff) | |
| parent | texture_cache: Address Feedback (diff) | |
| download | yuzu-772c86a260eb446b0fe4232b0a50666511bef25c.tar.gz yuzu-772c86a260eb446b0fe4232b0a50666511bef25c.tar.xz yuzu-772c86a260eb446b0fe4232b0a50666511bef25c.zip | |
Merge pull request #2601 from FernandoS27/texture_cache
Implement a new Texture Cache
Diffstat (limited to 'src/video_core/shader')
| -rw-r--r-- | src/video_core/shader/decode.cpp | 1 | ||||
| -rw-r--r-- | src/video_core/shader/decode/image.cpp | 120 | ||||
| -rw-r--r-- | src/video_core/shader/decode/texture.cpp | 45 | ||||
| -rw-r--r-- | src/video_core/shader/node.h | 52 | ||||
| -rw-r--r-- | src/video_core/shader/shader_ir.h | 14 |
5 files changed, 231 insertions, 1 deletions
diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index a0554c97e..2c9ff28f2 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp | |||
| @@ -169,6 +169,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { | |||
| 169 | {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, | 169 | {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, |
| 170 | {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, | 170 | {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, |
| 171 | {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, | 171 | {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, |
| 172 | {OpCode::Type::Image, &ShaderIR::DecodeImage}, | ||
| 172 | {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, | 173 | {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, |
| 173 | {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, | 174 | {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, |
| 174 | {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, | 175 | {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, |
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp new file mode 100644 index 000000000..24f022cc0 --- /dev/null +++ b/src/video_core/shader/decode/image.cpp | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | // Copyright 2019 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <vector> | ||
| 7 | #include <fmt/format.h> | ||
| 8 | |||
| 9 | #include "common/assert.h" | ||
| 10 | #include "common/bit_field.h" | ||
| 11 | #include "common/common_types.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "video_core/engines/shader_bytecode.h" | ||
| 14 | #include "video_core/shader/node_helper.h" | ||
| 15 | #include "video_core/shader/shader_ir.h" | ||
| 16 | |||
| 17 | namespace VideoCommon::Shader { | ||
| 18 | |||
| 19 | using Tegra::Shader::Instruction; | ||
| 20 | using Tegra::Shader::OpCode; | ||
| 21 | |||
| 22 | namespace { | ||
| 23 | std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { | ||
| 24 | switch (image_type) { | ||
| 25 | case Tegra::Shader::ImageType::Texture1D: | ||
| 26 | case Tegra::Shader::ImageType::TextureBuffer: | ||
| 27 | return 1; | ||
| 28 | case Tegra::Shader::ImageType::Texture1DArray: | ||
| 29 | case Tegra::Shader::ImageType::Texture2D: | ||
| 30 | return 2; | ||
| 31 | case Tegra::Shader::ImageType::Texture2DArray: | ||
| 32 | case Tegra::Shader::ImageType::Texture3D: | ||
| 33 | return 3; | ||
| 34 | } | ||
| 35 | UNREACHABLE(); | ||
| 36 | return 1; | ||
| 37 | } | ||
| 38 | } // Anonymous namespace | ||
| 39 | |||
| 40 | u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { | ||
| 41 | const Instruction instr = {program_code[pc]}; | ||
| 42 | const auto opcode = OpCode::Decode(instr); | ||
| 43 | |||
| 44 | switch (opcode->get().GetId()) { | ||
| 45 | case OpCode::Id::SUST: { | ||
| 46 | UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); | ||
| 47 | UNIMPLEMENTED_IF(instr.sust.image_type == Tegra::Shader::ImageType::TextureBuffer); | ||
| 48 | UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); | ||
| 49 | UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store | ||
| 50 | |||
| 51 | std::vector<Node> values; | ||
| 52 | constexpr std::size_t hardcoded_size{4}; | ||
| 53 | for (std::size_t i = 0; i < hardcoded_size; ++i) { | ||
| 54 | values.push_back(GetRegister(instr.gpr0.Value() + i)); | ||
| 55 | } | ||
| 56 | |||
| 57 | std::vector<Node> coords; | ||
| 58 | const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; | ||
| 59 | for (std::size_t i = 0; i < num_coords; ++i) { | ||
| 60 | coords.push_back(GetRegister(instr.gpr8.Value() + i)); | ||
| 61 | } | ||
| 62 | |||
| 63 | const auto type{instr.sust.image_type}; | ||
| 64 | const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) | ||
| 65 | : GetBindlessImage(instr.gpr39, type)}; | ||
| 66 | MetaImage meta{image, values}; | ||
| 67 | const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; | ||
| 68 | bb.push_back(store); | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | default: | ||
| 72 | UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); | ||
| 73 | } | ||
| 74 | |||
| 75 | return pc; | ||
| 76 | } | ||
| 77 | |||
| 78 | const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { | ||
| 79 | const auto offset{static_cast<std::size_t>(image.index.Value())}; | ||
| 80 | |||
| 81 | // If this image has already been used, return the existing mapping. | ||
| 82 | const auto itr{std::find_if(used_images.begin(), used_images.end(), | ||
| 83 | [=](const Image& entry) { return entry.GetOffset() == offset; })}; | ||
| 84 | if (itr != used_images.end()) { | ||
| 85 | ASSERT(itr->GetType() == type); | ||
| 86 | return *itr; | ||
| 87 | } | ||
| 88 | |||
| 89 | // Otherwise create a new mapping for this image. | ||
| 90 | const std::size_t next_index{used_images.size()}; | ||
| 91 | const Image entry{offset, next_index, type}; | ||
| 92 | return *used_images.emplace(entry).first; | ||
| 93 | } | ||
| 94 | |||
| 95 | const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, | ||
| 96 | Tegra::Shader::ImageType type) { | ||
| 97 | const Node image_register{GetRegister(reg)}; | ||
| 98 | const Node base_image{ | ||
| 99 | TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; | ||
| 100 | const auto cbuf{std::get_if<CbufNode>(&*base_image)}; | ||
| 101 | const auto cbuf_offset_imm{std::get_if<ImmediateNode>(&*cbuf->GetOffset())}; | ||
| 102 | const auto cbuf_offset{cbuf_offset_imm->GetValue()}; | ||
| 103 | const auto cbuf_index{cbuf->GetIndex()}; | ||
| 104 | const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; | ||
| 105 | |||
| 106 | // If this image has already been used, return the existing mapping. | ||
| 107 | const auto itr{std::find_if(used_images.begin(), used_images.end(), | ||
| 108 | [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; | ||
| 109 | if (itr != used_images.end()) { | ||
| 110 | ASSERT(itr->GetType() == type); | ||
| 111 | return *itr; | ||
| 112 | } | ||
| 113 | |||
| 114 | // Otherwise create a new mapping for this image. | ||
| 115 | const std::size_t next_index{used_images.size()}; | ||
| 116 | const Image entry{cbuf_index, cbuf_offset, next_index, type}; | ||
| 117 | return *used_images.emplace(entry).first; | ||
| 118 | } | ||
| 119 | |||
| 120 | } // namespace VideoCommon::Shader | ||
diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index 4a356dbd4..cb480be9b 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp | |||
| @@ -245,6 +245,18 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { | |||
| 245 | } | 245 | } |
| 246 | break; | 246 | break; |
| 247 | } | 247 | } |
| 248 | case OpCode::Id::TLD: { | ||
| 249 | UNIMPLEMENTED_IF_MSG(instr.tld.aoffi, "AOFFI is not implemented"); | ||
| 250 | UNIMPLEMENTED_IF_MSG(instr.tld.ms, "MS is not implemented"); | ||
| 251 | UNIMPLEMENTED_IF_MSG(instr.tld.cl, "CL is not implemented"); | ||
| 252 | |||
| 253 | if (instr.tld.nodep_flag) { | ||
| 254 | LOG_WARNING(HW_GPU, "TLD.NODEP implementation is incomplete"); | ||
| 255 | } | ||
| 256 | |||
| 257 | WriteTexInstructionFloat(bb, instr, GetTldCode(instr)); | ||
| 258 | break; | ||
| 259 | } | ||
| 248 | case OpCode::Id::TLDS: { | 260 | case OpCode::Id::TLDS: { |
| 249 | const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; | 261 | const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; |
| 250 | const bool is_array{instr.tlds.IsArrayTexture()}; | 262 | const bool is_array{instr.tlds.IsArrayTexture()}; |
| @@ -575,6 +587,39 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de | |||
| 575 | return values; | 587 | return values; |
| 576 | } | 588 | } |
| 577 | 589 | ||
| 590 | Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { | ||
| 591 | const auto texture_type{instr.tld.texture_type}; | ||
| 592 | const bool is_array{instr.tld.is_array}; | ||
| 593 | const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; | ||
| 594 | const std::size_t coord_count{GetCoordCount(texture_type)}; | ||
| 595 | |||
| 596 | u64 gpr8_cursor{instr.gpr8.Value()}; | ||
| 597 | const Node array_register{is_array ? GetRegister(gpr8_cursor++) : nullptr}; | ||
| 598 | |||
| 599 | std::vector<Node> coords; | ||
| 600 | coords.reserve(coord_count); | ||
| 601 | for (std::size_t i = 0; i < coord_count; ++i) { | ||
| 602 | coords.push_back(GetRegister(gpr8_cursor++)); | ||
| 603 | } | ||
| 604 | |||
| 605 | u64 gpr20_cursor{instr.gpr20.Value()}; | ||
| 606 | // const Node bindless_register{is_bindless ? GetRegister(gpr20_cursor++) : nullptr}; | ||
| 607 | const Node lod{lod_enabled ? GetRegister(gpr20_cursor++) : Immediate(0u)}; | ||
| 608 | // const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr}; | ||
| 609 | // const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr}; | ||
| 610 | |||
| 611 | const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); | ||
| 612 | |||
| 613 | Node4 values; | ||
| 614 | for (u32 element = 0; element < values.size(); ++element) { | ||
| 615 | auto coords_copy = coords; | ||
| 616 | MetaTexture meta{sampler, array_register, {}, {}, {}, lod, {}, element}; | ||
| 617 | values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); | ||
| 618 | } | ||
| 619 | |||
| 620 | return values; | ||
| 621 | } | ||
| 622 | |||
| 578 | Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { | 623 | Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { |
| 579 | const std::size_t type_coord_count = GetCoordCount(texture_type); | 624 | const std::size_t type_coord_count = GetCoordCount(texture_type); |
| 580 | const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; | 625 | const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; |
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 3cfb911bb..0ac83fcf0 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h | |||
| @@ -146,6 +146,8 @@ enum class OperationCode { | |||
| 146 | TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 | 146 | TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 |
| 147 | TexelFetch, /// (MetaTexture, int[N], int) -> float4 | 147 | TexelFetch, /// (MetaTexture, int[N], int) -> float4 |
| 148 | 148 | ||
| 149 | ImageStore, /// (MetaImage, float[N] coords) -> void | ||
| 150 | |||
| 149 | Branch, /// (uint branch_target) -> void | 151 | Branch, /// (uint branch_target) -> void |
| 150 | PushFlowStack, /// (uint branch_target) -> void | 152 | PushFlowStack, /// (uint branch_target) -> void |
| 151 | PopFlowStack, /// () -> void | 153 | PopFlowStack, /// () -> void |
| @@ -263,6 +265,48 @@ private: | |||
| 263 | bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. | 265 | bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. |
| 264 | }; | 266 | }; |
| 265 | 267 | ||
| 268 | class Image { | ||
| 269 | public: | ||
| 270 | explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) | ||
| 271 | : offset{offset}, index{index}, type{type}, is_bindless{false} {} | ||
| 272 | |||
| 273 | explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, | ||
| 274 | Tegra::Shader::ImageType type) | ||
| 275 | : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, | ||
| 276 | is_bindless{true} {} | ||
| 277 | |||
| 278 | explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, | ||
| 279 | bool is_bindless) | ||
| 280 | : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} | ||
| 281 | |||
| 282 | std::size_t GetOffset() const { | ||
| 283 | return offset; | ||
| 284 | } | ||
| 285 | |||
| 286 | std::size_t GetIndex() const { | ||
| 287 | return index; | ||
| 288 | } | ||
| 289 | |||
| 290 | Tegra::Shader::ImageType GetType() const { | ||
| 291 | return type; | ||
| 292 | } | ||
| 293 | |||
| 294 | bool IsBindless() const { | ||
| 295 | return is_bindless; | ||
| 296 | } | ||
| 297 | |||
| 298 | bool operator<(const Image& rhs) const { | ||
| 299 | return std::tie(offset, index, type, is_bindless) < | ||
| 300 | std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); | ||
| 301 | } | ||
| 302 | |||
| 303 | private: | ||
| 304 | std::size_t offset{}; | ||
| 305 | std::size_t index{}; | ||
| 306 | Tegra::Shader::ImageType type{}; | ||
| 307 | bool is_bindless{}; | ||
| 308 | }; | ||
| 309 | |||
| 266 | struct GlobalMemoryBase { | 310 | struct GlobalMemoryBase { |
| 267 | u32 cbuf_index{}; | 311 | u32 cbuf_index{}; |
| 268 | u32 cbuf_offset{}; | 312 | u32 cbuf_offset{}; |
| @@ -289,8 +333,14 @@ struct MetaTexture { | |||
| 289 | u32 element{}; | 333 | u32 element{}; |
| 290 | }; | 334 | }; |
| 291 | 335 | ||
| 336 | struct MetaImage { | ||
| 337 | const Image& image; | ||
| 338 | std::vector<Node> values; | ||
| 339 | }; | ||
| 340 | |||
| 292 | /// Parameters that modify an operation but are not part of any particular operand | 341 | /// Parameters that modify an operation but are not part of any particular operand |
| 293 | using Meta = std::variant<MetaArithmetic, MetaTexture, MetaStackClass, Tegra::Shader::HalfType>; | 342 | using Meta = |
| 343 | std::variant<MetaArithmetic, MetaTexture, MetaImage, MetaStackClass, Tegra::Shader::HalfType>; | ||
| 294 | 344 | ||
| 295 | /// Holds any kind of operation that can be done in the IR | 345 | /// Holds any kind of operation that can be done in the IR |
| 296 | class OperationNode final { | 346 | class OperationNode final { |
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index edcf2288e..e22548208 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h | |||
| @@ -104,6 +104,10 @@ public: | |||
| 104 | return used_samplers; | 104 | return used_samplers; |
| 105 | } | 105 | } |
| 106 | 106 | ||
| 107 | const std::set<Image>& GetImages() const { | ||
| 108 | return used_images; | ||
| 109 | } | ||
| 110 | |||
| 107 | const std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances>& GetClipDistances() | 111 | const std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances>& GetClipDistances() |
| 108 | const { | 112 | const { |
| 109 | return used_clip_distances; | 113 | return used_clip_distances; |
| @@ -154,6 +158,7 @@ private: | |||
| 154 | u32 DecodeConversion(NodeBlock& bb, u32 pc); | 158 | u32 DecodeConversion(NodeBlock& bb, u32 pc); |
| 155 | u32 DecodeMemory(NodeBlock& bb, u32 pc); | 159 | u32 DecodeMemory(NodeBlock& bb, u32 pc); |
| 156 | u32 DecodeTexture(NodeBlock& bb, u32 pc); | 160 | u32 DecodeTexture(NodeBlock& bb, u32 pc); |
| 161 | u32 DecodeImage(NodeBlock& bb, u32 pc); | ||
| 157 | u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc); | 162 | u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc); |
| 158 | u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc); | 163 | u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc); |
| 159 | u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc); | 164 | u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc); |
| @@ -254,6 +259,12 @@ private: | |||
| 254 | Tegra::Shader::TextureType type, bool is_array, | 259 | Tegra::Shader::TextureType type, bool is_array, |
| 255 | bool is_shadow); | 260 | bool is_shadow); |
| 256 | 261 | ||
| 262 | /// Accesses an image. | ||
| 263 | const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); | ||
| 264 | |||
| 265 | /// Access a bindless image sampler. | ||
| 266 | const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); | ||
| 267 | |||
| 257 | /// Extracts a sequence of bits from a node | 268 | /// Extracts a sequence of bits from a node |
| 258 | Node BitfieldExtract(Node value, u32 offset, u32 bits); | 269 | Node BitfieldExtract(Node value, u32 offset, u32 bits); |
| 259 | 270 | ||
| @@ -277,6 +288,8 @@ private: | |||
| 277 | Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, | 288 | Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, |
| 278 | bool depth_compare, bool is_array, bool is_aoffi); | 289 | bool depth_compare, bool is_array, bool is_aoffi); |
| 279 | 290 | ||
| 291 | Node4 GetTldCode(Tegra::Shader::Instruction instr); | ||
| 292 | |||
| 280 | Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, | 293 | Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, |
| 281 | bool is_array); | 294 | bool is_array); |
| 282 | 295 | ||
| @@ -327,6 +340,7 @@ private: | |||
| 327 | std::set<Tegra::Shader::Attribute::Index> used_output_attributes; | 340 | std::set<Tegra::Shader::Attribute::Index> used_output_attributes; |
| 328 | std::map<u32, ConstBuffer> used_cbufs; | 341 | std::map<u32, ConstBuffer> used_cbufs; |
| 329 | std::set<Sampler> used_samplers; | 342 | std::set<Sampler> used_samplers; |
| 343 | std::set<Image> used_images; | ||
| 330 | std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; | 344 | std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; |
| 331 | std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; | 345 | std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; |
| 332 | bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes | 346 | bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes |