Files
oak-editor/crates/oakplugin/src/suites/progress.rs
T
Mike-Solar 2db1615453 feat(oakplugin): wire OpenFX plugins into the node graph and renderer
- oaknode: dynamic node factory registration, PluginNode value model
  pushing PluginJobPayload, traverser texture passthrough for texture
  inputs, type-stamped RefBox::get_checked.
- oakrender: PluginExecutor dependency-inversion slot; eval resolves
  and executes plugin jobs, purple frame on failure.
- oakplugin: node_factory with full OFX param -> node input
  translation (15 types, color semantics heuristic, combo ordering,
  secret/ui_group/ui_page, clip inputs), plugin instance registry,
  render executor + duplicator installation, progress reporter and
  active-viewer provider injection points, U8/U16/F16 input
  conversion with NaN scrubbing, in-place output frame writeback fix.
- gl_bridge.rs documents the wgpu<->GL interop spike: Metal-first on
  macOS rules out wgpu-hal GL interop; offscreen GL context deferred.

End-to-end tests cover registration, param translation, CPU render
pixel assertions, identity passthrough and NaN fallback.
2026-08-18 17:15:13 +08:00

202 lines
6.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! OfxProgressSuite v1/v2:进度 → 当前渲染的
//! [`crate::progress::ProgressReporter`]。
//!
//! 定位方式:TLSrender 驱动在调用插件 action 前
//! [`set_current`]action 返回后清除)——进度只在渲染期内有意义,
//! 不设全局状态(progress.rs 文档)。
//! 语义:update 返回 false(取消)→ kOfxStatReplyNo
//! HS: ofxhImageEffect.cpp gProgressSuite);无报告器(渲染外
//! 调用)→ 静默 OK。start/end 为括号,OK。
use std::ffi::{c_char, c_double, c_int, c_void};
use crate::progress::ProgressReporter;
use crate::suites::status;
thread_local! {
static CURRENT: std::cell::RefCell<Option<ProgressReporter>> = const { std::cell::RefCell::new(None) };
}
/// 设置/清除当前报告器(render 驱动调用;公开:render 驱动在
/// host 层,测试也直接注入)。
pub fn set_current(reporter: Option<ProgressReporter>) {
CURRENT.with(|c| *c.borrow_mut() = reporter);
}
/// 读取当前报告器的取消状态(image effect suite 的 abort 用)。
pub(crate) fn is_cancelled() -> bool {
CURRENT.with(|c| c.borrow().as_ref().is_some_and(|r| r.is_cancelled()))
}
/// 进度更新:取消 → kOfxStatReplyNo;无报告器/未取消 → OK。
fn progress_update(progress: f64) -> c_int {
let cancelled = CURRENT.with(|c| match c.borrow().as_ref() {
Some(r) => !r.update(progress),
None => false,
});
if cancelled {
status::REPLY_NO
} else {
status::OK
}
}
/// 公共入口模板:panic 兜底。
fn caught(f: impl FnOnce() -> c_int) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(status::FAILED)
}
/// 函数表布局(OfxProgressSuiteV1)。
#[repr(C)]
pub struct ProgressSuiteV1 {
/// progressStart
pub start: unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int,
/// progressUpdate(返回非 kOfxStatOK 即取消)
pub update: unsafe extern "C" fn(*mut c_void, c_double) -> c_int,
/// progressEnd
pub end: unsafe extern "C" fn(*mut c_void) -> c_int,
}
/// 函数表布局(OfxProgressSuiteV2start 带 label + message)。
#[repr(C)]
pub struct ProgressSuiteV2 {
/// progressStart
pub start: unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int,
/// progressUpdate
pub update: unsafe extern "C" fn(*mut c_void, c_double) -> c_int,
/// progressEnd
pub end: unsafe extern "C" fn(*mut c_void) -> c_int,
}
/// C 字符串解码(空指针/非法 UTF-8 → 空串)。
unsafe fn decode<'a>(p: *const c_char) -> &'a str {
if p.is_null() {
return "";
}
unsafe { std::ffi::CStr::from_ptr(p) }
.to_str()
.unwrap_or("")
}
/// progressStart:括号起点;携 labelv2 另携 message)经
/// [`crate::progress`] 工厂现造 UI 报告器(无工厂/已有报告器时
/// no-op),状态恒 OK。
unsafe extern "C" fn progress_start_v1(_handle: *mut c_void, label: *const c_char) -> c_int {
caught(|| {
let label = unsafe { decode(label) }.to_string();
CURRENT.with(|c| {
if let Some(r) = c.borrow().as_ref() {
r.install_ui(&label, "");
}
});
status::OK
})
}
unsafe extern "C" fn progress_update_v1(_handle: *mut c_void, progress: c_double) -> c_int {
caught(|| progress_update(progress))
}
unsafe extern "C" fn progress_end_v1(_handle: *mut c_void) -> c_int {
caught(|| status::OK)
}
unsafe extern "C" fn progress_start_v2(
_handle: *mut c_void,
label: *const c_char,
message: *const c_char,
) -> c_int {
caught(|| {
let label = unsafe { decode(label) }.to_string();
let message = unsafe { decode(message) }.to_string();
CURRENT.with(|c| {
if let Some(r) = c.borrow().as_ref() {
r.install_ui(&label, &message);
}
});
status::OK
})
}
unsafe extern "C" fn progress_update_v2(_handle: *mut c_void, progress: c_double) -> c_int {
caught(|| progress_update(progress))
}
unsafe extern "C" fn progress_end_v2(_handle: *mut c_void) -> c_int {
caught(|| status::OK)
}
/// 静态函数表实例(v1)。
pub fn suite_v1() -> &'static ProgressSuiteV1 {
static SUITE: std::sync::OnceLock<ProgressSuiteV1> = std::sync::OnceLock::new();
SUITE.get_or_init(|| ProgressSuiteV1 {
start: progress_start_v1,
update: progress_update_v1,
end: progress_end_v1,
})
}
/// 静态函数表实例(v2)。
pub fn suite_v2() -> &'static ProgressSuiteV2 {
static SUITE: std::sync::OnceLock<ProgressSuiteV2> = std::sync::OnceLock::new();
SUITE.get_or_init(|| ProgressSuiteV2 {
start: progress_start_v2,
update: progress_update_v2,
end: progress_end_v2,
})
}
#[cfg(test)]
mod tests {
use super::*;
/// 取消型出口(头文件契约:非 0 = 中止)。
unsafe extern "C" fn cancel_cb(_p: f64, _userdata: *mut c_void) -> c_int {
1
}
/// 静默:update 恒 OK;带取消回调:update → REPLY_NO 且 abort
/// 查询为真(粘滞)。
#[test]
fn update_cancel_and_abort() {
// 无报告器(渲染外):静默 OK。
set_current(None);
let s = suite_v1();
unsafe {
assert_eq!((s.update)(std::ptr::null_mut(), 0.5), status::OK);
assert_eq!(
(s.start)(std::ptr::null_mut(), std::ptr::null()),
status::OK
);
assert_eq!((s.end)(std::ptr::null_mut()), status::OK);
}
// 带取消回调:update → REPLY_NOis_cancelled 为真。
set_current(Some(unsafe {
ProgressReporter::new(cancel_cb, std::ptr::null_mut())
}));
unsafe {
assert_eq!((s.update)(std::ptr::null_mut(), 0.5), status::REPLY_NO);
}
assert!(is_cancelled());
set_current(None);
assert!(!is_cancelled());
}
}