Main Thread vs Compositor Thread, GPU Tiling & Paint Worklets
- Last updated
- Prerequisites:
- The rendering pipeline (layout, paint)
- Understanding of browser threads and GPU
- browser
- compositor
- gpu
- rasterization
- tiling
- threads
Read at your depth
The practical view
The browser uses multiple threads: the main thread (JS, style, layout, paint recording) and the compositor thread (GPU-friendly layer placement). Animating transform or opacity is handled by the compositor: it just repositions existing layer textures, so it runs on the compositor thread and stays smooth even when the main thread is busy. Animating width, height, or top forces the main thread to re-run layout and paint, then the compositor re-uploads — jank risk. Rasterization converts paint commands into pixels (often on the GPU via tiles). Paint Worklets (CSS Paint API) let you generate images procedurally for properties like background-image.
The same idea in other frameworks
React's concurrent rendering keeps JS off the critical path but cannot help compositor-vs-main work; transform animations still win.Angular animations that use transform are compositor-friendly; those animating layout properties are not.Vue <Transition> with transform-based classes composits smoothly; layout-based transitions jank.Svelte transitions generating transform keyframes run on the compositor; geometry animations don't.Legacy vs modern
Animating layout properties vs compositor-only transform
Animating left/top/width forces main-thread layout+paint every frame; animating transform runs entirely on the compositor thread.
el.animate([{ left: '0px' }, { left: '200px' }], { duration: 1000 }); // ❌ OLD: layout per frameel.animate([{ transform: 'translateX(0)' }, { transform: 'translateX(200px)' }], { duration: 1000 }); // ✅ NEWInterview gotchas
Context
This tests the compositing architecture — interviewers want the layer/texture model and the thread separation, not just 'transforms are fast'.
The mechanical answer
When an element is composited (promoted to its own layer), its rendering is captured as a GPU texture. The compositor thread owns the layer tree and draws each layer as a quad with a transform and opacity. Animating transform or opacity only changes those compositor-side values (via a cc::Animation sampled on the compositor thread), so no main-thread style/layout/paint runs — the existing texture is just repositioned/blended each frame. This is why these animations stay smooth even when the main thread is blocked: they don't need it. Animating any layout-affecting property (width, height, top, left) invalidates the layer's content, forcing main-thread layout + paint + re-raster + texture upload, which is both slow and main-thread-bound. Layer promotion has memory cost (a full texture per layer), which is why overusing will-change can hurt.
Trap
The naive answer is 'transforms are GPU-accelerated so they're always free'. They need the element to be on a composited layer, and the texture memory is a real cost. Another trap: thinking animating width is 'GPU-accelerated too' because some engines do partial optimization — geometry animations still engage layout, and layout-invalidating animations can't be compositor-driven. Candidates who mention texture upload and layer promotion show real understanding.
Context
This probes the rasterization strategy — interviewers want the memory/time trade-off behind tiling and how it relates to viewport visibility and scrolling.
The mechanical answer
A composited layer can be enormous (a long scrolling page), so rasterizing it as one texture is impractical — memory-heavy and slow. Instead, the compositor splits layers into tiles (e.g. 256×256 or 512×512) and rasterizes tiles independently, prioritizing visible tiles and tiles near the viewport. This enables: fast initial paint (only visible tiles rasterize), progressive rendering while scrolling (new tiles rasterize as they enter the viewport), and incremental invalidation (a content change re-rasterizes only the affected tile, not the whole layer). Tiles are rasterized on the GPU (or CPU workers as fallback) and uploaded as textures; the compositor draws only the tiles intersecting the viewport. Rasterization priority and budget management are core compositor responsibilities for smooth scrolling.
Trap
The naive answer is 'the whole layer is one GPU texture'. Large layers are tiled. Another trap: assuming repainting is per-pixel cheap — invalidating a tile re-rasterizes that tile fully, and invalidating many tiles (e.g. a layer-wide paint change) is costly. Candidates who explain visible-tile prioritization and incremental invalidation show real compositor knowledge.