texture_pass.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. // Copyright 2021 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <optional>
  5. #include <boost/container/flat_set.hpp>
  6. #include <boost/container/small_vector.hpp>
  7. #include "shader_recompiler/environment.h"
  8. #include "shader_recompiler/frontend/ir/basic_block.h"
  9. #include "shader_recompiler/frontend/ir/ir_emitter.h"
  10. #include "shader_recompiler/ir_opt/passes.h"
  11. #include "shader_recompiler/shader_info.h"
  12. namespace Shader::Optimization {
  13. namespace {
  14. struct ConstBufferAddr {
  15. u32 index;
  16. u32 offset;
  17. };
  18. struct TextureInst {
  19. ConstBufferAddr cbuf;
  20. IR::Inst* inst;
  21. IR::Block* block;
  22. };
  23. using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
  24. using VisitedBlocks = boost::container::flat_set<IR::Block*, std::less<IR::Block*>,
  25. boost::container::small_vector<IR::Block*, 2>>;
  26. IR::Opcode IndexedInstruction(const IR::Inst& inst) {
  27. switch (inst.Opcode()) {
  28. case IR::Opcode::BindlessImageSampleImplicitLod:
  29. case IR::Opcode::BoundImageSampleImplicitLod:
  30. return IR::Opcode::ImageSampleImplicitLod;
  31. case IR::Opcode::BoundImageSampleExplicitLod:
  32. case IR::Opcode::BindlessImageSampleExplicitLod:
  33. return IR::Opcode::ImageSampleExplicitLod;
  34. case IR::Opcode::BoundImageSampleDrefImplicitLod:
  35. case IR::Opcode::BindlessImageSampleDrefImplicitLod:
  36. return IR::Opcode::ImageSampleDrefImplicitLod;
  37. case IR::Opcode::BoundImageSampleDrefExplicitLod:
  38. case IR::Opcode::BindlessImageSampleDrefExplicitLod:
  39. return IR::Opcode::ImageSampleDrefExplicitLod;
  40. case IR::Opcode::BindlessImageGather:
  41. case IR::Opcode::BoundImageGather:
  42. return IR::Opcode::ImageGather;
  43. case IR::Opcode::BindlessImageGatherDref:
  44. case IR::Opcode::BoundImageGatherDref:
  45. return IR::Opcode::ImageGatherDref;
  46. case IR::Opcode::BindlessImageFetch:
  47. case IR::Opcode::BoundImageFetch:
  48. return IR::Opcode::ImageFetch;
  49. case IR::Opcode::BoundImageQueryDimensions:
  50. case IR::Opcode::BindlessImageQueryDimensions:
  51. return IR::Opcode::ImageQueryDimensions;
  52. case IR::Opcode::BoundImageQueryLod:
  53. case IR::Opcode::BindlessImageQueryLod:
  54. return IR::Opcode::ImageQueryLod;
  55. case IR::Opcode::BoundImageGradient:
  56. case IR::Opcode::BindlessImageGradient:
  57. return IR::Opcode::ImageGradient;
  58. default:
  59. return IR::Opcode::Void;
  60. }
  61. }
  62. bool IsBindless(const IR::Inst& inst) {
  63. switch (inst.Opcode()) {
  64. case IR::Opcode::BindlessImageSampleImplicitLod:
  65. case IR::Opcode::BindlessImageSampleExplicitLod:
  66. case IR::Opcode::BindlessImageSampleDrefImplicitLod:
  67. case IR::Opcode::BindlessImageSampleDrefExplicitLod:
  68. case IR::Opcode::BindlessImageGather:
  69. case IR::Opcode::BindlessImageGatherDref:
  70. case IR::Opcode::BindlessImageFetch:
  71. case IR::Opcode::BindlessImageQueryDimensions:
  72. case IR::Opcode::BindlessImageQueryLod:
  73. case IR::Opcode::BindlessImageGradient:
  74. return true;
  75. case IR::Opcode::BoundImageSampleImplicitLod:
  76. case IR::Opcode::BoundImageSampleExplicitLod:
  77. case IR::Opcode::BoundImageSampleDrefImplicitLod:
  78. case IR::Opcode::BoundImageSampleDrefExplicitLod:
  79. case IR::Opcode::BoundImageGather:
  80. case IR::Opcode::BoundImageGatherDref:
  81. case IR::Opcode::BoundImageFetch:
  82. case IR::Opcode::BoundImageQueryDimensions:
  83. case IR::Opcode::BoundImageQueryLod:
  84. case IR::Opcode::BoundImageGradient:
  85. return false;
  86. default:
  87. throw InvalidArgument("Invalid opcode {}", inst.Opcode());
  88. }
  89. }
  90. bool IsTextureInstruction(const IR::Inst& inst) {
  91. return IndexedInstruction(inst) != IR::Opcode::Void;
  92. }
  93. std::optional<ConstBufferAddr> Track(IR::Block* block, const IR::Value& value,
  94. VisitedBlocks& visited) {
  95. if (value.IsImmediate()) {
  96. // Immediates can't be a storage buffer
  97. return std::nullopt;
  98. }
  99. const IR::Inst* const inst{value.InstRecursive()};
  100. if (inst->Opcode() == IR::Opcode::GetCbufU32) {
  101. const IR::Value index{inst->Arg(0)};
  102. const IR::Value offset{inst->Arg(1)};
  103. if (!index.IsImmediate()) {
  104. // Reading a bindless texture from variable indices is valid
  105. // but not supported here at the moment
  106. return std::nullopt;
  107. }
  108. if (!offset.IsImmediate()) {
  109. // TODO: Support arrays of textures
  110. return std::nullopt;
  111. }
  112. return ConstBufferAddr{
  113. .index{index.U32()},
  114. .offset{offset.U32()},
  115. };
  116. }
  117. // Reversed loops are more likely to find the right result
  118. for (size_t arg = inst->NumArgs(); arg--;) {
  119. IR::Block* inst_block{block};
  120. if (inst->Opcode() == IR::Opcode::Phi) {
  121. // If we are going through a phi node, mark the current block as visited
  122. visited.insert(block);
  123. // and skip already visited blocks to avoid looping forever
  124. IR::Block* const phi_block{inst->PhiBlock(arg)};
  125. if (visited.contains(phi_block)) {
  126. // Already visited, skip
  127. continue;
  128. }
  129. inst_block = phi_block;
  130. }
  131. const std::optional storage_buffer{Track(inst_block, inst->Arg(arg), visited)};
  132. if (storage_buffer) {
  133. return *storage_buffer;
  134. }
  135. }
  136. return std::nullopt;
  137. }
  138. TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
  139. ConstBufferAddr addr;
  140. if (IsBindless(inst)) {
  141. VisitedBlocks visited;
  142. const std::optional<ConstBufferAddr> track_addr{Track(block, inst.Arg(0), visited)};
  143. if (!track_addr) {
  144. throw NotImplementedException("Failed to track bindless texture constant buffer");
  145. }
  146. addr = *track_addr;
  147. } else {
  148. addr = ConstBufferAddr{
  149. .index{env.TextureBoundBuffer()},
  150. .offset{inst.Arg(0).U32()},
  151. };
  152. }
  153. return TextureInst{
  154. .cbuf{addr},
  155. .inst{&inst},
  156. .block{block},
  157. };
  158. }
  159. class Descriptors {
  160. public:
  161. explicit Descriptors(TextureDescriptors& descriptors_) : descriptors{descriptors_} {}
  162. u32 Add(const TextureDescriptor& descriptor) {
  163. // TODO: Handle arrays
  164. auto it{std::ranges::find_if(descriptors, [&descriptor](const TextureDescriptor& existing) {
  165. return descriptor.cbuf_index == existing.cbuf_index &&
  166. descriptor.cbuf_offset == existing.cbuf_offset &&
  167. descriptor.type == existing.type;
  168. })};
  169. if (it != descriptors.end()) {
  170. return static_cast<u32>(std::distance(descriptors.begin(), it));
  171. }
  172. descriptors.push_back(descriptor);
  173. return static_cast<u32>(descriptors.size()) - 1;
  174. }
  175. private:
  176. TextureDescriptors& descriptors;
  177. };
  178. } // Anonymous namespace
  179. void TexturePass(Environment& env, IR::Program& program) {
  180. TextureInstVector to_replace;
  181. for (IR::Block* const block : program.post_order_blocks) {
  182. for (IR::Inst& inst : block->Instructions()) {
  183. if (!IsTextureInstruction(inst)) {
  184. continue;
  185. }
  186. to_replace.push_back(MakeInst(env, block, inst));
  187. }
  188. }
  189. // Sort instructions to visit textures by constant buffer index, then by offset
  190. std::ranges::sort(to_replace, [](const auto& lhs, const auto& rhs) {
  191. return lhs.cbuf.offset < rhs.cbuf.offset;
  192. });
  193. std::stable_sort(to_replace.begin(), to_replace.end(), [](const auto& lhs, const auto& rhs) {
  194. return lhs.cbuf.index < rhs.cbuf.index;
  195. });
  196. Descriptors descriptors{program.info.texture_descriptors};
  197. for (TextureInst& texture_inst : to_replace) {
  198. // TODO: Handle arrays
  199. IR::Inst* const inst{texture_inst.inst};
  200. inst->ReplaceOpcode(IndexedInstruction(*inst));
  201. const auto& cbuf{texture_inst.cbuf};
  202. auto flags{inst->Flags<IR::TextureInstInfo>()};
  203. if (inst->Opcode() == IR::Opcode::ImageQueryDimensions) {
  204. flags.type.Assign(env.ReadTextureType(cbuf.index, cbuf.offset));
  205. inst->SetFlags(flags);
  206. }
  207. const u32 index{descriptors.Add(TextureDescriptor{
  208. .type{flags.type},
  209. .cbuf_index{cbuf.index},
  210. .cbuf_offset{cbuf.offset},
  211. .count{1},
  212. })};
  213. inst->SetArg(0, IR::Value{index});
  214. }
  215. }
  216. } // namespace Shader::Optimization