Commit Graph
4 Commits
Author SHA1 Message Date
irisandClaude Opus 5 79dcc156c9 Give the atlas to the primitive that samples it, not to every shader
`Primitive::SAMPLES` says what a primitive samples and how often it has to be
bound: `Atlas` once for a list, `Image` for one instance alone. Group 2 is
that, whichever it is, so a rect's pipeline has no texture and no sampler in
its layout and the prelude hands out neither.

The two differ only in the view dimension and in what binds them, so they
share `sampled_layout` and `sampled_group`; `GpuPages` owns its bind group
again and rebuilds it when the array grows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 17:43:55 -04:00
irisandClaude Opus 5 29d390da52 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>
2026-09-13 16:14:53 -04:00
iris b3d3da5dab Draw textures separately, and keep them out of Primitives
Interleaving textures with primitives was solving a problem that does not
exist: within a layer, order is already undefined because freeing an
instance swap-removes it, and layering is what layers are for. So the run
batching is gone.

A layer is now `LayerDraws`: a `Primitives` and a texture `InstanceList`
side by side, with `updated` covering both. `Primitives` holds only
primitives again -- its instance list plus the group-1 data those
instances read -- and `InstanceList` is the shared push/free/apply_free
the two lists have in common rather than a second copy of it.
`PrimitiveHandle` names which list with `InstanceKind`, and
`PrimitiveChange` carries the same, since the two index independently.
The handle no longer carries a group-1 index at all: a primitive
instance already records where its entry is.

The renderer gives each layer a second instance buffer and draws its
textures one at a time after the instanced draw, each binding its own
group 2.

Review fixes alongside: `GlyphAtlas::allocate` returns the `PageUpload`
it reserved instead of a bare tuple; a page or image region uploads
through a new `write_region`, which passes the row stride to
`write_texture` rather than copying the rectangle out first.

Verified by replaying taps into the `tabs` example: three images added
and one deleted leaves two drawn with two live texture slots, and the
masked text-edit tab still clips, with the images freed on tab switch.
2026-09-13 13:32:05 -04:00
iris 0106257be0 Separate atlas pages from textures, and draw both through one instance list
Rework of the review on #11. Pages and standalone images were one
`Textures` manager separated by a `TextureKind` tag, and images were a
second instance list beside `Primitives::instances`. The tag forced
`image_index()`/`layer()` to panic on the wrong kind of handle, and the
second list forced an `is_image` branch through `free`, `region_mut`,
`apply_free` and `PrimitiveChange`.

Pages are now their own thing. `GlyphAtlas` owns its page images
outright and hands the renderer dirty rectangles; `GpuPages` owns the
array texture they upload to. `Textures` is standalone images only, so
`TextureHandle` has one kind, `slot()` cannot be wrong, and nothing
needs a free list that skips pages. `GlyphAtlas::insert` no longer takes
a `Textures`, which drops that parameter from `TextData::render` and
`SizeCtx` too.

Images go back through the one instance list. A texture instance is an
ordinary `PrimitiveInstance` whose `idx` names a texture rather than a
group-1 entry, which `PrimitiveHandle::data_idx: Option` records.
`RenderLayer::plan_draws` batches the layer's instances into runs
sharing a bind group, so a ui with no images still plans a single draw,
and an image draws in instance order rather than on top of its layer.

Group 2 is now one `texture_2d_array` and a sampler, bound per run: the
atlas for rects and glyphs, or one image viewed as an array of one. That
removes the second texture binding and the 1x1 null view that had to
fill it. Masks move to group 3, so resizing that buffer no longer stales
every texture bind group, and `GpuTextures` no longer reports whether
the caller must rebuild one.

`GlyphPrimitive` drops its manual pad: the WGSL struct now declares the
uvs as scalars, which matches the Rust layout exactly. `#[repr(C,
align(8))]` would have left real padding bytes, which `bytemuck::Pod`
forbids.

Verified with a headless run of the `tabs` example and of a scratch
example mixing images, rects, glyphs and a mask in one layer; 52 glyphs
at size 300 grew the atlas array from 1 to 4 layers with every earlier
page still sampling correctly.
2026-09-13 12:47:41 -04:00