Real openfx-misc/CImg/Shadertoy bundles (148 plugins at
/Library/OFX/Plugins) all failed to load before; every failure was
silent. Root causes found one by one with a probe example + lldb:
- property suite rejected propSet on undefined properties and
propGetDimension on empty ones, and disallowed the index==size
append — OFX semantics are create-on-set and appendable dimensions
(this alone failed every plugin's describe)
- host property set missed the mandatory OfxPropType/OfxPropAPIVersion
and the capability props ofxs' fetchHostDescription reads with
throwOnFailure=true (IsBackground, TemporalClipAccess, MaxPages,
PageRowColumnCount, host SupportedContexts, ...) — one missing prop
aborted the read chain and left a half-initialised host description,
which made every temporal plugin refuse to load
- MultiThreadSuiteV1 lacked the five mutex functions (the plugin reads
past the short table — UB); implemented as a real counting-semaphore
registry
- the OfxHost struct was a stack local; ofxs keeps the POINTER past
setHost, so describe/render-time fetchSuite calls dereferenced a
dangling stack address (bus error once plugins actually loaded) —
the struct is now a leaked process global
- General is a standard OFX context and is no longer filtered out
(Roto/AppendClip/STMap declare only it)
- every scan/load/describe early-out now logs its reason; suite entry
points report non-OK statuses with caller location under
OAK_OFX_TRACE
- examples/scan_probe.rs: scans the real plugin dirs and prints
discovered/registered counts (also usable from CI)
Result: 148/148 plugins discovered, 134 registered as node types (the
remaining 14 need vendor suites — Vegas stereoscopic etc. — and are
logged, not silent)
- The Window menu lists every panel, checks the open ones, and toggles
visibility on click (closed panels reopen at their last dock target,
falling back to the default group) — a panel closed by accident
(e.g. the inspector) is one menu click away again. The menu refreshes
on dock structure events.
- OFX plugin scanning now covers the full standard location set:
per-user (~/.OFX/Plugins, ~/.local/share, ~/Library/OFX/Plugins on
macOS), system-level (/Library/OFX/Plugins, /usr/OFX/Plugins,
/usr/local, %ProgramFiles%\Common Files\OFX\Plugins), app-relative,
and the OFX_PLUGIN_PATH environment variable.
- Main-process interact instances for the selected OFX effect card
(create on selection change, describe, destroy on deselect/close),
coexisting with the render-worker plugin instances per the OFX
multi-instance model.
- Program viewer composites the interact's overlay: draw into a GL
FBO via gl_bridge, read back, straight-alpha 'over' composite onto
the displayed frame; cached and only re-rendered on frame/time/
viewport/instance change or plugin redraw requests.
- Event forwarding: picture-area pointer maps through the contain-fit
letterbox inverse to OFX pen coordinates (pen_motion/down/up);
Keystroke to OFX key symbols (ASCII, navigation, F1-F35) for
key_down/up; a 50ms idle pump; global shortcut consumption keeps
precedence.
- e2e with the real test plugin: lifecycle marker assertions, pen/key
event records, and macOS GL overlay compositing verified (265 tests
green incl. gpui_widgets viewer suite).
- 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.
With the C ABI facade (oakengine) retired, the frozen-ABI rationale is
gone. UndoCommand now boxes a Send Command trait (new/from_closures/
multi), dropping OakUndoCommandVtable, the userdata trampolines, the
refcount shell, the handle module, and all undostack_* handle exports.
The global facade loses its raw-pointer out-params (can_undo/can_redo
return bool, command_name returns String). oaktimeline/oaknode/
oakplugin/oaktask construct commands directly via UndoCommand::new.
oakundo src is now free of unsafe; behavior (ordering, idempotence,
done flags, groups, observers, 200-row cap) is unchanged and pinned by
the rewritten tests.
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.
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).
- 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)