Layers & Frames
How OpenGPEX v2 organizes documents: Frames (artboards) containing Layers, backed by a content-addressed asset system and GPU-resident scene graph.
Frames
A Frame is a top-level canvas container with:
- Canvas dimensions (width × height in physical document pixels) and DPI
- Camera state (pan + zoom), kept strictly separate from document space — pan/zoom replays only the cheap ViewPass and never recomposites layers
- Layer stack (ordered array; reordering only touches the array, so undo patches stay minimal)
- Original source asset reference (
frame.assetId?) — the imported source file's content address (SHA-256), used for Revert (re-decode) and lossless fast export; absent on frames with no source file
Frames are assembled by LayerFactory.getNewFrame(patch: Partial<Frame>); imports route through FrameCreateCommands.trunk → createFrameFromFile(ctx, source) — the single ingest path shared by trunk frames and branch frames.
Layer Model
Every layer in the scene (LayerNode) carries:
| Property | Meaning |
|---|---|
transform, width/height |
Geometry (document pixels; dprScale separates logical from physical raster size) |
opacity, blendMode |
16 blend modes; Normal batches in hardware, the other 15 ping-pong in shader |
clip |
Clipping-group flag: content is modulated by the base layer below |
bmask / vmask |
Bitmap and vector mask descriptors (see Masks & Effects) |
adjustments, filters |
Non-destructive color grading and compute filters |
source |
Content-addressed asset reference (kind: 'raster' carries assetId; text/vector sources carry their own geometry) |
visibleShape |
Declared on the persisted Layer for non-rectangular fragments; the scene assembler injects it as an implicit vector mask — no explicit vmask entry needed |
The engine keeps a per-asset epoch keyed by assetId (engine-side, not a LayerNode field): every genuine re-transfer bumps it, which invalidates the composite signature and caches.
Asset System
Assets are the single source of truth for pixel identity, addressed by content hash:
| Asset kind | assetId format | Example |
|---|---|---|
| Imported | sha256(sourceBlob) + '#' + pageIndex |
3a9f…c2#0 |
| Derived (bake / composite) | sha256(displayBlob) (no #) |
e71d…91 |
Each imported asset owns up to three IndexedDB keys (ASSET_VERSION = 5):
${id} StoredAsset light record: geometry + ColorIdentity + 8-bit display blob (KB)
dec:${id} Uint16Array decoded f16/f32 raw pixels (~192MB tier, high-depth only)
raw:${fileHash} Blob original encoded source file (shared across pages of one file)
The ColorIdentity (gamut, trc, bitDepth, dataFormat) is written once at import and treated as immutable truth: gamuts span sRGB / Display-P3 / Adobe RGB / ProPhoto RGB / Rec.2020; bit depth records the source's true physical sampling (1–32 bit); dataFormat: 'rgba16float' | 'rgba32float' marks high-precision runtime containers. Reads of light records never touch dec: buffers — high-depth pixels are pulled in only when the render pipeline needs to build a texture.
Writes follow a "heavy before light" ordering: storeDec completes before the light record claims dataFormat, eliminating half-committed states. A birth-protection set (pendingIds) plus a 5s grace period protect new assets from the garbage collector; sweep(activeIds) collects the memory pool, dec: orphans, and raw: orphans with file-hash-prefix liveness rules.
Non-Destructive Editing
- Original assets are immutable in IndexedDB (and
raw:blobs retained whenever the decode channel is not verbatimimage-bitmap) - All editing state — transforms, masks, adjustments, filters — is lightweight metadata
- Rendering combines asset + metadata on the GPU each composite
- Undo reverts metadata only; Revert re-decodes the retained source blob, which reproduces the original content hash exactly (idempotent by construction)
Fragments and Cut-Link
Peel / Cut-to-Layer produces fragment layers whose visibility is carried by vector masks rather than rasterized alpha:
- A fragment's hard outline is intersected with the layer bounds, then stored as a tight polygon mask with
pad = ceil(feather + AA_MARGIN)outward padding — feather room stays inside the texture - The source layer receives a complementary inverted hole mask (
assocLayerIdlinks back to the fragment) - Non-rectangular fragments expose
visibleShape(path/rect/circle), which the scene assembler injects as an implicit vmask — no explicitvectorMasksentry required - Cut-derived hole masks carry
distBiasPx = 1(seam backing bias, gated to hole masks withassocLayerId,feather = 0, and AA on) so that the fragment and its hole composite to fully opaque when stacked in place, and retain independent smooth edges once separated - Merge Back and union merges operate on polygon geometry (vector precision), not pixels
Layer Lifecycle
- Deleting a layer releases its asset reference; the asset survives a 5-second grace period so undo can restore it without re-upload
- Composite invalidation is signature-based: a pure
computeCompositeSignature(scene)covers all layer properties and asset epochs but excludes the camera, so view changes never invalidate content caches - GPU textures are keyed by
assetId, notlayer.id— fragment layers and repeated references share one resident texture, differentiated byuv_rectand masks
Last updated: 2026-10-12