The last image in the tested chain is one pixel: four bytes of BGRA8 data. Getting the driver to produce those four bytes on the GPU meant answering questions that a rotating cube had never asked.

The cube and gears had connected application draws to Mesa, SGX535 and the desktop. Texture work adds another kind of dependency: an image written by one GPU pass becomes the input to the next. The image’s layout, the order of execution and the application’s own state all have to survive that handoff.

This work is in poulsback’s development stack, ahead of the current public alpha snapshot. The selected hardware checks described here ran on the Dell GMA500.

Why keep smaller copies of a texture?

A texture viewed from a distance can cover far fewer screen pixels than it contains texels. Mipmaps provide progressively smaller versions for the sampler to choose from. They help reduce minification aliasing and the texture working set needed for distant surfaces.

For a square 64 × 64 texture, a complete chain is 64, 32, 16, 8, 4, 2 and finally 1 pixel per side. The source is level zero. Each following level halves both dimensions, until there is nowhere smaller to go.

Generating the chain on the GPU means rendering into those smaller levels. The selected path samples the preceding level with linear filtering and writes the result into the next. That brings the sampler, framebuffer writer and buffer lifetime rules into one small workload.

Six ordered GPU passes sample each preceding mip level and write the next, reducing a 64×64 source through 32, 16, 8, 4 and 2 to a 1×1 destination.
One dependency chain, six generation passes. The diagram shows ordering, not measured execution times or the GPU's physical layout.

A tile is bigger than the final image

SGX535 renders by tiles. At first that makes the end of the chain look awkward: how does a tile-based renderer write a 1 × 1 image packed beside other levels in the same buffer?

The answer required separating the encoded target extent from the actual write footprint. For the tested BGRA8 mip tails, the target size fields describe one 16-pixel tile. The pixel backend also has the destination address, stride and clip limits. Those fields let it write the smaller packed level without consuming the storage of an entire 16 × 16 image.

The small levels have very concrete spans: 8 × 8 takes 256 bytes, 4 × 4 takes 64, 2 × 2 takes 16, and 1 × 1 takes four. Each level’s address and span must be valid before the driver submits a generation pass.

The packed BGRA8 mip tail contains 8×8, 4×4, 2×2 and 1×1 levels occupying 256, 64, 16 and 4 bytes, despite the encoded one-tile extent.
Conceptual target extents and pixel regions. The actual memory is twiddled; these squares do not depict linear byte order.

The early checks wrote individual small targets and compared their pixels and neighbouring levels. Seeing the requested colour was only half the question. We also needed to know that writing the tiny destination had left the other images intact. Those comparisons cover the texture’s visible pixels; allocation padding after the final 1 × 1 level was outside that check.

Texture layouts had already produced another useful failure. A mipmapped cubemap needed padding between faces that its allocation had omitted. The sampler’s last face read crossed the end of the buffer. The hardware field guide explains that separate alignment trap. Both cases made the layout agreement between sampler, writer and allocation impossible to treat as a detail.

Six passes sharing one allocation

Once the small targets worked, the full chain could use the same procedure repeatedly. Render 32 × 32 from the 64 × 64 source, then render 16 × 16 from that result, continuing down to 1 × 1.

Those passes share a buffer object, but use different regions of it. The next sampler must see the preceding write before it starts. Each generation pass has its own submission and leaves its completion fence on the texture’s reservation. The following pass is ordered after that fence. The shared buffer is represented once as a write resource in each submission.

This also gives failures a boundary. A healthy refusal before the first generation submission can use the existing CPU generator. Once submission of a generation pass has been attempted, falling back as if nothing happened could ignore work already in flight. The implementation then stops on error and preserves the existing fences and resource ownership. The error-path checks for this change use host fixtures; the successful hardware chain does not establish GPU fault recovery.

The GPU route therefore remains deliberately bounded. Other texture forms can still use the CPU path; this change does not remove fallback throughout the driver.

The application must get its state back

The generator is itself a small rendering workload. It binds shaders, samplers and targets to draw its levels, but the application did not ask to keep those bindings. The blitter saves the application’s state and restores it afterwards.

Queries need the same care. An application’s occlusion query should measure its own drawing. Counting the driver’s internal mipmap draws would change the application’s result. Query counting is paused around the internal work.

The full-chain experiment used a constructed pattern whose 2 × 2 averages are exact integers in every channel. An independent CPU averaging check agreed with the expected pattern before any GL submission. Every RGBA byte of each generated level then matched, both when read directly and when sampled. That qualifies this pattern and path, rather than all filtering and rounding cases.

The source and application pixels stayed exact. The framebuffer, shader program, vertex-buffer and texture bindings, plus the viewport, matched the test’s expected state after generation. Two application draws around the call contributed the surrounding query’s expected 128 samples. These are specific state and query checks, rather than an exhaustive GL-state preservation test.

The trace identified six ordered native generation submissions. There were 14 completed GPU jobs in the complete test, including its other drawing and checks, with no GPU fault or held engine. The product requalification repeated the chain before the change landed with regression coverage.

The distinction matters: a correct image could also have come from a CPU generator. Direct and sampled pixel comparisons establish the image; the native submissions establish which path produced it. Both are needed for the claim that this chain ran on SGX535.

A larger chain, with specific limits

The development GPU route admits selected generation requests on square, single-layer, single-sample B8G8R8A8_UNORM sources of 32, 64, 128, 256, 512 or 2048 pixels per side, subject to its layout and draw checks. The 2048 source uses a private chain allocation, and its largest generated destination is 1024 × 1024. An existing 2048 chain also has qualified generation windows starting at base level 2 or 4, preserving levels outside the requested range.

This route is narrower than a complete framebuffer extension. The original GLX mipmap benchmark was also exercised with a process-local extension override. Successful calls under that override do not qualify the full extension for ordinary applications, other formats or every framebuffer configuration.

The next performance question is how much work a framebuffer operation really needs. Selected tests preserve existing colour correctly, but efficiently leaving untouched regions alone remains part of the work. There is more to do before the complete milestone’s correctness, performance and lifecycle requirements are met.