HW / A FIELD GUIDE TO SGX535
Inside the hardware
Follow a triangle through SGX535: tiles, shader programs, shared memory and the handoffs that turn a rendered image into a displayed frame.
- THE CORE
- PowerVR SGX535
- THE RENDERER
- Tile-based deferred
- THE MEMORY
- Shared system RAM
A small netbook hides an intricate graphics system. Getting a cube onto its panel takes more than a fast shader: geometry must become tile lists, programs must receive their inputs, and several engines must agree about shared memory. Following one triangle makes those connections easier to see.
This tour follows poulsback’s hardware research. Recorded SGX rendering results come from the Dell and Fujitsu LOOX GMA500 references; GMA600 rendering needs its own validation. The diagrams explain dataflow and ownership, rather than the physical layout of the chip.
An Intel name, a PowerVR core
GMA500 and GMA600 carry Intel branding, but their 3D engine is Imagination’s PowerVR SGX535. Its instructions, command streams and rendering pipeline need their own driver. Intel’s i915 is a useful software architecture reference; its hardware commands cannot run this GPU.
Linux already includes a separate gma500 KMS framebuffer driver. Its
Linux 6.1 configuration
describes that display support. Keeping a panel lit and providing an
accelerated Mesa renderer are different jobs.
Even the PowerVR name spans different architectures. Mesa’s modern PowerVR Vulkan driver starts with Rogue, a later design. It does not supply the older SGX535 instruction compiler or submission path that poulsback needs.
GMA 600 brings a different platform
The Fujitsu STYLISTIC Q550/C adds an Oak Trail machine to the project. Linux groups its GMA600 with the SGX535 family, but the surrounding platform changes how that core connects to memory and the panel. Linux places its SGX register window at a different offset from Poulsbo. The LVDS panel uses another display pipe, and the plane has a different scanout-address interface. Porting the renderer therefore also needs platform-specific display work.
The tablet boots Debian 12 with the stock gma500 driver and a working
1280 × 800 framebuffer. That establishes a display baseline. poulsback’s
module-load and early-refusal checks have run on it; SGX rendering remains
untested, and the core identity, revision and clock remain unread.
Those checks and real rendering tests must precede any claim that the Dell’s
GPU results carry over to the tablet. The GMA600 name alone cannot supply
that evidence.
The Q550 field notes follow the Debian installation, legacy-driver search and first module checks.
Build the scene, then render its tiles
Start with three vertices. Their attributes are fetched, a vertex program computes their outputs, and geometry processing establishes the triangle’s position on screen. The TA, or Tile Accelerator, organizes primitives for the screen regions they affect. Parameter storage retains information that the renderer will need later; the DPM manages its pages.
The raster phase then works through the scene by tile. A triangle can affect several tiles, and several triangles can compete for the same pixels. Keeping work local to a screen region reduces memory traffic; visibility processing can also avoid shading hidden opaque surfaces.
Imagination’s TBDR overview and per-tile explanation introduce this architectural idea. They explain the approach rather than specifying every feature of this older core.
Geometry finishing does not make its parameters disposable: raster work still needs them. If a large scene exhausts parameter storage, partial rendering and restart become a problem of their own.
Two scenes can keep their place. SGX535 has two DPM contexts. In the tested scheduling path, each scene retains its context and region headers, letting geometry work in one overlap another scene’s raster without storing and reloading DPM state at every handoff.
What one pixel has to survive
Covering a pixel is only the beginning. A scissor can exclude it; depth and stencil tests can reject it; a fragment program must supply the colour that the draw permits writing. The ISP handles coverage, depth and stencil, while the TSP and fragment programs participate in producing colour. The PBE, or pixel back end, writes the resulting image into memory.
These are cooperating stages, not one arithmetic operation. In captured EMGD programs, blending and discard involve shader epilogues, while depth and stencil comparisons belong to ISP state.
Scissors reveal the difference between a tile and a pixel particularly well. EMGD combines a coarse tile box with viewport-pass objects that clear and set a per-pixel draw mask. Rejecting whole tiles alone cannot enforce a scissor edge that cuts through a tile.
The small programs around a shader
Think of PDS as the stage manager and USSE as the arithmetic performer. That analogy describes their jobs: both execute programs, but those programs solve different problems. Documentation calls the shader instruction stream USE code; poulsback translates Mesa’s NIR representation into those words.
A vertex PDS program turns an index and stream layout into attribute fetches by DMA, then dispatches the vertex USE task with the required dependencies. USSE performs the vertex arithmetic and produces outputs such as position and colour. Pixel PDS programs deliver fragment inputs and dispatch fragment work.
Constants have a separate journey. A SECONDARY PDS program transfers a draw’s
constants into shader registers. Its accompanying USE program can be just
nop.end: no arithmetic at all. The transfer still matters, because the
real shader must find the right values when it starts.
State emission has its own transfer limit. A state PDS task moves at most 16 words into attributes for the copy-and-emit USE program that writes TA state. Most single-sided draws fit in one packet, whereas a two-sided draw that carries both faces’ ISP words alongside its viewport and output selections reaches 19 words. The command writer then sends two state tasks in sequence: the ISP words first, followed by the remaining words, relying on the earlier state persisting in the TA stream.
Dispatch also declares resource budgets. One investigation found a vertex task whose input allowance was too small. Simple scenes rendered, but overlapping geometry and raster work triggered faults at garbage output addresses. Increasing the declared allowance fixed the tested cases. The evidence points to corruption of pixel-task attributes, although those attribute addresses were not directly observed. Correct shader maths needs correct scheduling and allocation around it.
Shared RAM still has boundaries
SGX uses system memory, but sharing physical pages does not make every address interchangeable. The SGX MMU translates GPU addresses; the display GTT translates the display controller’s view. The same suitable buffer can need both mappings, with separate owners and lifetimes.
Bringing up the Fujitsu LOOX U/C50N
made that boundary concrete. On the Dell, the BIOS places the physical display
aperture at 0xD0000000, which happened to match the private 0xD0000000
virtual window used by the SGX 2D requester in per-file SGX page tables. On
the LOOX, the BIOS places the physical display aperture at 0xF0000000.
Binding the 2D requester to the private SGX virtual layout rather than the
physical aperture base keeps display scanout mappings and SGX 2D virtual
addresses separate.
Even within SGX, requesters matter. The native 2D core reads through its 2D requester but writes through the TA requester. A copy and geometry work using different address spaces therefore cannot freely share that route at the same time. A 2D copy can overlap suitable raster work, whose access path is different.
That overlap improved the complete frame’s throughput on the Dell even though the copy and raster each took longer while running together. The engines still compete for shared resources: overlapping two operations does not make their individual costs disappear.
Three jobs behind one picture
Rendering creates an image. Presentation chooses how that image reaches the desktop. Scanout continuously reads the selected framebuffer and sends its pixels to the panel. SGX 3D, the native 2D core and the display controller have different responsibilities along that route.
A windowed OpenGL frame currently reaches the desktop through a 2D-core Present copy. An eligible fullscreen buffer can instead flip: the display starts reading another buffer, without copying the whole picture into the old one.
A rendering fence says nothing by itself about when the display stops reading. After a flip, the replaced buffer must remain alive until scanout can no longer fetch it. “Rendered” and “displayed” are different milestones; safe reuse comes after the last relevant hardware access, not simply after shader work.
Why moving a window changed its speed
A useful clue came from the same scene in the same-sized window. Moving it from x = 700 to x = 704 restored the aligned case’s rate. The shaders and geometry had not changed; the copy’s destination address had.
At four bytes per pixel, those positions put the destination 48 bytes into a 64-byte boundary or exactly on one. The unaligned copy took longer and also slowed the raster work beside it. The planner now splits sufficiently large unaligned rectangles into a short head and an aligned body, preserving copy order.
This is why a driver needs more than a triangle benchmark. Placement, presentation and memory layout can alter the cost of showing that triangle. The development note follows the frame trace behind this work.
A smaller frame still needs its own tile lists
Two frames in flight can use different render sizes. The next frame’s TA may build tile lists while the previous frame’s raster stage still reads its lists. Giving the two arrays different indices is useful only if they also occupy different bytes.
A Dell experiment exposed a mistake in poulsback’s memory layout: a 64 × 64 render followed a 256 × 256 window in the same context. Array positions were recalculated from each render’s size, so the small frame’s tile headers could overwrite the larger frame’s live headers. The ISP then accessed depth storage outside its buffer. Disabling overlap removed the fault.
The fix reserves both arrays at fixed, nonoverlapping offsets in the scene buffer. A check alternating the two sizes completed 800 jobs with 367 TA passes overlapping raster work and no fault; the final comparison frames matched their references. This was a driver layout bug. Its lesson is about lifetime: tile lists must stay separate until the renderer has finished using them. Complete framebuffer-object coverage remains a separate task.
Video has its own finish line
Video decoding belongs to MSVDX, with separate MTX firmware, messages, translation and decoded-image surfaces. Completing an SGX render does not mean a video picture has finished decoding.
VA-API is the application-facing interface. Beneath it, the driver must translate picture parameters and slices, track reference surfaces, submit the decoder’s protocol and match its replies. Showing the decoded image adds presentation and lifetime requirements again.
poulsback has CPU-checked MPEG-2 preparation, firmware-upload plans, protocol validation and a simulated transport. Physical decoding, validated output and VA-API presentation remain unfinished.
Field notes: three surprises
The most useful experiments explain a failure and leave a concrete rule for the driver. These three findings came from Dell hardware tests and captured legacy-driver behaviour.
The blitter remembers the previous job
Observed on the Dell. Finishing a 2D job does not quite end its memory story. When the next job starts, the core can touch the last page the previous job wrote, through the new job’s TA address routing. A page that was valid for one application may be absent from the next application’s address space.
The driver gives each nonempty 2D job a small, deliberate ending: a 1 × 1 fill of a reserved scratch page. Each client maps its own page at the same GPU address. That address stays valid after a routing change, even though it now names the next client’s scratch page. An otherwise puzzling extra command therefore protects the boundary between clients.
A fresh tile can inherit an old stencil
Observed on the Dell. A stencil buffer is a mask: drawing can update it, and later tests decide which pixels pass. In our experiment without depth/stencil load and store (ZLS), a new tile began with the stencil values left by the previous tile. The background object did not clear them. A new screen region was reusing internal state rather than starting blank.
Changing the background-object setting did not solve it. An explicit clear object did: it covers the target and replaces stencil values with the clear value, without writing depth or colour. The hardware test then passed.
This is why clearing colour and clearing stencil need their own treatment. A visible background alone tells us little about the hidden mask that will decide the next draw.
Sixteen bits declared, eight bits uploaded
Found in captured Linux EMGD 1.8 behaviour. One depth-texture test exposed a mismatch inside the original driver. Its descriptor told the sampler to read unsigned 16-bit texels, but the upload stored only one byte per texel, rounding each depth value to the range 0–255.
The sampler still advanced two bytes at a time. It combined neighbouring uploaded bytes as one texel, then continued beyond the uploaded data. The descriptor looked plausible; the bytes in memory explained the wrong pixels.
This was a bug in the selected legacy-driver path, not an eight-bit limit of SGX535. For poulsback, the rule is to keep actual 16-bit depth texels consistent with their descriptor. Captured commands reveal a hardware protocol, but API rules and independent reference results still decide whether the picture is correct.
Texture field notes
Textures connect several parts of the GPU at once: the sampler reads an image, the renderer may write another level of that same image, and both must agree about its layout. Three recent findings made those connections more concrete.
Six faces, one alignment trap
A cubemap stores six views around a point. Each face can also have a chain of progressively smaller images, or mip levels. Six correct face images are not enough if the sampler finds them at different addresses from the ones the driver allocated.
That happened with a 32 × 32 RGBA8 cubemap. Its six levels occupied
4 × (32² + 16² + 8² + 4² + 2² + 1²) = 5460 bytes per face. The driver
packed the faces together, fitting their 32760 bytes into a 32768-byte
buffer. But for this mipmapped form the sampler advances between faces in
2048-byte units: the required step was 6144 bytes.
The last face therefore started at byte 30720 from the sampler’s point of view. Its required top-level range extended to byte 34816 (exclusive), beyond the allocation. The Dell reported a GPU page fault. Giving each face the correct step fixed the layout, and the dedicated mipmapped-cube test matched its reference.
The rule depends on the texture form. In this layout, single-level cubemaps and mipmapped cubes whose top level is 8 × 8 or smaller pack their faces. The lesson is to derive the sampler’s layout, including padding, before deciding how much memory the texels need.
Writing one pixel inside a tile
The tested square 64 × 64 chain ends with 8 × 8, 4 × 4, 2 × 2 and 1 × 1. Those tiny levels still need to be written correctly by a tile-based renderer. For the tested packed BGRA8 targets, the size fields describe one 16-pixel tile even at the smallest level. That does not mean that the GPU must overwrite a whole 16 × 16 image.
The pixel backend also receives the level’s base address, stride and clip limits, which describe the intended pixel region. The four levels contain 256, 64, 16 and just 4 bytes. Dedicated Dell checks compared the written pixels and neighbouring levels; a plausible final 1 × 1 colour alone would have missed damage elsewhere in the chain. These comparisons cover GL-visible texels. Allocation padding after the final 1 × 1 level was outside that pixel check.
That made a complete GPU mipmap chain possible. Each pass samples the preceding level and writes the next. Alongside the mipmap generator’s full-level writes, the development path also admits target-load draws on colour-only square twiddled views of 8 × 8, 4 × 4, 2 × 2 and 1 × 1, as well as in-place rendering into canonical one-row BGRA8 levels where twiddled X addresses are linear. The mipmap development note follows the chain, its fences and the tests behind it.
Which way is Y?
Fragment shaders can ask how a value changes between nearby fragments with
dFdx and dFdy. SGX535 has native derivative instructions, but the compiler
must also respect the coordinate conventions around them.
Mesa already applies a target-dependent Y transform before our backend sees the derivative. In the qualified window path its multiplier is −1; for the qualified FBO it is +1. Adding another fixed minus in the backend would reverse a result that Mesa had already adjusted.
The checks used unequal X and Y slopes, compared both window and FBO results, and included triangle edges, viewport edges and untouched clear pixels. Because the derivative instructions operate on adjacent fragment slots without encoding image dimensions, the development path now accepts straight-line derivative expressions over smooth varyings, float uniforms and scalar arithmetic at the target’s actual dimensions, and can pair them on the window path with bounded 2D texture sampling. These checks use single-sample BGRA8 colour targets with depth, stencil, alpha testing and blending disabled. Explicit fine/coarse modes, control flow and full derivative conformance still need their own qualification.
What the working frames establish
The hardware demos show real SGX535 rendering through Mesa and X11 on their recorded builds. They demonstrate connected parts of this route, including accepted shaders and presentation. Complete OpenGL 2.1 and GLES 2.0 coverage, general numerical precision, resource teardown and broader machine qualification still need their own evidence.