MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
AsmGenerator.cpp
Go to the documentation of this file.
1#include "ShaderFoundry.hpp"
2
3/**
4 * @file AsmGenerator.cpp
5 * @brief SPIR-V assembly and GLSL kernel emitters for ShaderSpec.
6 *
7 * The assembly path (emit_spirv_asm) covers all binding modalities and
8 * kernel templates:
9 *
10 * SSBO (SCALAR_F32, VEC2_F32, VEC3_F32, VEC4_F32) — all KernelOps via
11 * emit_elementwise_body. ArrayStride, element type, and pointer type derived
12 * from GpuDataFormat. Scalar PC operands splatted to vector width via
13 * OpCompositeConstruct before arithmetic.
14 *
15 * IMAGE_2D storage images — emit_image_body handles any number of Input/InOut
16 * images and one Output image with all single- and two-operand KernelOps
17 * applied per channel.
18 *
19 * TEXTURE_2D sampled images — emit_image_body loads each binding via
20 * OpTypeSampledImage / OpImageSampleExplicitLod at normalised UV derived
21 * from GlobalInvocationID divided by width/height PC fields (first two PC
22 * fields by convention when TEXTURE_2D bindings are present).
23 *
24 * KernelTemplate::Reduction — emit_reduction_body emits a structured
25 * workgroup tree reduction with OpLoopMerge, OpPhi for the stride variable,
26 * OpULessThan active-thread guard, and two OpControlBarrier synchronisation
27 * points per iteration. Supports KernelOp::Sum and KernelOp::Max.
28 *
29 * GLSL kernel path (emit_glsl_kernel) handles all three binding modalities
30 * when spec.kernel is set via MF_KERNEL.
31 */
32
34
35namespace {
36
37 /**
38 * Maps GpuDataFormat to the GLSL type name used in SSBO array declarations.
39 */
40 std::string_view glsl_type(Kakshya::GpuDataFormat fmt) noexcept
41 {
42 switch (fmt) {
44 return "float";
46 return "vec2";
48 return "vec3";
50 return "vec4";
52 return "int";
56 return "uint";
57 default:
58 return "float";
59 }
60 }
61
62 /**
63 * Returns the SPIR-V type ID string for the element type of an SSBO binding.
64 */
65 std::string_view ssbo_elem_spirv_type(Kakshya::GpuDataFormat fmt) noexcept
66 {
67 switch (fmt) {
69 return "%v2f32";
71 return "%v3f32";
73 return "%v4f32";
77 return "%u32";
79 return "%i32";
80 default:
81 return "%f32";
82 }
83 }
84
85 /**
86 * Returns the component count for a GpuDataFormat SSBO element.
87 * Scalar formats return 1.
88 */
89 uint32_t ssbo_elem_components(Kakshya::GpuDataFormat fmt) noexcept
90 {
91 switch (fmt) {
93 return 2;
95 return 3;
97 return 4;
98 default:
99 return 1;
100 }
101 }
102
103 /**
104 * Emit a fixed header common to all generated compute kernels.
105 * Assigns IDs for void, voidfn, u32, f32, v3u32, glsl extension import,
106 * and the GlobalInvocationId builtin.
107 */
108 std::string emit_header(const ShaderSpec& spec)
109 {
110 const auto& ws = spec.workgroup_size;
111
112 bool has_storage_image = false;
113 bool has_input_image = false;
114 for (const auto& b : spec.bindings) {
115 if (b.modality == Kakshya::DataModality::IMAGE_2D) {
116 has_storage_image = true;
117 if (b.direction != BindingDirection::Output)
118 has_input_image = true;
119 }
120 }
121
122 std::string iface = "%gid_var";
123 for (const auto& b : spec.bindings) {
124 if (b.modality == Kakshya::DataModality::IMAGE_2D)
125 continue;
126 if (b.modality == Kakshya::DataModality::TEXTURE_2D) {
127 iface += " %tex_" + b.name;
128 continue;
129 }
130 iface += " %buf_" + b.name;
131 }
132 if (has_storage_image) {
133 for (const auto& b : spec.bindings) {
134 if (b.modality == Kakshya::DataModality::IMAGE_2D)
135 iface += " %img_" + b.name;
136 }
137 }
138 if (!spec.pc_fields.empty())
139 iface += " %pc";
140
142 iface += " %lid_var";
143
144 std::string o;
145 o += "OpCapability Shader\n";
146
147 if (has_storage_image)
148 o += "OpCapability StorageImageWriteWithoutFormat\n";
149 if (has_input_image)
150 o += "OpCapability StorageImageReadWithoutFormat\n";
151
152 o += "%glsl = OpExtInstImport \"GLSL.std.450\"\n";
153 o += "OpMemoryModel Logical GLSL450\n";
154 o += "OpEntryPoint GLCompute %main \"main\" " + iface + "\n";
155 o += "OpExecutionMode %main LocalSize "
156 + std::to_string(ws[0]) + " "
157 + std::to_string(ws[1]) + " "
158 + std::to_string(ws[2]) + "\n\n";
159 return o;
160 }
161
162 /**
163 * Emit decorations for all SSBO bindings and push constant member offsets.
164 */
165 std::string emit_decorations(const ShaderSpec& spec)
166 {
167 std::string o;
168 o += "OpDecorate %gid_var BuiltIn GlobalInvocationId\n";
169
170 for (const auto& b : spec.bindings) {
172 || b.modality == Kakshya::DataModality::IMAGE_2D)
173 continue;
174
175 const auto stride = static_cast<uint32_t>(Kakshya::gpu_data_format_bytes(b.format));
176 o += "OpDecorate %rta_" + b.name + " ArrayStride "
177 + std::to_string(stride) + "\n";
178 o += "OpMemberDecorate %blk_" + b.name + " 0 Offset 0\n";
179 o += "OpDecorate %blk_" + b.name + " Block\n";
180 o += "OpDecorate %buf_" + b.name + " DescriptorSet " + std::to_string(b.set) + "\n";
181 o += "OpDecorate %buf_" + b.name + " Binding "
182 + std::to_string(b.binding_index) + "\n";
183 }
184
185 for (const auto& b : spec.bindings) {
186 if (b.modality != Kakshya::DataModality::IMAGE_2D)
187 continue;
188
189 const std::string var = "%img_" + b.name;
190 o += "OpDecorate " + var + " DescriptorSet " + std::to_string(b.set) + "\n";
191 o += "OpDecorate " + var + " Binding "
192 + std::to_string(b.binding_index) + "\n";
193
194 if (b.direction == BindingDirection::Input) {
195 o += "OpDecorate " + var + " NonWritable\n";
196 } else if (b.direction == BindingDirection::Output) {
197 o += "OpDecorate " + var + " NonReadable\n";
198 }
199 }
200
201 for (const auto& b : spec.bindings) {
202 if (b.modality != Kakshya::DataModality::TEXTURE_2D)
203 continue;
204 o += "OpDecorate %tex_" + b.name + " DescriptorSet " + std::to_string(b.set) + "\n";
205 o += "OpDecorate %tex_" + b.name + " Binding "
206 + std::to_string(b.binding_index) + "\n";
207 }
208
210 o += "OpDecorate %lid_var BuiltIn LocalInvocationId\n";
211
212 if (!spec.pc_fields.empty()) {
213 o += "OpDecorate %pc_blk Block\n";
214 uint32_t off = 0;
215 for (size_t i = 0; i < spec.pc_fields.size(); ++i) {
216 o += "OpMemberDecorate %pc_blk " + std::to_string(i)
217 + " Offset " + std::to_string(off) + "\n";
218 off += static_cast<uint32_t>(
220 }
221 }
222 o += "\n";
223 return o;
224 }
225
226 /**
227 * Emit type declarations for all used types, including void, voidfn, u32, f32,
228 * v3u32, and the GlobalInvocationId builtin.
229 */
230 std::string emit_types(const ShaderSpec& spec)
231 {
232 std::string o;
233 o += "%void = OpTypeVoid\n";
234 o += "%voidfn = OpTypeFunction %void\n";
235 o += "%u32 = OpTypeInt 32 0\n";
236 o += "%f32 = OpTypeFloat 32\n";
237 o += "%v3u32 = OpTypeVector %u32 3\n";
238 o += "%ptr_in_v3u32 = OpTypePointer Input %v3u32\n";
239 o += "%gid_var = OpVariable %ptr_in_v3u32 Input\n\n";
240 o += "%bool = OpTypeBool\n";
241
242 bool need_v2f32 = false;
243 bool need_v3f32 = false;
244 bool need_v4f32 = false;
245
246 for (const auto& b : spec.bindings) {
248 || b.modality == Kakshya::DataModality::IMAGE_2D)
249 continue;
250 switch (b.format) {
252 need_v2f32 = true;
253 break;
255 need_v3f32 = true;
256 break;
258 need_v4f32 = true;
259 break;
260 default:
261 break;
262 }
263 }
264
265 if (need_v2f32)
266 o += "%v2f32 = OpTypeVector %f32 2\n";
267 if (need_v3f32)
268 o += "%v3f32 = OpTypeVector %f32 3\n";
269
270 bool need_i32 = false;
271 for (const auto& b : spec.bindings) {
273 || b.modality == Kakshya::DataModality::IMAGE_2D)
274 continue;
275 if (b.format == Kakshya::GpuDataFormat::INT32)
276 need_i32 = true;
277 }
278
279 bool has_image_2d = false;
280 for (const auto& b : spec.bindings) {
281 if (b.modality == Kakshya::DataModality::IMAGE_2D) {
282 has_image_2d = true;
283 break;
284 }
285 }
286 if (need_v4f32 && !has_image_2d)
287 o += "%v4f32 = OpTypeVector %f32 4\n";
288 if (need_v2f32 || need_v3f32 || (need_v4f32 && !has_image_2d))
289 o += "\n";
290 if (need_i32 && !has_image_2d)
291 o += "%i32 = OpTypeInt 32 1\n";
292
293 for (const auto& b : spec.bindings) {
295 || b.modality == Kakshya::DataModality::IMAGE_2D)
296 continue;
297
298 const std::string_view etype = ssbo_elem_spirv_type(b.format);
299 o += "%rta_" + b.name + " = OpTypeRuntimeArray " + std::string(etype) + "\n";
300 o += "%blk_" + b.name + " = OpTypeStruct %rta_" + b.name + "\n";
301 o += "%pblk_" + b.name + " = OpTypePointer StorageBuffer %blk_" + b.name + "\n";
302 o += "%buf_" + b.name + " = OpVariable %pblk_" + b.name + " StorageBuffer\n";
303 o += "%pelem_" + b.name + " = OpTypePointer StorageBuffer "
304 + std::string(etype) + "\n";
305 }
306 o += "\n";
307
308 if (has_image_2d) {
309 o += "%i32 = OpTypeInt 32 1\n";
310 o += "%v2i32 = OpTypeVector %i32 2\n";
311 o += "%v4f32 = OpTypeVector %f32 4\n";
312 o += "%img_czero = OpConstant %f32 0.0\n";
313 o += "%img_cone = OpConstant %f32 1.0\n";
314 o += "%ci0 = OpConstant %i32 0\n";
315 o += "%ci1 = OpConstant %i32 1\n";
316 o += "%img2d_t = OpTypeImage %f32 2D 0 0 0 2 Unknown\n";
317 o += "%ptr_img2d = OpTypePointer UniformConstant %img2d_t\n";
318 for (const auto& b : spec.bindings) {
319 if (b.modality != Kakshya::DataModality::IMAGE_2D)
320 continue;
321 o += "%img_" + b.name + " = OpVariable %ptr_img2d UniformConstant\n";
322 }
323 o += "\n";
324 }
325
326 bool has_texture_2d = false;
327 for (const auto& b : spec.bindings) {
328 if (b.modality == Kakshya::DataModality::TEXTURE_2D) {
329 has_texture_2d = true;
330 break;
331 }
332 }
333
334 if (has_texture_2d) {
335 if (!need_v2f32 && !has_image_2d)
336 o += "%v2f32 = OpTypeVector %f32 2\n";
337 o += "%sampler_t = OpTypeSampler\n";
338 o += "%img2d_s_t = OpTypeImage %f32 2D 0 0 0 1 Unknown\n";
339 o += "%simgc_t = OpTypeSampledImage %img2d_s_t\n";
340 o += "%ptr_simg = OpTypePointer UniformConstant %simgc_t\n";
341 for (const auto& b : spec.bindings) {
342 if (b.modality != Kakshya::DataModality::TEXTURE_2D)
343 continue;
344 o += "%tex_" + b.name + " = OpVariable %ptr_simg UniformConstant\n";
345 }
346 o += "%lod_zero = OpConstant %f32 0.0\n";
347 o += "\n";
348 }
349
350 if (spec.tmpl == KernelTemplate::Reduction) {
351 const uint32_t local = spec.workgroup_size[0];
352 const std::string ls = std::to_string(local);
353 const bool is_max_index = (spec.op == KernelOp::MaxIndex);
354
355 o += "%bool = OpTypeBool\n";
356 o += "%lid_var = OpVariable %ptr_in_v3u32 Input\n";
357 o += "%arr_sh_t = OpTypeArray %f32 %c" + ls + "u\n";
358 o += "%psh_arr = OpTypePointer Workgroup %arr_sh_t\n";
359 o += "%shared_a = OpVariable %psh_arr Workgroup\n";
360 o += "%psh_f32 = OpTypePointer Workgroup %f32\n";
361
362 if (is_max_index) {
363 o += "%arr_shu_t = OpTypeArray %u32 %c" + ls + "u\n";
364 o += "%pshu_arr = OpTypePointer Workgroup %arr_shu_t\n";
365 o += "%shared_idx = OpVariable %pshu_arr Workgroup\n";
366 o += "%psh_u32 = OpTypePointer Workgroup %u32\n";
367 }
368
369 o += "%c" + ls + "u = OpConstant %u32 " + ls + "\n";
370 o += "%c2u = OpConstant %u32 2\n";
371 o += "%c264u = OpConstant %u32 264\n";
372 o += "\n";
373 } else if (spec.tmpl == KernelTemplate::Scan) {
374 const uint32_t local = spec.workgroup_size[0];
375 const std::string ls = std::to_string(local);
376
377 o += "%bool = OpTypeBool\n";
378 o += "%lid_var = OpVariable %ptr_in_v3u32 Input\n";
379 o += "%arr_sh_t = OpTypeArray %f32 %c" + ls + "u\n";
380 o += "%psh_arr = OpTypePointer Workgroup %arr_sh_t\n";
381 o += "%shared_a = OpVariable %psh_arr Workgroup\n";
382 o += "%shared_b = OpVariable %psh_arr Workgroup\n";
383 o += "%psh_f32 = OpTypePointer Workgroup %f32\n";
384 o += "%c" + ls + "u = OpConstant %u32 " + ls + "\n";
385 o += "%c2u = OpConstant %u32 2\n";
386 o += "%c264u = OpConstant %u32 264\n";
387 o += "\n";
388 }
389
390 if (!spec.pc_fields.empty()) {
391 bool pc_has_uint = false;
392 bool pc_has_int = false;
393 o += "%pc_blk = OpTypeStruct";
394 for (const auto& f : spec.pc_fields) {
395 o += " " + std::string(ssbo_elem_spirv_type(f.format));
396 if (f.format == Kakshya::GpuDataFormat::UINT32) {
397 pc_has_uint = true;
398 } else if (f.format == Kakshya::GpuDataFormat::INT32) {
399 pc_has_int = true;
400 }
401 }
402 o += "\n";
403 o += "%ppc = OpTypePointer PushConstant %pc_blk\n";
404 o += "%pc = OpVariable %ppc PushConstant\n";
405 o += "%ppc_f32 = OpTypePointer PushConstant %f32\n";
406 if (pc_has_uint)
407 o += "%ppc_u32 = OpTypePointer PushConstant %u32\n";
408 if (pc_has_int)
409 o += "%ppc_i32 = OpTypePointer PushConstant %i32\n";
410 o += "\n";
411 }
412
413 o += "%c0u = OpConstant %u32 0\n";
414 o += "%c1u = OpConstant %u32 1\n";
415
416 for (size_t i = 2; i < spec.pc_fields.size(); ++i) {
417 o += "%c" + std::to_string(i) + "u = OpConstant %u32 "
418 + std::to_string(i) + "\n";
419 }
420
421 o += "\n";
422 return o;
423 }
424
425 /**
426 * Emit the entry point body for the Elementwise template (also used
427 * as the fallback for GeometryEmit).
428 * Loads the index, loads PC fields, loads SSBO elements, applies op, stores.
429 *
430 * Fixed shape for a 3x3 Moore-neighborhood sum over a single input SSBO
431 * (scalar element format: FLOAT32, UINT32, or INT32), written to a single
432 * output SSBO of the same element count. Grid width and height are read
433 * from the first two PC fields, mirroring emit_image_body's width/height
434 * PC convention. Out-of-bounds neighbor reads clamp to the nearest
435 * in-bounds cell.
436 *
437 * The op field selects how the center cell's own value and the eight-
438 * neighbor sum combine into the output: Add sums center plus neighbor
439 * sum, Sub subtracts neighbor sum from center, Multiply scales center by
440 * neighbor sum. Any other op copies the neighbor sum through unchanged,
441 * matching emit_elementwise_body's own default-case convention.
442 */
443 std::string emit_stencil_body(const ShaderSpec& spec)
444 {
445 const BindingSlot* in_ssbo = nullptr;
446 const BindingSlot* out_ssbo = nullptr;
447 for (const auto& b : spec.bindings) {
448 if (b.direction == BindingDirection::Output && !out_ssbo) {
449 out_ssbo = &b;
450 } else if (b.direction != BindingDirection::Output && !in_ssbo) {
451 in_ssbo = &b;
452 }
453 }
454
455 const std::string_view etype = ssbo_elem_spirv_type(in_ssbo->format);
456 const bool is_float = (etype == "%f32");
457 const std::string add_op = is_float ? "OpFAdd" : "OpIAdd";
458 const std::string sub_op = is_float ? "OpFSub" : "OpISub";
459 const std::string mul_op = is_float ? "OpFMul" : "OpIMul";
460 const std::string in_name = in_ssbo->name;
461 const std::string out_name = out_ssbo->name;
462
463 std::string o;
464 o += "%main = OpFunction %void None %voidfn\n";
465 o += "%entry = OpLabel\n";
466 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
467 o += "%ix = OpCompositeExtract %u32 %gid3 0\n";
468 o += "%iy = OpCompositeExtract %u32 %gid3 1\n\n";
469
470 o += "%ppc_width = OpAccessChain %ppc_u32 %pc %c0u\n";
471 o += "%pc_width = OpLoad %u32 %ppc_width\n";
472 o += "%ppc_height = OpAccessChain %ppc_u32 %pc %c1u\n";
473 o += "%pc_height = OpLoad %u32 %ppc_height\n\n";
474
475 o += "%w_m1 = OpISub %u32 %pc_width %c1u\n";
476 o += "%h_m1 = OpISub %u32 %pc_height %c1u\n\n";
477
478 o += "%i = OpIMul %u32 %iy %pc_width\n";
479 o += "%i_flat = OpIAdd %u32 %i %ix\n\n";
480
481 const std::array<std::pair<int, int>, 9> offsets = { { { -1, -1 }, { 0, -1 }, { 1, -1 },
482 { -1, 0 }, { 0, 0 }, { 1, 0 },
483 { -1, 1 }, { 0, 1 }, { 1, 1 } } };
484
485 std::string center_val;
486 std::string running_sum;
487 bool sum_started = false;
488
489 for (size_t n = 0; n < offsets.size(); ++n) {
490 const auto [dx, dy] = offsets[n];
491 const std::string ns = std::to_string(n);
492
493 std::string nx = "%ix";
494 if (dx == 1) {
495 o += "%nxr" + ns + " = OpIAdd %u32 %ix %c1u\n";
496 o += "%oobx" + ns + " = OpUGreaterThan %bool %nxr" + ns + " %w_m1\n";
497 o += "%nx" + ns + " = OpSelect %u32 %oobx" + ns + " %w_m1 %nxr" + ns + "\n";
498 nx = "%nx" + ns;
499 } else if (dx == -1) {
500 o += "%z" + ns + " = OpIEqual %bool %ix %c0u\n";
501 o += "%nxr" + ns + " = OpISub %u32 %ix %c1u\n";
502 o += "%nx" + ns + " = OpSelect %u32 %z" + ns + " %c0u %nxr" + ns + "\n";
503 nx = "%nx" + ns;
504 }
505
506 std::string ny = "%iy";
507 if (dy == 1) {
508 o += "%nyr" + ns + " = OpIAdd %u32 %iy %c1u\n";
509 o += "%ooby" + ns + " = OpUGreaterThan %bool %nyr" + ns + " %h_m1\n";
510 o += "%ny" + ns + " = OpSelect %u32 %ooby" + ns + " %h_m1 %nyr" + ns + "\n";
511 ny = "%ny" + ns;
512 } else if (dy == -1) {
513 o += "%zy" + ns + " = OpIEqual %bool %iy %c0u\n";
514 o += "%nyr" + ns + " = OpISub %u32 %iy %c1u\n";
515 o += "%ny" + ns + " = OpSelect %u32 %zy" + ns + " %c0u %nyr" + ns + "\n";
516 ny = "%ny" + ns;
517 }
518
519 o += "%nrow" + ns + " = OpIMul %u32 " + ny + " %pc_width\n";
520 o += "%nidx" + ns + " = OpIAdd %u32 %nrow" + ns + " " + nx + "\n";
521 o += "%ngep" + ns + " = OpAccessChain %pelem_" + in_name
522 + " %buf_" + in_name + " %c0u %nidx" + ns + "\n";
523 o += "%nval" + ns + " = OpLoad " + std::string(etype) + " %ngep" + ns + "\n";
524
525 if (dx == 0 && dy == 0) {
526 center_val = "%nval" + ns;
527 continue;
528 }
529
530 if (!sum_started) {
531 running_sum = "%nval" + ns;
532 sum_started = true;
533 } else {
534 const std::string next_sum = "%sum" + ns;
535 o += next_sum + " = " + add_op + " " + std::string(etype) + " " + running_sum + " %nval" + ns + "\n";
536 running_sum = next_sum;
537 }
538 }
539 o += "\n";
540
541 std::string result;
542 switch (spec.op) {
543 case KernelOp::Add:
544 o += "%res = " + add_op + " " + std::string(etype) + " " + center_val + " " + running_sum + "\n";
545 result = "%res";
546 break;
547 case KernelOp::Sub:
548 o += "%res = " + sub_op + " " + std::string(etype) + " " + center_val + " " + running_sum + "\n";
549 result = "%res";
550 break;
552 o += "%res = " + mul_op + " " + std::string(etype) + " " + center_val + " " + running_sum + "\n";
553 result = "%res";
554 break;
556 o += "%ppc_rate = OpAccessChain %ppc_f32 %pc %c2u\n";
557 o += "%pc_rate = OpLoad %f32 %ppc_rate\n";
558 o += "%ppc_wsum = OpAccessChain %ppc_f32 %pc %c3u\n";
559 o += "%pc_wsum = OpLoad %f32 %ppc_wsum\n";
560 o += "%scaled_sum = OpFMul %f32 " + running_sum + " %pc_wsum\n";
561 o += "%delta = OpFSub %f32 %scaled_sum " + center_val + "\n";
562 o += "%weighted = OpFMul %f32 %pc_rate %delta\n";
563 o += "%res = OpFAdd %f32 " + center_val + " %weighted\n";
564 result = "%res";
565 break;
566 default:
567 result = running_sum;
568 break;
569 }
570 o += "\n";
571
572 o += "%out_gep = OpAccessChain %pelem_" + out_name
573 + " %buf_" + out_name + " %c0u %i_flat\n";
574 o += "OpStore %out_gep " + result + "\n";
575
576 o += "OpReturn\n";
577 o += "OpFunctionEnd\n";
578 return o;
579 }
580
581 /**
582 * Emit the entry point body for Elementwise/Stencil templates.
583 * Loads the index, loads PC fields, loads SSBO elements, applies op, stores.
584 */
585 std::string emit_elementwise_body(const ShaderSpec& spec)
586 {
587 std::string o;
588 o += "%main = OpFunction %void None %voidfn\n";
589 o += "%entry = OpLabel\n";
590 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
591 o += "%i = OpCompositeExtract %u32 %gid3 0\n\n";
592
593 for (size_t fi = 0; fi < spec.pc_fields.size(); ++fi) {
594 const auto& f = spec.pc_fields[fi];
595 const std::string_view pptr = (f.format == Kakshya::GpuDataFormat::UINT32)
596 ? "%ppc_u32"
597 : (f.format == Kakshya::GpuDataFormat::INT32 ? "%ppc_i32" : "%ppc_f32");
598 const std::string_view ld_type = ssbo_elem_spirv_type(f.format);
599 o += "%ppc_" + f.name + " = OpAccessChain " + std::string(pptr)
600 + " %pc %c" + std::to_string(fi) + "u\n";
601 o += "%pc_" + f.name + " = OpLoad " + std::string(ld_type)
602 + " %ppc_" + f.name + "\n";
603 }
604 o += "\n";
605
606 std::vector<const BindingSlot*> ssbos;
607 for (const auto& b : spec.bindings) {
609 || b.modality == Kakshya::DataModality::IMAGE_2D)
610 continue;
611 ssbos.push_back(&b);
612 }
613
615 for (const auto* b : ssbos) {
616 if (b->direction != BindingDirection::Output) {
617 primary_fmt = b->format;
618 break;
619 }
620 }
621 const std::string_view etype = ssbo_elem_spirv_type(primary_fmt);
622 const uint32_t ncomp = ssbo_elem_components(primary_fmt);
623 const bool is_vector = (ncomp > 1);
624
625 for (const auto* b : ssbos) {
626 o += "%gep_" + b->name + " = OpAccessChain %pelem_" + b->name
627 + " %buf_" + b->name + " %c0u %i\n";
628 if (b->direction != BindingDirection::Output) {
629 o += "%val_" + b->name + " = OpLoad " + std::string(etype)
630 + " %gep_" + b->name + "\n";
631 }
632 }
633 o += "\n";
634
635 auto pc_operand = [&](const std::string& field_name) -> std::string {
636 if (!is_vector)
637 return "%pc_" + field_name;
638 const std::string splat = "%spc_" + field_name;
639 std::string construct = splat + " = OpCompositeConstruct "
640 + std::string(etype);
641 const std::string scalar = " %pc_" + field_name;
642 for (uint32_t c = 0; c < ncomp; ++c)
643 construct += scalar;
644 o += construct + "\n";
645 return splat;
646 };
647
648 const std::string v0 = ssbos.empty() ? "" : ("%val_" + ssbos[0]->name);
649 const std::string v1 = ssbos.size() > 1 ? ("%val_" + ssbos[1]->name) : "";
650 const std::string p0 = spec.pc_fields.empty() ? ""
651 : pc_operand(spec.pc_fields[0].name);
652 const std::string p1 = spec.pc_fields.size() > 1
653 ? pc_operand(spec.pc_fields[1].name)
654 : "";
655
656 std::string result;
657 const std::string et = std::string(etype);
658 switch (spec.op) {
659 case KernelOp::Scale:
660 if (is_vector) {
661 o += "%res = OpVectorTimesScalar " + et + " " + v0
662 + " %pc_" + spec.pc_fields[0].name + "\n";
663 } else {
664 o += "%res = OpFMul " + et + " " + v0 + " " + p0 + "\n";
665 }
666
667 result = "%res";
668 break;
670 if (is_vector) {
671 o += "%scaled = OpVectorTimesScalar " + et + " " + v0
672 + " %pc_" + spec.pc_fields[0].name + "\n";
673 o += "%res = OpFAdd " + et + " %scaled " + p1 + "\n";
674 } else {
675 o += "%mul = OpFMul " + et + " " + v0 + " " + p0 + "\n";
676 o += "%res = OpFAdd " + et + " %mul " + p1 + "\n";
677 }
678
679 result = "%res";
680 break;
681 case KernelOp::Offset:
682 o += "%res = OpFAdd " + et + " " + v0 + " " + p0 + "\n";
683 result = "%res";
684 break;
685 case KernelOp::Clip:
686 o += "%res = OpExtInst " + et + " %glsl FClamp " + v0
687 + " " + p0 + " " + p1 + "\n";
688 result = "%res";
689 break;
690 case KernelOp::Abs:
691 o += "%res = OpExtInst " + et + " %glsl FAbs " + v0 + "\n";
692 result = "%res";
693 break;
694 case KernelOp::Negate:
695 o += "%res = OpFNegate " + et + " " + v0 + "\n";
696 result = "%res";
697 break;
698 case KernelOp::Add:
699 o += "%res = OpFAdd " + et + " " + v0 + " " + v1 + "\n";
700 result = "%res";
701 break;
703 o += "%res = OpFMul " + et + " " + v0 + " " + v1 + "\n";
704 result = "%res";
705 break;
706 case KernelOp::Mix:
707 o += "%dlt = OpFSub %f32 " + v1 + " " + v0 + "\n";
708 o += "%scl = OpFMul %f32 %dlt " + p0 + "\n";
709 o += "%res = OpFAdd %f32 " + v0 + " %scl\n";
710 result = "%res";
711 break;
712 case KernelOp::Sub:
713 o += "%res = OpFSub " + et + " " + v0 + " " + v1 + "\n";
714 result = "%res";
715 break;
716 case KernelOp::Fma:
717 o += "%res = OpExtInst %f32 %glsl Fma " + v0 + " " + p0 + " " + p1 + "\n";
718 result = "%res";
719 break;
720 case KernelOp::Floor:
721 o += "%res = OpExtInst %f32 %glsl Floor " + v0 + "\n";
722 result = "%res";
723 break;
724 case KernelOp::Ceil:
725 o += "%res = OpExtInst %f32 %glsl Ceil " + v0 + "\n";
726 result = "%res";
727 break;
728 case KernelOp::Round:
729 o += "%res = OpExtInst %f32 %glsl Round " + v0 + "\n";
730 result = "%res";
731 break;
732 case KernelOp::Trunc:
733 o += "%res = OpExtInst %f32 %glsl Trunc " + v0 + "\n";
734 result = "%res";
735 break;
736 case KernelOp::Fract:
737 o += "%res = OpExtInst %f32 %glsl Fract " + v0 + "\n";
738 result = "%res";
739 break;
740 case KernelOp::Sqrt:
741 o += "%res = OpExtInst %f32 %glsl Sqrt " + v0 + "\n";
742 result = "%res";
743 break;
745 o += "%res = OpExtInst %f32 %glsl InverseSqrt " + v0 + "\n";
746 result = "%res";
747 break;
748 case KernelOp::Sin:
749 o += "%res = OpExtInst %f32 %glsl Sin " + v0 + "\n";
750 result = "%res";
751 break;
752 case KernelOp::Cos:
753 o += "%res = OpExtInst %f32 %glsl Cos " + v0 + "\n";
754 result = "%res";
755 break;
756 case KernelOp::Tan:
757 o += "%res = OpExtInst %f32 %glsl Tan " + v0 + "\n";
758 result = "%res";
759 break;
760 case KernelOp::Asin:
761 o += "%res = OpExtInst %f32 %glsl Asin " + v0 + "\n";
762 result = "%res";
763 break;
764 case KernelOp::Acos:
765 o += "%res = OpExtInst %f32 %glsl Acos " + v0 + "\n";
766 result = "%res";
767 break;
768 case KernelOp::Atan:
769 o += "%res = OpExtInst %f32 %glsl Atan " + v0 + "\n";
770 result = "%res";
771 break;
772 case KernelOp::Sinh:
773 o += "%res = OpExtInst %f32 %glsl Sinh " + v0 + "\n";
774 result = "%res";
775 break;
776 case KernelOp::Cosh:
777 o += "%res = OpExtInst %f32 %glsl Cosh " + v0 + "\n";
778 result = "%res";
779 break;
780 case KernelOp::Tanh:
781 o += "%res = OpExtInst %f32 %glsl Tanh " + v0 + "\n";
782 result = "%res";
783 break;
784 case KernelOp::Exp:
785 o += "%res = OpExtInst %f32 %glsl Exp " + v0 + "\n";
786 result = "%res";
787 break;
788 case KernelOp::Exp2:
789 o += "%res = OpExtInst %f32 %glsl Exp2 " + v0 + "\n";
790 result = "%res";
791 break;
792 case KernelOp::Log:
793 o += "%res = OpExtInst %f32 %glsl Log " + v0 + "\n";
794 result = "%res";
795 break;
796 case KernelOp::Log2:
797 o += "%res = OpExtInst %f32 %glsl Log2 " + v0 + "\n";
798 result = "%res";
799 break;
800 case KernelOp::Pow:
801 o += "%res = OpExtInst %f32 %glsl Pow " + v0 + " " + v1 + "\n";
802 result = "%res";
803 break;
804 case KernelOp::Atan2:
805 o += "%res = OpExtInst %f32 %glsl Atan2 " + v0 + " " + v1 + "\n";
806 result = "%res";
807 break;
808 case KernelOp::Min:
809 o += "%res = OpExtInst %f32 %glsl FMin " + v0 + " " + v1 + "\n";
810 result = "%res";
811 break;
812 case KernelOp::MaxTwo:
813 o += "%res = OpExtInst %f32 %glsl FMax " + v0 + " " + v1 + "\n";
814 result = "%res";
815 break;
816 case KernelOp::Step:
817 o += "%res = OpExtInst %f32 %glsl Step " + v0 + " " + v1 + "\n";
818 result = "%res";
819 break;
821 o += "%res = OpExtInst %f32 %glsl SmoothStep " + v0 + " " + v1 + " " + p0 + "\n";
822 result = "%res";
823 break;
825 o += "%i_f32 = OpConvertUToF %f32 %i\n";
826 o += "%res = OpFMul " + et + " " + v0 + " %i_f32\n";
827 result = "%res";
828 break;
829 }
830 case KernelOp::CompareGE: {
831 Kakshya::GpuDataFormat out_fmt = primary_fmt;
832 uint32_t out_ncomp = ncomp;
833 for (const auto* b : ssbos) {
834 if (b->direction == BindingDirection::Output) {
835 out_fmt = b->format;
836 out_ncomp = ssbo_elem_components(out_fmt);
837 break;
838 }
839 }
840 const std::string_view out_etype = ssbo_elem_spirv_type(out_fmt);
841
842 std::string cmp_lhs = v0;
843 if (is_vector) {
844 o += "%cmp_scalar = OpCompositeExtract %f32 " + v0 + " 0\n";
845 cmp_lhs = "%cmp_scalar";
846 } else if (etype != "%f32") {
847 o += "%cmp_lhs_f = OpConvertUToF %f32 " + v0 + "\n";
848 cmp_lhs = "%cmp_lhs_f";
849 }
850
851 const bool has_threshold = !spec.pc_fields.empty();
852 std::string threshold_f;
853 if (has_threshold) {
854 threshold_f = p0;
855 } else {
856 o += "%cmp_zero_u = OpConvertUToF %f32 %c0u\n";
857 threshold_f = "%cmp_zero_u";
858 }
859
860 o += "%cmp = OpFOrdGreaterThanEqual %bool " + cmp_lhs + " " + threshold_f + "\n";
861 o += "%cmp_one_f = OpConvertUToF %f32 %c1u\n";
862 o += "%cmp_zero_f = OpConvertUToF %f32 %c0u\n";
863 o += "%cmp_scaled = OpSelect %f32 %cmp %cmp_one_f %cmp_zero_f\n";
864
865 if (out_ncomp > 1) {
866 o += "%res = OpCompositeConstruct " + std::string(out_etype);
867 for (uint32_t c = 0; c < out_ncomp; ++c)
868 o += " %cmp_scaled";
869 o += "\n";
870 } else if (out_etype != "%f32") {
871 o += "%res = OpConvertFToU " + std::string(out_etype) + " %cmp_scaled\n";
872 } else {
873 o += "%res = OpCopyObject %f32 %cmp_scaled\n";
874 }
875 result = "%res";
876 break;
877 }
878 default:
879 for (const auto* b : ssbos) {
880 if (b->direction == BindingDirection::Output && b->format != primary_fmt)
881 return {};
882 }
883 o += "%res = OpCopyObject " + et + " " + v0 + "\n";
884 result = "%res";
885 break;
886 }
887 o += "\n";
888
889 for (const auto* b : ssbos) {
890 if (b->direction == BindingDirection::Input)
891 continue;
892 o += "OpStore %gep_" + b->name + " " + result + "\n";
893 }
894
895 o += "OpReturn\n";
896 o += "OpFunctionEnd\n";
897 return o;
898 }
899
900 std::string emit_reduction_body(const ShaderSpec& spec)
901 {
902 const uint32_t local = spec.workgroup_size[0];
903 const std::string ls = std::to_string(local);
904 const bool is_max = (spec.op == KernelOp::Max);
905 const auto& b0 = spec.bindings.front();
906
907 std::string o;
908 o += "%main = OpFunction %void None %voidfn\n";
909 o += "%entry = OpLabel\n";
910
911 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
912 o += "%i = OpCompositeExtract %u32 %gid3 0\n";
913 o += "%lid3 = OpLoad %v3u32 %lid_var\n";
914 o += "%lid = OpCompositeExtract %u32 %lid3 0\n\n";
915
916 o += "%gep_in = OpAccessChain %pelem_" + b0.name
917 + " %buf_" + b0.name + " %c0u %i\n";
918 o += "%elem = OpLoad %f32 %gep_in\n";
919 o += "%pgsh = OpAccessChain %psh_f32 %shared_a %lid\n";
920 o += "OpStore %pgsh %elem\n";
921 o += "OpControlBarrier %c2u %c2u %c264u\n\n";
922
923 o += "%s_init = OpShiftRightLogical %u32 %c" + ls + "u %c1u\n";
924 o += "OpBranch %loop_hdr\n\n";
925
926 o += "%loop_hdr = OpLabel\n";
927 o += "%stride = OpPhi %u32 %s_init %entry %s_next %loop_cont\n";
928 o += "OpLoopMerge %loop_merge %loop_cont None\n";
929 o += "OpBranch %loop_body\n\n";
930
931 o += "%loop_body = OpLabel\n";
932 o += "%active = OpULessThan %bool %lid %stride\n";
933 o += "OpSelectionMerge %sel_merge None\n";
934 o += "OpBranchConditional %active %do_op %sel_merge\n\n";
935
936 o += "%do_op = OpLabel\n";
937 o += "%pgsh_a = OpAccessChain %psh_f32 %shared_a %lid\n";
938 o += "%a = OpLoad %f32 %pgsh_a\n";
939 o += "%lid_b = OpIAdd %u32 %lid %stride\n";
940 o += "%pgsh_b = OpAccessChain %psh_f32 %shared_a %lid_b\n";
941 o += "%b = OpLoad %f32 %pgsh_b\n";
942
943 if (is_max) {
944 o += "%combined = OpExtInst %f32 %glsl FMax %a %b\n";
945 } else {
946 o += "%combined = OpFAdd %f32 %a %b\n";
947 }
948
949 o += "OpStore %pgsh_a %combined\n";
950 o += "OpBranch %sel_merge\n\n";
951
952 o += "%sel_merge = OpLabel\n";
953 o += "OpBranch %loop_cont\n\n";
954
955 o += "%loop_cont = OpLabel\n";
956 o += "OpControlBarrier %c2u %c2u %c264u\n";
957 o += "%s_next = OpShiftRightLogical %u32 %stride %c1u\n";
958 o += "%done = OpIEqual %bool %s_next %c0u\n";
959 o += "OpBranchConditional %done %loop_merge %loop_hdr\n\n";
960
961 o += "%loop_merge = OpLabel\n";
962 o += "%is_zero = OpIEqual %bool %lid %c0u\n";
963 o += "OpSelectionMerge %write_merge None\n";
964 o += "OpBranchConditional %is_zero %do_write %write_merge\n\n";
965
966 o += "%do_write = OpLabel\n";
967 o += "%result = OpLoad %f32 %pgsh\n";
968 o += "%gep_out = OpAccessChain %pelem_" + b0.name
969 + " %buf_" + b0.name + " %c0u %c0u\n";
970 o += "OpStore %gep_out %result\n";
971 o += "OpBranch %write_merge\n\n";
972
973 o += "%write_merge = OpLabel\n";
974 o += "OpReturn\n";
975 o += "OpFunctionEnd\n";
976 return o;
977 }
978
979 /**
980 * @brief Emit the entry point body for the Scan template (inclusive
981 * prefix sum over one InOut SSBO).
982 *
983 * Double-buffered Hillis-Steele scan in workgroup shared memory:
984 * log2(local_size) fixed passes, unrolled at generation time, each pass
985 * reading exclusively from one shared array and writing exclusively to
986 * the other, so no lane can read a value another lane has already
987 * overwritten in the same pass. Strides are derived via successive
988 * OpShiftLeftLogical on %c1u rather than emitting a fresh OpConstant
989 * per pass, since a literal-valued constant can collide with an
990 * existing constant of the same value already declared elsewhere in
991 * the module.
992 *
993 * @param spec ShaderSpec with tmpl == KernelTemplate::Scan and exactly
994 * one InOut FLOAT32 SSBO binding.
995 * @return SPIR-V function body text for %main.
996 */
997 std::string emit_scan_body(const ShaderSpec& spec)
998 {
999 const uint32_t local = spec.workgroup_size[0];
1000 const auto log2_local = static_cast<uint32_t>(std::log2(static_cast<double>(local)));
1001 const auto& b0 = spec.bindings.front();
1002
1003 std::string o;
1004 o += "%main = OpFunction %void None %voidfn\n";
1005 o += "%entry = OpLabel\n";
1006
1007 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
1008 o += "%i = OpCompositeExtract %u32 %gid3 0\n";
1009 o += "%lid3 = OpLoad %v3u32 %lid_var\n";
1010 o += "%lid = OpCompositeExtract %u32 %lid3 0\n\n";
1011
1012 o += "%gep_in = OpAccessChain %pelem_" + b0.name
1013 + " %buf_" + b0.name + " %c0u %i\n";
1014 o += "%elem = OpLoad %f32 %gep_in\n";
1015 o += "%pgsh0 = OpAccessChain %psh_f32 %shared_a %lid\n";
1016 o += "OpStore %pgsh0 %elem\n";
1017 o += "OpControlBarrier %c2u %c2u %c264u\n\n";
1018
1019 std::string read_buf = "%shared_a";
1020 std::string write_buf = "%shared_b";
1021 std::string stride_val = "%c1u";
1022
1023 for (uint32_t pass = 0; pass < log2_local; ++pass) {
1024 const std::string ps = std::to_string(pass);
1025
1026 o += "%has_left_" + ps + " = OpUGreaterThanEqual %bool %lid " + stride_val + "\n";
1027 o += "OpSelectionMerge %scan_merge_" + ps + " None\n";
1028 o += "OpBranchConditional %has_left_" + ps + " %scan_add_" + ps + " %scan_copy_" + ps + "\n\n";
1029
1030 o += "%scan_add_" + ps + " = OpLabel\n";
1031 o += "%self_ptr_" + ps + " = OpAccessChain %psh_f32 " + read_buf + " %lid\n";
1032 o += "%self_val_" + ps + " = OpLoad %f32 %self_ptr_" + ps + "\n";
1033 o += "%left_idx_" + ps + " = OpISub %u32 %lid " + stride_val + "\n";
1034 o += "%left_ptr_" + ps + " = OpAccessChain %psh_f32 " + read_buf + " %left_idx_" + ps + "\n";
1035 o += "%left_val_" + ps + " = OpLoad %f32 %left_ptr_" + ps + "\n";
1036 o += "%sum_" + ps + " = OpFAdd %f32 %self_val_" + ps + " %left_val_" + ps + "\n";
1037 o += "%wptr_add_" + ps + " = OpAccessChain %psh_f32 " + write_buf + " %lid\n";
1038 o += "OpStore %wptr_add_" + ps + " %sum_" + ps + "\n";
1039 o += "OpBranch %scan_merge_" + ps + "\n\n";
1040
1041 o += "%scan_copy_" + ps + " = OpLabel\n";
1042 o += "%pass_ptr_" + ps + " = OpAccessChain %psh_f32 " + read_buf + " %lid\n";
1043 o += "%pass_val_" + ps + " = OpLoad %f32 %pass_ptr_" + ps + "\n";
1044 o += "%wptr_copy_" + ps + " = OpAccessChain %psh_f32 " + write_buf + " %lid\n";
1045 o += "OpStore %wptr_copy_" + ps + " %pass_val_" + ps + "\n";
1046 o += "OpBranch %scan_merge_" + ps + "\n\n";
1047
1048 o += "%scan_merge_" + ps + " = OpLabel\n";
1049 o += "OpControlBarrier %c2u %c2u %c264u\n\n";
1050
1051 std::swap(read_buf, write_buf);
1052
1053 if (pass + 1 < log2_local) {
1054 const std::string next_stride = "%stride_next_" + ps;
1055 o += next_stride + " = OpShiftLeftLogical %u32 " + stride_val + " %c1u\n";
1056 stride_val = next_stride;
1057 }
1058 }
1059
1060 o += "%final_ptr = OpAccessChain %psh_f32 " + read_buf + " %lid\n";
1061 o += "%final_val = OpLoad %f32 %final_ptr\n";
1062 o += "%gep_out = OpAccessChain %pelem_" + b0.name
1063 + " %buf_" + b0.name + " %c0u %i\n";
1064 o += "OpStore %gep_out %final_val\n";
1065
1066 o += "OpReturn\n";
1067 o += "OpFunctionEnd\n";
1068 return o;
1069 }
1070
1071 std::string emit_max_index_body(const ShaderSpec& spec)
1072 {
1073 const uint32_t local = spec.workgroup_size[0];
1074 const std::string ls = std::to_string(local);
1075 const auto& b0 = spec.bindings[0];
1076 const auto& b1 = spec.bindings[1];
1077
1078 std::string o;
1079 o += "%main = OpFunction %void None %voidfn\n";
1080 o += "%entry = OpLabel\n";
1081
1082 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
1083 o += "%i = OpCompositeExtract %u32 %gid3 0\n";
1084 o += "%lid3 = OpLoad %v3u32 %lid_var\n";
1085 o += "%lid = OpCompositeExtract %u32 %lid3 0\n\n";
1086
1087 o += "%gep_in = OpAccessChain %pelem_" + b0.name
1088 + " %buf_" + b0.name + " %c0u %i\n";
1089 o += "%elem = OpLoad %f32 %gep_in\n";
1090 o += "%pgsh = OpAccessChain %psh_f32 %shared_a %lid\n";
1091 o += "OpStore %pgsh %elem\n";
1092 o += "%pgidx = OpAccessChain %psh_u32 %shared_idx %lid\n";
1093 o += "OpStore %pgidx %lid\n";
1094 o += "OpControlBarrier %c2u %c2u %c264u\n\n";
1095
1096 o += "%s_init = OpShiftRightLogical %u32 %c" + ls + "u %c1u\n";
1097 o += "OpBranch %loop_hdr\n\n";
1098
1099 o += "%loop_hdr = OpLabel\n";
1100 o += "%stride = OpPhi %u32 %s_init %entry %s_next %loop_cont\n";
1101 o += "OpLoopMerge %loop_merge %loop_cont None\n";
1102 o += "OpBranch %loop_body\n\n";
1103
1104 o += "%loop_body = OpLabel\n";
1105 o += "%active = OpULessThan %bool %lid %stride\n";
1106 o += "OpSelectionMerge %sel_merge None\n";
1107 o += "OpBranchConditional %active %do_op %sel_merge\n\n";
1108
1109 o += "%do_op = OpLabel\n";
1110 o += "%pgsh_a = OpAccessChain %psh_f32 %shared_a %lid\n";
1111 o += "%a = OpLoad %f32 %pgsh_a\n";
1112 o += "%lid_b = OpIAdd %u32 %lid %stride\n";
1113 o += "%pgsh_b = OpAccessChain %psh_f32 %shared_a %lid_b\n";
1114 o += "%b = OpLoad %f32 %pgsh_b\n";
1115 o += "%pgidx_a = OpAccessChain %psh_u32 %shared_idx %lid\n";
1116 o += "%idx_a = OpLoad %u32 %pgidx_a\n";
1117 o += "%pgidx_b = OpAccessChain %psh_u32 %shared_idx %lid_b\n";
1118 o += "%idx_b = OpLoad %u32 %pgidx_b\n";
1119 o += "%b_wins = OpFOrdGreaterThan %bool %b %a\n";
1120 o += "%combined = OpSelect %f32 %b_wins %b %a\n";
1121 o += "%combined_idx = OpSelect %u32 %b_wins %idx_b %idx_a\n";
1122 o += "OpStore %pgsh_a %combined\n";
1123 o += "OpStore %pgidx_a %combined_idx\n";
1124 o += "OpBranch %sel_merge\n\n";
1125
1126 o += "%sel_merge = OpLabel\n";
1127 o += "OpBranch %loop_cont\n\n";
1128
1129 o += "%loop_cont = OpLabel\n";
1130 o += "OpControlBarrier %c2u %c2u %c264u\n";
1131 o += "%s_next = OpShiftRightLogical %u32 %stride %c1u\n";
1132 o += "%done = OpIEqual %bool %s_next %c0u\n";
1133 o += "OpBranchConditional %done %loop_merge %loop_hdr\n\n";
1134
1135 o += "%loop_merge = OpLabel\n";
1136 o += "%is_zero = OpIEqual %bool %lid %c0u\n";
1137 o += "OpSelectionMerge %write_merge None\n";
1138 o += "OpBranchConditional %is_zero %do_write %write_merge\n\n";
1139
1140 o += "%do_write = OpLabel\n";
1141 o += "%result_v = OpLoad %f32 %pgsh\n";
1142 o += "%result_i = OpLoad %u32 %pgidx\n";
1143 o += "%gep_out_v = OpAccessChain %pelem_" + b0.name
1144 + " %buf_" + b0.name + " %c0u %c0u\n";
1145 o += "OpStore %gep_out_v %result_v\n";
1146 o += "%gep_out_i = OpAccessChain %pelem_" + b1.name
1147 + " %buf_" + b1.name + " %c0u %c0u\n";
1148 o += "OpStore %gep_out_i %result_i\n";
1149 o += "OpBranch %write_merge\n\n";
1150
1151 o += "%write_merge = OpLabel\n";
1152 o += "OpReturn\n";
1153 o += "OpFunctionEnd\n";
1154 return o;
1155 }
1156
1157 /**
1158 * Emit the entry point body for specs whose bindings include IMAGE_2D slots.
1159 *
1160 * Assumes workgroup_size is {8, 8, 1} or any 2D shape. GlobalInvocationId
1161 * x/y components are used as the texel coordinate. One output IMAGE_2D
1162 * binding is required. PC fields provide float operands identical to the
1163 * SSBO elementwise path. The op is applied per-channel on the rgba vec4
1164 * loaded from the first input IMAGE_2D, or on a zero vec4 if no input image
1165 * is declared.
1166 */
1167 std::string emit_image_body(const ShaderSpec& spec)
1168 {
1169 const BindingSlot* img_out = nullptr;
1170 std::vector<const BindingSlot*> img_inputs;
1171 for (const auto& b : spec.bindings) {
1172 if (b.modality != Kakshya::DataModality::IMAGE_2D)
1173 continue;
1174 if (b.direction == BindingDirection::Output && !img_out) {
1175 img_out = &b;
1176 } else if (b.direction != BindingDirection::Output) {
1177 img_inputs.push_back(&b);
1178 }
1179 }
1180
1181 std::string o;
1182 o += "%main = OpFunction %void None %voidfn\n";
1183 o += "%entry = OpLabel\n";
1184 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
1185 o += "%ix = OpCompositeExtract %u32 %gid3 0\n";
1186 o += "%iy = OpCompositeExtract %u32 %gid3 1\n";
1187 o += "%six = OpBitcast %i32 %ix\n";
1188 o += "%siy = OpBitcast %i32 %iy\n";
1189 o += "%coord = OpCompositeConstruct %v2i32 %six %siy\n\n";
1190
1191 if (img_out)
1192 o += "%img_out_val = OpLoad %img2d_t %img_" + std::string(img_out->name) + "\n";
1193 for (size_t ii = 0; ii < img_inputs.size(); ++ii)
1194 o += "%img_in_val" + std::to_string(ii) + " = OpLoad %img2d_t %img_" + img_inputs[ii]->name + "\n";
1195 o += "\n";
1196
1197 for (size_t fi = 0; fi < spec.pc_fields.size(); ++fi) {
1198 const auto& f = spec.pc_fields[fi];
1199 o += "%ppc_" + f.name + " = OpAccessChain %ppc_f32 %pc %c"
1200 + std::to_string(fi) + "u\n";
1201 o += "%pc_" + f.name + " = OpLoad %f32 %ppc_" + f.name + "\n";
1202 }
1203 if (!spec.pc_fields.empty())
1204 o += "\n";
1205
1206 std::vector<const BindingSlot*> tex_inputs;
1207 for (const auto& b : spec.bindings) {
1208 if (b.modality == Kakshya::DataModality::TEXTURE_2D)
1209 tex_inputs.push_back(&b);
1210 }
1211
1212 if (!tex_inputs.empty()) {
1213 const std::string pw = spec.pc_fields.size() > 0
1214 ? ("%pc_" + spec.pc_fields[0].name)
1215 : "%lod_zero";
1216 const std::string ph = spec.pc_fields.size() > 1
1217 ? ("%pc_" + spec.pc_fields[1].name)
1218 : "%lod_zero";
1219 o += "%fix = OpConvertUToF %f32 %ix\n";
1220 o += "%fiy = OpConvertUToF %f32 %iy\n";
1221 o += "%u = OpFDiv %f32 %fix " + pw + "\n";
1222 o += "%v = OpFDiv %f32 %fiy " + ph + "\n";
1223 o += "%uv = OpCompositeConstruct %v2f32 %u %v\n\n";
1224
1225 for (size_t ti = 0; ti < tex_inputs.size(); ++ti) {
1226 const std::string idx = std::to_string(img_inputs.size() + ti);
1227 o += "%simg_" + tex_inputs[ti]->name
1228 + " = OpLoad %simgc_t %tex_" + tex_inputs[ti]->name + "\n";
1229 o += "%raw_in" + idx + " = OpImageSampleExplicitLod %v4f32 %simg_"
1230 + tex_inputs[ti]->name + " %uv Lod %lod_zero\n";
1231 }
1232 o += "\n";
1233 }
1234
1235 for (size_t ii = 0; ii < img_inputs.size(); ++ii) {
1236 const std::string idx = std::to_string(ii);
1237 o += "%raw_in" + idx + " = OpImageRead %v4f32 %img_in_val" + idx + " %coord\n";
1238 }
1239 if (img_inputs.empty()) {
1240 o += "%raw_in0 = OpCompositeConstruct %v4f32 %img_czero %img_czero"
1241 " %img_czero %img_czero\n";
1242 }
1243 o += "\n";
1244
1245 o += "%ch0_r = OpCompositeExtract %f32 %raw_in0 0\n";
1246 o += "%ch0_g = OpCompositeExtract %f32 %raw_in0 1\n";
1247 o += "%ch0_b = OpCompositeExtract %f32 %raw_in0 2\n";
1248 o += "%ch0_a = OpCompositeExtract %f32 %raw_in0 3\n";
1249
1250 const bool has_second = img_inputs.size() > 1
1251 || (!img_inputs.empty() && !tex_inputs.empty())
1252 || tex_inputs.size() > 1;
1253
1254 const std::string second_idx = img_inputs.size() > 1
1255 ? "1"
1256 : (!tex_inputs.empty() ? std::to_string(img_inputs.size()) : "");
1257
1258 if (has_second && !second_idx.empty()) {
1259 o += "%ch1_r = OpCompositeExtract %f32 %raw_in" + second_idx + " 0\n";
1260 o += "%ch1_g = OpCompositeExtract %f32 %raw_in" + second_idx + " 1\n";
1261 o += "%ch1_b = OpCompositeExtract %f32 %raw_in" + second_idx + " 2\n";
1262 o += "%ch1_a = OpCompositeExtract %f32 %raw_in" + second_idx + " 3\n";
1263 }
1264 o += "\n";
1265
1266 const std::string p0 = spec.pc_fields.empty()
1267 ? ""
1268 : ("%pc_" + spec.pc_fields[0].name);
1269 const std::string p1 = spec.pc_fields.size() > 1
1270 ? ("%pc_" + spec.pc_fields[1].name)
1271 : "";
1272
1273 if (spec.op == KernelOp::ChannelDot) {
1274 const std::string& wr = "%pc_" + spec.pc_fields[0].name;
1275 const std::string& wg = spec.pc_fields.size() > 1 ? "%pc_" + spec.pc_fields[1].name : "%img_czero";
1276 const std::string& wb = spec.pc_fields.size() > 2 ? "%pc_" + spec.pc_fields[2].name : "%img_czero";
1277 const std::string& wa = spec.pc_fields.size() > 3 ? "%pc_" + spec.pc_fields[3].name : "%img_czero";
1278 o += "%dot_r = OpFMul %f32 %ch0_r " + wr + "\n";
1279 o += "%dot_g = OpFMul %f32 %ch0_g " + wg + "\n";
1280 o += "%dot_b = OpFMul %f32 %ch0_b " + wb + "\n";
1281 o += "%dot_a = OpFMul %f32 %ch0_a " + wa + "\n";
1282 o += "%dot_rg = OpFAdd %f32 %dot_r %dot_g\n";
1283 o += "%dot_rgb = OpFAdd %f32 %dot_rg %dot_b\n";
1284 o += "%dot_val = OpFAdd %f32 %dot_rgb %dot_a\n";
1285 o += "%out_vec = OpCompositeConstruct %v4f32 %dot_val %dot_val %dot_val %dot_val\n";
1286 if (img_out)
1287 o += "OpImageWrite %img_out_val %coord %out_vec\n";
1288 o += "OpReturn\n";
1289 o += "OpFunctionEnd\n";
1290 return o;
1291 }
1292
1293 if (spec.op == KernelOp::ChannelReplicate) {
1294 o += "%out_vec = OpCompositeConstruct %v4f32 %ch0_r %ch0_r %ch0_r %img_cone\n";
1295 if (img_out)
1296 o += "OpImageWrite %img_out_val %coord %out_vec\n";
1297 o += "OpReturn\n";
1298 o += "OpFunctionEnd\n";
1299 return o;
1300 }
1301
1302 auto emit_channel_op = [&](
1303 const std::string& c0, const std::string& c1,
1304 const std::string& suffix) {
1305 switch (spec.op) {
1306 case KernelOp::Scale:
1307 o += "%res_" + suffix + " = OpFMul %f32 " + c0 + " " + p0 + "\n";
1308 break;
1310 o += "%mul_" + suffix + " = OpFMul %f32 " + c0 + " " + p0 + "\n";
1311 o += "%res_" + suffix + " = OpFAdd %f32 %mul_" + suffix + " " + p1 + "\n";
1312 break;
1313 case KernelOp::Offset:
1314 o += "%res_" + suffix + " = OpFAdd %f32 " + c0 + " " + p0 + "\n";
1315 break;
1316 case KernelOp::Clip:
1317 o += "%res_" + suffix + " = OpExtInst %f32 %glsl FClamp "
1318 + c0 + " " + p0 + " " + p1 + "\n";
1319 break;
1320 case KernelOp::Abs:
1321 o += "%res_" + suffix + " = OpExtInst %f32 %glsl FAbs " + c0 + "\n";
1322 break;
1323 case KernelOp::Negate:
1324 o += "%res_" + suffix + " = OpFNegate %f32 " + c0 + "\n";
1325 break;
1326 case KernelOp::Add:
1327 o += "%res_" + suffix + " = OpFAdd %f32 " + c0 + " " + c1 + "\n";
1328 break;
1329 case KernelOp::Multiply:
1330 o += "%res_" + suffix + " = OpFMul %f32 " + c0 + " " + c1 + "\n";
1331 break;
1332 case KernelOp::Mix:
1333 o += "%res_" + suffix + " = OpExtInst %f32 %glsl FMix "
1334 + c0 + " " + c1 + " " + p0 + "\n";
1335 break;
1336 case KernelOp::Sub:
1337 o += "%res_" + suffix + " = OpFSub %f32 " + c0 + " " + c1 + "\n";
1338 break;
1340 o += "%cmp_" + suffix + " = OpFOrdGreaterThanEqual %bool " + c0 + " " + p0 + "\n";
1341 o += "%res_" + suffix + " = OpSelect %f32 %cmp_" + suffix + " %img_cone %img_czero\n";
1342 break;
1344 o += "%cmp_" + suffix + " = OpFOrdGreaterThanEqual %bool " + c0 + " " + p0 + "\n";
1345 o += "%res_" + suffix + " = OpSelect %f32 %cmp_" + suffix + " " + p1 + " " + c0 + "\n";
1346 break;
1347 default:
1348 o += "%res_" + suffix + " = OpCopyObject %f32 " + c0 + "\n";
1349 break;
1350 }
1351 };
1352
1353 const std::string zero = "%img_czero";
1354 const std::string c1r = has_second ? "%ch1_r" : zero;
1355 const std::string c1g = has_second ? "%ch1_g" : zero;
1356 const std::string c1b = has_second ? "%ch1_b" : zero;
1357 const std::string c1a = has_second ? "%ch1_a" : zero;
1358
1359 emit_channel_op("%ch0_r", c1r, "r");
1360 emit_channel_op("%ch0_g", c1g, "g");
1361 emit_channel_op("%ch0_b", c1b, "b");
1362 emit_channel_op("%ch0_a", c1a, "a");
1363 o += "\n";
1364
1365 o += "%out_vec = OpCompositeConstruct %v4f32 %res_r %res_g %res_b %res_a\n";
1366 if (img_out)
1367 o += "OpImageWrite %img_out_val %coord %out_vec\n";
1368
1369 o += "OpReturn\n";
1370 o += "OpFunctionEnd\n";
1371 return o;
1372 }
1373
1374 /**
1375 * Emit the entry point body for BitonicSort template.
1376 * Loads the index, loads PC fields, loads SSBO elements, applies op, stores.
1377 */
1378 std::string emit_bitonic_body(const ShaderSpec& spec)
1379 {
1380 const auto& bkeys = spec.bindings[0];
1381 const auto& bidx = spec.bindings[1];
1382
1383 const std::string ktype(ssbo_elem_spirv_type(bkeys.format));
1384 const std::string itype(ssbo_elem_spirv_type(bidx.format));
1385
1386 std::string o;
1387 o += "%main = OpFunction %void None %voidfn\n";
1388 o += "%entry = OpLabel\n";
1389 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
1390 o += "%i = OpCompositeExtract %u32 %gid3 0\n\n";
1391
1392 o += "%ppc_stage = OpAccessChain %ppc_u32 %pc %c0u\n";
1393 o += "%stage = OpLoad %u32 %ppc_stage\n";
1394 o += "%ppc_pass = OpAccessChain %ppc_u32 %pc %c1u\n";
1395 o += "%pass = OpLoad %u32 %ppc_pass\n";
1396 o += "%ppc_count = OpAccessChain %ppc_u32 %pc %c2u\n";
1397 o += "%count = OpLoad %u32 %ppc_count\n";
1398 o += "%ppc_desc = OpAccessChain %ppc_u32 %pc %c3u\n";
1399 o += "%descending = OpLoad %u32 %ppc_desc\n\n";
1400
1401 o += "%c1u_shift = OpShiftLeftLogical %u32 %c1u %pass\n";
1402 o += "%partner = OpBitwiseXor %u32 %i %c1u_shift\n\n";
1403
1404 o += "%partner_le_i = OpULessThanEqual %bool %partner %i\n";
1405 o += "%i_oob = OpUGreaterThanEqual %bool %i %count\n";
1406 o += "%p_oob = OpUGreaterThanEqual %bool %partner %count\n";
1407 o += "%oob_raw = OpLogicalOr %bool %i_oob %p_oob\n";
1408 o += "%skip = OpLogicalOr %bool %partner_le_i %oob_raw\n\n";
1409
1410 o += "OpSelectionMerge %early_merge None\n";
1411 o += "OpBranchConditional %skip %early_ret %do_sort\n\n";
1412
1413 o += "%do_sort = OpLabel\n";
1414
1415 o += "%gep_ki = OpAccessChain %pelem_" + bkeys.name
1416 + " %buf_" + bkeys.name + " %c0u %i\n";
1417 o += "%key_i = OpLoad " + ktype + " %gep_ki\n";
1418 o += "%gep_kp = OpAccessChain %pelem_" + bkeys.name
1419 + " %buf_" + bkeys.name + " %c0u %partner\n";
1420 o += "%key_p = OpLoad " + ktype + " %gep_kp\n\n";
1421
1422 o += "%gep_ii = OpAccessChain %pelem_" + bidx.name
1423 + " %buf_" + bidx.name + " %c0u %i\n";
1424 o += "%idx_i = OpLoad " + itype + " %gep_ii\n";
1425 o += "%gep_ip = OpAccessChain %pelem_" + bidx.name
1426 + " %buf_" + bidx.name + " %c0u %partner\n";
1427 o += "%idx_p = OpLoad " + itype + " %gep_ip\n\n";
1428
1429 o += "%dir_shift = OpShiftRightLogical %u32 %i %stage\n";
1430 o += "%dir_bit = OpBitwiseAnd %u32 %dir_shift %c1u\n\n";
1431
1432 o += "%gt = OpFOrdGreaterThan %bool %key_i %key_p\n";
1433 o += "%gt_u = OpSelect %u32 %gt %c1u %c0u\n";
1434 o += "%xor1 = OpBitwiseXor %u32 %gt_u %dir_bit\n";
1435 o += "%xor2 = OpBitwiseXor %u32 %xor1 %descending\n";
1436 o += "%do_swap = OpINotEqual %bool %xor2 %c0u\n\n";
1437
1438 o += "%new_ki = OpSelect " + ktype + " %do_swap %key_p %key_i\n";
1439 o += "%new_kp = OpSelect " + ktype + " %do_swap %key_i %key_p\n";
1440 o += "%new_ii = OpSelect " + itype + " %do_swap %idx_p %idx_i\n";
1441 o += "%new_ip = OpSelect " + itype + " %do_swap %idx_i %idx_p\n\n";
1442
1443 o += "OpStore %gep_ki %new_ki\n";
1444 o += "OpStore %gep_kp %new_kp\n";
1445 o += "OpStore %gep_ii %new_ii\n";
1446 o += "OpStore %gep_ip %new_ip\n";
1447 o += "OpBranch %early_merge\n\n";
1448
1449 o += "%early_ret = OpLabel\n";
1450 o += "OpBranch %early_merge\n\n";
1451
1452 o += "%early_merge = OpLabel\n";
1453 o += "OpReturn\n";
1454 o += "OpFunctionEnd\n";
1455 return o;
1456 }
1457
1458 /**
1459 * Emit the entry point body for 2D convolution template.
1460 * Loads the thread coordinates, loads PC fields, loads kernel weights from SSBO,
1461 * applies convolution to the input image, stores to output image.
1462 */
1463 std::string emit_convolve2d_body(const ShaderSpec& spec)
1464 {
1465 const BindingSlot* img_out = nullptr;
1466 const BindingSlot* img_src = nullptr;
1467 const BindingSlot* kern_ssbo = nullptr;
1468 for (const auto& b : spec.bindings) {
1469 if (b.modality == Kakshya::DataModality::IMAGE_2D) {
1470 if (b.direction == BindingDirection::Output) {
1471 img_out = &b;
1472 } else {
1473 img_src = &b;
1474 }
1475 } else {
1476 kern_ssbo = &b;
1477 }
1478 }
1479
1480 std::string o;
1481 o += "%main = OpFunction %void None %voidfn\n";
1482 o += "%entry = OpLabel\n";
1483
1484 o += "%gid3 = OpLoad %v3u32 %gid_var\n";
1485 o += "%ix = OpCompositeExtract %u32 %gid3 0\n";
1486 o += "%iy = OpCompositeExtract %u32 %gid3 1\n";
1487
1488 o += "%ppc_radius = OpAccessChain %ppc_u32 %pc %c0u\n";
1489 o += "%pc_radius = OpLoad %u32 %ppc_radius\n";
1490 o += "%ppc_width = OpAccessChain %ppc_u32 %pc %c1u\n";
1491 o += "%pc_width = OpLoad %u32 %ppc_width\n";
1492 o += "%ppc_height = OpAccessChain %ppc_u32 %pc %c2u\n";
1493 o += "%pc_height = OpLoad %u32 %ppc_height\n";
1494
1495 o += "%oob_x = OpUGreaterThanEqual %bool %ix %pc_width\n";
1496 o += "%oob_y = OpUGreaterThanEqual %bool %iy %pc_height\n";
1497 o += "%oob = OpLogicalOr %bool %oob_x %oob_y\n";
1498 o += "OpSelectionMerge %main_merge None\n";
1499 o += "OpBranchConditional %oob %main_merge %conv_start\n\n";
1500
1501 o += "%conv_start = OpLabel\n";
1502
1503 o += "%img_src_val = OpLoad %img2d_t %img_" + std::string(img_src->name) + "\n";
1504 o += "%img_out_val = OpLoad %img2d_t %img_" + std::string(img_out->name) + "\n";
1505
1506 o += "%diam = OpIMul %u32 %pc_radius %c2u\n";
1507 o += "%diam1 = OpIAdd %u32 %diam %c1u\n";
1508
1509 o += "%six = OpBitcast %i32 %ix\n";
1510 o += "%siy = OpBitcast %i32 %iy\n";
1511 o += "%srad = OpBitcast %i32 %pc_radius\n";
1512 o += "%sw = OpBitcast %i32 %pc_width\n";
1513 o += "%sh = OpBitcast %i32 %pc_height\n";
1514 o += "%sw_1 = OpISub %i32 %sw %ci1\n";
1515 o += "%sh_1 = OpISub %i32 %sh %ci1\n";
1516
1517 o += "%czero4 = OpCompositeConstruct %v4f32 %img_czero %img_czero %img_czero %img_czero\n";
1518
1519 o += "OpBranch %ky_hdr\n\n";
1520
1521 o += "%ky_hdr = OpLabel\n";
1522 o += "%ky_u = OpPhi %u32 %c0u %conv_start %ky_next %ky_cont\n";
1523 o += "%acc_ky = OpPhi %v4f32 %czero4 %conv_start %acc_kx_done %ky_cont\n";
1524 o += "%ky_done = OpUGreaterThanEqual %bool %ky_u %diam1\n";
1525 o += "OpLoopMerge %ky_merge %ky_cont None\n";
1526 o += "OpBranchConditional %ky_done %ky_merge %kx_pre\n\n";
1527
1528 o += "%kx_pre = OpLabel\n";
1529 o += "%ky_si = OpBitcast %i32 %ky_u\n";
1530 o += "%ky_off = OpISub %i32 %ky_si %srad\n";
1531 o += "%sy_raw = OpIAdd %i32 %siy %ky_off\n";
1532 o += "%sy_lo = OpExtInst %i32 %glsl SMax %sy_raw %ci0\n";
1533 o += "%sy = OpExtInst %i32 %glsl SMin %sy_lo %sh_1\n";
1534 o += "OpBranch %kx_hdr\n\n";
1535
1536 o += "%kx_hdr = OpLabel\n";
1537 o += "%kx_u = OpPhi %u32 %c0u %kx_pre %kx_next %kx_cont\n";
1538 o += "%acc_kx = OpPhi %v4f32 %acc_ky %kx_pre %acc_new %kx_cont\n";
1539 o += "%kx_done = OpUGreaterThanEqual %bool %kx_u %diam1\n";
1540 o += "OpLoopMerge %kx_merge %kx_cont None\n";
1541 o += "OpBranchConditional %kx_done %kx_merge %kx_body\n\n";
1542
1543 o += "%kx_body = OpLabel\n";
1544 o += "%kx_si = OpBitcast %i32 %kx_u\n";
1545 o += "%kx_off = OpISub %i32 %kx_si %srad\n";
1546 o += "%sx_raw = OpIAdd %i32 %six %kx_off\n";
1547 o += "%sx_lo = OpExtInst %i32 %glsl SMax %sx_raw %ci0\n";
1548 o += "%sx = OpExtInst %i32 %glsl SMin %sx_lo %sw_1\n";
1549
1550 o += "%sc = OpCompositeConstruct %v2i32 %sx %sy\n";
1551 o += "%px = OpImageRead %v4f32 %img_src_val %sc\n";
1552
1553 o += "%kidx_r = OpIMul %u32 %ky_u %diam1\n";
1554 o += "%kidx = OpIAdd %u32 %kidx_r %kx_u\n";
1555 o += "%k_gep = OpAccessChain %pelem_" + std::string(kern_ssbo->name)
1556 + " %buf_" + std::string(kern_ssbo->name) + " %c0u %kidx\n";
1557 o += "%kw = OpLoad %f32 %k_gep\n";
1558
1559 o += "%kw4 = OpCompositeConstruct %v4f32 %kw %kw %kw %kw\n";
1560 o += "%prod = OpFMul %v4f32 %px %kw4\n";
1561 o += "%acc_new = OpFAdd %v4f32 %acc_kx %prod\n";
1562 o += "OpBranch %kx_cont\n\n";
1563
1564 o += "%kx_cont = OpLabel\n";
1565 o += "%kx_next = OpIAdd %u32 %kx_u %c1u\n";
1566 o += "OpBranch %kx_hdr\n\n";
1567
1568 o += "%kx_merge = OpLabel\n";
1569 o += "%acc_kx_done = OpPhi %v4f32 %acc_kx %kx_hdr\n";
1570 o += "OpBranch %ky_cont\n\n";
1571
1572 o += "%ky_cont = OpLabel\n";
1573 o += "%ky_next = OpIAdd %u32 %ky_u %c1u\n";
1574 o += "OpBranch %ky_hdr\n\n";
1575
1576 o += "%ky_merge = OpLabel\n";
1577 o += "%final_acc = OpPhi %v4f32 %acc_ky %ky_hdr\n";
1578
1579 o += "%out_coord = OpCompositeConstruct %v2i32 %six %siy\n";
1580 o += "OpImageWrite %img_out_val %out_coord %final_acc\n";
1581 o += "OpBranch %main_merge\n\n";
1582
1583 o += "%main_merge = OpLabel\n";
1584 o += "OpReturn\n";
1585 o += "OpFunctionEnd\n";
1586 return o;
1587 }
1588
1589} // namespace
1590
1591std::string emit_spirv_asm(const ShaderSpec& spec)
1592{
1593 std::string src;
1594 src += emit_header(spec);
1595 src += emit_decorations(spec);
1596 src += emit_types(spec);
1597
1598 if (spec.tmpl == KernelTemplate::Convolve2D) {
1599 src += emit_convolve2d_body(spec);
1600 return src;
1601 }
1602
1603 bool has_image = false;
1604 for (const auto& b : spec.bindings) {
1605 if (b.modality == Kakshya::DataModality::IMAGE_2D) {
1606 has_image = true;
1607 break;
1608 }
1609 }
1610
1611 if (has_image) {
1612 src += emit_image_body(spec);
1613 return src;
1614 }
1615
1616 switch (spec.tmpl) {
1618 src += (spec.op == KernelOp::MaxIndex) ? emit_max_index_body(spec) : emit_reduction_body(spec);
1619 break;
1621 src += emit_scan_body(spec);
1622 break;
1624 src += emit_bitonic_body(spec);
1625 break;
1627 src += emit_stencil_body(spec);
1628 break;
1631 default:
1632 src += emit_elementwise_body(spec);
1633 break;
1634 }
1635 return src;
1636}
1637
1638std::string emit_glsl_kernel(const ShaderSpec& spec)
1639{
1640 const auto& ws = spec.workgroup_size;
1641 const auto& ks = *spec.kernel;
1642
1643 bool has_image = false;
1644 for (const auto& b : spec.bindings) {
1645 if (b.modality == Kakshya::DataModality::IMAGE_2D)
1646 has_image = true;
1647 }
1648
1649 std::string o;
1650 o += "#version 460\n";
1651 o += "layout(local_size_x = " + std::to_string(ws[0])
1652 + ", local_size_y = " + std::to_string(ws[1])
1653 + ", local_size_z = " + std::to_string(ws[2]) + ") in;\n\n";
1654
1655 for (const auto& b : spec.bindings) {
1656 if (b.modality == Kakshya::DataModality::IMAGE_2D) {
1657 const std::string qual = (b.direction == BindingDirection::Input)
1658 ? "readonly"
1659 : "writeonly";
1660 o += "layout(set = 0, binding = " + std::to_string(b.binding_index)
1661 + ", rgba32f) " + qual + " uniform image2D " + b.name + ";\n";
1662 continue;
1663 }
1664 if (b.modality == Kakshya::DataModality::TEXTURE_2D) {
1665 o += "layout(set = 0, binding = " + std::to_string(b.binding_index)
1666 + ") uniform sampler2D " + b.name + ";\n";
1667 continue;
1668 }
1669 const auto t = std::string(glsl_type(b.format));
1670 o += "layout(set = 0, binding = " + std::to_string(b.binding_index)
1671 + ", std430) buffer Block_" + b.name
1672 + " { " + t + " " + b.name + "[]; };\n";
1673 }
1674
1675 if (!spec.pc_fields.empty()) {
1676 o += "\nlayout(push_constant) uniform PC {\n";
1677 for (const auto& f : spec.pc_fields)
1678 o += " " + std::string(glsl_type(f.format)) + " " + f.name + ";\n";
1679 o += "} pc;\n";
1680 }
1681
1682 for (const auto& f : spec.functions) {
1683 o += "\n" + f.return_type + " " + f.name + "(" + f.params + ") {\n";
1684 o += f.body;
1685 o += "\n}\n";
1686 }
1687
1688 o += "\nvoid main() {\n";
1689 o += " uint i = gl_GlobalInvocationID.x;\n";
1690 if (has_image)
1691 o += " ivec2 coord = ivec2(gl_GlobalInvocationID.xy);\n";
1692
1693 for (const auto& f : spec.pc_fields) {
1694 const auto t = std::string(glsl_type(f.format));
1695 o += " " + t + " " + f.name + " = pc." + f.name + ";\n";
1696 }
1697
1698 o += ks.body;
1699 o += "\n}\n";
1700 return o;
1701}
1702
1703} // namespace MayaFlux::Portal::Graphics::detail
size_t b
float wb
uint32_t pass
float wg
float wa
float wr
size_t gpu_data_format_bytes(GpuDataFormat fmt) noexcept
Byte size of one element of a GpuDataFormat.
Definition NDData.cpp:9
@ IMAGE_2D
2D image (grayscale or single channel)
GpuDataFormat
GPU data formats with explicit precision levels.
Definition NDData.hpp:25
std::string emit_glsl_kernel(const ShaderSpec &spec)
Emit a complete GLSL compute shader from spec metadata and a KernelSource body.
std::string emit_spirv_asm(const ShaderSpec &spec)
Emit complete SPIR-V assembly text for a generated compute kernel.
@ Scan
Inclusive prefix scan over one InOut SSBO, double-buffered Hillis-Steele in shared memory.
@ Convolve2D
2D separable or non-separable convolution; kernel weights in SSBO, radius in PC
@ Reduction
f(x[0..n]) -> scalar; shared-memory tree reduction
@ Elementwise
f(x[i]) -> y[i]; one thread per element
@ Stencil
f(x[i-k..i+k]) -> y[i]; neighbourhood reads, radius in PC
@ GeometryEmit
Writes into vertex SSBO with atomic counter.
@ BitonicSort
Bitonic sort network; one thread per element.
@ CompareGE
out[ch] = pixel[ch] >= pc[0] ? 1.0 : 0.0
@ ChannelDot
out = dot(pixel.rgba, pc[0..3]) broadcast to all channels
@ WeightedBlend
Stencil-only: blends a cell toward a scaled neighbor sum by a rate factor: out = center + pc[N] * (su...
@ IndexScale
out[i] = float(i) * a[i].
@ CompareGEPreserve
out[ch] = pixel[ch] >= pc[0] ? pc[1] : pixel[ch]
@ MaxIndex
Reduction variant: finds max value AND its index.
@ ChannelReplicate
out = pixel[pc_channel_index].xxxx (single channel to all)
Declaration of one SSBO or image binding in a generated shader.
std::vector< PushConstantField > pc_fields
std::vector< BindingSlot > bindings
std::array< uint32_t, 3 > workgroup_size
std::optional< KernelSource > kernel
When set, KernelOp is ignored.
std::vector< FunctionDef > functions
Emitted before main(). GLSL path only.
Complete declarative description of a generated compute shader.