260 lines
7.0 KiB
WebGPU Shading Language
260 lines
7.0 KiB
WebGPU Shading Language
// synthesizes triangle and line vertices per-bin from a storage buffer, with mirrors as an instance axis.
|
|
|
|
struct Globals {
|
|
bounds: vec2<f32>, // full canvas in pixels
|
|
base: vec2<f32>, // building rect in pixels, 0.55w by 0.5h when mirrored
|
|
num_bins: u32,
|
|
num_channels: u32,
|
|
flags: u32, // 1=glass, 2=mirrored, 4=inverted, 8=stereo
|
|
fade_bins: u32, // count of tail bins fading toward zero alpha when mirrored
|
|
hue_param: f32,
|
|
contrast: f32,
|
|
brightness: f32,
|
|
_pad0: f32,
|
|
unified_hue: f32,
|
|
unified_sat: f32,
|
|
unified_val: f32,
|
|
_pad1: f32,
|
|
};
|
|
|
|
struct Bin {
|
|
log_x: f32, // 0..1 along the log-frequency axis
|
|
visual_norm: f32, // smoothed dB on a 0..1 scale
|
|
primary_norm: f32, // primary dB on a 0..1 scale
|
|
bright_mod: f32,
|
|
alpha_mod: f32,
|
|
hue: f32,
|
|
sat: f32,
|
|
val: f32,
|
|
splash: f32, // 0..1 suppressed-energy color splash
|
|
};
|
|
|
|
// 5/12 hue-wheel rotation per full splash, cycling all 12 wheel segments.
|
|
const SPLASH_HUE_OFFSET: f32 = 0.4166667;
|
|
|
|
@group(0) @binding(0) var<uniform> globals: Globals;
|
|
@group(0) @binding(1) var<storage, read> bins: array<Bin>;
|
|
|
|
struct VertexOut {
|
|
@builtin(position) clip_position: vec4<f32>,
|
|
@location(0) color: vec4<f32>,
|
|
};
|
|
|
|
fn flag(bit: u32) -> bool {
|
|
return (globals.flags & bit) != 0u;
|
|
}
|
|
|
|
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> vec3<f32> {
|
|
let hh = fract(fract(h) + 1.0) * 6.0;
|
|
let i = floor(hh);
|
|
let f = hh - i;
|
|
let p = v * (1.0 - s);
|
|
let q = v * (1.0 - s * f);
|
|
let t = v * (1.0 - s * (1.0 - f));
|
|
let ii = i32(i) % 6;
|
|
if (ii == 0) { return vec3<f32>(v, t, p); }
|
|
if (ii == 1) { return vec3<f32>(q, v, p); }
|
|
if (ii == 2) { return vec3<f32>(p, v, t); }
|
|
if (ii == 3) { return vec3<f32>(p, q, v); }
|
|
if (ii == 4) { return vec3<f32>(t, p, v); }
|
|
return vec3<f32>(v, p, q);
|
|
}
|
|
|
|
fn final_brightness(b: Bin) -> f32 {
|
|
let base_b = sqrt(b.primary_norm);
|
|
let bm = b.bright_mod;
|
|
var b_mult: f32;
|
|
if (bm >= 0.0) {
|
|
b_mult = 1.0 + bm;
|
|
} else {
|
|
b_mult = 1.0 / (1.0 - bm * 2.0);
|
|
}
|
|
return clamp(base_b * b_mult * globals.brightness, 0.0, 1.0);
|
|
}
|
|
|
|
fn alpha_for(b: Bin, fade: f32) -> f32 {
|
|
let am = b.alpha_mod;
|
|
var a_mult: f32;
|
|
if (am >= 0.0) {
|
|
a_mult = 1.0 + am * 0.5;
|
|
} else {
|
|
a_mult = 1.0 + am;
|
|
}
|
|
a_mult = max(a_mult, 0.1);
|
|
var a = 0.4 + (b.primary_norm - 0.5) * globals.contrast;
|
|
a = clamp(a * a_mult, 0.0, 1.0);
|
|
return a * fade;
|
|
}
|
|
|
|
fn dyn_rgb(b: Bin) -> vec3<f32> {
|
|
let fb = final_brightness(b);
|
|
let hue = fract(b.hue + SPLASH_HUE_OFFSET * b.splash);
|
|
let s = clamp((b.sat + 0.4 * b.splash) * globals.hue_param, 0.0, 1.0);
|
|
let v = clamp((b.val + 0.4 * b.splash) * fb, 0.0, 1.0);
|
|
return hsv_to_rgb(hue, s, v);
|
|
}
|
|
|
|
fn fill_rgb(b: Bin) -> vec3<f32> {
|
|
if (flag(1u)) {
|
|
let fb = final_brightness(b);
|
|
let hue = fract(globals.unified_hue + SPLASH_HUE_OFFSET * b.splash);
|
|
let s = clamp(globals.unified_sat + 0.4 * b.splash, 0.0, 1.0);
|
|
let v = clamp((globals.unified_val + 0.4 * b.splash) * fb, 0.0, 1.0);
|
|
return hsv_to_rgb(hue, s, v);
|
|
}
|
|
return dyn_rgb(b);
|
|
}
|
|
|
|
fn channel_offset(rgb: vec3<f32>, ch: u32) -> vec3<f32> {
|
|
if (ch == 1u && flag(8u)) {
|
|
let off = 40.0 / 255.0;
|
|
return vec3<f32>(
|
|
max(rgb.x - off, 0.0),
|
|
max(rgb.y - off, 0.0),
|
|
min(rgb.z + off, 1.0),
|
|
);
|
|
}
|
|
return rgb;
|
|
}
|
|
|
|
fn fade_factor(seg: u32) -> f32 {
|
|
if (!flag(2u)) { return 1.0; }
|
|
let from_end = i32(globals.num_bins) - 2 - i32(seg);
|
|
if (from_end < i32(globals.fade_bins)) {
|
|
var f = f32(from_end + 1) / f32(globals.fade_bins + 1u);
|
|
f = f * f;
|
|
return max(f, 0.0);
|
|
}
|
|
return 1.0;
|
|
}
|
|
|
|
fn pixel_to_clip(p: vec2<f32>) -> vec4<f32> {
|
|
let nx = (p.x / max(globals.bounds.x, 1.0)) * 2.0 - 1.0;
|
|
let ny = 1.0 - (p.y / max(globals.bounds.y, 1.0)) * 2.0;
|
|
return vec4<f32>(nx, ny, 0.0, 1.0);
|
|
}
|
|
|
|
fn mirror_xform(iid: u32, p: vec2<f32>) -> vec2<f32> {
|
|
var sx: f32 = 1.0;
|
|
var sy: f32 = 1.0;
|
|
var tx: f32 = 0.0;
|
|
var ty: f32 = 0.0;
|
|
if (iid == 1u) {
|
|
sx = -1.0; tx = globals.bounds.x;
|
|
} else if (iid == 2u) {
|
|
sy = -1.0; ty = globals.bounds.y;
|
|
} else if (iid == 3u) {
|
|
sx = -1.0; sy = -1.0;
|
|
tx = globals.bounds.x; ty = globals.bounds.y;
|
|
}
|
|
return vec2<f32>(p.x * sx + tx, p.y * sy + ty);
|
|
}
|
|
|
|
@vertex
|
|
fn vs_fill(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VertexOut {
|
|
let nb = globals.num_bins;
|
|
let segs = max(nb, 1u) - 1u;
|
|
let per_ch = segs * 6u;
|
|
let ch = vid / per_ch;
|
|
let in_ch = vid % per_ch;
|
|
let seg = in_ch / 6u;
|
|
let corner = in_ch % 6u;
|
|
|
|
var i = seg;
|
|
var j = seg + 1u;
|
|
if (flag(4u)) {
|
|
i = nb - 1u - seg;
|
|
j = nb - 2u - seg;
|
|
}
|
|
|
|
let base = ch * nb;
|
|
let bi = bins[base + i];
|
|
let bj = bins[base + j];
|
|
|
|
let w = globals.base.x;
|
|
let h = globals.base.y;
|
|
let x1 = bi.log_x * w;
|
|
let x2 = bj.log_x * w;
|
|
let y1 = h - bi.visual_norm * h;
|
|
let y2 = h - bj.visual_norm * h;
|
|
let anchor_y = h;
|
|
|
|
var p: vec2<f32>;
|
|
switch corner {
|
|
case 0u: { p = vec2<f32>(x1, anchor_y); }
|
|
case 1u: { p = vec2<f32>(x1, y1); }
|
|
case 2u: { p = vec2<f32>(x2, y2); }
|
|
case 3u: { p = vec2<f32>(x1, anchor_y); }
|
|
case 4u: { p = vec2<f32>(x2, y2); }
|
|
default: { p = vec2<f32>(x2, anchor_y); }
|
|
}
|
|
let pw = mirror_xform(iid, p);
|
|
|
|
var rgb = fill_rgb(bi);
|
|
rgb = channel_offset(rgb, ch);
|
|
let a = alpha_for(bi, fade_factor(seg));
|
|
|
|
var out: VertexOut;
|
|
out.clip_position = pixel_to_clip(pw);
|
|
out.color = vec4<f32>(rgb, a);
|
|
return out;
|
|
}
|
|
|
|
@vertex
|
|
fn vs_line(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VertexOut {
|
|
let nb = globals.num_bins;
|
|
let per_ch = nb * 2u;
|
|
let ch = vid / per_ch;
|
|
let in_ch = vid % per_ch;
|
|
let seg = in_ch / 2u;
|
|
let endpoint = in_ch % 2u;
|
|
|
|
var i = seg;
|
|
if (flag(4u)) {
|
|
i = nb - 1u - seg;
|
|
}
|
|
let bi = bins[ch * nb + i];
|
|
|
|
let w = globals.base.x;
|
|
let h = globals.base.y;
|
|
let x = bi.log_x * w;
|
|
let y_top = h - bi.visual_norm * h;
|
|
let anchor_y = h;
|
|
var p: vec2<f32>;
|
|
if (endpoint == 0u) {
|
|
p = vec2<f32>(x, anchor_y);
|
|
} else {
|
|
p = vec2<f32>(x, y_top);
|
|
}
|
|
let pw = mirror_xform(iid, p);
|
|
|
|
var rgb = dyn_rgb(bi);
|
|
rgb = channel_offset(rgb, ch);
|
|
var a = alpha_for(bi, fade_factor(seg));
|
|
a = min(a, 0.9);
|
|
|
|
var out: VertexOut;
|
|
out.clip_position = pixel_to_clip(pw);
|
|
out.color = vec4<f32>(rgb, a);
|
|
return out;
|
|
}
|
|
|
|
// cepstrum line strip in pixel space.
|
|
struct CepIn {
|
|
@location(0) position: vec2<f32>,
|
|
@location(1) color: vec4<f32>,
|
|
};
|
|
|
|
@vertex
|
|
fn vs_cep(in: CepIn) -> VertexOut {
|
|
var out: VertexOut;
|
|
out.clip_position = pixel_to_clip(in.position);
|
|
out.color = in.color;
|
|
return out;
|
|
}
|
|
|
|
@fragment
|
|
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|
return in.color;
|
|
}
|