Donner SVG 0.8.0
0.8.0 ยท ebf353960.8.0 main
SVG editor and embeddable C⁠+⁠+⁠20 engine.
Loading...
Searching...
No Matches
AsyncRenderer.h File Reference

AsyncRenderer owns a svg::Renderer and runs compositor rendering plus any final presentation snapshot handoff on a dedicated worker thread so heavy renders don't block the UI thread. More...

#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <string>
#include <thread>
#include <unordered_map>
#include <variant>
#include <vector>
#include "donner/base/EcsRegistry.h"
#include "donner/base/Transform.h"
#include "donner/base/Vector2.h"
#include "donner/editor/CapturedPresentation.h"
#include "donner/editor/CompositedRenderingMode.h"
#include "donner/editor/OverlayRenderer.h"
#include "donner/editor/ViewportState.h"
#include "donner/svg/SVGDocument.h"
#include "donner/svg/SVGElement.h"
#include "donner/svg/compositor/CompositorController.h"
#include "donner/svg/compositor/ScopedCompositorHint.h"
#include "donner/svg/renderer/Renderer.h"
#include "donner/svg/renderer/RendererInterface.h"
Include dependency graph for AsyncRenderer.h:
This graph shows which files directly or indirectly include this file:

Classes

struct  donner::editor::RenderLease
 Non-null renderer/document handoff for a render request. More...
struct  donner::editor::RenderRequest
 Per-request handoff data captured at render-request time so the worker has everything it needs without touching live UI state. More...
struct  donner::editor::RenderRequest::DragPreview
 Drag state snapshotted with one worker render request. More...
struct  donner::editor::PresentationSnapshotPlan
 Final full-canvas snapshot work needed after compositor rendering. More...
struct  donner::editor::HandoffTimings
 Attribution of one worker-to-UI handoff, in milliseconds. More...
struct  donner::editor::RenderResult
 Presentation payload plus the document version it was rendered from. More...
struct  donner::editor::RenderResult::WorkerTimingBreakdown
 Internal timing split for one async worker iteration. More...
struct  donner::editor::RenderResult::CompositedTile
 One paint-order tile from the worker's compositor snapshot. Ids and generations let presentation reuse unchanged GL textures or Geode UI registrations, including regular immediate static segments. A matching published tile may carry metadata without a fresh payload. Geometry is in document units so the presenter follows live zoom while a new canvas raster is deferred. More...
struct  donner::editor::RenderResult::CompositedPreview
 Paint-ordered composited tiles and promoted interaction state. More...
struct  donner::editor::SampleThumbnailRenderRequest
 One SVG source queued for bounded, low-priority rendering on the existing render worker. More...
struct  donner::editor::SampleThumbnailRenderResult
 CPU bitmap returned by one asynchronous sample-thumbnail attempt. More...
struct  donner::editor::SampleThumbnailRenderStats
 Observable state and monotonic counters for the bounded sample-thumbnail lane. More...
class  donner::editor::AsyncRenderer
 Background render worker with foreground document and low-priority thumbnail lanes. More...
struct  donner::editor::AsyncRenderer::GpuWaitFailure
 Device-health outcome the render worker observed, independent of any frame it produced. 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.

Enumerations

enum class  donner::editor::SampleThumbnailRenderOutcome : std::uint8_t {
  Rendered ,
  FontsPending ,
  ResourceLimit ,
  Cancelled ,
  ParseError ,
  RenderError ,
  RendererUnavailable
}
 Terminal outcome for one low-priority sample-thumbnail render attempt.
enum class  donner::editor::AuxiliaryPreviewKind : std::uint8_t {
  Sample ,
  FontFamily
}
 Consumer of one bounded low-priority SVG preview render.
enum class  donner::editor::AsyncRendererStartMode : std::uint8_t {
  Immediate ,
  Deferred
}
 Whether construction starts the render worker or leaves it inert until an explicit start call.

Functions

PresentationSnapshotPlan donner::editor::ChoosePresentationSnapshotPlan (bool hasCompositedPreview, bool fullCanvasPresentationAllowed, bool requiresTextureSnapshotPresentation, bool captureCpuSnapshot)
 Choose final full-canvas snapshot work for a render result.
bool donner::editor::CaptureFullCanvasTextureSnapshot (svg::RendererInterface &renderer, std::shared_ptr< const svg::RendererTextureSnapshot > &texture)
 Capture GPU presentation backing without a CPU fallback.
bool donner::editor::CanPresentTilePayload (bool requiresTexturePresentation, const svg::RendererBitmap &bitmap, const std::shared_ptr< const svg::RendererTextureSnapshot > &texture, const std::function< bool()> &shouldCancel)
 Check whether a tile can reach its receiver without changing its storage kind.
HandoffTimings donner::editor::ComputeHandoffTimings (std::chrono::steady_clock::time_point workerCompletedAt, std::chrono::steady_clock::time_point polledAt)
 Attribute the worker-to-UI handoff delay.
SampleThumbnailRenderOutcome donner::editor::ClassifyTemporaryFontResources (std::span< const svg::FontFaceDependency > dependencies, svg::FontResourcePreflight::Status status)
 Classify a temporary document's font result before destroying it. Retained-budget pressure cannot be repaired by recreating the same preview on another consumer's decode-slot wake.

Detailed Description

AsyncRenderer owns a svg::Renderer and runs compositor rendering plus any final presentation snapshot handoff on a dedicated worker thread so heavy renders don't block the UI thread.

Threading model

The worker thread owns the Renderer for its entire lifetime. Backends with thread-affined GPU objects depend on device, pipeline, texture, and readback use staying on that thread.

The worker additionally takes exclusive ownership of the SVGDocument during an active render. The UI thread must not mutate the document while a render is in flight.

UI thread flow per frame:

  1. pollResult() - if a render just finished, pick up the bitmap.
  2. If NOT busy: process mutations via flushFrame().
  3. If NOT busy AND a new render is needed: requestRender().
  4. If busy: skip flushFrame, leave pending mutations in the queue. They apply on the next idle frame. Input (drags, typing) still gets processed and queued - just not dispatched to the ECS.
  5. The editor overlay is the exception: it may take guarded document access for immediate presentation chrome. That access serializes behind the worker's render access instead of racing it, and must not be taken while holding AsyncRenderer's mutex.

The safety invariant: between requestRender() and a non-nullopt return from pollResult(), the UI thread must not mutate the SVGDocument. Registry-reading UI paths should normally gate on !isBusy() unless they are using guarded access for immediate overlay presentation. The UI thread must not call any method on the worker Renderer at any time - it lives on the worker.


Class Documentation

◆ donner::editor::RenderRequest::DragPreview

struct donner::editor::RenderRequest::DragPreview

Drag state snapshotted with one worker render request.

Class Members
Transform2d documentFromCachedDocument = Transform2d() Active affine transform represented by this request, relative to the drag-start cached document. Selection prewarms use identity.
uint64_t dragGeneration = 0 Monotonic id for the active drag gesture. Selection prewarms use zero.
Entity entity = entt::null Primary entity whose drag this request represents.
vector< Entity > extraEntities Additional entities moving with entity under the same active drag transform.
bool forceLayerRasterization = false True when this request must re-rasterize the promoted drag layer instead of only publishing updated compose metadata. Used for scheduler-requested affine recaptures that keep scaled drag previews crisp.
InteractionHint interactionKind = svg::compositor::InteractionHint::ActiveDrag Which interaction phase drove this preview. Selection means the editor is pre-warming a layer for the selected entity before any drag begins. ActiveDrag means the user is actively dragging - the DOM's transform attribute already reflects the cursor delta. The compositor stamps the correct InteractionHint on the entity based on this field so downstream introspection stays accurate.
Vector2d translation = Vector2d::Zero() Active drag translation represented by this request. Selection prewarms use zero.

◆ donner::editor::RenderResult::WorkerTimingBreakdown

struct donner::editor::RenderResult::WorkerTimingBreakdown

Internal timing split for one async worker iteration.

Class Members
double buildPreviewMs = 0.0 Time spent building composited-preview tile metadata/payloads.
int compositorReadbackCount = 0 GPU-to-CPU readbacks performed inside compositor rendering, before document unlock.
double dequeueToStartMs = 0.0 Worker-thread preflight between dequeue and the measured render iteration.
bool deviceLost = false True when the worker's backend device has been declared lost, either by the driver or by a bounded GPU wait exceeding its deadline.
double diagnosticsMs = 0.0 Time spent copying compositor diagnostics for editor panels.
double documentWriteLockMs = 0.0 Time the captured document remained unavailable for UI edits.
double finalSnapshotMs = 0.0 Time spent taking the final fallback canvas snapshot, when needed.
int finalSnapshotReadbackCount = 0 GPU-to-CPU map attempts while capturing the final frame, including explicit captures.
int fullCanvasTextureAllocationFailureCount = 0 Failed full-canvas GPU captures. The previous usable presentation is retained.
double idleCallbackMs = 0.0 Optional maintenance callback duration preceding this render.
double idleLockMs = 0.0 Time acquiring the worker-state lock after idle maintenance.
double idleWaitMs = 0.0 Condition-variable wait duration preceding this render, including time before submission.
double idleYieldMs = 0.0 Browser yield duration in the maintenance pass preceding this render.
bool nothingToPresent = false True when the iteration produced nothing to present: no compositor tile, and no full-canvas payload where one is permitted. The result then carries neither a preview nor a bitmap, and the UI keeps presenting its previous frame.
double pollDelayMs = 0.0 Time from worker result completion until the UI thread polls it.
double polledAtEpochMs = 0.0 Browser epoch milliseconds at the UI-thread poll, zero in native builds.
double queueWaitMs = 0.0 Time from UI-thread request submission until the worker dequeues the request.
int readbackCount = 0 GPU-to-CPU readbacks performed by the worker renderer and its offscreen instances.
int readbackPollIterations = 0 Legacy device-poll iterations used while waiting for those readbacks.
double renderFrameMs = 0.0 Time spent in CompositorController::renderFrame.
double setupMs = 0.0 Time before CompositorController::renderFrame, including compositor selection setup.
int tileHandoffReadbackCount = 0 GPU-to-CPU map attempts while preparing changed tile payloads after document unlock.
int timedOutWaitMs = 0 Wall time that wait spent before giving up, in milliseconds.
GpuWaitTimeoutSite timedOutWaitSite = svg::GpuWaitTimeoutSite::None Which bounded GPU wait declared that loss, when a deadline did.
bool usedTimedWaitAny = false True when browser readbacks used the event-driven timed WaitAny path.
double wakeToPollMs = 0.0 Portion of pollDelayMs spent waiting for the UI thread's next frame after the result became pollable. This is the handoff's animation-frame phase wait.

◆ donner::editor::AsyncRenderer::GpuWaitFailure

struct donner::editor::AsyncRenderer::GpuWaitFailure

Device-health outcome the render worker observed, independent of any frame it produced.

A bounded GPU wait that burns its deadline declares the device lost and ends the worker iteration with nothing to hand over, so the per-frame diagnostics a result carries are never published. That makes the most severe renderer failure the only one that leaves no trace in the frame stats. The worker records the outcome here instead, so a poller can report it without a frame ever landing.

Class Members
bool deviceLost = false True once the worker's backend device has been declared lost. Sticky.
uint64_t generation = 0 Bumped whenever the record above changes. A poller compares it against what it last reported so a sticky loss is reported once rather than on every frame.
int timedOutWaitMs = 0 Wall time that wait spent before giving up, in milliseconds.
GpuWaitTimeoutSite timedOutWaitSite = svg::GpuWaitTimeoutSite::None Which bounded GPU wait declared that loss, when a deadline did.

Function Documentation

◆ CanPresentTilePayload()

bool donner::editor::CanPresentTilePayload ( bool requiresTexturePresentation,
const svg::RendererBitmap & bitmap,
const std::shared_ptr< const svg::RendererTextureSnapshot > & texture,
const std::function< bool()> & shouldCancel )
nodiscard

Check whether a tile can reach its receiver without changing its storage kind.

Parameters
requiresTexturePresentationWhether rendered pixels must remain on the GPU.
bitmapCPU payload from a software renderer, or empty for a GPU or metadata-only tile.
textureGPU payload, or null for a software or metadata-only tile.
shouldCancelCancellation predicate for the presentation handoff.
Returns
False for cancellation or a payload requiring GPU readback or CPU upload.

◆ CaptureFullCanvasTextureSnapshot()

bool donner::editor::CaptureFullCanvasTextureSnapshot ( svg::RendererInterface & renderer,
std::shared_ptr< const svg::RendererTextureSnapshot > & texture )
nodiscard

Capture GPU presentation backing without a CPU fallback.

Parameters
rendererWorker renderer that just finished the frame.
textureReceives the texture, or null on failure; presentation retains its prior frame.
Returns
True when capture failed.

◆ ChoosePresentationSnapshotPlan()

PresentationSnapshotPlan donner::editor::ChoosePresentationSnapshotPlan ( bool hasCompositedPreview,
bool fullCanvasPresentationAllowed,
bool requiresTextureSnapshotPresentation,
bool captureCpuSnapshot )
nodiscard

Choose final full-canvas snapshot work for a render result.

Parameters
hasCompositedPreviewTrue when compositor tiles already provide the presented pixels.
fullCanvasPresentationAllowedTrue when a full-canvas snapshot may be presented in place of compositor tiles: composited rendering is off, the request is an overview infill, or the geometry debug pass is active.
requiresTextureSnapshotPresentationTrue when presentation must remain on GPU textures.
captureCpuSnapshotTrue when the caller explicitly requested a CPU-readable frame.
Returns
The final snapshot plan for this worker iteration.

◆ ComputeHandoffTimings()

HandoffTimings donner::editor::ComputeHandoffTimings ( std::chrono::steady_clock::time_point workerCompletedAt,
std::chrono::steady_clock::time_point polledAt )
inline

Attribute the worker-to-UI handoff delay.

A default-constructed workerCompletedAt means "not recorded" and yields all-zero timings. Timestamps that go backwards are clamped to zero rather than reported as negative durations.

Parameters
workerCompletedAtWhen the worker finished the render and staged the result.
polledAtWhen the UI thread accepted the result.