texture_pass.cpp 7.2 KB

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