Launch OpenGPEX

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

  1. Original assets are immutable in IndexedDB (and raw: blobs retained whenever the decode channel is not verbatim image-bitmap)
  2. All editing state — transforms, masks, adjustments, filters — is lightweight metadata
  3. Rendering combines asset + metadata on the GPU each composite
  4. 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 (assocLayerId links back to the fragment)
  • Non-rectangular fragments expose visibleShape (path/rect/circle), which the scene assembler injects as an implicit vmask — no explicit vectorMasks entry required
  • Cut-derived hole masks carry distBiasPx = 1 (seam backing bias, gated to hole masks with assocLayerId, 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, not layer.id — fragment layers and repeated references share one resident texture, differentiated by uv_rect and masks

Last updated: 2026-10-12