Commit Graph
24 Commits
Author SHA1 Message Date
Mike-Solar 4babbf5de8 core: merge oak-common into oak-core
CI / Build & test (Linux) (push) Successful in 24m6s
CI / Build & test (Windows) (push) Successful in 31m14s
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.
2026-09-03 17:42:20 +08:00
Mike-Solar 9352fca4a9 codec/render: silence hw-decode failures, vram-aware dynamic worker pool
- open_hw_accel marks the device unavailable when the decoder OPEN fails
  (cuvidCreateDecoder OOM at 4K) too, not just device-context creation:
  without it every subsequent decoder session retried CUDA and flooded
  the log per open.
- Decoder gains hardware_decoding(); the oak-render decode-session LRU
  evicts hardware sessions first (each pins a GPU surface pool — ~100 MB
  at 4K), so a full cache cannot exhaust video memory before the next
  open.
- Worker pool count now factors GPU vram: per-worker budget = 1 GiB
  (1080p peak) scaled by pixel ratio + 256 MiB idle floor, 10% reserve
  of free vram; applied when hardware decoding is on (nvidia-smi query,
  None otherwise falls back to the RAM/CPU policy).
- Dynamic pool resize: ProcessDispatcher::set_target_workers grows or
  retires workers; retiring ones stop claiming, drain their in-flight
  batch (future playback frames included), then exit naturally on the
  shutdown signal — no mid-work kill (30 s deadline only as a hung-
  decoder last resort). A retiring worker that dies re-queues its frames
  to surviving workers. Resizes are throttled to 2 s (a resolution burst
  merges; only the latest target applies) so 1080p<->4K flaps cannot
  thrash process spawns.
- RenderManager::set_workspace_size announces the sequence resolution;
  RealEngine calls it from refresh_sequence_info.
- Integration test: shrink 3->1 mid-wave (all frames complete, retired
  workers exit naturally) then regrow 1->3 and render a fresh wave.
2026-08-30 21:59:06 +08:00
Mike-Solar cbd4ba2442 optimize: video and audio render
CI / Build & test (Linux) (push) Canceled after 0s
CI / Build & test (Windows) (push) Canceled after 0s
2026-08-29 05:01:55 +08:00
Mike-Solar fdb5caabd5 color: non-sRGB preview, per-monitor display ICC, pipeline hardening
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).
2026-08-29 00:24:15 +08:00
Mike-Solar 9c0269bcbb render: cache worker frames and decouple audio from the video workers
- 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
2026-08-27 20:35:50 +08:00
Mike-Solar c51a349070 render: translate node GLSL shaders to WGSL and run them as wgpu passes
CI / Build & test (Linux) (push) Failing after 16m57s
CI / Build & test (Windows) (push) Successful in 31m46s
2026-08-27 07:07:09 +08:00
Mike-Solar f2aab8ce15 render: apply clip effect stacks in the montage path
CI / Build & test (Windows) (push) Failing after 16m26s
CI / Build & test (Linux) (push) Successful in 19m12s
Adding an effect to a clip did nothing: the sequence render is
flattened into a montage (decode + composite), and MontageClip carried
no effect data at all.

- MontageClip gains an ordered effect stack (type id / enabled /
  effect input / parameter values); protocol v2 carries it as an
  additive wire field (older peers default to an empty stack).
- renderops::video_montage fills the stack from the effect chain
  (the footage source node — the chain end without an effect input —
  is dropped; the montage decodes the footage itself). Export
  (oak-task) and the multicam single-track montage fill it too.
- The worker applies the stack between decode and composite: built-in
  Opacity gets a CPU evaluator (C++ opacity.frag parity — whole vec4,
  alpha included, unity pass-through); everything else dispatches as an
  OFX plugin job through a new instance-factory slot (oak-plugin
  lazily creates + caches one instance per identifier per render
  process) with the montage's parameters injected. Disabled effects
  bypass (the C++ traverser pushes the effect input through). Unknown
  types warn once per type id and pass through — no silent no-ops.

Not covered (explicitly): Transform/Crop and the other ~30 built-in
effects have no CPU evaluator in oak-render (they pass through with a
warning), keyframed parameter animation, audio effect chains, and the
CLI's simplified montage.

Acceptance: a real 50% Opacity on real media quarters the rendered
pixels both in-process (renderops test) and through a real worker
process over IPC + shared memory (procpool_integration test);
disabling restores the plain render byte-for-byte.
2026-08-25 04:49:00 +08:00
Mike-Solar f7b5996032 render: fix the BGRA8 display transform + dispatcher diagnostics
CI / Build & test (Windows) (push) Failing after 17m13s
CI / Build & test (Linux) (push) Successful in 19m6s
convert_bgra8 applied packed u8 pixels to the default (F32-finalized)
OCIO CPU processor, which rejects them with a bit-depth mismatch; the
callers swallow the error, so the display-ICC transform was silently
inert on the shm preview path. ocio-rs exposes no Uint8-finalized CPU
processor, so the conversion now detours through F32. Verified against
the machine's actual display profile with the new
display_icc_bgra8_never_outputs_black test (OAK_DISPLAY_ICC).

Also:
- procpool_integration: audio tickets are Seek priority and claimable
  by any worker now, so the shard-spread assertion goes (rendering on a
  live worker is what matters).
- OAK_DEBUG_VIEWER=1: the program viewer logs frame pushes and dumps
  the displayed frame to /tmp/oak_viewer_frame.ppm (the black-screen
  investigation tooling).
2026-08-24 23:26:09 +08:00
Mike-Solar c32341c888 ci+fixtures: UCRT64 msys2 env; fix remaining fixture paths
CI / Build & test (Windows) (push) Failing after 2m56s
CI / Build & test (Linux) (push) Failing after 17m43s
ci (Windows): the runner's msys2 shell starts as the base MSYS
environment (MSYSTEM=MSYS), which install-deps.sh rejects. Set the
job-level MSYSTEM=UCRT64 env and prepend /ucrt64/bin to PATH in every
Windows step, so pacman installs and the toolchain resolve against the
mingw-w64-ucrt-x86_64 packages.

fixtures: the restructured workspace moved the media fixtures into
crates/oak-app/tests; the remaining references pointed at the old
repo-root tests/ — oak-codec realmedia_tests + hwdecode, oak-node
serializer golden, oak-worker procpool integration. All point at
../oak-app/tests now (oak-app's own tests/demo.mp4 references were
already correct after the move).
2026-08-22 22:28:24 +08:00
Mike-Solar 244d5e860f workspace: kebab-case crates, app under crates/oak-app, shared versions
CI / Build & test (Windows) (push) Failing after 7s
All crates take the oak-* kebab-case naming (oak-audio, oak-codec,
oak-common, oak-core, oak-ffmpeg-link, oak-node, oak-otio, oak-plugin,
oak-render, oak-storage, oak-task, oak-timeline, oak-undo), with the
lib identifiers rewritten (oakrender:: -> oak_render::, oakcore_rs:: ->
oak_core::, ...) across all 226 referencing files.

The GUI application moves from the workspace root into
crates/oak-app/: src/, build.rs (paths fixed for the new location) and
tests/ travel with it, the root Cargo.toml becomes workspace-only
([workspace] + workspace.package + profiles), and the app package
inherits the workspace version. The screenshots example becomes a
standalone crate examples/simple_player/ with its own Cargo.toml.

Every crate now inherits the single workspace version
(version.workspace = true), and the workflows' crate paths and the
build docs follow the renames.

Validated with a clean cargo check --workspace.
2026-08-22 16:58:37 +08:00
Mike-Solar 551909df62 ci: clear MSVC INCLUDE/LIB for the GNU build; worker test helper fixes; hw tolerance
- the Windows runner image exports MSVC's INCLUDE/LIB; cc-rs was
  appending the MSVC SDK headers to MinGW compiles (vcruntime.h not
  found)
- oak-worker handshake test helper advertised the input pool's total
  byte size as per-slot data bytes (macOS tolerated the oversized
  attach; Linux correctly rejects it)
- hw/sw decode comparison tolerance 0.05 -> 0.08 (VideoToolbox's
  YUV->RGB legitimately differs by ~1 LSB of intermediate depth)
2026-08-21 05:52:30 +08:00
Mike-Solar 738120bffc ci: Windows uses the MSYS2 OpenColorIO (2.5.2, dynamic); test diagnostics
- the vendored OCIO source needs MSVC-only constructs (wide-path
  ifstream); MSYS2's mingw build of the exact 2.5.2 the bridge targets
  is the sane Windows path — DLLs get packaged next to the binaries
- oak-worker handshake test prints the error response on failure
  (CI-only attach failure needs the message)
2026-08-21 05:17:54 +08:00
Mike-Solar 9003b78176 feat: built-in effect params, clip click-select, effect drag-and-drop, project load with plugins
- serializer resolves node types through the factory's dynamic
  (runtime-registered OpenFX) entries, so a project carrying plugin
  nodes loads again (was: "unknown node type"); covered by a new
  CI-gated round-trip test driving the real fixture plugin
- built-in effect nodes expose their inputs as inspector parameters
  like the C++ parameter editor: localized input names from the
  behavior, combo option tables via the new
  NodeBehavior::input_combo_strings (16 nodes, string-for-string from
  the C++ set_combo_box_strings), connection/data inputs excluded
- effect library: live drag-and-drop — onto the inspector's effect
  stack (lands at the indicator position) and onto the node editor
  canvas (creates the node at the drop point); double-click still
  appends to the selected clip
- inspector parameter controls are no longer recreated per render
  (gpui stack view caches them per effect), so sliders drag and
  checkboxes click; the view observes the engine and silently re-syncs
  values (undo/redo land on the widgets)
- timeline: left-press selects clips (plain/keep-multi/Ctrl-Cmd
  toggle); clip moves clamp the shared delta so no clip of a linked
  group lands before frame 0 instead of failing with "invalid move
  target"
- oakplugin: createInstance-rejected instances skip the destroyInstance
  notification (the plugin never owned them); vendor-suite fetchSuite
  misses moved behind OAK_OFX_TRACE; the worker logs the discovered/
  registered plugin counts
- CI: the OFX probe step also runs the serialization round-trip test
- gpui submodule: params view caching, clip click-select, library
  drag payload, graph_position_at
2026-08-20 23:46:40 +08:00
Mike-Solar b4ceaa9cab feat(oakplugin): GL render bridge, color picker, push-button action, worker progress, Interact host
- gl_bridge: macOS CGL offscreen context (process-wide singleton,
  serialized GlGuard), real GL output textures/FBOs, glReadPixels
  readback with vertical flip and format conversion; use_opengl now
  really engages for OpenGLRenderSupported plugins (verified with real
  GL rendering: C smoke 11/11, unit tests, GL e2e).
- OfxColor: color params get a swatch button plus a real picker popup
  (RGBA sliders, live preview, hex input, undoable commit) replacing
  the four spinboxes.
- Push buttons route kOfxActionInstanceChanged (UserEdited) per the
  OFX contract; test plugin asserts the callback.
- Worker-side plugin progress flows to the main-process progress
  dialog over the NDJSON control channel, with cancel propagation.
- OFX Interact host: NewInteract/Describe lifecycle, Draw/Pen/Key/Idle
  action surface with proper in-args, DrawSuite v1 host implementation
  sharing the gl_bridge context; interact test plugin verifies the
  event stream and real GL drawing.
2026-08-19 22:14:54 +08:00
Mike-Solar a17d5be56a fix(render): assign batch slots in the worker's acquisition order
A claim mixing audio and video tickets is delivered as the video
message first and the audio message second, and the worker pops one
free-ring slot per ticket in that message order, checking each pop
against the assignment. The dispatcher however assigned slots in the
scheduler's interleaved frame order, so every audio ticket inside a
mixed batch mismatched, and each mismatch consumed a worker slot
without recycling it — cascading into the 'slot assignment mismatch'
flood and failed frames during playback.

Slot assignment now partitions the claim: video tickets first, then
audio. The mixed_audio_video integration test forces mixed claims
(queue depth > slot count with immediate releases) and fails with the
exact production signature when the fix is reverted.
2026-08-19 04:49:15 +08:00
Mike-Solar adf2cef32c feat(oakrender): render-process isolation S3 - audio over shm, per-ticket slot formats, tuning
- Audio tickets join the process backend: render_audio_batch wire
  message, workers mix straight into shm slots (SLOT_FORMAT_AUDIO_F32),
  ShmAudio payload with release semantics, crash isolation covers audio
  renders; playback audio uses an async 4-chunk prefetch drained on the
  UI tick (also fixes the sub-60fps chunk truncation bug); oversized
  ranges and dispatcher outages fall back to in-process inline.
- Per-ticket slot formats: force_format is honored (exports request
  F32 slots, dropping the BGRA8 round-trip and its 8-bit quantization);
  segments grow on demand via worker-idle rebuild with generation
  handoff; the scheduler filters over-capacity tickets.
- Adaptive defaults: 128-256MB/worker segment budgets drive slots per
  worker, batch size follows workers/slots; bench_process example
  measures throughput and adjacent-frame completion deltas
  (e.g. 4 workers: 841 fps, 4.6ms mean delta).
2026-08-19 00:42:03 +08:00
Mike-Solar ec7b7e6d13 feat(app): OpenFX UI wiring - effect library, inspector params, startup glue
- src/oakui/ofx.rs: startup sequence (host scan, register_plugin_nodes,
  progress reporter factory -> app progress dialog channel, active
  viewer time provider, project extent sync); all failures degrade to
  logs. oak-worker runtime also scans and registers plugins.
- Effect library groups OpenFX entries by sub-category (Filter/
  Generator/Transition/General); effect insertion goes through
  Factory::create_any so dynamic plugin nodes resolve.
- Inspector renders OFX parameters from node inputs (sliders, combo
  boxes from repeated combo_option/combo_value properties, vec/color
  spinboxes, text with explicit commit, push buttons), edits are
  undoable; persistent plugin messages surface as a card badge.
- oakplugin: push_button_clicked and per-instance persistent message
  counting (thin public layers).
2026-08-18 22:16:08 +08:00
Mike-Solar cad1d93544 feat(oakrender): render-process isolation S2 - process backend by default, zero-copy onscreen
- WorkerPool thread pool deleted; RenderManager defaults to the
  Processes backend (oak-worker children), Threads kept as a test-only
  inline dispatcher; audio tickets stay in-process until S3.
- Onscreen path reads worker shm slots directly: BGRA8 slot format,
  RenderedFrame::Shm wrapped into the display buffer (single disclosed
  GPU-staging memcpy), scopes analyze BGRA8; the long-lived full-res /
  thumbnail paths take the counted slot_to_vec copy and release.
- Playback pre-render window: forward 120 frames (configurable) fed to
  the PreviewScheduler at Playback priority, interleaved across
  workers, cached in shm slots until the playhead consumes them;
  generation-based invalidation cancels and releases on edits.
- oaktask export and oak-cli run on private ProcessDispatchers (fixed
  a pump-while-locked self-deadlock in the export loop); facade
  get_frame handles ShmFrame payloads.
- Acceptance: preview path main_heap_frame_copies == 0 with spawned
  workers, CLI transcode/render verified end to end.
2026-08-18 20:45:24 +08:00
Mike-Solar 431b9ed2b1 feat(oakrender): render-process isolation S1 - dispatcher, scheduler, real worker
Per the M15 design (docs/zh/plans/riir/M15-render-process-isolation.md):

- ipc.rs moved into oakrender with protocol v2: hello_caps,
  render_batch, batch_accepted, frame_failed; main-process-assigned
  slots; BGRA8 slot format. POSIX shm verified to 1GiB on macOS.
- ProcessDispatcher: spawns oak-worker processes, handshake, stdio
  NDJSON control, shm segment lifecycle with generation-tagged keys,
  crash detection with bounded restart and frame redispatch, zero-copy
  ShmFrameRef delivery and copy counters.
- PreviewScheduler: interleaved batch claiming (frame % W per worker,
  no work stealing), seek > playback-distance > background priority,
  credit-based flow control, crash recovery.
- oak-worker renders for real: graph snapshot deserialization, montage
  decode+composite straight into the assigned shm slot, F32->BGRA8
  final conversion in-worker, OFX plugin executor installed in-worker,
  crash hooks for isolation testing.

Thread pool coexists for now (S2 removes it). Integration tests cover
two-worker zero-copy rendering, crash isolation with redelivery, and
real H.264 footage decode into slots.
2026-08-18 18:58:38 +08:00
Mike-Solar 61da70ecf8 fix(build): launch crash — @rpath/libz.1.dylib had no LC_RPATH
The static FFmpeg's external codec libs pull in -lz, which on this
toolchain resolves to a copy whose install name is @rpath/libz.1.dylib
(zlib-ng-compat); without an LC_RPATH entry all three binaries died in
dyld at startup. The app/cli/worker build scripts now emit
-Wl,-rpath,/usr/lib.

Also: FFMPEG_DIR moves into the committed .cargo/config.toml as a
workspace-relative [env] entry — ffmpeg-sys-next's build script cannot
read .env files, and without it the crate silently linked the shared
Homebrew FFmpeg while oakffmpeg-link emitted the static transitive
flags (mixed linkage). docs/build.md updated.
2026-08-17 19:42:45 +08:00
Mike-Solar a45a7af2ac refactor(cli,worker): cut liboakengine, link module rlibs directly (M14 R2)
- oak-cli: new engine.rs assembly layer maps every facade call to
  module Rust APIs (oaknode graph/serializer, oaktimeline commands,
  oakrender ticket arena, oaktask ExportTask, oakcommon config); the
  ffi/optional/host layers and build.rs link config are gone
- oak-worker: the worker session + POSIX shm transport moved into the
  crate (oakrender backend + serde_json control plane); no dylib
- both binaries carry zero liboakengine references (otool verified);
  tests green (30 cli / 41 worker)
2026-08-16 21:16:27 +08:00
Mike-Solar 061aab9180 fix(app): full-res worker crash + source playback frozen + cli/worker DS batch
- full-res source jobs carried only the footage node box: dropping the
  project mid-flight left the node dangling (crash in the worker's free
  path) and the node was also freed twice (at renderer creation AND at
  release). The request now carries an addref'd project copy and the
  node is freed exactly once; regression test drops the project before
  the worker runs
- the source clock ticked at the SEQUENCE length, so playing footage
  with an empty sequence froze the source playhead at 0; the source
  clock now loops at the selected footage's probed duration
- oak-cli/oak-worker: DeepSeek's refactor batch (clap migration, engine
  FFI consumers); the stale exporter-family test flipped to the real
  contract (mp4 is written)
- engine: render_audio smoke test on an empty sequence
2026-08-16 19:07:07 +08:00
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00
Mike-Solar 013a175707 refactor: workspace layout — crates/, app at root, legacy C++ removed
Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
2026-08-10 20:24:25 +08:00