diff options
Diffstat (limited to 'src/shader_recompiler')
4 files changed, 106 insertions, 104 deletions
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt index 181eac9f2..700b17113 100644 --- a/src/shader_recompiler/CMakeLists.txt +++ b/src/shader_recompiler/CMakeLists.txt | |||
| @@ -27,6 +27,7 @@ add_library(shader_recompiler STATIC | |||
| 27 | frontend/ir/attribute.h | 27 | frontend/ir/attribute.h |
| 28 | frontend/ir/basic_block.cpp | 28 | frontend/ir/basic_block.cpp |
| 29 | frontend/ir/basic_block.h | 29 | frontend/ir/basic_block.h |
| 30 | frontend/ir/breadth_first_search.h | ||
| 30 | frontend/ir/condition.cpp | 31 | frontend/ir/condition.cpp |
| 31 | frontend/ir/condition.h | 32 | frontend/ir/condition.h |
| 32 | frontend/ir/flow_test.cpp | 33 | frontend/ir/flow_test.cpp |
diff --git a/src/shader_recompiler/frontend/ir/breadth_first_search.h b/src/shader_recompiler/frontend/ir/breadth_first_search.h new file mode 100644 index 000000000..b35f062d4 --- /dev/null +++ b/src/shader_recompiler/frontend/ir/breadth_first_search.h | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <optional> | ||
| 8 | #include <type_traits> | ||
| 9 | #include <queue> | ||
| 10 | |||
| 11 | #include <boost/container/small_vector.hpp> | ||
| 12 | |||
| 13 | #include "shader_recompiler/frontend/ir/microinstruction.h" | ||
| 14 | #include "shader_recompiler/frontend/ir/value.h" | ||
| 15 | |||
| 16 | namespace Shader::IR { | ||
| 17 | |||
| 18 | template <typename Pred> | ||
| 19 | auto BreadthFirstSearch(const Value& value, Pred&& pred) | ||
| 20 | -> std::invoke_result_t<Pred, const Inst*> { | ||
| 21 | if (value.IsImmediate()) { | ||
| 22 | // Nothing to do with immediates | ||
| 23 | return std::nullopt; | ||
| 24 | } | ||
| 25 | // Breadth-first search visiting the right most arguments first | ||
| 26 | // Small vector has been determined from shaders in Super Smash Bros. Ultimate | ||
| 27 | boost::container::small_vector<const Inst*, 2> visited; | ||
| 28 | std::queue<const Inst*> queue; | ||
| 29 | queue.push(value.InstRecursive()); | ||
| 30 | |||
| 31 | while (!queue.empty()) { | ||
| 32 | // Pop one instruction from the queue | ||
| 33 | const Inst* const inst{queue.front()}; | ||
| 34 | queue.pop(); | ||
| 35 | if (const std::optional result = pred(inst)) { | ||
| 36 | // This is the instruction we were looking for | ||
| 37 | return result; | ||
| 38 | } | ||
| 39 | // Visit the right most arguments first | ||
| 40 | for (size_t arg = inst->NumArgs(); arg--;) { | ||
| 41 | const Value arg_value{inst->Arg(arg)}; | ||
| 42 | if (arg_value.IsImmediate()) { | ||
| 43 | continue; | ||
| 44 | } | ||
| 45 | // Queue instruction if it hasn't been visited | ||
| 46 | const Inst* const arg_inst{arg_value.InstRecursive()}; | ||
| 47 | if (std::ranges::find(visited, arg_inst) == visited.end()) { | ||
| 48 | visited.push_back(arg_inst); | ||
| 49 | queue.push(arg_inst); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | // SSA tree has been traversed and the result hasn't been found | ||
| 54 | return std::nullopt; | ||
| 55 | } | ||
| 56 | |||
| 57 | } // namespace Shader::IR | ||
diff --git a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp index f94c82e21..0858a0bdd 100644 --- a/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp +++ b/src/shader_recompiler/ir_opt/global_memory_to_storage_buffer_pass.cpp | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include <boost/container/small_vector.hpp> | 12 | #include <boost/container/small_vector.hpp> |
| 13 | 13 | ||
| 14 | #include "shader_recompiler/frontend/ir/basic_block.h" | 14 | #include "shader_recompiler/frontend/ir/basic_block.h" |
| 15 | #include "shader_recompiler/frontend/ir/breadth_first_search.h" | ||
| 15 | #include "shader_recompiler/frontend/ir/ir_emitter.h" | 16 | #include "shader_recompiler/frontend/ir/ir_emitter.h" |
| 16 | #include "shader_recompiler/frontend/ir/microinstruction.h" | 17 | #include "shader_recompiler/frontend/ir/microinstruction.h" |
| 17 | #include "shader_recompiler/ir_opt/passes.h" | 18 | #include "shader_recompiler/ir_opt/passes.h" |
| @@ -219,68 +220,35 @@ std::optional<LowAddrInfo> TrackLowAddress(IR::Inst* inst) { | |||
| 219 | }; | 220 | }; |
| 220 | } | 221 | } |
| 221 | 222 | ||
| 222 | /// Tries to get the storage buffer out of a constant buffer read instruction | ||
| 223 | std::optional<StorageBufferAddr> TryGetStorageBuffer(const IR::Inst* inst, const Bias* bias) { | ||
| 224 | if (inst->Opcode() != IR::Opcode::GetCbufU32) { | ||
| 225 | return std::nullopt; | ||
| 226 | } | ||
| 227 | const IR::Value index{inst->Arg(0)}; | ||
| 228 | const IR::Value offset{inst->Arg(1)}; | ||
| 229 | if (!index.IsImmediate()) { | ||
| 230 | // Definitely not a storage buffer if it's read from a non-immediate index | ||
| 231 | return std::nullopt; | ||
| 232 | } | ||
| 233 | if (!offset.IsImmediate()) { | ||
| 234 | // TODO: Support SSBO arrays | ||
| 235 | return std::nullopt; | ||
| 236 | } | ||
| 237 | const StorageBufferAddr storage_buffer{ | ||
| 238 | .index{index.U32()}, | ||
| 239 | .offset{offset.U32()}, | ||
| 240 | }; | ||
| 241 | if (bias && !MeetsBias(storage_buffer, *bias)) { | ||
| 242 | // We have to blacklist some addresses in case we wrongly point to them | ||
| 243 | return std::nullopt; | ||
| 244 | } | ||
| 245 | return storage_buffer; | ||
| 246 | } | ||
| 247 | |||
| 248 | /// Tries to track the storage buffer address used by a global memory instruction | 223 | /// Tries to track the storage buffer address used by a global memory instruction |
| 249 | std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias) { | 224 | std::optional<StorageBufferAddr> Track(const IR::Value& value, const Bias* bias) { |
| 250 | if (value.IsImmediate()) { | 225 | const auto pred{[bias](const IR::Inst* inst) -> std::optional<StorageBufferAddr> { |
| 251 | // Nothing to do with immediates | 226 | if (inst->Opcode() != IR::Opcode::GetCbufU32) { |
| 252 | return std::nullopt; | 227 | return std::nullopt; |
| 253 | } | ||
| 254 | // Breadth-first search visiting the right most arguments first | ||
| 255 | // Small vector has been determined from shaders in Super Smash Bros. Ultimate | ||
| 256 | small_vector<const IR::Inst*, 2> visited; | ||
| 257 | std::queue<const IR::Inst*> queue; | ||
| 258 | queue.push(value.InstRecursive()); | ||
| 259 | |||
| 260 | while (!queue.empty()) { | ||
| 261 | // Pop one instruction from the queue | ||
| 262 | const IR::Inst* const inst{queue.front()}; | ||
| 263 | queue.pop(); | ||
| 264 | if (const std::optional<StorageBufferAddr> result = TryGetStorageBuffer(inst, bias)) { | ||
| 265 | // This is the instruction we were looking for | ||
| 266 | return result; | ||
| 267 | } | 228 | } |
| 268 | // Visit the right most arguments first | 229 | const IR::Value index{inst->Arg(0)}; |
| 269 | for (size_t arg = inst->NumArgs(); arg--;) { | 230 | const IR::Value offset{inst->Arg(1)}; |
| 270 | const IR::Value arg_value{inst->Arg(arg)}; | 231 | if (!index.IsImmediate()) { |
| 271 | if (arg_value.IsImmediate()) { | 232 | // Definitely not a storage buffer if it's read from a |
| 272 | continue; | 233 | // non-immediate index |
| 273 | } | 234 | return std::nullopt; |
| 274 | // Queue instruction if it hasn't been visited | ||
| 275 | const IR::Inst* const arg_inst{arg_value.InstRecursive()}; | ||
| 276 | if (std::ranges::find(visited, arg_inst) == visited.end()) { | ||
| 277 | visited.push_back(arg_inst); | ||
| 278 | queue.push(arg_inst); | ||
| 279 | } | ||
| 280 | } | 235 | } |
| 281 | } | 236 | if (!offset.IsImmediate()) { |
| 282 | // SSA tree has been traversed and the origin hasn't been found | 237 | // TODO: Support SSBO arrays |
| 283 | return std::nullopt; | 238 | return std::nullopt; |
| 239 | } | ||
| 240 | const StorageBufferAddr storage_buffer{ | ||
| 241 | .index{index.U32()}, | ||
| 242 | .offset{offset.U32()}, | ||
| 243 | }; | ||
| 244 | if (bias && !MeetsBias(storage_buffer, *bias)) { | ||
| 245 | // We have to blacklist some addresses in case we wrongly | ||
| 246 | // point to them | ||
| 247 | return std::nullopt; | ||
| 248 | } | ||
| 249 | return storage_buffer; | ||
| 250 | }}; | ||
| 251 | return BreadthFirstSearch(value, pred); | ||
| 284 | } | 252 | } |
| 285 | 253 | ||
| 286 | /// Collects the storage buffer used by a global memory instruction and the instruction itself | 254 | /// Collects the storage buffer used by a global memory instruction and the instruction itself |
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index da8977b76..bcb94ce4d 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp | |||
| @@ -2,13 +2,14 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | ||
| 5 | #include <optional> | 6 | #include <optional> |
| 6 | 7 | ||
| 7 | #include <boost/container/flat_set.hpp> | ||
| 8 | #include <boost/container/small_vector.hpp> | 8 | #include <boost/container/small_vector.hpp> |
| 9 | 9 | ||
| 10 | #include "shader_recompiler/environment.h" | 10 | #include "shader_recompiler/environment.h" |
| 11 | #include "shader_recompiler/frontend/ir/basic_block.h" | 11 | #include "shader_recompiler/frontend/ir/basic_block.h" |
| 12 | #include "shader_recompiler/frontend/ir/breadth_first_search.h" | ||
| 12 | #include "shader_recompiler/frontend/ir/ir_emitter.h" | 13 | #include "shader_recompiler/frontend/ir/ir_emitter.h" |
| 13 | #include "shader_recompiler/ir_opt/passes.h" | 14 | #include "shader_recompiler/ir_opt/passes.h" |
| 14 | #include "shader_recompiler/shader_info.h" | 15 | #include "shader_recompiler/shader_info.h" |
| @@ -28,9 +29,6 @@ struct TextureInst { | |||
| 28 | 29 | ||
| 29 | using TextureInstVector = boost::container::small_vector<TextureInst, 24>; | 30 | using TextureInstVector = boost::container::small_vector<TextureInst, 24>; |
| 30 | 31 | ||
| 31 | using VisitedBlocks = boost::container::flat_set<IR::Block*, std::less<IR::Block*>, | ||
| 32 | boost::container::small_vector<IR::Block*, 2>>; | ||
| 33 | |||
| 34 | IR::Opcode IndexedInstruction(const IR::Inst& inst) { | 32 | IR::Opcode IndexedInstruction(const IR::Inst& inst) { |
| 35 | switch (inst.Opcode()) { | 33 | switch (inst.Opcode()) { |
| 36 | case IR::Opcode::BindlessImageSampleImplicitLod: | 34 | case IR::Opcode::BindlessImageSampleImplicitLod: |
| @@ -101,57 +99,35 @@ bool IsTextureInstruction(const IR::Inst& inst) { | |||
| 101 | return IndexedInstruction(inst) != IR::Opcode::Void; | 99 | return IndexedInstruction(inst) != IR::Opcode::Void; |
| 102 | } | 100 | } |
| 103 | 101 | ||
| 104 | std::optional<ConstBufferAddr> Track(IR::Block* block, const IR::Value& value, | 102 | std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) { |
| 105 | VisitedBlocks& visited) { | 103 | if (inst->Opcode() != IR::Opcode::GetCbufU32) { |
| 106 | if (value.IsImmediate()) { | ||
| 107 | // Immediates can't be a storage buffer | ||
| 108 | return std::nullopt; | 104 | return std::nullopt; |
| 109 | } | 105 | } |
| 110 | const IR::Inst* const inst{value.InstRecursive()}; | 106 | const IR::Value index{inst->Arg(0)}; |
| 111 | if (inst->Opcode() == IR::Opcode::GetCbufU32) { | 107 | const IR::Value offset{inst->Arg(1)}; |
| 112 | const IR::Value index{inst->Arg(0)}; | 108 | if (!index.IsImmediate()) { |
| 113 | const IR::Value offset{inst->Arg(1)}; | 109 | // Reading a bindless texture from variable indices is valid |
| 114 | if (!index.IsImmediate()) { | 110 | // but not supported here at the moment |
| 115 | // Reading a bindless texture from variable indices is valid | 111 | return std::nullopt; |
| 116 | // but not supported here at the moment | ||
| 117 | return std::nullopt; | ||
| 118 | } | ||
| 119 | if (!offset.IsImmediate()) { | ||
| 120 | // TODO: Support arrays of textures | ||
| 121 | return std::nullopt; | ||
| 122 | } | ||
| 123 | return ConstBufferAddr{ | ||
| 124 | .index{index.U32()}, | ||
| 125 | .offset{offset.U32()}, | ||
| 126 | }; | ||
| 127 | } | 112 | } |
| 128 | // Reversed loops are more likely to find the right result | 113 | if (!offset.IsImmediate()) { |
| 129 | for (size_t arg = inst->NumArgs(); arg--;) { | 114 | // TODO: Support arrays of textures |
| 130 | IR::Block* inst_block{block}; | 115 | return std::nullopt; |
| 131 | if (inst->Opcode() == IR::Opcode::Phi) { | ||
| 132 | // If we are going through a phi node, mark the current block as visited | ||
| 133 | visited.insert(block); | ||
| 134 | // and skip already visited blocks to avoid looping forever | ||
| 135 | IR::Block* const phi_block{inst->PhiBlock(arg)}; | ||
| 136 | if (visited.contains(phi_block)) { | ||
| 137 | // Already visited, skip | ||
| 138 | continue; | ||
| 139 | } | ||
| 140 | inst_block = phi_block; | ||
| 141 | } | ||
| 142 | const std::optional storage_buffer{Track(inst_block, inst->Arg(arg), visited)}; | ||
| 143 | if (storage_buffer) { | ||
| 144 | return *storage_buffer; | ||
| 145 | } | ||
| 146 | } | 116 | } |
| 147 | return std::nullopt; | 117 | return ConstBufferAddr{ |
| 118 | .index{index.U32()}, | ||
| 119 | .offset{offset.U32()}, | ||
| 120 | }; | ||
| 121 | } | ||
| 122 | |||
| 123 | std::optional<ConstBufferAddr> Track(const IR::Value& value) { | ||
| 124 | return IR::BreadthFirstSearch(value, TryGetConstBuffer); | ||
| 148 | } | 125 | } |
| 149 | 126 | ||
| 150 | TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { | 127 | TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) { |
| 151 | ConstBufferAddr addr; | 128 | ConstBufferAddr addr; |
| 152 | if (IsBindless(inst)) { | 129 | if (IsBindless(inst)) { |
| 153 | VisitedBlocks visited; | 130 | const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0))}; |
| 154 | const std::optional<ConstBufferAddr> track_addr{Track(block, inst.Arg(0), visited)}; | ||
| 155 | if (!track_addr) { | 131 | if (!track_addr) { |
| 156 | throw NotImplementedException("Failed to track bindless texture constant buffer"); | 132 | throw NotImplementedException("Failed to track bindless texture constant buffer"); |
| 157 | } | 133 | } |