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.

  1. 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.
  2. SurfaceDrawQuads: When the aggregator encounters a SurfaceDrawQuad, it pauses its walk of the current frame. It uses the SurfaceId inside that quad to look up the child renderer's Surface in memory.
  3. Recursive Merging: The aggregator fetches the active CompositorFrame from the child's Surface and 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.
  4. Flattening: The aggregator replaces the SurfaceDrawQuad with the actual visual quads from the child renderer. If the child frame contains another SurfaceDrawQuad (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.