emit_glsl_atomic.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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/value.h"
  8. namespace Shader::Backend::GLSL {
  9. namespace {
  10. constexpr const char cas_loop[]{R"(for (;;){{
  11. uint old_value={};
  12. {}=atomicCompSwap({},old_value,{}({},{}));
  13. if ({}==old_value){{break;}}
  14. }})"};
  15. void SharedCasFunction(EmitContext& ctx, IR::Inst& inst, std::string_view offset,
  16. std::string_view value, std::string_view function) {
  17. const auto ret{ctx.var_alloc.Define(inst, GlslVarType::U32)};
  18. const std::string smem{fmt::format("smem[{}>>2]", offset)};
  19. ctx.Add(cas_loop, smem, ret, smem, function, smem, value, ret);
  20. }
  21. void SsboCasFunction(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  22. const IR::Value& offset, std::string_view value, std::string_view function) {
  23. const auto ret{ctx.var_alloc.Define(inst, GlslVarType::U32)};
  24. const std::string ssbo{fmt::format("{}_ssbo{}[{}>>2]", ctx.stage_name, binding.U32(),
  25. ctx.var_alloc.Consume(offset))};
  26. ctx.Add(cas_loop, ssbo, ret, ssbo, function, ssbo, value, ret);
  27. }
  28. void SsboCasFunctionF32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  29. const IR::Value& offset, std::string_view value,
  30. std::string_view function) {
  31. const std::string ssbo{fmt::format("{}_ssbo{}[{}>>2]", ctx.stage_name, binding.U32(),
  32. ctx.var_alloc.Consume(offset))};
  33. const auto ret{ctx.var_alloc.Define(inst, GlslVarType::U32)};
  34. ctx.Add(cas_loop, ssbo, ret, ssbo, function, ssbo, value, ret);
  35. ctx.AddF32("{}=utof({});", inst, ret);
  36. }
  37. } // Anonymous namespace
  38. void EmitSharedAtomicIAdd32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  39. std::string_view value) {
  40. ctx.AddU32("{}=atomicAdd(smem[{}>>2],{});", inst, pointer_offset, value);
  41. }
  42. void EmitSharedAtomicSMin32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  43. std::string_view value) {
  44. const std::string u32_value{fmt::format("uint({})", value)};
  45. SharedCasFunction(ctx, inst, pointer_offset, u32_value, "CasMinS32");
  46. }
  47. void EmitSharedAtomicUMin32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  48. std::string_view value) {
  49. ctx.AddU32("{}=atomicMin(smem[{}>>2],{});", inst, pointer_offset, value);
  50. }
  51. void EmitSharedAtomicSMax32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  52. std::string_view value) {
  53. const std::string u32_value{fmt::format("uint({})", value)};
  54. SharedCasFunction(ctx, inst, pointer_offset, u32_value, "CasMaxS32");
  55. }
  56. void EmitSharedAtomicUMax32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  57. std::string_view value) {
  58. ctx.AddU32("{}=atomicMax(smem[{}>>2],{});", inst, pointer_offset, value);
  59. }
  60. void EmitSharedAtomicInc32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  61. std::string_view value) {
  62. SharedCasFunction(ctx, inst, pointer_offset, value, "CasIncrement");
  63. }
  64. void EmitSharedAtomicDec32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  65. std::string_view value) {
  66. SharedCasFunction(ctx, inst, pointer_offset, value, "CasDecrement");
  67. }
  68. void EmitSharedAtomicAnd32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  69. std::string_view value) {
  70. ctx.AddU32("{}=atomicAnd(smem[{}>>2],{});", inst, pointer_offset, value);
  71. }
  72. void EmitSharedAtomicOr32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  73. std::string_view value) {
  74. ctx.AddU32("{}=atomicOr(smem[{}>>2],{});", inst, pointer_offset, value);
  75. }
  76. void EmitSharedAtomicXor32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  77. std::string_view value) {
  78. ctx.AddU32("{}=atomicXor(smem[{}>>2],{});", inst, pointer_offset, value);
  79. }
  80. void EmitSharedAtomicExchange32(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  81. std::string_view value) {
  82. ctx.AddU32("{}=atomicExchange(smem[{}>>2],{});", inst, pointer_offset, value);
  83. }
  84. void EmitSharedAtomicExchange64(EmitContext& ctx, IR::Inst& inst, std::string_view pointer_offset,
  85. std::string_view value) {
  86. // LOG_WARNING("Int64 Atomics not supported, fallback to non-atomic");
  87. ctx.AddU64("{}=packUint2x32(uvec2(smem[{}>>2],smem[({}+4)>>2]));", inst, pointer_offset,
  88. pointer_offset);
  89. ctx.Add("smem[{}>>2]=unpackUint2x32({}).x;smem[({}+4)>>2]=unpackUint2x32({}).y;",
  90. pointer_offset, value, pointer_offset, value);
  91. }
  92. void EmitStorageAtomicIAdd32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  93. const IR::Value& offset, std::string_view value) {
  94. ctx.AddU32("{}=atomicAdd({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  95. ctx.var_alloc.Consume(offset), value);
  96. }
  97. void EmitStorageAtomicSMin32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  98. const IR::Value& offset, std::string_view value) {
  99. const std::string u32_value{fmt::format("uint({})", value)};
  100. SsboCasFunction(ctx, inst, binding, offset, u32_value, "CasMinS32");
  101. }
  102. void EmitStorageAtomicUMin32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  103. const IR::Value& offset, std::string_view value) {
  104. ctx.AddU32("{}=atomicMin({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  105. ctx.var_alloc.Consume(offset), value);
  106. }
  107. void EmitStorageAtomicSMax32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  108. const IR::Value& offset, std::string_view value) {
  109. const std::string u32_value{fmt::format("uint({})", value)};
  110. SsboCasFunction(ctx, inst, binding, offset, u32_value, "CasMaxS32");
  111. }
  112. void EmitStorageAtomicUMax32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  113. const IR::Value& offset, std::string_view value) {
  114. ctx.AddU32("{}=atomicMax({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  115. ctx.var_alloc.Consume(offset), value);
  116. }
  117. void EmitStorageAtomicInc32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  118. const IR::Value& offset, std::string_view value) {
  119. SsboCasFunction(ctx, inst, binding, offset, value, "CasIncrement");
  120. }
  121. void EmitStorageAtomicDec32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  122. const IR::Value& offset, std::string_view value) {
  123. SsboCasFunction(ctx, inst, binding, offset, value, "CasDecrement");
  124. }
  125. void EmitStorageAtomicAnd32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  126. const IR::Value& offset, std::string_view value) {
  127. ctx.AddU32("{}=atomicAnd({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  128. ctx.var_alloc.Consume(offset), value);
  129. }
  130. void EmitStorageAtomicOr32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  131. const IR::Value& offset, std::string_view value) {
  132. ctx.AddU32("{}=atomicOr({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  133. ctx.var_alloc.Consume(offset), value);
  134. }
  135. void EmitStorageAtomicXor32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  136. const IR::Value& offset, std::string_view value) {
  137. ctx.AddU32("{}=atomicXor({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  138. ctx.var_alloc.Consume(offset), value);
  139. }
  140. void EmitStorageAtomicExchange32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  141. const IR::Value& offset, std::string_view value) {
  142. ctx.AddU32("{}=atomicExchange({}_ssbo{}[{}>>2],{});", inst, ctx.stage_name, binding.U32(),
  143. ctx.var_alloc.Consume(offset), value);
  144. }
  145. void EmitStorageAtomicIAdd64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  146. const IR::Value& offset, std::string_view value) {
  147. // LOG_WARNING(..., "Op falling to non-atomic");
  148. ctx.AddU64("{}=packUint2x32(uvec2({}_ssbo{}[{}>>2],{}_ssbo{}[({}>>2)+1]));", inst,
  149. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  150. binding.U32(), ctx.var_alloc.Consume(offset));
  151. ctx.Add("{}_ssbo{}[{}>>2]+=unpackUint2x32({}).x;{}_ssbo{}[({}>>2)+1]+=unpackUint2x32({}).y;",
  152. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value, ctx.stage_name,
  153. binding.U32(), ctx.var_alloc.Consume(offset), value);
  154. }
  155. void EmitStorageAtomicSMin64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  156. const IR::Value& offset, std::string_view value) {
  157. // LOG_WARNING(..., "Op falling to non-atomic");
  158. ctx.AddS64("{}=packInt2x32(ivec2({}_ssbo{}[{}>>2],{}_ssbo{}[({}>>2)+1]));", inst,
  159. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  160. binding.U32(), ctx.var_alloc.Consume(offset));
  161. ctx.Add("for(int i=0;i<2;++i){{ "
  162. "{}_ssbo{}[({}>>2)+i]=uint(min(int({}_ssbo{}[({}>>2)+i]),unpackInt2x32(int64_t({}))[i])"
  163. ");}}",
  164. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  165. binding.U32(), ctx.var_alloc.Consume(offset), value);
  166. }
  167. void EmitStorageAtomicUMin64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  168. const IR::Value& offset, std::string_view value) {
  169. // LOG_WARNING(..., "Op falling to non-atomic");
  170. ctx.AddU64("{}=packUint2x32(uvec2({}_ssbo{}[{}>>2],{}_ssbo{}[({}>>2)+1]));", inst,
  171. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  172. binding.U32(), ctx.var_alloc.Consume(offset));
  173. ctx.Add("for(int i=0;i<2;++i){{ "
  174. "{}_ssbo{}[({}>>2)+i]=min({}_ssbo{}[({}>>2)+i],unpackUint2x32(uint64_t({}))[i]);}}",
  175. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  176. binding.U32(), ctx.var_alloc.Consume(offset), value);
  177. }
  178. void EmitStorageAtomicSMax64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  179. const IR::Value& offset, std::string_view value) {
  180. // LOG_WARNING(..., "Op falling to non-atomic");
  181. ctx.AddS64("{}=packInt2x32(ivec2({}_ssbo{}[{}>>2],{}_ssbo{}[({}>>2)+1]));", inst,
  182. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  183. binding.U32(), ctx.var_alloc.Consume(offset));
  184. ctx.Add("for(int i=0;i<2;++i){{ "
  185. "{}_ssbo{}[({}>>2)+i]=uint(max(int({}_ssbo{}[({}>>2)+i]),unpackInt2x32(int64_t({}))[i])"
  186. ");}}",
  187. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  188. binding.U32(), ctx.var_alloc.Consume(offset), value);
  189. }
  190. void EmitStorageAtomicUMax64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  191. const IR::Value& offset, std::string_view value) {
  192. // LOG_WARNING(..., "Op falling to non-atomic");
  193. ctx.AddU64("{}=packUint2x32(uvec2({}_ssbo{}[{}>>2],{}_ssbo{}[({}>>2)+1]));", inst,
  194. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  195. binding.U32(), ctx.var_alloc.Consume(offset));
  196. ctx.Add("for(int "
  197. "i=0;i<2;++i){{{}_ssbo{}[({}>>2)+i]=max({}_ssbo{}[({}>>2)+i],unpackUint2x32(uint64_t({}"
  198. "))[i]);}}",
  199. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), ctx.stage_name,
  200. binding.U32(), ctx.var_alloc.Consume(offset), value);
  201. }
  202. void EmitStorageAtomicAnd64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  203. const IR::Value& offset, std::string_view value) {
  204. ctx.AddU64(
  205. "{}=packUint2x32(uvec2(atomicAnd({}_ssbo{}[{}>>2],unpackUint2x32({}).x),atomicAnd({}_"
  206. "ssbo{}[({}>>2)+1],unpackUint2x32({}).y)));",
  207. inst, ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value, ctx.stage_name,
  208. binding.U32(), ctx.var_alloc.Consume(offset), value);
  209. }
  210. void EmitStorageAtomicOr64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  211. const IR::Value& offset, std::string_view value) {
  212. ctx.AddU64("{}=packUint2x32(uvec2(atomicOr({}_ssbo{}[{}>>2],unpackUint2x32({}).x),atomicOr({}_"
  213. "ssbo{}[({}>>2)+1],unpackUint2x32({}).y)));",
  214. inst, ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value,
  215. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value);
  216. }
  217. void EmitStorageAtomicXor64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  218. const IR::Value& offset, std::string_view value) {
  219. ctx.AddU64(
  220. "{}=packUint2x32(uvec2(atomicXor({}_ssbo{}[{}>>2],unpackUint2x32({}).x),atomicXor({}_"
  221. "ssbo{}[({}>>2)+1],unpackUint2x32({}).y)));",
  222. inst, ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value, ctx.stage_name,
  223. binding.U32(), ctx.var_alloc.Consume(offset), value);
  224. }
  225. void EmitStorageAtomicExchange64(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  226. const IR::Value& offset, std::string_view value) {
  227. ctx.AddU64("{}=packUint2x32(uvec2(atomicExchange({}_ssbo{}[{}>>2],unpackUint2x32({}).x),"
  228. "atomicExchange({}_ssbo{}[({}>>2)+1],unpackUint2x32({}).y)));",
  229. inst, ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value,
  230. ctx.stage_name, binding.U32(), ctx.var_alloc.Consume(offset), value);
  231. }
  232. void EmitStorageAtomicAddF32(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  233. const IR::Value& offset, std::string_view value) {
  234. SsboCasFunctionF32(ctx, inst, binding, offset, value, "CasFloatAdd");
  235. }
  236. void EmitStorageAtomicAddF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  237. const IR::Value& offset, std::string_view value) {
  238. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatAdd16x2");
  239. }
  240. void EmitStorageAtomicAddF32x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  241. const IR::Value& offset, std::string_view value) {
  242. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatAdd32x2");
  243. }
  244. void EmitStorageAtomicMinF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  245. const IR::Value& offset, std::string_view value) {
  246. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatMin16x2");
  247. }
  248. void EmitStorageAtomicMinF32x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  249. const IR::Value& offset, std::string_view value) {
  250. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatMin32x2");
  251. }
  252. void EmitStorageAtomicMaxF16x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  253. const IR::Value& offset, std::string_view value) {
  254. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatMax16x2");
  255. }
  256. void EmitStorageAtomicMaxF32x2(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding,
  257. const IR::Value& offset, std::string_view value) {
  258. SsboCasFunction(ctx, inst, binding, offset, value, "CasFloatMax32x2");
  259. }
  260. void EmitGlobalAtomicIAdd32(EmitContext&) {
  261. throw NotImplementedException("GLSL Instrucion");
  262. }
  263. void EmitGlobalAtomicSMin32(EmitContext&) {
  264. throw NotImplementedException("GLSL Instrucion");
  265. }
  266. void EmitGlobalAtomicUMin32(EmitContext&) {
  267. throw NotImplementedException("GLSL Instrucion");
  268. }
  269. void EmitGlobalAtomicSMax32(EmitContext&) {
  270. throw NotImplementedException("GLSL Instrucion");
  271. }
  272. void EmitGlobalAtomicUMax32(EmitContext&) {
  273. throw NotImplementedException("GLSL Instrucion");
  274. }
  275. void EmitGlobalAtomicInc32(EmitContext&) {
  276. throw NotImplementedException("GLSL Instrucion");
  277. }
  278. void EmitGlobalAtomicDec32(EmitContext&) {
  279. throw NotImplementedException("GLSL Instrucion");
  280. }
  281. void EmitGlobalAtomicAnd32(EmitContext&) {
  282. throw NotImplementedException("GLSL Instrucion");
  283. }
  284. void EmitGlobalAtomicOr32(EmitContext&) {
  285. throw NotImplementedException("GLSL Instrucion");
  286. }
  287. void EmitGlobalAtomicXor32(EmitContext&) {
  288. throw NotImplementedException("GLSL Instrucion");
  289. }
  290. void EmitGlobalAtomicExchange32(EmitContext&) {
  291. throw NotImplementedException("GLSL Instrucion");
  292. }
  293. void EmitGlobalAtomicIAdd64(EmitContext&) {
  294. throw NotImplementedException("GLSL Instrucion");
  295. }
  296. void EmitGlobalAtomicSMin64(EmitContext&) {
  297. throw NotImplementedException("GLSL Instrucion");
  298. }
  299. void EmitGlobalAtomicUMin64(EmitContext&) {
  300. throw NotImplementedException("GLSL Instrucion");
  301. }
  302. void EmitGlobalAtomicSMax64(EmitContext&) {
  303. throw NotImplementedException("GLSL Instrucion");
  304. }
  305. void EmitGlobalAtomicUMax64(EmitContext&) {
  306. throw NotImplementedException("GLSL Instrucion");
  307. }
  308. void EmitGlobalAtomicInc64(EmitContext&) {
  309. throw NotImplementedException("GLSL Instrucion");
  310. }
  311. void EmitGlobalAtomicDec64(EmitContext&) {
  312. throw NotImplementedException("GLSL Instrucion");
  313. }
  314. void EmitGlobalAtomicAnd64(EmitContext&) {
  315. throw NotImplementedException("GLSL Instrucion");
  316. }
  317. void EmitGlobalAtomicOr64(EmitContext&) {
  318. throw NotImplementedException("GLSL Instrucion");
  319. }
  320. void EmitGlobalAtomicXor64(EmitContext&) {
  321. throw NotImplementedException("GLSL Instrucion");
  322. }
  323. void EmitGlobalAtomicExchange64(EmitContext&) {
  324. throw NotImplementedException("GLSL Instrucion");
  325. }
  326. void EmitGlobalAtomicAddF32(EmitContext&) {
  327. throw NotImplementedException("GLSL Instrucion");
  328. }
  329. void EmitGlobalAtomicAddF16x2(EmitContext&) {
  330. throw NotImplementedException("GLSL Instrucion");
  331. }
  332. void EmitGlobalAtomicAddF32x2(EmitContext&) {
  333. throw NotImplementedException("GLSL Instrucion");
  334. }
  335. void EmitGlobalAtomicMinF16x2(EmitContext&) {
  336. throw NotImplementedException("GLSL Instrucion");
  337. }
  338. void EmitGlobalAtomicMinF32x2(EmitContext&) {
  339. throw NotImplementedException("GLSL Instrucion");
  340. }
  341. void EmitGlobalAtomicMaxF16x2(EmitContext&) {
  342. throw NotImplementedException("GLSL Instrucion");
  343. }
  344. void EmitGlobalAtomicMaxF32x2(EmitContext&) {
  345. throw NotImplementedException("GLSL Instrucion");
  346. }
  347. } // namespace Shader::Backend::GLSL