How Chromium's Viz Service Aggregates Renderer Frames
This article provides a technical overview of how the Viz compositor service in the Chromium web browser combines visual outputs from multiple isolated processes into a single cohesive image. Running inside the GPU process, Viz receives independent frames from the main browser UI and various renderer processes. Through a system of memory surfaces, placeholder quads, and synchronization protocols, the Viz service merges these separate visual trees into one unified frame, which is then drawn to the screen using hardware acceleration.
Compositor Clients and CompositorFrames
Chromium uses a multi-process architecture for security and stability. The main browser UI, individual web pages, and out-of-process iframes (OOPIFs) all run in separate processes. Because renderer processes are sandboxed, they cannot draw directly to the screen. Instead, the browser process and each renderer process act as clients to the Viz service, which lives inside the GPU process.
Instead of outputting raw pixels, each client generates a data
structure called a CompositorFrame. A
CompositorFrame is a lightweight blueprint of what needs to
be drawn. It consists of a list of RenderPass objects,
which in turn contain a list of DrawQuads. A
DrawQuad is a basic drawing instruction—such as drawing a
solid color, a rasterized tile of a webpage, or a video frame—at a
specific location with a specific transformation.
Surfaces and Placeholders
When a client finishes building a CompositorFrame, it
submits it to Viz over an IPC (Inter-Process Communication) channel. Viz
stores this frame in a construct called a Surface, which is
uniquely identified by a SurfaceId.
Because processes are isolated, the browser process does not know what the renderer process is drawing, and a main webpage renderer does not know what an embedded cross-origin iframe is drawing. To solve this, Chromium uses indirection.
When the browser UI wants to display a webpage, it builds its own
CompositorFrame containing the tabs and address bar. For
the area where the webpage belongs, the browser inserts a special
placeholder quad called a SurfaceDrawQuad. This quad does
not contain visual data; instead, it simply holds the
SurfaceId of the target renderer process. Similarly, if
that webpage contains an out-of-process iframe, the webpage's
CompositorFrame will include a SurfaceDrawQuad
pointing to the iframe's SurfaceId.
Surface Synchronization
Before Viz can combine these frames, it must ensure they are visually synchronized. For example, if a user resizes the browser window, the browser UI and the webpage renderer must update their frames to the new size simultaneously to prevent visual glitches or guttering.
When the parent process (like the browser UI) submits a frame
referencing a new SurfaceId for a child (the renderer) that
has not yet submitted its corresponding frame, Viz holds the parent's
frame in a "pending" state. The frame is blocked from being displayed
until the child submits the matching CompositorFrame for
that SurfaceId. Once all referenced dependencies are met—or
if a strict millisecond deadline expires to prevent the browser from
freezing—the pending frames become "active" and are ready for
aggregation.
The SurfaceAggregator
The core of the frame-merging process happens inside a Viz component
called the SurfaceAggregator. When the display hardware
signals that it is ready for a new frame (a VSync signal), Viz triggers
the SurfaceAggregator to build the final scene.
The SurfaceAggregator starts at the root
Surface, which belongs to the browser process. It reads the
active CompositorFrame in that surface and begins walking
through its list of DrawQuads.
- Standard Quads: When the aggregator encounters
standard quads (like the textures for the browser's back button or tab
strip), it copies them directly into a new, master frame called the
AggregatedFrame. - SurfaceDrawQuads: When the aggregator encounters a
SurfaceDrawQuad, it pauses its walk of the current frame. It uses theSurfaceIdinside that quad to look up the child renderer'sSurfacein memory. - Recursive Merging: The aggregator fetches the
active
CompositorFramefrom the child'sSurfaceand begins walking the child's quads. It calculates the geometric transformations and clipping rectangles required to map the child's coordinate space into the parent's coordinate space. - Flattening: The aggregator replaces the
SurfaceDrawQuadwith the actual visual quads from the child renderer. If the child frame contains anotherSurfaceDrawQuad(such as an embedded iframe), the aggregator repeats this process recursively.
Final Rendering to the Screen
Once the SurfaceAggregator finishes walking the entire
tree of surfaces, the output is a single, flattened
AggregatedFrame. All SurfaceDrawQuad
placeholders have been resolved and removed. The
AggregatedFrame now contains a complete, ordered list of
concrete drawing instructions from the browser and all visible
renderers, all mapped to the global screen coordinate space.
Viz then passes this AggregatedFrame to its display
renderer, known as SkiaRenderer. SkiaRenderer
iterates through the final list of quads and translates them into
low-level GPU API commands (such as Vulkan, Metal, Direct3D, or OpenGL).
The GPU executes these commands, compositing the textures into the final
framebuffer that is presented on the user's monitor.