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)
oakrender Rust crate
Status: implemented (M7 render wave). Every
todo!()from the declaration draft is implemented; the crate builds, tests green (cargo test), and the C ABI surface ininclude/render/*.his exported fromsrc/ffi.rs. Deferred items are documented inline and in the Deferred section below.
Scope
Replaces the C++ oakrender module (src/render/src): render manager,
ticket system + worker pool, textures and GPU backend dispatch, playback/
frame-hash caches, color processing (OCIO), the preview auto-cacher, and
the blit/display path.
Public contract: include/render/*.h (8 headers, ~165 functions) —
frozen, implemented verbatim by src/ffi.rs.
Key architectural decisions (C++ → Rust mapping)
- The ProjectCopier inversion disappears. C++ render deep-copied
the node project with raw C++ calls (the biggest render→node
coupling). In Rust this is impossible by construction: the copier
calls
oaknode_project_deep_copy/sync_copy(designed in the oaknode crate) through the C ABI.copier.rshere is a thin client. - RenderProcessor's inheritance disappears. C++
RenderProcessor : NodeTraverserbecomeseval.rs(the closedJobSpecset + the CPU-side hook implementations; graph traversal stays in oaknode). - Ticket/watchers. C++ RenderTicket/RenderTicketWatcher (Qt
signals) become a ticket arena with completion callbacks —
exactly-once delivery (
ticket.rs),FnOnceboxes fired on the worker thread. - GPU backend = wgpu (v25). The C++ tree's backend plugin split
(liboakgl2/liboakvulkan behind
renderbackend_c.h) exists because C++ had no portable GPU abstraction. Rust haswgpu(Metal/Vulkan/ GL/DX12 in one safe API), so the Rust crate useswgpudirectly — no backend plugins, norenderbackend_c.h, no dlopen.backend.rsowns the wgpu instance/device/queue, the texture registry and the WGSL blit pipeline. Headless status: verified — texture create/upload/download and the plain-copy blit run without any surface or event loop on macOS Metal (the GPU tests exercise them and skip gracefully when no adapter is available). - Threading. The worker pool is scoped threads with a job
channel; every shared structure is
Mutex/RwLock. Process isolation (ProcessPool) is preserved as a documented stub: it needs the oakengine_ipc worker binary, which is not wired this pass. - OFX disappears from render. pluginrenderer.cpp's functionality moves to the oakplugin crate; this crate only sees plugin jobs as opaque C ABI calls.
Dependencies (registered)
| Crate | Version | Reason |
|---|---|---|
oakcore-rs |
path | Rational/TimeRange/PixelFormat value types (crate-internal) |
wgpu |
25 | portable GPU backend — the direct replacement for the C++ GL/Vulkan backend plugins |
ocio-rs |
0.2 | safe Rust bindings for OpenColorIO v2.5.2 (bundled real-OCIO build); the ColorProcessor implementation — OCIO is never rewritten |
Layout
src/
lib.rs crate doc + module map
error.rs error codes (mirrors include/render/error.h)
handle.rs refcounted-handle scaffolding + live-object accounting
texture.rs Texture value type (wraps backend textures / CPU frames)
frame.rs VideoParamsPod + Frame helpers
cache.rs PlaybackCache / FrameHashCache family + C++-parity disk state
color.rs ColorProcessor over ocio-rs + default config + LUT library
manager.rs RenderManager singleton + lifecycle + disk cache
ticket.rs Ticket arena, params, exactly-once completion delivery
worker.rs Worker pool + process pool (stub) + graph snapshot store
autocacher.rs PreviewAutoCacher
eval.rs RenderHooks impl: the CPU evaluation seam
backend.rs wgpu device/queue/texture management + DisplayRenderer
copier.rs Render-side project copy client (bridge::node)
cancelatom.rs the cancellation primitive
bridge/ C ABI imports: node.rs, common.rs, codec.rs (dlsym-resolved)
ffi.rs include/render/*.h export layer
tests/ contract + golden tests (common/ has shared helpers)
Hard rules
- Every export goes through
handle::guard*. - No
unsafeoutsidebackend.rs(GPU FFI) andbridge/. - F32 + ACEScg pipeline invariants are asserted in tests, not in comments (see tests/pipeline_test.rs).
Deferred (documented; tests gated with #[ignore])
- oakcodec frame payload I/O — disk frame-cache read/write
(
oakrender_frame_cache_load/save) and footage decode go through thebridge::codecC ABI (EXR/JPEG). The oakcodec crate is a concurrent wave; until it lands these fail explainably and the success-path tests are#[ignore = "needs oakcodec final"]. - oaknode C ABI —
oakrender_project_copier_set_project/get_copysuccess paths needoaknode_project_deep_copy; the success-path copier tests are#[ignore = "needs oaknode C ABI"]. - Color-managed GPU blit — the OCIO→WGSL shader generation is not
in this pass:
GpuContext::blithandles the plain copy and returnsError::Failedfor a processor; the CPU path applies the processor in float.oakrender_color_processor_create_transformresolves the destination transform against the default config's reference role until the oakcommon color-transform bridge lands. - Worker process isolation —
ProcessPool(oakengine_ipc worker binary) is a stub;start/postfail explainably; the crash-isolation tests are#[ignore]. - Audio rendering — audio tickets complete with
Error::Failed(the audio graph path is not implemented);oakrender_ticket_get_samplesfails explainably. - Borrowed caches —
oakrender_cache_wrap_borrowedboxes an opaque marker; queries on borrowed caches returnOAKRENDER_E_INVALIDuntil the C++ interop layer lands. RenderManager::global()returnsOption<Arc<…>>instead of the draft'sOption<&'static …>— a resettable singleton cannot hand out stable references safely.
Coverage
COVERAGE.md maps every C++ class of src/render/src to its Rust
home. cargo tarpaulin ≥ 80% excluding the deferred areas listed
above.