# Phase 0 — substrate for iter4 Opened **2026-05-19** after [iter3 close GREEN](phase8_iteration3_close.md). ## Locked research question — iter4 > **Sample a 4×4 R8G8B8A8_UNORM source texture (uploaded via staging buffer + `vkCmdCopyBufferToImage`) in a fragment shader via `texelFetch(sampler, ivec2(gl_FragCoord.xy) % 4, 0)`, into a 64×64 attachment. Verify every output pixel at (col, row) equals the source texel at (col%4, row%4) — a 16×16-tile-repeated 4×4 pattern.** > > Source texel encoding: `R = 0x10 + 0x40*x`, `G = 0x10 + 0x40*y`, `B = 0x80`, `A = 0xff` → texel(0,0) = `0xff801010`, texel(3,3) = `0xff80d0d0`. 16 unique values, position-identifiable. > > If GREEN → iter5 adds vertex buffer or UBO. If RED → first interesting bug, characterize against the Bifrost descriptor model. ## Why this shape iter1+2+3 closed the compute, image-side, and graphics-pipeline paths. **iter4 is the first iter that exercises the Bifrost-specific descriptor model** (`PANVK_BIFROST_DESC_TABLE_COUNT`, `bifrost/panvk_vX_meta_desc_copy.c`, `panvk_vX_nir_lower_descriptors.c` Bifrost paths). This is the most-likely-to-find-bugs surface area we've encountered so far. What iter4 adds: - Source texture image (SAMPLED|TRANSFER_DST, 4×4 RGBA8) - Texture upload via staging buffer + `vkCmdCopyBufferToImage` - `VkImageView` on the texture (SHADER_READ layout target) - `VkSampler` (NEAREST filter, CLAMP_TO_EDGE — sampler attached for descriptor binding but not exercised by `texelFetch`) - Descriptor set layout with COMBINED_IMAGE_SAMPLER binding - Descriptor pool + allocate set - `vkUpdateDescriptorSets` with image+sampler - Pipeline layout with descriptor set layout (non-empty) - `vkCmdBindDescriptorSets` for graphics bind point - Fragment shader with `texelFetch` from descriptor What iter4 does *not* add: vertex buffer (still fullscreen triangle from `gl_VertexIndex`), UBO, push constants, multiple draws, mipmaps, MSAA, depth/stencil, sampler filtering (NEAREST + texelFetch == no filter), legacy render pass. ## Why `texelFetch` and not `texture()` `texture(sampler, uv)` exercises filter logic (bilinear sampling, wrapping). Any bug there could mask whether the underlying *fetch* worked. `texelFetch` skips filtering and addressing — it's a direct memory-coordinate read. Isolates the descriptor model + image read from the sampler-state machinery. If iter4 passes with `texelFetch`, iter5 can add `texture()` to test sampler state separately. ## Why 4×4 and not 1×1 or larger - 1×1 would side-step any layout/tiling code in the source texture (single texel fits in one byte position). - 4×4 fits in the smallest Mali tile (1×1 tile per Mali's accounting) but still has 16 distinct positions to verify against. - Larger (8×8, 16×16, etc.) would add more verification work without exercising different code paths until we hit multi-tile boundaries — that's an iter6+ question. ## Hypothesis space — where iter4 may fail first 1. **Source texture upload (`vkCmdCopyBufferToImage` to TRANSFER_DST).** First time we go buffer→image (iter2 was image-clear, iter3 was image→buffer). Bifrost's tile-layout transform for *writes* into an optimal-tiled image may have bugs the read path didn't exercise. 2. **Layout transition TRANSFER_DST → SHADER_READ_ONLY_OPTIMAL.** New layout never used before. Cache-flush behavior between transfer-write and shader-read on Bifrost is implementation-specific. 3. **`VkSampler` creation.** First time. Sampler descriptor layout differs across Mali generations; Bifrost's may have stale fields the v7 path doesn't populate correctly. 4. **`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` descriptor binding.** This is the **headline hypothesis**. Bifrost's descriptor table model (`PANVK_BIFROST_DESC_TABLE_COUNT`) is structurally different from Valhall's. iter1 used `STORAGE_BUFFER` (a simpler descriptor type), iter3 used no descriptors. This is the first test of the descriptor model on the graphics pipeline. 5. **NIR lowering for `texelFetch` on Bifrost.** `panvk_vX_nir_lower_descriptors.c` contains Bifrost-conditional paths (per the iter1 grep). If the lowering for sampled-image fetch on Bifrost is broken, we'll get a compile-time or run-time shader failure. 6. **Bifrost sampled-image read instruction emission.** Even with correct lowering, the actual ISA emission for `texelFetch` on Bifrost may have bugs. We can't easily distinguish this from H5 without `RADV_DEBUG=...`-style Mesa env vars (PanVk has `PAN_MESA_DEBUG=trace` etc. — out of scope for iter4 unless we hit a failure). ## Phase 0 deliverables - This document. - iter4 in scope: the textured-quad probe. ## In-scope (LOCKED 2026-05-19 for iter4) - Hardware: ohm only. - Source texture: 4×4 R8G8B8A8_UNORM, optimal tiling, SAMPLED|TRANSFER_DST. - Sampler: NEAREST filter, CLAMP_TO_EDGE (attached for descriptor; not exercised by texelFetch). - Pipeline: 1 descriptor set with 1 COMBINED_IMAGE_SAMPLER binding. - Fragment shader: `texelFetch(tex, ivec2(gl_FragCoord.xy) % 4, 0)`. - Verify: every pixel matches modulo-4 tile-repeated pattern. ## Out-of-scope (LOCKED 2026-05-19 for iter4) - Vertex buffer / vertex input. - UBO, SSBO, push constants. - Sampler filtering (NEAREST + texelFetch == no filter). - Mipmaps, layered textures, depth textures. - Legacy render pass. - MSAA. - Multiple textures / multiple descriptor bindings. - Image format other than RGBA8 UNORM. - Mesa debug env vars (`PAN_MESA_DEBUG`, etc.) — defer until needed. ## Reference - [phase0_findings.md](phase0_findings.md) — campaign substrate. - [phase8_iteration{1,2,3}_close.md](phase8_iteration1_close.md) — prior iter closes. - Mesa source: `src/panfrost/vulkan/panvk_vX_nir_lower_descriptors.c`, `bifrost/panvk_vX_meta_desc_copy.c`, `panvk_vX_cmd_desc_state.c`.