Draw the glyph atlas as an array texture and images with their own bind groups

The renderer bound every texture through one
`binding_array<texture_2d<f32>>` indexed per primitive. That needs
`VK_EXT_descriptor_indexing`, which a real share of Android GPUs do not
have, so the shape did not run there at all.

Split the two things being bound, since they want opposite treatment:

- Glyph atlas pages become layers of one `texture_2d_array`. A glyph
  primitive carries a layer rather than a view/sampler index pair, and a
  layer index is an ordinary sampling operand -- no extension. Growing the
  atlas recreates the array with headroom and copies the old layers across
  GPU-side.
- A standalone image gets its own texture and its own bind group, and
  draws in its own call. It no longer needs a per-instance entry in
  `PrimitiveData` at all: the bind group has already picked the texture.

`Primitives` therefore keeps images in a list of their own, with
`PrimitiveChange::is_image` saying which list a renumbering belongs to --
the two have independent index spaces, so `(layer, inst_idx)` alone would
collide between them.

Verified on this machine's real GPU (Venus onto an RX 7900 XT, confirmed
by the loaded ICD rather than assumed): the `tabs` example renders
byte-identical screenshots before and after, both for a text-and-rect tab
and for one holding a standalone image.
This commit is contained in:
iris committed 2026-09-13 03:58:12 -04:00
1 parent 0f6a28b4dd
commit bafaa1db6d
10 files changed
+794 -255

No files matched your search

+24 -11
View File
@@ -77,17 +77,31 @@ impl<'a> Painter<'a> {
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region.within(&self.region));
self.write_image(handle.image_index(), region.within(&self.region));
}
pub fn texture(&mut self, handle: &TextureHandle) {
self.textures.push(handle.clone());
self.primitive(handle.primitive());
self.write_image(handle.image_index(), self.region);
}
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
self.textures.push(handle.clone());
self.primitive_at(handle.primitive(), region);
self.write_image(handle.image_index(), region);
}
/// A standalone image draws with its own bind group rather than sharing
/// the layer's one instanced draw, so it goes through
/// `Primitives::write_image` instead of `primitive_at`/`Primitive::vec`.
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
let h = self
.state
.layers
.write_image(self.layer, self.id, texture_idx, region, self.mask);
if self.mask != MaskIdx::NONE {
self.rsc.ui_mut().masks.push_ref(self.mask);
}
self.primitives.push(h);
}
pub fn render_text(
@@ -109,14 +123,13 @@ impl<'a> Painter<'a> {
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(
GlyphPrimitive {
uv_min: glyph.entry.uv_min,
uv_max: glyph.entry.uv_max,
view_idx: glyph.entry.view_idx,
sampler_idx: glyph.entry.sampler_idx,
color: text.color,
flags: glyph.entry.flags(),
},
GlyphPrimitive::new(
glyph.entry.uv_min,
glyph.entry.uv_max,
glyph.entry.layer,
text.color,
glyph.entry.flags(),
),
region,
);
}