The params view reapplies the engine snapshot to every control on each
render (every engine tick), which wiped the in-progress text a frame
after each keystroke - the text effect's field was effectively
untypeable. Skip the re-sync while the field is focused (the same
guard the curve editor has for drags); it re-syncs on blur and the
row's explicit commit writes the edit back. MockEngine gains a
sentinel text parameter for the regression test.
Edits invalidated the display caches, but the debounced snapshot
upload (150 ms) landed later - meanwhile the viewer had already
rendered and cached a proxy through the stale snapshot, and nothing
invalidated it again, so a paused monitor kept the pre-edit picture
until the playhead moved. Re-invalidate when the fresh snapshot is
actually installed so the next paint re-renders from it.
- Runtime window icon: X11 takes the embedded 512px PNG up front
(_NET_WM_ICON), Wayland resolves it from oak.desktop via the app id,
Windows reads it from the exe's icon resource.
- assets/appicon: 512px PNG + 7-size ICO rasterized from Oak_Icon.svg.
- Windows: oak.rc (ICON resource ID 1, also the Explorer icon) compiled
by build.rs through embed-resource.
- Linux packaging (deb/rpm/PKGBUILD/oak.spec): install Oak_Icon.svg
into the hicolor scalable theme alongside the existing 512px PNG.
- macOS: no change needed - cargo packager already builds the .icns
from the CD-generated icons/icon.png (same source art).
Two export killers fixed and the feature surfaced properly:
- TicketArena::allocate() reaps finished fire-and-forget slots on every
new submit, but RenderTask classified finished tickets by asking the
arena for their kind/time afterwards - any ticket that completed
before the next submit (all of them on the inline backend) failed
with 'Render ticket reported an unexpected timestamp' and the export
died after writing a header-only file. RenderTask now records each
ticket's delivery key at submit time instead.
- File > Export becomes 导出序列 (all 8 locales): the dialog offers a
sequence picker (any sequence in the project, the current one
preselected) instead of silently exporting the current one, and the
project bin's sequence context menu gains 导出序列 opening the same
dialog. Engines expose sequence_entries / current_sequence_id /
start_export_of.
- The export-project dialog drops its dead hand-typed path field (OK
already asked the platform save dialog).
- New end-to-end test: a one-second sequence exports to a real
H.264/AAC MP4 that probes and decodes back to the clip's content.
The sampled pixel lands (disarming the eyedropper) between the button
going down on the program viewer and coming back up, so the trailing
mouse-up read as an outside click: the popup closed and close_menu
discarded the just-picked draft - pick-from-viewer could never be
confirmed. Swallow the pick's own mouse-up once; a later genuine
outside click still dismisses.
- process_shader_job: bind all texture params by name, recurse into nested
shader payloads (depth cap 8), fall back to frame size without inputs
- run_effect: take iterative_input so dropshadow previous_iteration_in works
- merge: actually composite inputs; keyer mask, opacity modulation, math
texture ops and mrg generator layers now bind their textures
- transform distort: real fragment-side inverse-matrix sampling
- time offset / time remap: wire NodeBehavior time adjustment hooks
- plugin: fix first-node identity colliding with unbound sentinel
oak-common is gone; its modules (configstore, xmlutils, ocioutils,
oiioutils, colormath, colortransform, videoparams, ffmpegutils, ...)
now live in oak-core alongside the value types. The render value/GPU
types moved too: backend (wgpu context + DisplayRenderer), color
(ColorProcessor over ocio-rs), texture, frame, and the commonutil
config helpers.
Fix-ups to make the merged tree build and pass tests:
- oak-core Cargo.toml: wgpu back to 25 (the moved backend code is
written against that API generation); add the toml/quick-xml/image
deps oak-common carried.
- lib.rs: drop the duplicate 'pub mod error;'.
- error.rs: unified OAKCORE_* codes; restore Error::new() and
From<OcioError> from oak-common's error type.
- backend.rs/color.rs: oak_core::/oak_render:: self-references
rewritten to crate::; the shaderfx-dependent GPU effect test moved
to oak-render's shaderfx tests (shaderfx depends on oak-node and
cannot live in oak-core).
- oak-render's error module re-exports oak_core::error::{Error,
Result}; the OAKRENDER_* codes stay as the public-code contract.
- oak-node jobs.rs: ColorProcessor imported from oak_core::color.
- Integration tests repointed at oak_core::{texture, frame, backend,
color, colormath}.
- the display-ICC regression test treats an empty OAK_DISPLAY_ICC as
unset, matching displayicc::env_override_icc.
- 导出工程文件: the in-app dialog only picks the format (OTIO / OVE /
FCPXML); OK asks the system save dialog with the suggested file name
carrying the format's extension, then exports
- 打开项目: the system open dialog restricts the choosable files to
.ove/.ovexml/.otio/.fcpxml (PathPromptOptions.allowed_extensions
plumbed through gpui; Linux portal glob filter)
The three values are SpinBoxes (typed input + stepping): width & height
(0 = use the sequence size), frame rate in fps (0 = sequence rate),
bit rate in bit/s (0 = the codec default). The engine already accepted
arbitrary values, so the dialog now exposes them instead of the closed
preset lists. i18n across the 8 packs.
- ExportDialogContent: container format (existing), then the video and
audio codec combos rebuilt per container from the compatibility tables
(an unsupported codec is never offered — switching container
re-selects the first compatible entry), a color-space combo (SDR
Rec.709 8-bit / HDR BT.2020 10-bit), an export-range combo (whole
timeline / in-out) and the output path
- ComboBox::set_options added for the container-driven list rebuild
- settings() gathers the ExportSettings the engine consumes
- begin_export routes through start_export_with
- i18n keys across the 8 packs (zh-CN uses distinct values so the
en==zh equality test passes)
- ExportSettings: container format, video/audio codecs, size/fps/
bitrate, bit depth (8/10 → U8/U10), SDR 709 + HDR 2020 code points,
export range
- AppEngine::start_export_with(settings, path); real builds the
task params via encoding_params_with_settings (codec tables gate
each container — a non-supporting combo errors, never silently
defaults; the one menu only offers compatible codecs)
- oak-task EncodingParams: video_bit_rate / audio_bit_rate +
color_override_enabled + primaries/trc/space (override wins over
the project's delivery colorspace)
- mock start_export_with (no-op fake progress)
The dialog UI rebuild (fields + container→codec→color enability
linking) follows in the next unit.
- save_project_as(name): library_duplicate the open project's row,
rename the copy, open it as the current project
- 文件 → 另存为 opens a name dialog (prefilled '<name> Copy'); OK
duplicates + switches (the save-as semantics: a new, differently-
named project in the library, editing continues on the copy)
- 文件 → 新建项目 opens an in-app dialog: project name + the first
sequence's format (preset / custom / interlaced, reusing
SequenceFormatFields); OK creates the library project then seeds the
first sequence from the chosen format
- 打开项目 prompt advertises the supported project formats
(.ove / .otio / .fcpxml)
- manager.new.project_name across the 8 i18n packs
- action-search first-item test follows the new flow (New Project now
opens its dialog)
文件 → 导出工程文件… now opens an in-app dialog (format dropdown:
OpenTimelineIO / Oak / FCPXML + output path) instead of the bare
platform save prompt; OK routes the chosen extension through the
engine's export_project_path. Adds project.export.format i18n across
the 8 packs.
The 窗口 → 多机位向导 entry is removed from the menu bar and the
action-search list (menu_id 0, the hidden convention); the engine
pathway (wizard dialog, sync, multicam creation) stays intact behind
the action dispatch for a future release. The panel's multi-cam
feature remains.
- MulticamPanel: playback angle refresh runs one cycle per 3 ticks
instead of re-requesting every source every tick — each angle decode
is a keyframe-scanning FFmpeg seek that stole worker capacity from
the main viewer
- PreRender frames default 120 -> 12: a window larger than what the
pool can render in real time queues far ahead of the playhead, so
the painted frame lags seconds behind (playback frozen); a smaller
window keeps the backlog bounded
- render_graph_frame: OAK_PERF clip-level timing
- multicam_switch_to: a NO-SPLIT switch only changes current source —
push snapshot + invalidate preview windows, skip the full
apply_edit rebuild (the 'switch once then everything grinds' hit)
- MulticamPanel: playback angle refresh runs one cycle per 3 ticks
instead of re-requesting every source every tick (each angle decode
is a keyframe-scanning FFmpeg seek)
- footage value(): a ready proxy stands in for the original media in
the node-graph decode request (proxy enabled but 4K decoded report)
- proxy_resolution made public (engine's proxy-first preview size)
- Project explorer header gains a 新建序列 button (opens the existing
new-sequence dialog, seeded like the menu action).
- Sequence presets gain 4K UHD (3840x2160@25) and 4K DCI (4096x2160@24);
the sequence-properties dialog re-selects them on reopen. Format fields
are also seedable from a probed footage format (the drop flow).
- Chroma Key (and Color Difference Key) value() now box a ShaderJobPayload
like Despill: the old OCIO-processor gate pushed nothing (the processor
is never populated without the render bridge), so the traverser handed
the clip NodeValue::None and the rendered frame lost the clip. The
renderer resolves the OCIO stub at compile time from OCIO_SHADER_STUBS.
End-to-end graph test: green key on a green frame keys out, red key
keeps it.
- The OFX color picker's SV palette / hue bar / preview / swatch canvases
get size_full(): the bare canvases collapsed to zero height in the
block layout, so the palette painted nothing (the reported 色板没显示).
Regression test clicks the palette center and expects mid s/v.
Per-chunk submit->render wait and render time (audio_thread), the push
lead against the playhead, the negotiated stream config, and the true
device-side callback rate (outputdevice) -- the ground truth used to
localize the playback starvation to the sequence-rate/clock mismatch.
Two playback-audio defects found while chasing periodic pops, clipped
words and repeated phrases:
1. The playback clocks are created at engine init with the fallback
rate (hd_1080p25) and never updated, while audio chunks are sized by
the SEQUENCE time base (30/1 from the broken default). The playhead
advanced at 25 fps but each chunk carried only 1600 samples, so
production ran at 40000 samples/s against the device's 48000 -- ~17%
of playback time was zero-filled silence (clipped words). tick() now
syncs both clocks to the sequence's actual rate.
2. The underrun self-heal cleared the whole device queue (~190 ms of
unheard content skipped), reset the prefetch (whose first
re-rendered chunk was then always dropped as late) and re-anchored
the master clock, re-rendering already-played audio whenever the
playhead lagged (audible as a pop plus a repeated phrase, every
~1.1 s). It now trims just the stale lead from the queue
(PreviewAudioDevice::drop_front_frames): the zero-fill already
advanced the output clock past that content, so the rest of the
queue stays audible and the anchor stays valid.
Also adds the OAK_DEBUG_AUDIO event log used to diagnose all of this
(prefetch resets, late drops, underruns, heals, reanchor jumps,
per-tick queue state).
retrieve_audio_to decodes whole codec frames but copies only the part
inside the requested chunk; the tail of the frame crossing the chunk end
(up to 1023 samples for AAC) was consumed by the decoder and lost, so
the next chunk started with a hole. On the playback grid (1920 samples
at 25fps/48kHz) the hole cycles 128..896 samples and hits 7 of 8 chunk
starts -- the heavy stutter/noise heard during playback.
Keep the overflow (decode and resampler-flush tails) in a per-session
carry buffer and serve it at the start of the next contiguous chunk;
clear it on seek, format change and chunk failure. Also make seek()
actually drop the cached resampler as its comment claimed.
Verified sample-exact: chunked decode of a 440Hz tone now matches a
one-shot decode bit for bit, and chunked renders of real media line up
with the ffmpeg CLI reference at correlation 1.0 / drift 0.
Adds a regression test (playback_sized_chunks_match_oneshot_sample_exact)
with a new tone fixture -- demo.mp4's audio is -90dB digital silence and
cannot expose the holes -- plus a render_audio_wav example used to
diagnose chunk-boundary artifacts offline.
Run 48's Linux leg flaked: full_res_worker_outlives_a_dropped_project
panicked with "the render manager failed to start" while all 236 other
tests passed -- a worker-pool startup race under full-suite parallelism,
not a regression (the same binary passes locally and passed on the
previous commit). Mirror the Windows job's retry-once policy and make
ensure_render_manager log the underlying init error so the next flake is
diagnosable. The Windows leg's real failure (gpui_widgets referenced
SurfaceSource::Texture, which only exists on linux/freebsd) is fixed in
the gpui submodule bump of the previous commit.
Preview now follows the project output colorspace end to end: the
display chain derives its content space from the project's OutputColorSpec
instead of a hardcoded sRGB name, self-managed ICC transforms go through
an XYZ D65 interchange stage (OCIO cie_xyz_d65_interchange) for non-sRGB
targets, and the platform layer declares the content colorspace (gpui
submodule bump). macOS defaults to OS-managed (fixes wide-gamut UI
oversaturation); Windows ACM warns once on non-sRGB targets.
Multi-monitor: the display ICC is looked up per the window's current
screen (macOS display id, Windows per-monitor DC, X11 RandR output
profile) with a throttled poll that invalidates frame caches on moves.
Pipeline precision: 10-bit+ sources fall back to YUV444P16LE + a Rust
matrix conversion when swscale lacks F32 output (no more 8-bit
truncation); BT.709/2020 SDR decodes with BT.1886 gamma 2.4 instead of
the sRGB EOTF; working-space compositing no longer clamps RGB to [0,1]
(alpha still clamped); the output node clamps to the target gamut;
frames without colorimetry metadata convert with BT.709 defaults
(warned once) instead of passing through; scopes read the
output-colorspace signal on both F32 paths.
Also: only emit rerun-if-changed for .env when it exists (a missing file
made every build fully dirty).
- the popup now offers a large SV palette with a hue bar plus RGB/HSV
mode tabs (shared slider model swap, hsv<->rgb pure functions)
- while the eyedropper is armed, clicks outside the popup pick a colour
instead of dismissing it, so the picked draft can still be confirmed
- regression test pinning the OFX plugin colour set/read/undo chain
- i18n: ofx.color.mode_rgb/mode_hsv in all eight packs
It is a plain deferred div rather than an anchored element, so it gets
no automatic BlockMouse hitbox; clicks on its padding fell through to
the controls below.
- oak-worker gains an LRU frame cache (default 64 MiB) keyed by the
render-deterministic spec subset; repeat frames (paused frames,
scrubs over rendered ranges, re-renders after an effect change) are
memcpy-cheap, which is what made adding an OFX plugin -- and the
in-flight batches after removing one -- stall the UI
- audio dispatch on the Processes backend now mixes inline on the UI
tick instead of queueing behind video batches in the worker pool, so
video stalls no longer starve the ~100ms cpal output buffer into
silence
The display path no longer passes through BGRA8: render workers are
asked for F32, rows are repacked, colour management runs on the F32
data, and the frame uploads to an Rgba16Float wgpu texture that the
viewer presents directly on the Rgb10a2Unorm swapchain. The BGRA8
image remains only for scopes, the eyedropper, thumbnails and the
no-GPU fallback.
Preferences gains a display-bit-depth combo stored in the config store;
at startup the app forwards it to gpui_wgpu via OAK_DISPLAY_BIT_DEPTH,
which prefers Rgb10a2Unorm for 10-bit presentation. Takes effect after
restart (noted in the dialog); i18n in all eight packs.
The 18 multicam switch actions were bound globally to keys 1-9, so
gpui dispatched every digit keystroke as an action and text fields
never received it -- every numeric input in the app (slider value
boxes, spin boxes, the colour picker's hex field) could only type 0,
and the colour swatch never updated because hex parsing failed.
The bindings now carry the MulticamPanel key context: digits switch
angles while the panel is focused and type normally everywhere else.
- paused full-res renders now scale by the playback divider instead of
always rendering at native sequence size
- when a clip's proxy is active, sequence/footage renders clamp to the
proxy resolution (never upscale a 720p proxy to 1080p and back),
which was making proxied playback slower than no proxy
Replaces the project-thumbnail picker: the color popup's eyedropper
button arms the program viewer, the next click on the picture samples
that pixel (contain-fit mapped, BGRA from the current CPU frame) into
the picker's draft; Esc or the button cancels. Coordinated through a
global eyedropper mailbox on the engine.
- the eight toolbar tools now drive real editing: razor splits at the
click point, ripple/slip/roll/slide emit new undoable composite
commands (ripple_trim_clip, roll_edit, slide_clip, slip_clip),
zoom clicks zoom anchored at the cursor, track-select selects the
track right of the click; panel tool state and the Tools menu stay
in sync both ways
- the zoom-in/out toolbar icons now actually zoom (anchored at the
playhead) and the snap magnet icon toggles snapping like its checkbox
Every item in the program/source viewer menu now works and reflects
real state: zoom levels, full-screen toggle, safe margins (off/on/
custom), stop-on-last (honoured by both playback clocks), waveform
mode, show-fps overlay, and save-frame (writes a PNG of the current
frame). The viewer widget's in/out/clear buttons are wired to the
shared program workarea.
- File > New > Sequence opens a real dialog (name, PAL/NTSC/HD presets,
width/height/frame rate, progressive/interlaced); VideoParams gains an
interlaced flag
- File > New > Folder creates a folder in the project root
- sequences are mounted under the root folder so they appear in the
project explorer (including the auto-created Sequence 1 and orphans
from older projects); right-click > Sequence Properties edits the
parameters afterwards
- dropping footage onto an empty, sequence-less timeline auto-creates a
sequence from the footage's first video stream
- the swatch now follows the draft color while the popup is open
(cancel still reverts) and set_effect_param failures are logged
instead of silently reverting the swatch
- the popup lists project footage thumbnails; clicking a pixel picks
its color into the draft (contain-fit coordinate mapping, no new
dependencies)
- i18n: ofx.color.pick_project in all eight packs
- clicking anywhere on the track moves the thumb to that position
(double-click still opens the inline editor)
- a compact numeric field next to every slider shows the value and
accepts typed input, clamped to the slider's min/max range
Match the timeline UI (V_max drawn topmost): composite tracks from
V1 up to V_max so the highest-numbered track is composited last, in
both the montage path and direct graph evaluation.
- take_node clears the partner's dangling link references
- are_linked checks both directions; link() repairs asymmetric pairs
- paste writes links through the graph API instead of BlockCore.links
- drop link undo removes only its own pair instead of restoring a snapshot
- move_clip_with_links skips off-track partners instead of failing
Proxy generation no longer pegs the machine:
- the transcode probes for a hardware H.264 encoder once per process
and uses it when present (macOS h264_videotoolbox; Windows/Linux
h264_nvenc -> h264_qsv -> h264_amf; libx264 remains the universal
fallback and the untouched C++ parity path), with decode-side
-hwaccel auto (HEVC 4:2:2 10-bit decodes in hardware on Apple
Silicon / RTX 50+ / Intel GPUs and falls back to software cleanly);
quality/preset map from the proxy CRF/preset per encoder
- the concurrency limit is configurable (ProxyMaxConcurrent, default
1) in the Proxy Settings dialog; auto-generated jobs queue and the
next starts when a slot frees. The invariant
concurrency x per-job threads <= logical cores / 2 holds by
construction (thread budget = half the cores / concurrency, clamped
to [1,8], covered by a unit test), and on Unix ffmpeg runs nice -n 10
so the background task never starves the foreground
- deleting a footage's proxy also removes it from the auto-generation
queue
playback_display_tracks_the_playhead asserted the displayed frame lag
the playhead by less than 4 frames at playhead 120 — a render-
throughput assumption that fails under machine load (parallel builds)
rather than for a broken pipeline, which made the test flaky. It now
asserts the machine-speed-independent property: the display advances
while the playhead advances (peak displayed grows between the two
checkpoints), which still catches a permanently frozen picture.
rebuild_timeline synchronously extracted every audio clip's waveform,
and the extractor decodes the clip's ENTIRE audio inline — with two
40-minute files on the timeline, opening the project froze the UI for
seconds ("the project takes a while to open"). Extraction now runs on
a background thread; the cache bumps a version counter on insert and
the engine tick repaints the timeline when it changes.
With the global proxy switch on, footage that needs a proxy now gets
one generated in the background automatically — on import, on project
open, and when proxy use is enabled for a footage — instead of only
through the manual Generate menu action. Footage explicitly opted out
(a recorded proxy path with use disabled) and footage whose last
generation failed are not restarted; the generation path enables proxy
use so preview switches to the proxy as soon as it is ready.
The bin shows the lifecycle as a corner badge on each footage entry
(green P = ready, amber P = generating, red ! = failed; the widget
half lives in the gpui fork, submodule bumped here).
Includes a real-media test driving ProxyTask end-to-end on 4K H.265
4:2:2 10-bit (the media class the 4K playback lag was reported on):
180 s transcodes to a 720p proxy in ~25 s.