Tile-Based Rendering on the Arm Mali-G78AE - Tile-Based Rendering on the Arm Mali-G78AE - WP557

Decoding the Versal AI Edge Series Gen 2 and Prime Series Gen 2 GPU for Next-Gen Visualization (WP557)

Document_ID
WP557
Release_Date
2025-01-03
Revision
1.0 English

The Arm Mali-G78AE's tile-based rendering architecture is designed to minimize memory bandwidth usage by splitting the frame buffer into smaller tiles, each processed independently. This approach dramatically reduces the need for frequent memory accesses, as most operations can be handled in fast on-chip memory, reducing power consumption, and improving performance.

Key Advantages of Tile-based Rendering

Deferred Shading
Geometry is processed tile-by-tile, and only visible fragments are shaded, avoiding unnecessary computations for occluded pixels.
On-Chip Tile Memory
Intermediate results are stored in local memory, reducing external memory traffic and improving performance.

Efficient Render Pass Handling in OpenGL ES

In the Mali-G78AE tile-based rendering architecture, tile memory is initialized at the start of each render pass, and only necessary outputs are written back to system memory at the end. The intermediate frame buffer data remains in tile memory, minimizing external memory writes. To optimize render passes, follow these best practices:

Clear or Invalidate Attachments
At the start of each render pass, use glClear() or glInvalidateFramebuffer() to clear unnecessary data from attachments, unless the content is required for further rendering.
Invalidate Unused Attachments
Before switching to the next frame buffer, invalidate any attachments that are no longer needed to avoid unnecessary memory writes.
Avoid Multiple Frame-buffer Switches
Do not switch back and forth between the same frame buffer multiple times in a frame, as this increases memory flushes and introduces overhead.
Use Scissor Box for Sub-region Rendering
For rendering to a sub-region of the frame buffer, use a scissor box to restrict the area being cleared and rendered, reducing memory usage, and improving performance.

The optimized pipeline, illustrated in the Figure 1 diagram, leverages tile-based rendering to handle multiple operations, such as velocity buffer generation, shadow mapping, and post-processing, on a per-tile basis. This approach reduces memory bandwidth overhead and power consumption.

Figure 1. Efficient Timeline

Efficient Render Pass Handling in Vulkan

Unlike OpenGL ES, Vulkan provides explicit control over render passes with operations such as loadOp and storeOp, which define how tile memory is initialized and what is written back at the beginning and end of each pass. This control allows for more efficient render pass management, making it possible to fine-tune memory operations.

Key optimization techniques for Vulkan:

Use loadOp = LOAD_OP_CLEAR or LOAD_OP_DONT_CARE
At the start of a render pass, use these operations to clear or invalidate attachments, ensuring efficient memory initialization.
Transient Attachments
Attachments that are only used within a single render pass should be marked as TRANSIENT_ATTACHMENT, backed by LAZILY_ALLOCATED memory, eliminating the need for physical storage.
Use storeOp = STORE_OP_DONT_CARE
At the end of a pass, invalidate attachments that are no longer needed. For read-only attachments that were not modified during the pass, but still need to be retained, use storeOp = STORE_OP_NONE to avoid unnecessary memory writes.

Practices to avoid in Vulkan:

Avoid vkCmdClearAttachments()
Instead of clearing attachments inside a render pass, use a loadOp operation at the start of the pass to minimize overhead.
Avoid Shader-Based Clears
Instead of clearing attachments inside a render pass, use a loadOp operation at the start of the pass to minimize overhead.
Limit the Use of LOAD_OP_LOAD
Instead of clearing attachments inside a render pass, use a loadOp operation at the start of the pass to minimize overhead.
Skip Unnecessary loadOp or storeOp Operations
For attachments that are not needed in a render pass, do not define a loadOp or storeOp, as these can generate unnecessary memory transactions.
Avoid vkCmdBlitImage() for Upscaling
Up-scaling low-resolution frames using this command is inefficient, as it causes extra memory transfers.

Optimized Code for Rendering Pipeline

The following code demonstrates an optimized approach to implementing the rendering pipeline efficiently, minimizing memory operations and redundant passes.

#define CLEAR_ALL (GL_COLOR_BUFFER_BIT | \
GL_DEPTH_BUFFER_BIT | \
GL_STENCIL_BUFFER_BIT)

static const GLenum INVALIDATE_ATTACHMENTS[2] = {
    GL_DEPTH_ATTACHMENT,
    GL_STENCIL_ATTACHMENT
};

// Helper function to bind, clear, and draw to a framebuffer
void RenderToFramebuffer(GLuint framebuffer, GLenum clearBits) {
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClear(clearBits);
    glDrawElements(...); // Render elements
}

// Helper function to invalidate framebuffer attachments
void InvalidateFramebufferAttachments(GLuint framebuffer) {
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, INVALIDATE_ATTACHMENTS);
}

void RenderScene() {
    // Render off-screen shadow map pass
    RenderToFramebuffer(2, CLEAR_ALL);

    // Render off-screen velocity map pass
    RenderToFramebuffer(1, CLEAR_ALL);
    InvalidateFramebufferAttachments(2);

    // Render the main 3D scene
    RenderToFramebuffer(3, CLEAR_ALL);
    InvalidateFramebufferAttachments(3);

    // Render final output with motion blur
    RenderToFramebuffer(0, CLEAR_ALL);
    eglSwapBuffers();  // Present the final frame
    }

By optimizing the render pass structure with techniques such as tile-based rendering, sub-pass utilization, and render target reuse, significant improvements can be achieved in memory usage, power consumption, and performance. This optimized approach reduces unnecessary memory operations and minimizes frame-buffer switches, making it ideal for platforms like the Versal AI Edge Series Gen 2 and Versal Prime Series Gen 2 adaptive SoCs that use the Arm Mali-G78AE GPU.