Engineer Notes
Render Layer System
Render Layer System
Overview
The Render Layer System provides a compositing model for Harrlow similar to modern browser engines (Chrome, Safari, Firefox). It ensures correct paint order for UI elements, particularly addressing the issue where text was rendering behind backgrounds.
Based on concepts from: https://web.dev/articles/speed-layers
Architecture
Key Components
- HarrlowDrawCommand (
HarrlowCommandList.h)- Extended with
layerId,zIndex, andcreatesStackingContextfields - Commands are sorted by
(layerId, drawOrder)before rendering
- Extended with
- RenderLayer (
RenderLayer.h)- Represents a compositing layer in the render tree
- Contains layer creation reasons, transform, opacity, blend mode
- Maintains list of command indices belonging to this layer
- Supports layer caching for performance
- LayerTree (
RenderLayer.h/.cpp)- Manages the hierarchy of render layers
- Handles layer creation, sorting, and compositing order
- Provides flattened layer list for rendering
- SimpleAPHIRenderContext (
SimpleAPHIRenderContext.cpp)- Modified to sort commands by
(layerId, drawOrder)before processing - Ensures backgrounds render before text, z-index is respected
- Modified to sort commands by
Layer Creation Criteria
Elements get their own compositing layer for these reasons:
- RootLayer: html/body element
- PositionedWithZIndex: position:absolute/relative/fixed with z-index
- Opacity: opacity < 1
- Transform3D: 3D transforms (perspective, rotateX/Y/Z)
- Transform2D: 2D transforms (rotate, scale, translate)
- Filter: CSS filter effects
- Video:
<video>elements - Canvas:
<canvas>elements - WillChange: will-change CSS hint
- AnimatedTransform/Opacity: CSS animations on transform/opacity
Paint Order (CSS 2.1 Appendix E)
Within each stacking context, elements are painted in this order:
- Background and borders of the element
- Descendants with negative z-index (in stacking order)
- In-flow, non-positioned, block-level descendants
- Non-positioned floats
- In-flow, non-positioned, inline-level descendants
- Positioned descendants with z-index: auto or z-index: 0
- Descendants with positive z-index (in stacking order)
Command Sorting
Commands are sorted using a stable sort with key (layerId, drawOrder):
std::stable_sort(commands.begin(), commands.end(),
[](const HarrlowDrawCommand& a, const HarrlowDrawCommand& b)
{
if (a.layerId != b.layerId)
return a.layerId < b.layerId;
return a.drawOrder < b.drawOrder;
});
This ensures:
- Lower layers paint first (backgrounds before foregrounds)
- Within a layer, lower drawOrder paints first
- Stable sort preserves document tree order for equal keys
Usage
Automatic (HTMLPainter)
HTMLPainter automatically assigns drawOrder values based on the CSS stacking context rules. No additional code is required for correct rendering.
Manual (Custom Rendering)
When appending commands manually:
HarrlowDrawCommand cmd;
cmd.drawOrder = 100; // Background
// ... setup vertices, etc
recorder->AppendCommand(std::move(cmd));
HarrlowDrawCommand textCmd;
textCmd.drawOrder = 200; // Text (higher = on top)
// ... setup text vertices
recorder->AppendCommand(std::move(textCmd));
Performance Considerations
- Batching: Commands with identical state (texture, blend, clip) but different drawOrder are still batched together after sorting
- Layer Caching: Layers can cache their content as textures to avoid repaint
- Sort Cost: O(n log n) sort per frame, but n is typically small (<1000 commands)
- Memory: LayerTree allocates per-layer, but most UIs have <50 layers
Future Enhancements
- GPU-accelerated layer caching (render-to-texture)
- Layer squashing (merge overlapping layers with compatible properties)
- Partial layer updates (only repaint dirty regions)
- Will-change optimization hints
- Layer debugging overlay (show layer boundaries, reasons)

