Loading...
Searching...
No Matches
isf.hpp
1#pragma once
2#include <ossia/detail/variant.hpp>
3
4#include <score_plugin_gfx_export.h>
5
6#include <array>
7#include <optional>
8#include <stdexcept>
9#include <string>
10#include <vector>
11
12namespace isf
13{
14class invalid_file : public std::runtime_error
15{
16public:
17 using std::runtime_error::runtime_error;
18};
19
20struct event_input
21{
22};
23
24struct bool_input
25{
26 using value_type = bool;
27 using has_default = std::true_type;
28 bool def{};
29};
30
31struct long_input
32{
33 using value_type = int64_t;
34 using has_minmax = std::true_type;
35 std::vector<ossia::variant<int64_t, double, std::string>> values;
36 std::vector<std::string> labels;
37
38 // Enum mode (values/labels non-empty): `def` is the INDEX into `values`.
39 // Numeric mode (values empty, min/max set): `def` is the default VALUE.
40 //
41 // The shader always receives the selected numeric VALUE from `values[i]`
42 // (for int/double entries) or the INDEX (for string-only VALUES, since
43 // GLSL can't consume strings). The renderer's UBO-init path resolves this
44 // index→value step so the initial shader state matches what arrives after
45 // any user interaction — see ISFNode.cpp / GeometryFilterNode.cpp long_input
46 // port visitors.
47 std::size_t def{};
48
49 // Numeric mode: when values/labels are empty and min/max are set,
50 // create an IntSpinBox instead of a ComboBox. In that mode `def` is the
51 // default value directly (not an index).
52 std::optional<int64_t> min;
53 std::optional<int64_t> max;
54};
55
56struct float_input
57{
58 using value_type = double;
59 using has_minmax = std::true_type;
60 double min{0.};
61 double max{0.};
62 double def{0.};
63};
64
65struct point2d_input
66{
67 using value_type = std::array<double, 2>;
68 using has_minmax = std::true_type;
69 std::optional<value_type> def{};
70 std::optional<value_type> min{};
71 std::optional<value_type> max{};
72};
73
74struct point3d_input
75{
76 using value_type = std::array<double, 3>;
77 using has_minmax = std::true_type;
78 std::optional<value_type> def{};
79 std::optional<value_type> min{};
80 std::optional<value_type> max{};
81
82 // AS_COLOR: hint to the UI that this vec3 should be shown as a color
83 // swatch (RGB picker) rather than three spin boxes. Useful for e.g.
84 // direction-as-RGB visualisations where editing components individually
85 // is awkward. Does not affect the GLSL type (still vec3).
86 bool as_color{false};
87};
88
89struct color_input
90{
91 using value_type = std::array<double, 4>;
92 using has_minmax = std::true_type;
93 std::optional<value_type> def{};
94 std::optional<value_type> min{};
95 std::optional<value_type> max{};
96};
97
98// Sampler configuration fields shared by image/texture/cubemap inputs.
99// All fields are optional: empty/unset string keeps the current default.
100// Address modes accept: "repeat", "clamp_to_edge"/"clamp", "mirror"/"mirrored_repeat",
101// "mirror_once"/"mirror_clamp_to_edge".
102// Filter modes accept: "nearest", "linear" (and "none" for mipmap_mode).
103// Border color accepts: "transparent_black"/"transparent", "opaque_black", "opaque_white".
104// Compare op accepts: "never", "less", "less_equal"/"lequal", "equal",
105// "greater", "greater_equal"/"gequal", "not_equal"/"neq", "always".
106// When set (and not "never") a comparison sampler is created and
107// the GLSL type becomes sampler*Shadow. Supported on 2D,
108// 2D-array, cubemap (image/texture/cubemap inputs) and
109// cubemap-array (AUXILIARY only). Silently dropped with a
110// stderr warning on 3D inputs (sampler3DShadow is not a core
111// GLSL type) — use a 2D / 2D-array / cube shadow instead.
112// With the engine's reverse-Z convention, the typical
113// compare op for a standard "shadowed if closer" test is
114// "greater_equal" (not "less_equal").
115struct sampler_config
116{
117 std::string wrap; // Applied to all 3 axes if individual WRAP_S/T/R unset
118 std::string wrap_s;
119 std::string wrap_t;
120 std::string wrap_r;
121 std::string filter; // Applied to both min and mag if individual MIN/MAG_FILTER unset
122 std::string min_filter;
123 std::string mag_filter;
124 std::string mipmap_mode;
125 std::optional<float> anisotropy;
126 std::string border_color;
127 std::optional<float> lod_bias;
128 std::optional<float> min_lod;
129 std::optional<float> max_lod;
130 std::string compare; // empty / "never" = no comparison sampler
131
132 friend bool operator==(const sampler_config&, const sampler_config&) = default;
133};
134
135// COMPOSITE header key: how a colour output is composited onto what its
136// target already holds. `unspecified` resolves to `over`.
137enum class composite_mode : uint8_t
138{
139 unspecified,
140 over,
141 add,
142 multiply,
143 screen,
144 replace
145};
146
147struct image_input
148{
149 int dimensions{2}; // 2 or 3
150 bool depth{false}; // true = shader wants sampleable depth on this input
151 bool is_array{false}; // true = sampler2DArray rather than sampler2D
152 // STATIC: producer publishes a long-lived QRhiTexture that downstream binds
153 // directly; engine skips the consumer-side render-target allocation. Use for
154 // precomputed LUTs, IBL bakes, asset caches — anything where the upstream
155 // is a CPU producer (avnd gpu_texture_output, etc.) rather than an ISF /
156 // raster pass that draws into the consumer's RT each frame. Orthogonal to
157 // dimensions / is_array (cube + 3D + array inputs already grab from source
158 // implicitly because they can't be 2D color attachments anyway).
159 bool is_static{false};
160 sampler_config sampler;
161};
162
163struct cubemap_input
164{
165 // DEPTH: true = request a sampleable depth cube alongside the color cube.
166 // Mirrors image_input::depth: pairs the main `samplerCube` (or
167 // `samplerCubeShadow` under COMPARE) with a `samplerCube <name>_depth`
168 // companion for raw depth reads. Useful for omni-directional scene probes
169 // where the upstream provides both a colour cube and its depth cube.
170 // For plain shadow-cube sampling (HW PCF only) set COMPARE instead and
171 // leave DEPTH false — the texture already has to be depth-format for the
172 // compare sampler to return meaningful values.
173 //
174 // Note: cube-arrays (samplerCubeArray) are intentionally NOT exposed. No
175 // QRhi backend (Vulkan/D3D12/Metal/GL) constructs a cube-array view
176 // correctly from the CubeMap | TextureArray flag combination, so the
177 // shader-side type would always disagree with the bound resource. Bind N
178 // individual cubemap inputs instead, or decompose to a sampler2DArray
179 // with face math in the shader.
180 bool depth{false};
181 sampler_config sampler;
182};
183
184// Sampler state accepted by all audio input flavours. Reuses the same
185// string vocabulary as sampler_config (see above) — any unrecognised or
186// empty string keeps the built-in default (linear / clamp_to_edge). Full
187// sampler_config is overkill here: audio textures are 1-mip 2D samplers
188// with no COMPARE / BORDER_COLOR / LOD semantics, so only FILTER and WRAP
189// are honoured. Nearest filtering is the common ask for band-exact FFT
190// reads where linear interpolation would smear adjacent bins.
191struct audio_sampler_config
192{
193 std::string filter; // "nearest" or "linear" (default)
194 std::string wrap; // "repeat", "clamp_to_edge"/"clamp", "mirror"/"mirrored_repeat"
195};
196
197struct audio_input
198{
199 int max{};
200 audio_sampler_config sampler;
201};
202
203struct audioFFT_input
204{
205 int max{};
206 audio_sampler_config sampler;
207};
208
209struct audioHist_input
210{
211 int max{};
212 audio_sampler_config sampler;
213};
214
215// UBO-style input declared in INPUTS as `"TYPE": "uniform"`.
216//
217// Emitted as `layout(std140, binding=N) uniform <name>_t { ... } <name>;`
218// and bound via QRhiShaderResourceBinding::uniformBuffer (not bufferLoad).
219//
220// Use for small (≤ MaxUniformBufferRange, typically 16KB), read-only data
221// like cameras, light/material counts, indexing constants. For larger or
222// writable data, use `storage_input` (SSBO) instead.
223struct uniform_input
224{
225 // Reuse storage_input's layout_field shape via full struct definition here
226 // to keep the type self-contained.
227 struct layout_field
228 {
229 std::string name;
230 std::string type;
231 };
232
233 std::vector<layout_field> layout;
234
235 // VISIBILITY: which shader stage(s) see this binding in a graphics pipeline.
236 // Accepted values: "vertex+fragment"/"both" (default), "fragment", "vertex",
237 // "compute" (implicit for CSF).
238 std::string visibility{"vertex+fragment"};
239};
240
241// CSF-specific input types
242struct storage_input
243{
244 std::string access; // "read_only", "write_only", "read_write"
245
246 struct layout_field
247 {
248 std::string name;
249 std::string type;
250 };
251
252 std::vector<layout_field> layout;
253
254 std::string buffer_usage; // "", "indirect_draw", "indirect_draw_indexed", "dispatch_args"
255
256 // PERSISTENT: creates a ping-pong pair of SSBOs swapped each frame.
257 // In GLSL, `name` is the current (read-write) buffer, `name_prev` is the
258 // previous frame's read-only buffer.
259 bool persistent{false};
260
261 // VISIBILITY: which shader stage(s) see this binding in a graphics pipeline.
262 // Accepted values: "fragment" (default), "vertex", "vertex+fragment"/"both",
263 // "compute" (implicit for CSF), "none" (no shader binding).
264 std::string visibility{"fragment"};
265};
266
267struct texture_input
268{
269 int dimensions{2}; // 2 or 3
270 sampler_config sampler;
271};
272
273struct csf_image_input
274{
275 std::string access; // "read_only", "write_only", "read_write"
276 std::string format; // "RGBA8", "R32F", etc.
277
278 std::string width_expression;
279 std::string height_expression;
280 std::string depth_expression; // non-empty means 3D texture
281
282 int dimensions{2}; // 2 or 3 (alternative to depth_expression for declaring 3D)
283
284 // Set internally when the RESOURCES entry uses TYPE: "image_cube".
285 // Writable cubemap (imageCube in GLSL, QRhiTexture::CubeMap |
286 // UsedWithLoadStore). Width must equal height (face edge length). Use for
287 // in-compute reflection-probe baking, environment IBL, etc. Read-only
288 // sampling of the same data is done via TYPE: "cubemap".
289 bool cubemap{false};
290
291 // IS_ARRAY: writable 2D texture array (image2DArray in GLSL, allocated
292 // via QRhi::newTextureArray + UsedWithLoadStore). Layer count comes from
293 // layers_expression (LAYERS: "$USER" / literal). Useful for shadow
294 // cascades, layered G-buffers, compute-written texture atlases.
295 //
296 // Cube-arrays (imageCubeArray) are intentionally NOT supported: no QRhi
297 // backend plumbs CubeMap | TextureArray views correctly, and the shader-
298 // side type would disagree with the bound resource. The parser rejects
299 // is_array + cubemap combinations with a stderr warning.
300 bool is_array{false};
301 std::string layers_expression; // LAYERS: expression for arraySize, may contain $USER
302
303 // VISIBILITY: which shader stage(s) see this binding.
304 // Accepted: "compute" (default), "fragment", "vertex", "vertex+fragment"/"both".
305 std::string visibility{"compute"};
306
307 // PERSISTENT: creates a ping-pong pair of images swapped each frame.
308 // In GLSL, `<name>` is the current (write or read_write) image and
309 // `<name>_prev` is the previous frame's read-only image — mirrors the
310 // storage_input convention. Works for both 2D and 3D images.
311 bool persistent{false};
312
313 // GENERATE_MIPS: when true, the runtime runs QRhi's generateMips() on
314 // this image after every frame's compute dispatches complete, so
315 // downstream samplers with MIPMAP_MODE: linear / nearest see a valid
316 // mip chain instead of zero-filled upper levels. Ignored for 3D images,
317 // cubemaps, and 2D arrays where generateMips semantics differ across
318 // QRhi backends (per-face / per-layer / per-slice).
319 bool generate_mips{false};
320
321 // COMPOSITE: how the copy of this image into a consumer is composited.
322 composite_mode composite{composite_mode::unspecified};
323
324 bool is3D() const noexcept { return dimensions == 3 || !depth_expression.empty(); }
325 bool isCube() const noexcept { return cubemap; }
326};
327
328// CSF geometry port input: SoA layout, one SSBO per attribute.
329// Declares which geometry attributes the compute shader wants to access.
330struct geometry_input
331{
332 // Explicit cross-geometry forwarding directive.
333 // Used when an output geometry needs data from a different input geometry.
334 struct copy_from
335 {
336 std::string geometry; // Source geometry resource name (e.g. "geoIn")
337 std::string attribute; // Source attribute name (for attribute forwarding)
338 std::string auxiliary; // Source auxiliary name (for auxiliary forwarding; defaults to this name)
339 };
340
341 struct attribute_request
342 {
343 std::string name; // Attribute name used in GLSL (e.g. "position", "velocity")
344 std::string semantic; // Maps to ossia::attribute_semantic name (e.g. "position", "custom")
345 std::string type; // GLSL type (e.g. "vec3", "vec4", "float")
346 std::string access; // "read_only", "write_only", "read_write"
347 std::string rate; // "vertex" (default) or "instance"
348 bool required{true}; // false = optional, zero fallback if missing
349
350 // ACCESS "gather": read_write, and the shader reads indices other than its
351 // own invocation's. Those reads must not observe this dispatch's writes, so
352 // _in and _out become two buffers and the engine keeps them apart -- a
353 // ping-pong swap for a feedback receiver, a per-frame snapshot otherwise.
354 // Plain read_write reads only its own index, where aliasing _in onto _out
355 // is well defined and costs nothing.
356 bool gathers{false};
357
358 // If set, this attribute is forwarded from another geometry's buffer
359 // rather than being allocated/computed by this shader.
360 std::optional<copy_from> forward;
361 };
362
363 // Structured buffers that travel with the geometry (matched by name
364 // against ossia::geometry::auxiliary_buffer entries). Default kind is
365 // SSBO (`layout(std430) buffer`); set `is_uniform = true` to declare a
366 // std140 UBO instead (`layout(std140) uniform`).
367 struct auxiliary_request
368 {
369 std::string name;
370 std::string access; // "read_only", "write_only", "read_write"
371 // (meaningful for SSBO kind only; UBO is always read-only from GLSL)
372 std::vector<storage_input::layout_field> layout;
373 std::string size; // expression for flexible array count, may contain $USER
374 // (SSBO only; UBOs require fixed-size layouts per std140)
375
376 // If set, this auxiliary is forwarded from another geometry's upstream.
377 std::optional<copy_from> forward;
378
379 // Raw-raster only: when true the node owns a ping-pong pair of buffers
380 // (allocated from the LAYOUT + SIZE) that are swapped each frame, and
381 // the auxiliary is NOT resolved from upstream geometry. In GLSL,
382 // `<name>` is the current (writable) buffer, `<name>_prev` is the
383 // previous frame's read-only buffer. Useful for temporal accumulation
384 // / history buffers that live only in the rendering node.
385 // (SSBO only; persistent ping-pong makes no sense for read-only UBOs.)
386 bool persistent{false};
387
388 // When true, declare/bind this auxiliary as a std140 uniform block
389 // (`layout(std140, binding=N) uniform name_t { … } name;`) and bind
390 // with QRhiShaderResourceBinding::uniformBuffer. When false (default),
391 // it's an std430 SSBO. The upstream geometry's
392 // ossia::geometry::auxiliary_buffer is kind-agnostic — the shader's
393 // declaration alone determines how the buffer is bound.
394 bool is_uniform{false};
395 };
396
397 // Texture variant of auxiliary: resolved from ossia::geometry::auxiliary_textures
398 // by name, no score input port. Declared in the top-level AUXILIARY array
399 // with TYPE: "image" / "texture" / "cubemap". Unlike regular INPUTS
400 // textures, does not create an input port — the texture handle travels
401 // bundled with the geometry (e.g. ScenePreprocessor ships `base_color_array`
402 // / `skybox` / `shadow_atlas`).
403 struct auxiliary_texture_request
404 {
405 std::string name;
406 int dimensions{2}; // 2 or 3
407 bool is_array{false}; // sampler2DArray when true
408 bool is_cubemap{false};// samplerCube when true
409 bool is_depth{false}; // sampleable depth (promotes comparison when cfg set)
410 // Storage-image kind: emit `image2D/3D/Cube/Array` with imageLoad/
411 // imageStore semantics instead of `sampler2D/…` with texture(). Set
412 // by TYPE: "storage_image" in the AUXILIARY JSON. Paired with:
413 // - `format`: GLSL layout qualifier (e.g. "rgba8", "r32f", "rgba16f").
414 // - `access`: "read_only" / "write_only" / "read_write", controlling
415 // imageLoad / imageStore / imageLoadStore binding type + the
416 // GLSL `readonly`/`writeonly` decoration.
417 bool is_storage{false};
418 std::string format{"rgba8"}; // only meaningful when is_storage
419 std::string access{"read_write"}; // only meaningful when is_storage
420
421 // Sizing expressions for write_only / read_write storage images. Same
422 // convention as csf_image_input (top-level INPUTS images): an integer
423 // literal or a `$variable` reference resolved against the shader's
424 // long/float input ports + the standard $WIDTH/$HEIGHT/$DEPTH/$LAYERS
425 // family. Empty → engine falls back to renderer state (renderSize for
426 // 2D, voxel-resolution heuristics for 3D). When the engine
427 // auto-allocates a writable nested-aux storage image, these strings
428 // drive its dimensions; for sampled (read-only) entries they're
429 // ignored — the texture comes from the upstream producer at whatever
430 // size that producer baked.
431 std::string width_expression;
432 std::string height_expression;
433 std::string depth_expression; // 3rd dimension for 3D textures
434 std::string layers_expression; // array slice count for 2D arrays
435
436 sampler_config sampler;
437
448 std::string ladder_base;
449 int ladder_index{-1};
450 int ladder_size{0};
451
452 bool in_ladder() const noexcept { return !ladder_base.empty(); }
453 bool owns_ladder() const noexcept { return ladder_index == 0 && in_ladder(); }
454 };
455
456 std::vector<attribute_request> attributes;
457 std::vector<auxiliary_request> auxiliary;
458 std::vector<auxiliary_texture_request> auxiliary_textures;
459
460 std::string vertex_count; // expression string, may contain $USER
461 std::string instance_count; // expression string, may contain $USER
462
463 // PERSISTENT: read_write attributes and auxiliaries work in place on the
464 // upstream's buffers, so their changes accumulate frame to frame and every
465 // other consumer of the upstream sees them. Without it they work on a copy
466 // of the upstream data refreshed every frame.
467 bool persistent{false};
468
469 // Optional format identity stamped onto the consumer geometry's
470 // filter_tag (rapidhash truncated to 32 bits). Only meaningful on
471 // RESOURCES of TYPE: geometry used as outputs (geoOut). Empty leaves
472 // filter_tag at 0 (the "untagged" sentinel) — no routing change for
473 // CSFs that don't author an output format.
474 std::string format_id;
475
476 // Primitive topology of an output geometry: triangles, triangle_strip,
477 // triangle_fan, lines, line_strip or points. Empty inherits the upstream
478 // geometry's topology, or points without one.
479 std::string topology;
480
481 struct indirect_request
482 {
483 std::string count; // expression string (same resolver as vertex_count)
484 // "DRAW_COUNT": true — additionally allocate a GPU-writable u32 draw
485 // count exposed to the compute pass as <name>_indirect_count and
486 // published downstream through the "_indirect_draw_count" auxiliary, for
487 // consumption by drawIndexedIndirectCount (Qt 6.13-era). The command
488 // slots beyond the written count must stay zeroed by the shader so the
489 // capacity-draw fallback rungs paint the same picture.
490 bool draw_count{false};
491
492 // "INDEXED": true — the commands in this buffer drive an INDEXED draw
493 // (the geometry carries an index buffer, e.g. forwarded from upstream),
494 // so the record must be laid out as QRhiDrawIndexedIndirectCommand:
495 // { indexCount, instanceCount, firstIndex, baseVertex, firstInstance }.
496 //
497 // Default (false) is the NON-INDEXED layout, whose first four words are
498 // a native QRhiDrawIndirectCommand:
499 // { vertexCount, instanceCount, firstVertex, firstInstance }
500 // followed by an unused fifth word so that both shapes keep the same
501 // 20-byte stride. Getting this wrong shifts firstInstance by one word
502 // between the GPU indirect rung and the CPU readback rung.
503 bool indexed{false};
504 };
505 std::optional<indirect_request> indirect;
506};
507
508struct input
509{
510 using input_impl = ossia::variant<
511 float_input, long_input, event_input, bool_input, color_input, point2d_input,
512 point3d_input, image_input, cubemap_input, audio_input, audioFFT_input,
513 audioHist_input, storage_input, texture_input, csf_image_input,
514 geometry_input, uniform_input>;
515
516 std::string name;
517 std::string label;
518
519 input_impl data;
520};
521
522// Matches QShaderDescription::VariableType
523enum class attribute_type
524{
525 Unknown = 0,
526
527 Float,
528 Vec2,
529 Vec3,
530 Vec4,
531 Mat2,
532 Mat2x3,
533 Mat2x4,
534 Mat3,
535 Mat3x2,
536 Mat3x4,
537 Mat4,
538 Mat4x2,
539 Mat4x3,
540
541 Int,
542 Int2,
543 Int3,
544 Int4,
545
546 Uint,
547 Uint2,
548 Uint3,
549 Uint4,
550
551 Bool,
552 Bool2,
553 Bool3,
554 Bool4,
555
556 Double,
557 Double2,
558 Double3,
559 Double4,
560 DMat2,
561 DMat2x3,
562 DMat2x4,
563 DMat3,
564 DMat3x2,
565 DMat3x4,
566 DMat4,
567 DMat4x2,
568 DMat4x3,
569
570 Sampler1D,
571 Sampler2D,
572 Sampler2DMS,
573 Sampler3D,
574 SamplerCube,
575 Sampler1DArray,
576 Sampler2DArray,
577 Sampler2DMSArray,
578 Sampler3DArray,
579 SamplerCubeArray,
580 SamplerRect,
581 SamplerBuffer,
582 SamplerExternalOES,
583 Sampler,
584
585 Image1D,
586 Image2D,
587 Image2DMS,
588 Image3D,
589 ImageCube,
590 Image1DArray,
591 Image2DArray,
592 Image2DMSArray,
593 Image3DArray,
594 ImageCubeArray,
595 ImageRect,
596 ImageBuffer,
597
598 Struct,
599
600 Half,
601 Half2,
602 Half3,
603 Half4
604};
605
606struct vertex_attribute
607{
608 int location{};
609 attribute_type type{};
610 std::string name;
611
612 // Optional explicit ossia attribute_semantic name ("position", "velocity",
613 // "texcoord0", ..., "custom"). Only meaningful on `vertex_input` (raw
614 // raster), where it controls how the runtime matches the declared input
615 // to an upstream geometry attribute — same lookup algorithm as CSF
616 // attribute_request. When empty, the parser implicitly uses `name` as the
617 // semantic key. Set to "custom" to force exact-name matching against
618 // custom attributes.
619 std::string semantic;
620
621 // Interpolation qualifier (only applicable to vertex_output / fragment_input).
622 // Allowed: "smooth" (default), "flat", "noperspective", "centroid", "sample".
623 // "sample" forces per-sample fragment shading on this varying — the fragment
624 // shader runs once per MSAA sample for that coverage. Required when MSAA
625 // outputs need per-sample correct interpolation (specular highlights,
626 // normal-mapped surfaces). Empty string = default smooth.
627 std::string interpolation;
628};
629
630struct vertex_input : vertex_attribute
631{
632 // When false, the raw-raster renderer tolerates an upstream geometry that
633 // does not carry a matching attribute: instead of failing the pipeline
634 // build, it synthesises a tiny PerInstance step_rate=1 buffer filled with
635 // a neutral "identity" value (zero for translation, white for color, 1
636 // for roughness, etc.) and binds that in place of the missing upstream
637 // attribute. Lets a single shader cover both instanced and non-instanced
638 // upstreams without per-shape variants.
639 //
640 // When false AND `default_val` is set, those explicit numbers are used
641 // verbatim (after component-truncation / zero-padding against the
642 // declared TYPE). When false AND `default_val` is empty, the runtime
643 // looks the semantic up in a built-in whitelist (see
644 // score::gfx::vertexFallbackDefault) — non-whitelisted semantics without
645 // an explicit DEFAULT are rejected at pipeline-build time with a clear
646 // error to avoid silently-wrong rendering.
647 //
648 // When true (default), the upstream geometry MUST provide the attribute
649 // or the pipeline build fails — existing strict behaviour.
650 bool required{true};
651
652 // Explicit DEFAULT numbers from the JSON header. Stored as doubles for
653 // JSON fidelity; converted to the runtime format (float / int) at
654 // buffer-build time. Empty = use the whitelist neutral (see `required`).
655 // Length is not pre-validated against TYPE here — the runtime truncates
656 // or zero-pads to match the declared GLSL type width.
657 std::vector<double> default_val;
658};
659struct vertex_output : vertex_attribute
660{
661};
662struct fragment_input : vertex_attribute
663{
664};
665struct fragment_output : vertex_attribute
666{
667};
668
669// --- Pipeline state control (PIPELINE_STATE descriptor key) ---------------
670//
671// All fields are optional (std::optional): missing = keep current/legacy
672// default. Two instances live in `descriptor`: a global `default_state`
673// (from PIPELINE_STATE), and a per-pass `override_state` that merges on top.
674
675// ALPHA header key: how a shader output's colour relates to its alpha.
676// `unspecified` resolves per mode through resolve_alpha().
677enum class alpha_mode : uint8_t
678{
679 unspecified,
680 straight,
681 premultiplied
682};
683
684struct blend_attachment
685{
686 bool enable{false};
687 std::string src_color{"src_alpha"};
688 std::string dst_color{"one_minus_src_alpha"};
689 std::string op_color{"add"};
690 std::string src_alpha{"one"};
691 std::string dst_alpha{"one_minus_src_alpha"};
692 std::string op_alpha{"add"};
693 std::string color_write{"rgba"}; // "rgba", "rgb", "r", ...
694};
695
696struct stencil_op_state
697{
698 std::string fail_op{"keep"};
699 std::string depth_fail_op{"keep"};
700 std::string pass_op{"keep"};
701 std::string compare_op{"always"};
702};
703
704struct pipeline_state
705{
706 std::optional<bool> depth_test;
707 std::optional<bool> depth_write;
708 std::optional<std::string> depth_compare; // "less", "less_equal", "greater", ...
709 std::optional<float> depth_bias;
710 std::optional<float> slope_scaled_depth_bias;
711
712 std::optional<std::string> cull_mode; // "none", "front", "back"
713 std::optional<std::string> front_face; // "ccw", "cw"
714 std::optional<std::string> polygon_mode;// "fill", "line"
715 std::optional<float> line_width;
716
717 // Procedural-draw override (Vertex Shader Art style). When
718 // `vertex_count` is set, the renderer issues a single
719 // cb.draw(vertex_count, instance_count, 0, 0) and ignores the
720 // incoming geometry's index / indirect buffers entirely. The vertex
721 // shader drives positions purely from gl_VertexIndex +
722 // gl_InstanceIndex. Use cases:
723 // - Fullscreen passes: VERTEX_COUNT=3, TOPOLOGY=triangles (skybox).
724 // - VSA-style plasma / curves: VERTEX_COUNT=10000,
725 // TOPOLOGY=line_strip.
726 // - Procedural particle grids: VERTEX_COUNT=65536, TOPOLOGY=points.
727 //
728 // Safety: if VERTEX_INPUTS is non-empty (the shader declares vertex
729 // attribute reads), the renderer clamps vertex_count to the incoming
730 // geometry's vertex_count to avoid reading past buffer ends. Shaders
731 // that rely purely on gl_VertexIndex should declare an empty
732 // `VERTEX_INPUTS: []` so the pipeline is built with no vertex
733 // bindings and the draw count is used verbatim.
734 std::optional<uint32_t> vertex_count;
735 std::optional<uint32_t> instance_count;
736 // Topology override. When unset, the incoming geometry's topology is
737 // used. Values: "triangles", "triangle_strip", "triangle_fan",
738 // "lines", "line_strip", "points".
739 std::optional<std::string> topology;
740
741 // Blending: either a single state applied to all color attachments, or a
742 // per-attachment vector. If both are present the per-attachment wins.
743 std::optional<blend_attachment> blend_all;
744 std::vector<blend_attachment> blend_per_attachment;
745
746 // Stencil (optional)
747 std::optional<bool> stencil_test;
748 std::optional<uint32_t> stencil_read_mask;
749 std::optional<uint32_t> stencil_write_mask;
750 std::optional<stencil_op_state> stencil_front;
751 std::optional<stencil_op_state> stencil_back;
752
753 // Variable-rate shading (VRS).
754 // "SHADING_RATE": [w, h] — per-draw shading rate where w,h ∈ {1, 2, 4}.
755 // [1,1] = 1×1 (full rate, default).
756 // [2,2] = 1 invocation per 2×2 pixel block.
757 // [4,4] = 1 per 4×4 block.
758 // Combined with a shading-rate map (set on the render target) the actual
759 // rate is the per-draw rate combined with the per-tile rate via the chosen
760 // combiner op. Requires QRhi::Feature::VariableRateShading (Vulkan, D3D12).
761 std::optional<std::array<int, 2>> shading_rate;
762};
763
764struct pass
765{
766 std::string target;
767 bool persistent{};
768 bool float_storage{};
769 bool nearest_filter{};
770 std::string width_expression{};
771 std::string height_expression{};
772
773 // Render to a specific layer of a texture-array output (-1 = layer 0).
774 int layer{-1};
775
776 // Render to a specific Z-slice of a 3D output. Expression string so the
777 // slice can be computed from inputs (e.g. "$USER_slice"). Empty = slice 0
778 // when the target is 3D, or irrelevant when 2D.
779 std::string z_expression{};
780
781 // Optional format override for the intermediate render target of this
782 // pass (e.g. "rgba16f" for precision-sensitive blur stages). Empty = use
783 // FLOAT: true mapping (rgba32f / rgba8) as before.
784 std::string format{};
785
786 // Per-pass pipeline state overrides (merged with descriptor.default_state).
787 pipeline_state override_state;
788};
789
790struct output_declaration
791{
792 std::string name; // User-chosen name (e.g. "color", "sceneDepth")
793 std::string type; // "color" (default) or "depth"
794
795 // LAYERS: >1 allocates a texture array with this many layers.
796 int layers{1};
797
798 // DEPTH: >1 allocates a 3D texture of this depth. Mutually exclusive with
799 // LAYERS (a ThreeDimensional texture is not a TextureArray). A fragment
800 // PASSES entry with Z renders into a single Z-slice via a color attachment
801 // with setLayer(z).
802 int depth{1};
803
804 // FORMAT: optional explicit texture format ("rgba8", "rgba16f", "r32f", "d32f", ...).
805 // Empty = use the default (RGBA8 for color, D32F for depth).
806 std::string format;
807
808 // SAMPLES: MSAA sample count (1, 2, 4, 8, 16, 32, 64). 1 = no MSAA.
809 // 0 = not declared: the renderer's own sample count applies.
810 // The renderer allocates an MSAA texture and inserts an automatic resolve
811 // pass when downstream consumers expect a non-MSAA input. Each declared
812 // OUTPUT can have its own sample count; the depth attachment for a colour
813 // OUTPUT inherits the same sample count.
814 int samples{0};
815
816 // CUBEMAP: when true the output is allocated with the QRhi cubemap flag
817 // so downstream consumers can bind it as a samplerCube. Implies
818 // `layers == 6` on allocation even when the shader didn't set LAYERS
819 // explicitly. Used by the IBL precompute path (irradiance_convolve,
820 // prefilter_ggx) together with MULTIVIEW:6.
821 bool is_cubemap{false};
822
823 // GENERATE_MIPS: when true the runtime calls generateMips() on this
824 // output's texture after the render pass completes, auto-averaging
825 // the base level into a full mip chain. Implies the QRhi
826 // `MipMapped` + `UsedWithGenerateMips` flags on allocation. Use this
827 // for "source-data" targets whose base level is authored by the
828 // fragment shader and whose sub-mips should be GPU-filtered (skybox
829 // converter, base color textures, SSAO LUTs…). NOT for the
830 // prefilter-style case where each mip has distinct shader-authored
831 // content — use EXECUTION_MODEL: PER_MIP instead.
832 bool generate_mips{false};
833
834 // WIDTH / HEIGHT: explicit target size for offscreen outputs. Set
835 // by the shader author when the intrinsic size of the algorithm
836 // isn't tied to the window / swap-chain (IBL precompute, shadow
837 // atlases, post-process LUTs, …). Zero → fall back to the
838 // renderer's render-size (classic behaviour). Integer literal or
839 // string expression; the expression is evaluated once at init
840 // against the same variable surface as CSF dispatch expressions
841 // ($WIDTH_<input> / $HEIGHT_<input> / scalar input values).
842 //
843 // All colour OUTPUTs of a single RAW_RASTER_PIPELINE shader share
844 // a render pass and must therefore resolve to the same final size;
845 // the runtime uses the first colour OUTPUT's resolved size as the
846 // RT size and allocates every attachment at that size. Cubemaps
847 // are additionally clamped to square via min(w, h) (QRhi contract).
848 int width{0};
849 int height{0};
850 std::string width_expression;
851 std::string height_expression;
852
853 // ALPHA: overrides the descriptor-level ALPHA for this output.
854 alpha_mode alpha{alpha_mode::unspecified};
855
856 // COMPOSITE: overrides the descriptor-level COMPOSITE for this output.
857 composite_mode composite{composite_mode::unspecified};
858};
859
860// QUEUE header key: the order of the cables sharing a consumer input.
861// `unspecified` resolves to transparent for a LAYER shader, else opaque.
862enum class render_queue : uint8_t
863{
864 unspecified,
865 opaque,
866 transparent
867};
868
869// One LAYER.TARGETS entry: a private colour target the draw writes as the
870// fragment output of the same NAME.
871struct layer_target
872{
873 std::string name;
874 std::string format{"rgba16f"};
875 std::array<float, 4> clear{0.f, 0.f, 0.f, 0.f};
876 std::optional<blend_attachment> blend;
877 composite_mode composite{composite_mode::unspecified};
878};
879
880// LAYER.RESOLVE: the shader's own full-screen pass into the consumer, compiled
881// from the fragment source with ISF_RESOLVE_PASS defined.
882struct layer_resolve
883{
884 bool declared{false};
885 std::string output{"isf_FragColor"};
886 std::optional<blend_attachment> blend;
887 composite_mode composite{composite_mode::unspecified};
888 bool depth_write{false};
889 std::string depth_input;
890 std::string fragment;
891};
892
893struct layer_state
894{
895 bool declared{false};
896 std::vector<layer_target> targets;
897 bool depth_test{true};
898 layer_resolve resolve;
899
900 bool enabled() const noexcept { return declared; }
901};
902
903struct descriptor
904{
905 enum Mode
906 {
907 ISF,
908 VSA,
909 CSF,
910 RawRaster
911 } mode{ISF};
912 std::string description;
913 std::string credits;
914 std::vector<std::string> categories;
915
916 // ALPHA: what every colour output writes, unless an OUTPUTS entry says
917 // otherwise. Unspecified: straight for ISF, premultiplied for CSF, raw
918 // raster and VSA.
919 alpha_mode alpha{alpha_mode::unspecified};
920
921 // COMPOSITE: how every colour output is composited onto its target, unless
922 // an OUTPUTS entry or a storage image says otherwise. Unspecified: over.
923 composite_mode composite{composite_mode::unspecified};
924 std::vector<input> inputs;
925 std::vector<output_declaration> outputs; // Parsed from OUTPUTS array; empty = single color output
926 std::vector<pass> passes;
927 std::vector<std::string> pass_targets;
928 bool default_vertex_shader{};
929
930 // For VSA
931 int point_count{};
932 std::string primitive_mode;
933 std::string line_size;
934 std::array<double, 4> background_color{};
935
936 // For CSF
937 struct type_definition
938 {
939 std::string name;
940 std::vector<storage_input::layout_field> layout;
941 };
942 std::vector<type_definition> types;
943
944 struct dispatch_info
945 {
946 std::array<int, 3> local_size{16, 16, 1};
947 std::string execution_type{"2D_IMAGE"}; // "2D_IMAGE", "1D_BUFFER", "PER_VERTEX", "PER_INSTANCE", "MANUAL", "USER", "INDIRECT"
948 std::string target_resource;
949 // MANUAL: the dispatch size. INDIRECT: the mandatory worst-case CEILING
950 // used when the backend cannot dispatch indirectly (Qt < 6.13 or the
951 // feature is absent/killed): the pass is dispatched at this size and the
952 // shader must bound itself by reading its own arguments buffer.
953 std::array<int, 3> workgroups{1, 1, 1};
954 // INDIRECT only: byte offset of the {x,y,z} u32 triplet inside the
955 // TARGET storage buffer ("OFFSET" key, 4-byte aligned).
956 int indirect_byte_offset{0};
957 std::array<std::string, 3> stride{"1", "1", "1"}; // Per-axis stride (supports formulas)
958 std::array<int, 3> user_dispatch_ports{-1, -1, -1}; // Port indices for USER mode (X, Y, Z)
959 };
960 std::vector<dispatch_info> csf_passes;
961
962 // For raw shaders
963
964 std::vector<vertex_input> vertex_inputs;
965 std::vector<vertex_output> vertex_outputs;
966 std::vector<fragment_input> fragment_inputs;
967 std::vector<fragment_output> fragment_outputs;
968
969 // Auxiliary SSBOs expected from upstream geometry (matched by name).
970 // Populated from top-level AUXILIARY key in RAW_RASTER_PIPELINE mode.
971 std::vector<geometry_input::auxiliary_request> auxiliary;
972
973 // Auxiliary textures travelling with the geometry (matched by name
974 // against ossia::geometry::auxiliary_textures). Populated from the same
975 // top-level AUXILIARY array when entries have TYPE: "image" / "texture"
976 // / "cubemap". Unlike INPUTS-declared textures they don't consume a
977 // score input port — the renderer looks them up on the geometry every
978 // frame.
979 std::vector<geometry_input::auxiliary_texture_request> auxiliary_textures;
980
981 // PIPELINE_STATE: global pipeline state (depth, blend, cull, stencil, ...).
982 // Applies to every output pass; may be overridden per-pass via pass::override_state.
983 pipeline_state default_state;
984
985 // MULTIVIEW: render to N layers of a texture array in a single draw.
986 // 0 or 1 = disabled. N>=2 = enabled (requires QRhi::MultiView capability).
987 int multiview_count{0};
988
989 // EXECUTION_MODEL (RAW_RASTER_PIPELINE only — silently ignored in other
990 // modes). Drives the invocation count of the single raster pass:
991 //
992 // "SINGLE" (default) — one invocation per frame, RT bound at
993 // mip 0.
994 // "PER_MIP" — N invocations, RT bound at mip `i` on iteration
995 // `i`. N is derived from the `target` texture's
996 // mip chain (floor(log2(min(w, h))) + 1).
997 // ProcessUBO.passIndex carries the mip index.
998 // "PER_CUBE_FACE" — 6 invocations, RT bound at cube layer `i`
999 // (face order +X, -X, +Y, -Y, +Z, -Z). Target
1000 // OUTPUT must be CUBEMAP: true. Mutually
1001 // exclusive with MULTIVIEW (which already
1002 // amplifies one draw to 6 faces).
1003 // "PER_LAYER" — N invocations, RT bound at array layer `i`. N
1004 // comes from the target OUTPUT's `layers`
1005 // declaration. Works on either colour TextureArray
1006 // targets (setLayer attachment) or depth
1007 // TextureArray targets (rendered to a scratch
1008 // and copied into the array layer post-pass —
1009 // QRhi 6.11 has no per-layer depth attachment
1010 // API). ProcessUBO.passIndex carries the layer
1011 // index. Drives shadow_cascades.frag.
1012 // "MANUAL" — N invocations, same RT each time, where N is
1013 // evaluated from the `count` expression string
1014 // via the math_expression parser every frame
1015 // (same variable bindings as CSF's stride /
1016 // image-size expressions: $WIDTH, $HEIGHT,
1017 // $<inputName>, ...).
1018 struct raster_execution_model
1019 {
1020 std::string type; // "SINGLE" / "PER_MIP" / "PER_CUBE_FACE" / "PER_LAYER" / "MANUAL"
1021 std::string target; // PER_MIP / PER_CUBE_FACE / PER_LAYER: OUTPUT name to iterate
1022 std::string count_expression; // MANUAL: integer-valued expression
1023 };
1024 raster_execution_model execution_model;
1025
1026 // User-declared GLSL extension names, emitted as `#extension NAME : require`
1027 // immediately after `#version` in every generated stage. Examples:
1028 // "GL_KHR_shader_subgroup_arithmetic", "GL_EXT_shader_atomic_float".
1029 std::vector<std::string> extensions{
1030 "GL_GOOGLE_include_directive", "GL_GOOGLE_cpp_style_line_directive"};
1031
1032 // CLIP_DISTANCES: number of gl_ClipDistance[N] outputs the vertex shader
1033 // writes (1..8 typical). When > 0 the parser injects
1034 // `out float gl_ClipDistance[N];` in the vertex stage so user code can
1035 // assign without writing the declaration. Each declared distance enables
1036 // one user-defined clipping plane: fragments where gl_ClipDistance[i] < 0
1037 // are discarded.
1038 int clip_distances{0};
1039
1040 // PRIMITIVE_DATA (RAW_RASTER_PIPELINE): the fragment stage gets PRIMITIVE_ID,
1041 // the index of its primitive in the draw, and BARYCENTRIC, its position in
1042 // the primitive, both derived from gl_VertexIndex in the vertex stage. The
1043 // renderer expands indexed and strip meshes into lists so every primitive
1044 // has vertices of its own.
1045 bool primitive_data{false};
1046
1047 // CULL_DISTANCES: like clip distances but per-primitive: a primitive whose
1048 // every vertex has all gl_CullDistance[i] < 0 is fully culled before
1049 // rasterisation. Useful for cheap frustum-/occlusion-style culling.
1050 int cull_distances{0};
1051
1052 // LAYER (RAW_RASTER_PIPELINE): the draw renders into private targets of the
1053 // consumer's size instead of the consumer, then a resolve pass writes the
1054 // consumer.
1055 // TARGETS [{ NAME, FORMAT, CLEAR, BLEND | COMPOSITE }]: one per
1056 // FRAGMENT_OUTPUTS entry, same names, same order (synthesised
1057 // as vec4 outputs when FRAGMENT_OUTPUTS is absent). FORMAT
1058 // rgba8 | rgba16f (default) | rgba32f | r8 | rg8 | r16 | rg16 |
1059 // r16f | r32f; CLEAR a vec4, default transparent black; BLEND
1060 // a PIPELINE_STATE.BLEND value, COMPOSITE a COMPOSITE mode,
1061 // default the premultiplied over. Without TARGETS, one rgba16f
1062 // target per FRAGMENT_OUTPUTS entry.
1063 // DEPTH_TEST test the draw against the consumer's depth, read-only
1064 // (default true).
1065 // RESOLVE { OUTPUT, BLEND | COMPOSITE, DEPTH_WRITE, DEPTH_INPUT }: the
1066 // shader's `#if defined(ISF_RESOLVE_PASS)` section runs as a
1067 // full-screen pass into the consumer, each target a sampler2D
1068 // of its NAME, DEPTH_INPUT (a name) the consumer's depth,
1069 // writing OUTPUT (default isf_FragColor) and, with DEPTH_WRITE,
1070 // gl_FragDepth through the consumer's depth test. Without
1071 // RESOLVE the first target is composited with COMPOSITE.
1072 // Needs no OUTPUTS, SINGLE execution, no MULTIVIEW.
1073 layer_state layer;
1074
1075 // QUEUE: "opaque" | "transparent". Transparent sources draw after the opaque
1076 // ones into an input they share.
1077 render_queue queue{render_queue::unspecified};
1078
1079 // DEPTH_LAYOUT: conservative-depth qualifier on gl_FragDepth. Allowed:
1080 // "any" — driver default (no guarantee, disables early-Z when
1081 // gl_FragDepth is written).
1082 // "greater" — promise the value written is >= the value rasterisation
1083 // would have produced. Lets the HW keep early-Z reject
1084 // for fragments already deeper than the depth buffer.
1085 // "less" — symmetric promise in the other direction.
1086 // "unchanged" — promise the written value equals the rasterised value
1087 // (mostly for documentation; same fast path as "greater"
1088 // on hardware where reverse-Z applies).
1089 // Empty = no qualifier emitted.
1090 std::string depth_layout;
1091};
1092
1093// The ALPHA convention of a colour output: the OUTPUTS entry's, else the
1094// descriptor's, else the mode's default.
1095SCORE_PLUGIN_GFX_EXPORT
1096alpha_mode resolve_alpha(
1097 const descriptor& d, const output_declaration* out = nullptr) noexcept;
1098
1099// The COMPOSITE of a colour output: the OUTPUTS entry's (or the storage
1100// image's), else the descriptor's, else over.
1101SCORE_PLUGIN_GFX_EXPORT
1102composite_mode resolve_composite(
1103 const descriptor& d, const output_declaration* out = nullptr) noexcept;
1104SCORE_PLUGIN_GFX_EXPORT
1105composite_mode resolve_composite(const descriptor& d, const csf_image_input& img) noexcept;
1106
1107// A straight output composited with multiply or screen has no exact blend
1108// factors: the generated code (or the engine's copy) premultiplies it, and it
1109// is blended as premultiplied.
1110SCORE_PLUGIN_GFX_EXPORT
1111bool premultiplied_by_engine(alpha_mode alpha, composite_mode composite) noexcept;
1112
1113// Whether the shader's DEPTH_COMPARE keeps the greater depth (reverse-Z, the
1114// default) rather than the smaller one.
1115SCORE_PLUGIN_GFX_EXPORT
1116bool depth_nearer_is_greater(const descriptor& d) noexcept;
1117
1118// Whether the shader draws in the transparent QUEUE.
1119SCORE_PLUGIN_GFX_EXPORT
1120bool draws_transparent(const descriptor& d) noexcept;
1121
1122// Whether a raw raster renders into the faces of a cube OUTPUT. Its clip-space
1123// y is then negated on every backend whose clip space is not y-up, so that the
1124// faces land in the order of the GL cube-map face table.
1125SCORE_PLUGIN_GFX_EXPORT
1126bool renders_cube_faces(const descriptor& d) noexcept;
1127
1128// Whether a pipeline state declares BLEND or BLEND_PER_ATTACHMENT.
1129SCORE_PLUGIN_GFX_EXPORT
1130bool declares_blend(const pipeline_state& s) noexcept;
1131
1132// Whether an input samples the engine's premultiplied render target (a 2D
1133// image or texture input fed by cables), rather than a producer's texture
1134// bound as is (3D, array, STATIC, cubemap, audio). IMG_PIXEL, IMG_NORM_PIXEL,
1135// IMG_THIS_PIXEL and IMG_THIS_NORM_PIXEL unpremultiply such inputs.
1136SCORE_PLUGIN_GFX_EXPORT
1137bool is_premultiplied_render_target(const input& in) noexcept;
1138
1139class SCORE_PLUGIN_GFX_EXPORT parser
1140{
1141 std::string m_sourceVertex;
1142 std::string m_sourceFragment;
1143 std::string m_source_geometry_filter;
1144 int m_version{450};
1145
1146 std::string m_vertex;
1147 std::string m_fragment;
1148 std::string m_geometry_filter;
1149
1150 // True when parse_raw_raster_pipeline synthesised the `camera` auxiliary
1151 // rather than the shader declaring its own. Gates the typed accessors: a
1152 // hand-written camera block has its own layout and must not be shadowed.
1153 bool m_injected_camera_aux{false};
1154
1155 descriptor m_desc;
1156
1157public:
1158 enum class ShaderType
1159 {
1160 Autodetect,
1161 ISF,
1162 ShaderToy,
1163 GLSLSandBox,
1164 GeometryFilter,
1165 VertexShaderArt,
1166 CSF,
1167 RawRasterPipeline,
1168 RawRaytracePipeline,
1169 RawMeshPipeline
1170 };
1171 parser(std::string vert, std::string frag, int glslVersion, ShaderType);
1172 explicit parser(std::string isf_geom_filter, ShaderType t);
1173
1174 descriptor data() const;
1175 descriptor::Mode mode() const;
1176 std::string vertex() const;
1177 std::string fragment() const;
1178 std::string geometry_filter() const;
1179 std::string compute_shader() const;
1180 static std::pair<int, descriptor> parse_isf_header(std::string_view source);
1181 void parse_shadertoy_json(const std::string& json);
1182
1183 std::string write_isf() const;
1184
1185private:
1186 void parse_isf();
1187 void parse_raw_raster_pipeline();
1188 void parse_shadertoy();
1189 void parse_glsl_sandbox();
1190 void parse_geometry_filter();
1191 void parse_vsa();
1192 void parse_csf();
1193};
1194}
@ Unknown
Versions unreadable; treated as usable.