Donner SVG Editor & Engine
SVG-native editor and embeddable SVG2 + CSS3 engine in C++20, with GPU (WebGPU) and compact CPU renderers, built for correctness, security, and performance.
Loading...
Searching...
No Matches
donner::geode::GeodeDevice Class Reference

Owns (or wraps) a WebGPU device/queue pair for GPU rendering. More...

#include "donner/svg/renderer/geode/GeodeDevice.h"

Public Member Functions

 ~GeodeDevice ()
 Destructor releases the device and all GPU resources.
 GeodeDevice (const GeodeDevice &)=delete
GeodeDevice & operator= (const GeodeDevice &)=delete
 GeodeDevice (GeodeDevice &&)=delete
GeodeDevice & operator= (GeodeDevice &&)=delete
const wgpu::Device & device () const
 Returns the wgpu::Device. Guaranteed valid for the lifetime of this object.
const wgpu::Queue & queue () const
 Returns the default queue.
const wgpu::Instance & instance () const
 Returns the instance that created this device. In embedded mode this is null unless the host supplied GeodeEmbedConfig::instance.
const wgpu::Adapter & adapter () const
 Returns the adapter backing this device. May be null in embedded mode if the host did not provide an adapter.
wgpu::TextureFormat textureFormat () const
 Render-target texture format. Defaults to RGBA8Unorm for headless devices; set by the host via GeodeEmbedConfig::textureFormat in embedded mode.
void deferDestroy (wgpu::Buffer buffer)
 Enqueue a GPU buffer for deferred destruction.
void deferDestroy (wgpu::Texture texture)
 Enqueue a GPU texture for deferred destruction.
void drainDeferredDestroys ()
 Drop all deferred-destroy handles, releasing their GPU resources.
uint64_t deviceId () const
 Process-unique identity for this device instance, assigned at construction from a monotonic counter (never reused, starts at 1).
void setCounters (GeodeCounters *counters)
 Install a GeodeCounters struct for this device.
GeodeCounterscounters () const
 Non-owning pointer to the installed counters, or null.
void countBuffer () const
void countBindGroup () const
void countTexture () const
uint64_t lifetimeTextureCreates () const
 Cumulative number of countTexture() calls since this GeodeDevice was created. Does not account for textures released back into a pool - it is an allocation-site counter, not a live-count.
uint64_t lifetimeBufferCreates () const
 Cumulative number of countBuffer() calls since this GeodeDevice was created. Same caveat as lifetimeTextureCreates().
void countSubmit () const
void countPathEncode () const
void countDraw () const
void countPipelineSwitch () const
void countBufferWrite (uint64_t bytes) const
 Record one wgpu::Queue::writeBuffer call of bytes payload bytes.
void countTextureWrite (uint64_t bytes) const
 Record one wgpu::Queue::writeTexture call of bytes payload bytes.
const std::shared_ptr< std::atomic< int64_t > > & residentBytesGauge () const
 Shared live-resident-bytes gauge (design doc 0030 wave 2: GPU residence). Co-owned with each GeodeResidentSlot's buffer so resident-memory accounting stays lifetime-safe even if a document (and its ECS registry) outlives this device. Lazily created on first access. GeoEncoder bumps it when a slot gains residence; the slot decrements it on reset / destruction (geometry change or document teardown), which is the eviction signal for "many distinct documents".
int64_t liveResidentBytesForTesting () const
 Current live resident-geometry bytes across every GeodeResidentSlot buffer created against this device. Zero at construction and after all resident documents are torn down; used by eviction tests.
bool supportsTimestamps () const
 Whether the driver supports GPU timestamp queries.
bool isVulkan () const
 True when the active wgpu backend is Vulkan (Intel Arc hardware or Mesa lavapipe software). GeodeFilterEngine uses this to force inter-pass serialization that eliminates a nondeterministic cross-submit storage-write -> sampled-read visibility race observed on Arc Vulkan. Metal returns false and keeps the fast multi-submit path.
Shared dummy resources (design doc 0030 Milestone 4.2)

GeoEncoder's bind groups always include pattern + clip-mask texture/sampler slots, even when the current draw doesn't actually use them. Each slot is filled with a 1×1 "identity" texture when the feature is inactive. Prior to M4.2 every GeoEncoder instance created its own dummies (two textures per encoder), which showed up as 2+ textureCreates per frame per push/pop. Caching the dummies on the device - one instance per GeodeDevice - drops that to zero steady-state.

const wgpu::Texture & dummyPatternTexture () const
 1×1 opaque-black RGBA8 dummy. Bound into the pattern slot when the current draw is solid / gradient (not a pattern). The shader does not sample from it, but the bind group layout still requires a valid binding.
const wgpu::TextureView & dummyPatternTextureView () const
 View of dummyPatternTexture().
const wgpu::Sampler & dummyPatternSampler () const
 Linear-Repeat sampler used for both the dummy and real pattern tiles.
const wgpu::Texture & dummyClipMaskTexture () const
 1×1 R8Unorm with value 0xFF (= 1.0 coverage). Bound into the clip-mask slot when no clip mask is active - the shader multiplies coverage by this value, so 1.0 is a no-op.
const wgpu::TextureView & dummyClipMaskTextureView () const
 View of dummyClipMaskTexture().
const wgpu::Sampler & dummyClipMaskSampler () const
 Linear-ClampToEdge sampler used for both the dummy and real clip masks.
const wgpu::Buffer & identityInstanceTransformBuffer () const
 One-element instance-transform storage buffer carrying the identity affine. Bound at binding 7 of the Slug fill bind-group layout by every non-instanced solid fill so the bind-group layout stays stable across draw calls regardless of whether fillPathInstanced is in play. See design doc 0030 §M6 Bullet 2.
Shared render / compute pipelines (issue #575 fix)

Every wgpu pipeline created by createRenderPipeline / createComputePipeline is retained internally by wgpu-native even after the public handle's refcount drops to zero - wgpuDevicePoll does not drain it. Prior to this, RendererGeode constructed the four pipeline objects below (~18 wgpu pipelines in total, most inside GeodeFilterEngine) per-instance, so the image-comparison suite leaked ~1.6 MB per test and ultimately exhausted the driver memory budget (see issue #575). Moving ownership here - one copy per GeodeDevice - caps the pipeline footprint at a fixed cost.

Every renderer that talks to this device shares the same pipeline objects; their state is intentionally immutable after construction (no per-draw mutation), so concurrent use from sibling renderers is safe as long as it is serialized at the wgpu::Queue level (which Donner's render path already is).

GeodePipelinepipeline () const
 Slug solid-fill render pipeline.
GeodeGradientPipelinegradientPipeline () const
 Slug gradient-fill render pipeline.
GeodeImagePipelineimagePipeline () const
 Image-blit render pipeline (used by GeoEncoder::drawImage and the pattern / layer composition path).
GeodeMaskPipelinemaskPipeline () const
 Clip-path mask render pipeline. Built lazily on first access rather than eagerly at device creation, matching the prior per-encoder lazy path - most documents don't use <clipPath> and the production WASM path avoids the cost.
GeodeFilterEnginefilterEngine () const
 GPU filter-graph executor. Owns ~15 compute pipelines for SVG filter primitives.
GeodeCheckerboardPipelinecheckerboardPipeline () const
 Framebuffer checkerboard underlay pipeline used by the editor's direct presentation path. Built lazily on first access - only the editor draws it, so headless/WASM consumers never pay the compile cost.

Static Public Member Functions

static std::unique_ptr< GeodeDevice > CreateHeadless ()
 Create a headless GeodeDevice.
static std::unique_ptr< GeodeDevice > CreateFromExternal (const GeodeEmbedConfig &config)
 Create a GeodeDevice wrapping a host-provided device and queue.
static int headlessCreationCountForTesting ()
 Number of CreateHeadless calls made so far in this process. Each headless creation stands up a full WebGPU instance/adapter/device, so hot paths must share one device instead of re-creating; tests pin that sharing by asserting this count stays flat across repeated operations.

Detailed Description

Owns (or wraps) a WebGPU device/queue pair for GPU rendering.

GeodeDevice is the entry point to the Geode rendering backend. In headless mode (CreateHeadless), it creates a WebGPU instance, selects a default adapter, and creates a device - all without any window system integration.

In embedded mode (CreateFromExternal), it wraps a device and queue already created by the host application. The host retains ownership of the underlying WebGPU objects; GeodeDevice's destructor will not destroy them.

Typical headless usage:

auto maybeDevice = GeodeDevice::CreateHeadless();
if (!maybeDevice) {
  // No GPU available.
  return;
}

Typical embedded usage:

GeodeEmbedConfig config;
config.device = hostDevice;
config.queue = hostQueue;
auto geodeDevice = GeodeDevice::CreateFromExternal(config);

Member Function Documentation

◆ CreateFromExternal()

std::unique_ptr< GeodeDevice > donner::geode::GeodeDevice::CreateFromExternal ( const GeodeEmbedConfig & config)
static

Create a GeodeDevice wrapping a host-provided device and queue.

The returned device does NOT own the underlying WebGPU instance, adapter, device, or queue - the host is responsible for keeping them alive.

Parameters
configEmbedding configuration with valid device/queue handles.
Returns
A valid GeodeDevice on success, or null if config.device or config.queue is null.

◆ CreateHeadless()

std::unique_ptr< GeodeDevice > donner::geode::GeodeDevice::CreateHeadless ( )
static

Create a headless GeodeDevice.

Returns
A valid GeodeDevice on success, or an empty unique_ptr if the runtime could not create an adapter/device (e.g., no GPU, no driver).

◆ deferDestroy() [1/2]

void donner::geode::GeodeDevice::deferDestroy ( wgpu::Buffer buffer)

Enqueue a GPU buffer for deferred destruction.

The buffer handle is kept alive until drainDeferredDestroys() is called, preventing the underlying GPU resource from being freed while an in-flight command buffer may still reference it.

◆ deferDestroy() [2/2]

void donner::geode::GeodeDevice::deferDestroy ( wgpu::Texture texture)

Enqueue a GPU texture for deferred destruction.

Same semantics as the buffer variant.

◆ deviceId()

uint64_t donner::geode::GeodeDevice::deviceId ( ) const
inline

Process-unique identity for this device instance, assigned at construction from a monotonic counter (never reused, starts at 1).

Used by GPU-residence slots (design doc 0030 wave 2) to detect when a cached buffer / bind group belongs to a DIFFERENT device than the one now rendering: a document (and its ECS GeodeResidentPathComponents) can outlive the device that filled them and later be rendered by a second RendererGeode / GeodeDevice. WebGPU rejects cross-device resources inside a render pass, so a slot whose stored id does not match deviceId() is treated as non-resident and re-uploaded. A monotonic counter (rather than a raw this pointer) avoids the ABA hazard of a freed device's address being recycled by a later allocation.

◆ drainDeferredDestroys()

void donner::geode::GeodeDevice::drainDeferredDestroys ( )

Drop all deferred-destroy handles, releasing their GPU resources.

Called at the top of each frame (before new allocations) so resources from the previous frame's command buffer submission have had time to complete on the GPU. WebGPU internally reference-counts resources used by submitted command buffers, so dropping our handle here is safe even without an explicit device.poll().

◆ identityInstanceTransformBuffer()

const wgpu::Buffer & donner::geode::GeodeDevice::identityInstanceTransformBuffer ( ) const

One-element instance-transform storage buffer carrying the identity affine. Bound at binding 7 of the Slug fill bind-group layout by every non-instanced solid fill so the bind-group layout stays stable across draw calls regardless of whether fillPathInstanced is in play. See design doc 0030 §M6 Bullet 2.

Layout mirrors the WGSL InstanceTransform struct in shaders/slug_fill.wgsl: two vec4f per entry, row-major affine, so the identity is {(1, 0, 0, 0), (0, 1, 0, 0)}. The .z components carry the translation (0 for identity).

◆ setCounters()

void donner::geode::GeodeDevice::setCounters ( GeodeCounters * counters)
inline

Install a GeodeCounters struct for this device.

Non-owning; the caller must keep the struct alive for as long as the device might increment it. Pass nullptr to disable instrumentation.

See design doc 0030 (geode_performance). All Geode components that hold a GeodeDevice& route their per-frame hot-path allocation and submission sites through this hook.

◆ supportsTimestamps()

bool donner::geode::GeodeDevice::supportsTimestamps ( ) const
inline

Whether the driver supports GPU timestamp queries.

Always false today - reserved for future work (design doc 0030, "Future Work").


The documentation for this class was generated from the following file: