Skip to content

Preview & Playback

The Preview shows your composition at the playhead: press play or scrub to watch it, and switch to Edit to move and scale layers directly in the picture; masks are drawn from the clip’s Masks tab (Masks). Clips and playhead: Timeline; output to projectors and extra screens: Output → New Output Window.

Current time / duration – In the compact timeline layout, the clock beside the playback buttons shows the current composition position and duration. Click the duration to edit it; Enter applies and Escape cancels. Double-click the current time to switch between time and frames (Enter or Space works when focused). With both In and Out set, it shows position within that range and the range’s duration. Narrow panels stack the clock below the playback buttons.

Behind it are WebGPU rendering, RAM preview caching, the Source Monitor, edit overlays, several preview targets and output windows.


Settings > General > Performance — Timeline RAM cache controls an additional CPU pixel cache in 0.25 GB steps (binary GB/GiB), including 0 to disable it. The default is up to 0.5 GB. The maximum is a quarter of browser-reported RAM, capped at 4 GB; browsers without a memory estimate use a 1 GB maximum. The RAM estimate is rounded/capped by the browser and is not a reading of free memory. This is a per-editor-tab budget, not a reservation or a total application limit.

Non-proxy HTML-video scrub frames use a 960-pixel longest-side cap. A bounded CPU pixel LRU retains frames beyond the separate 192 MB/192-frame GPU cache and uploads them again on demand. The yellow ruler ranges include both tiers. Lowering the budget immediately drops older CPU frames; settings persist across reloads. Paused background loading progressively fills a budget-sized neighborhood, sharing the allowance between active sources; its 72-frame queue is a batch size rather than a total preload limit. Starting playback releases detached preload decoders while retaining cached frames. During HTML-video playback, already decoded frames also populate the cache opportunistically, with one bitmap conversion in flight and no extra decoder. Seeking, busy conversions and a disabled RAM tier skip this playback capture to prioritize playback. Proxy caches, temporal effects and rendered RAM previews keep their own budgets.

Allocations evict older entries first. Catchable CPU capture failures lower the effective RAM budget for the current cache owner; Settings reports the reduction. GPU error scopes prevent failed uploads from becoming visible cache entries and reduce the GPU budget on out-of-memory errors. Device loss clears both tiers.

A neighbour that lives only in the RAM tier starts its GPU upload without ending the nearest-frame search, so a resident frame a few steps further still answers the current render. HTML-video hold paths (emergency-hold, same-clip-hold, playback-stall-hold) likewise prefer the cached frame nearest the target over the last live capture. Without this, fast 4K scrubbing or a playback stall could flash back to a frame up to a second old. These safeguards do not guarantee recovery from a browser/process-level memory termination. Capturing CPU pixels adds readback work, and restoring them adds a GPU upload; the setting does not pre-decode an entire timeline.

The preview system uses a shared render host and a unified render-target store. The main preview canvas, additional preview panels, multi-preview slots, and output windows all register as render targets through the shared render-target path.

Current preview-related overlays and modes include:

  • Main composition preview
  • Source monitor for raw media playback
  • Edit mode overlay for clip/layer transforms
  • Mask and SAM2 overlays
  • Statistics overlay
  • Multi-preview panel
  • Output windows with separate popup canvases

  • Renders the active composition or a pinned composition source.
  • The source menu groups layers under each composition. Hovering or keyboard focus expands a group and keeps it open while moving to its layers; leaving the group collapses it. A disclosure button supports touch input.
  • Uses renderHostPort.registerTargetCanvas() through the preview-target registration helper to attach the canvas to WebGPU.
  • Registers as an active-comp or independent render target in renderTargetStore.
  • Normal composition previews use the same viewer navigation model as the Source Monitor: wheel or trackpad gestures zoom around the pointer, middle-button drag pans, and zooming fully out stops at Fit (100%) and restores the centered position.
  • The shared lower transport provides scrubbing, edit/frame navigation, playback, and toggleable timeline In/Out flags. Clicking an active In or Out flag removes that point again.
  • Previous/Next frame, the scrubber step and the timecode follow the active composition’s frame rate (24, 25, 29.97, 50, 60, …). A step goes from the frame shown at the playhead (frame k covers [k/fps, (k+1)/fps)) to the start of the neighbouring frame, computed from the frame index (stepTimeByFrames in src/utils/timelineFrameQuantization.ts, shared with the timeline’s Left/Right arrows), so repeated steps land exactly on the frame grid and never drift. Stepping while playing pauses first and steps from the paused playhead.
  • A paused seek (frame step, ruler or scrubber release, setPlayhead, pause after playback) ends with the preview showing exactly the playhead’s frame without another user action; see Frame-exact paused preview.
  • A failed WebGPU initialization replaces the canvas with a large, high-contrast error panel. The message distinguishes unavailable WebGPU, missing adapters, request timeouts, and renderer-resource failures without recommending a particular browser and keeps Linux/Vulkan guidance visible.
  • The Linux/Vulkan performance banner remains visible until dismissed. Dismissal is stored locally, so subsequent engine initializations do not show it again.
  • Non-active compositions are rendered as independent targets.
  • renderScheduler drives those targets without depending on the main editor preview loop.
  • A pinned child preview follows the parent playhead when that child is used as a nested composition. Linked audio companions do not create a second visual occurrence.
  • Parent and child previews reuse the already-composited nested GPU frame when their time and resolution match. The parent still applies its own wrapper transform, effects, masks, and crop.
  • Reusing the same child composition more than once in a parent keeps every visual instance on the normal nested render path during playback; paused preview may use the matching composited passthrough frame without turning the child’s audio companion into a visual layer.
  • Pausing preserves the complete final ping-pong accumulator, including the last composited layer.
  • Adding, removing, replacing, or converting a layer in the active nested composition rebinds its runtime sources and invalidates dependent parents automatically. Transform, effect, text, and keyframe edits remain on the lightweight live-evaluation path.
  • A composition nested right after its clip played inside it gets a new video element that can find scrub frames cached under the same source before it has loaded metadata. Such a cached frame is reported with the element’s real size, otherwise the layer’s known intrinsic size, otherwise the cached texture’s own size. It is dropped only when none of these is positive (resolveCachedScrubFrameSize). A layer that still has a texture but no valid source size is skipped for that frame with a Compositor warning (an export frame fails instead), and the compositor’s uniform change check also replaces a previously uploaded NaN value. Before this, a 0×0 size gave a NaN aspect that stayed in the layer’s uniforms, so a smaller nested composition showed its leftmost column stretched across the layer (src/engine/render/compositeSourceSize.ts, src/engine/pipeline/compositor/uniforms.ts; mosaic case T09a).
  • Each target can toggle its own transparency grid state.
  • During edit-mode scrubbing, independent previews keep their last visible frame while the video decoder seeks instead of flashing black.
  • Edit mode is panel-local: the Edit button only affects that preview, and the global Tab shortcut targets the focused preview or, with no focused preview, the first editable preview.
  • Output windows are popup windows managed by OutputWindowManager.
  • They reconnect after refresh when the session still knows the window was open.
  • Popup focus is transferred only when playback is not active, so playback is less likely to stall in the background.

The source monitor shows a raw media file in the preview panel instead of the composition.

  • Regular video sources use the panel-local HTML <video> path. Codec providers handle ProRes, HAP, MXF and HEVC; HEVC verifies native decoding and falls back to WASM automatically, including source audio and container rotation.
  • Audio sources use a panel-local audio playback path with waveform display and scrubbing.
  • Images render through a plain <img> element in the same panel surface.
  • Supports images, but images do not show transport controls.
  • Images and videos support wheel zoom and middle-button panning; image sources also provide a crop tool with aspect-ratio presets.
  • Source-specific playback and placement controls are hosted in the same lower Preview transport shell used by composition previews, while retaining the Source Monitor’s own timeline and marked range.
  • Time display, play/pause, scrubbing, start/end buttons, and frame stepping are provided for video sources.
  • Audio sources provide waveform scrubbing, playback controls, In/Out marking, and placement actions for inserting or dragging the selected range into the timeline.
  • Space toggles source playback only while the pointer hovers the playable source monitor. Outside it, Space controls timeline playback.
  • Starting timeline playback through any transport path closes the source monitor and restores the composition preview.
  • Escape closes the monitor without also triggering a timeline shortcut.
  • The source monitor is a playback aid, not a WebGPU composition target. Codec-backed sources share the runtime provider selection, while regular HTML sources retain their panel-local playback.

The engine render loop is RAF-based and has three important behaviors:

  • It idles after about 1 second of inactivity, and at once on a static preview without video. It never idles during playback: while the playhead crosses a gap (no visual clip under it) the loop still runs the render callback (cadence in the next point). The callback draws the gap black, sees the playhead enter the next clip on the first frame inside it, and keeps the video sync running, which warms, positions and pre-rolls the clip ahead and pauses the one that ended. Before this the loop went idle in every gap (since 56cb1a8a1, “idle static previews”); only an unrelated wake, often the ended clip’s last presented frames, noticed the next clip, so after a clip that ran to the end of its source the next one stayed black for a second or more and entered cold (owner report 2026-10-29, journey tests/playwright/playback/gap-speed-playback.spec.ts, unit tests in tests/unit/renderLoop.test.ts and tests/unit/useEngineExactFrameContext.test.tsx).
  • Through a gap the callback runs once per display frame only within 2 s (wall clock, so faster playback starts earlier) of the next clip in the playback direction, which covers the video sync’s warmup and pre-roll windows. Further ahead, during audio-only playback or in a long gap at the end, it runs at 8 Hz. A requested frame (an edit) still runs at display rate. The playback loop wakes the render host on the frame the playhead enters a clip, so the clip shows on time after a loop wrap, a seek or reverse playback too (src/services/render/playbackGapCadence.ts, unit tests tests/unit/renderLoop.test.ts, tests/unit/timelineVisualDemandLead.test.ts, tests/unit/usePlaybackLoopGapWake.test.ts).
  • Pause and stop also end pre-rolls: the muted early start of an upcoming clip’s video. This applies on Android too, and even when no clip is visible at the playhead. Paused in a gap, a pre-roll used to keep running, and its stale entry blocked a fresh pre-roll on the next play (tests/unit/videoSyncPlaybackStopPause.test.ts).
  • While paused on a video clip, the held frame is re-presented every 250 ms. A requested render (a paused seek, the decoded frame it asked for, a capture) runs at display cadence instead; a request held back by a frame limiter stays pending for the next allowed tick rather than being dropped.
  • Idle detection is suppressed until the first play event so browser video surfaces can warm up after reload.
  • A watchdog checks for stalls and restarts the loop if it dies while the engine is expected to render. Playback counts as render demand in a gap too, so a loop stalled there is woken instead of left asleep.
  • The composition playback clock is hosted above individual dock layouts. Switching between Video Edit, Color, and other editor layouts therefore preserves active playback and keeps every mounted transport and compact playhead synchronized.

Paused preview frames come from the HTML video element of the clip (or its caches) and are judged by frame index, never by a time distance that also admits a neighbour:

  • Seeks go into the target frame. Chromium truncates a seek position to whole microseconds but rounds demuxed frame timestamps (MP4: microseconds, WebM: milliseconds), so a seek to exactly k/fps presented frame k-1 for every third frame at 30 fps. Every HTML seek therefore goes to htmlVideoFrameSeekTime: up to 1 ms into the target’s source frame, never past the middle of what remains of it (src/services/layerBuilder/videoSyncFrameSelection.ts, applied by VideoSyncManager.safeSeekTime). The position also stays at least 1 ms before the next frame: the demuxer rounds a seek to the container time base (±33 µs for MP4 at 1/15360 s) and starts at a keyframe it rounds onto, so a reversed clip’s target, which sits 10 µs before a frame boundary, used to present the following keyframe and leave the paused preview empty on the last frame of every GOP. The background scrub-cache fill seeks each 1/30 s slot the same way (SLOT_SEEK_LEAD_SECONDS), so a slot no longer holds the frame before its own.
  • The previous frame is never the target frame. A presented frame (its requestVideoFrameCallback timestamp) matches the target by its source frame bin; the “earlier PTS within one frame” allowance for averaged VFR rates keeps a 1 ms rounding margin, so floating-point noise (0.1333 − 0.1 < 1/30) no longer passes frame 3 off as frame 4. Only an element at the frame-exact seek position for the target counts as seeked onto it.
  • Jittered phone timestamps keep their frame. Phone recorders stamp capture times: Android footage at 30 fps carries frames up to 4 ms off k/30. A presented timestamp up to a quarter frame before k/fps is labelled k/fps (nominalPresentedFrameTime in src/services/videoSourceFrameRate.ts, applied where requestVideoFrameCallback times enter the sync: seek callbacks, the Android frame capture and the decoder warmup), so the scrub-cache slot, the held-frame guard and the presented frame all name frame k. A timestamp within a quarter frame after the previous grid point is the previous frame, however late it was stamped, and never settles the next target. Before, on the Pixel 10 Pro, frame 246 (stamped 2.6 ms early) filled slot 245 and timeline frame 245 kept showing it, and frame 152 (2.8 ms late) settled a step to frame 153, so the paused preview stayed one keyboard step behind.
  • A late warmup report waits for the next frame. The decoder warmup plays the element briefly and labels its capture with the frame the requestVideoFrameCallback reports. A callback that runs while currentTime is already more than half a frame past that frame’s timestamp is late: the element may already show the next frame (presentedFrameCallbackIsLate in src/services/videoSourceFrameRate.ts). The warmup then waits for the next callback (up to three, and logs a warning if all three are late; requestOnTimePresentedFrame in src/services/layerBuilder/videoSyncPresentedFrame.ts) instead of labelling the picture as the earlier frame and pausing on it. Before, a callback that reported frame 0 at currentTime 0.0269 s cached frame 1 as frame 0, and the paused preview showed frame 1 at timeline frame 0 until the next play (phone journey m1-edit 5, about one run in eight).
  • Held and cached frames show the target frame only. The paused guard (htmlVideoPausedFrameGuard.ts) compares frame indices on the source grid, never finer than the scrub cache’s 1/30 s slots; it used to accept anything from one 1/30 s slot back to 50 ms ahead.
  • Pause keeps the playhead. After playback the playhead stays where the pause put it, and the frozen video element is seeked frame-exactly to the playhead’s frame (vf_stop_resync): a paused element’s clock does not prove its picture, the frame shown after playback can be one off the frame holding currentTime in either direction. Only an element already at the frame-exact seek position is kept as is. The playhead is no longer moved onto the video’s frame a render later.
  • Failures are visible. A frame-exact seek whose presented timestamp still is not the target’s frame records vf_seek_wrong_frame (target, seek position, presented time) in the VF pipeline.

tests/playwright/foundation/preview-truth.spec.ts checks this against the preview’s real pixels (frame-code clip, Next frame ×37, play/pause); tests/unit/pausedFrameExactSeek.test.ts pins the seek positions, frame identity, guard and stepping at 24 to 60 fps, and the jittered timestamps of the generated Android phone recording.

  • Imported video clips expose a Free Run checkbox in the Transform tab. While the clip is visible, its existing HTML video source loops on its own clock instead of seeking with the timeline; it keeps updating when timeline playback is paused. Offline export stays timeline-timed and deterministic.
  • Playback is rate-limited to about 60 fps.
  • Dynamic preview target FPS is derived from the active composition’s frameRate; renderer RAF may still report a 60 fps loop while the visual target is 24/25/30/etc.
  • Scrubbing is rate-limited to about 60 fps unless a fresh frame arrives via requestVideoFrameCallback.
  • The loop does not render while export is active.
  • During normal playback outside strict worker GPU mode, video clips stay on the live HTMLVideo/WebGPU import path even when JPEG proxy frames are available. Proxy image frames are used for paused preview, scrub fallback, and timeline thumbnails, but not as the primary playback surface because dense cut sequences need the browser video decoder and cut warmup path to remain active.
  • Split, recreate, undo, and similar timeline edits bind lazy media elements to the current clip owner instead of inheriting a predecessor clip’s DOM handle. This keeps every active video layer available after continued editing and applies equally inside nested compositions.
  • During forward playback, a healthy live HTML-video frame may replace a stale pre-edit presentation-owner marker when its source time still matches the requested timeline time. Paused preview, scrubbing, and seeking retain strict owner checks, so cached frames cannot leak between clips while live multi-layer composition remains continuous.
  • Worker-first render hosts, including a strict worker-gpu-only diagnostic mode, are present but feature-gated. The normal default is the main fallback render host; full WebCodecs playback is also disabled by default.
  • When enabled for worker-GPU diagnostics, the worker path can present HTML-video frames or Worker WebCodecs frames and labels its presentation paths in playback statistics. Capability and browser support determine the active path.
  • Firefox uses copied HTML-video textures because imported frames can go black there.
  • Android Chromium copies HTML-video preview frames into persistent GPU textures during playback, pause, and seeking. This avoids the session-first and intermittent black frames caused by unstable external video textures on mobile GPU drivers.
  • A collapsed timeline In/Out selection is treated as no active range, so Play uses the full composition instead of stopping immediately.
  • Full-frame composite, copy, and output passes use one oversized triangle instead of a two-triangle quad. This avoids diagonal half-frame loss on affected Android WebGPU drivers.
  • The render path prefers live video import when the frame is ready, but it can fall back to cached frames or the last known frame to avoid black flashes.

RAM preview is implemented by RamPreviewEngine and the timeline RAM preview slice.

  • Frames are generated outward from the playhead.
  • Only frames where there is visible content are generated.
  • Each frame is verified against expected video positions before it is committed.
  • Caching uses the same composition render path as normal preview, then stores the composited frame for playback.
  • Scrubbing cache: 300 frames
  • Composite RAM preview cache: 900 frames and 512 MB memory budget
  • GPU RAM preview cache: 60 frames
  • RAM preview generation runs at 30 fps
  • Frame verification tolerance is 0.04 seconds
  • The green timeline range indicator comes from cached RAM preview frames.
  • The proxy cache indicator is separate and comes from proxyFrameCache, not from RAM preview.
  • RAM preview generation is best-effort; it skips frames when the clip positions drift during generation.

  • Multi-preview renders four slots in a shared panel.
  • Slots can follow the active composition or pin a specific composition.
  • The auto-distribute mode maps the first four layers of a chosen composition to the four slots.
  • Multicam camera tiles composite at their displayed pixel size, capped at 1280 × 720, while source decoding and the main preview keep their original quality. Plain video tiles reuse their last presentation until a new decoded frame or a visual setting changes; effects retain continuous rendering. Off-air codec frames do not force duplicate main-preview frames during steady playback. Resizing a tile refreshes its render surface automatically.
  • Forward codec playback presents completed, newer pictures even when the playback clock has already requested another frame, preventing decode starvation under multicamera load. Exact seeks and backwards requests remain strict. The explicit Full preview setting retains full composition resolution during playback and scrubbing.
  • Newly decoded program frames can present before the next playback sampling deadline, within the 60 Hz rendering ceiling. Camera slots observe the shared engine’s readiness without registering additional engine lifecycles.
  • During a playhead drag, off-air camera views hold their pictures and catch up on release. The program camera borrows the main preview’s current source instead of decoding the same picture twice. H.264 Long-GOP MXF reuses its warm track decoder for playback, scrubbing, and release; native WebCodecs sources retain their separate scrub sessions. The MXF H.264 WebCodecs adapter prefers software decoding to reduce hardware-decoder contention between concurrent camera seeks; this preserves source resolution and leaves GPU composition unchanged. MXF seeks can feed bounded reordering input as the decoder consumes packets, avoiding a fixed 300 ms wait for pictures that need further input.
  • When a browser frame is delayed during forward playback, the main preview can retain its last complete video composite for the same clip and source. Composition changes, cuts, explicit seeks, loop jumps, and timeline edits invalidate that longer hold; real gaps still clear.
  • Inactive tracking, roto, face, live-input, and storyboard overlays do not subscribe to playback updates. Opening the relevant editing feature restores its live overlay updates.
  • The Multicam toggle turns on multicam cut mode for the active composition (see Timeline Multicam). The slots show camera angles 1-4 with their number and track name, using original source decoding and panel-sized presentation, also where a camera is currently cut out of the program. The camera on air at the playhead is framed in red. MXF and other codec-provider cameras share the decode session of their program track, so a cut to that camera finds its decoder already at the playhead; other video plays through a muted element kept in sync with the timeline. A slot keeps its last picture while its decoder catches up instead of flashing black. When a camera’s media switches to another file (linked proxy, relink or reconnect, e.g. an H.264 copy of an HEVC original), its slot rebuilds its decoder for that file. By touch, tapping a camera slot cuts to that camera at the playhead (a finger that moves more than 10 px is not a tap); mouse and pen keep the keyboard 1–n workflow.
  • Isolated layer slots render the layer as its source by normalizing non-normal blend modes for that slot only. The original composition layer keeps its stored blend mode.
  • Every camera-edit Preview owns its viewport camera and panel-sized render surface. Any number of visible panels can therefore show different live perspectives of the same composition while sharing timeline, selection, object transforms, and gizmo state.
  • A panel in Edit mode is rendered as an independent target; non-edit panels continue to show the timeline camera instead of inheriting or freezing the edited panel’s view.
  • Output targets are registered in renderTargetStore.
  • Output canvases, preview canvases, and windows all use the same source routing model.
  • ShowTransparencyGrid is per-target, not global.

  • Every dock preview has pixel rulers along its top and left edges. Toggle them with the ruler button in the viewer tools or Shift+R. The choice is stored per preview panel (showRulers); the Image workspace opens with rulers on.
  • Rulers count composition pixels from the canvas origin. They follow zoom and pan, tint the canvas span, and mark the pointer position. The pointer marker is measured against the rulers’ own box, because their parent element is display: contents. They are SVG (PreviewRulers.tsx, ticks from previewRulerTicks.ts).
  • Rulers are hidden in the Source Monitor and in panel-sized 3D camera edit views, where screen space does not map to composition pixels.
  • When the displayed composition is an image document (still), the scrubber and playback row are replaced by a zoom bar: composition size, zoom out, Fit, 1:1 (actual pixels), zoom in, and the zoom level relative to the composition’s pixels (PreviewCanvasZoomBar.tsx, usePreviewCanvasZoom.ts). With rulers on, Fit places the canvas in the area right of and below the rulers.

Guides are cyan lines across the preview, like Photoshop guides. They belong to the composition (Composition.guides, composition pixels, src/types/compositionGuides.ts), so they are saved with the project, undoable and shared by every preview of that composition.

  • Create: drag down out of the top ruler for a horizontal guide, or right out of the left ruler for a vertical guide. A readout shows the position.
  • Move: drag a guide. Positions are whole composition pixels; holding Shift snaps to the canvas edges and center.
  • Remove: drag a guide back onto its ruler or out of the preview (the readout says Remove guide), or focus it and press Delete or Backspace. The × in the ruler corner clears all guides of the composition.
  • Keyboard: guides are focusable sliders. The arrow keys along the guide’s axis nudge it by 1 px, with Shift by 10 px. Escape cancels a drag.
  • Guides show and hide together with the rulers (Shift+R). While visible, moving a layer in Edit mode always snaps its edges and center to them; Shift adds the canvas and layer targets.
  • A drag writes the composition once on release, so each guide drag is one undo step (usePreviewGuideDrag.ts, src/services/compositionGuides.ts). Invalid stored guides are dropped with a warning on load.

Edit mode is a canvas overlay for layer transforms.

  • Selects a layer from the preview and syncs the corresponding clip in the timeline.
  • Shows bounding boxes and drag handles for the selected layer.
  • Supports zoom, pan, and transform gestures.
  • Touch uses the same edit paths as mouse input: one finger can drag layer and mask controls, including the scene gizmo’s axis arrows, rotation rings, and center grip. A free one-finger drag in the perspective 3D Edit view orbits the editor camera without stealing touches that begin on those gizmos.
  • Two-finger pinch cancels an active one-finger orbit and changes edit-view zoom or camera distance continuously from the live finger spacing, without fixed zoom steps. In both 2D and 3D Preview Edit modes its touch zoom delta is amplified to four times the normal preview pinch rate.
  • 2D transform, text, and mask editing preserve the composition resolution and aspect ratio; only camera/3D edit views use the full panel viewport for an independent perspective.
  • Moving or scaling a layer publishes a transient render transform once per animation frame, so the canvas and edit handles stay live without rewriting the durable clip, mask state, or RAM-preview caches. The final transform is committed as one history batch when the pointer is released.
  • A dock preview with initialEditMode starts in Edit mode. With initialEditCameraView it opens a 3D camera edit view (3D workspace); without one it opens 2D canvas edit mode (Image workspace, see Image Mode).
  • Multiple preview panels can mix edit and non-edit views at the same time. Camera and object changes update every visible view immediately, while each panel keeps its own perspective.
  • 3D object handles remain visible across preview modes; selecting one activates the native 3D scene gizmo for that clip.
  • In camera Edit mode, the independent editor camera always orbits the scene origin (0, 0, 0); timeline and viewport selection do not change its pivot.
  • Vertical camera orbit drags carry the front of the scene with the pointer, matching horizontal orbit movement.
  • Camera Edit mode uses an independent editor camera with a 35 mm default, initially offset so the timeline camera is visible as an object. Navigating the editor view never changes the timeline camera; dragging the timeline camera’s object gizmo updates its output live in every normal Preview.
  • The projected timeline-camera frame in camera Edit mode is a world-space object drawn from the camera’s FOV/mm and Resolution X/Y. It becomes smaller with distance, wide lenses draw a larger front frame, tele lenses draw a smaller one, and the frame aspect follows the camera resolution.
  • Edit views can render a projected world grid that follows camera-view animation instead of snapping as a screen overlay. The grid plane matches the edit view: Front uses XY at z=0, Side uses YZ at x=0, and Top/free camera uses XZ at y=0.
  • Holding Shift while dragging a layer in Edit mode enables snapping for that drag. The layer can snap to composition edges/center and to the bounds of other visible layers. Visible ruler guides snap without Shift; otherwise layer movement stays free.
  • The Tools panel’s canvas tools change what a left click on the canvas does: Hand pans, Zoom zooms around the pointer (Alt zooms out), Text places a text layer. They take the click before layer selection, show their own cursor, and need Edit mode (usePreviewCanvasToolPointer.ts, see UI Panels).
  • Pan and zoom move the canvas without a CSS transition, and the edit overlay is drawn in a layout effect. The canvas, its handles, rulers and guides therefore move in the same frame instead of the canvas easing behind them.
  • Hit-testing and handles use the render layers that the Timeline panel syncs. In layouts without a Timeline panel (the Image workspace), EditorPlaybackRuntimeHost runs the same layer sync headlessly (HeadlessLayerSync.tsx, timelineLayerSyncOwners.ts).
  • When Scene Nav is active on a camera clip, the preview wheel moves the real camera position along the current view direction, updating Position X/Y/Z as needed. It does not change FOV or millimeters.
  • The Transform tab shows the same lens value as both FOV degrees and full-frame-equivalent mm; those lens fields are independent from camera position.
  • Resolution X/Y controls the camera gate aspect used by the edit-view camera frame.
  • In FPS navigation, wheel still changes movement speed while the camera is actively moving or looking; otherwise it moves the camera position forward/backward.
  • Edit mode is only available for editable sources. It is disabled for non-editable preview sources.

The UI exposes Full / Half / Quarter preview quality choices.

  • The setting is persisted in settingsStore.
  • The selector is visible in preview UI.
  • useEngineResolutionSync() reads the value, scales the active composition resolution by 1, 0.5, or 0.25, and calls renderHostPort.setResolution(...).
  • Lower preview quality reduces the engine-backed preview resolution for the main preview, multi-preview targets, and output targets that share the engine path.
  • It does not change export resolution or the HTML-only source monitor.

  • Firefox does not use zero-copy HTML video import for preview frames.
  • RAM preview and proxy cache generation are separate systems.
  • Browser media readiness still controls how quickly source monitor and preview frames appear after reload.
  • Source monitor playback is intentionally HTML-only and does not mirror the engine’s preview backend selection.

Key implementation files:

  • src/components/preview/Preview.tsx
  • src/components/preview/SourceMonitor.tsx
  • src/components/preview/StatsOverlay.tsx
  • src/components/preview/MultiPreviewPanel.tsx
  • src/components/preview/PreviewBottomControls.tsx
  • src/components/preview/usePreviewRenderTargetRegistration.ts
  • src/engine/WebGPUEngine.ts
  • src/engine/render/RenderDispatcher.ts
  • src/engine/render/htmlVideoPreviewFallback.ts
  • src/engine/render/layerCollector/htmlVideoReadyCollector.ts
  • src/engine/managers/OutputWindowManager.ts
  • src/hooks/engine/useEngineResolutionSync.ts
  • src/services/ramPreviewEngine.ts
  • src/services/proxyFrameCache.ts
  • src/services/render/previewTargetRegistration.ts
  • src/services/timeline/lazyMediaElements.ts
  • src/stores/timeline/ramPreviewSlice.ts
  • src/stores/timeline/proxyCacheSlice.ts

Software presentation of GPU preview on Linux

Section titled “Software presentation of GPU preview on Linux”

Linux desktop uses a main-thread 2D canvas for preview and registered output surfaces. The complete image is still rendered on WebGPU, including strands, path tracing, transparency grids, and output slices. A single asynchronous GPU readback presents the newest completed image as software pixels, with bounded canvas dimensions. This avoids transparent/blank GPU canvases that expose stale window contents on Mesa. Android and other desktop platforms retain native GPU presentation. It adds a final readback cost on Linux; rendering and export quality are unchanged. WebGPU must still be available to perform the rendering.