frust-engine 0.6.2

Sparse-strip GPU render pipeline for Frust: compiles a scene display list into strips and records the draw passes on wgpu.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
//! The engine's WGSL sources, assembled at compile time.
//!
//! Every shader lives in `crates/frust-engine/shaders/` as its own `.wgsl`
//! file and reaches Rust through `include_str!` — never as an inline string
//! literal — so the downlevel lint that scans that directory
//! (`frust_gpu::lint::lint_wgsl_dir`, driven by `frust-gpu`'s
//! `downlevel_rules` test) sees every line the GPU ever compiles.
//!
//! ## The helper prelude
//!
//! The sources are ported from a WESL reference whose entry-point modules
//! pull shared code in with `import package::helpers::...`. WESL's own
//! resolver is not available here — its crate family needs a newer toolchain
//! than this workspace's MSRV — so the import graph is resolved two ways:
//! within [`HELPERS`] by hand-inlining, and between an entry-point module and
//! its helpers by `concat!`, exactly the shape `frust-render`'s shader-effect
//! `VERTEX_PRELUDE` uses.
//!
//! Prepending the whole helper file to every entry-point module is safe
//! because [`HELPERS`] declares no `@group`/`@binding` global: a helper that
//! reads a texture takes it as a parameter. An entry-point module's derived
//! bind-group layout is therefore exactly what its own bindings say, with or
//! without the prelude. [`FILTER_KERNELS`] is a second prelude on the same
//! terms, prepended to [`FILTER`] alone.
//!
//! Every module the engine compiles is assembled here and nowhere else:
//! [`MODULES`] lists the four fixed ones, and [`strip_class_source`] derives
//! the strip program's paint-class specialisations from [`STRIP`] — together
//! the whole list [`crate::gpu::pipelines::EngineShaders::register`] walks,
//! and the one the validation tests below walk.
//!
//! ## The paint-class pre-pass
//!
//! The strip program is compiled once per [`PaintClass`] from the ONE source,
//! by a line-oriented string pass ([`specialise`]) rather than a second copy of
//! the shader: a block between a `//#if FEATURE` line and its `//#endif` line
//! survives only in a class that enables `FEATURE` ([`class_features`]), and a
//! `//#else` line inside it opens the arm that survives exactly when the `//#if`
//! arm does not. The directives are ordinary WGSL comments, which keeps the
//! downlevel lint (which scans `shaders/`) reading the real source.
//!
//! ## The two program families
//!
//! [`IMAGE_SAMPLER`] is not a class feature but a family switch, settled per
//! renderer (`FRUST_ENGINE_IMAGE_SAMPLER`, [`crate::config::image_sampler_enabled`]):
//! with it, every program that keeps `IMAGE` — [`PaintClass::Image`],
//! [`PaintClass::ImageFiltered`] and [`PaintClass::Uber`] — reads a bilinear
//! atlas image through one hardware-filtered `textureSampleLevel`; without it
//! they keep the manual four-`textureLoad` path in the feature's `//#else` arm.
//! The sampler family is the default; the switch's `0` is the kill switch
//! that selects the manual one. A renderer compiles one family, so the switch
//! adds no pipeline variant ([`strip_program_source`]). The manual family's
//! [`PaintClass::Uber`]
//! program is [`STRIP`] with only the `IMAGE_SAMPLER` arm blanked out — line
//! for line the program the engine shipped before either axis existed.
//!
//! A removed line is replaced by an empty one rather than dropped, so a naga
//! diagnostic in a specialised module names the same line number as the source
//! file. The pass emits only lines the source already contains, and the blocks
//! are placed so that dropping any combination of them leaves well-formed WGSL
//! (each block is a whole `else if` arm), so a specialisation uses no construct
//! the Uber program does not — the property the WebGL2 arm's naga GLSL-ES
//! lowering depends on. Overrides (`override` constants) were the alternative
//! and are deliberately not used: no pipeline here sets one, and the pre-pass
//! needs no new pipeline-description axis to carry them.

use std::borrow::Cow;

use super::strips::PaintClass;

/// Binding-free helper functions shared by every entry-point module:
/// packing, quad geometry, extend modes, encoded-paint accessors, gradient
/// and blur evaluation, and atlas/external image sampling.
///
/// Not a shader in its own right — it has no entry point. It is prepended to
/// each of [`STRIP`], [`CLEAR`] and [`COPY`].
pub const HELPERS: &str = include_str!("../../shaders/helpers.wgsl");

/// The sparse-strip rasterizer: `vs_main` + `fs_main`, six render-state
/// variants (see [`crate::gpu::pipelines`]) — and, unchanged, the
/// [`PaintClass::Uber`] program every class specialisation is cut from.
pub const STRIP: &str = concat!(
    include_str!("../../shaders/helpers.wgsl"),
    include_str!("../../shaders/strip.wgsl")
);

/// Region and fullscreen clears of an intermediate texture: `vs_main` /
/// `vs_main_fullscreen` + `fs_main`.
pub const CLEAR: &str = concat!(
    include_str!("../../shaders/helpers.wgsl"),
    include_str!("../../shaders/clear.wgsl")
);

/// Rectangular region copies between textures: `vs_main` + `fs_main`. Drawn
/// by a backdrop blur's region copy (a region of the frame's own target into a
/// pooled page — see `crate::renderer::RegionCopyMode`) and, built for a
/// swapchain's format, by the host's plain present copy
/// ([`crate::gpu::present::CopyPresentPass`]).
pub const COPY: &str = concat!(
    include_str!("../../shaders/helpers.wgsl"),
    include_str!("../../shaders/copy.wgsl")
);

/// The Gaussian-blur kernels [`FILTER`]'s fragment stage dispatches into: the
/// two rescaling steps a decimated blur is built from, and the separable
/// convolution run between them.
///
/// A second prelude rather than a module of its own: like [`HELPERS`] it
/// declares no entry point and no `@group`/`@binding` global — a kernel that
/// reads a texture takes the texture and the sampler as parameters — so
/// prepending it cannot disturb [`FILTER`]'s derived bind-group layout.
pub const FILTER_KERNELS: &str = include_str!("../../shaders/filters_blur.wgsl");

/// The drop-shadow passes [`FILTER`]'s fragment stage dispatches into beyond
/// what [`FILTER_KERNELS`] already covers: the shadow's own device-space
/// shift, and the recolour from a blurred alpha mask into the shadow's
/// premultiplied colour.
///
/// A third prelude on the same terms as [`FILTER_KERNELS`]: no entry point, no
/// `@group`/`@binding` global, so prepending it cannot disturb [`FILTER`]'s
/// derived bind-group layout either. The layouts and constants it reads are
/// [`crate::filters::drop_shadow`]'s; that module's own tests pin the two
/// sides against each other.
pub const DROP_SHADOW_KERNELS: &str = include_str!("../../shaders/filters_drop_shadow.wgsl");

/// One filter pass over one destination page: `vs_main` + `fs_main`, one
/// pipeline (see [`crate::gpu::pipelines::EnginePipeline::Filter`]).
///
/// The only module assembled from more than one prelude: the binding-free
/// helpers every module gets, then [`FILTER_KERNELS`], then
/// [`DROP_SHADOW_KERNELS`], then the entry-point module that declares the
/// bindings and dispatches on the pass kind. The layouts and constants it
/// reads are [`crate::filters::blur`]'s and [`crate::filters::drop_shadow`]'s;
/// those modules' own tests pin all three sides against each other.
pub const FILTER: &str = concat!(
    include_str!("../../shaders/helpers.wgsl"),
    include_str!("../../shaders/filters_blur.wgsl"),
    include_str!("../../shaders/filters_drop_shadow.wgsl"),
    include_str!("../../shaders/filter.wgsl")
);

/// The name [`STRIP`] is registered under in the shader library.
pub const STRIP_NAME: &str = "frust-engine strip";

/// The class feature names a `//#if` directive in `strip.wgsl` may name —
/// [`IMAGE_SAMPLER`], the family switch, is the only other one.
///
/// - `IMAGE` — atlas image sampling at nearest and bilinear quality;
/// - `IMAGE_FILTERED` — nested inside `IMAGE`: bicubic atlas sampling and the
///   external texture;
/// - `GRADIENT` — the linear, radial and sweep gradients;
/// - `BLURRED_RECT` — the blurred rounded rectangle;
/// - `LAYER` — the layer colour source a composite samples its page through;
/// - `GLYPH` — the glyph quad: one nearest atlas load at the texel the
///   instance's payload names. Not nested inside `IMAGE` and never reading the
///   sampler, so it compiles identically in both program families.
pub const STRIP_FEATURES: [&str; 6] = [
    "IMAGE",
    "IMAGE_FILTERED",
    "GRADIENT",
    "BLURRED_RECT",
    "LAYER",
    "GLYPH",
];

/// The family feature: bilinear atlas images through a hardware sampler (see
/// the module doc's *The two program families*). Kept by the image programs
/// of the sampler family only, never by a class on its own.
pub const IMAGE_SAMPLER: &str = "IMAGE_SAMPLER";

/// The `//#if` class features `class` keeps.
///
/// Coverage, the solid paint and the vertex stage sit outside every block, so
/// they are in every class; [`PaintClass::Uber`] keeps every feature.
#[must_use]
pub const fn class_features(class: PaintClass) -> &'static [&'static str] {
    match class {
        PaintClass::SolidCoverage => &[],
        PaintClass::Gradient => &["GRADIENT"],
        PaintClass::Image => &["IMAGE"],
        PaintClass::ImageFiltered => &["IMAGE", "IMAGE_FILTERED"],
        PaintClass::BlurredRect => &["BLURRED_RECT"],
        PaintClass::LayerComposite => &["LAYER"],
        PaintClass::Glyph => &["GLYPH"],
        PaintClass::Uber => &STRIP_FEATURES,
    }
}

/// The name the strip program specialised for `class` is registered under in
/// the shader library — [`STRIP_NAME`] itself for [`PaintClass::Uber`].
#[must_use]
pub const fn strip_class_name(class: PaintClass) -> &'static str {
    match class {
        PaintClass::SolidCoverage => "frust-engine strip/solid",
        PaintClass::Gradient => "frust-engine strip/gradient",
        PaintClass::Image => "frust-engine strip/image",
        PaintClass::ImageFiltered => "frust-engine strip/image-filtered",
        PaintClass::BlurredRect => "frust-engine strip/blurred-rect",
        PaintClass::LayerComposite => "frust-engine strip/layer",
        PaintClass::Glyph => "frust-engine strip/glyph",
        PaintClass::Uber => STRIP_NAME,
    }
}

/// The strip program of the manual family specialised for `class`:
/// [`STRIP`] with every `//#if` block whose feature `class` does not keep, and
/// the [`IMAGE_SAMPLER`] arm, blanked out.
///
/// [`PaintClass::Uber`] keeps every class feature, so the kill switch
/// (`FRUST_ENGINE_UBER_PROGRAM`) compiles [`STRIP`] with only the sampler arm
/// and the directive lines blanked — line for line the program the engine
/// shipped before the classes existed.
#[must_use]
pub fn strip_class_source(class: PaintClass) -> Cow<'static, str> {
    strip_program_source(class, false)
}

/// Whether `class`'s program reads atlas images at all, and so whether the
/// sampler family specialises it with [`IMAGE_SAMPLER`].
#[must_use]
pub fn samples_images(class: PaintClass) -> bool {
    class_features(class).contains(&"IMAGE")
}

/// The features `class`'s program keeps in the family `image_sampler` names:
/// [`class_features`], plus [`IMAGE_SAMPLER`] for an image program of the
/// sampler family.
#[must_use]
pub fn strip_program_features(class: PaintClass, image_sampler: bool) -> Vec<&'static str> {
    let mut features = class_features(class).to_vec();
    if image_sampler && samples_images(class) {
        features.push(IMAGE_SAMPLER);
    }
    features
}

/// The strip program for `class` in the family `image_sampler` names: [`STRIP`]
/// with every block [`strip_program_features`] does not keep blanked out.
///
/// The two families differ only in the programs that keep `IMAGE`; every other
/// class compiles the same source in both.
#[must_use]
pub fn strip_program_source(class: PaintClass, image_sampler: bool) -> Cow<'static, str> {
    Cow::Owned(specialise(
        STRIP,
        &strip_program_features(class, image_sampler),
    ))
}

/// The shader-library name of `class`'s program in the family `image_sampler`
/// names — [`strip_class_name`] for every program the families share, with a
/// suffix on the sampler family's image programs so a library never holds two
/// different sources under one name.
#[must_use]
pub const fn strip_program_name(class: PaintClass, image_sampler: bool) -> &'static str {
    if !image_sampler {
        return strip_class_name(class);
    }
    match class {
        PaintClass::Image => "frust-engine strip/image+sampler",
        PaintClass::ImageFiltered => "frust-engine strip/image-filtered+sampler",
        PaintClass::Uber => "frust-engine strip+sampler",
        _ => strip_class_name(class),
    }
}

/// The directive opening a feature block, followed by one feature name.
const DIRECTIVE_IF: &str = "//#if ";

/// The directive closing the innermost open feature block.
const DIRECTIVE_ENDIF: &str = "//#endif";

/// The directive switching the innermost open feature block to its other arm.
const DIRECTIVE_ELSE: &str = "//#else";

/// Runs the feature pre-pass over `source`, keeping the blocks whose feature
/// is in `enabled`.
///
/// Line-oriented and total: a directive is a line whose trimmed text starts
/// with `//#if ` (one feature name after it) or is exactly `//#else` or
/// `//#endif`; blocks nest, a `//#else` inverts its block for the lines after
/// it, and a line is kept only while every enclosing block is live. Each
/// directive line, and each line of a dead arm, becomes an empty line, so the
/// output has exactly the source's line count. An `//#else` or `//#endif` with
/// no open block is ignored and an unclosed block runs to the end of the
/// source; neither occurs in the shipped shaders, which the tests below pin.
#[must_use]
pub fn specialise(source: &str, enabled: &[&str]) -> String {
    let mut out = String::with_capacity(source.len());
    // Whether each open block is live, innermost last; a line is kept only
    // when every entry is.
    let mut open: Vec<bool> = Vec::new();
    for line in source.split_inclusive('\n') {
        let trimmed = line.trim();
        let newline = if line.ends_with('\n') { "\n" } else { "" };
        if let Some(feature) = trimmed.strip_prefix(DIRECTIVE_IF) {
            let feature = feature.trim();
            open.push(enabled.contains(&feature));
            out.push_str(newline);
        } else if trimmed == DIRECTIVE_ELSE {
            if let Some(live) = open.last_mut() {
                *live = !*live;
            }
            out.push_str(newline);
        } else if trimmed == DIRECTIVE_ENDIF {
            open.pop();
            out.push_str(newline);
        } else if open.iter().all(|live| *live) {
            out.push_str(line);
        } else {
            out.push_str(newline);
        }
    }
    out
}

/// The name [`CLEAR`] is registered under in the shader library.
pub const CLEAR_NAME: &str = "frust-engine clear";

/// The name [`COPY`] is registered under in the shader library.
pub const COPY_NAME: &str = "frust-engine copy";

/// The name [`FILTER`] is registered under in the shader library.
pub const FILTER_NAME: &str = "frust-engine filter";

/// Every module source the engine assembles at compile time, as
/// `(name, source)` pairs — the list the validation tests walk.
///
/// [`STRIP`] is listed as written, both [`IMAGE_SAMPLER`] arms present, which
/// is valid WGSL in its own right; what a renderer compiles from it are the
/// specialisations [`strip_program_source`] derives at start-up, the Uber
/// program among them.
pub const MODULES: [(&str, &str); 4] = [
    (STRIP_NAME, STRIP),
    (CLEAR_NAME, CLEAR),
    (COPY_NAME, COPY),
    (FILTER_NAME, FILTER),
];

#[cfg(test)]
mod tests {
    use super::*;
    use crate::gpu::{GpuConfig, GpuStrip};
    use wgpu::naga;

    /// Parses and validates `src` the way a `wgpu::Device` would, returning
    /// the validated module.
    ///
    /// `wgpu` re-exports the same `naga` its own WGSL front end uses, so this
    /// is the device's validation path minus the device: no GPU, no adapter,
    /// and a failure surfaces here as a test failure instead of as a
    /// pipeline-creation error at run time. The device-backed counterpart
    /// (`gpu::pipelines`' real-pipeline test) additionally proves the
    /// *pipelines* are accepted, but it needs hardware and is `#[ignore]`d.
    fn validate(name: &str, src: &str) -> naga::Module {
        let module = naga::front::wgsl::parse_str(src)
            .unwrap_or_else(|err| panic!("{name} failed to parse: {err:?}"));
        naga::valid::Validator::new(
            naga::valid::ValidationFlags::all(),
            naga::valid::Capabilities::empty(),
        )
        .validate(&module)
        .unwrap_or_else(|err| panic!("{name} failed validation: {err:?}"));
        module
    }

    /// The byte size naga computed for the struct named `name`.
    fn struct_span(module: &naga::Module, name: &str) -> u32 {
        module
            .types
            .iter()
            .find_map(|(_, ty)| match (&ty.name, &ty.inner) {
                (Some(ty_name), naga::TypeInner::Struct { span, .. }) if ty_name == name => {
                    Some(*span)
                }
                _ => None,
            })
            .unwrap_or_else(|| panic!("the module declares no struct named {name}"))
    }

    #[test]
    fn every_module_parses_and_validates() {
        for (name, src) in MODULES {
            validate(name, src);
        }
        for image_sampler in [false, true] {
            for class in PaintClass::ALL {
                validate(
                    strip_program_name(class, image_sampler),
                    &strip_program_source(class, image_sampler),
                );
            }
        }
    }

    /// Whether `line` is a pre-pass directive.
    fn is_directive(line: &str) -> bool {
        let trimmed = line.trim();
        trimmed.starts_with(DIRECTIVE_IF) || trimmed == DIRECTIVE_ELSE || trimmed == DIRECTIVE_ENDIF
    }

    /// The kill switch's whole promise: the manual family's Uber class
    /// compiles the shipped source, not a reconstruction of it — every line of
    /// [`STRIP`] kept in place, save the directives and the sampler family's
    /// own arm, which are blanked rather than dropped.
    #[test]
    fn the_manual_uber_program_is_the_strip_source_without_the_sampler_arm() {
        let uber = strip_class_source(PaintClass::Uber);
        let sampled = strip_program_source(PaintClass::Uber, true);
        assert_eq!(strip_class_name(PaintClass::Uber), STRIP_NAME);
        assert_eq!(uber.lines().count(), STRIP.lines().count());
        let mut blanked = 0;
        for ((source, kept), sampled) in STRIP.lines().zip(uber.lines()).zip(sampled.lines()) {
            if kept == source {
                continue;
            }
            assert!(kept.is_empty(), "a kept line was rewritten: `{source}`");
            if is_directive(source) || source.trim().is_empty() {
                continue;
            }
            assert_eq!(
                sampled, source,
                "the manual Uber program blanked `{source}`, which is not the sampler arm"
            );
            blanked += 1;
        }
        assert!(blanked > 0, "the sampler arm is in the source");
        assert!(!uber.contains("atlas_sampler"));
        assert!(!uber.contains("textureSampleLevel("));
        assert!(sampled.contains("var atlas_sampler: sampler;"));
    }

    /// The families differ exactly where the switch says: in the programs
    /// that read atlas images, and nowhere else.
    #[test]
    fn the_families_differ_only_in_the_image_programs() {
        for class in PaintClass::ALL {
            let default = strip_program_source(class, false);
            let sampled = strip_program_source(class, true);
            assert_eq!(
                default == sampled,
                !samples_images(class),
                "{}",
                class.label()
            );
            assert_eq!(
                strip_program_name(class, false) == strip_program_name(class, true),
                !samples_images(class),
                "{}",
                class.label()
            );
            assert_eq!(strip_program_name(class, false), strip_class_name(class));
            assert_eq!(default, strip_class_source(class));
        }
        assert!(samples_images(PaintClass::Image));
        assert!(samples_images(PaintClass::ImageFiltered));
        assert!(samples_images(PaintClass::Uber));
        // The sampler arm replaces the manual one: neither family keeps both.
        for class in [
            PaintClass::Image,
            PaintClass::ImageFiltered,
            PaintClass::Uber,
        ] {
            let default = strip_program_source(class, false);
            let sampled = strip_program_source(class, true);
            let fs =
                |src: &str| src[src.find("fn fs_main(").expect("a fragment stage")..].to_string();
            assert!(fs(&default).contains("sample_color = bilinear_sample("));
            assert!(!fs(&default).contains("sampled_bilinear("));
            assert!(fs(&sampled).contains("sample_color = sampled_bilinear("));
            assert!(!fs(&sampled).contains("bilinear_sample("));
        }
    }

    #[test]
    fn every_directive_names_a_known_feature_and_every_block_closes() {
        let mut depth = 0_usize;
        let mut seen = Vec::new();
        for (number, line) in STRIP.lines().enumerate() {
            let trimmed = line.trim();
            if let Some(feature) = trimmed.strip_prefix(DIRECTIVE_IF) {
                assert!(
                    STRIP_FEATURES.contains(&feature.trim()) || feature.trim() == IMAGE_SAMPLER,
                    "line {number} names an unknown feature `{feature}`"
                );
                seen.push(feature.trim().to_string());
                depth += 1;
            } else if trimmed == DIRECTIVE_ELSE {
                assert!(
                    depth > 0,
                    "line {number} switches a block that was never opened"
                );
            } else if trimmed == DIRECTIVE_ENDIF {
                assert!(
                    depth > 0,
                    "line {number} closes a block that was never opened"
                );
                depth -= 1;
            } else {
                assert!(
                    !trimmed.starts_with("//#"),
                    "line {number} looks like a directive but is not one: `{trimmed}`"
                );
            }
        }
        assert_eq!(
            depth, 0,
            "a feature block is left open at the end of the source"
        );
        for feature in STRIP_FEATURES.into_iter().chain([IMAGE_SAMPLER]) {
            assert!(
                seen.iter().any(|f| f == feature),
                "feature {feature} gates nothing — a class enabling it would equal one that does not"
            );
        }
    }

    #[test]
    fn the_pre_pass_keeps_line_numbers_and_only_drops_disabled_blocks() {
        let src = "a\n//#if X\nb\n//#if Y\nc\n//#endif\n//#endif\nd";
        assert_eq!(specialise(src, &["X", "Y"]), "a\n\nb\n\nc\n\n\nd");
        assert_eq!(specialise(src, &["X"]), "a\n\nb\n\n\n\n\nd");
        assert_eq!(specialise(src, &["Y"]), "a\n\n\n\n\n\n\nd");
        assert_eq!(specialise(src, &[]), "a\n\n\n\n\n\n\nd");
        // An `//#else` arm is live exactly when its `//#if` arm is not, and
        // only while every enclosing block is.
        let src = "a\n//#if X\nb\n//#if Y\nc\n//#else\ne\n//#endif\n//#endif\nd";
        assert_eq!(specialise(src, &["X", "Y"]), "a\n\nb\n\nc\n\n\n\n\nd");
        assert_eq!(specialise(src, &["X"]), "a\n\nb\n\n\n\ne\n\n\nd");
        assert_eq!(specialise(src, &["Y"]), "a\n\n\n\n\n\n\n\n\nd");
        for image_sampler in [false, true] {
            for class in PaintClass::ALL {
                assert_eq!(
                    strip_program_source(class, image_sampler).lines().count(),
                    STRIP.lines().count(),
                    "{} must keep the source's line numbering",
                    class.label()
                );
            }
        }
    }

    /// Every class keeps exactly its own colour-source branches: the fragment
    /// entry point of each specialisation names only the paint types the
    /// class owns, and the solid paint and both coverage paths in all of them.
    #[test]
    fn each_class_keeps_only_its_own_branches() {
        let branch = |src: &str, needle: &str| src.contains(needle);
        let image = "paint_type == PAINT_TYPE_IMAGE {";
        let bicubic = "sample_color = bicubic_sample(";
        let external = "sample_color = sample_external_image(";
        let bilinear = "sample_color = bilinear_sample(";
        let linear = "paint_type == PAINT_TYPE_LINEAR_GRADIENT {";
        let radial = "paint_type == PAINT_TYPE_RADIAL_GRADIENT {";
        let sweep = "paint_type == PAINT_TYPE_SWEEP_GRADIENT {";
        let blurred = "paint_type == PAINT_TYPE_BLURRED_ROUNDED_RECT {";
        let layer = "} else if color_source == COLOR_SOURCE_LAYER {\n        let layer_opacity";
        for class in PaintClass::ALL {
            let src = strip_class_source(class);
            let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
            let keeps = |feature: &str| class_features(class).contains(&feature);
            assert!(
                branch(fs, "paint_type == PAINT_TYPE_SOLID {"),
                "{}",
                class.label()
            );
            assert!(
                branch(fs, "if is_rect && rect_frac != 0u {"),
                "{}",
                class.label()
            );
            assert_eq!(branch(fs, image), keeps("IMAGE"), "{}", class.label());
            assert_eq!(branch(fs, bilinear), keeps("IMAGE"), "{}", class.label());
            for filtered in [bicubic, external] {
                assert_eq!(
                    branch(fs, filtered),
                    keeps("IMAGE_FILTERED"),
                    "{}",
                    class.label()
                );
            }
            for gradient in [linear, radial, sweep] {
                assert_eq!(branch(fs, gradient), keeps("GRADIENT"), "{}", class.label());
            }
            assert_eq!(
                branch(fs, blurred),
                keeps("BLURRED_RECT"),
                "{}",
                class.label()
            );
            assert_eq!(branch(&src, layer), keeps("LAYER"), "{}", class.label());
        }
    }

    /// candB: the strip program addresses its alpha and LUT textures by the
    /// `Config` shifts alone — no per-fragment `textureDimensions` query and no
    /// integer `%`/`/` against a queried width (see `gpu::config`'s guard).
    /// The one query in the source is the sampler family's normalisation of an
    /// atlas sample position, which no manual-family program keeps.
    #[test]
    fn the_strip_program_addresses_its_resource_textures_by_config_shifts() {
        assert_eq!(STRIP.matches("textureDimensions(").count(), 1);
        assert!(STRIP.contains("vec2<f32>(textureDimensions(tex))"));
        for class in PaintClass::ALL {
            assert!(!strip_class_source(class).contains("textureDimensions("));
            let sampled = strip_program_source(class, true);
            assert_eq!(
                sampled.contains("textureDimensions("),
                samples_images(class),
                "{}",
                class.label()
            );
        }
        assert!(STRIP.contains("(1u << config.alphas_tex_width_bits) - 1u"));
        assert_eq!(
            STRIP
                .matches(
                    "get_gradient_texture_width(gradient_texel0),\n                \
                     config.encoded_paints_tex_width_bits\n"
                )
                .count(),
            3,
            "each gradient kind passes the LUT width shift"
        );
        assert!(HELPERS.contains("flat_coord & ((1u << lut_width_bits) - 1u)"));
        assert!(HELPERS.contains("flat_coord >> lut_width_bits"));
    }

    /// The glyph program keeps the glyph quad's one atlas load and nothing of
    /// the image paths — and, holding no `IMAGE` feature, is the same program in
    /// both families.
    #[test]
    fn the_glyph_class_keeps_only_the_glyph_branch() {
        let glyph_arm = "paint_type == PAINT_TYPE_GLYPH {";
        for image_sampler in [false, true] {
            let src = strip_program_source(PaintClass::Glyph, image_sampler);
            let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
            assert!(fs.contains(glyph_arm));
            assert!(!fs.contains("paint_type == PAINT_TYPE_IMAGE {"));
            assert!(!fs.contains("sampled_bilinear("));
            assert!(!fs.contains("bilinear_sample("));
            assert!(!src.contains("var atlas_sampler: sampler;"));
            assert!(fs.contains("paint_type == PAINT_TYPE_SOLID {"));
        }
        assert_eq!(
            strip_program_source(PaintClass::Glyph, false),
            strip_program_source(PaintClass::Glyph, true)
        );
        for class in PaintClass::ALL {
            let src = strip_class_source(class);
            let fs = &src[src.find("fn fs_main(").expect("a fragment entry point")..];
            assert_eq!(
                fs.contains(glyph_arm),
                class_features(class).contains(&"GLYPH"),
                "{}",
                class.label()
            );
        }
        assert!(strip_class_source(PaintClass::Uber).contains(glyph_arm));
    }

    #[test]
    fn the_helper_prelude_validates_on_its_own() {
        // It has no entry point, so nothing else would catch a syntax error
        // in a helper that no current entry point happens to call.
        validate("helpers", HELPERS);
    }

    #[test]
    fn every_module_is_the_helper_prelude_followed_by_its_own_source() {
        for (name, src) in MODULES {
            assert!(
                src.starts_with(HELPERS),
                "{name} must be assembled as the helper prelude followed by its own source"
            );
            assert!(
                src.len() > HELPERS.len(),
                "{name} must contribute source of its own"
            );
        }
    }

    #[test]
    fn the_helper_prelude_declares_no_bindings() {
        // The whole reason every entry-point module can be prefixed with it
        // without disturbing its derived bind-group layout. Comment lines are
        // skipped: this file's own header describes the rule. The blur
        // kernels are held to the same rule for the same reason — they are a
        // prelude too, just one only the filter module gets.
        for (name, prelude) in [
            ("helpers", HELPERS),
            ("filter kernels", FILTER_KERNELS),
            ("drop shadow kernels", DROP_SHADOW_KERNELS),
        ] {
            let declaration = prelude
                .lines()
                .find(|line| !line.trim_start().starts_with("//") && line.contains("@group"));
            assert!(
                declaration.is_none(),
                "a prelude that declared a binding would change the bind-group layout of every \
                 module it is prepended to ({name}): {declaration:?}"
            );
        }
    }

    #[test]
    fn the_filter_module_is_every_prelude_followed_by_its_own_source() {
        // `every_module_is_the_helper_prelude_followed_by_its_own_source`
        // above only pins the first prelude; the filter program is the one
        // module assembled from three, and each has to sit between the helpers
        // it calls and the entry point that calls it.
        assert!(FILTER.starts_with(HELPERS));
        assert!(FILTER[HELPERS.len()..].starts_with(FILTER_KERNELS));
        assert!(FILTER[HELPERS.len() + FILTER_KERNELS.len()..].starts_with(DROP_SHADOW_KERNELS));
        assert!(FILTER.len() > HELPERS.len() + FILTER_KERNELS.len() + DROP_SHADOW_KERNELS.len());
    }

    #[test]
    fn the_filter_instance_matches_its_rust_layout() {
        let module = validate(FILTER_NAME, FILTER);
        assert_eq!(
            struct_span(&module, "FilterInstanceData") as usize,
            size_of::<crate::filters::blur::FilterInstanceData>(),
            "the shader's `FilterInstanceData` must stay byte-identical to the Rust one"
        );
    }

    #[test]
    fn the_strip_config_uniform_matches_its_rust_layout() {
        let module = validate(STRIP_NAME, STRIP);
        assert_eq!(
            u64::from(struct_span(&module, "Config")),
            GpuConfig::SIZE,
            "the shader's `Config` must stay byte-identical to `GpuConfig`"
        );
    }

    #[test]
    fn the_strip_instance_matches_its_rust_layout() {
        let module = validate(STRIP_NAME, STRIP);
        assert_eq!(
            struct_span(&module, "StripInstance") as usize,
            size_of::<GpuStrip>(),
            "the shader's `StripInstance` must stay byte-identical to `GpuStrip`"
        );
    }
}