Files
ai-app/docs/TEXTURES.md
T

71 lines
3.7 KiB
Markdown

# How iris renders an unbounded number of images
Iris cannot require Vulkan descriptor indexing. The Android Vulkan Profile
2025, covering 80.1% of active Vulkan-capable Android devices as of October
2025, does not require `VK_EXT_descriptor_indexing` or its bindless texture
features ([Android Vulkan profiles](https://developer.android.com/ndk/guides/graphics/android-vulkan-profile)).
Arm guarantees the extension only on Valhall and fifth-generation GPUs
([Arm Vulkan guidance](https://developer.arm.com/mobile-graphics-and-gaming/vulkan-api-best-practices-on-arm-gpus)).
The emulator also rejects wgpu requests for `TEXTURE_BINDING_ARRAY`,
`SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING`, and
`PARTIALLY_BOUND_BINDING_ARRAY`. Those features therefore must not enter
Iris's required device feature set.
## Current design
Glyph atlas pages are layers of one `texture_2d_array`. `GpuTextures` doubles
the array when it runs out of layers, copies the old layers on the GPU, and
rebuilds every bind group that referenced the old view. Page numbers are
assigned synchronously by `Textures::add_page` because glyph insertion needs
the layer before the renderer processes queued texture updates.
Standalone images each own a bind group and are not placed in the glyph
array. Each render layer keeps ordinary rect/glyph instances separately from
image instances. It draws the ordinary batch once, then binds and draws each
standalone image. This removes any fixed image count at the cost of one bind
and draw call per visible image, which is the appropriate tradeoff for phone
transcripts containing a modest number of screenshots.
The masks storage buffer appears in every image bind group. If that buffer or
the atlas array is reallocated, all affected bind groups must be rebuilt;
retaining a bind group across either reallocation would leave it pointing at
the old GPU resource.
Texture updates accumulate their rebuild requirement with OR. A patch must
never clear a rebuild requested by an earlier push in the same batch.
Within a render layer, images are drawn after rects and glyphs. Both primitive
lists use `swap_remove`, so no code may infer draw adjacency from arena
adjacency after a free.
Standalone images currently use `NonFiltering` sampling. Thumbnail scaling
and filtering remain image-widget decisions, not texture-storage decisions.
## Colour convention
Palette literals and decoded image bytes enter Iris as straight-alpha sRGB.
Solid paints are converted once when registered and stored in a separate GPU
paint table as linear `vec4<f32>`; rect and glyph primitives carry only the
paint-table index. `Paints::set` rewrites a slot in place, so a shared theme
can change without rebuilding widgets or primitive buffers. A rect may also
hold a `Paint` definition and resolve it to a `PaintId` on its first draw;
independently constructed definitions deliberately receive independent slots.
Standalone images, colour glyphs, and atlas pages use
`Rgba8UnormSrgb`, which makes sampling decode their RGB channels to linear
light. Shaders therefore always return linear values. Both window backends
render through an sRGB texture view in `SurfaceColorSpace::Srgb`, which
encodes exactly once on output; the clear colour is linear too. A surface
without an advertised RGBA/BGRA sRGB view and sRGB output space is rejected
rather than silently displaying a different colour pipeline. The readback
test in `iris/tests/color_space.rs` guards the solid, image, and in-place
theme-update paths end to end.
## Verification rig
`scripts/rigs/gpu-probe` requests Iris's exact feature and limit set without a
window. Run it on the target device when changing renderer requirements. A
successful desktop adapter is not evidence that the same feature is available
on Android hardware.