RUNE-304: Drawing Proposed
Summary
Rune records drawing as one typed, 8-byte-aligned command stream plus one typed data arena per RenderNodeSnapshot. DATA_PATH version 2 can carry original bytecode plus source-aware arc-length metrics. CMD_STROKE_TRIMMED_PATH consumes an anonymous measured path to reveal a normalized stroke interval with Windows Composition-compatible wrapping while fill and clip keep the complete path. Retained RuneSvg assets participate in the same command stream through CMD_DRAW_SVG.
Motivation
The drawing model must keep snapshots compact, replayable by linear scan, and self-contained enough for render-thread traversal. At the same time, Rune needs stable paint identity across snapshots, explicit transform state, animated path geometry, child composition that participates in structural bounds and replay uniformly, and a backend where solid, gradient, image, text, and double-rrect all flow through one replay pipeline. Encoding children as CMD_DRAW_CHILD, keeping paint in CMD_DATA, and moving path payloads into typed DATA_PATH records preserves that uniformity without forcing parent command-buffer rebuilds whenever only a child snapshot changes, while ResourceSlot gives brush and path realizations retained lifetimes instead of per-frame cache keys.
Design
Command Header and Op Set
Every record starts with the same header:
struct CmdHeader {
uint8_t op;
uint8_t sub_type;
uint16_t size;
};op selects the record family, sub_type refines families such as CMD_DATA, and size advances linear replay across fixed-size and variable-size records alike.
enum RuneCommandOp : uint8_t {
CMD_DATA,
CMD_SET_OFFSET, CMD_SET_OPACITY,
CMD_SAVE_TRANSFORM, CMD_RESTORE_TRANSFORM,
CMD_SET_TRANSFORM, CMD_CONCAT_TRANSFORM,
CMD_CONCAT_SCALE, CMD_CONCAT_ROTATION, CMD_SET_TRANSFORM_ORIGIN,
CMD_DRAW_RECT, CMD_DRAW_RRECT, CMD_DRAW_DOUBLE_RRECT, CMD_DRAW_ELLIPSE, CMD_DRAW_ARC, CMD_DRAW_PATH,
CMD_STROKE_LINE, CMD_STROKE_PATH, CMD_STROKE_TRIMMED_PATH,
CMD_DRAW_NINE_SLICE, CMD_DRAW_SHAPED_TEXT, CMD_DRAW_CHILD,
CMD_PUSH_CLIP_RECT, CMD_PUSH_CLIP_RRECT, CMD_PUSH_CLIP_PATH, CMD_POP_CLIP,
CMD_DRAW_BOX_SHADOW, CMD_DRAW_BOX_SHADOW4, CMD_DRAW_SVG,
};That is 29 ops total: 1 data op, 2 property ops, 7 transform ops, 9 shape/effect/asset draw ops, 3 stroke ops, 3 other draw ops, and 4 clip ops. Consumers must use symbolic names rather than command numbers.
CMD_DATA and Sub Types
CMD_DATA is the shared carrier for reusable brush descriptors:
enum RuneDataType : uint8_t {
DATA_BRUSH_SOLID,
DATA_BRUSH_LINEAR_GRADIENT,
DATA_BRUSH_RADIAL_GRADIENT,
DATA_BRUSH_CONIC_GRADIENT,
DATA_BRUSH_IMAGE,
DATA_PATH_PROGRAM, // legacy inline path subtype, no longer recorded
};DATA_BRUSH_*stores a brush descriptor plus theResourceSlot*that replay configures.- Paths now live in
data_bufferasDATA_PATH, addressed byPathoffsets instead ofCMD_DATAsubtypes.
This keeps the op set small while preserving one linear replay path for executable commands. sub_type is therefore semantic, not padding.
Property and Transform Ops
Property and transform ops live in the same buffer as draw data:
struct CmdSetOffset { CmdHeader header; Value x, y; };
struct CmdSetOpacity { CmdHeader header; Value alpha; };
struct CmdSaveTransform { CmdHeader header; };
struct CmdRestoreTransform { CmdHeader header; };
struct CmdSetTransform { CmdHeader header; Value m11, m12, m21, m22, m31, m32; };
struct CmdConcatTransform { CmdHeader header; Value m11, m12, m21, m22, m31, m32; };
struct CmdConcatScale { CmdHeader header; Value sx, sy; };
struct CmdConcatRotation { CmdHeader header; Value degrees; };
struct CmdSetTransformOrigin { CmdHeader header; Value x, y; uint8_t mode; };Transform replay is explicit command-stream state:
CMD_SAVE_TRANSFORMpushes the active matrix and transform origin.CMD_RESTORE_TRANSFORMpops and restores the previous matrix and transform origin. If no saved state exists, replay treats it as a no-op so inherited parent transform state is not discarded.CMD_SET_TRANSFORMreplaces the active 3x2 matrix.CMD_CONCAT_TRANSFORMappends a raw 3x2 matrix.CMD_CONCAT_SCALEandCMD_CONCAT_ROTATIONappend semantic transforms around the current transform origin.CMD_SET_TRANSFORM_ORIGINsets the origin used by later semantic concat commands. Origin mode can be absolute local pixels or relative to untransformed layout/content bounds; if neither layout nor local bounds exist, relative origin falls back to the local origin(0,0).- All numeric fields stay in
Value, so static and animated values share the same decode path.
Replay mirrors the active matrix to ID2D1DeviceContext::SetTransform. Local content bounds remain untransformed; snapshot visual bounds are conservatively transformed for capture, projection, and transient layer allocation and account for command offset plus save/restore-scoped transform state.
Fill and Stroke Commands
All shape and stroke commands carry geometry plus typed handles. Brush is a uint32_t cmd_buffer offset to a brush CMD_DATA record. Path is a uint32_t data_buffer offset to a DATA_PATH record that points to path bytecode and the slot used for path-geometry caching.
typedef struct Path { uint32_t _id; } Path;
typedef struct Brush { uint32_t _id; } Brush;
struct CmdDrawRect { CmdHeader header; Value x, y, w, h; Brush brush; };
struct CmdDrawRRect { CmdHeader header; Value x, y, w, h, radius; Brush brush; };
struct CmdDrawDoubleRRect { CmdHeader header; Value x, y, w, h, radius, border_width; Brush fill_brush; Brush border_brush; };
struct CmdDrawBoxShadow { CmdHeader header; Value x, y, w, h, radius, offset_x, offset_y, blur_radius, spread, c0, c1, c2, alpha; uint8_t color_space; };
struct CmdDrawBoxShadow4 { CmdHeader header; Value x, y, w, h, top_left, top_right, bottom_right, bottom_left, offset_x, offset_y, blur_radius, spread, c0, c1, c2, alpha; uint8_t color_space; };
struct CmdDrawEllipse { CmdHeader header; Value cx, cy, rx, ry; Brush brush; };
struct CmdDrawArc { CmdHeader header; Value cx, cy, rx, ry, start_angle, sweep_angle; Brush brush; };
struct CmdDrawPath { CmdHeader header; Path path; Brush brush; };
struct CmdStrokeLine {
CmdHeader header;
Value x1, y1, x2, y2, width;
Brush brush;
uint8_t cap;
};
struct CmdStrokePath {
CmdHeader header; // size includes inline dash values
Path path;
Value width;
float miter_limit;
float dash_offset;
Brush brush;
uint8_t cap, join, dash_count;
// float dash_values[dash_count] follows
};
struct CmdStrokeTrimmedPath {
CmdHeader header;
Path path;
Value width, trim_start, trim_end, trim_offset;
float miter_limit;
Brush brush;
uint8_t cap, join;
uint16_t reserved;
};Dash values stay inline in CmdStrokePath because they are small command-local payloads rather than reusable resources. A null dash pointer records a solid stroke even if a non-zero dash count is supplied.
CmdStrokeTrimmedPath is a fixed 36-byte command. Its recording API atomically appends a slot-backed anonymous measured DATA_PATH and the command; width/start/end/offset remain live Values. Trim is stroke-only in V1: fill and clip continue using complete immutable geometry, and the existing dashed stroke command is unchanged.
Trim normalization sanitizes non-finite values to zero, clamps start/end to [0,1], classifies exact empty/full before offset, and applies positive-modulo offset. end < start is a forward seam-wrapped interval, matching Windows Composition. Lottie instead sorts start/end, so importers must translate that semantic difference. start=1,end=0 is empty.
CmdDrawArc records a filled pie-sector command. Angles are radians, 0 points right, positive sweep is clockwise in the D2D y-down coordinate space, and a full sweep fills the equivalent ellipse.
D2D replay maps path fills to FillGeometry, stroke paths to DrawGeometry, stroke lines to DrawLine, and arcs to command-local geometry plus FillGeometry. Stroke commands use stroked visual bounds for relative gradient and image brush mapping. ID2D1StrokeStyle objects are command-local in the first implementation; style caching is deferred until profiling shows repeated styled strokes are hot.
Other Draw, Text, Child, and Clip Commands
struct CmdDrawNineSlice { CmdHeader header; Value x, y, w, h; NineSlice* nine_slice; };
struct CmdDrawShapedText { CmdHeader header; ShapedText* shaped; uint32_t glyph_start, glyph_count; Value x, y; Brush brush; };
struct CmdDrawChild { CmdHeader header; uint16_t child_slot; };
struct CmdDrawSvg { CmdHeader header; RuneSvg* svg; Value x, y, w, h; uint8_t preserve_aspect; };
struct CmdPushClipRect { CmdHeader header; Value x, y, w, h; };
struct CmdPushClipRRect { CmdHeader header; Value x, y, w, h, radius; };
struct CmdPushClipPath { CmdHeader header; Path path; };
struct CmdPopClip { CmdHeader header; };CmdDrawChild is recorded inline beside every other draw command. Its child_slot indexes into the owning snapshot's child_slots[] array. It participates in structural snapshot bounds and child-boundary replay, with contribution derived from the referenced child snapshot's projected bounds rather than stored in the command record.
CmdDrawNineSlice references an immutable NineSlice resource created from a Image, source rect, source insets, and interpolation mode. D2D replay resolves the image through the per-device bitmap cache, splits the source and destination into a 3x3 grid, and draws non-empty cells with DrawBitmap. V1 stretches edges and center only. If the destination is smaller than the fixed insets, replay scales destination insets proportionally so the center never becomes negative and cells do not overlap.
CmdDrawSvg references an immutable RuneSvg asset. The asset addrefs a finished root snapshot plus viewBox/intrinsic-size metadata and can be shared across draw sites. Replay maps the SVG viewBox into the live destination rectangle, resolves the retained snapshot at the current frame time, and replays it recursively. Bounds and value analysis recognize CMD_DRAW_SVG, so animated destinations and animated values inside the retained snapshot remain part of ordinary snapshot analysis. The preserve_aspect field is retained, but V1 replay stretches to the destination rectangle.
Clip replay uses a mixed D2D clip stack. Rectangular clips use PushAxisAlignedClip / PopAxisAlignedClip; rounded-rectangle and path clips use PushLayer with a geometry mask and PopLayer. CMD_POP_CLIP dispatches to the correct pop based on the stack entry, and end-of-snapshot cleanup unwinds any unbalanced clips. Path clips use Winding/non-zero fill in V1.
Replay stays linear until it encounters CMD_DRAW_CHILD:
- Decode the command header.
- Read
snapshot->child_slots[child_slot]from the owning snapshot. - Inspect the child snapshot's
opacityto choose direct replay or D2DPushLayergroup-opacity compositing.blend_modeandcache_hintare retained for future compositing/cache features but do not affect D2D replay in V1. - Recurse into that child snapshot using the selected path.
This keeps child composition in the command model instead of a parallel child tree structure embedded in the snapshot.
Brush Data Records
All paint lives in CMD_DATA records which carry the ResourceSlot* configured during replay. Brush recording functions (rune_node_brush_solid, etc.) return a Brush handle — the cmd_buffer offset of the brush CMD_DATA. Draw commands store this Brush offset and the D2D backend resolves it back to the slot during replay:
struct BrushSolid {
CmdHeader header; // CMD_DATA + DATA_BRUSH_SOLID
ResourceSlot* slot;
uint8_t color_space;
Value c0, c1, c2, alpha;
};
struct BrushLinearGradient {
CmdHeader header; // CMD_DATA + DATA_BRUSH_LINEAR_GRADIENT
ResourceSlot* slot;
uint8_t color_space, gradient_mode, tile_mode, stop_count;
Value start_x, start_y, end_x, end_y;
// GradientStopRV stops[stop_count] follows
};
struct BrushRadialGradient {
CmdHeader header; // CMD_DATA + DATA_BRUSH_RADIAL_GRADIENT
ResourceSlot* slot;
uint8_t color_space, gradient_mode, tile_mode, stop_count;
Value cx, cy, rx, ry, focal_x, focal_y;
// GradientStopRV stops[stop_count] follows
};
struct BrushConicGradient {
CmdHeader header; // CMD_DATA + DATA_BRUSH_CONIC_GRADIENT
ResourceSlot* slot;
uint8_t color_space, gradient_mode, tile_mode, stop_count;
Value cx, cy, start_angle, end_angle;
// GradientStopRV stops[stop_count] follows
};
struct BrushImage {
CmdHeader header; // CMD_DATA + DATA_BRUSH_IMAGE
ResourceSlot* slot;
Image* image;
uint8_t fill_mode, interpolation;
Value src_x, src_y, src_w, src_h, offset_x, offset_y;
};For image brushes, offset_x/offset_y are target-space offsets applied only to repeating axes: Tile applies both, TileX applies offset_x only, TileY applies offset_y only, and Stretch ignores both.
One brush format serves fill, stroke, shaped text, and both inputs of CMD_DRAW_DOUBLE_RRECT. Gradient stops remain inline so one slot-configured brush descriptor can be reused by many draws while preserving stable brush identity across snapshots and frames.
Typed Path Data
Path segments are stored as 4-byte-opcode bytecode inside DATA_PATH version 2. Its fixed header remains 64 bytes and measured payload is:
bytecode
MeasuredPathSegment[segment_count] // bytecode offset + segment start point
PathMetricKnot[knot_count] // cumulative length + segment index + source tEach measured record is 4-byte component aligned and 8-byte record aligned. The old sample count is now knot_count; the old reserved 32-bit field is segment_count. Draw-only CreatePath uses zero counts. SamplePath records a slotless anonymous measured path. Trimmed stroke records a separate slot-backed anonymous measured path; V1 intentionally has no public shared measured-Path handle.
Managed measurement retains source t while adaptively flattening curves at the default 0.25 DIP tolerance. Lookup binary-searches cumulative knots and evaluates the original Line/Quad/Cubic/Close instruction, so sampling and trim boundaries share one curve-faithful metric model. Exact boundary ties choose the next positive-length segment at t=0; Close contributes length; all-zero segment-bearing paths sample their initial point; move-only, dynamic, and multiple-figure measured paths are rejected.
Direct2D exposes complete-path measurement but no arc-length extraction. Partial trimmed replay therefore creates a new open ID2D1PathGeometry, copies complete interior source segments, and de Casteljau-splits boundary curves. A wrapped closed contour remains one figure across the seam; a wrapped open path uses tail and head figures. Full trim reuses the slot-cached complete geometry, and partial geometry never replaces it.
Recorded bounds use the complete source path expanded by the maximum animated width and cap/join factor. At replay, relative brush coordinates use complete geometry widened by the current resolved stroke style, never the partial trim geometry. Sampled X/Y descriptor ranges likewise use complete conservative source bounds rather than knot coordinates. This keeps structural bounds, composited layers, gradients, and follower alignment stable throughout reveal.
Sample and trim recording preflight the aligned measured path and complete pending DATA_EXPRESSION before mutation. Sampling additionally reserves expression output slots and builder bytes; trim additionally reserves the fixed command and resource ref. Malformed input returns invalid argument, dynamic measured geometry returns unsupported, and capacity returns failed without a success-shaped fallback.
Double Rounded Rect
CMD_DRAW_DOUBLE_RRECT remains Rune's dedicated border primitive:
struct CmdDrawDoubleRRect {
CmdHeader header;
Value x, y, w, h, radius, border_width;
Brush fill_brush;
Brush border_brush;
};Replay resolves both brush slots to D2D brushes. The D2D backend builds outer and inner rounded-rectangle geometries; the border is an alternate-fill geometry group and the fill is drawn in the inner geometry. The rendering model remains:
- outer shape =
(x, y, w, h, radius) - inner shape = inset by
border_width, withinner_radius = max(radius - border_width, 0) - result = fill interior + border ring, never a centerline stroke
Box Shadow
CMD_DRAW_BOX_SHADOW and CMD_DRAW_BOX_SHADOW4 are Rune's dedicated CSS-like outer shadow primitives for rounded rectangles:
struct CmdDrawBoxShadow {
CmdHeader header;
Value x, y, w, h, radius;
Value offset_x, offset_y;
Value blur_radius, spread;
Value c0, c1, c2, alpha;
uint8_t color_space;
};
struct CmdDrawBoxShadow4 {
CmdHeader header;
Value x, y, w, h;
Value top_left, top_right, bottom_right, bottom_left;
Value offset_x, offset_y;
Value blur_radius, spread;
Value c0, c1, c2, alpha;
uint8_t color_space;
};The rendering model is:
- scalar caster shape =
(x, y, w, h, radius) - per-corner caster shape =
(x, y, w, h, top_left, top_right, bottom_right, bottom_left) - shadow shape = caster translated by
(offset_x, offset_y)and inflated byspread - output bounds = shadow shape inflated by
blur_radius - negative spread is allowed; degenerate shadow geometry is skipped
V1 uses single-pass analytical SDF effects with scene-space constants, not a temporary render target blur. The shader applies a Gaussian-like finite falloff to the shadow SDF and multiplies it by an outside-caster mask, so the outer shadow is clipped out of the original caster interior. Shadow color is direct solid color values (color_space, c0, c1, c2, alpha) rather than a Brush; multiple shadows are recorded as multiple commands. CMD_DRAW_BOX_SHADOW4 uses top-left, top-right, bottom-right, bottom-left radius order and normalizes adjacent radii to fit the box.
Command Buffer Encoding and 1 MB Limit
A RenderNodeSnapshot owns one contiguous cmd_buffer plus one contiguous data_buffer. Property ops, CMD_DATA records, and draw or clip commands live in cmd_buffer; immutable DATA_PATH records live in data_buffer. Every record is 8-byte aligned so pointer-bearing payloads remain naturally aligned on 64-bit targets.
CommandBuffer::MAX_SIZE is 0x100000, so one snapshot may use up to 1 MB of cmd_buffer space. Brush remains a uint32_t cmd_buffer offset. Path is a typed uint32_t data_buffer offset. CmdHeader::size still stays uint16_t, so single commands remain capped at <= 0xFFFF bytes and push_with_extra() rejects any larger aligned payload, while DataHeader::size is 32-bit so a single DATA_PATH record is no longer constrained by the command header.
Variable-size brush payloads such as gradient stops and stroke dashes still depend only on CmdHeader::size, while path bytecode, measured segments, and knots use the data arena's 32-bit DataHeader::size.
D2D Brush Pipeline
Rune realizes brush CMD_DATA into slot-owned D2D resources. Solid brushes store a resolved color descriptor and lazily create ID2D1SolidColorBrush for ordinary drawing; shader-input paths can request a lazy Flood-effect image. Gradient brushes use effect outputs as their image source, and image brushes use a cached ID2D1Bitmap1 from the referenced Image.
- normal shapes: solid brushes feed
FillRectangle,FillGeometry, glyph drawing, and related draw paths asID2D1SolidColorBrush; gradient/image brushes feed those calls asID2D1ImageBrush - double-rrect: fill and border resolve to ordinary D2D brushes and draw disjoint rounded-rectangle geometry
- box shadow: reusable custom SDF effects draw solid-color scalar and per-corner rounded-rectangle shadows with finite bounds
- image fill: source image and sampling parameters resolve through the same slot/resource path as other brushes
ResourceSlot owns the D2D realization (direct solid brush, effect/image brush resources for brushes, or ID2D1PathGeometry for paths). Replay reads brush slots from resolved CMD_DATA and path slots from resolved DATA_PATH, checks ISlotResource type-tag compatibility, and uses hashes to skip rebuilds when the resolved configuration is unchanged. Solid slots track separate applied hashes for direct-brush and Flood-image realizations because both are lazy. If the slot type changes, replay destroys the old realization and creates a new one.
API Surface
Enums
enum RuneCommandOp : uint8_t {
CMD_DATA,
CMD_SET_OFFSET, CMD_SET_OPACITY,
CMD_SAVE_TRANSFORM, CMD_RESTORE_TRANSFORM,
CMD_SET_TRANSFORM, CMD_CONCAT_TRANSFORM,
CMD_CONCAT_SCALE, CMD_CONCAT_ROTATION, CMD_SET_TRANSFORM_ORIGIN,
CMD_DRAW_RECT, CMD_DRAW_RRECT, CMD_DRAW_DOUBLE_RRECT, CMD_DRAW_ELLIPSE, CMD_DRAW_ARC, CMD_DRAW_PATH,
CMD_STROKE_LINE, CMD_STROKE_PATH, CMD_STROKE_TRIMMED_PATH,
CMD_DRAW_NINE_SLICE, CMD_DRAW_SHAPED_TEXT, CMD_DRAW_CHILD,
CMD_PUSH_CLIP_RECT, CMD_PUSH_CLIP_RRECT, CMD_PUSH_CLIP_PATH, CMD_POP_CLIP,
CMD_DRAW_BOX_SHADOW,
};
enum RuneDataType : uint8_t {
DATA_BRUSH_SOLID,
DATA_BRUSH_LINEAR_GRADIENT,
DATA_BRUSH_RADIAL_GRADIENT,
DATA_BRUSH_CONIC_GRADIENT,
DATA_BRUSH_IMAGE,
DATA_PATH_PROGRAM, // legacy inline path subtype
};typedef enum {
RUNE_COLOR_SRGB,
RUNE_COLOR_LINEAR_RGB,
RUNE_COLOR_OKLAB,
RUNE_COLOR_OKLCH,
RUNE_COLOR_DISPLAY_P3,
} RuneColorSpace;
typedef enum { RUNE_GRADIENT_ABSOLUTE, RUNE_GRADIENT_RELATIVE } RuneGradientMode;
// ABSOLUTE: coordinates in scene/local space (pixels)
// RELATIVE: coordinates in [0,1] normalized to the draw rect (like WPF RelativeToBoundingBox)
typedef enum { RUNE_GRADIENT_CLAMP, RUNE_GRADIENT_REPEAT, RUNE_GRADIENT_MIRROR } RuneGradientTileMode;
typedef enum { RUNE_CAP_BUTT, RUNE_CAP_ROUND, RUNE_CAP_SQUARE } RuneLineCap;
typedef enum { RUNE_JOIN_MITER, RUNE_JOIN_ROUND, RUNE_JOIN_BEVEL } RuneLineJoin;
typedef enum { RUNE_BLEND_NORMAL, RUNE_BLEND_MULTIPLY, RUNE_BLEND_SCREEN, RUNE_BLEND_OVERLAY, RUNE_BLEND_DST_IN, RUNE_BLEND_SRC_IN, RUNE_BLEND_SRC_ATOP } RuneBlendMode;
typedef enum { RUNE_IMAGE_FILL_MODE_STRETCH, RUNE_IMAGE_FILL_MODE_TILE, RUNE_IMAGE_FILL_MODE_TILE_X, RUNE_IMAGE_FILL_MODE_TILE_Y } ImageFillMode;
typedef enum { RUNE_IMAGE_INTERPOLATION_NEAREST, RUNE_IMAGE_INTERPOLATION_LINEAR, RUNE_IMAGE_INTERPOLATION_HIGH_QUALITY } ImageInterpolation;Representative Record Layouts
struct CmdHeader { uint8_t op; uint8_t sub_type; uint16_t size; };
struct CmdDrawPath { CmdHeader header; Path path; Brush brush; };
struct CmdStrokePath { CmdHeader header; Path path; Value width; float miter_limit; float dash_offset; Brush brush; uint8_t cap, join, dash_count; };
struct CmdStrokeTrimmedPath { CmdHeader header; Path path; Value width, trim_start, trim_end, trim_offset; float miter_limit; Brush brush; uint8_t cap, join; uint16_t reserved; };
struct CmdDrawDoubleRRect { CmdHeader header; Value x, y, w, h, radius, border_width; Brush fill_brush; Brush border_brush; };
struct CmdDrawBoxShadow { CmdHeader header; Value x, y, w, h, radius, offset_x, offset_y, blur_radius, spread, c0, c1, c2, alpha; uint8_t color_space; };
struct CmdDrawBoxShadow4 { CmdHeader header; Value x, y, w, h, top_left, top_right, bottom_right, bottom_left, offset_x, offset_y, blur_radius, spread, c0, c1, c2, alpha; uint8_t color_space; };
struct CmdDrawShapedText { CmdHeader header; ShapedText* shaped; uint32_t glyph_start, glyph_count; Value x, y; Brush brush; };
struct BrushLinearGradient { CmdHeader header; ResourceSlot* slot; uint8_t color_space, gradient_mode, tile_mode, stop_count; Value start_x, start_y, end_x, end_y; /* stops[] */ };
struct BrushRadialGradient { CmdHeader header; ResourceSlot* slot; uint8_t color_space, gradient_mode, tile_mode, stop_count; Value cx, cy, rx, ry, focal_x, focal_y; /* stops[] */ };
struct BrushConicGradient { CmdHeader header; ResourceSlot* slot; uint8_t color_space, gradient_mode, tile_mode, stop_count; Value cx, cy, start_angle, end_angle; /* stops[] */ };
struct DataPath { DataHeader header; ResourceSlot* slot; uint64_t static_hash; float bounds_min_x, bounds_min_y, bounds_max_x, bounds_max_y; float total_length, flattening_tolerance; uint32_t bytecode_size, knot_count; uint8_t fill_rule, flags; uint16_t reserved0; uint32_t segment_count; /* bytecode[]; MeasuredPathSegment[]; PathMetricKnot[] */ };
struct CmdDrawChild { CmdHeader header; uint16_t child_slot; };
struct CmdPushClipPath { CmdHeader header; Path path; };Examples
Shared Brush ResourceSlot Reuse
[CMD_DATA solid brush] slot=theme_slot → brush_id=offset
[DATA_PATH] slot=icon_slot fillRule=nonZero → path_id=offset
[CMD_DRAW_RECT brush=brush_id]
[CMD_DRAW_PATH path=path_id, brush=brush_id]One slot-configured brush can be reused by multiple commands via its Brush offset, and the slot-owned D2D realization can persist across frames when ISlotResource::config_hash says the resolved configuration is unchanged.
Image Fill without CMD_DRAW_IMAGE
[CMD_DATA image brush] slot=image_slot → brush_id=offset
[CMD_DRAW_RECT x y w h brush=brush_id]Image drawing is ordinary brush-plus-geometry recording. The Image is a snapshot resource reference, while the ResourceSlot owns the ID2D1ImageBrush realization and the D2D device cache shares the uploaded bitmap across slots.
Typed Path Recording
[DATA_PATH] slot=blob_slot fillRule=nonZero bytecode=...
[CMD_DRAW_PATH path=path_id, brush=brush_id]The path geometry is snapshot-owned because Path points at DATA_PATH inside the snapshot data_buffer, while the slot-owned D2DPathResource can persist across frames when the resolved geometry hash stays stable.
Double Rounded Rect with Two Brush Slots
[CMD_DATA linear gradient] slot=fill_slot → fill_brush_id
[CMD_DATA solid brush] slot=border_slot → border_brush_id
[CMD_DRAW_DOUBLE_RRECT fill_brush=fill_brush_id, border_brush=border_brush_id]The primitive resolves two slot-owned brush images and keeps fill and border disjoint.
Rounded Box Shadow
[CMD_DRAW_BOX_SHADOW x y w h radius offset blur spread color]
[CMD_DRAW_RRECT x y w h radius brush=surface_brush]Box shadow is a direct solid-color effect command. It expands bounds by offset, spread, and blur, then the caster can be drawn normally on top.
Child Composition as a Draw Command
[CMD_SET_OFFSET]
[CMD_DATA solid brush] slot=panel_slot → brush_id
[DATA_PATH] slot=outline_slot → path_id
[CMD_DRAW_RRECT brush=brush_id]
[CMD_DRAW_CHILD child_slot=2]
[CMD_STROKE_PATH path=path_id, brush=brush_id]The child is drawn in declaration order, contributes bounds like any other draw, and can change independently because commit can publish a new parent snapshot shell with an updated child_slots[2] entry without rewriting the parent buffer.
Performance
- One contiguous command buffer enables linear replay and cheap skipping through
header.size. - 8-byte alignment preserves natural alignment for pointer-bearing records.
- The separate 1 MB command/data caps keep per-snapshot memory bounded while leaving
BrushandPathoffset space roomy. - Resolving
Brushthroughcmd_bufferandPaththrough typeddata_bufferkeeps paint and geometry lookup uniform while removing large path payloads from the command-size limit. ISlotResource::config_hashlets both brush and path slots skip rebuilds when the resolved configuration is unchanged.- Full trimmed stroke reuses the complete slot-cached path; only partial geometry is reconstructed.
Pathremoves tagged-pointer branching and shared-path special cases.CMD_DRAW_CHILDlets dirty children publish new snapshots without forcing parent command-buffer rebuilds.- The unified image-based D2D pipeline keeps solid, gradient, image, text tint, and double-rrect on one replay path.
Alternatives Considered
| Approach | Rejected because |
|---|---|
| Inline fill payloads on every draw command | Duplicates paint data and prevents brush reuse |
| Separate op variants for static path and animated path | One Path + DATA_PATH model already handles both without widening the op set |
Dedicated CMD_DRAW_IMAGE op | Image fill fits the same brush-plus-geometry model as other paint |
Treat child nodes as snapshot-owned children[] metadata | Breaks command uniformity and forces parent rebuilds when only child snapshots change |
| Native D2D brush objects for ordinary replay | Native D2D brushes do not fit the unified ID2D1Image pipeline, cannot feed double-rrect's two-input shader path directly, and would split slot-owned realization logic across backends |
| Per-frame brush cache keyed by command-buffer identity | ResourceSlot already owns the persistent D2D realization, so an ephemeral replay cache duplicates state and loses cross-frame reuse |
| Separate side buffer for animated paths | Adds another lifetime and publication mechanism when snapshot data_buffer already carries immutable DATA_PATH records |
| Dash emulation for path trim | Cannot preserve cap, seam-wrap, offset, or SamplePath synchronization semantics |
Store trim on Path | Makes reusable immutable geometry stateful and prevents independent fill/full/trimmed uses |
| Use D2D path measurement as the canonical metric | Would let visible trim drift from backend-neutral SamplePath |
| Unbounded command buffer | Breaks predictable per-snapshot memory bounds and still cannot lift the 64 KiB per-command cap imposed by CmdHeader::size |
Dependencies
- RUNE-301 defines
Value, used by property ops, geometry fields, gradient stops, and path coordinates. - RUNE-303 defines snapshot publication and traversal, including parent snapshot shells during commit and linear replay on the render thread.
- RUNE-308 defines the ownership rules for images, nine-slices, shaped text, brush slots, path slots, snapshot-local anim arrays, and child-slot references.
Test Strategy
- Verify every record writes a valid
CmdHeaderwithop,sub_type, and 8-byte-alignedsize. - Verify
RuneCommandOpcontains 27 ops, includingCMD_STROKE_TRIMMED_PATH. - Verify
CmdDrawChildrecordsuint16_t child_slotand resolves through the owning snapshot'schild_slots[]array. - Verify
CMD_DRAW_CHILDparticipates in structural bounds through the referenced child snapshot's projected bounds and replays through immutablechild_slots[]. - Verify draw, stroke, and text commands store
Brushhandles (cmd_buffer offsets), andBrushSolid,BrushLinearGradient,BrushRadialGradient,BrushConicGradient, andBrushImagerecords carryResourceSlot* slot. - Verify
CMD_DATAdifferentiates the activeDATA_BRUSH_*subtypes throughsub_type, while paths resolve through typedDATA_PATHrecords indata_buffer. - Verify
CmdDrawPath,CmdStrokePath, andCmdPushClipPathcarryPathhandles that resolve toDATA_PATHrecords in the snapshotdata_buffer. - Verify
push_with_extra()rejects any single aligned command larger than0xFFFF, both buffers reject writes beyond the 1 MB (0x100000) cap, brush-recording APIs returnBrush,rune_node_create_path()returnsPath, and pointer-bearing records remain valid up to the overall buffer caps. - Verify
CMD_DRAW_DOUBLE_RRECTresolvesfill_brushandborder_brushoffsets to slot pointers and preserves non-overlapping fill and border regions. - Verify
CMD_DRAW_BOX_SHADOWexpands bounds by offset, spread, and blur, clips the shadow out of the caster interior, and applies solid color plus replay opacity. - Verify image fill uses
DATA_BRUSH_IMAGEplus ordinary geometry, andResourceSlotrealizations persist across frames whenISlotResourcetype tag is compatible, updating only dynamicValueparameters viaSetValue(). - Verify
DATA_PATHv2 fixed/header sizes, segment/knot topology, exact boundary ties, Close, zero-length paths, hostile counts, and aggregate capacity failure. - Verify trimmed stroke empty/full/partial/wrapped behavior on open and closed paths, animated start/end/offset liveness, curve/follower agreement, and cap geometry.
- Verify Direct2D partial extraction preserves source curves while full trim reuses complete geometry.
- Verify trimmed-stroke bounds and relative brushes use complete style-expanded source bounds and remain stable as trim animates.
- Verify
RuneSvgowns its retained root snapshot,CMD_DRAW_SVGparticipates in value/bounds analysis, and replay maps the viewBox into animated destination values.