Constant TEMPLATE
Source const TEMPLATE: &str = "// advanced_blend.wgsl \u{2014} TEMPLATE for the 21 advanced blend modes (Tier C).\n//\n// One full-screen triangle samples both `t_backdrop` (the previously-\n// rendered destination) and `t_foreground` (this node\'s contribution\n// rendered into its own RT), runs `blend_fn` per channel, and writes\n// the composited result.\n//\n// The Rust side substitutes a per-mode `blend_fn(base, blend) -> vec3<f32>`\n// body in place of the placeholder marker below. The Rust template\n// resolver in `render/advanced_blend.rs` is the only place that\n// generates valid WGSL from this file.\n\n@group(0) @binding(0) var t_backdrop: texture_2d<f32>;\n@group(0) @binding(1) var t_foreground: texture_2d<f32>;\n@group(0) @binding(2) var s_sampler: sampler;\n\nstruct VsOut {\n @builtin(position) pos: vec4<f32>,\n @location(0) uv: vec2<f32>,\n};\n\n@vertex\nfn main_vs(@builtin(vertex_index) vi: u32) -> VsOut {\n // Single-triangle fullscreen quad \u{2014} covers NDC [-1, +1].\n let x = f32(i32(vi & 1u) * 4 - 1);\n let y = f32(i32(vi & 2u) * 2 - 1);\n var out: VsOut;\n out.pos = vec4<f32>(x, y, 0.0, 1.0);\n // UV in [0,1] with y flipped so the texture isn\'t upside down.\n out.uv = vec2<f32>((x + 1.0) * 0.5, 1.0 - (y + 1.0) * 0.5);\n return out;\n}\n\n// \u{2500}\u{2500}\u{2500} HSL helpers (used by Saturation / Color / Luminosity) \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\nfn lum(c: vec3<f32>) -> f32 {\n return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;\n}\n\nfn clip_color(c: vec3<f32>) -> vec3<f32> {\n let l = lum(c);\n let n = min(min(c.r, c.g), c.b);\n let x = max(max(c.r, c.g), c.b);\n var out = c;\n if (n < 0.0) {\n out = l + (((out - l) * l) / (l - n));\n }\n if (x > 1.0) {\n out = l + (((out - l) * (1.0 - l)) / (x - l));\n }\n return out;\n}\n\nfn set_lum(c: vec3<f32>, l: f32) -> vec3<f32> {\n let d = l - lum(c);\n return clip_color(c + vec3<f32>(d));\n}\n\nfn sat(c: vec3<f32>) -> f32 {\n return max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);\n}\n\nfn set_sat(c: vec3<f32>, s: f32) -> vec3<f32> {\n let cmin = min(min(c.r, c.g), c.b);\n let cmax = max(max(c.r, c.g), c.b);\n if (cmax > cmin) {\n return ((c - vec3<f32>(cmin)) * s) / (cmax - cmin);\n }\n return vec3<f32>(0.0);\n}\n\n// \u{2500}\u{2500}\u{2500} PER-MODE BLEND FUNCTION (substituted by Rust) \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n// __BLEND_FN_PLACEHOLDER__\n\n@fragment\nfn main_fs(in: VsOut) -> @location(0) vec4<f32> {\n let backdrop = textureSample(t_backdrop, s_sampler, in.uv);\n let foreground = textureSample(t_foreground, s_sampler, in.uv);\n\n let blended = blend_fn(backdrop.rgb, foreground.rgb);\n\n // PixiJS-style compositing: the foreground.alpha controls how much\n // of the blended result overrides the backdrop. Output alpha is\n // source-over (`fg.a + bg.a\u{b7}(1 - fg.a)`).\n let mixed_rgb = mix(backdrop.rgb, blended, foreground.a);\n let out_alpha = foreground.a + backdrop.a * (1.0 - foreground.a);\n return vec4<f32>(mixed_rgb, out_alpha);\n}\n";