texture_pass.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  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 <bit>
  6. #include <optional>
  7. #include <boost/container/small_vector.hpp>
  8. #include "shader_recompiler/environment.h"
  9. #include "shader_recompiler/frontend/ir/basic_block.h"
  10. #include "shader_recompiler/frontend/ir/breadth_first_search.h"
  11. #include "shader_recompiler/frontend/ir/ir_emitter.h"
  12. #include "shader_recompiler/ir_opt/passes.h"
  13. #include "shader_recompiler/shader_info.h"
  14. namespace Shader::Optimization {
  15. namespace {
  16. struct ConstBufferAddr {
  17. u32 index;
  18. u32 offset;
  19. u32 secondary_index;
  20. u32 secondary_offset;
  21. IR::U32 dynamic_offset;
  22. u32 count;
  23. bool has_secondary;
  24. };
  25. struct TextureInst {
  26. ConstBufferAddr cbuf;
  27. IR::Inst* inst;
  28. IR::Block* block;
  29. };
  30. using TextureInstVector = boost::container::small_vector<TextureInst, 24>;
  31. constexpr u32 DESCRIPTOR_SIZE = 8;
  32. constexpr u32 DESCRIPTOR_SIZE_SHIFT = static_cast<u32>(std::countr_zero(DESCRIPTOR_SIZE));
  33. IR::Opcode IndexedInstruction(const IR::Inst& inst) {
  34. switch (inst.GetOpcode()) {
  35. case IR::Opcode::BindlessImageSampleImplicitLod:
  36. case IR::Opcode::BoundImageSampleImplicitLod:
  37. return IR::Opcode::ImageSampleImplicitLod;
  38. case IR::Opcode::BoundImageSampleExplicitLod:
  39. case IR::Opcode::BindlessImageSampleExplicitLod:
  40. return IR::Opcode::ImageSampleExplicitLod;
  41. case IR::Opcode::BoundImageSampleDrefImplicitLod:
  42. case IR::Opcode::BindlessImageSampleDrefImplicitLod:
  43. return IR::Opcode::ImageSampleDrefImplicitLod;
  44. case IR::Opcode::BoundImageSampleDrefExplicitLod:
  45. case IR::Opcode::BindlessImageSampleDrefExplicitLod:
  46. return IR::Opcode::ImageSampleDrefExplicitLod;
  47. case IR::Opcode::BindlessImageGather:
  48. case IR::Opcode::BoundImageGather:
  49. return IR::Opcode::ImageGather;
  50. case IR::Opcode::BindlessImageGatherDref:
  51. case IR::Opcode::BoundImageGatherDref:
  52. return IR::Opcode::ImageGatherDref;
  53. case IR::Opcode::BindlessImageFetch:
  54. case IR::Opcode::BoundImageFetch:
  55. return IR::Opcode::ImageFetch;
  56. case IR::Opcode::BoundImageQueryDimensions:
  57. case IR::Opcode::BindlessImageQueryDimensions:
  58. return IR::Opcode::ImageQueryDimensions;
  59. case IR::Opcode::BoundImageQueryLod:
  60. case IR::Opcode::BindlessImageQueryLod:
  61. return IR::Opcode::ImageQueryLod;
  62. case IR::Opcode::BoundImageGradient:
  63. case IR::Opcode::BindlessImageGradient:
  64. return IR::Opcode::ImageGradient;
  65. case IR::Opcode::BoundImageRead:
  66. case IR::Opcode::BindlessImageRead:
  67. return IR::Opcode::ImageRead;
  68. case IR::Opcode::BoundImageWrite:
  69. case IR::Opcode::BindlessImageWrite:
  70. return IR::Opcode::ImageWrite;
  71. case IR::Opcode::BoundImageAtomicIAdd32:
  72. case IR::Opcode::BindlessImageAtomicIAdd32:
  73. return IR::Opcode::ImageAtomicIAdd32;
  74. case IR::Opcode::BoundImageAtomicSMin32:
  75. case IR::Opcode::BindlessImageAtomicSMin32:
  76. return IR::Opcode::ImageAtomicSMin32;
  77. case IR::Opcode::BoundImageAtomicUMin32:
  78. case IR::Opcode::BindlessImageAtomicUMin32:
  79. return IR::Opcode::ImageAtomicUMin32;
  80. case IR::Opcode::BoundImageAtomicSMax32:
  81. case IR::Opcode::BindlessImageAtomicSMax32:
  82. return IR::Opcode::ImageAtomicSMax32;
  83. case IR::Opcode::BoundImageAtomicUMax32:
  84. case IR::Opcode::BindlessImageAtomicUMax32:
  85. return IR::Opcode::ImageAtomicUMax32;
  86. case IR::Opcode::BoundImageAtomicInc32:
  87. case IR::Opcode::BindlessImageAtomicInc32:
  88. return IR::Opcode::ImageAtomicInc32;
  89. case IR::Opcode::BoundImageAtomicDec32:
  90. case IR::Opcode::BindlessImageAtomicDec32:
  91. return IR::Opcode::ImageAtomicDec32;
  92. case IR::Opcode::BoundImageAtomicAnd32:
  93. case IR::Opcode::BindlessImageAtomicAnd32:
  94. return IR::Opcode::ImageAtomicAnd32;
  95. case IR::Opcode::BoundImageAtomicOr32:
  96. case IR::Opcode::BindlessImageAtomicOr32:
  97. return IR::Opcode::ImageAtomicOr32;
  98. case IR::Opcode::BoundImageAtomicXor32:
  99. case IR::Opcode::BindlessImageAtomicXor32:
  100. return IR::Opcode::ImageAtomicXor32;
  101. case IR::Opcode::BoundImageAtomicExchange32:
  102. case IR::Opcode::BindlessImageAtomicExchange32:
  103. return IR::Opcode::ImageAtomicExchange32;
  104. default:
  105. return IR::Opcode::Void;
  106. }
  107. }
  108. bool IsBindless(const IR::Inst& inst) {
  109. switch (inst.GetOpcode()) {
  110. case IR::Opcode::BindlessImageSampleImplicitLod:
  111. case IR::Opcode::BindlessImageSampleExplicitLod:
  112. case IR::Opcode::BindlessImageSampleDrefImplicitLod:
  113. case IR::Opcode::BindlessImageSampleDrefExplicitLod:
  114. case IR::Opcode::BindlessImageGather:
  115. case IR::Opcode::BindlessImageGatherDref:
  116. case IR::Opcode::BindlessImageFetch:
  117. case IR::Opcode::BindlessImageQueryDimensions:
  118. case IR::Opcode::BindlessImageQueryLod:
  119. case IR::Opcode::BindlessImageGradient:
  120. case IR::Opcode::BindlessImageRead:
  121. case IR::Opcode::BindlessImageWrite:
  122. case IR::Opcode::BindlessImageAtomicIAdd32:
  123. case IR::Opcode::BindlessImageAtomicSMin32:
  124. case IR::Opcode::BindlessImageAtomicUMin32:
  125. case IR::Opcode::BindlessImageAtomicSMax32:
  126. case IR::Opcode::BindlessImageAtomicUMax32:
  127. case IR::Opcode::BindlessImageAtomicInc32:
  128. case IR::Opcode::BindlessImageAtomicDec32:
  129. case IR::Opcode::BindlessImageAtomicAnd32:
  130. case IR::Opcode::BindlessImageAtomicOr32:
  131. case IR::Opcode::BindlessImageAtomicXor32:
  132. case IR::Opcode::BindlessImageAtomicExchange32:
  133. return true;
  134. case IR::Opcode::BoundImageSampleImplicitLod:
  135. case IR::Opcode::BoundImageSampleExplicitLod:
  136. case IR::Opcode::BoundImageSampleDrefImplicitLod:
  137. case IR::Opcode::BoundImageSampleDrefExplicitLod:
  138. case IR::Opcode::BoundImageGather:
  139. case IR::Opcode::BoundImageGatherDref:
  140. case IR::Opcode::BoundImageFetch:
  141. case IR::Opcode::BoundImageQueryDimensions:
  142. case IR::Opcode::BoundImageQueryLod:
  143. case IR::Opcode::BoundImageGradient:
  144. case IR::Opcode::BoundImageRead:
  145. case IR::Opcode::BoundImageWrite:
  146. case IR::Opcode::BoundImageAtomicIAdd32:
  147. case IR::Opcode::BoundImageAtomicSMin32:
  148. case IR::Opcode::BoundImageAtomicUMin32:
  149. case IR::Opcode::BoundImageAtomicSMax32:
  150. case IR::Opcode::BoundImageAtomicUMax32:
  151. case IR::Opcode::BoundImageAtomicInc32:
  152. case IR::Opcode::BoundImageAtomicDec32:
  153. case IR::Opcode::BoundImageAtomicAnd32:
  154. case IR::Opcode::BoundImageAtomicOr32:
  155. case IR::Opcode::BoundImageAtomicXor32:
  156. case IR::Opcode::BoundImageAtomicExchange32:
  157. return false;
  158. default:
  159. throw InvalidArgument("Invalid opcode {}", inst.GetOpcode());
  160. }
  161. }
  162. bool IsTextureInstruction(const IR::Inst& inst) {
  163. return IndexedInstruction(inst) != IR::Opcode::Void;
  164. }
  165. std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst);
  166. std::optional<ConstBufferAddr> Track(const IR::Value& value) {
  167. return IR::BreadthFirstSearch(value, TryGetConstBuffer);
  168. }
  169. std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
  170. switch (inst->GetOpcode()) {
  171. default:
  172. return std::nullopt;
  173. case IR::Opcode::BitwiseOr32: {
  174. std::optional lhs{Track(inst->Arg(0))};
  175. std::optional rhs{Track(inst->Arg(1))};
  176. if (!lhs || !rhs) {
  177. return std::nullopt;
  178. }
  179. if (lhs->has_secondary || rhs->has_secondary) {
  180. return std::nullopt;
  181. }
  182. if (lhs->count > 1 || rhs->count > 1) {
  183. return std::nullopt;
  184. }
  185. if (lhs->index > rhs->index || lhs->offset > rhs->offset) {
  186. std::swap(lhs, rhs);
  187. }
  188. return ConstBufferAddr{
  189. .index = lhs->index,
  190. .offset = lhs->offset,
  191. .secondary_index = rhs->index,
  192. .secondary_offset = rhs->offset,
  193. .dynamic_offset = {},
  194. .count = 1,
  195. .has_secondary = true,
  196. };
  197. }
  198. case IR::Opcode::GetCbufU32x2:
  199. case IR::Opcode::GetCbufU32:
  200. break;
  201. }
  202. const IR::Value index{inst->Arg(0)};
  203. const IR::Value offset{inst->Arg(1)};
  204. if (!index.IsImmediate()) {
  205. // Reading a bindless texture from variable indices is valid
  206. // but not supported here at the moment
  207. return std::nullopt;
  208. }
  209. if (offset.IsImmediate()) {
  210. return ConstBufferAddr{
  211. .index = index.U32(),
  212. .offset = offset.U32(),
  213. .secondary_index = 0,
  214. .secondary_offset = 0,
  215. .dynamic_offset = {},
  216. .count = 1,
  217. .has_secondary = false,
  218. };
  219. }
  220. IR::Inst* const offset_inst{offset.InstRecursive()};
  221. if (offset_inst->GetOpcode() != IR::Opcode::IAdd32) {
  222. return std::nullopt;
  223. }
  224. u32 base_offset{};
  225. IR::U32 dynamic_offset;
  226. if (offset_inst->Arg(0).IsImmediate()) {
  227. base_offset = offset_inst->Arg(0).U32();
  228. dynamic_offset = IR::U32{offset_inst->Arg(1)};
  229. } else if (offset_inst->Arg(1).IsImmediate()) {
  230. base_offset = offset_inst->Arg(1).U32();
  231. dynamic_offset = IR::U32{offset_inst->Arg(0)};
  232. } else {
  233. return std::nullopt;
  234. }
  235. return ConstBufferAddr{
  236. .index = index.U32(),
  237. .offset = base_offset,
  238. .secondary_index = 0,
  239. .secondary_offset = 0,
  240. .dynamic_offset = dynamic_offset,
  241. .count = 8,
  242. .has_secondary = false,
  243. };
  244. }
  245. TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
  246. ConstBufferAddr addr;
  247. if (IsBindless(inst)) {
  248. const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0))};
  249. if (!track_addr) {
  250. throw NotImplementedException("Failed to track bindless texture constant buffer");
  251. }
  252. addr = *track_addr;
  253. } else {
  254. addr = ConstBufferAddr{
  255. .index = env.TextureBoundBuffer(),
  256. .offset = inst.Arg(0).U32(),
  257. .secondary_index = 0,
  258. .secondary_offset = 0,
  259. .dynamic_offset = {},
  260. .count = 1,
  261. .has_secondary = false,
  262. };
  263. }
  264. return TextureInst{
  265. .cbuf = addr,
  266. .inst = &inst,
  267. .block = block,
  268. };
  269. }
  270. TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
  271. const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
  272. const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
  273. const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
  274. const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
  275. return env.ReadTextureType(lhs_raw | rhs_raw);
  276. }
  277. class Descriptors {
  278. public:
  279. explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
  280. ImageBufferDescriptors& image_buffer_descriptors_,
  281. TextureDescriptors& texture_descriptors_,
  282. ImageDescriptors& image_descriptors_)
  283. : texture_buffer_descriptors{texture_buffer_descriptors_},
  284. image_buffer_descriptors{image_buffer_descriptors_},
  285. texture_descriptors{texture_descriptors_}, image_descriptors{image_descriptors_} {}
  286. u32 Add(const TextureBufferDescriptor& desc) {
  287. return Add(texture_buffer_descriptors, desc, [&desc](const auto& existing) {
  288. return desc.cbuf_index == existing.cbuf_index &&
  289. desc.cbuf_offset == existing.cbuf_offset &&
  290. desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
  291. desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
  292. desc.count == existing.count && desc.size_shift == existing.size_shift &&
  293. desc.has_secondary == existing.has_secondary;
  294. });
  295. }
  296. u32 Add(const ImageBufferDescriptor& desc) {
  297. const u32 index{Add(image_buffer_descriptors, desc, [&desc](const auto& existing) {
  298. return desc.format == existing.format && desc.cbuf_index == existing.cbuf_index &&
  299. desc.cbuf_offset == existing.cbuf_offset && desc.count == existing.count &&
  300. desc.size_shift == existing.size_shift;
  301. })};
  302. image_buffer_descriptors[index].is_written |= desc.is_written;
  303. image_buffer_descriptors[index].is_read |= desc.is_read;
  304. return index;
  305. }
  306. u32 Add(const TextureDescriptor& desc) {
  307. return Add(texture_descriptors, desc, [&desc](const auto& existing) {
  308. return desc.type == existing.type && desc.is_depth == existing.is_depth &&
  309. desc.has_secondary == existing.has_secondary &&
  310. desc.cbuf_index == existing.cbuf_index &&
  311. desc.cbuf_offset == existing.cbuf_offset &&
  312. desc.secondary_cbuf_index == existing.secondary_cbuf_index &&
  313. desc.secondary_cbuf_offset == existing.secondary_cbuf_offset &&
  314. desc.count == existing.count && desc.size_shift == existing.size_shift;
  315. });
  316. }
  317. u32 Add(const ImageDescriptor& desc) {
  318. const u32 index{Add(image_descriptors, desc, [&desc](const auto& existing) {
  319. return desc.type == existing.type && desc.format == existing.format &&
  320. desc.cbuf_index == existing.cbuf_index &&
  321. desc.cbuf_offset == existing.cbuf_offset && desc.count == existing.count &&
  322. desc.size_shift == existing.size_shift;
  323. })};
  324. image_descriptors[index].is_written |= desc.is_written;
  325. image_descriptors[index].is_read |= desc.is_read;
  326. return index;
  327. }
  328. private:
  329. template <typename Descriptors, typename Descriptor, typename Func>
  330. static u32 Add(Descriptors& descriptors, const Descriptor& desc, Func&& pred) {
  331. // TODO: Handle arrays
  332. const auto it{std::ranges::find_if(descriptors, pred)};
  333. if (it != descriptors.end()) {
  334. return static_cast<u32>(std::distance(descriptors.begin(), it));
  335. }
  336. descriptors.push_back(desc);
  337. return static_cast<u32>(descriptors.size()) - 1;
  338. }
  339. TextureBufferDescriptors& texture_buffer_descriptors;
  340. ImageBufferDescriptors& image_buffer_descriptors;
  341. TextureDescriptors& texture_descriptors;
  342. ImageDescriptors& image_descriptors;
  343. };
  344. } // Anonymous namespace
  345. void TexturePass(Environment& env, IR::Program& program) {
  346. TextureInstVector to_replace;
  347. for (IR::Block* const block : program.post_order_blocks) {
  348. for (IR::Inst& inst : block->Instructions()) {
  349. if (!IsTextureInstruction(inst)) {
  350. continue;
  351. }
  352. to_replace.push_back(MakeInst(env, block, inst));
  353. }
  354. }
  355. // Sort instructions to visit textures by constant buffer index, then by offset
  356. std::ranges::sort(to_replace, [](const auto& lhs, const auto& rhs) {
  357. return lhs.cbuf.offset < rhs.cbuf.offset;
  358. });
  359. std::stable_sort(to_replace.begin(), to_replace.end(), [](const auto& lhs, const auto& rhs) {
  360. return lhs.cbuf.index < rhs.cbuf.index;
  361. });
  362. Descriptors descriptors{
  363. program.info.texture_buffer_descriptors,
  364. program.info.image_buffer_descriptors,
  365. program.info.texture_descriptors,
  366. program.info.image_descriptors,
  367. };
  368. for (TextureInst& texture_inst : to_replace) {
  369. // TODO: Handle arrays
  370. IR::Inst* const inst{texture_inst.inst};
  371. inst->ReplaceOpcode(IndexedInstruction(*inst));
  372. const auto& cbuf{texture_inst.cbuf};
  373. auto flags{inst->Flags<IR::TextureInstInfo>()};
  374. switch (inst->GetOpcode()) {
  375. case IR::Opcode::ImageQueryDimensions:
  376. flags.type.Assign(ReadTextureType(env, cbuf));
  377. inst->SetFlags(flags);
  378. break;
  379. case IR::Opcode::ImageFetch:
  380. if (flags.type != TextureType::Color1D) {
  381. break;
  382. }
  383. if (ReadTextureType(env, cbuf) == TextureType::Buffer) {
  384. // Replace with the bound texture type only when it's a texture buffer
  385. // If the instruction is 1D and the bound type is 2D, don't change the code and let
  386. // the rasterizer robustness handle it
  387. // This happens on Fire Emblem: Three Houses
  388. flags.type.Assign(TextureType::Buffer);
  389. }
  390. break;
  391. default:
  392. break;
  393. }
  394. u32 index;
  395. switch (inst->GetOpcode()) {
  396. case IR::Opcode::ImageRead:
  397. case IR::Opcode::ImageAtomicIAdd32:
  398. case IR::Opcode::ImageAtomicSMin32:
  399. case IR::Opcode::ImageAtomicUMin32:
  400. case IR::Opcode::ImageAtomicSMax32:
  401. case IR::Opcode::ImageAtomicUMax32:
  402. case IR::Opcode::ImageAtomicInc32:
  403. case IR::Opcode::ImageAtomicDec32:
  404. case IR::Opcode::ImageAtomicAnd32:
  405. case IR::Opcode::ImageAtomicOr32:
  406. case IR::Opcode::ImageAtomicXor32:
  407. case IR::Opcode::ImageAtomicExchange32:
  408. case IR::Opcode::ImageWrite: {
  409. if (cbuf.has_secondary) {
  410. throw NotImplementedException("Unexpected separate sampler");
  411. }
  412. const bool is_written{inst->GetOpcode() != IR::Opcode::ImageRead};
  413. const bool is_read{inst->GetOpcode() != IR::Opcode::ImageWrite};
  414. if (flags.type == TextureType::Buffer) {
  415. index = descriptors.Add(ImageBufferDescriptor{
  416. .format = flags.image_format,
  417. .is_written = is_written,
  418. .is_read = is_read,
  419. .cbuf_index = cbuf.index,
  420. .cbuf_offset = cbuf.offset,
  421. .count = cbuf.count,
  422. .size_shift = DESCRIPTOR_SIZE_SHIFT,
  423. });
  424. } else {
  425. index = descriptors.Add(ImageDescriptor{
  426. .type = flags.type,
  427. .format = flags.image_format,
  428. .is_written = is_written,
  429. .is_read = is_read,
  430. .cbuf_index = cbuf.index,
  431. .cbuf_offset = cbuf.offset,
  432. .count = cbuf.count,
  433. .size_shift = DESCRIPTOR_SIZE_SHIFT,
  434. });
  435. }
  436. break;
  437. }
  438. default:
  439. if (flags.type == TextureType::Buffer) {
  440. index = descriptors.Add(TextureBufferDescriptor{
  441. .has_secondary = cbuf.has_secondary,
  442. .cbuf_index = cbuf.index,
  443. .cbuf_offset = cbuf.offset,
  444. .secondary_cbuf_index = cbuf.secondary_index,
  445. .secondary_cbuf_offset = cbuf.secondary_offset,
  446. .count = cbuf.count,
  447. .size_shift = DESCRIPTOR_SIZE_SHIFT,
  448. });
  449. } else {
  450. index = descriptors.Add(TextureDescriptor{
  451. .type = flags.type,
  452. .is_depth = flags.is_depth != 0,
  453. .has_secondary = cbuf.has_secondary,
  454. .cbuf_index = cbuf.index,
  455. .cbuf_offset = cbuf.offset,
  456. .secondary_cbuf_index = cbuf.secondary_index,
  457. .secondary_cbuf_offset = cbuf.secondary_offset,
  458. .count = cbuf.count,
  459. .size_shift = DESCRIPTOR_SIZE_SHIFT,
  460. });
  461. }
  462. break;
  463. }
  464. flags.descriptor_index.Assign(index);
  465. inst->SetFlags(flags);
  466. if (cbuf.count > 1) {
  467. const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)};
  468. IR::IREmitter ir{*texture_inst.block, insert_point};
  469. const IR::U32 shift{ir.Imm32(std::countr_zero(DESCRIPTOR_SIZE))};
  470. inst->SetArg(0, ir.UMin(ir.ShiftRightArithmetic(cbuf.dynamic_offset, shift),
  471. ir.Imm32(DESCRIPTOR_SIZE - 1)));
  472. } else {
  473. inst->SetArg(0, IR::Value{});
  474. }
  475. }
  476. }
  477. void JoinTextureInfo(Info& base, Info& source) {
  478. Descriptors descriptors{
  479. base.texture_buffer_descriptors,
  480. base.image_buffer_descriptors,
  481. base.texture_descriptors,
  482. base.image_descriptors,
  483. };
  484. for (auto& desc : source.texture_buffer_descriptors) {
  485. descriptors.Add(desc);
  486. }
  487. for (auto& desc : source.image_buffer_descriptors) {
  488. descriptors.Add(desc);
  489. }
  490. for (auto& desc : source.texture_descriptors) {
  491. descriptors.Add(desc);
  492. }
  493. for (auto& desc : source.image_descriptors) {
  494. descriptors.Add(desc);
  495. }
  496. }
  497. } // namespace Shader::Optimization