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

+17 -20
View File
@@ -2,8 +2,8 @@ use crate::{
Axis, Len, RenderedText, Size, SizeCtx, StrongWidget, TextAttrs, TextBuffer, TextData,
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
render::{
GLYPH, GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst,
PrimitiveKind, TEXTURE, TexturePrimitive,
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
TexturePrimitive,
},
util::Vec2,
};
@@ -23,12 +23,14 @@ pub struct Painter<'a> {
}
impl<'a> Painter<'a> {
fn primitive_at<P: Primitive>(
&mut self,
kind: PrimitiveKind<P>,
primitive: P,
region: UiRegion,
) {
fn primitive_at<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
let kind = self.rsc.ui_mut().primitives.kind::<P>();
self.write(kind, primitive, region);
}
/// For a caller with many of one primitive to write, since looking the kind
/// up is per type rather than per instance.
fn write<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P, region: UiRegion) {
let h = self.state.layers.write(
self.layer,
PrimitiveInst {
@@ -51,17 +53,12 @@ impl<'a> Painter<'a> {
}
/// Writes a primitive to be rendered
pub fn primitive<P: Primitive>(&mut self, kind: PrimitiveKind<P>, primitive: P) {
self.primitive_at(kind, primitive, self.region)
pub fn primitive<P: Primitive>(&mut self, primitive: P) {
self.primitive_at(primitive, self.region)
}
pub fn primitive_within<P: Primitive>(
&mut self,
kind: PrimitiveKind<P>,
primitive: P,
region: UiRegion,
) {
self.primitive_at(kind, primitive, region.within(&self.region));
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
self.primitive_at(primitive, region.within(&self.region));
}
pub fn set_mask(&mut self, region: UiRegion) {
@@ -104,7 +101,6 @@ impl<'a> Painter<'a> {
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(
TEXTURE,
TexturePrimitive {
slot: handle.slot(),
},
@@ -123,6 +119,7 @@ impl<'a> Painter<'a> {
}
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
for glyph in text.glyphs.iter() {
let mut region = origin;
region.x.end = region.x.start;
@@ -130,8 +127,8 @@ impl<'a> Painter<'a> {
let mut region = region.offset(UiVec2::abs(glyph.offset));
region.x.end = region.x.start + UiScalar::abs(glyph.entry.width as f32);
region.y.end = region.y.start + UiScalar::abs(glyph.entry.height as f32);
self.primitive_at(
GLYPH,
self.write(
kind,
GlyphPrimitive {
uv_min: glyph.entry.uv_min,
uv_max: glyph.entry.uv_max,