Donner SVG main
0.8.0 · 382bd0190.8.0 main
SVG editor and embeddable C⁠+⁠+⁠20 engine.
Loading...
Searching...
No Matches
CompositorController.h File Reference
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <ostream>
#include <span>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "donner/base/Box.h"
#include "donner/base/EcsRegistry.h"
#include "donner/base/Transform.h"
#include "donner/svg/SVGDocument.h"
#include "donner/svg/compositor/ComplexityBucketer.h"
#include "donner/svg/compositor/CompositorLayer.h"
#include "donner/svg/compositor/LayerResolver.h"
#include "donner/svg/compositor/MandatoryHintDetector.h"
#include "donner/svg/compositor/ScopedCompositorHint.h"
#include "donner/svg/renderer/RendererInterface.h"
Include dependency graph for CompositorController.h:
This graph shows which files directly or indirectly include this file:

Classes

struct  donner::svg::compositor::CompositorTile
 A single bitmap cache unit the compositor exposes for GPU upload - either a static segment (non-promoted content between promoted layers) or a promoted layer's own rasterization. More...
class  donner::svg::compositor::CancellationToken
 Cancellation handle for CompositorController::renderFrame. The token wraps a single std::atomic<bool>; the compositor's per-layer / per-segment rasterize loops poll isCancelled() at coarse safe points (between rasterizeLayer / rasterizeStaticSegment calls, never mid-rasterize) and bail early when set. More...
struct  donner::svg::compositor::CompositorConfig
 Runtime feature gates for CompositorController. More...
class  donner::svg::compositor::CompositorController
 Controls compositor layer promotion/demotion and orchestrates composited rendering. More...
struct  donner::svg::compositor::CompositorController::PromoteResult
 Result of requesting an editor-facing compositor presentation plan. More...
struct  donner::svg::compositor::CompositorController::BitmapMemoryBreakdown
 Split of totalBitmapMemory by what holds the pixels. The four kinds are evicted by different rules, so a single total cannot say which rule is the one letting memory grow. More...
struct  donner::svg::compositor::CompositorController::FastPathCounters
 Diagnostic counters for the translation-only fast path. Tests read these to assert that a drag is taking the fast path every frame, not falling through to prepareDocumentForRendering. More...
struct  donner::svg::compositor::CompositorController::LayerInspectorRow
 One row of diagnostic state per active compositor layer, intended for the editor's read-only layer-inspector panel. Cheap to build, cheap to copy: a few ints and one short string per layer. More...
struct  donner::svg::compositor::CompositorController::SegmentInspectorRow
 One row of diagnostic state per static segment (the non-promoted content between promoted layers, plus the pre-first and post-last slots). Sized N+1 where N is layerCount(). More...
struct  donner::svg::compositor::CompositorController::StaticSpanPlan
 Conservative draw-cost plan for one static segment slot. More...
struct  donner::svg::compositor::CompositorController::CompositeTileSnapshot
 One row of the layer inspector's paint-order tile structure. Promoted layers remain visible in this view even when they have no bitmap or texture payload. When pixels are available, a CPU thumbnail or backend texture snapshot lets the panel show a preview inline. More...
struct  donner::svg::compositor::CompositorController::RenderFrameStats
 Worker render costs from the most recent renderFrame call, split by whether the work was caused by immediate-mode transient spans or retained cached tiles. More...
struct  donner::svg::compositor::CompositorController::StateSnapshot
 Compact observable compositor state for diagnostics and tests. More...

Namespaces

namespace  donner
 Top-level Donner namespace, which is split into different sub-namespaces such as donner::svg and donner::css.
namespace  donner::svg
 Donner SVG library, which can load, manipulate and render SVG files.
namespace  donner::svg::components
 Contains the implementation of the Donner ECS,.

Enumerations

enum class  donner::svg::compositor::CompositorTileBitmapPayload : uint8_t {
  All ,
  DragTargetOnly ,
  ImmediateOnly ,
  ImmediateAndDragTargetOnly ,
  MetadataOnly
}
 Bitmap payload policy for CompositorController::snapshotTilesForUpload. More...
enum class  donner::svg::compositor::StaticSpanMode : uint8_t {
  CachedTile ,
  Immediate
}
 Presentation mode chosen for one non-promoted paint-order span. More...
enum class  donner::svg::compositor::PixelIdentityVerificationStatus : uint8_t {
  NotAttempted ,
  Compared ,
  InconclusiveStyleWork ,
  SnapshotUnavailable
}
 Outcome of the optional compositor pixel-identity diagnostic. More...

Functions

std::ostream & donner::svg::compositor::operator<< (std::ostream &stream, PixelIdentityVerificationStatus status)
 Print a compositor pixel-verification outcome.
std::unordered_map< Entity, Entity > donner::svg::compositor::BuildStructuralEntityRemap (const SVGDocument &oldDoc, const SVGDocument &newDoc)
 Walk the XML trees of oldDoc and newDoc in lockstep and build a remap from old-registry entity ids → new-registry entity ids, suitable for CompositorController::remapAfterStructuralReplace.
size_t donner::svg::compositor::EffectiveCompositorMemoryBudget ()
 Effective compositor bitmap budget for the current process.

Variables

constexpr int donner::svg::compositor::kMaxCompositorLayers = 32
 Maximum number of compositor layers that can be simultaneously active.
constexpr size_t donner::svg::compositor::kMaxCompositorMemoryBytes = 1024ull * 1024ull * 1024ull
 Maximum total memory budget for compositor layer bitmaps, in bytes, on hosts with a large address space.

Class Documentation

◆ donner::svg::compositor::CompositorController::BitmapMemoryBreakdown

struct donner::svg::compositor::CompositorController::BitmapMemoryBreakdown

Split of totalBitmapMemory by what holds the pixels. The four kinds are evicted by different rules, so a single total cannot say which rule is the one letting memory grow.

Class Members
size_t layerBitmapBytes = 0 CPU pixel buffers for promoted layers.
size_t layerCount = 0 Promoted layers holding either a bitmap or a texture.
size_t layerTextureBytes = 0 GPU textures for promoted layers.
size_t segmentBitmapBytes = 0 CPU pixel buffers for cached static segments.
size_t segmentCount = 0 Cached static segments holding either a bitmap or a texture.
size_t segmentTextureBytes = 0 GPU textures for cached static segments.

◆ donner::svg::compositor::CompositorController::FastPathCounters

struct donner::svg::compositor::CompositorController::FastPathCounters

Diagnostic counters for the translation-only fast path. Tests read these to assert that a drag is taking the fast path every frame, not falling through to prepareDocumentForRendering.

Class Members
uint64_t fastPathFrames = 0 Incremented every frame that takes the fast path and successfully handled every dirty entity via a compose-transform update.
uint64_t noDirtyFrames = 0 Incremented every frame where the fast-path eligibility check wasn't reached because no entities were dirty (e.g. page-load, selection-change-only frames).
uint64_t paintOrderSnapshots = 0 Number of full document paint-order snapshots taken while planning dirty static segments. Clean frames should not walk paint order at all.
uint64_t segmentResyncRebuilds = 0 Number of times segment topology/state vectors were rebuilt. Stable frames must preserve these vectors in place instead of reallocating them.
uint64_t slowPathFramesWithDirty = 0 Incremented every frame whose dirty-entity set disqualified the fast path (transform+other flags, subtree with non-translation delta, missing layer, etc.) and fell through to the slow path.

◆ donner::svg::compositor::CompositorController::LayerInspectorRow

struct donner::svg::compositor::CompositorController::LayerInspectorRow

One row of diagnostic state per active compositor layer, intended for the editor's read-only layer-inspector panel. Cheap to build, cheap to copy: a few ints and one short string per layer.

Class Members
Vector2i bitmapSize = Vector2i::Zero() Pixel size of the layer's cached bitmap. Vector2i::Zero() when the layer has not yet been rasterized (hasValidBitmap false).
Vector2d canvasOffset = Vector2d::Zero() Canvas-space top-left position where this layer's bitmap blits back. Vector2d::Zero() for canvas-sized layers; non-zero for intrinsic-sized layers.
bool dirty = false Whether this layer is currently flagged dirty (a rasterize is pending on the next renderFrame call).
Entity entity = entt::null Root entity of the promoted subtree.
FallbackReason fallbackReasons = FallbackReason::None Raw fallback flags (handy for tests; the panel renders fallbackReasonsText for display).
string fallbackReasonsText Pre-formatted fallback flag list (e.g. "Filter | IsolatedLayer").
uint64_t generation = 0 Monotonic generation counter from CompositorLayer::generation.
bool hasValidBitmap = false Whether the layer has a valid cached bitmap.
double lastRasterizeMs = 0.0 Wall-clock duration of the most recent rasterize, in ms.
uint32_t layerId = 0 Layer's stable numeric id (CompositorLayer::id).
uint32_t rasterizeCount = 0 Cumulative rasterize count from CompositorLayer::rasterizeCount.
Vector2i thumbnailDims = Vector2i::Zero() Pixel dimensions of the downsampled thumbnail. Vector2i::Zero() when the layer has no valid bitmap. Otherwise the longer side is kLayerThumbnailMaxSide and the shorter side preserves aspect.
vector< uint8_t > thumbnailPixels RGBA8 thumbnail pixels, tightly packed (thumbnailDims.x * 4 row stride), suitable for direct upload via glTexImage2D. Empty when the layer has no valid bitmap.

◆ donner::svg::compositor::CompositorController::SegmentInspectorRow

struct donner::svg::compositor::CompositorController::SegmentInspectorRow

One row of diagnostic state per static segment (the non-promoted content between promoted layers, plus the pre-first and post-last slots). Sized N+1 where N is layerCount().

Class Members
Vector2i bitmapSize = Vector2i::Zero() Pixel dimensions of the cached segment bitmap. Vector2i::Zero() when the slot has no bitmap yet.
Vector2d canvasOffset = Vector2d::Zero() Canvas-space top-left offset where this segment's bitmap blits back (non-zero only on the tight-bounded path).
bool dirty = false Whether this slot is currently flagged dirty (a rasterize is pending on the next renderFrame call).
uint64_t generation = 0 Monotonic per-slot generation counter.
bool hasValidBitmap = false Whether the slot has a non-empty cached bitmap.
double lastRasterizeMs = 0.0 Wall-clock duration of the most recent rasterize, in ms. Zero when this slot has never been rasterized in the current session.
size_t slotIndex = 0 Slot index (0..N inclusive). Segment 0 is the pre-first-layer content; segment N is the post-last-layer content.

◆ donner::svg::compositor::CompositorController::StaticSpanPlan

struct donner::svg::compositor::CompositorController::StaticSpanPlan

Conservative draw-cost plan for one static segment slot.

Class Members
Box2d boundsCanvas Snapped canvas-space bounds used for immediate eligibility.
Vector2i budgetCanvasSize = Vector2i::Zero() Canvas size at which budgetRejected and budgetImmediate were observed.
bool budgetImmediate = false True when two consecutive first-of-frame rejections at budgetCanvasSize switched the span to immediate presentation: retries stop and the content stays visible until the canvas size changes.
bool budgetRejected = false True when the latest allocation at budgetCanvasSize was the first rejection of a frame's surface budget: one strike toward giving up on retained presentation, not proof that the span alone exceeds the budget.
bool demotedDynamicImmediate = false True when the span was dynamically immediate last frame but this render exceeded budget, so the freshly-rendered payload is retained as a cached tile instead of staying immediate.
bool dynamicHeuristicImmediate = false True when timing expanded the span into immediate presentation.
double estimatedCacheOverheadCost = 0.0 Relative fixed/cache memory cost avoided by immediate presentation.
int estimatedDrawOps = 0 Estimated number of direct geometry draws in the span.
int estimatedPathVerbs = 0 Estimated number of path verbs across direct geometry draws.
double estimatedRasterizeMs = 0.0 Deterministic geometry estimate (ms) of this span's re-rasterize cost. Drives the immediate-vs-cached decision in place of measuredRasterizeMs so the choice is timing-independent. See EstimateStaticSpanRasterizeMs.
double estimatedRedrawCost = 0.0 Relative redraw cost from tight area and geometry complexity.
uint64_t estimatedRetainedBytes = 0 Estimated presentation texture bytes retained by a cached tile.
bool estimatedUsesAreaCostlyPaint = false True when the span fills with a gradient, whose rasterize cost scales with covered area; feeds the area term of estimatedRasterizeMs.
Entity firstEntity = entt::null First render instance covered by the span, or null for an empty slot.
bool hasExpensiveEffect = false True when the span uses effects or resources that force cached-tile presentation.
double immediateBudgetChargeMs = 0.0 Budget charged by this span when it is immediate.
double immediateBudgetMs = 0.0 Total dynamic immediate-span frame budget for 120 Hz interaction.
Entity lastEntity = entt::null Last render instance covered by the span, or null for an empty slot.
double measuredRasterizeMs = 0.0 Raster time from the most recent span render. Telemetry only - recorded so the geometry cost model behind estimatedRasterizeMs can be recalibrated against real timings. NOT used in the immediate decision.
StaticSpanMode mode = StaticSpanMode::CachedTile Presentation mode selected for this span.
size_t slotIndex = 0 Slot index in the static segment array.
string spanRangeLabel Human-readable first/last element range covered by this span.
bool staticHeuristicImmediate = false True when the static cost heuristic chose immediate presentation.
bool visible = false True when the span has a visible, bounded contribution to the canvas.

◆ donner::svg::compositor::CompositorController::RenderFrameStats

struct donner::svg::compositor::CompositorController::RenderFrameStats

Worker render costs from the most recent renderFrame call, split by whether the work was caused by immediate-mode transient spans or retained cached tiles.

Class Members
double cachedRasterizeMs = 0.0 Segment/layer raster time that produces retained cached bitmap/texture tiles.
int cachedTileCount = 0 Count of segment/layer tiles charged to cached raster work.
int composePayloadRefusalCount = 0 Compose payloads refused this frame: a texture draw the renderer declined, or a CPU bitmap offered in texture-presentation mode. Neither is repaired by a CPU upload.
int composePayloadRefusalTotal = 0 Controller-lifetime total of composePayloadRefusalCount.
size_t damagePatchGeometryDraws = 0 Geometry draws charged while rasterizing those damage rectangles.
int damagePatchTileCount = 0 Retained owner tiles refreshed by a bounded damage patch this frame.
double firstFrameDrawMs = 0.0 First-frame full-document draw that produces the immediately presentable surface.
double firstFramePlanningMs = 0.0 First-frame detector, resolver, and layer-plan construction after the initial draw.
double firstFrameWarmupMs = 0.0 First-frame retained layer/segment cache warmup performed after planning.
double immediateRasterizeMs = 0.0 Segment raster time caused by immediate-mode static spans.
int immediateTileCount = 0 Count of static spans charged to immediate raster work.
int mainComposeCount = 0 Main-renderer composition passes, excluding the initial direct document render.
double mainComposeMs = 0.0 Time spent composing retained tiles and immediate spans into the main renderer.
int maskedChildComposeTileCount = 0 Masked owner tiles rebuilt this frame from cached pieces around a held-drag child.
int offscreenCreateCount = 0 Offscreen renderer instances constructed this frame. With pooling, a steady-state frame reuses the pooled instance and reports 0 or 1 here regardless of tile count; per-tile construction (the pre-pool behavior whose teardown polls dominated size-commit frames) would report one per tile. Pinned by regression tests.
int offscreenCreateTotal = 0 Monotonic controller-lifetime totals of the two counts above. Browser regression tests poll published stats asynchronously and can miss any individual frame's per-frame values; the totals let them assert "tiles rasterized without constructing instances" across a window without sampling the exact re-rasterize frame.
int offscreenRecycleCount = 0 Tile rasterizations served by the pooled offscreen instance.
int offscreenRecycleTotal = 0
PixelIdentityVerificationStatus pixelIdentityVerification Result of Config.verifyPixelIdentity for this render attempt.
int textureAllocationFailureCount = 0 Tile rasterizations whose payload snapshot failed this frame: a null texture snapshot, or an empty bitmap from a failed readback or a refused target. The tile stays dirty for the next frame, or switches to direct compose after two first-of-frame budget rejections at the same canvas size.
int textureAllocationFailureTotal = 0 Controller-lifetime total of textureAllocationFailureCount.

◆ donner::svg::compositor::CompositorController::StateSnapshot

struct donner::svg::compositor::CompositorController::StateSnapshot

Compact observable compositor state for diagnostics and tests.

Class Members
uint32_t activeHintsCount = 0 Editor-driven explicit promotions (drag target + selection prewarm). Mandatory-detector hints don't count toward this number - they live in a separate map.
Vector2i canvasSize = Vector2i::Zero() Canvas size the compositor most recently rendered against.
bool firstFrameWarmupPending = false A correct flat frame was published while retained caches still await low-priority warmup.
Entity lastPromoteRefusalEntity = entt::null Entity the failed promoteEntity was called with. entt::null when lastPromoteRefusalReason is None.
PromoteRefusalReason lastPromoteRefusalReason = PromoteRefusalReason::None Reason the most-recent promoteEntity call returned false, or None when the most-recent call succeeded (or no call has happened yet). Sticky on failure.
uint32_t layerCount = 0 Total layers currently in layers_ (mandatory + explicit).
bool splitPathActive = false hasSplitStaticLayers() - the editor's drag-overlay fast path is active when this is true.
Entity splitStaticLayersEntity = entt::null Entity the compositor cached the bg/fg split for. entt::null when split-path is inactive.

Enumeration Type Documentation

◆ CompositorTileBitmapPayload

Bitmap payload policy for CompositorController::snapshotTilesForUpload.

Enumerator
All 

Include every available tile bitmap.

DragTargetOnly 

Include only the active drag-target bitmap; keep non-drag tile metadata.

ImmediateOnly 

Include only static spans that are planned for immediate presentation.

ImmediateAndDragTargetOnly 

Include immediate static spans plus the active drag-target bitmap.

MetadataOnly 

Include tile metadata only.

◆ PixelIdentityVerificationStatus

Outcome of the optional compositor pixel-identity diagnostic.

Enumerator
NotAttempted 

Disabled or not reached by this render path.

Compared 

Complete, compatible captures passed the strict comparison.

InconclusiveStyleWork 

Style admission left either rendering incomplete.

SnapshotUnavailable 

A diagnostic renderer or compatible capture was unavailable.

◆ StaticSpanMode

enum class donner::svg::compositor::StaticSpanMode : uint8_t
strong

Presentation mode chosen for one non-promoted paint-order span.

Enumerator
CachedTile 

Rasterize once into a cached compositor tile and reuse until dirty.

Immediate 

Re-rasterize as a transient tile and do not publish metadata-only reuse.

Function Documentation

◆ BuildStructuralEntityRemap()

std::unordered_map< Entity, Entity > donner::svg::compositor::BuildStructuralEntityRemap ( const SVGDocument & oldDoc,
const SVGDocument & newDoc )
nodiscard

Walk the XML trees of oldDoc and newDoc in lockstep and build a remap from old-registry entity ids → new-registry entity ids, suitable for CompositorController::remapAfterStructuralReplace.

Returns an empty map if the two trees are not structurally equivalent - defined as: same preorder walk, same element tag name at every step, and same id attribute at every step (or neither has one). Attribute values other than id are allowed to differ - the drag-end writeback case changes transform values, not tree shape.

The returned map covers every element visited in the walk. The caller is responsible for handing it to remapAfterStructuralReplace on the compositor that was previously tracking oldDoc's entity space.

◆ EffectiveCompositorMemoryBudget()

size_t donner::svg::compositor::EffectiveCompositorMemoryBudget ( )
nodiscard

Effective compositor bitmap budget for the current process.

On wasm the linear memory is small and FIXED (growth is fatal on every browser engine, so the build links with equal initial and maximum memory), and a budget larger than the whole address space means admission can never refuse: full-canvas payloads accrue across zoom size-commit epochs until the module aborts (measured 2026-08-11: ~one 24.8MB surface per 250ms at DPR 2). Bound the budget to a third of the actual heap there. Native keeps the constant.

NOTE the measured caveat: refusing promotion is not free - an unpromoted heavy subtree re-rasterizes full-document per frame, which can cost MORE transient memory than the retained tile (a blind 96MB budget made storms OOM sooner). This bound therefore stays generous (heap/3, not heap/10); right-sizing below that requires measuring the demotion fallback path.

◆ operator<<()

std::ostream & donner::svg::compositor::operator<< ( std::ostream & stream,
PixelIdentityVerificationStatus status )
inline

Print a compositor pixel-verification outcome.

Parameters
streamOutput stream.
statusOutcome to print.

Variable Documentation

◆ kMaxCompositorMemoryBytes

size_t donner::svg::compositor::kMaxCompositorMemoryBytes = 1024ull * 1024ull * 1024ull
inlineconstexpr

Maximum total memory budget for compositor layer bitmaps, in bytes, on hosts with a large address space.

Sized for high-DPI editor sessions with mandatory filter, mask, and isolated-layer promotions plus one active drag target. Scale-band caching should eventually reduce per-layer memory by letting layers live below full canvas resolution.