facett-core 0.1.10

facett — visual kernel: render a node/edge Scene into egui (wgpu fast path to come)
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
//! **MSDF text / label rendering** (feature `wgpu`) — crisp glowing labels at any
//! zoom from a multi-channel signed-distance-field atlas. Net-new (the tree had no
//! GPU glyph pipeline; text was egui galleys only). A consumer bakes an MSDF atlas
//! offline (e.g. `msdf-atlas-gen`) and feeds glyph quads here; the fragment takes
//! the **median** of the three distance channels — the MSDF trick that keeps sharp
//! corners a single-channel SDF would round — and converts it to ~1px coverage via
//! the per-glyph **screen pixel range**, so a label stays razor-sharp whether it's
//! tiny or zoomed across the viewport.
//!
//! To keep the render proof self-contained (no external atlas generator), this
//! module also ships [`disk_msdf_atlas`]: an analytic single-glyph atlas (a filled
//! disk, whose true signed distance is known in closed form) — a valid degenerate
//! MSDF (all three channels equal) that exercises the exact sample → median →
//! coverage path. The coverage math ([`median3`], [`coverage`], [`screen_px_range`])
//! is mirrored on the CPU and unit-tested with no GPU (FC-7).

use wgpu::util::DeviceExt;
use wgpu::TextureFormat;

/// The MSDF glyph shader (`msdf_vs`/`msdf_fs`).
pub const MSDF_WGSL: &str = include_str!("msdf.wgsl");

/// One glyph quad: a pixel-space rect, its atlas UV rect, a colour, and the
/// per-glyph **screen pixel range** (atlas field range × glyph-screen-px ÷
/// atlas-glyph-px) that sets the AA width. 64 bytes; the vertex attribute offsets in
/// [`MsdfText::new`] hard-code this layout.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GlyphInstance {
    pub rect_min: [f32; 2],
    pub rect_max: [f32; 2],
    pub uv_min: [f32; 2],
    pub uv_max: [f32; 2],
    pub color: [f32; 4],
    pub px_range: f32,
    pub _pad: [f32; 3],
}

impl GlyphInstance {
    /// A glyph at pixel rect `(x0,y0)-(x1,y1)` sampling atlas UV `(u0,v0)-(u1,v1)`,
    /// in `color` (straight RGBA), with the screen pixel range `px_range`.
    #[allow(clippy::too_many_arguments)]
    #[must_use]
    pub fn new(rect: [f32; 4], uv: [f32; 4], color: [f32; 4], px_range: f32) -> Self {
        Self {
            rect_min: [rect[0], rect[1]],
            rect_max: [rect[2], rect[3]],
            uv_min: [uv[0], uv[1]],
            uv_max: [uv[2], uv[3]],
            color,
            px_range,
            _pad: [0.0; 3],
        }
    }
}

#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct MsdfUniforms {
    vp: [f32; 4],
}

/// The median of three values — the MSDF reconstruction (mirrors `median3` in the
/// WGSL). For a degenerate equal-channel atlas it is just that value.
#[must_use]
pub fn median3(a: f32, b: f32, c: f32) -> f32 {
    a.max(b).min(a.min(b).max(c)).max(a.min(b))
}

/// Coverage (alpha, `[0,1]`) from a sampled `median` distance and a `screen_px_range`
/// — mirrors `msdf_fs`. The transition is ~1 screen pixel wide, so the edge is crisp
/// at any zoom; a larger range = a sharper (steeper) edge.
#[must_use]
pub fn coverage(median: f32, screen_px_range: f32) -> f32 {
    (screen_px_range * (median - 0.5) + 0.5).clamp(0.0, 1.0)
}

/// The **screen pixel range** for a glyph: the atlas field range (`px_range_atlas`,
/// in atlas texels) scaled by how many screen pixels each atlas texel covers
/// (`glyph_screen_px / atlas_glyph_px`). This is what keeps coverage ~1px wide
/// independent of zoom.
#[must_use]
pub fn screen_px_range(px_range_atlas: f32, glyph_screen_px: f32, atlas_glyph_px: f32) -> f32 {
    px_range_atlas * (glyph_screen_px / atlas_glyph_px.max(1.0))
}

/// Build an analytic single-glyph MSDF atlas: a filled disk of radius
/// `radius_frac × tile/2`, distance field range `px_range` (texels), as `tile×tile`
/// `Rgba8Unorm` bytes (all three colour channels carry the same encoded signed
/// distance — a valid degenerate MSDF; alpha = 255). The encoding is the standard
/// `0.5 + sd/range`, so the shader's `median - 0.5` recovers the signed distance.
#[must_use]
pub fn disk_msdf_atlas(tile: u32, radius_frac: f32, px_range: f32) -> Vec<u8> {
    let tile = tile.max(2);
    let cx = tile as f32 / 2.0;
    let cy = tile as f32 / 2.0;
    let radius = radius_frac * (tile as f32 / 2.0);
    let range = px_range.max(1e-3);
    let mut out = vec![0u8; (tile * tile * 4) as usize];
    for y in 0..tile {
        for x in 0..tile {
            // Texel centre.
            let px = x as f32 + 0.5;
            let py = y as f32 + 0.5;
            let dist = ((px - cx).powi(2) + (py - cy).powi(2)).sqrt();
            let sd = radius - dist; // positive inside the disk
            let enc = (0.5 + sd / range).clamp(0.0, 1.0);
            let b = (enc * 255.0).round() as u8;
            let i = ((y * tile + x) * 4) as usize;
            out[i] = b;
            out[i + 1] = b;
            out[i + 2] = b;
            out[i + 3] = 255;
        }
    }
    out
}

/// The MSDF glyph renderer: an instanced quad pipeline sampling an MSDF atlas,
/// built for a `target_format` (the LDR surface the labels composite onto).
pub struct MsdfText {
    pipeline: wgpu::RenderPipeline,
    uniform_bgl: wgpu::BindGroupLayout,
    atlas_bgl: wgpu::BindGroupLayout,
    sampler: wgpu::Sampler,
    uniform: wgpu::Buffer,
    target_format: TextureFormat,
}

impl MsdfText {
    /// Build the glyph pipeline (validates `msdf.wgsl`). `target_format` is the
    /// colour format the labels are drawn into (alpha-blended over it).
    pub fn new(device: &wgpu::Device, target_format: TextureFormat) -> Self {
        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
            label: Some("l0_msdf"),
            source: wgpu::ShaderSource::Wgsl(MSDF_WGSL.into()),
        });
        let uniform_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("l0_msdf_uniform_bgl"),
            entries: &[wgpu::BindGroupLayoutEntry {
                binding: 0,
                visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
                ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None },
                count: None,
            }],
        });
        let atlas_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
            label: Some("l0_msdf_atlas_bgl"),
            entries: &[
                wgpu::BindGroupLayoutEntry {
                    binding: 0,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Texture { sample_type: wgpu::TextureSampleType::Float { filterable: true }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false },
                    count: None,
                },
                wgpu::BindGroupLayoutEntry {
                    binding: 1,
                    visibility: wgpu::ShaderStages::FRAGMENT,
                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
                    count: None,
                },
            ],
        });
        let blend = Some(wgpu::BlendState {
            color: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add },
            alpha: wgpu::BlendComponent { src_factor: wgpu::BlendFactor::One, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, operation: wgpu::BlendOperation::Add },
        });
        // Glyph instance attrs (offsets match GlyphInstance, stride 64).
        let attrs = [
            wgpu::VertexAttribute { offset: 0, shader_location: 0, format: wgpu::VertexFormat::Float32x2 },
            wgpu::VertexAttribute { offset: 8, shader_location: 1, format: wgpu::VertexFormat::Float32x2 },
            wgpu::VertexAttribute { offset: 16, shader_location: 2, format: wgpu::VertexFormat::Float32x2 },
            wgpu::VertexAttribute { offset: 24, shader_location: 3, format: wgpu::VertexFormat::Float32x2 },
            wgpu::VertexAttribute { offset: 32, shader_location: 4, format: wgpu::VertexFormat::Float32x4 },
            wgpu::VertexAttribute { offset: 48, shader_location: 5, format: wgpu::VertexFormat::Float32 },
        ];
        let layout = wgpu::VertexBufferLayout {
            array_stride: std::mem::size_of::<GlyphInstance>() as wgpu::BufferAddress,
            step_mode: wgpu::VertexStepMode::Instance,
            attributes: &attrs,
        };
        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
            label: Some("l0_msdf_pipeline"),
            layout: Some(&device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
                label: Some("l0_msdf_pll"),
                bind_group_layouts: &[Some(&uniform_bgl), Some(&atlas_bgl)],
                immediate_size: 0,
            })),
            vertex: wgpu::VertexState { module: &shader, entry_point: Some("msdf_vs"), compilation_options: Default::default(), buffers: &[layout] },
            primitive: wgpu::PrimitiveState { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() },
            depth_stencil: None,
            multisample: wgpu::MultisampleState::default(),
            fragment: Some(wgpu::FragmentState {
                module: &shader,
                entry_point: Some("msdf_fs"),
                compilation_options: Default::default(),
                targets: &[Some(wgpu::ColorTargetState { format: target_format, blend, write_mask: wgpu::ColorWrites::ALL })],
            }),
            multiview_mask: None,
            cache: None,
        });
        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
            label: Some("l0_msdf_sampler"),
            mag_filter: wgpu::FilterMode::Linear,
            min_filter: wgpu::FilterMode::Linear,
            address_mode_u: wgpu::AddressMode::ClampToEdge,
            address_mode_v: wgpu::AddressMode::ClampToEdge,
            ..Default::default()
        });
        let uniform = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("l0_msdf_uniform"),
            size: std::mem::size_of::<MsdfUniforms>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        Self { pipeline, uniform_bgl, atlas_bgl, sampler, uniform, target_format }
    }

    /// The colour format the pipeline targets.
    #[must_use]
    pub fn target_format(&self) -> TextureFormat {
        self.target_format
    }

    /// Build a bind group for an MSDF `atlas_view`. Reuse it across frames while the
    /// atlas is unchanged.
    pub fn atlas_bind(&self, device: &wgpu::Device, atlas_view: &wgpu::TextureView) -> wgpu::BindGroup {
        device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("l0_msdf_atlas_bind"),
            layout: &self.atlas_bgl,
            entries: &[
                wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(atlas_view) },
                wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(&self.sampler) },
            ],
        })
    }

    /// Record the glyph draw into `target` (must be `target_format`). `glyphs` is the
    /// per-glyph instance batch; `atlas_bind` comes from [`atlas_bind`](Self::atlas_bind).
    /// `load` keeps the existing target (compose over a scene); `false` clears first.
    #[allow(clippy::too_many_arguments)]
    pub fn render(
        &self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        encoder: &mut wgpu::CommandEncoder,
        target: &wgpu::TextureView,
        atlas_bind: &wgpu::BindGroup,
        glyphs: &[GlyphInstance],
        load: bool,
        w: u32,
        h: u32,
    ) {
        if glyphs.is_empty() {
            return;
        }
        let u = MsdfUniforms { vp: [w.max(1) as f32, h.max(1) as f32, 0.0, 0.0] };
        queue.write_buffer(&self.uniform, 0, bytemuck::bytes_of(&u));
        let uni_bind = device.create_bind_group(&wgpu::BindGroupDescriptor {
            label: Some("l0_msdf_uni_bind"),
            layout: &self.uniform_bgl,
            entries: &[wgpu::BindGroupEntry { binding: 0, resource: self.uniform.as_entire_binding() }],
        });
        let inst = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
            label: Some("l0_msdf_instances"),
            contents: bytemuck::cast_slice(glyphs),
            usage: wgpu::BufferUsages::VERTEX,
        });
        let load_op = if load { wgpu::LoadOp::Load } else { wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT) };
        let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
            label: Some("l0_msdf_pass"),
            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
                view: target,
                resolve_target: None,
                depth_slice: None,
                ops: wgpu::Operations { load: load_op, store: wgpu::StoreOp::Store },
            })],
            depth_stencil_attachment: None,
            timestamp_writes: None,
            occlusion_query_set: None,
            multiview_mask: None,
        });
        rp.set_pipeline(&self.pipeline);
        rp.set_bind_group(0, &uni_bind, &[]);
        rp.set_bind_group(1, atlas_bind, &[]);
        rp.set_vertex_buffer(0, inst.slice(..));
        rp.draw(0..6, 0..glyphs.len() as u32);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// INJECT-ASSERT: median3 returns the middle value for any ordering (the MSDF
    /// reconstruction). Pure CPU.
    #[test]
    fn median3_is_the_middle() {
        assert_eq!(median3(0.1, 0.5, 0.9), 0.5);
        assert_eq!(median3(0.9, 0.5, 0.1), 0.5);
        assert_eq!(median3(0.5, 0.9, 0.1), 0.5);
        assert_eq!(median3(0.2, 0.2, 0.8), 0.2);
        assert_eq!(median3(0.7, 0.7, 0.7), 0.7);
    }

    /// INJECT-ASSERT: coverage is 1 well inside (median≫0.5), 0 well outside, ~0.5
    /// at the edge — and a larger screen px range gives a **sharper** edge (steeper
    /// slope through 0.5). This is the crispness guarantee, on data.
    #[test]
    fn coverage_is_an_edge_and_sharper_with_more_range() {
        assert!(coverage(0.9, 8.0) > 0.99, "inside → opaque");
        assert!(coverage(0.1, 8.0) < 0.01, "outside → transparent");
        assert!((coverage(0.5, 8.0) - 0.5).abs() < 1e-6, "edge → 0.5");
        // Just off the edge: more range → moves further from 0.5 (sharper).
        let soft = coverage(0.52, 4.0);
        let sharp = coverage(0.52, 16.0);
        assert!(sharp > soft, "more screen-px-range = crisper edge ({soft} → {sharp})");
    }

    /// INJECT-ASSERT: screen px range scales with the on-screen glyph size — a glyph
    /// drawn 2× larger gets 2× the range (so coverage still spans ~1 screen px → it
    /// stays crisp when zoomed). This is the "any zoom" property as math.
    #[test]
    fn screen_px_range_scales_with_glyph_size() {
        let small = screen_px_range(4.0, 16.0, 32.0);
        let big = screen_px_range(4.0, 64.0, 32.0);
        assert!((big / small - 4.0).abs() < 1e-4, "4× glyph → 4× range");
        assert!(small > 0.0);
    }

    /// INJECT-ASSERT: the analytic disk atlas encodes the field correctly — the
    /// centre texel is well inside (>0.5), a corner is outside (<0.5), and the
    /// encoded value crosses 0.5 at the disk radius. Drives the exact sample path.
    #[test]
    fn disk_atlas_encodes_signed_distance() {
        let tile = 32u32;
        let atlas = disk_msdf_atlas(tile, 0.7, 6.0);
        let at = |x: u32, y: u32| atlas[((y * tile + x) * 4) as usize] as f32 / 255.0;
        assert!(at(tile / 2, tile / 2) > 0.5, "centre is inside the disk");
        assert!(at(0, 0) < 0.5, "corner is outside the disk");
        // Coverage of the centre is opaque; coverage of the corner is transparent.
        assert!(coverage(at(tile / 2, tile / 2), 8.0) > 0.9);
        assert!(coverage(at(1, 1), 8.0) < 0.1);
    }

    /// INJECT-ASSERT: the glyph instance is a tight 64 bytes (its vertex attrs are
    /// hard-coded to these offsets) and the shader has the named entry points.
    #[test]
    fn glyph_layout_and_shader_entry_points() {
        assert_eq!(std::mem::size_of::<GlyphInstance>(), 64);
        assert!(MSDF_WGSL.contains("fn msdf_vs"));
        assert!(MSDF_WGSL.contains("fn msdf_fs"));
        assert!(MSDF_WGSL.contains("fn median3"));
    }

    fn headless_device() -> Option<(wgpu::Device, wgpu::Queue)> {
        let instance = wgpu::Instance::default();
        let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
            power_preference: wgpu::PowerPreference::default(),
            force_fallback_adapter: false,
            compatible_surface: None,
        }))
        .ok()?;
        pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
            label: Some("l0-msdf-proof"),
            required_features: wgpu::Features::empty(),
            required_limits: wgpu::Limits::downlevel_defaults(),
            memory_hints: wgpu::MemoryHints::default(),
            experimental_features: wgpu::ExperimentalFeatures::disabled(),
            trace: wgpu::Trace::Off,
        }))
        .ok()
    }

    /// Upload the disk atlas as an Rgba8Unorm texture and return its view.
    fn upload_atlas(device: &wgpu::Device, queue: &wgpu::Queue, tile: u32, bytes: &[u8]) -> wgpu::TextureView {
        let tex = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("msdf-proof-atlas"),
            size: wgpu::Extent3d { width: tile, height: tile, depth_or_array_layers: 1 },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: TextureFormat::Rgba8Unorm,
            usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
            view_formats: &[],
        });
        queue.write_texture(
            wgpu::TexelCopyTextureInfo { texture: &tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
            bytes,
            wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(tile * 4), rows_per_image: Some(tile) },
            wgpu::Extent3d { width: tile, height: tile, depth_or_array_layers: 1 },
        );
        tex.create_view(&Default::default())
    }

    /// Render `glyphs` to a `w×h` Rgba8Unorm target and read back the pixels.
    fn render_glyphs(w: u32, h: u32, tile: u32, atlas: &[u8], glyphs: &[GlyphInstance]) -> Option<Vec<u8>> {
        let (device, queue) = headless_device()?;
        let atlas_view = upload_atlas(&device, &queue, tile, atlas);
        let msdf = MsdfText::new(&device, TextureFormat::Rgba8Unorm);
        let atlas_bind = msdf.atlas_bind(&device, &atlas_view);
        let target = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("msdf-proof-target"),
            size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format: TextureFormat::Rgba8Unorm,
            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
            view_formats: &[],
        });
        let view = target.create_view(&Default::default());
        let mut enc = device.create_command_encoder(&Default::default());
        msdf.render(&device, &queue, &mut enc, &view, &atlas_bind, glyphs, false, w, h);

        let bpp = 4u32;
        let unpadded = w * bpp;
        let align = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
        let padded = unpadded.div_ceil(align) * align;
        let readback = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("msdf-proof-readback"),
            size: (padded * h) as u64,
            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
            mapped_at_creation: false,
        });
        enc.copy_texture_to_buffer(
            wgpu::TexelCopyTextureInfo { texture: &target, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All },
            wgpu::TexelCopyBufferInfo { buffer: &readback, layout: wgpu::TexelCopyBufferLayout { offset: 0, bytes_per_row: Some(padded), rows_per_image: Some(h) } },
            wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
        );
        queue.submit(Some(enc.finish()));
        let slice = readback.slice(..);
        let (tx, rx) = std::sync::mpsc::channel();
        slice.map_async(wgpu::MapMode::Read, move |r| { let _ = tx.send(r); });
        device.poll(wgpu::PollType::wait_indefinitely()).ok()?;
        rx.recv().ok()?.ok()?;
        let data = slice.get_mapped_range();
        let mut rgba = Vec::with_capacity((w * h * 4) as usize);
        for row in 0..h {
            let s = (row * padded) as usize;
            rgba.extend_from_slice(&data[s..s + unpadded as usize]);
        }
        drop(data);
        readback.unmap();
        Some(rgba)
    }

    /// RENDER PROOF (self-skips without an adapter): the MSDF disk glyph draws a
    /// crisp filled disk — the centre is opaque-coloured, a corner is empty, and the
    /// lit-pixel count is close to the disk's area (the shape is rendered, not a
    /// blurry blob). Plus a **crispness** check: drawing 2× larger grows the lit area
    /// ~4× (area scaling) — the edge stays thin, it does not smear.
    #[test]
    fn msdf_render_proof_crisp_disk_at_any_zoom() {
        let tile = 32u32;
        let px_range = 6.0f32;
        let radius_frac = 0.8f32;
        let atlas = disk_msdf_atlas(tile, radius_frac, px_range);

        // ── base size: a 24px glyph centred in a 48×48 target ──
        let (w, h) = (48u32, 48u32);
        let g = 24.0f32;
        let x0 = (w as f32 - g) / 2.0;
        let y0 = (h as f32 - g) / 2.0;
        let spr = screen_px_range(px_range, g, tile as f32);
        let glyph = GlyphInstance::new([x0, y0, x0 + g, y0 + g], [0.0, 0.0, 1.0, 1.0], [0.2, 0.9, 1.0, 1.0], spr);
        let Some(px) = render_glyphs(w, h, tile, &atlas, &[glyph]) else {
            eprintln!("[msdf] no GPU adapter — skipping render proof");
            return;
        };
        let at = |buf: &[u8], stride: u32, x: u32, y: u32| -> [u8; 4] {
            let i = ((y * stride + x) * 4) as usize;
            [buf[i], buf[i + 1], buf[i + 2], buf[i + 3]]
        };
        let centre = at(&px, w, w / 2, h / 2);
        assert!(centre[1] > 180 && centre[2] > 180, "disk centre is opaque tint, got {centre:?}");
        let corner = at(&px, w, 0, 0);
        assert!((corner[0] as u32 + corner[1] as u32 + corner[2] as u32) < 20, "corner empty, got {corner:?}");

        let lit_base = px.chunks_exact(4).filter(|p| p[3] > 128).count();
        // Expected disk area in glyph px: π r², r = radius_frac * g/2.
        let r = radius_frac * g / 2.0;
        let area = std::f32::consts::PI * r * r;
        assert!(
            (lit_base as f32) > area * 0.6 && (lit_base as f32) < area * 1.6,
            "lit area {lit_base} ≈ disk area {area:.0} (a real shape, not a blob)"
        );

        // ── crispness: same atlas, 2× glyph → ~4× lit area (sharp edges, no smear) ──
        let (w2, h2) = (96u32, 96u32);
        let g2 = 48.0f32;
        let x2 = (w2 as f32 - g2) / 2.0;
        let y2 = (h2 as f32 - g2) / 2.0;
        let spr2 = screen_px_range(px_range, g2, tile as f32);
        let glyph2 = GlyphInstance::new([x2, y2, x2 + g2, y2 + g2], [0.0, 0.0, 1.0, 1.0], [0.2, 0.9, 1.0, 1.0], spr2);
        let px2 = render_glyphs(w2, h2, tile, &atlas, &[glyph2]).expect("adapter present (base render succeeded)");
        let lit_2x = px2.chunks_exact(4).filter(|p| p[3] > 128).count();
        let ratio = lit_2x as f32 / lit_base as f32;
        assert!(
            (3.0..5.0).contains(&ratio),
            "2× zoom grows area ~4× ({ratio:.2}×) — edges stay crisp, not blurred"
        );
    }
}