Take a primitive's kind from its type, and keep images out of the rest

A `Primitive` now carries its own WGSL, and `PrimitiveRegistry` keys ids by
`TypeId`, so `Painter::primitive` takes only the value and the `RECT`,
`GLYPH` and `TEXTURE` constants are gone. Registering is what a first draw
does; the built-ins are seeded up front so first-draw order cannot decide
anything about them.

What a primitive samples is no longer something every registration states.
The glyph atlas and the one sampler moved into the shared group, which is
where a mask texture would go too, so a rect's pipeline has no texture in
its layout at all. Only a primitive whose type sets `TEXTURE` gets an image
group, and that is also what records the slot at write time -- so nothing
reads a `u32` back out of the instance payload.

Verified on the headless rig: the tabs example, two images added at runtime,
an image alone in a layer, and text spanning a four-layer atlas after the
array grew twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-13 16:14:53 -04:00
1 parent 7b318e3271
commit 29d390da52
11 files changed
+268 -333

No files matched your search

+5 -2
View File
@@ -1,3 +1,6 @@
// Matches `GlyphEntry::IS_COLORED`.
const COLORED: u32 = 1u;
struct GlyphInfo {
uv_min: vec2<f32>,
uv_max: vec2<f32>,
@@ -14,8 +17,8 @@ var<storage> glyphs: array<GlyphInfo>;
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let g = glyphs[in.idx];
let uv = mix(g.uv_min, g.uv_max, in.uv);
let texel = textureSample(tex, samp, uv, i32(g.layer));
if (g.flags & 1u) != 0u {
let texel = textureSample(atlas, samp, uv, i32(g.layer));
if (g.flags & COLORED) != 0u {
return masked(in, texel);
}
var color = unpack4x8unorm(g.color);