iris: a one-layer glyph atlas is a GL_TEXTURE_2D, so every glyph drew as a box

The emulator was blamed for two days for what is iris's own defect on any
GL adapter. `GpuTextures::new` created the atlas `texture_2d_array` with
one layer; wgpu-hal picks the GL target from the descriptor alone
(`gles::Texture::get_info_from_desc`, `(false, 1) => TEXTURE_2D`), so the
shader's `sampler2DArray` was handed a `GL_TEXTURE_2D`, the unit was
incomplete, every `textureSample` returned (0,0,0,1), and `draw_glyph`'s
`color.a *= texel.a` painted the whole glyph quad.

`MIN_ARRAY_LAYERS = 2`, with the account at `create_array_texture` and a
`debug_assert!` there. Vulkan -- the phone's build and the desktop's
default backend -- was never affected.

`force-gles` now switches the desktop backend too, so the GLES path is
reproducible on a machine with a real GPU in seconds rather than only
through an APK: that is how this was found, with two shader probes
showing the sample was exactly (0,0,0,1).
This commit is contained in:
iris committed 2026-09-06 19:41:40 -04:00
1 parent 69525bd131
commit 3cb18ac5c2
5 files changed
+108 -14

No files matched your search

+14 -11
View File
@@ -609,17 +609,20 @@ agent ticks it here with the evidence.
format rather than the palette -- but it makes the desktop build
useless as a colour reference, which is exactly what P1a needed it for
when the emulator could not draw glyphs.
- [ ] **The emulator cannot draw iris's glyphs.** Under `-gpu host` with
Vulkan disabled (Mesa 26.2.2 / virgl -- what `emu` does on this
machine) every character renders as a solid filled box: the atlas
sample's alpha reads 1, which is what an incomplete GL texture returns
(0,0,0,1). Not new (`20303e0` does it too) and not the platform's
(Compose draws text perfectly on the same AVD in the same minute).
Enabling host Vulkan still dies at boot in gfxstream, and
`EMU_GPU=software` gives SwiftShader Vulkan on which iris **SIGSEGVs
in `surface_changed`**. Either of the last two would restore
appearance testing on Android; today it has to be done on the desktop
backend or on Iris's phone.
- [x] **Every glyph was a solid box on the GLES backend -- iris's bug,
not the emulator's.** Fixed 2026-09-06. The atlas is one
`texture_2d_array` and `GpuTextures::new` created it with **one
layer**; wgpu-hal picks the GL target from the descriptor
(`(false, 1) => TEXTURE_2D`), so under GLES that array was a
`GL_TEXTURE_2D` bound to the shader's `sampler2DArray`, the unit was
incomplete, every `textureSample` returned (0,0,0,1), and
`draw_glyph`'s `color.a *= texel.a` filled the quad. `MIN_ARRAY_LAYERS
= 2` in `iris/core/src/render/texture.rs`, with a `debug_assert!` at
`create_array_texture`. Vulkan (the phone, the desktop's default
backend) was never affected. Reproduce the class in seconds without an
emulator: `iris`'s `force-gles` feature now switches the **desktop**
backend too -- `./run-headless.sh transcript --shot /tmp/x.png -- -p
transcript-ui --features iris/force-gles`.
## Build (for the port)
+55
View File
@@ -5600,6 +5600,61 @@ device.
transcript-ui -p desktop-app -p tabs-ui --all-targets`
warning-free; `cargo test` 85 (iris, +4) + 13 (iris-core) +
31 (transcript-ui, +11) + 123 (client-core).
**2026-09-06, after P1a: "the emulator cannot draw iris's glyphs"
was iris's bug, not the emulator's.** The finding recorded in the
box above -- that every glyph is a solid filled box under `-gpu
host` GLES and that this is what an incomplete GL texture returns
-- had the mechanism right and the attribution wrong. It is a real
defect on **any** adapter that is GL rather than Vulkan.
- **Reproduced off the emulator entirely**, which is what made it
cheap: `default/render.rs` now honours the same `force-gles`
feature `android/render.rs` did, so
`./run-headless.sh transcript --shot /tmp/x.png -- -p
transcript-ui --features iris/force-gles` draws the boxes on this
machine's own GPU in seconds. Two shader probes then said what
the sample was: `return vec4(texel.rgb, 1.0)` drew black boxes and
`return vec4(texel.a, texel.a, texel.a, 1.0)` drew white ones, so
the atlas sample was exactly (0, 0, 0, 1) -- GL's answer for an
**incomplete texture unit**, and not the null texture (which is
zeroed, alpha 0).
- **Root cause: the glyph atlas array was created with one layer.**
`GpuTextures::new` started `array_capacity` at 1 and `grow_array`
only doubles once a page needs a layer past it, so the ordinary
case -- one atlas page -- is a one-layer array. wgpu-hal picks the
GL texture target from the descriptor alone
(`gles::Texture::get_info_from_desc`: `(false, 1) => TEXTURE_2D`),
so that array is created as a `GL_TEXTURE_2D` and then bound to
the shader's `sampler2DArray`. wgpu has a name for this
(`log_failing_target_heuristics`, its issues #1614/#1574); the
result is an incomplete unit, `texel.a == 1`, and `draw_glyph`'s
`color.a *= texel.a` paints the whole glyph quad.
- **Fix**: `MIN_ARRAY_LAYERS = 2` in
`iris/core/src/render/texture.rs` -- the array is never created
with fewer, with the account at `create_array_texture` and a
`debug_assert!` there so a future capacity arithmetic change fails
at the mistake rather than as boxes on a screen. Cost: one page of
texture memory, which the next atlas page uses anyway.
- **Not a regression from `3e72a4e..20303e0`, and the bisect was not
run.** The defect is a function of the layer count, not of any
commit in that range: it has been there since the atlas became a
`texture_2d_array` (TEXTURES.md, 2026-09-04) and it reproduces at
HEAD and disappears at HEAD with the one-line capacity change. The
claimed "visible text at `3e72a4e`" is a misreading of its own
evidence -- `/tmp/final-typing.png`, the screenshot that entry
cites, is boxes; what the agent read was the `iris text render:
chars=5 glyphs=5` log line, which reports what **parley shaped**,
not what reached the screen. The earlier genuinely-good emulator
shots (`/tmp/after3.png`, 2026-09-05 19:56) predate the APK being
built with `force-gles` at all, so they were the Vulkan path.
- **The phone build is not affected and does not need withdrawing.**
`android-app/build-apk.sh`'s default features are deliberately
without `force-gles` (`d73db97`'s comment), so a phone build takes
`Backends::PRIMARY` -> Vulkan, where a one-layer array is an
ordinary one-layer array and glyphs draw correctly -- which is
also what Iris's phone reports have shown all along. The fix
matters for any device that falls back to GLES, which is why it is
not just an emulator convenience.
- [ ] **P1b — tool-call cards and grouping.** `ToolRows.kt`/
`ToolInput.kt`'s cards: a collapsed row per call with name
and a one-line summary, expand to input and output, runs of