Skip to main content

pfx_live/flat/
shader.rs

1use pfx_post::look::shader::COLOUR_WGSL;
2
3pub const SDF_WGSL: &str = r#"
4const FLAT_RECT: u32 = 0u;
5const FLAT_CIRCLE: u32 = 1u;
6const FLAT_RING: u32 = 2u;
7const FLAT_ARC: u32 = 3u;
8const FLAT_ICON_FILL: u32 = 4u;
9const FLAT_ICON_LINE: u32 = 5u;
10const FLAT_TAU: f32 = 6.283185307179586;
11
12fn flat_sign(value: f32) -> f32 {
13    return select(-1.0, 1.0, value >= 0.0);
14}
15
16fn flat_direction(p: vec2f) -> vec2f {
17    let length_p = length(p);
18    return select(vec2f(1.0, 0.0), p / length_p, length_p > 0.0);
19}
20
21fn flat_corner(p: vec2f, radii: vec4f) -> f32 {
22    let right = p.x >= 0.0;
23    if (p.y >= 0.0) {
24        return select(radii.w, radii.z, right);
25    }
26    return select(radii.x, radii.y, right);
27}
28
29fn flat_rect(p: vec2f, half: vec2f, radii: vec4f) -> vec3f {
30    let radius = flat_corner(p, radii);
31    let s = vec2f(flat_sign(p.x), flat_sign(p.y));
32    let q = abs(p) - half + vec2f(radius);
33    if (q.x > 0.0 && q.y > 0.0) {
34        let l = length(q);
35        return vec3f(l - radius, s * q / l);
36    }
37    if (q.x > q.y) {
38        return vec3f(q.x - radius, s.x, 0.0);
39    }
40    return vec3f(q.y - radius, 0.0, s.y);
41}
42
43fn flat_circle(p: vec2f, radius: f32) -> vec3f {
44    return vec3f(length(p) - radius, flat_direction(p));
45}
46
47fn flat_ring(p: vec2f, radius: f32, width: f32) -> vec3f {
48    let offset = length(p) - radius;
49    return vec3f(abs(offset) - width * 0.5, flat_sign(offset) * flat_direction(p));
50}
51
52fn flat_turn(p: vec2f, angle: f32) -> vec2f {
53    let c = cos(angle);
54    let s = sin(angle);
55    return vec2f(c * p.x - s * p.y, s * p.x + c * p.y);
56}
57
58fn flat_arc(p: vec2f, radius: f32, width: f32, start: f32, sweep: f32) -> vec3f {
59    if (sweep >= FLAT_TAU) {
60        return flat_ring(p, radius, width);
61    }
62    let half = sweep * 0.5;
63    let middle = start + half;
64    let q = flat_turn(p, -middle);
65    let angle = atan2(q.y, q.x);
66    if (abs(angle) <= half) {
67        return flat_ring(p, radius, width);
68    }
69    let end = radius * vec2f(cos(half), flat_sign(q.y) * sin(half));
70    let away = q - end;
71    return vec3f(length(away) - width * 0.5, flat_turn(flat_direction(away), middle));
72}
73
74struct FlatAlong {
75    s: f32,
76    tangent: vec2f,
77}
78
79fn flat_rect_along(p: vec2f, half: vec2f, radii: vec4f) -> FlatAlong {
80    let arc = radii * (FLAT_TAU * 0.25);
81    let top = (half.x - radii.x) + (half.x - radii.y);
82    let right = (half.y - radii.y) + (half.y - radii.z);
83    let bottom = (half.x - radii.w) + (half.x - radii.z);
84    let left = (half.y - radii.x) + (half.y - radii.w);
85    let at_right = top + arc.y;
86    let at_bottom_arc = at_right + right;
87    let at_bottom = at_bottom_arc + arc.z;
88    let at_left_arc = at_bottom + bottom;
89    let at_left = at_left_arc + arc.w;
90    let at_top_arc = at_left + left;
91    let radius = flat_corner(p, radii);
92    let inner = half - vec2f(radius);
93    let q = abs(p) - inner;
94    if (q.x > 0.0 && q.y > 0.0) {
95        let s = vec2f(flat_sign(p.x), flat_sign(p.y));
96        let centre = s * inner;
97        let offset = p - centre;
98        let rho = max(length(offset), 1e-6);
99        let tangent = vec2f(-offset.y, offset.x) / rho * (radius / rho);
100        var start = top;
101        var angle = atan2(offset.y, offset.x) + FLAT_TAU * 0.25;
102        if (s.x > 0.0 && s.y > 0.0) {
103            start = at_bottom_arc;
104            angle = atan2(offset.y, offset.x);
105        } else if (s.x < 0.0 && s.y > 0.0) {
106            start = at_left_arc;
107            angle = atan2(offset.y, offset.x) - FLAT_TAU * 0.25;
108        } else if (s.x < 0.0 && s.y < 0.0) {
109            start = at_top_arc;
110            angle = atan2(-offset.y, -offset.x);
111        }
112        return FlatAlong(start + radius * clamp(angle, 0.0, FLAT_TAU * 0.25), tangent);
113    }
114    if (q.x > q.y) {
115        if (p.x > 0.0) {
116            return FlatAlong(at_right + p.y + (half.y - radii.y), vec2f(0.0, 1.0));
117        }
118        return FlatAlong(at_left + (half.y - radii.w) - p.y, vec2f(0.0, -1.0));
119    }
120    if (p.y < 0.0) {
121        return FlatAlong(p.x + (half.x - radii.x), vec2f(1.0, 0.0));
122    }
123    return FlatAlong(at_bottom + (half.x - radii.z) - p.x, vec2f(-1.0, 0.0));
124}
125
126fn flat_circle_along(p: vec2f, radius: f32) -> FlatAlong {
127    let rho = max(length(p), 1e-6);
128    var angle = atan2(p.y, p.x);
129    if (angle < 0.0) {
130        angle += FLAT_TAU;
131    }
132    return FlatAlong(radius * angle, vec2f(-p.y, p.x) / rho * (radius / rho));
133}
134
135fn flat_dash(along: FlatAlong, on: f32, off: f32, phase: f32) -> vec3f {
136    let period = on + off;
137    let u = along.s + phase - on * 0.5;
138    let centred = u - period * floor(u / period + 0.5);
139    return vec3f(abs(centred) - on * 0.5, flat_sign(centred) * along.tangent);
140}
141
142fn flat_coverage(field: vec3f, inverse: mat2x2f) -> f32 {
143    let screen = vec2f(dot(inverse[0], field.yz), dot(inverse[1], field.yz));
144    return clamp(0.5 - field.x / max(length(screen), 1e-6), 0.0, 1.0);
145}
146
147fn flat_coverage_even(distance: f32, inverse: mat2x2f) -> f32 {
148    let unit = sqrt(abs(determinant(inverse)));
149    return clamp(0.5 - distance / max(unit, 1e-6), 0.0, 1.0);
150}
151
152const FLAT_SHADOW: u32 = 6u;
153const FLAT_SHADOW_STEPS: u32 = 8u;
154const FLAT_SHADOW_REACH: f32 = 4.0;
155
156fn flat_erf(x: f32) -> f32 {
157    let t = 1.0 / (1.0 + 0.3275911 * abs(x));
158    let y = 1.0 - (((((1.0614054 * t - 1.4531521) * t) + 1.4214138) * t - 0.28449674) * t + 0.2548296) * t * exp(-x * x);
159    return select(-y, y, x >= 0.0);
160}
161
162fn flat_shadow_reach(half: vec2f, radius: f32, y: f32) -> f32 {
163    let delta = min(half.y - radius - abs(y), 0.0);
164    return half.x - radius + sqrt(max(radius * radius - delta * delta, 0.0));
165}
166
167fn flat_shadow_row(x: f32, y: f32, half: vec2f, radii: vec4f, sigma: f32) -> f32 {
168    var left = radii.w;
169    var right = radii.z;
170    if (y < 0.0) {
171        left = radii.x;
172        right = radii.y;
173    }
174    let k = 0.70710678 / sigma;
175    return 0.5 * (flat_erf((x + flat_shadow_reach(half, left, y)) * k) - flat_erf((x - flat_shadow_reach(half, right, y)) * k));
176}
177
178fn flat_rounded_shadow(p: vec2f, half: vec2f, radii: vec4f, sigma: vec2f) -> f32 {
179    let k = 0.70710678 / sigma.y;
180    let low = max(-half.y, p.y - FLAT_SHADOW_REACH * sigma.y);
181    let high = min(half.y, p.y + FLAT_SHADOW_REACH * sigma.y);
182    if (high <= low) {
183        return 0.0;
184    }
185    let first = asin(clamp(low / half.y, -1.0, 1.0));
186    let last = asin(clamp(high / half.y, -1.0, 1.0));
187    let step = (last - first) / f32(FLAT_SHADOW_STEPS);
188    var total = 0.0;
189    var edge = flat_erf((p.y - low) * k);
190    for (var index = 0u; index < FLAT_SHADOW_STEPS; index += 1u) {
191        let a = first + step * f32(index);
192        let b = a + step;
193        let next = flat_erf((p.y - half.y * sin(b)) * k);
194        let weight = 0.5 * (edge - next);
195        edge = next;
196        total += weight * flat_shadow_row(p.x, half.y * sin((a + b) * 0.5), half, radii, sigma.x);
197    }
198    return total;
199}
200
201fn flat_stripe(u: f32, width: f32) -> vec2f {
202    let c = u - floor(u + 0.5);
203    return vec2f(abs(c) - width * 0.5, flat_sign(c));
204}
205
206fn flat_pattern_field(kind: u32, params: vec4f, p: vec2f) -> vec3f {
207    let scale = params.x;
208    let width = params.z;
209    let q = flat_turn(p, -params.y) / scale;
210    var d = 0.0;
211    var g = vec2f(1.0, 0.0);
212    if (kind == 1u) {
213        let s = flat_stripe(q.x, width);
214        d = s.x;
215        g = vec2f(s.y, 0.0);
216    } else if (kind == 2u) {
217        let f = q - floor(q + vec2f(0.5));
218        d = length(f) - width * 0.5;
219        g = flat_direction(f);
220    } else if (kind == 3u) {
221        let a = flat_stripe(q.x, width);
222        let b = flat_stripe(q.y, width);
223        if (a.x <= b.x) {
224            d = a.x;
225            g = vec2f(a.y, 0.0);
226        } else {
227            d = b.x;
228            g = vec2f(0.0, b.y);
229        }
230    } else {
231        let t = q.y - floor(q.y + 0.5);
232        let s = flat_stripe(q.x + abs(t), width);
233        d = s.x * 0.70710678;
234        g = vec2f(s.y, s.y * flat_sign(t)) * 0.70710678;
235    }
236    return vec3f(d * scale, flat_turn(g, params.y));
237}
238
239fn flat_pattern_over(base: vec4f, kind: u32, params: vec4f, packed: u32, p: vec2f, inverse: mat2x2f) -> vec4f {
240    let colour = unpack4x8unorm(packed);
241    let cover = flat_coverage(flat_pattern_field(kind, params, p), inverse);
242    let paint = vec4f(colour.rgb * colour.a, colour.a) * cover;
243    return paint + base * (1.0 - paint.a);
244}
245"#;
246
247pub const PASS_WGSL: &str = r#"
248const FLAT_FILL_SHIFT: u32 = 4u;
249const FLAT_DASHED: u32 = 256u;
250
251struct FlatView {
252    size: vec4f,
253    light: vec4f,
254    environment: vec4f,
255}
256
257@group(0) @binding(0) var<uniform> flat_view: FlatView;
258@group(0) @binding(1) var flat_icons: texture_2d<f32>;
259@group(0) @binding(2) var flat_icon_sampler: sampler;
260@group(0) @binding(3) var flat_environment: texture_2d<f32>;
261@group(0) @binding(4) var flat_environment_sampler: sampler;
262@group(0) @binding(5) var flat_gradients: texture_2d<f32>;
263@group(0) @binding(6) var flat_sprites: texture_2d<f32>;
264@group(0) @binding(7) var flat_sprite_sampler: sampler;
265
266struct FlatInstance {
267    @location(0) axes: vec4f,
268    @location(1) origin: vec4f,
269    @location(2) bounds: vec4f,
270    @location(3) shape: vec4f,
271    @location(4) stroke: vec4f,
272    @location(5) fill_top: vec4f,
273    @location(6) fill_bottom: vec4f,
274    @location(7) stroke_colour: vec4f,
275    @location(8) icon: vec4f,
276    @location(9) info: vec4u,
277    @location(10) material: vec4f,
278    @location(11) frame_x: vec4f,
279    @location(12) frame_y: vec4f,
280    @location(13) frame_z: vec4f,
281    @location(14) corners: vec4f,
282}
283
284struct FlatVaryings {
285    @builtin(position) position: vec4f,
286    @location(0) local: vec2f,
287    @location(1) @interpolate(flat) inverse: vec4f,
288    @location(2) @interpolate(flat) origin: vec4f,
289    @location(3) @interpolate(flat) shape: vec4f,
290    @location(4) @interpolate(flat) stroke: vec4f,
291    @location(5) @interpolate(flat) fill_top: vec4f,
292    @location(6) @interpolate(flat) fill_bottom: vec4f,
293    @location(7) @interpolate(flat) stroke_colour: vec4f,
294    @location(8) @interpolate(flat) icon: vec4f,
295    @location(9) @interpolate(flat) info: vec4u,
296    @location(10) @interpolate(flat) material: vec4f,
297    @location(11) @interpolate(flat) frame_x: vec4f,
298    @location(12) @interpolate(flat) frame_y: vec4f,
299    @location(13) @interpolate(flat) frame_z: vec4f,
300    @location(14) @interpolate(flat) corners: vec4f,
301}
302
303fn flat_place(instance: FlatInstance, index: u32) -> FlatVaryings {
304    let corner = vec2f(f32(index & 1u), f32((index >> 1u) & 1u)) * 2.0 - 1.0;
305    let axes = mat2x2f(instance.axes.xy, instance.axes.zw);
306    let inverse = mat2x2f(vec2f(axes[1].y, -axes[0].y), vec2f(-axes[1].x, axes[0].x))
307        * (1.0 / determinant(axes));
308    let margin = 1.5 * vec2f(
309        length(vec2f(inverse[0].x, inverse[1].x)),
310        length(vec2f(inverse[0].y, inverse[1].y)),
311    );
312    let local = instance.bounds.xy + corner * (instance.bounds.zw + margin);
313    let pixel = axes * local + instance.origin.xy;
314    var out: FlatVaryings;
315    out.position = vec4f(
316        pixel.x * flat_view.size.z * 2.0 - 1.0,
317        1.0 - pixel.y * flat_view.size.w * 2.0,
318        0.0,
319        1.0,
320    );
321    out.local = local;
322    out.inverse = vec4f(inverse[0], inverse[1]);
323    out.origin = instance.origin;
324    out.shape = instance.shape;
325    out.stroke = instance.stroke;
326    out.fill_top = instance.fill_top;
327    out.fill_bottom = instance.fill_bottom;
328    out.stroke_colour = instance.stroke_colour;
329    out.icon = instance.icon;
330    out.info = instance.info;
331    out.material = instance.material;
332    out.frame_x = instance.frame_x;
333    out.frame_y = instance.frame_y;
334    out.frame_z = instance.frame_z;
335    out.corners = instance.corners;
336    return out;
337}
338
339@vertex
340fn flat_vertex(instance: FlatInstance, @builtin(vertex_index) index: u32) -> FlatVaryings {
341    return flat_place(instance, index);
342}
343
344@vertex
345fn flat_id_vertex(instance: FlatInstance, @builtin(vertex_index) index: u32) -> FlatVaryings {
346    var out = flat_place(instance, index);
347    if ((instance.info.x & 15u) == FLAT_SHADOW) {
348        out.position = vec4f(2.0, 2.0, 2.0, 1.0);
349    }
350    return out;
351}
352
353struct FlatPaint {
354    colour: vec4f,
355    footprint: f32,
356}
357
358fn flat_field(v: FlatVaryings, kind: u32, p: vec2f) -> vec3f {
359    if (kind == FLAT_RECT) {
360        return flat_rect(p, v.shape.xy, v.corners);
361    } else if (kind == FLAT_CIRCLE) {
362        return flat_circle(p, v.shape.x);
363    } else if (kind == FLAT_RING) {
364        return flat_ring(p, v.shape.x, v.shape.y);
365    } else if (kind == FLAT_ARC) {
366        return flat_arc(p, v.shape.x, v.shape.y, v.shape.z, v.shape.w);
367    }
368    return vec3f(textureSampleLevel(flat_icons, flat_icon_sampler, v.icon.xy + p * v.icon.zw, 0.0).r, 0.0, 0.0);
369}
370
371fn flat_stroke_cover(v: FlatVaryings, kind: u32, p: vec2f, field: vec3f, inverse: mat2x2f) -> f32 {
372    let icon = kind == FLAT_ICON_FILL || kind == FLAT_ICON_LINE;
373    let band = vec3f(abs(field.x) - v.stroke.x * 0.5, flat_sign(field.x) * field.yz);
374    var cover = select(flat_coverage(band, inverse), flat_coverage_even(band.x, inverse), icon);
375    if ((v.info.x & FLAT_DASHED) != 0u) {
376        var along = flat_circle_along(p, v.shape.x);
377        if (kind == FLAT_RECT) {
378            along = flat_rect_along(p, v.shape.xy, v.corners);
379        }
380        cover *= flat_coverage(flat_dash(along, v.stroke.y, v.stroke.z, v.stroke.w), inverse);
381    }
382    return cover;
383}
384
385fn flat_gradient_offset(row: i32, index: u32) -> f32 {
386    return textureLoad(flat_gradients, vec2i(8 + i32(index / 4u), row), 0)[index % 4u];
387}
388
389fn flat_gradient_stop(row: i32, index: u32) -> vec4f {
390    return textureLoad(flat_gradients, vec2i(i32(index), row), 0);
391}
392
393fn flat_gradient(v: FlatVaryings, p: vec2f) -> vec4f {
394    let row = i32(v.fill_bottom.x);
395    let count = u32(v.fill_bottom.y);
396    let mode = u32(v.fill_bottom.z);
397    let oklab = (mode & 2u) != 0u;
398    let t = look_gradient_position(f32(mode & 1u), v.fill_top, p);
399    var colour = flat_gradient_stop(row, count - 1u);
400    if (t <= flat_gradient_offset(row, 0u)) {
401        colour = flat_gradient_stop(row, 0u);
402    } else {
403        for (var i = 0u; i + 1u < count; i += 1u) {
404            let a = flat_gradient_offset(row, i);
405            let b = flat_gradient_offset(row, i + 1u);
406            if (t < b) {
407                colour = look_stop_mix(flat_gradient_stop(row, i), flat_gradient_stop(row, i + 1u), (t - a) / (b - a), oklab);
408                break;
409            }
410        }
411    }
412    return vec4f(colour.rgb * colour.a, colour.a);
413}
414
415fn flat_fill_base(v: FlatVaryings, fill_mode: u32, p: vec2f) -> vec4f {
416    if (fill_mode == 3u) {
417        return flat_gradient(v, p);
418    }
419    if (fill_mode == 4u) {
420        let texel = textureSampleLevel(flat_sprites, flat_sprite_sampler, v.icon.xy + p * v.icon.zw, 0.0) * v.fill_top;
421        return vec4f(texel.rgb * texel.a, texel.a);
422    }
423    var t = 0.0;
424    if (fill_mode == 2u) {
425        t = clamp((p.y - v.origin.z) / (v.origin.w - v.origin.z), 0.0, 1.0);
426    }
427    let colour = mix(v.fill_top, v.fill_bottom, t);
428    return vec4f(colour.rgb * colour.a, colour.a);
429}
430
431fn flat_fill_alpha(v: FlatVaryings, fill_mode: u32) -> f32 {
432    return select(max(v.fill_top.a, v.fill_bottom.a), v.fill_bottom.a, fill_mode == 3u);
433}
434
435fn flat_paint(v: FlatVaryings) -> FlatPaint {
436    let p = v.local;
437    let inverse = mat2x2f(v.inverse.xy, v.inverse.zw);
438    let kind = v.info.x & 15u;
439    if (kind == FLAT_SHADOW) {
440        let cover = flat_rounded_shadow(p - v.icon.zw, v.shape.xy, v.corners, v.icon.xy);
441        return FlatPaint(vec4f(v.fill_top.rgb * v.fill_top.a, v.fill_top.a) * cover, 0.0);
442    }
443    let fill_mode = (v.info.x >> FLAT_FILL_SHIFT) & 15u;
444    let opacity = bitcast<f32>(v.info.w);
445    let icon = kind == FLAT_ICON_FILL || kind == FLAT_ICON_LINE;
446    let field = flat_field(v, kind, p);
447    var fill = vec4f(0.0);
448    var fill_cover = 0.0;
449    if (fill_mode != 0u) {
450        fill_cover = select(flat_coverage(field, inverse), flat_coverage_even(field.x, inverse), icon);
451        var base = flat_fill_base(v, fill_mode, p);
452        let pattern = (v.info.x >> 12u) & 7u;
453        if (pattern != 0u) {
454            base = flat_pattern_over(base, pattern, v.icon, v.info.z, p, inverse);
455        }
456        fill = base * fill_cover;
457        fill = flat_bevel_over(v, kind, p, field, inverse, fill, fill_cover);
458    }
459    var stroke = vec4f(0.0);
460    var stroke_cover = 0.0;
461    if (v.stroke.x > 0.0) {
462        stroke_cover = flat_stroke_cover(v, kind, p, field, inverse);
463        stroke = vec4f(v.stroke_colour.rgb * v.stroke_colour.a, v.stroke_colour.a) * stroke_cover;
464    }
465    let colour = (stroke + fill * (1.0 - stroke.a)) * opacity;
466    let fill_seen = select(0.0, fill_cover, fill_mode != 0u && flat_fill_alpha(v, fill_mode) > 0.0);
467    let stroke_seen = select(0.0, stroke_cover, v.stroke_colour.a > 0.0);
468    let footprint = select(0.0, max(fill_seen, stroke_seen), opacity > 0.0);
469    return FlatPaint(colour, footprint);
470}
471
472@fragment
473fn flat_colour(v: FlatVaryings) -> @location(0) vec4f {
474    return flat_paint(v).colour;
475}
476
477@fragment
478fn flat_id(v: FlatVaryings) -> @location(0) u32 {
479    if (flat_paint(v).footprint < 0.5) {
480        discard;
481    }
482    return v.info.y;
483}
484
485struct FlatLayerVaryings {
486    @builtin(position) position: vec4f,
487    @location(0) @interpolate(flat) opacity: f32,
488}
489
490@vertex
491fn flat_layer_vertex(@location(0) rect: vec4f, @location(1) opacity: vec4f, @builtin(vertex_index) index: u32) -> FlatLayerVaryings {
492    let corner = vec2f(f32(index & 1u), f32((index >> 1u) & 1u));
493    let pixel = mix(rect.xy, rect.zw, corner);
494    var out: FlatLayerVaryings;
495    out.position = vec4f(pixel.x * flat_view.size.z * 2.0 - 1.0, 1.0 - pixel.y * flat_view.size.w * 2.0, 0.0, 1.0);
496    out.opacity = opacity.x;
497    return out;
498}
499
500@group(1) @binding(0) var flat_layer: texture_2d<f32>;
501
502@fragment
503fn flat_layer_clear(v: FlatLayerVaryings) -> @location(0) vec4f {
504    return vec4f(0.0);
505}
506
507@fragment
508fn flat_layer_composite(v: FlatLayerVaryings) -> @location(0) vec4f {
509    return textureLoad(flat_layer, vec2i(v.position.xy), 0) * v.opacity;
510}
511"#;
512
513pub const LIT_WGSL: &str = r#"
514const FLAT_SLAB: u32 = 512u;
515const FLAT_LIT: u32 = 1024u;
516const FLAT_PI: f32 = 3.141592653589793;
517
518fn flat_decode(c: vec3f) -> vec3f {
519    return select(pow((c + 0.055) / 1.055, vec3f(2.4)), c / 12.92, c <= vec3f(0.04045));
520}
521
522fn flat_encode(c: vec3f) -> vec3f {
523    let x = clamp(c, vec3f(0.0), vec3f(1.0));
524    return select(1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, x * 12.92, x <= vec3f(0.0031308));
525}
526
527fn flat_footprint(p: vec2f, v: FlatVaryings, kind: u32) -> f32 {
528    if (kind == FLAT_RECT) {
529        return flat_rect(p, v.shape.xy, v.corners).x;
530    } else if (kind == FLAT_ICON_FILL || kind == FLAT_ICON_LINE) {
531        return textureSampleLevel(flat_icons, flat_icon_sampler, v.icon.xy + p * v.icon.zw, 0.0).r
532            - v.stroke.x * 0.5;
533    }
534    return length(p) - v.shape.x;
535}
536
537fn flat_footprint_field(p: vec2f, v: FlatVaryings, kind: u32, unit: f32) -> vec3f {
538    if (kind == FLAT_RECT) {
539        return flat_rect(p, v.shape.xy, v.corners);
540    } else if (kind == FLAT_CIRCLE) {
541        return flat_circle(p, v.shape.x);
542    }
543    let e = 0.5 * unit;
544    let d = flat_footprint(p, v, kind);
545    let gradient = vec2f(
546        flat_footprint(p + vec2f(e, 0.0), v, kind) - flat_footprint(p - vec2f(e, 0.0), v, kind),
547        flat_footprint(p + vec2f(0.0, e), v, kind) - flat_footprint(p - vec2f(0.0, e), v, kind),
548    );
549    return vec3f(d, flat_direction(gradient));
550}
551
552fn flat_slab(q: vec3f, v: FlatVaryings, kind: u32, thickness: f32, bevel: f32) -> f32 {
553    let w = vec2f(flat_footprint(q.xy, v, kind) + bevel, abs(q.z + thickness * 0.5) - thickness * 0.5 + bevel);
554    return min(max(w.x, w.y), 0.0) + length(max(w, vec2f(0.0))) - bevel;
555}
556
557fn flat_ggx(n: vec3f, l: vec3f, v: vec3f, roughness: f32) -> f32 {
558    let h = normalize(l + v);
559    let nl = max(dot(n, l), 0.0);
560    let nv = max(dot(n, v), 1e-4);
561    let nh = max(dot(n, h), 0.0);
562    let vh = max(dot(v, h), 0.0);
563    let a = roughness * roughness;
564    let a2 = a * a;
565    let denominator = nh * nh * (a2 - 1.0) + 1.0;
566    let d = a2 / (FLAT_PI * denominator * denominator);
567    let k = a * 0.5;
568    let g = nl / (nl * (1.0 - k) + k) * nv / (nv * (1.0 - k) + k);
569    let f = 0.04 + 0.96 * pow(1.0 - vh, 5.0);
570    return d * g * f / (4.0 * nv);
571}
572
573fn flat_reflected(direction: vec3f, roughness: f32) -> vec3f {
574    let u = 0.5 + atan2(direction.x, -direction.y) / (2.0 * FLAT_PI);
575    let w = acos(clamp(direction.z, -1.0, 1.0)) / FLAT_PI;
576    let level = roughness * max(flat_view.environment.x - 1.0, 0.0);
577    return textureSampleLevel(flat_environment, flat_environment_sampler, vec2f(u, w), level).rgb
578        * flat_view.environment.y;
579}
580
581fn flat_lit(v: FlatVaryings) -> FlatPaint {
582    let inverse = mat2x2f(v.inverse.xy, v.inverse.zw);
583    let kind = v.info.x & 15u;
584    let fill_mode = (v.info.x >> FLAT_FILL_SHIFT) & 15u;
585    let opacity = bitcast<f32>(v.info.w);
586    let icon = kind == FLAT_ICON_FILL || kind == FLAT_ICON_LINE;
587    let slab = (v.info.x & FLAT_SLAB) != 0u;
588    let unit = sqrt(abs(determinant(inverse)));
589    let local_from_world = mat3x3f(v.frame_x.xyz, v.frame_y.xyz, v.frame_z.xyz);
590    let world_from_local = transpose(local_from_world);
591    let down = -v.frame_z.xyz;
592    var hit = vec3f(v.local, 0.0);
593    var normal = vec3f(0.0, 0.0, 1.0);
594    var cover = 1.0;
595    var settled = false;
596    if (slab && down.z < -0.99999) {
597        let footprint = flat_footprint_field(v.local, v, kind, unit);
598        if (footprint.x >= 1.5 * unit) {
599            return FlatPaint(vec4f(0.0), 0.0);
600        }
601        let bevel = v.material.y;
602        let across = footprint.x + bevel;
603        if (across > 0.0) {
604            let x = min(across, bevel);
605            let height = sqrt(max(bevel * bevel - x * x, 0.0));
606            normal = normalize(vec3f(footprint.yz * max(x, 1e-6), height));
607            hit = vec3f(v.local, height - bevel);
608        }
609        cover = clamp(0.5 - footprint.x / unit, 0.0, 1.0);
610        settled = true;
611    }
612    if (slab && !settled) {
613        let thickness = v.material.x;
614        let bevel = v.material.y;
615        let reach = thickness / max(-down.z, 0.05) + 2.0 * unit;
616        let start = hit;
617        var s = 0.0;
618        var best = 1e9;
619        var closest = hit;
620        var found = false;
621        for (var step = 0u; step < 32u; step += 1u) {
622            let q = start + down * s;
623            let distance = flat_slab(q, v, kind, thickness, bevel);
624            if (distance < best) {
625                best = distance;
626                closest = q;
627            }
628            if (distance < 0.02 * unit) {
629                found = true;
630                break;
631            }
632            s += max(distance, 0.1 * unit);
633            if (s > reach) {
634                break;
635            }
636        }
637        hit = closest;
638        if (found) {
639            let probe = min(unit, thickness * 0.5);
640            let inside = flat_slab(hit + down * probe, v, kind, thickness, bevel);
641            cover = clamp(0.5 - inside / max(probe, 1e-6), 0.0, 1.0);
642        } else {
643            cover = clamp(0.5 - best / unit, 0.0, 1.0);
644        }
645        let e = 0.25 * unit;
646        normal = normalize(vec3f(
647            flat_slab(hit + vec3f(e, 0.0, 0.0), v, kind, thickness, bevel)
648                - flat_slab(hit - vec3f(e, 0.0, 0.0), v, kind, thickness, bevel),
649            flat_slab(hit + vec3f(0.0, e, 0.0), v, kind, thickness, bevel)
650                - flat_slab(hit - vec3f(0.0, e, 0.0), v, kind, thickness, bevel),
651            flat_slab(hit + vec3f(0.0, 0.0, e), v, kind, thickness, bevel)
652                - flat_slab(hit - vec3f(0.0, 0.0, e), v, kind, thickness, bevel),
653        ));
654    }
655    let p = hit.xy;
656    let field = flat_field(v, kind, p);
657    var fill = vec4f(0.0);
658    var fill_cover = 0.0;
659    if (fill_mode != 0u) {
660        fill_cover = select(select(flat_coverage(field, inverse), flat_coverage_even(field.x, inverse), icon), 1.0, slab);
661        var base = flat_fill_base(v, fill_mode, p);
662        let pattern = (v.info.x >> 12u) & 7u;
663        if (pattern != 0u) {
664            base = flat_pattern_over(base, pattern, v.icon, v.info.z, p, inverse);
665        }
666        fill = base * fill_cover;
667    }
668    var stroke = vec4f(0.0);
669    var stroke_cover = 0.0;
670    if (v.stroke.x > 0.0) {
671        stroke_cover = select(flat_stroke_cover(v, kind, p, field, inverse), 1.0, slab && kind == FLAT_ICON_LINE);
672        stroke = vec4f(v.stroke_colour.rgb * v.stroke_colour.a, v.stroke_colour.a) * stroke_cover;
673    }
674    let painted = stroke + fill * (1.0 - stroke.a);
675    let alpha = painted.a;
676    if (alpha <= 0.0) {
677        return FlatPaint(vec4f(0.0), 0.0);
678    }
679    let albedo = flat_decode(painted.rgb / alpha);
680    let world_normal = normalize(world_from_local * normal);
681    let key = flat_view.light.xyz;
682    let view = vec3f(0.0, 0.0, 1.0);
683    let lean = length(world_normal.xy);
684    var shade = 1.0;
685    if (slab) {
686        let ambient = flat_view.light.w;
687        let ratio = clamp(dot(world_normal, key) / max(key.z, 0.05), 0.0, 1.5);
688        shade = 1.0 + (1.0 - ambient) * (ratio - 1.0);
689    }
690    var linear = albedo * shade;
691    let roughness = v.frame_y.w;
692    if (lean > 0.0) {
693        linear += vec3f(lean * v.material.z * flat_ggx(world_normal, key, view, roughness) * max(dot(world_normal, key), 0.0));
694        if (v.frame_x.w > 0.0 && flat_view.environment.z > 0.0) {
695            let nv = max(dot(world_normal, view), 0.0);
696            let fresnel = 0.04 + 0.96 * pow(1.0 - nv, 5.0);
697            linear += lean * v.frame_x.w * fresnel * flat_reflected(reflect(-view, world_normal), roughness);
698        }
699    }
700    let coverage = alpha * cover * opacity;
701    var colour = painted * (cover * opacity);
702    if (any(linear != albedo)) {
703        colour += vec4f((flat_encode(linear) - flat_encode(albedo)) * coverage, 0.0);
704    }
705    let footprint = select(0.0, select(max(fill_cover, stroke_cover), cover, slab), opacity > 0.0);
706    return FlatPaint(colour, footprint);
707}
708
709fn flat_rich(v: FlatVaryings) -> FlatPaint {
710    var paint: FlatPaint;
711    if ((v.info.x & FLAT_BLUR) != 0u) {
712        paint = flat_blur(v);
713    } else if ((v.info.x & FLAT_LIT) != 0u) {
714        paint = flat_lit(v);
715    } else {
716        paint = flat_paint(v);
717    }
718    if ((v.info.x & FLAT_ADDITIVE) != 0u) {
719        paint.colour.a = 0.0;
720    }
721    return paint;
722}
723
724@fragment
725fn flat_colour_lit(v: FlatVaryings) -> @location(0) vec4f {
726    return flat_rich(v).colour;
727}
728
729@fragment
730fn flat_id_lit(v: FlatVaryings) -> @location(0) u32 {
731    if ((v.info.x & FLAT_UNPICKED) != 0u) {
732        discard;
733    }
734    var footprint = 0.0;
735    if ((v.info.x & FLAT_BLUR) != 0u) {
736        footprint = flat_paint(v).footprint;
737    } else {
738        footprint = flat_rich(v).footprint;
739    }
740    if (footprint < 0.5) {
741        discard;
742    }
743    return v.info.y;
744}
745"#;
746
747pub const FX_WGSL: &str = r#"
748const FLAT_BLUR: u32 = 2048u;
749const FLAT_ADDITIVE: u32 = 32768u;
750const FLAT_UNPICKED: u32 = 65536u;
751const FLAT_BLUR_ROWS: u32 = 4u;
752const FLAT_BLUR_STEPS: u32 = 48u;
753const FLAT_BLUR_FLOOR: f32 = 0.35;
754
755@vertex
756fn flat_id_vertex_rich(instance: FlatInstance, @builtin(vertex_index) index: u32) -> FlatVaryings {
757    var out = flat_place(instance, index);
758    if ((instance.info.x & 15u) == FLAT_SHADOW || (instance.info.x & FLAT_UNPICKED) != 0u) {
759        out.position = vec4f(2.0, 2.0, 2.0, 1.0);
760    }
761    return out;
762}
763
764fn flat_blur_distance(v: FlatVaryings, kind: u32, p: vec2f) -> f32 {
765    if (kind == FLAT_ICON_FILL || kind == FLAT_ICON_LINE) {
766        let c = clamp(p, v.shape.xy - v.shape.zw, v.shape.xy + v.shape.zw);
767        let outside = length(p - c);
768        if (outside > 0.0) {
769            return outside + 0.5 * v.stroke.x;
770        }
771        return textureSampleLevel(flat_icons, flat_icon_sampler, v.icon.xy + c * v.icon.zw, 0.0).r;
772    }
773    return flat_field(v, kind, p).x;
774}
775
776fn flat_blur_band(v: FlatVaryings, kind: u32, stroke: bool, p: vec2f) -> f32 {
777    let d = flat_blur_distance(v, kind, p);
778    if (!stroke) {
779        return d;
780    }
781    let band = abs(d) - v.stroke.x * 0.5;
782    if ((v.info.x & FLAT_DASHED) == 0u) {
783        return band;
784    }
785    var along = flat_circle_along(p, v.shape.x);
786    if (kind == FLAT_RECT) {
787        along = flat_rect_along(p, v.shape.xy, v.corners);
788    }
789    return max(band, flat_dash(along, v.stroke.y, v.stroke.z, v.stroke.w).x);
790}
791
792fn flat_blur_cdf(x: f32, span: f32) -> f32 {
793    let hi = 0.5 * (span + 1.0);
794    let lo = 0.5 * abs(span - 1.0);
795    let ramp = min(span, 1.0);
796    let height = 1.0 / max(span, 1.0);
797    if (x <= -hi) {
798        return 0.0;
799    }
800    if (x >= hi) {
801        return 1.0;
802    }
803    if (x < -lo) {
804        let u = x + hi;
805        return 0.5 * height / ramp * u * u;
806    }
807    if (x > lo) {
808        let u = hi - x;
809        return 1.0 - 0.5 * height / ramp * u * u;
810    }
811    return 0.5 * height * ramp + height * (x + lo);
812}
813
814fn flat_blur_line(v: FlatVaryings, kind: u32, stroke: bool, start: vec2f, along: vec2f, unit: f32, span: f32) -> f32 {
815    let hi = 0.5 * (span + 1.0);
816    let least = max(FLAT_BLUR_FLOOR, 2.0 * hi / f32(FLAT_BLUR_STEPS - 8u));
817    var x = -hi;
818    var s0 = flat_blur_band(v, kind, stroke, start + along * x) / unit;
819    var total = 0.0;
820    for (var step = 0u; step < FLAT_BLUR_STEPS; step += 1u) {
821        if (x >= hi) {
822            break;
823        }
824        let x1 = min(x + max(abs(s0), least), hi);
825        let s1 = flat_blur_band(v, kind, stroke, start + along * x1) / unit;
826        if (s0 <= 0.0 && s1 <= 0.0) {
827            total += flat_blur_cdf(x1, span) - flat_blur_cdf(x, span);
828        } else if (s0 <= 0.0) {
829            let r = x + (x1 - x) * s0 / (s0 - s1);
830            total += flat_blur_cdf(r, span) - flat_blur_cdf(x, span);
831        } else if (s1 <= 0.0) {
832            let r = x + (x1 - x) * s0 / (s0 - s1);
833            total += flat_blur_cdf(x1, span) - flat_blur_cdf(r, span);
834        }
835        x = x1;
836        s0 = s1;
837    }
838    if (x < hi && s0 <= 0.0) {
839        total += 1.0 - flat_blur_cdf(x, span);
840    }
841    return total;
842}
843
844fn flat_line_empty() -> vec2f {
845    return vec2f(1.0, -1.0);
846}
847
848fn flat_line_merge(a: vec2f, b: vec2f) -> vec2f {
849    if (a.x >= a.y) {
850        return b;
851    }
852    if (b.x >= b.y) {
853        return a;
854    }
855    return vec2f(min(a.x, b.x), max(a.y, b.y));
856}
857
858fn flat_line_disc(start: vec2f, along: vec2f, centre: vec2f, radius: f32) -> vec2f {
859    if (radius <= 0.0) {
860        return flat_line_empty();
861    }
862    let o = start - centre;
863    let aa = dot(along, along);
864    let b = dot(o, along);
865    let c = dot(o, o) - radius * radius;
866    let d = b * b - aa * c;
867    if (d <= 0.0) {
868        return flat_line_empty();
869    }
870    let root = sqrt(d);
871    return vec2f((-b - root) / aa, (-b + root) / aa);
872}
873
874fn flat_line_box(start: vec2f, along: vec2f, half: vec2f) -> vec2f {
875    if (half.x < 0.0 || half.y < 0.0) {
876        return flat_line_empty();
877    }
878    var low = -1e30;
879    var high = 1e30;
880    for (var axis = 0; axis < 2; axis += 1) {
881        if (abs(along[axis]) < 1e-12) {
882            if (abs(start[axis]) > half[axis]) {
883                return flat_line_empty();
884            }
885        } else {
886            let a = (-half[axis] - start[axis]) / along[axis];
887            let b = (half[axis] - start[axis]) / along[axis];
888            low = max(low, min(a, b));
889            high = min(high, max(a, b));
890        }
891    }
892    return vec2f(low, high);
893}
894
895fn flat_line_rounded(start: vec2f, along: vec2f, half: vec2f, radius: f32) -> vec2f {
896    if (half.x <= 0.0 || half.y <= 0.0) {
897        return flat_line_empty();
898    }
899    let r = clamp(radius, 0.0, min(half.x, half.y));
900    var hit = flat_line_box(start, along, vec2f(half.x, half.y - r));
901    hit = flat_line_merge(hit, flat_line_box(start, along, vec2f(half.x - r, half.y)));
902    if (r > 0.0) {
903        let c = half - vec2f(r);
904        hit = flat_line_merge(hit, flat_line_disc(start, along, c, r));
905        hit = flat_line_merge(hit, flat_line_disc(start, along, vec2f(-c.x, c.y), r));
906        hit = flat_line_merge(hit, flat_line_disc(start, along, vec2f(c.x, -c.y), r));
907        hit = flat_line_merge(hit, flat_line_disc(start, along, -c, r));
908    }
909    return hit;
910}
911
912fn flat_blur_weight(hit: vec2f, span: f32) -> f32 {
913    if (hit.x >= hit.y) {
914        return 0.0;
915    }
916    return flat_blur_cdf(hit.y, span) - flat_blur_cdf(hit.x, span);
917}
918
919fn flat_blur_closed(v: FlatVaryings, kind: u32, stroke: bool) -> bool {
920    if (kind == FLAT_RING) {
921        return !stroke;
922    }
923    if (kind != FLAT_RECT && kind != FLAT_CIRCLE) {
924        return false;
925    }
926    if (kind == FLAT_RECT && any(v.corners != vec4f(v.corners.x))) {
927        return false;
928    }
929    return !stroke || (v.info.x & FLAT_DASHED) == 0u;
930}
931
932fn flat_blur_row(v: FlatVaryings, kind: u32, stroke: bool, start: vec2f, a: vec2f, span: f32) -> f32 {
933    let w = select(0.0, v.stroke.x * 0.5, stroke);
934    var outer = flat_line_empty();
935    var inner = flat_line_empty();
936    if (kind == FLAT_RECT) {
937        outer = flat_line_rounded(start, a, v.shape.xy + vec2f(w), v.corners.x + w);
938        if (stroke) {
939            inner = flat_line_rounded(start, a, v.shape.xy - vec2f(w), max(v.corners.x - w, 0.0));
940        }
941    } else if (kind == FLAT_CIRCLE) {
942        outer = flat_line_disc(start, a, vec2f(0.0), v.shape.x + w);
943        if (stroke) {
944            inner = flat_line_disc(start, a, vec2f(0.0), v.shape.x - w);
945        }
946    } else {
947        outer = flat_line_disc(start, a, vec2f(0.0), v.shape.x + 0.5 * v.shape.y);
948        inner = flat_line_disc(start, a, vec2f(0.0), v.shape.x - 0.5 * v.shape.y);
949    }
950    return flat_blur_weight(outer, span) - flat_blur_weight(inner, span);
951}
952
953fn flat_blur_cover(v: FlatVaryings, kind: u32, stroke: bool, p: vec2f, inverse: mat2x2f) -> f32 {
954    let axes = mat2x2f(vec2f(inverse[1].y, -inverse[0].y), vec2f(-inverse[1].x, inverse[0].x))
955        * (1.0 / determinant(inverse));
956    let screen = axes * v.material.xy;
957    let span = max(length(screen), 1e-6);
958    let direction = screen / span;
959    let along = inverse * direction;
960    let across = inverse * vec2f(-direction.y, direction.x);
961    let unit = max(length(along), 1e-12);
962    let closed = flat_blur_closed(v, kind, stroke);
963    var total = 0.0;
964    for (var row = 0u; row < FLAT_BLUR_ROWS; row += 1u) {
965        let u = (f32(row) + 0.5) / f32(FLAT_BLUR_ROWS) - 0.5;
966        if (closed) {
967            total += flat_blur_row(v, kind, stroke, p + across * u, along, span);
968        } else {
969            total += flat_blur_line(v, kind, stroke, p + across * u, along, unit, span);
970        }
971    }
972    return clamp(total / f32(FLAT_BLUR_ROWS), 0.0, 1.0);
973}
974
975fn flat_blur(v: FlatVaryings) -> FlatPaint {
976    let p = v.local;
977    let inverse = mat2x2f(v.inverse.xy, v.inverse.zw);
978    let kind = v.info.x & 15u;
979    let fill_mode = (v.info.x >> FLAT_FILL_SHIFT) & 15u;
980    let opacity = bitcast<f32>(v.info.w);
981    var fill = vec4f(0.0);
982    var fill_cover = 0.0;
983    if (fill_mode != 0u) {
984        fill_cover = flat_blur_cover(v, kind, false, p, inverse);
985        var base = flat_fill_base(v, fill_mode, p);
986        let pattern = (v.info.x >> 12u) & 7u;
987        if (pattern != 0u) {
988            base = flat_pattern_over(base, pattern, v.icon, v.info.z, p, inverse);
989        }
990        fill = base * fill_cover;
991    }
992    var stroke = vec4f(0.0);
993    var stroke_cover = 0.0;
994    if (v.stroke.x > 0.0) {
995        stroke_cover = flat_blur_cover(v, kind, true, p, inverse);
996        stroke = vec4f(v.stroke_colour.rgb * v.stroke_colour.a, v.stroke_colour.a) * stroke_cover;
997    }
998    let colour = (stroke + fill * (1.0 - stroke.a)) * opacity;
999    let fill_seen = select(0.0, fill_cover, fill_mode != 0u && flat_fill_alpha(v, fill_mode) > 0.0);
1000    let stroke_seen = select(0.0, stroke_cover, v.stroke_colour.a > 0.0);
1001    let footprint = select(0.0, max(fill_seen, stroke_seen), opacity > 0.0);
1002    return FlatPaint(colour, footprint);
1003}
1004"#;
1005
1006pub const FINISH_WGSL: &str = r#"
1007@group(0) @binding(0) var flat_source: texture_2d<f32>;
1008
1009@vertex
1010fn flat_finish_vertex(@builtin(vertex_index) index: u32) -> @builtin(position) vec4f {
1011    let xy = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
1012    return vec4f(xy[index], 0.0, 1.0);
1013}
1014
1015fn flat_finish_decode(c: vec3f) -> vec3f {
1016    return select(pow((c + 0.055) / 1.055, vec3f(2.4)), c / 12.92, c <= vec3f(0.04045));
1017}
1018
1019@fragment
1020fn flat_finish_copy(@builtin(position) position: vec4f) -> @location(0) vec4f {
1021    return textureLoad(flat_source, vec2i(position.xy), 0);
1022}
1023
1024@fragment
1025fn flat_finish_linear(@builtin(position) position: vec4f) -> @location(0) vec4f {
1026    let c = textureLoad(flat_source, vec2i(position.xy), 0);
1027    if (c.a <= 0.0) {
1028        return vec4f(0.0);
1029    }
1030    return vec4f(flat_finish_decode(c.rgb / c.a) * c.a, c.a);
1031}
1032"#;
1033
1034pub use super::style::{STYLE_CALL, STYLE_WGSL};
1035
1036pub fn flat_source() -> String {
1037    format!(
1038        "{COLOUR_WGSL}
1039{SDF_WGSL}
1040{STYLE_WGSL}
1041{PASS_WGSL}"
1042    )
1043}
1044
1045pub fn source() -> String {
1046    format!(
1047        "{COLOUR_WGSL}
1048{SDF_WGSL}
1049{STYLE_WGSL}
1050{PASS_WGSL}
1051{LIT_WGSL}
1052{FX_WGSL}"
1053    )
1054}
1055
1056pub fn unstyled_source() -> String {
1057    format!(
1058        "{COLOUR_WGSL}
1059{SDF_WGSL}
1060{}
1061{LIT_WGSL}
1062{FX_WGSL}",
1063        PASS_WGSL.replace(STYLE_CALL, "")
1064    )
1065}