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

+10 -6
View File
@@ -5,7 +5,12 @@ use crate::{
use image::RgbaImage;
use swash::scale::image::{Content, Image};
const PAGE: u32 = 1024;
/// Side of one atlas page, in pixels. 1024 is 4 MB at RGBA8 -- enough for a
/// few thousand glyphs at UI sizes, and small enough that a page nobody fills
/// is not a big waste. Also the fixed width/height of every layer of the
/// shared array texture in `render::texture` -- `pub(crate)` so that module
/// can size it without a second constant to keep in sync.
pub(crate) const PAGE: u32 = 1024;
/// Transparent margin kept around every glyph, so that sampling one cannot
/// pick up its neighbour along a shared edge.
@@ -35,8 +40,8 @@ pub struct GlyphEntry {
pub width: u32,
pub height: u32,
pub is_colored: bool,
pub view_idx: u32,
pub sampler_idx: u32,
/// The atlas array layer this glyph's page occupies.
pub layer: u32,
}
impl GlyphEntry {
@@ -120,8 +125,7 @@ impl GlyphAtlas {
width: w,
height: h,
is_colored: matches!(image.content, Content::Color),
view_idx: page.handle.primitive().view_idx,
sampler_idx: page.handle.primitive().sampler_idx,
layer: page.handle.layer(),
};
self.entries.insert(key, Some(entry));
Some(entry)
@@ -137,7 +141,7 @@ impl GlyphAtlas {
return (i, x, y);
}
let handle = textures.add(RgbaImage::new(PAGE, PAGE));
let handle = textures.add_page(RgbaImage::new(PAGE, PAGE));
self.pages.push(Page {
handle,
x: PAD + w + PAD,