Module-internal object references are Rust types now (values, Arc, Mutex); CHandle remains only at the oakengine C-ABI boundary: - oakundo: the global stack holds UndoStack/UndoCommand values directly (stack token is the static's address) - oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands share the same allocation through Arc clones (readers in oakengine stubs and the app's graphops updated to lock) - oaktask/oakstorage: sessions, write-through bindings and the database backend pass ProjectArc; the Session drops its manual release bookkeeping; nodeutil keeps the CHandle<->Arc boundary conversion (release_project restored for the app) - oakcodec: handle.rs deleted outright (no facade entry needed it); texture/block placeholders are unit structs - oakrender: copier's project handle is an identity u64; alive-count machinery removed; handle.rs is make_owned/get/get_mut only - oakplugin: the instance registry is gone (its unregister key never matched, leaking weak entries); handle.rs is the RefBox boundary type - oaknode/oakcommon: only dead guard/borrow helpers removed; external payload handles (texture/processor) documented as the boundary Flake hunts landed along the way: the audio recording test serializes on the shared manager lock with a normalized state; the autocacher cancel test uses a slow producer so cancellation is deterministic.
127 lines
6.3 KiB
Markdown
127 lines
6.3 KiB
Markdown
# 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 in `include/render/*.h` is
|
|
> exported from `src/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)
|
|
|
|
1. **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.rs` here is a thin client.
|
|
2. **RenderProcessor's inheritance disappears.** C++
|
|
`RenderProcessor : NodeTraverser` becomes `eval.rs` (the closed
|
|
`JobSpec` set + the CPU-side hook implementations; graph traversal
|
|
stays in oaknode).
|
|
3. **Ticket/watchers.** C++ RenderTicket/RenderTicketWatcher (Qt
|
|
signals) become a ticket arena with completion callbacks —
|
|
exactly-once delivery (`ticket.rs`), `FnOnce` boxes fired on the
|
|
worker thread.
|
|
4. **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 has `wgpu` (Metal/Vulkan/
|
|
GL/DX12 in one safe API), so the Rust crate uses `wgpu` directly —
|
|
no backend plugins, no `renderbackend_c.h`, no dlopen. `backend.rs`
|
|
owns 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).
|
|
5. **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.
|
|
6. **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 (facade entry points only)
|
|
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
|
|
|
|
1. `CHandle` only appears at the facade boundary: the crate's internal
|
|
calls pass Rust types directly; `handle::make_owned`/`get`/`get_mut`
|
|
are the facade entry points the oakengine stubs call.
|
|
2. No `unsafe` outside `backend.rs` (GPU FFI) and `bridge/`.
|
|
3. 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 the
|
|
`bridge::codec` C 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_copy` success paths need `oaknode_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::blit` handles the plain copy and returns
|
|
`Error::Failed` for a processor; the CPU path applies the processor
|
|
in float. `oakrender_color_processor_create_transform` resolves 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/post` fail 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_samples`
|
|
fails explainably.
|
|
- **Borrowed caches** — `oakrender_cache_wrap_borrowed` boxes an
|
|
opaque marker; queries on borrowed caches return `OAKRENDER_E_INVALID`
|
|
until the C++ interop layer lands.
|
|
- **`RenderManager::global()` returns `Option<Arc<…>>`** instead of the
|
|
draft's `Option<&'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.
|