Rendering Pipeline
Chromium’s seven-stage sequence (Parse, Style, Layout, Paint, Compositing, Raster, Display) that transforms HTML, CSS, and JavaScript into pixels in the RenderingNG architecture, with each stage running on a specific thread and process and exposing a distinct failure mode.
The Chromium project calls its current rendering architecture RenderingNG (a contraction of Rendering plus Next Generation) to distinguish it from the pre-2021 architecture that did not consistently use the compositor for every page. Philip Rogers’ 2021 article series on developer.chrome.com introduced the name and the canonical seven-stage breakdown. The same series uses the rendering pipeline and the pipeline as shorthand for the stage sequence; the singular pipeline in this book always refers to RenderingNG, not to other graphics pipelines (GPU command pipelines, video decode pipelines) the codebase also names.
What It Is
The pipeline is the path content takes from network bytes to lit pixels. Seven stages run, in order, on specific threads and in specific processes, and the stage at which a frame fails determines the user-visible symptom.
- Parse turns the HTML byte stream into a DOM tree and the CSS byte stream into a CSSOM tree. It runs on the renderer’s main thread (with off-thread tokenization for HTML and an off-thread parser preload scanner that races ahead to start fetching subresources). The CSS parser is single-threaded today. Parse runs once per document and incrementally as additional bytes arrive.
- Style computes the cascaded, inherited, and resolved style for every element by matching CSS rules against the DOM. It runs on the renderer’s main thread. The output is a
ComputedStyleper element, holding the values the next stage needs (display, position, font, the box-model dimensions in resolved units). - Layout computes the geometry of every box: the position and size of each element on the page, accounting for the parent’s containing block, the cascade of style, the fonts that have loaded, and the viewport’s current dimensions. It runs on the renderer’s main thread. The current implementation is
LayoutNG, which replaced the legacyRenderTreetraversal between 2019 and 2022 and produces an immutable fragment tree per layout. - Paint walks the fragment tree and produces a display list: an ordered series of drawing commands (
drawRect,drawText,drawImage,clip) that, when executed, would produce the page’s image. Paint does not produce pixels; it produces the command stream the next stages execute. Paint runs on the renderer’s main thread. - Compositing partitions the display list into independent compositor layers (regions of the page that can be transformed, faded, or scrolled without re-running Paint) and uploads each layer’s display list to the compositor thread. Compositing on the main thread is small; the layer-tree management afterward runs on the renderer’s compositor thread, off the main thread.
- Raster turns each compositor layer’s display list into a texture (a 2D array of pixels) by invoking Skia, the rasterization library. The work happens in the renderer’s raster worker threads (CPU rasterization) or in the GPU process (GPU rasterization through
Skia Graphiteor, on hardware without modern API support,Skia Ganesh). Raster is the stage the Skia Graphite Transition reorganized. - Display composites the rastered layers into a single back buffer in the GPU process, applies any final per-layer transforms, and swaps the back buffer to the screen at the next vertical blank. Display lives entirely in the GPU process.
The main thread runs Parse, Style, Layout, and Paint. The compositor thread runs the post-Paint half of Compositing. The renderer’s raster worker pool and the GPU process run Raster and Display. The split is the foundational performance fact. Work that fits the compositor thread’s capabilities (transforms, opacity changes, scroll, fixed-position elements) can run at 60 frames per second without ever touching the main thread. Work that requires the main thread (DOM mutation, style recalculation, layout invalidation, paint invalidation) competes with JavaScript execution and input handling for the same 16 ms frame.
Why It Matters
Without the stage vocabulary, performance discussions collapse to “the page is slow,” and there is no path from that complaint to an engineering decision. With the vocabulary, the same page becomes legible: it is slow at Layout (a DOM mutation invalidates layout for one third of the page on every keystroke), it is slow at Style (an animated CSS variable forces style recalculation on a deep subtree), it is slow at Raster (an unbounded shadow filter on a 4K image produces a 30 ms paint), or it is slow at Display (the GPU process is producing dropped frames because of a backed-up command queue). Each of those diagnoses points at a different engineering move, and the names are the prerequisite for telling them apart.
The split between main-thread stages and off-main-thread stages also names what the project calls the compositor-only path: a class of changes (transform, opacity, filter, scroll position) that can be expressed as compositor operations on existing layers, bypassing Layout and Paint entirely. A scroll handler that adjusts transform: translateY() runs at 60 frames per second on the compositor thread no matter what the main thread is doing; a scroll handler that adjusts top: triggers Layout and Paint on every scroll event and contends with everything else the main thread has queued. The two implementations look almost identical in code and differ by an order of magnitude in user-perceived smoothness. The pipeline is the vocabulary that makes the difference describable.
For Chromium itself, the pipeline structures every performance-related architectural decision in the project. The Multi-Process Architecture distributes the seven stages across processes: the renderer holds the main-thread half and the compositor thread, the GPU process holds Raster and Display, the browser process holds the navigation state that drives Parse to begin. Site Isolation further partitions the renderer-side stages: a page with cross-site iframes runs Style and Layout for each iframe in its own renderer, with the parent’s compositor thread aggregating the per-frame results. The Skia Graphite Transition replaced the Raster-stage backend without touching the rest of the pipeline. Each of those decisions is locatable on the pipeline before it is interpretable.
For an AI coding agent generating front-end code, the pipeline is the load-bearing reference the generated code must respect. A style change that animates a property in the main-thread half of the pipeline (width, height, top, left) is a different cost than a style change that animates a property in the compositor-only path (transform, opacity); an agent that doesn’t hold the distinction generates animations that look correct in development and stutter in production. The agent’s grounding context needs the seven-stage map to produce code that meets the RAIL Performance Model’s budgets rather than violating them by default.
How to Recognize It
The pipeline is directly observable from several surfaces a reader sitting at a running browser already has.
The DevTools Performance panel labels every event with the stage it belongs to. The timeline view renders a colored band per stage: Parse and Compile events in yellow, Style and Layout in violet (with sub-labels for Recalculate Style and Layout), Paint in green, Composite Layers and Update Layer Tree in light green, Rasterize Paint in green-grey, and the GPU and display events in a separate GPU track at the bottom. A frame whose total duration exceeds 16 ms shows up in red on the Frames ribbon at the top, and clicking the frame surfaces a per-stage breakdown that names which stage exceeded its share. The categorization is the same vocabulary the RenderingNG documents use; learning to read the panel is learning to recognize the pipeline.
The Chromium tracing infrastructure (chrome://tracing, the source format DevTools loads when given a saved trace) marks events with stage-aware category labels: blink, cc (chromium compositor), gpu, viz (the GPU process’s compositor sub-component), paint, loading. A trace from a slow page reveals where the time actually went; a trace from a fast page reveals which stages stayed inside their per-frame budgets. Internal benchmarks like MotionMark and Speedometer produce per-stage breakdowns the project uses to compare backends and detect regressions.
The source tree maps each stage to a code subtree: third_party/blink/renderer/core/html/parser/ for Parse, third_party/blink/renderer/core/css/resolver/ for Style, third_party/blink/renderer/core/layout/ for Layout (with layout_ng/ for the current LayoutNG implementation), third_party/blink/renderer/core/paint/ for Paint, cc/ (the chromium compositor) for Compositing’s layer-tree half, third_party/skia/ and components/viz/ for Raster, and components/viz/service/display/ for Display. A regression bisect that lands inside one of these subtrees identifies the pipeline stage directly.
A simpler recognition cue is the relationship between a CSS property and the pipeline stage it forces. The csstriggers.com reference (now maintained as the CSS Property Triggers table) lists which CSS properties trigger Layout, which trigger Paint, and which can be handled on the compositor alone. Animating transform or opacity triggers only Compositing; animating width, height, top, or left triggers Layout, Paint, and everything downstream. The table is the cheat-sheet version of the pipeline; the underlying stage sequence is what lets a reader predict the entries the table records.
How It Plays Out
Three scenarios illustrate the pipeline’s daily diagnostic value.
A team building an enterprise dashboard reports that opening a particular panel takes 1.2 seconds and feels slow. A profile reveals 800 ms of Parse and Compile time and 200 ms of Layout time. The diagnosis is not “JavaScript is slow”; the diagnosis is that the panel is bundled as a single 2 MB JavaScript file that the renderer parses synchronously on first open, and that the panel’s initial render mutates layout for the entire viewport rather than a sub-region. The fix is a code split (the bundle is broken into a small startup chunk and a deferred chunk loaded after first paint) and a layout boundary (a contain: layout rule on the panel’s root scopes layout invalidation to the panel rather than the full document). The pipeline named both fixes precisely: the parse cost is at Parse, the invalidation cost is at Layout, and each one has its own remediation.
A games studio shipping a WebGL canvas product on a Chromium-based runtime reports that scrolling outside the canvas stutters even though the canvas itself runs smoothly. A profile reveals that scrolling triggers a full-document Paint pass on every frame, because a CSS rule applies a background-attachment: fixed image to the page body. The background image cannot be promoted to a compositor layer and must be re-painted on every scroll. The diagnosis is at Paint; the fix is to remove the background-attachment: fixed (the rule was inherited from a starter template and was never load-bearing for the product’s design) and the scroll stutters disappear. The Composite stage was healthy the whole time; it was Paint that ran out of budget.
A team building a video editor with timeline scrubbing reports that scrubbing introduces visible tearing on a 120 Hz display. A trace reveals the renderer-side compositor is producing frames at 120 Hz cleanly, but the GPU process is dropping every fourth frame at the Display stage. The diagnosis is at Display, not at Paint or Raster; the cause is a per-frame OffscreenCanvas transfer that the GPU process’s command queue is back-pressuring on. The fix is to keep the canvas surface on the GPU side rather than transferring through transferToImageBitmap() on every frame. The diagnosis required attributing the dropped frames to Display rather than to the main-thread stages where the team’s instinct had been to look.
Consequences
Naming the seven stages buys several operational properties.
Performance work becomes diagnosable by category. A slow page is rarely uniformly slow; it is slow at a specific stage, and the per-stage diagnosis points at a per-stage remediation. The pipeline is the vocabulary that turns “the page is slow” into a tractable engineering question.
Performance regressions become attributable. The Perf Sheriff rotation triages regressions by reading the stage-attributed signal from the regression detector; a regression that fires on the Animation budget is a different problem (likely Compositing or Display) than a regression that fires on the long-tasks histogram (likely Parse, Style, Layout, or Paint). The triage cost differs by an order of magnitude depending on whether the stage is right.
Architectural decisions become locatable. The Multi-Process Architecture, Site Isolation, Skia Graphite Transition, and the project’s ongoing GPU-process consolidation work each rearrange specific stages of the pipeline. A reader who holds the seven-stage map can place each architectural decision on it and predict which stages the decision will move, accelerate, or slow.
The pipeline also names what it doesn’t include. Network fetches happen upstream of Parse and are governed by the network stack, the resource fetcher, and the priority hints API; they are not stages of the rendering pipeline even though they precede it. JavaScript execution interleaves with Parse, Style, and Layout on the main thread but is not itself a pipeline stage; the engine running it (V8) is its own subsystem. The pipeline’s job is to turn parsed content into pixels; everything upstream and downstream of that job sits in adjacent subsystems with their own architectures.
The cost of holding the pipeline in mind is real. Front-end code that respects the stage structure (compositor-only animation, scoped layout containment, idle-time deferred parse) reads as more verbose than code that ignores it; the verbosity is the cost the structure asks the page to pay. The pipeline is the framing that makes the verbosity worthwhile.
Notes for Agent Context
An AI coding agent producing front-end code targeting Chromium treats the rendering pipeline as seven distinct stages with three distinct execution contexts: Parse, Style, Layout, and Paint on the renderer’s main thread; Compositing’s layer-tree work and scroll on the renderer’s compositor thread; Raster and Display in the GPU process. Never animate width, height, top, left, margin, padding, border-width, or any property whose change requires Layout; animate transform and opacity instead, which run on the compositor without re-entering the main-thread stages. When a layout cost is unavoidable, scope it with contain: layout style on the affected subtree so the invalidation does not propagate to ancestors.
Never read layout-dependent values (offsetWidth, offsetHeight, getBoundingClientRect(), scrollTop) inside an animation frame after a style mutation in the same frame; the read forces a synchronous Layout pass that drops the frame. Code that needs the pipeline to bypass Layout for a region should set contain: paint or will-change: transform to give the compositor an explicit promotion hint; do not over-apply will-change to elements that don’t need it, because every promoted layer costs GPU memory.
Related Patterns
Sources
The canonical source for the current pipeline structure is the Chromium project’s RenderingNG article series by Philip Rogers, published on developer.chrome.com between 2021 and 2024; the introductory article names the seven stages and the data-flow diagram, and subsequent articles in the series go deep on individual stages. The Life of a Pixel lecture by Steve Kobes, recorded for Chrome University and re-published annually on the Chrome Developers YouTube channel, is the matching long-form treatment and is the most thorough walkthrough of the stages in motion. The LayoutNG design document by Christian Biesinger and the Blink Style Recalc document by Rune Lillesveen, both in the Chromium docs/ tree, are the authoritative descriptions of the Layout and Style stages respectively. The cc/ and viz/ subsystem documentation and the Skia project’s own Graphite design notes cover the Compositing-through-Display half of the pipeline. Håkon Wium Lie and Bert Bos’s CSS specifications and the WHATWG HTML standard are the upstream layer the pipeline implements; the pipeline is what the platform calls the implementation of those specifications.
Technical Drill-Down
- RenderingNG, Philip Rogers,
developer.chrome.com(2021 onward) — the canonical article series; the introductory page names the seven stages and the data-flow diagram the rest of the series elaborates. - RenderingNG Data Structures, Philip Rogers,
developer.chrome.com— the data-structure follow-up; describes the immutable fragment tree (Layout output), the property trees (Compositing input), and the display lists (Paint output) that flow between stages. - Life of a Pixel, Steve Kobes, Chrome University — the annual long-form lecture walking through a single frame from Parse to Display; the most thorough public treatment of the stages in motion.
third_party/blink/renderer/core/layout/— the Layout subtree;layout_ng/holds the current LayoutNG implementation that replaced the legacyRenderTreetraversal between 2019 and 2022.third_party/blink/renderer/core/paint/— the Paint subtree; the display-list builder is here and is the input to the compositor.cc/— the Chromium Compositor; manages the layer tree, the compositor thread’s per-frame loop, and the handoff to the Viz service.components/viz/— the Viz service in the GPU process; runs Raster (through Skia) and Display.- CSS Property Triggers reference — the quick-reference table for which CSS properties trigger Layout, Paint, or Compositing; useful as a lint surface during code review.