Skip to content
SiteEmail

Game HUD elements such as minimap markers, waypoints, floating nameplates, and radar blips frequently move, rotate, or scale every frame as the camera or player moves. In standard rendering, moving these subtrees requires continuous positioning recalculation and repeated rasterization of all internal primitives.

With Persistent Layer Caching (coh-layer), Gameface rasterizes the designated DOM subtree once into an isolated GPU texture surface. On subsequent frames, pure CSS transformations (such as transform: translate(...), rotate(...), or scale(...)) reuse the cached GPU texture directly without re-positioning cost or command re-recording. When visual styles, content, or DOM structure inside the layer change, Gameface cleanly invalidates the cached surface and re-rasterizes the subtree.

You can designate an element as a persistent cached layer using CSS:

Apply coh-layer: on to any container element:

.hud-compass {
position: absolute;
top: 20px;
right: 20px;
width: 250px;
height: 250px;
coh-layer: on;
transform: rotate(0deg);
}

To turn caching off dynamically, set coh-layer: off;.

  1. Initial Rasterization (Cache Miss): When the layer is first encountered, Gameface computes its visual bounding box and allocates an isolated scratch surface texture. All child elements within the subtree are rendered into this scratch buffer in local coordinates.
  2. Transform Animations (Cache Hit): In subsequent frames, if only the layer’s transform (translation, rotation, scale) or ancestor transforms change, Gameface skips re-positioning and re-rasterization entirely. It blits the cached GPU texture directly onto the target buffer using the computed transformation matrix.
  3. Invalidations (Cache Reset): If a child element changes style, text, border, or DOM hierarchy, the cached surface is invalidated, and the subtree is re-rasterized fresh into the texture surface on the next frame.

Gameface automatically detects mutations and resets the layer cache. Common invalidation triggers include, but are not limited to:

Invalidation TriggerDescription
Descendant Content ChangedChild DOM nodes added, removed, or reparented; text content or innerHTML altered.
Descendant Visual Style ChangedChild colors, background images, shadows (box-shadow), filters, or opacity modified.
Box Model & Layout ChangesAlterations to width, height, padding or border on the layer root or its descendants that affect layout dimensions.
Containing Block Status ChangedDescendants or layer roots gaining or losing CSS transforms that modify fixed/absolute positioning containing blocks.
Scroll Offset ChangedScrolling occurring within the cached layer container or any of its scrollable descendants.
Transform Scale ChangedLarge scale factor changes exceeding redraw thresholds, re-rasterizing the layer to preserve crisp visual fidelity.
Overflow ChangedChanges in overflow or clipping boundaries.

Best Practices & Performance Recommendations

Section titled “Best Practices & Performance Recommendations”
  • Animate transforms on the layer root or ancestors: Limit animations and transitions to CSS transform on the layer container or its ancestors, which reuses the cached surface without re-rendering. Avoid animating any layout geometry or visual paint properties (such as dimensions, offsets, colors, or filters), as modifying internal layout or styling forces the layer cache to invalidate and redraw on every frame.
  • Cache static panels in dynamic views: Even if a HUD panel does not animate its own transform, marking it with coh-layer is beneficial if surrounding UI elements frequently invalidate the screen or if the panel uses backdrop-filter. The static panel avoids costly re-rasterization whenever other parts of the view trigger repaints.
  • Isolate dynamic content: If a HUD element has both static and rapidly updating elements (such as a health bar with an animated meter and static frame), place coh-layer on the static frame, and keep the dynamic bar as a separate sibling outside the cached layer.
  • Combine with contain: Using contain: paint layout alongside coh-layer is highly recommended. Containment guarantees that layout and paint changes inside the layer cannot escape its boundaries, localizing layout recalculations and ensuring maximum rendering independence.
  • Nested layers: Nested coh-layer elements are planned to be supported. Currently, nesting layers can increase GPU memory consumption without bringing the expected performance benefits. For now, avoid nesting layers and instead keep layers as siblings.
  • Be mindful of GPU memory: Each active persistent layer uses a dedicated GPU surface proportional to its visual bounding box dimensions. Avoid creating unnecessary persistent layers for elements that change content continuously every frame.

Gameface outputs descriptive warnings when coh-layer is combined with unsupported features:

  • Compositor collision (coh-composition-id): coh-layer cannot be used on an element that also uses coh-composition-id.
  • Surface partitioning (coh-partitioned): coh-layer cannot be combined with coh-partitioned.
  • Backdrop filters on descendants (backdrop-filter): A backdrop-filter applied directly to the coh-layer root is fully supported and rendered every frame over live background content without invalidating the layer’s cached DOM subtree. However, backdrop-filter on descendant elements inside a coh-layer hierarchy causes continuous invalidation of the layer, which negates the performance benefits of the layer.
  • Inline SVG children: Do not place coh-layer on child tags within an <svg> element (such as <path>, <circle>, or <rect>). Instead, place coh-layer on the outer <svg> container or an enclosing <div>.
  • Simple Opacity fallback & blending differences: coh-layer cannot be used on an element that also uses coh-simple-opacity: on. When simple opacity is specified on an element with coh-layer, simple opacity is automatically ignored and falls back to standard group opacity, which may result in visual blending differences if overlapping translucent children are present.

Persistent layer surfaces are tracked under the ICACHE_CohLayerSurfaces cache type:

For this cache, GetCacheBytesStats reports an estimate, in bytes, of the GPU memory footprint of persistent layer surfaces, calculated from their surface dimensions.

cohtml::View* view;
// Query active layer count and capacity
unsigned activeLayers = 0;
unsigned layersCapacity = 0;
view->GetCacheCountStats(cohtml::ICACHE_CohLayerSurfaces, activeLayers, layersCapacity);
// Query an estimate, in bytes, of the GPU memory footprint of persistent layer surfaces, in bytes
unsigned usedBytes = 0;
unsigned capacityBytes = 0;
view->GetCacheBytesStats(cohtml::ICACHE_CohLayerSurfaces, usedBytes, capacityBytes);
// Queue clearing of all persistent cached surfaces
view->QueueClearCaches(cohtml::ICACHE_CohLayerSurfaces);

When profiling with your engine’s profiler or Tracy/RenderDoc, look for the following markers:

  • View.CohLayerRerenderSubtree: Measured during cache misses and resets when the DOM subtree is rasterized into the scratch texture.
  • View.CohLayerDrawCachedSurface: Measured during cache hits when the cached GPU surface is blitted onto the scene target.

Persistent Layer Caching is continually evolving. Future updates will introduce expanded layer reuse heuristics across additional animation, layout, and compositing workflows to maximize performance across diverse UI setups. If you have specific use cases, questions, or ideas for improving the feature’s usability, please reach out to our team with your feedback!