Merge M13.9 gpu noise, blurs and time (fx_noise)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-03 08:21:23 -04:00
co-authored by Claude Opus 5.5
10 changed files with 1778 additions and 82 deletions
+822
View File
@@ -1,3 +1,825 @@
// GPU effects (noise, blur and time family): see src/fx_noise.rs. Each entry point mirrors the CPU effect in
// effectcraft-effects operation for operation. Every name here is prefixed `fxn_` (the
// family files share one module).
const FXN_MAX: f32 = 3.40282347e38;
fn fxn_pixel(gid: vec3<u32>) -> vec2<i32> {
let dims = out_dims();
let p = vec2<i32>(gid.xy);
if (p.x >= dims.x || p.y >= dims.y) {
return vec2<i32>(-1);
}
return p;
}
// a / b rounded like the CPU's IEEE division (GPU division may be approximate): one
// residual correction with fma. Used where a threshold follows the quotient.
fn fxn_div4(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
let q = a / b;
let r = fma(-q, b, a);
return fma(r, 1.0 / b, q);
}
// util::unpremul: (straight colour, alpha).
fn fxn_unpremul(p: vec4<f32>) -> vec4<f32> {
if (p.w > 1e-6) {
return vec4<f32>(p.xyz / p.w, p.w);
}
return vec4<f32>(0.0, 0.0, 0.0, max(p.w, 0.0));
}
// ---------------------------------------------------------------- Median family (noise::median_image)
// Quantise to the median's 512 levels (0..511). u[0].x = 1: straight colour with alpha 1
// (Median (Legacy) colour only).
@compute @workgroup_size(16, 16)
fn fxn_quant(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var v = textureLoad(src, p, 0);
if (P.u[0].x == 1u) {
v = vec4<f32>(fxn_unpremul(v).xyz, 1.0);
}
let q = clamp(v, vec4<f32>(0.0), vec4<f32>(1.0)) * 511.0;
textureStore(out, p, vec4<f32>(round_away(q.x), round_away(q.y), round_away(q.z), round_away(q.w)));
}
// Per-channel median of the quantised levels over a (2r+1)² window (edges repeated): the
// smallest level m with more than half the window at or below m. u[0].x = r.
@compute @workgroup_size(16, 16)
fn fxn_median(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let r = i32(P.u[0].x);
let n = (2 * r + 1) * (2 * r + 1);
let half = f32(n / 2);
var lo = vec4<f32>(0.0);
var hi = vec4<f32>(511.0);
for (var it = 0; it < 9; it++) {
let mid = floor((lo + hi) * 0.5);
var cnt = vec4<f32>(0.0);
for (var dy = -r; dy <= r; dy++) {
for (var dx = -r; dx <= r; dx++) {
let q = tex_get_clamped(src, p.x + dx, p.y + dy);
cnt += select(vec4<f32>(0.0), vec4<f32>(1.0), q <= mid);
}
}
let above = cnt > vec4<f32>(half);
hi = select(hi, mid, above);
lo = select(mid + 1.0, lo, above);
}
textureStore(out, p, fxn_div4(lo, vec4<f32>(511.0)));
}
// noise::median_px.
fn fxn_median_px(o: vec4<f32>, m: vec4<f32>, on_alpha: bool) -> vec4<f32> {
if (on_alpha) {
let a = clamp(m.w, 0.0, 1.0);
return vec4<f32>(min(m.xyz, vec3<f32>(a)), a);
}
if (m.w <= 1e-4) {
return o;
}
return vec4<f32>(min(fxn_div4(m, vec4<f32>(m.w)).xyz, vec3<f32>(1.0)) * o.w, o.w);
}
// src = the layer, aux = the median. u[0] = (mode, operate on alpha): 0 Median, 1 Dust &
// Scratches (f[0].x = threshold), 2 Median (Legacy) with alpha, 3 Median (Legacy) colour only.
@compute @workgroup_size(16, 16)
fn fxn_median_out(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let o = textureLoad(src, p, 0);
let m = textureLoad(aux, p, 0);
let on_alpha = P.u[0].y != 0u;
var r = o;
switch P.u[0].x {
case 0u: {
r = fxn_median_px(o, m, on_alpha);
}
case 1u: {
let cand = fxn_median_px(o, m, on_alpha);
let d = abs(cand - o);
if (max(max(d.x, d.y), max(d.z, d.w)) > P.f[0].x) {
r = cand;
}
}
case 2u: {
r = m;
}
default: {
r = vec4<f32>(m.xyz * o.w, o.w);
}
}
textureStore(out, p, r);
}
// ---------------------------------------------------------------- Bilateral / Smart Blur (blur2.rs)
// blur2::edge_preserving. u[0] = (ceil r, stride, hard, largest d²); u[1].x = grey (Bilateral
// with Colorize off); f[0] = (2σs², 2σr², range).
@compute @workgroup_size(16, 16)
fn fxn_edge(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let ri = i32(P.u[0].x);
let stride = i32(P.u[0].y);
let hard = P.u[0].z != 0u;
let d2max = i32(P.u[0].w);
let sig_s2 = P.f[0].x;
let sig_r2 = P.f[0].y;
let range = P.f[0].z;
let c = textureLoad(src, p, 0);
let cl = luminance(c.xyz);
var acc = vec4<f32>(0.0);
var wsum = 0.0;
for (var dy = -ri; dy <= ri; dy += stride) {
for (var dx = -ri; dx <= ri; dx += stride) {
let d2 = dx * dx + dy * dy;
if (d2 > d2max) {
continue;
}
let q = tex_get_clamped(src, p.x + dx, p.y + dy);
var w = 0.0;
if (hard) {
w = select(0.0, 1.0, abs(luminance(q.xyz) - cl) <= range);
} else {
let d = q - c;
let dc = d.x * d.x + d.y * d.y + d.z * d.z + d.w * d.w;
w = exp(-f32(d2) / sig_s2 - dc / sig_r2);
}
if (w > 0.0) {
acc += q * w;
wsum += w;
}
}
}
var o = c;
if (wsum > 0.0) {
o = acc / wsum;
}
if (P.u[1].x != 0u && o.w > 0.0) {
let l = luminance(o.xyz / o.w);
o = vec4<f32>(vec3<f32>(l * o.w), o.w);
}
textureStore(out, p, o);
}
// Smart Blur's Edge Only / Overlay Edge (blur2::sobel on the blurred layer's premultiplied
// luminance). u[0].x = mode; f[0].x = edge threshold.
@compute @workgroup_size(16, 16)
fn fxn_smart_edges(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var l: array<f32, 9>;
for (var j = 0; j < 3; j++) {
for (var i = 0; i < 3; i++) {
l[j * 3 + i] = luminance(tex_get_clamped(src, p.x + i - 1, p.y + j - 1).xyz);
}
}
let gx = l[2] + 2.0 * l[5] + l[8] - l[0] - 2.0 * l[3] - l[6];
let gy = l[6] + 2.0 * l[7] + l[8] - l[0] - 2.0 * l[1] - l[2];
let e = sqrt(gx * gx + gy * gy) * 0.25;
let px = textureLoad(src, p, 0);
let on = e > P.f[0].x;
var o = px;
if (P.u[0].x == 1u) {
o = vec4<f32>(vec3<f32>(select(0.0, 1.0, on) * px.w), px.w);
} else if (on) {
o = vec4<f32>(vec3<f32>(px.w), px.w);
}
textureStore(out, p, o);
}
// ---------------------------------------------------------------- Sharpen / Unsharp Mask (misc::unsharp_core)
// src = the layer, aux = its blur. f[0] = (amount, threshold in 0..255).
@compute @workgroup_size(16, 16)
fn fxn_unsharp(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var o = textureLoad(src, p, 0);
let bl = textureLoad(aux, p, 0);
for (var c = 0; c < 3; c++) {
let d = o[c] - bl[c];
if (abs(d) * 255.0 >= P.f[0].y) {
o[c] = max(o[c] + d * P.f[0].x, 0.0);
}
}
textureStore(out, p, o);
}
// ---------------------------------------------------------------- planes
// Channel select: channel c of `src` becomes aux[c] (u[0].x = 0) or aux's alpha (1) where
// u[1][c] != 0.
@compute @workgroup_size(16, 16)
fn fxn_pick(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var o = textureLoad(src, p, 0);
let a = textureLoad(aux, p, 0);
for (var c = 0; c < 4; c++) {
if (P.u[1][c] != 0u) {
o[c] = select(a[c], a.w, P.u[0].x == 1u);
}
}
textureStore(out, p, o);
}
// Channel Blur's result: src = blurred premultiplied channels, aux = the alpha blurred like
// each channel (alpha = the Alpha Blurriness result).
@compute @workgroup_size(16, 16)
fn fxn_cblur_out(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let v = textureLoad(src, p, 0);
let ba = textureLoad(aux, p, 0);
var s = vec3<f32>(0.0);
for (var c = 0; c < 3; c++) {
if (ba[c] > 1e-6) {
s[c] = v[c] / ba[c];
}
}
let a = clamp(ba.w, 0.0, 1.0);
textureStore(out, p, vec4<f32>(s * a, a));
}
// Straight colour planes with the raw alpha (Minimax).
@compute @workgroup_size(16, 16)
fn fxn_straight(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let v = textureLoad(src, p, 0);
textureStore(out, p, vec4<f32>(fxn_unpremul(v).xyz, v.w));
}
// Premultiply straight planes by the clamped alpha.
@compute @workgroup_size(16, 16)
fn fxn_premul(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let v = textureLoad(src, p, 0);
let a = clamp(v.w, 0.0, 1.0);
textureStore(out, p, vec4<f32>(v.xyz * a, a));
}
// util::morph_plane along one axis (edges repeated). u[0] = (r, vertical); u[1] = per-channel
// operation (0 min, 1 max, 2 keep).
@compute @workgroup_size(16, 16)
fn fxn_morph(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let r = i32(P.u[0].x);
let vertical = P.u[0].y != 0u;
let centre = textureLoad(src, p, 0);
var lo = centre;
var hi = centre;
for (var k = -r; k <= r; k++) {
var v: vec4<f32>;
if (vertical) {
v = tex_get_clamped(src, p.x, p.y + k);
} else {
v = tex_get_clamped(src, p.x + k, p.y);
}
lo = min(lo, v);
hi = max(hi, v);
}
var o = centre;
for (var c = 0; c < 4; c++) {
if (P.u[1][c] == 0u) {
o[c] = lo[c];
} else if (P.u[1][c] == 1u) {
o[c] = hi[c];
}
}
textureStore(out, p, o);
}
// Crop: out(p) = src(p + u[0].xy).
@compute @workgroup_size(16, 16)
fn fxn_crop(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
textureStore(out, p, textureLoad(src, p + vec2<i32>(P.u[0].xy), 0));
}
// ---------------------------------------------------------------- Compound Blur (blur2::compound_blur)
// The blur map (straight luminance × alpha, inverted with u[0].y) in every channel. u[0].x = 1:
// `src` is the Blur Layer sampled at the per-column / per-row coordinates in `data` (u[1].x =
// width); 0: `src` is the layer itself.
@compute @workgroup_size(16, 16)
fn fxn_cb_map(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var v: vec4<f32>;
if (P.u[0].x == 1u) {
let w = P.u[1].x;
v = sample_bilinear(src, data[u32(p.x)], data[w + u32(p.y)]);
} else {
v = textureLoad(src, p, 0);
}
let s = fxn_unpremul(v);
var l = luminance(s.xyz) * s.w;
if (P.u[0].y != 0u) {
l = 1.0 - l;
}
textureStore(out, p, vec4<f32>(l));
}
// Writes the pixels whose blur level falls between levels k (src) and k + 1 (aux). The map is
// `data` (RGBA f32 rows of u[0].y pixels); u[0].x = k.
@compute @workgroup_size(16, 16)
fn fxn_cb_level(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let m = data[(u32(p.y) * P.u[0].y + u32(p.x)) * 4u];
let t = clamp(m, 0.0, 1.0) * 4.0;
let k = min(u32(floor(t)), 3u);
if (k != P.u[0].x) {
return;
}
let f = t - f32(k);
let a = textureLoad(src, p, 0);
let c = textureLoad(aux, p, 0);
textureStore(out, p, a + (c - a) * f);
}
// ---------------------------------------------------------------- Echo (time_fx::echo)
// acc (src) ⊕ frame (aux) × f[0].x. u[0] = (operator, first frame).
@compute @workgroup_size(16, 16)
fn fxn_echo(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let op = P.u[0].x;
var acc = textureLoad(src, p, 0);
if (P.u[0].y != 0u) {
acc = select(vec4<f32>(0.0), vec4<f32>(FXN_MAX), op == 2u);
}
let k = P.f[0].x;
let q = textureLoad(aux, p, 0) * k;
switch op {
case 1u: {
acc = max(acc, q);
}
case 2u: {
acc = min(acc, q);
}
case 3u: {
acc = acc + q - acc * q;
}
case 4u: {
acc += q * (1.0 - acc.w);
}
case 5u: {
acc = q + acc * (1.0 - q.w);
}
default: {
acc += q;
}
}
textureStore(out, p, acc);
}
// Echo's finish. u[0].x = operator; f[0].x = number of frames.
@compute @workgroup_size(16, 16)
fn fxn_echo_fin(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let op = P.u[0].x;
var acc = textureLoad(src, p, 0);
if (op == 6u) {
acc = acc / P.f[0].x;
}
if (op == 2u && acc.x == FXN_MAX) {
acc = vec4<f32>(0.0);
}
textureStore(out, p, vec4<f32>(max(acc.xyz, vec3<f32>(0.0)), clamp(acc.w, 0.0, 1.0)));
}
// ---------------------------------------------------------------- Fractal / Turbulent Noise (noise3.rs)
fn fxn_catmull(p0: f32, p1: f32, p2: f32, p3: f32, t: f32) -> f32 {
return 0.5 * (2.0 * p1 + (p2 - p0) * t + (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t * t + (3.0 * p1 - p0 - 3.0 * p2 + p3) * t * t * t);
}
fn fxn_slice(ix: i32, iy: i32, iz: i32, tx: f32, ty: f32, seed: u32, nt: u32) -> f32 {
if (nt == 0u) {
return lattice(ix, iy, iz, seed);
}
if (nt == 1u) {
let a = lattice(ix, iy, iz, seed);
let b = lattice(ix + 1, iy, iz, seed);
let c = lattice(ix, iy + 1, iz, seed);
let d = lattice(ix + 1, iy + 1, iz, seed);
let top = a + (b - a) * tx;
let bot = c + (d - c) * tx;
return top + (bot - top) * ty;
}
var rows: array<f32, 4>;
for (var j = 0; j < 4; j++) {
let y = iy + j - 1;
rows[j] = fxn_catmull(lattice(ix - 1, y, iz, seed), lattice(ix, y, iz, seed), lattice(ix + 1, y, iz, seed), lattice(ix + 2, y, iz, seed), tx);
}
return fxn_catmull(rows[0], rows[1], rows[2], rows[3], ty);
}
// noise3::typed_noise.
fn fxn_typed_noise(x: f32, y: f32, z: f32, seed: u32, nt: u32) -> f32 {
if (nt == 2u) {
return value_noise(x, y, z, seed);
}
let x0 = floor(x);
let y0 = floor(y);
let z0 = floor(z);
let tx = x - x0;
let ty = y - y0;
let fz = fade(z - z0);
let a = fxn_slice(i32(x0), i32(y0), i32(z0), tx, ty, seed, nt);
let b = fxn_slice(i32(x0), i32(y0), i32(z0) + 1, tx, ty, seed, nt);
return a + (b - a) * fz;
}
// noise3::shape.
fn fxn_shape(kind: u32, n: f32) -> f32 {
let s = n * 2.0 - 1.0;
switch kind {
case 1u: {
return 1.0 - s * s;
}
case 2u: {
return 1.0 - abs(s);
}
case 3u: {
let t = 1.0 - abs(s);
return t * t;
}
case 8u: {
let t = clamp(abs(s) / 0.12, 0.0, 1.0);
return 1.0 - t * t;
}
default: {
return n;
}
}
}
// Fractal::layer. `o` = octave data base index.
fn fxn_layer(u: f32, v: f32, o: u32, z: f32, seed: u32) -> f32 {
let nt = P.u[0].y;
if (P.u[1].y != 0u) {
let t = P.f[2].x;
let c = P.f[2].y;
let w = P.f[2].z;
let a = fxn_typed_noise(u, v, t + z, seed, nt);
let b = fxn_typed_noise(u, v, t - c + z, seed, nt);
return a * (1.0 - w) + b * w;
}
return fxn_typed_noise(u, v, data[o + 7u] + z, seed, nt);
}
// u[0] = (kind, noise type, octaves, seed); u[1] = (sub rotation, cycle, invert, overflow);
// u[2].x = blend mode (BlendMode::ALL index, 255 = None); f[0] = (sin, cos, size x, size y);
// f[1] = (contrast, brightness, opacity, blend with original); f[2] = (cycle t, cycle, t / cycle,
// norm); data = 10 floats per octave (noise3::FractalGpu).
@compute @workgroup_size(16, 16)
fn fxn_fractal(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let kind = P.u[0].x;
let n_oct = P.u[0].z;
let sr = P.f[0].x;
let cr = P.f[0].y;
let x = f32(p.x) + 0.5;
let y = f32(p.y) + 0.5;
var sum = 0.0;
var best = 0.0;
for (var oi = 0u; oi < n_oct; oi++) {
let o = oi * 10u;
let dx = x - data[o];
let dy = y - data[o + 1u];
var u = (dx * cr + dy * sr) / P.f[0].z;
var v = (-dx * sr + dy * cr) / P.f[0].w;
if (P.u[1].x != 0u) {
let s = data[o + 2u];
let c = data[o + 3u];
let u2 = u * c + v * s;
v = -u * s + v * c;
u = u2;
}
let f = data[o + 4u];
u = u * f;
v = v * f;
let z = data[o + 8u];
let seed = P.u[0].w + oi;
if (kind >= 4u && kind <= 6u) {
let k = data[o + 9u];
let a = fxn_layer(u * 0.5, v * 0.5, o, z + 3.1, seed ^ 0x55u) - 0.5;
let bb = fxn_layer(u * 0.5, v * 0.5, o, z + 5.7, seed ^ 0xaau) - 0.5;
if (kind == 5u) {
let s = sin(a * 3.0);
let c = cos(a * 3.0);
let u2 = u * c - v * s;
v = u * s + v * c;
u = u2;
} else {
u += a * 1.5 * k;
v += bb * 1.5 * k;
}
}
let n = fxn_shape(kind, fxn_layer(u, v, o, z, seed));
let amp = data[o + 5u];
if (kind == 7u) {
best = max(best, n * amp);
}
sum += n * amp * data[o + 6u];
}
var raw = sum / max(P.f[2].w, 1e-6);
if (kind == 7u) {
raw = best;
}
var val = (raw - 0.5) * P.f[1].x + 0.5 + P.f[1].y;
let ov = P.u[1].w;
if (ov == 0u) {
val = clamp(val, 0.0, 1.0);
} else if (ov == 1u) {
val = clamp(0.5 + 0.5 * tanh(2.0 * (val - 0.5)) / tanh(1.0), 0.0, 1.0);
} else if (ov == 2u) {
let t = val - 2.0 * floor(val / 2.0);
val = select(t, 2.0 - t, t > 1.0);
}
if (P.u[1].z != 0u) {
val = 1.0 - val;
}
let op = P.f[1].z;
let s = vec4<f32>(vec3<f32>(val * op), op);
let orig = textureLoad(src, p, 0);
var r = s;
if (P.u[2].x != 255u) {
r = blend_pixel(P.u[2].x, orig, s, 0.5);
}
let bo = P.f[1].w;
textureStore(out, p, orig * bo + r * (1.0 - bo));
}
// ---------------------------------------------------------------- Noise Alpha / Noise HLS (noise.rs)
// noise::phased_cycle with the phase's two seeds and smoothed fraction resolved on the CPU.
fn fxn_phased(x: u32, y: u32, sa: u32, sb: u32, t: f32) -> f32 {
let a = hash_noise(x, y, sa);
let b = hash_noise(x, y, sb);
return a + (b - a) * t;
}
// u[0] = (squared, original alpha mode, overflow, seed a); u[1].x = seed b; f[0] = (amount, t).
@compute @workgroup_size(16, 16)
fn fxn_noise_alpha(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let k = P.f[0].x;
var n = fxn_phased(u32(p.x), u32(p.y), P.u[0].w, P.u[1].x, P.f[0].y);
if (P.u[0].x != 0u) {
n = n * n;
}
let s = fxn_unpremul(textureLoad(src, p, 0));
let a = s.w;
var na: f32;
switch P.u[0].y {
case 0u: {
na = a + n * k;
}
case 1u: {
na = select(0.0, a + n * k, a > 0.0);
}
case 2u: {
na = a * (1.0 + (2.0 * n - 1.0) * k);
}
default: {
na = a + (2.0 * n - 1.0) * k * 4.0 * a * (1.0 - a);
}
}
if (P.u[0].z == 1u) {
let t = na - 2.0 * floor(na / 2.0);
na = select(t, 2.0 - t, t > 1.0);
} else if (P.u[0].z == 2u && !(na >= 0.0 && na <= 1.0)) {
na = na - floor(na);
}
na = clamp(na, 0.0, 1.0);
textureStore(out, p, vec4<f32>(s.xyz * na, na));
}
// Noise HLS: u[0] = (noise kind, seed); u[1], u[2].xy = per channel (hue, lightness,
// saturation) the phase's two seeds; f[0] = (hue, lightness, saturation, grain size); f[1].x =
// phase (Grain), f[1].y = smoothed fraction.
fn fxn_hls_seed(i: u32) -> u32 {
if (i < 4u) {
return P.u[1][i];
}
return P.u[2][i - 4u];
}
fn fxn_hls_n(x: u32, y: u32, k: u32) -> f32 {
let s = P.u[0].y + k * 7919u;
switch P.u[0].x {
case 2u: {
let gs = P.f[0].w;
return clamp((value_noise(f32(x) / gs, f32(y) / gs, P.f[1].x, s) - 0.5) * 3.0, -1.0, 1.0);
}
case 1u: {
let v = fxn_phased(x, y, fxn_hls_seed(2u * k), fxn_hls_seed(2u * k + 1u), P.f[1].y) * 2.0 - 1.0;
return v * abs(v);
}
default: {
return fxn_phased(x, y, fxn_hls_seed(2u * k), fxn_hls_seed(2u * k + 1u), P.f[1].y) * 2.0 - 1.0;
}
}
}
@compute @workgroup_size(16, 16)
fn fxn_noise_hls(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let px = textureLoad(src, p, 0);
let s0 = fxn_unpremul(px);
let a = s0.w;
if (a <= 0.0) {
textureStore(out, p, px);
return;
}
let x = u32(p.x);
let y = u32(p.y);
var hsl = rgb_to_hsl(s0.xyz);
if (P.f[0].x != 0.0) {
hsl.x = fract_euclid(hsl.x + fxn_hls_n(x, y, 0u) * P.f[0].x * 0.5);
}
if (P.f[0].y != 0.0) {
hsl.z = clamp(hsl.z + fxn_hls_n(x, y, 1u) * P.f[0].y, 0.0, 1.0);
}
if (P.f[0].z != 0.0) {
hsl.y = clamp(hsl.y + fxn_hls_n(x, y, 2u) * P.f[0].z, 0.0, 1.0);
}
let c = hsl_to_rgb(hsl.x, hsl.y, hsl.z);
textureStore(out, p, vec4<f32>(c * a, a));
}
// ---------------------------------------------------------------- Remove Grain (noise::remove_grain)
// The guide (util::guided_filter's g): the luminance of the premultiplied channels in every
// channel (Multichannel), or the channels themselves (u[0].x, Single Channel).
@compute @workgroup_size(16, 16)
fn fxn_gf_guide(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let v = textureLoad(src, p, 0);
if (P.u[0].x != 0u) {
textureStore(out, p, v);
} else {
textureStore(out, p, vec4<f32>(luminance(v.xyz)));
}
}
// Guided filter arithmetic, S = src, A = aux, D = `data` (RGBA f32 rows of u[0].y pixels):
// u[0].x = 0: S − A·D, 1: S − A·A, 2: S / (A + f[0].x), 3: A − S·D, 4: S·D + A, 5: S·A, 6: S·S.
@compute @workgroup_size(16, 16)
fn fxn_gf(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let s = textureLoad(src, p, 0);
let a = textureLoad(aux, p, 0);
var d = vec4<f32>(0.0);
let mode = P.u[0].x;
if (mode == 0u || mode == 3u || mode == 4u) {
let i = (u32(p.y) * P.u[0].y + u32(p.x)) * 4u;
d = vec4<f32>(data[i], data[i + 1u], data[i + 2u], data[i + 3u]);
}
var o: vec4<f32>;
switch mode {
case 0u: {
o = s - a * d;
}
case 1u: {
o = s - a * a;
}
case 2u: {
o = s / (a + P.f[0].x);
}
case 3u: {
o = a - s * d;
}
case 4u: {
o = s * d + a;
}
case 5u: {
o = s * a;
}
default: {
o = s * s;
}
}
textureStore(out, p, o);
}
// Texture: f + (o − f) × f[0].x on colour (src = filtered, aux = original).
@compute @workgroup_size(16, 16)
fn fxn_rg_texture(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let f = textureLoad(src, p, 0);
let o = textureLoad(aux, p, 0);
textureStore(out, p, vec4<f32>(f.xyz + (o.xyz - f.xyz) * P.f[0].x, f.w));
}
// Unsharp Mask on the planes: src = channels, aux = their blur; f[0] = (amount, threshold).
@compute @workgroup_size(16, 16)
fn fxn_rg_unsharp(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
var v = textureLoad(src, p, 0);
let l = textureLoad(aux, p, 0);
for (var c = 0; c < 3; c++) {
if (abs(v[c] - l[c]) > P.f[0].y) {
v[c] = v[c] + (v[c] - l[c]) * P.f[0].x;
}
}
textureStore(out, p, v);
}
// Remove Grain's result: colour clamped to 0..alpha (alpha from aux = the original); with
// u[0].x the Preview viewing mode (noise::preview_compose): the original (aux) outside the
// region f[0] = (x0, x1, y0, y1), the box outline in f[1] where u[0].y.
@compute @workgroup_size(16, 16)
fn fxn_rg_out(@builtin(global_invocation_id) gid: vec3<u32>) {
let p = fxn_pixel(gid);
if (p.x < 0) {
return;
}
let o = textureLoad(aux, p, 0);
let a = o.w;
var v = vec4<f32>(clamp(textureLoad(src, p, 0).xyz, vec3<f32>(0.0), vec3<f32>(max(a, 0.0))), a);
if (P.u[0].x != 0u) {
let x = f32(p.x) + 0.5;
let y = f32(p.y) + 0.5;
let r = P.f[0];
let inside = x >= r.x && x < r.y && y >= r.z && y < r.w;
if (!inside) {
v = o;
}
if (P.u[0].y != 0u) {
let nx = abs(x - r.x) < 0.75 || abs(x - r.y) < 0.75;
let ny = abs(y - r.z) < 0.75 || abs(y - r.w) < 0.75;
if ((nx && y >= r.z - 0.75 && y < r.w + 0.75) || (ny && x >= r.x - 0.75 && x < r.y + 0.75)) {
v = vec4<f32>(P.f[1].xyz, 1.0);
}
}
}
textureStore(out, p, v);
}