Donner 0.5.1
Embeddable browser-grade SVG2 engine
Loading...
Searching...
No Matches
donner::geode::GeoEncoder Class Reference

Drawing API for the Geode GPU renderer. More...

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

Classes

struct  PatternPaint
 Describes a pattern tile used as a paint source for fillPathPattern. More...

Public Member Functions

 GeoEncoder (GeodeDevice &device, const GeodePipeline &fillPipeline, const GeodeGradientPipeline &gradientPipeline, const GeodeImagePipeline &imagePipeline, const wgpu::Texture &msaaTarget, const wgpu::Texture &resolveTarget)
 Create an encoder targeting the given texture pair.
 GeoEncoder (GeodeDevice &device, const GeodePipeline &fillPipeline, const GeodeGradientPipeline &gradientPipeline, const GeodeImagePipeline &imagePipeline, const wgpu::Texture &msaaTarget, const wgpu::Texture &resolveTarget, wgpu::CommandEncoder sharedCommandEncoder)
 Shared-CommandEncoder constructor (design doc 0030 Milestone 3).
 GeoEncoder (const GeoEncoder &)=delete
GeoEncoderoperator= (const GeoEncoder &)=delete
 GeoEncoder (GeoEncoder &&) noexcept
 Move constructor.
GeoEncoderoperator= (GeoEncoder &&) noexcept
 Move assignment operator.
void clear (const css::RGBA &color)
 Clear the target texture to the given color.
void setLoadPreserve ()
 Switch the next render pass's load op from Clear to Load, preserving whatever the target texture already contains.
void setTransform (const Transform2d &transform)
 Set the model-view transform for subsequent draw calls.
void setScissorRect (int32_t x, int32_t y, int32_t w, int32_t h)
 Set a scissor rectangle in target-pixel coordinates.
void clearScissorRect ()
 Remove any active scissor, restoring full-target rasterization.
void setClipPolygon (const Vector2d corners[4])
 Activate a convex 4-vertex clip polygon (Phase 3a).
void clearClipPolygon ()
 Clear any active clip polygon, restoring unclipped rasterisation (or falling back to just the scissor rect, if one is set).
void beginMaskPass (const wgpu::Texture &msaaMask, const wgpu::Texture &resolveMask)
 Phase 3b: open a new render pass that writes into the given mask texture pair.
void fillPathIntoMask (const Path &path, FillRule rule, const EncodedPath *precomputedEncoded=nullptr)
 Fill path into the currently open mask pass using the Slug mask pipeline.
void endMaskPass ()
 Close the mask render pass opened by beginMaskPass.
void setClipMask (const wgpu::TextureView &maskView)
 Bind maskView as the clip mask texture for subsequent fill / gradient draws.
void setClipMask (const wgpu::Texture &maskTexture, const wgpu::TextureView &maskView)
 Preferred overload: sets both the view AND the parent texture so the encoder keeps the underlying Vulkan resource alive for as long as it's bound.
void clearClipMask ()
 Remove any active clip mask, restoring unclipped rasterisation.
void blitFullTarget (const wgpu::Texture &src, double opacity)
 Blit an offscreen texture across the entire target with an alpha multiplier.
void blitFullTargetMasked (const wgpu::Texture &content, const wgpu::Texture &mask, const std::optional< Box2d > &maskBounds)
 Phase 3c <mask> compositing.
void blitFullTargetBlended (const wgpu::Texture &layer, const wgpu::Texture &dstSnapshot, uint32_t blendMode, double opacity)
 Phase 3d mix-blend-mode compositing.
void drawImage (const svg::ImageResource &image, const Box2d &destRect, double opacity, bool pixelated)
 Draw a raster image into the given destination rectangle.
void fillPath (const Path &path, const css::RGBA &color, FillRule rule, const EncodedPath *precomputedEncoded=nullptr)
 Fill a path with a solid color.
void fillPathInstanced (const EncodedPath &encoded, const css::RGBA &color, FillRule rule, std::span< const float > instanceTransforms)
 Fill N copies of the same encoded path at N different affine transforms, in one GPU draw call (Milestone 6 Bullet 2).
void fillPathLinearGradient (const Path &path, const LinearGradientParams &params, FillRule rule, const EncodedPath *precomputedEncoded=nullptr)
 Fill a path with a linear gradient.
void fillPathRadialGradient (const Path &path, const RadialGradientParams &params, FillRule rule, const EncodedPath *precomputedEncoded=nullptr)
 Fill a path with a radial gradient.
void fillPathPattern (const Path &path, FillRule rule, const PatternPaint &paint, const EncodedPath *precomputedEncoded=nullptr)
 Fill a path with a repeating pattern tile.
void finish ()
 Submit all encoded commands to the GPU queue.

Detailed Description

Drawing API for the Geode GPU renderer.

GeoEncoder is a per-frame command builder. Construct one against a target texture, issue draw calls (fillPath, clear, etc.), then call finish() to submit the command buffer to the GPU.

The encoder owns no GPU buffers itself — each draw call allocates fresh vertex / band / curve / uniform buffers. This is the simplest possible implementation; later phases will add buffer pooling and the ECS-backed GeodePathCacheComponent for paths whose geometry hasn't changed.

Typical usage:

GeoEncoder encoder(device, pipeline, targetTexture);
encoder.clear(css::RGBA::White);
encoder.setTransform(Transform2d::Scale(2.0));
encoder.fillPath(myPath, css::RGBA::Red, FillRule::NonZero);
encoder.finish();

Class Documentation

◆ donner::geode::GeoEncoder::PatternPaint

struct donner::geode::GeoEncoder::PatternPaint

Describes a pattern tile used as a paint source for fillPathPattern.

The tile texture is expected to contain pre-rendered pattern content in premultiplied RGBA. The Slug fill shader samples it with the Repeat wrap mode (equivalent to SVG <pattern> default behaviour) using the provided transform to map path-space positions into tile-space.

Collaboration diagram for donner::geode::GeoEncoder::PatternPaint:
[legend]
Class Members
double opacity = 1.0 Multiplicative alpha applied to the sampled tile color. Usually fill-opacity * opacity.
Transform2d patternFromPath Transform from path space (where the path being filled lives) to pattern tile space. Typically inverse(targetFromPattern) composed with the current encoder transform — the RendererGeode layer builds the right composition.
Texture tile Pre-rendered tile texture (RGBA8, premultiplied).
Vector2d tileSize Size of the tile rectangle in pattern space (width, height). The shader uses this to wrap sample positions via fract().

Constructor & Destructor Documentation

◆ GeoEncoder() [1/2]

donner::geode::GeoEncoder::GeoEncoder ( GeodeDevice & device,
const GeodePipeline & fillPipeline,
const GeodeGradientPipeline & gradientPipeline,
const GeodeImagePipeline & imagePipeline,
const wgpu::Texture & msaaTarget,
const wgpu::Texture & resolveTarget )

Create an encoder targeting the given texture pair.

The encoder uses 4× MSAA internally: every render pass attaches msaaTarget as the color attachment and resolveTarget as the pass's resolve attachment. The MSAA attachment's store op is Store so multi-pass work (e.g., re-opening a pass via setLoadPreserve() after a nested layer composite) can pick up the previous MSAA state via LoadOp::Load.

External code reads back / samples from the resolveTarget (always 1-sample), never from the MSAA texture, because sampling an MSAA texture requires texture_multisampled_2d bindings in WGSL which the image-blit shader doesn't use.

Parameters
deviceThe Geode device (owns the wgpu::Device + queue).
fillPipelineThe Slug fill pipeline (4× multisample).
gradientPipelineThe Slug gradient-fill pipeline (4× multisample).
imagePipelineThe image-blit pipeline (4× multisample).
msaaTarget4× multisampled render target texture. Usage must include RenderAttachment. Same width/height as resolveTarget.
resolveTarget1-sample resolve texture. Usage must include RenderAttachment + TextureBinding + CopySrc (for readback). The encoder retains references to both; both must outlive finish().

◆ GeoEncoder() [2/2]

donner::geode::GeoEncoder::GeoEncoder ( GeodeDevice & device,
const GeodePipeline & fillPipeline,
const GeodeGradientPipeline & gradientPipeline,
const GeodeImagePipeline & imagePipeline,
const wgpu::Texture & msaaTarget,
const wgpu::Texture & resolveTarget,
wgpu::CommandEncoder sharedCommandEncoder )

Shared-CommandEncoder constructor (design doc 0030 Milestone 3).

Uses the provided sharedCommandEncoder instead of creating its own. When this overload is used, finish() only ends any open render pass — it does NOT finish the CommandEncoder or submit to the queue. The caller (typically RendererGeode) owns the lifetime of the shared encoder and is responsible for calling sharedCommandEncoder.finish() + queue.submit() exactly once at the end of the frame.

Enables push/pop of isolated layers / filter layers / mask layers without forcing a queue submit per layer boundary — the whole frame's render passes batch into a single command buffer.

Member Function Documentation

◆ beginMaskPass()

void donner::geode::GeoEncoder::beginMaskPass ( const wgpu::Texture & msaaMask,
const wgpu::Texture & resolveMask )

Phase 3b: open a new render pass that writes into the given mask texture pair.

Used by RendererGeode::pushClip to materialise a path-based clip into an RGBA8Unorm coverage texture that subsequent fill/gradient draws can sample.

The main render pass, if open, is closed first. Subsequent fillPathIntoMask calls add paths to the mask via the Slug mask pipeline. endMaskPass closes the mask pass and re-opens the main pass (with LoadOp::Load) when the next draw lands.

Parameters
msaaMask4× MSAA RGBA8Unorm render target. Must be the same size as this encoder's target when GeodeDevice::sampleCount() > 1. On the alpha-coverage path (sampleCount() == 1) this may be null and the pass draws directly into resolveMask.
resolveMask1-sample RGBA8Unorm resolve target. Sampled by setClipMask after endMaskPass.

◆ blitFullTarget()

void donner::geode::GeoEncoder::blitFullTarget ( const wgpu::Texture & src,
double opacity )

Blit an offscreen texture across the entire target with an alpha multiplier.

Used by RendererGeode::popIsolatedLayer to composite a sub-layer's content back onto the outer target. The source texture must be the SAME SIZE as this encoder's target — the blit takes the full texture and maps it 1:1 onto the full target. The current setTransform does NOT affect the blit (it's a device-pixel-space operation, not a model-space draw).

Parameters
srcSource texture (RGBA8, premultiplied).
opacityOverall alpha multiplier in [0, 1].

◆ blitFullTargetBlended()

void donner::geode::GeoEncoder::blitFullTargetBlended ( const wgpu::Texture & layer,
const wgpu::Texture & dstSnapshot,
uint32_t blendMode,
double opacity )

Phase 3d mix-blend-mode compositing.

Blits layer across the entire target using one of the 16 W3C Compositing Level 1 blend formulas, reading the backdrop from dstSnapshot (which the caller has already copied from the parent target because the shader cannot read the render pass's own color attachment).

Both textures must be the SAME SIZE as this encoder's target and already stored in premultiplied alpha. The encoder's pass must be in a LoadOp::Clear state — the blend shader writes the final pixel directly and relies on the render target being zeroed before the draw.

Parameters
layerOffscreen RGBA8 layer texture (premultiplied).
dstSnapshotFrozen copy of the parent target's current state (premultiplied) — the blend's backdrop.
blendModeValue in 1..=16 matching the donner::svg::MixBlendMode enumeration. A value of 0 silently falls through to no-op.
opacityGroup opacity in [0, 1]. Applied to the layer BEFORE the blend formula so an opacity + mix-blend-mode combo on the same element composes correctly — the source colour that enters the blend is layer * opacity.

◆ blitFullTargetMasked()

void donner::geode::GeoEncoder::blitFullTargetMasked ( const wgpu::Texture & content,
const wgpu::Texture & mask,
const std::optional< Box2d > & maskBounds )

Phase 3c <mask> compositing.

Same as blitFullTarget but additionally samples a luminance mask texture and multiplies the content by the mask's BT.709 luminance (× alpha, matching tiny-skia's Mask::fromPixmap(Luminance)). When maskBounds is provided, pixels outside the rect are discarded so the <mask> element's x/y/width/height are honoured.

Parameters
contentOffscreen RGBA8 content texture, premultiplied, same size as this encoder's target.
maskOffscreen RGBA8 mask texture, premultiplied, same size as content. The mask's luminance × alpha becomes a per-pixel coverage multiplier on the content.
maskBoundsOptional clip rect in target-pixel space.

◆ clear()

void donner::geode::GeoEncoder::clear ( const css::RGBA & color)

Clear the target texture to the given color.

Must be called before any draw calls — clear is implemented as the load op of the first render pass, so calling it after a draw is a no-op. Subsequent calls override the previous clear color.

◆ drawImage()

void donner::geode::GeoEncoder::drawImage ( const svg::ImageResource & image,
const Box2d & destRect,
double opacity,
bool pixelated )

Draw a raster image into the given destination rectangle.

The image's straight-alpha RGBA8 pixels are uploaded to a fresh wgpu::Texture and sampled through the image-blit pipeline. The current transform is applied via the MVP matrix, and the destination rectangle is specified in local (pre-transform) coordinates — i.e., the transform and the rect compose the same way as any other draw call against this encoder.

Parameters
imageDecoded RGBA8 image resource. No-op if empty or zero-size.
destRectDestination rectangle in local (pre-transform) space.
opacityOverall opacity in [0, 1], combined with the sampled texel's alpha in the fragment shader.
pixelatedIf true, use nearest-neighbor filtering (for image-rendering: pixelated). Otherwise, bilinear.

◆ fillPath()

void donner::geode::GeoEncoder::fillPath ( const Path & path,
const css::RGBA & color,
FillRule rule,
const EncodedPath * precomputedEncoded = nullptr )

Fill a path with a solid color.

The path is encoded into Slug band data on the CPU, uploaded to GPU buffers, and a draw call is recorded. The fill is applied with the current transform.

Parameters
pathThe path to fill.
colorSolid fill color (NOT premultiplied — the encoder handles premultiplication for the blend pipeline).
ruleFill rule (NonZero or EvenOdd).
precomputedEncodedOptional M2 cache-hit payload — see GeodePathCacheComponent. When non-null, skips encode + counter bump.

◆ fillPathInstanced()

void donner::geode::GeoEncoder::fillPathInstanced ( const EncodedPath & encoded,
const css::RGBA & color,
FillRule rule,
std::span< const float > instanceTransforms )

Fill N copies of the same encoded path at N different affine transforms, in one GPU draw call (Milestone 6 Bullet 2).

The caller has already packed each transform into the wire format the shader expects: two vec4f rows per instance (row-major affine, 32 bytes per entry). See donner/svg/renderer/geode/shaders/slug_fill.wgsl struct InstanceTransform for the exact layout.

The vertex shader composes each instance's transform with the currently-bound uniforms.mvp, so encoder.setTransform(...) still applies an outer world transform; per-instance data is the delta relative to that. Pass Transform2d::Identity to setTransform if instance transforms already encode the full path→clip-space mapping (the common case for <use> batching, where each instance's transform is the full worldFromEntity).

If instanceTransforms is empty the call is a no-op. If instanceTransforms.size() == 1 this is equivalent to a single fillPath with the given transform folded in — the caller can still prefer it when bouncing through the batcher.

Parameters
encodedPrecomputed EncodedPath shared across all instances. Required (non-null) — there's no "encode inline" path here; the whole point is to amortize one encode across many draws.
colorSolid fill color (NOT premultiplied).
ruleFill rule.
instanceTransformsSpan of {row0, row1} vec4f pairs, two consecutive vec4f per instance (32 bytes each). The span must contain exactly 8 * instanceCount floats.

◆ fillPathIntoMask()

void donner::geode::GeoEncoder::fillPathIntoMask ( const Path & path,
FillRule rule,
const EncodedPath * precomputedEncoded = nullptr )

Fill path into the currently open mask pass using the Slug mask pipeline.

Must be called between beginMaskPass and endMaskPass. The current encoder transform applies (so clip paths use the same device-pixel mapping as the content being clipped).

Parameters
precomputedEncodedOptional M2 cache-hit payload. When non-null, the encoder skips GeodePathEncoder::encode and the pathEncodes counter bump. Used by RendererGeode to plumb a cached GeodePathCacheComponent::strokeSlot result.

◆ fillPathLinearGradient()

void donner::geode::GeoEncoder::fillPathLinearGradient ( const Path & path,
const LinearGradientParams & params,
FillRule rule,
const EncodedPath * precomputedEncoded = nullptr )

Fill a path with a linear gradient.

Same CPU-side Slug band encoding as fillPath, but the shading stage uses the linear-gradient pipeline — each pixel inside the path is colored by sampling the stop list at a per-pixel parameter t computed from params.gradientFromPath, params.startGrad, and params.endGrad, then folded through params.spreadMode.

If the path encodes to zero bands or the stop list is empty, the call is a no-op.

Parameters
pathThe path to fill.
paramsLinear gradient parameters. Colors are straight alpha in 0..1 per channel; the encoder premultiplies before upload. Up to 16 stops are honored; excess are silently truncated with a one-shot verbose warning at the call site in RendererGeode.
ruleFill rule (NonZero or EvenOdd).

◆ fillPathPattern()

void donner::geode::GeoEncoder::fillPathPattern ( const Path & path,
FillRule rule,
const PatternPaint & paint,
const EncodedPath * precomputedEncoded = nullptr )

Fill a path with a repeating pattern tile.

The pattern texture must have been rendered earlier (e.g., by a nested GeoEncoder) and contains premultiplied RGBA. The shader applies the Slug winding-number coverage test identically to fillPath, and samples the tile for pixels inside the path.

◆ fillPathRadialGradient()

void donner::geode::GeoEncoder::fillPathRadialGradient ( const Path & path,
const RadialGradientParams & params,
FillRule rule,
const EncodedPath * precomputedEncoded = nullptr )

Fill a path with a radial gradient.

Same CPU encoding and GPU-dispatch machinery as fillPathLinearGradient, but the gradient parameter t is derived from a two-circle radial construction in the shader (see radial_t() in shaders/slug_gradient.wgsl).

If the path encodes to zero bands, the stop list is empty, or params.radius <= 0, the call is a no-op.

Parameters
pathThe path to fill.
paramsRadial gradient parameters (center + radius, optional focal point + radius, shared transform and stops).
ruleFill rule (NonZero or EvenOdd).

◆ finish()

void donner::geode::GeoEncoder::finish ( )

Submit all encoded commands to the GPU queue.

After this call, the encoder is in a "finished" state and no further draws can be issued. The caller is responsible for any synchronization (e.g., MapAsync + Tick loop) needed to actually use the rendered output.

◆ setClipMask() [1/2]

void donner::geode::GeoEncoder::setClipMask ( const wgpu::Texture & maskTexture,
const wgpu::TextureView & maskView )

Preferred overload: sets both the view AND the parent texture so the encoder keeps the underlying Vulkan resource alive for as long as it's bound.

The 1-arg overload is kept only for call sites that guarantee the parent's lifetime by other means.

See the activeClipMaskTexture field comment (and issue #551) for the bug this addresses — without the parent keepalive, popping the clip stack can free a VkImage while the encoder's createBindGroup still references its view.

◆ setClipMask() [2/2]

void donner::geode::GeoEncoder::setClipMask ( const wgpu::TextureView & maskView)

Bind maskView as the clip mask texture for subsequent fill / gradient draws.

The view must reference a 1-sample RGBA8Unorm texture the same size as the encoder's target — typically the resolve texture produced by beginMaskPass + endMaskPass.

The shader samples .r at the pixel center and multiplies it into fragment coverage, so the mask represents a pre-rendered clip region in [0, 1].

◆ setClipPolygon()

void donner::geode::GeoEncoder::setClipPolygon ( const Vector2d corners[4])

Activate a convex 4-vertex clip polygon (Phase 3a).

Unlike setScissorRect, this clips to the exact parallelogram described by the 4 corners — used for <symbol> / <svg> / <use> viewports that have a non-axis-aligned ancestor transform where WebGPU's rectangular scissor can only express the AABB of the transformed rect, not the true polygon. The fragment shader tests each of 4 edge half-planes against its sub-pixel sample positions and AND's the result into @builtin(sample_mask) so clipping integrates with the 4× MSAA coverage path.

Parameters
corners4 polygon vertices in target-pixel space, given in consistent (clockwise OR counter-clockwise) winding order. The encoder normalises edge normals so a fragment strictly INSIDE the polygon satisfies every half-plane test.

◆ setLoadPreserve()

void donner::geode::GeoEncoder::setLoadPreserve ( )

Switch the next render pass's load op from Clear to Load, preserving whatever the target texture already contains.

Useful when the encoder is being reused to append draws on top of previously submitted content (e.g., resuming outer-frame drawing after a nested pattern-tile pass).

Must be called before any draws — once a render pass is open it's too late to change its load op.

◆ setScissorRect()

void donner::geode::GeoEncoder::setScissorRect ( int32_t x,
int32_t y,
int32_t w,
int32_t h )

Set a scissor rectangle in target-pixel coordinates.

Subsequent draws are clipped to the intersection of (0,0,targetWidth,targetHeight) and this rectangle. Used by RendererGeode::pushClip to implement SVG viewport rect clipping (nested <svg> clip, overflow: hidden, etc.).

The scissor persists across setTransform, fillPath, and friends until explicitly cleared via clearScissorRect.

Negative x/y and widths/heights that extend past the target are clamped internally; the caller can pass any AABB in pixel space without bounds-checking.


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