emit_glsl_image.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. // Copyright 2021 yuzu Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <string_view>
  5. #include "shader_recompiler/backend/glsl/emit_context.h"
  6. #include "shader_recompiler/backend/glsl/emit_glsl_instructions.h"
  7. #include "shader_recompiler/frontend/ir/modifiers.h"
  8. #include "shader_recompiler/frontend/ir/value.h"
  9. namespace Shader::Backend::GLSL {
  10. namespace {
  11. std::string Texture(EmitContext& ctx, const IR::TextureInstInfo& info,
  12. [[maybe_unused]] const IR::Value& index) {
  13. if (info.type == TextureType::Buffer) {
  14. throw NotImplementedException("TextureType::Buffer");
  15. } else {
  16. return fmt::format("tex{}", ctx.texture_bindings.at(info.descriptor_index));
  17. }
  18. }
  19. std::string CastToIntVec(std::string_view value, const IR::TextureInstInfo& info) {
  20. switch (info.type) {
  21. case TextureType::Color1D:
  22. return fmt::format("int({})", value);
  23. case TextureType::ColorArray1D:
  24. case TextureType::Color2D:
  25. case TextureType::ColorArray2D:
  26. return fmt::format("ivec2({})", value);
  27. case TextureType::Color3D:
  28. case TextureType::ColorCube:
  29. return fmt::format("ivec3({})", value);
  30. case TextureType::ColorArrayCube:
  31. return fmt::format("ivec4({})", value);
  32. default:
  33. throw NotImplementedException("Offset type {}", info.type.Value());
  34. }
  35. }
  36. std::string TexelFetchCastToInt(std::string_view value, const IR::TextureInstInfo& info) {
  37. switch (info.type) {
  38. case TextureType::Color1D:
  39. return fmt::format("int({})", value);
  40. case TextureType::ColorArray1D:
  41. case TextureType::Color2D:
  42. return fmt::format("ivec2({})", value);
  43. case TextureType::ColorArray2D:
  44. case TextureType::Color3D:
  45. case TextureType::ColorCube:
  46. return fmt::format("ivec3({})", value);
  47. case TextureType::ColorArrayCube:
  48. return fmt::format("ivec4({})", value);
  49. default:
  50. throw NotImplementedException("Offset type {}", info.type.Value());
  51. }
  52. }
  53. std::string ShadowSamplerVecCast(TextureType type) {
  54. switch (type) {
  55. case TextureType::ColorArray2D:
  56. case TextureType::ColorCube:
  57. case TextureType::ColorArrayCube:
  58. return "vec4";
  59. default:
  60. return "vec3";
  61. }
  62. }
  63. std::string PtpOffsets(const IR::Value& offset, const IR::Value& offset2) {
  64. const std::array values{offset.InstRecursive(), offset2.InstRecursive()};
  65. if (!values[0]->AreAllArgsImmediates() || !values[1]->AreAllArgsImmediates()) {
  66. // LOG_WARNING("Not all arguments in PTP are immediate, STUBBING");
  67. return "";
  68. }
  69. const IR::Opcode opcode{values[0]->GetOpcode()};
  70. if (opcode != values[1]->GetOpcode() || opcode != IR::Opcode::CompositeConstructU32x4) {
  71. throw LogicError("Invalid PTP arguments");
  72. }
  73. auto read{[&](unsigned int a, unsigned int b) { return values[a]->Arg(b).U32(); }};
  74. return fmt::format("ivec2[](ivec2({},{}),ivec2({},{}),ivec2({},{}),ivec2({},{}))", read(0, 0),
  75. read(0, 1), read(0, 2), read(0, 3), read(1, 0), read(1, 1), read(1, 2),
  76. read(1, 3));
  77. }
  78. IR::Inst* PrepareSparse(IR::Inst& inst) {
  79. const auto sparse_inst{inst.GetAssociatedPseudoOperation(IR::Opcode::GetSparseFromOp)};
  80. if (sparse_inst) {
  81. sparse_inst->Invalidate();
  82. }
  83. return sparse_inst;
  84. }
  85. } // namespace
  86. void EmitImageSampleImplicitLod([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  87. [[maybe_unused]] const IR::Value& index,
  88. [[maybe_unused]] std::string_view coords,
  89. [[maybe_unused]] std::string_view bias_lc,
  90. [[maybe_unused]] const IR::Value& offset) {
  91. const auto info{inst.Flags<IR::TextureInstInfo>()};
  92. if (info.has_lod_clamp) {
  93. throw NotImplementedException("Lod clamp samples");
  94. }
  95. const auto texture{Texture(ctx, info, index)};
  96. const auto bias{info.has_bias ? fmt::format(",{}", bias_lc) : ""};
  97. const auto texel{ctx.reg_alloc.Define(inst, Type::F32x4)};
  98. const auto sparse_inst{PrepareSparse(inst)};
  99. if (!sparse_inst) {
  100. if (!offset.IsEmpty()) {
  101. ctx.Add("{}=textureOffset({},{},{}{});", texel, texture, coords,
  102. CastToIntVec(ctx.reg_alloc.Consume(offset), info), bias);
  103. } else {
  104. if (ctx.stage == Stage::Fragment) {
  105. ctx.Add("{}=texture({},{}{});", texel, texture, coords, bias);
  106. } else {
  107. ctx.Add("{}=textureLod({},{},0.0);", texel, texture, coords);
  108. }
  109. }
  110. return;
  111. }
  112. // TODO: Query sparseTexels extension support
  113. if (!offset.IsEmpty()) {
  114. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureOffsetARB({},{},{},{}{}));",
  115. *sparse_inst, texture, coords, CastToIntVec(ctx.reg_alloc.Consume(offset), info),
  116. texel, bias);
  117. } else {
  118. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureARB({},{},{}{}));", *sparse_inst,
  119. texture, coords, texel, bias);
  120. }
  121. }
  122. void EmitImageSampleExplicitLod([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  123. [[maybe_unused]] const IR::Value& index,
  124. [[maybe_unused]] std::string_view coords,
  125. [[maybe_unused]] std::string_view lod_lc,
  126. [[maybe_unused]] const IR::Value& offset) {
  127. const auto info{inst.Flags<IR::TextureInstInfo>()};
  128. if (info.has_bias) {
  129. throw NotImplementedException("Bias texture samples");
  130. }
  131. if (info.has_lod_clamp) {
  132. throw NotImplementedException("Lod clamp samples");
  133. }
  134. const auto texture{Texture(ctx, info, index)};
  135. const auto texel{ctx.reg_alloc.Define(inst, Type::F32x4)};
  136. const auto sparse_inst{PrepareSparse(inst)};
  137. if (!sparse_inst) {
  138. if (!offset.IsEmpty()) {
  139. ctx.Add("{}=textureLodOffset({},{},{},{});", texel, texture, coords, lod_lc,
  140. CastToIntVec(ctx.reg_alloc.Consume(offset), info));
  141. } else {
  142. ctx.Add("{}=textureLod({},{},{});", texel, texture, coords, lod_lc);
  143. }
  144. return;
  145. }
  146. // TODO: Query sparseTexels extension support
  147. if (!offset.IsEmpty()) {
  148. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTexelFetchOffsetARB({},{},int({}),{},{}));",
  149. *sparse_inst, texture, CastToIntVec(coords, info), lod_lc,
  150. CastToIntVec(ctx.reg_alloc.Consume(offset), info), texel);
  151. } else {
  152. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureLodARB({},{},{},{}));", *sparse_inst,
  153. texture, coords, lod_lc, texel);
  154. }
  155. }
  156. void EmitImageSampleDrefImplicitLod([[maybe_unused]] EmitContext& ctx,
  157. [[maybe_unused]] IR::Inst& inst,
  158. [[maybe_unused]] const IR::Value& index,
  159. [[maybe_unused]] std::string_view coords,
  160. [[maybe_unused]] std::string_view dref,
  161. [[maybe_unused]] std::string_view bias_lc,
  162. [[maybe_unused]] const IR::Value& offset) {
  163. const auto info{inst.Flags<IR::TextureInstInfo>()};
  164. const auto sparse_inst{PrepareSparse(inst)};
  165. if (sparse_inst) {
  166. throw NotImplementedException("Sparse texture samples");
  167. }
  168. if (info.has_bias) {
  169. throw NotImplementedException("Bias texture samples");
  170. }
  171. if (info.has_lod_clamp) {
  172. throw NotImplementedException("Lod clamp samples");
  173. }
  174. if (!offset.IsEmpty()) {
  175. throw NotImplementedException("textureLodOffset");
  176. }
  177. const auto texture{Texture(ctx, info, index)};
  178. const auto bias{info.has_bias ? fmt::format(",{}", bias_lc) : ""};
  179. const auto cast{ShadowSamplerVecCast(info.type)};
  180. if (ctx.stage == Stage::Fragment) {
  181. ctx.AddF32("{}=texture({},{}({},{}){});", inst, texture, cast, coords, dref, bias);
  182. } else {
  183. ctx.AddF32("{}=textureLod({},{}({},{}),0.0);", inst, texture, cast, coords, dref);
  184. }
  185. }
  186. void EmitImageSampleDrefExplicitLod([[maybe_unused]] EmitContext& ctx,
  187. [[maybe_unused]] IR::Inst& inst,
  188. [[maybe_unused]] const IR::Value& index,
  189. [[maybe_unused]] std::string_view coords,
  190. [[maybe_unused]] std::string_view dref,
  191. [[maybe_unused]] std::string_view lod_lc,
  192. [[maybe_unused]] const IR::Value& offset) {
  193. const auto info{inst.Flags<IR::TextureInstInfo>()};
  194. const auto sparse_inst{PrepareSparse(inst)};
  195. if (sparse_inst) {
  196. throw NotImplementedException("Sparse texture samples");
  197. }
  198. if (info.has_bias) {
  199. throw NotImplementedException("Bias texture samples");
  200. }
  201. if (info.has_lod_clamp) {
  202. throw NotImplementedException("Lod clamp samples");
  203. }
  204. if (!offset.IsEmpty()) {
  205. throw NotImplementedException("textureLodOffset");
  206. }
  207. const auto texture{Texture(ctx, info, index)};
  208. if (info.type == TextureType::ColorArrayCube) {
  209. ctx.AddF32("{}=textureLod({},{},{},{});", inst, texture, coords, dref, lod_lc);
  210. } else {
  211. ctx.AddF32("{}=textureLod({},vec3({},{}),{});", inst, texture, coords, dref, lod_lc);
  212. }
  213. }
  214. void EmitImageGather([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  215. [[maybe_unused]] const IR::Value& index,
  216. [[maybe_unused]] std::string_view coords,
  217. [[maybe_unused]] const IR::Value& offset,
  218. [[maybe_unused]] const IR::Value& offset2) {
  219. const auto info{inst.Flags<IR::TextureInstInfo>()};
  220. const auto texture{Texture(ctx, info, index)};
  221. const auto texel{ctx.reg_alloc.Define(inst, Type::F32x4)};
  222. const auto sparse_inst{PrepareSparse(inst)};
  223. if (!sparse_inst) {
  224. if (offset.IsEmpty()) {
  225. ctx.Add("{}=textureGather({},{},int({}));", texel, texture, coords,
  226. info.gather_component);
  227. return;
  228. }
  229. if (offset2.IsEmpty()) {
  230. ctx.Add("{}=textureGatherOffset({},{},{},int({}));", texel, texture, coords,
  231. CastToIntVec(ctx.reg_alloc.Consume(offset), info), info.gather_component);
  232. return;
  233. }
  234. // PTP
  235. const auto offsets{PtpOffsets(offset, offset2)};
  236. ctx.Add("{}=textureGatherOffsets({},{},{},int({}));", texel, texture, coords, offsets,
  237. info.gather_component);
  238. return;
  239. }
  240. // TODO: Query sparseTexels extension support
  241. if (offset.IsEmpty()) {
  242. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherARB({},{},{},int({})));",
  243. *sparse_inst, texture, coords, texel, info.gather_component);
  244. }
  245. if (offset2.IsEmpty()) {
  246. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherOffsetARB({},{},{},{},int({})));",
  247. *sparse_inst, texture, CastToIntVec(coords, info),
  248. CastToIntVec(ctx.reg_alloc.Consume(offset), info), texel, info.gather_component);
  249. }
  250. // PTP
  251. const auto offsets{PtpOffsets(offset, offset2)};
  252. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherOffsetARB({},{},{},{},int({})));",
  253. *sparse_inst, texture, CastToIntVec(coords, info), offsets, texel,
  254. info.gather_component);
  255. }
  256. void EmitImageGatherDref([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  257. [[maybe_unused]] const IR::Value& index,
  258. [[maybe_unused]] std::string_view coords,
  259. [[maybe_unused]] const IR::Value& offset,
  260. [[maybe_unused]] const IR::Value& offset2,
  261. [[maybe_unused]] std::string_view dref) {
  262. const auto info{inst.Flags<IR::TextureInstInfo>()};
  263. const auto texture{Texture(ctx, info, index)};
  264. const auto texel{ctx.reg_alloc.Define(inst, Type::F32x4)};
  265. const auto sparse_inst{PrepareSparse(inst)};
  266. if (!sparse_inst) {
  267. if (offset.IsEmpty()) {
  268. ctx.Add("{}=textureGather({},{},{});", texel, texture, coords, dref);
  269. return;
  270. }
  271. if (offset2.IsEmpty()) {
  272. ctx.Add("{}=textureGatherOffset({},{},{},{});", texel, texture, coords, dref,
  273. CastToIntVec(ctx.reg_alloc.Consume(offset), info));
  274. return;
  275. }
  276. // PTP
  277. const auto offsets{PtpOffsets(offset, offset2)};
  278. ctx.Add("{}=textureGatherOffsets({},{},{},{});", texel, texture, coords, dref, offsets);
  279. return;
  280. }
  281. // TODO: Query sparseTexels extension support
  282. if (offset.IsEmpty()) {
  283. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherARB({},{},{},{}));", *sparse_inst,
  284. texture, coords, dref, texel);
  285. }
  286. if (offset2.IsEmpty()) {
  287. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherOffsetARB({},{},{},,{},{}));",
  288. *sparse_inst, texture, CastToIntVec(coords, info), dref,
  289. CastToIntVec(ctx.reg_alloc.Consume(offset), info), texel);
  290. }
  291. // PTP
  292. const auto offsets{PtpOffsets(offset, offset2)};
  293. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTextureGatherOffsetARB({},{},{},,{},{}));",
  294. *sparse_inst, texture, CastToIntVec(coords, info), dref, offsets, texel);
  295. }
  296. void EmitImageFetch([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  297. [[maybe_unused]] const IR::Value& index,
  298. [[maybe_unused]] std::string_view coords,
  299. [[maybe_unused]] std::string_view offset, [[maybe_unused]] std::string_view lod,
  300. [[maybe_unused]] std::string_view ms) {
  301. const auto info{inst.Flags<IR::TextureInstInfo>()};
  302. if (info.has_bias) {
  303. throw NotImplementedException("Bias texture samples");
  304. }
  305. if (info.has_lod_clamp) {
  306. throw NotImplementedException("Lod clamp samples");
  307. }
  308. const auto texture{Texture(ctx, info, index)};
  309. const auto sparse_inst{PrepareSparse(inst)};
  310. const auto texel{ctx.reg_alloc.Define(inst, Type::F32x4)};
  311. if (!sparse_inst) {
  312. if (!offset.empty()) {
  313. ctx.Add("{}=texelFetchOffset({},{},int({}),{});", texel, texture,
  314. TexelFetchCastToInt(coords, info), lod, TexelFetchCastToInt(offset, info));
  315. } else {
  316. ctx.Add("{}=texelFetch({},{},int({}));", texel, texture,
  317. TexelFetchCastToInt(coords, info), lod);
  318. }
  319. return;
  320. }
  321. // TODO: Query sparseTexels extension support
  322. if (!offset.empty()) {
  323. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTexelFetchOffsetARB({},{},int({}),{},{}));",
  324. *sparse_inst, texture, CastToIntVec(coords, info), lod,
  325. CastToIntVec(offset, info), texel);
  326. } else {
  327. ctx.AddU1("{}=sparseTexelsResidentARB(sparseTexelFetchARB({},{},{},{}));", *sparse_inst,
  328. texture, CastToIntVec(coords, info), lod, texel);
  329. }
  330. }
  331. void EmitImageQueryDimensions([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  332. [[maybe_unused]] const IR::Value& index,
  333. [[maybe_unused]] std::string_view lod) {
  334. const auto info{inst.Flags<IR::TextureInstInfo>()};
  335. const auto texture{Texture(ctx, info, index)};
  336. switch (info.type) {
  337. case TextureType::Color1D:
  338. return ctx.AddU32x4(
  339. "{}=uvec4(uint(textureSize({},int({}))),0u,0u,uint(textureQueryLevels({})));", inst,
  340. texture, lod, texture);
  341. case TextureType::ColorArray1D:
  342. case TextureType::Color2D:
  343. case TextureType::ColorCube:
  344. return ctx.AddU32x4(
  345. "{}=uvec4(uvec2(textureSize({},int({}))),0u,uint(textureQueryLevels({})));", inst,
  346. texture, lod, texture);
  347. case TextureType::ColorArray2D:
  348. case TextureType::Color3D:
  349. case TextureType::ColorArrayCube:
  350. return ctx.AddU32x4(
  351. "{}=uvec4(uvec3(textureSize({},int({}))),uint(textureQueryLevels({})));", inst, texture,
  352. lod, texture);
  353. case TextureType::Buffer:
  354. throw NotImplementedException("Texture buffers");
  355. }
  356. throw LogicError("Unspecified image type {}", info.type.Value());
  357. }
  358. void EmitImageQueryLod([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  359. [[maybe_unused]] const IR::Value& index,
  360. [[maybe_unused]] std::string_view coords) {
  361. const auto info{inst.Flags<IR::TextureInstInfo>()};
  362. const auto texture{Texture(ctx, info, index)};
  363. return ctx.AddF32x4("{}=vec4(textureQueryLod({},{}),0.0,0.0);", inst, texture, coords);
  364. }
  365. void EmitImageGradient([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  366. [[maybe_unused]] const IR::Value& index,
  367. [[maybe_unused]] std::string_view coords,
  368. [[maybe_unused]] std::string_view derivates,
  369. [[maybe_unused]] std::string_view offset,
  370. [[maybe_unused]] std::string_view lod_clamp) {
  371. throw NotImplementedException("GLSL Instruction");
  372. }
  373. void EmitImageRead([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  374. [[maybe_unused]] const IR::Value& index,
  375. [[maybe_unused]] std::string_view coords) {
  376. throw NotImplementedException("GLSL Instruction");
  377. }
  378. void EmitImageWrite([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst,
  379. [[maybe_unused]] const IR::Value& index,
  380. [[maybe_unused]] std::string_view coords,
  381. [[maybe_unused]] std::string_view color) {
  382. throw NotImplementedException("GLSL Instruction");
  383. }
  384. void EmitBindlessImageSampleImplicitLod(EmitContext&) {
  385. throw NotImplementedException("GLSL Instruction");
  386. }
  387. void EmitBindlessImageSampleExplicitLod(EmitContext&) {
  388. throw NotImplementedException("GLSL Instruction");
  389. }
  390. void EmitBindlessImageSampleDrefImplicitLod(EmitContext&) {
  391. throw NotImplementedException("GLSL Instruction");
  392. }
  393. void EmitBindlessImageSampleDrefExplicitLod(EmitContext&) {
  394. throw NotImplementedException("GLSL Instruction");
  395. }
  396. void EmitBindlessImageGather(EmitContext&) {
  397. throw NotImplementedException("GLSL Instruction");
  398. }
  399. void EmitBindlessImageGatherDref(EmitContext&) {
  400. throw NotImplementedException("GLSL Instruction");
  401. }
  402. void EmitBindlessImageFetch(EmitContext&) {
  403. throw NotImplementedException("GLSL Instruction");
  404. }
  405. void EmitBindlessImageQueryDimensions(EmitContext&) {
  406. throw NotImplementedException("GLSL Instruction");
  407. }
  408. void EmitBindlessImageQueryLod(EmitContext&) {
  409. throw NotImplementedException("GLSL Instruction");
  410. }
  411. void EmitBindlessImageGradient(EmitContext&) {
  412. throw NotImplementedException("GLSL Instruction");
  413. }
  414. void EmitBindlessImageRead(EmitContext&) {
  415. throw NotImplementedException("GLSL Instruction");
  416. }
  417. void EmitBindlessImageWrite(EmitContext&) {
  418. throw NotImplementedException("GLSL Instruction");
  419. }
  420. void EmitBoundImageSampleImplicitLod(EmitContext&) {
  421. throw NotImplementedException("GLSL Instruction");
  422. }
  423. void EmitBoundImageSampleExplicitLod(EmitContext&) {
  424. throw NotImplementedException("GLSL Instruction");
  425. }
  426. void EmitBoundImageSampleDrefImplicitLod(EmitContext&) {
  427. throw NotImplementedException("GLSL Instruction");
  428. }
  429. void EmitBoundImageSampleDrefExplicitLod(EmitContext&) {
  430. throw NotImplementedException("GLSL Instruction");
  431. }
  432. void EmitBoundImageGather(EmitContext&) {
  433. throw NotImplementedException("GLSL Instruction");
  434. }
  435. void EmitBoundImageGatherDref(EmitContext&) {
  436. throw NotImplementedException("GLSL Instruction");
  437. }
  438. void EmitBoundImageFetch(EmitContext&) {
  439. throw NotImplementedException("GLSL Instruction");
  440. }
  441. void EmitBoundImageQueryDimensions(EmitContext&) {
  442. throw NotImplementedException("GLSL Instruction");
  443. }
  444. void EmitBoundImageQueryLod(EmitContext&) {
  445. throw NotImplementedException("GLSL Instruction");
  446. }
  447. void EmitBoundImageGradient(EmitContext&) {
  448. throw NotImplementedException("GLSL Instruction");
  449. }
  450. void EmitBoundImageRead(EmitContext&) {
  451. throw NotImplementedException("GLSL Instruction");
  452. }
  453. void EmitBoundImageWrite(EmitContext&) {
  454. throw NotImplementedException("GLSL Instruction");
  455. }
  456. } // namespace Shader::Backend::GLSL