Skip to content

GPU Engine

Back to Index

WebGPU rendering, texture management, preview output, export capture, and GPU-backed analysis.

Linux/Mesa: GPU-accelerated canvas, worker OffscreenCanvas, and WebGPU paths fail silently on open-source Mesa drivers. Before changing any canvas/GPU code, read Linux / Mesa GPU.


WebGPUEngine is a thin facade over the rendering subsystems. It owns WebGPU context setup, texture and cache managers, the render loop, the render dispatcher, output windows, and export canvas state.

The engine supports:

  • Main preview rendering
  • Independent preview targets
  • Popup output windows
  • Sliced output / corner-pin rendering
  • RAM preview and scrubbing caches
  • Zero-copy export frames when the export canvas path is available
  • CPU readback as a fallback path
  • Waveform, histogram, and vectorscope GPU panels
  • Native 3D scene and Gaussian-splat composition when their enabled runtime flags apply

  • WebGPUContext handles adapter/device setup and device-loss recovery.
  • RenderLoop runs the RAF loop, idle detection, scrub limiting, and watchdog recovery.
  • RenderDispatcher handles per-frame texture import, compositing, output, and preview fallbacks.
  • RenderTargetManager owns ping/pong compositing buffers plus independent preview buffers.
  • TextureManager and MaskTextureManager manage image/video/mask textures.
  • CacheManager owns scrubbing cache, composite RAM preview cache, and GPU frame cache.
  • ExportCanvasManager owns the export OffscreenCanvas and stacked-alpha mode.
  • The engine keeps a unified Map of target canvases.
  • Preview canvases and output windows are reconfigured after device restore.
  • HMR reuses the engine singleton when possible.

Risky GPU startup records a release-scoped boot attempt in bootHealth.ts. Three interrupted attempts inside 24 hours activate a GPU hazard for that release and route canvas policy through the existing software fallback. An attempt becomes healthy only after the active render mode proves a readable frame; worker submission alone is not treated as pixel proof.

Device loss records the same hazard signal. The visible warning offers a manual Retry GPU action that clears the safety state and reloads the app. Clean pagehide completion and a three-second settled startup prevent normal reloads from being counted as crashes. Runtime owners and the engine singleton remain HMR-safe.


  • HTMLVideoElement is imported as an external texture when the browser supports it.
  • VideoFrame can also be imported as an external texture.
  • Firefox preview uses copied textures instead of external video import because imported frames can intermittently go black.
  • Android Chromium copies HTML-video preview frames into persistent GPU textures during playback, pause, and seeking because its external video textures can otherwise present intermittent black frames.
  • Images and canvases are copied into rgba8unorm GPU textures.
  • Cached image views are reused when possible.
  • motion-shape clips render through src/engine/motion/MotionRenderer.ts.
  • Rectangle, ellipse, polygon, and star primitives are drawn with analytic WGSL SDFs into transparent rgba8unorm textures.
  • A bounded uniform layout composites up to 8 ordered color-fill, stroke, linear-gradient, radial-gradient, or texture-fill appearances, with up to 8 stable stops per gradient and six shader blend modes.
  • Replicated motion shapes use a per-shape instance buffer and instanced draws in the same shader path. The shader ceiling is 100,000 instances; effective counts are also constrained by the device, render target, and configured user limit.
  • The resulting texture view is composited through the normal CompositorPipeline, so masks, effects, blend mode, nested comps, preview targets, and export share the same downstream path.
  • Mask textures are uploaded per layer.
  • A 1x1 white fallback texture is used when no mask is present.
  • The zero-copy path only applies when the browser and source type support it. Preview and export still have explicit fallback paths.

  • Idle mode starts after about 1 second of no activity.
  • Idle detection is initially suppressed so browser video surfaces can warm up; it is lifted by the first play event or after about 3 seconds.
  • Playback is limited to about 60 fps.
  • Scrubbing is limited to about 60 fps unless a fresh video frame arrives, which bypasses the limiter.
  • A watchdog checks for stalls every 2 seconds.
  • If the render loop stops producing frames for about 3 seconds while it should be active, it wakes the loop or restarts it.
  • The render loop skips preview rendering while export is active.
  • Export and RAM preview both set flags through ExportCanvasManager.
  • These modes are separate from the normal preview path, but they share the same engine.

RenderDispatcher uses a best-effort chain for video preview:

  1. Cached scrubbing frame
  2. Copied fallback frame
  3. Live external texture import
  4. Last known frame or same-clip hold frame

This is why preview can stay stable during seeks while a fresh frame is pending.

  • Firefox uses getCopiedHtmlVideoPreviewFrame() for HTML video preview stability.
  • That path copies the current video frame into a persistent GPU texture and falls back to the previous stable frame when needed.

  • The output pipeline uses separate uniform buffers for transparency grid off/on and stacked-alpha export.
  • Full-frame output and compositor passes draw one oversized triangle, avoiding a diagonal seam or missing half-frame when a mobile WebGPU driver drops one primitive from a two-triangle quad.
  • renderToCanvas() catches canvas-context loss and simply skips that target for the frame.
  • Bind-group caches are separate per output mode.
  • Stacked alpha doubles the encoded export height.
  • RGB is rendered in the top half and alpha grayscale in the bottom half.
  • ExportCanvasManager creates the doubled-height OffscreenCanvas when stacked alpha is enabled.
  • readPixels() is the CPU fallback path.
  • It is used by export fallback paths and preview capture utilities.
  • It is slower than the zero-copy export path and should be treated as a fallback, not the normal route.

ExportCanvasManager is responsible for export-state flags and the export canvas lifecycle.

  • initExportCanvas() creates an OffscreenCanvas with WebGPU context.
  • createVideoFrameFromExport() waits for device.queue.onSubmittedWorkDone() before constructing a VideoFrame.
  • If the zero-copy path cannot be created, export falls back to readPixels().
  • cleanupExportCanvas() clears the canvas and stacked-alpha state after export.
  • Zero-copy export only works when OffscreenCanvas + WebGPU + VideoFrame creation are available and the export canvas can be configured.

Runtime flags are exposed on window.__ENGINE_FLAGS__.

  • useFullWebCodecsPlayback and disableHtmlPreviewFallback are synced with the persisted settings toggle.
  • useLiveSlotTrigger swaps slot-grid clicks from editor-open behavior to direct live triggering.
  • useWarmSlotDecks prepares reusable slot-owned live decks for faster layer adoption.
  • use3DLayers and useGaussianSplat are enabled in this branch.
  • workerFirstRenderHost exists for worker-host presentation, but defaults to false; the main-thread render host remains the default.
  • timelineCanvasWorker is enabled for eligible timeline rows; it is separate from the main preview render host.
  • Motion shape/appearance rendering and the Grid Replicator MVP are always part of the native layer path.

  • ScrubbingCache keeps up to 192 video scrub frames at 30 fps quantization, with a 192 MiB texture budget and a 960-pixel maximum source dimension.
  • Composite RAM preview caches are capped at 900 frames and 512 MB.
  • GPU RAM preview cache is capped at 60 frames.
  • These are hard cache limits from code.
  • useEngine() scales the active-composition base resolution by the persisted preview-quality multiplier before calling engine.setResolution(...).
  • Full, Half, and Quarter therefore change the GPU render size for engine-backed preview targets.
  • Export output resolution still comes from the composition/export settings, not from preview quality.

ProblemWhat To Check
Black preview after reloadVideo GPU surfaces may not be warm yet. The engine suppresses idle until first play, but browser readiness still matters.
Firefox preview instabilityHTML video fallback should be using copied textures, not external import.
Export canvas missingVerify WebGPU context creation on OffscreenCanvas and device validity.
Device lostThe engine attempts recovery and reconfigures canvases, but a manual reload may still be needed.

Key implementation files:

  • src/engine/WebGPUEngine.ts
  • src/engine/render/RenderDispatcher.ts
  • src/engine/render/RenderLoop.ts
  • src/engine/render/htmlVideoPreviewFallback.ts
  • src/engine/motion/MotionRenderer.ts
  • src/engine/motion/shaders/motionShapes.wgsl
  • src/engine/analysis/ScopeRenderer.ts
  • src/engine/native3d/NativeSceneRenderer.ts
  • src/engine/pipeline/OutputPipeline.ts
  • src/engine/pipeline/SlicePipeline.ts
  • src/engine/managers/ExportCanvasManager.ts
  • src/engine/texture/ScrubbingCache.ts
  • src/engine/core/RenderTargetManager.ts
  • src/engine/featureFlags.ts
  • src/engine/video/VideoFrameManager.ts
  • src/services/render/renderHostPort.ts