Files
amane-config/src/ring.wgsl
T
server2 3de3709871 Performance: surfaces, the candle ring and the equalizer curve as shaders, the pill's bars as quads; tools/perf.sh
A playing pill cost the iGPU about 2.9 ms and the CPU 3.7 ms a frame, 60 frames a
second: two vello passes over the whole island window for the surface's shadow and
gradients (vello keeps no gradient between frames) and one for the bars. Now 0.4 ms
GPU and 1.2 ms CPU. The open card went from 5.2 to 2.6 ms GPU and 6.2 to 3.4 ms CPU
a frame. surface.wgsl draws every design's shadow, fill and inner gradient in one
pass, with vello's own blurred-rectangle formula
2026-10-19 13:00:46 +02:00

91 lines
3.0 KiB
WebGPU Shading Language

// the ring of candles around the cd, one candle per angle step, each a line from the
// cover's edge outward as far as its level reaches, with a glow under the loud ones and
// a flame at the tip of the loudest. island.rs fills the values:
// 0: centre, inner radius, candles, levels given
// 1: reach at full level, glow width, body width, flame width
// 2: glow colour, 3: body colour, 4: flame colour (plain, 0 to 1)
// 5-15: levels for half the ring (the other half mirrors it), already eased, 0 to 1
const TAU: f32 = 6.28318530718;
fn level(step: u32, half: u32) -> f32 {
let count = u32(values[0].w);
if count == 0u {
return 0.0;
}
let index = min(step * count / max(half, 1u), count - 1u);
return values[5u + index / 4u][index % 4u];
}
// distance from p to the line from `start` to `end` along `direction`, out from the centre
fn line(p: vec2<f32>, direction: vec2<f32>, start: f32, end: f32) -> f32 {
let along = dot(p, direction);
let across = dot(p, vec2<f32>(-direction.y, direction.x));
return length(vec2<f32>(across, along - clamp(along, start, end)));
}
// how much of a pixel a round-capped stroke `width` wide covers, at distance d from its line
fn stroke(d: f32, width: f32, pixel: f32) -> f32 {
return clamp((width / 2.0 - d) / pixel + 0.5, 0.0, 1.0);
}
fn over(top: vec4<f32>, under: vec4<f32>) -> vec4<f32> {
return top + under * (1.0 - top.a);
}
@fragment
fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> {
let ring = values[0];
let widths = values[1];
let candles = u32(ring.z);
let half = candles / 2u;
let inner = ring.y;
let p = uv * size - vec2<f32>(ring.x);
let pixel = max(fwidth(p.x), 1e-4);
// the nearest candle by angle, 0 at the top going clockwise, and its neighbours
let angle = atan2(p.x, -p.y);
let nearest = i32(round(select(angle, angle + TAU, angle < 0.0) / TAU * f32(candles)));
var glow = 0.0;
var body = 0.0;
var flame = 0.0;
for (var offset = -1; offset <= 1; offset++) {
let index = u32((nearest + offset + i32(candles)) % i32(candles));
let step = select(candles - 1u - index, index, index < half);
let loud = level(step, half);
let a = f32(index) / f32(candles) * TAU;
let direction = vec2<f32>(sin(a), -cos(a));
let reach = inner + 3.0 + loud * widths.x;
let d = line(p, direction, inner, reach);
body = max(body, stroke(d, widths.z, pixel));
if loud > 0.25 {
glow = max(glow, stroke(d, widths.y, pixel));
}
if loud > 0.45 {
flame = max(flame, stroke(line(p, direction, reach - 0.5, reach), widths.w, pixel));
}
}
var color = vec4<f32>(values[2].rgb * values[2].a, values[2].a) * glow;
color = over(vec4<f32>(values[3].rgb * values[3].a, values[3].a) * body, color);
color = over(vec4<f32>(values[4].rgb * values[4].a, values[4].a) * flame, color);
if color.a <= 0.0 {
return vec4<f32>(0.0);
}
return vec4<f32>(color.rgb / color.a, color.a);
}