Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/ oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain Rust, CHandle marshalling shrinks to the oakengine boundary, and tests call the Rust APIs directly (pure C-ABI wrapper tests removed where the domain layer already covers the behavior). exporter.h family implemented: oakengine_export_render (CLI contract), oakengine_export_render_with_params (was a stub), last_error and progress callback; synchronous path reuses task_create_export + start_sync. Fixes on the way: oaktask video ticket self-deadlock, audio params dropped on the export path, codec encoder AAC slicing and H.264 time base. Real-mp4 tests cover both entry points, progress and the illegal-argument matrix. Also: oakstorage session maps null project handles to None (version- info path), configstore test double literal 3.14 -> 3.15 (clippy PI lint), oakaudio output callback scratch buffer + env-aware P1 test, cli media round-trip test uses a generated 16-frame clip (no more minute-long debug runs).
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.