feat(engine): clip move, clip effect_input, mandatory static FFmpeg

- 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)
This commit is contained in:
2026-08-11 23:04:48 +08:00
parent a209f63d52
commit 18ff60f147
351 changed files with 32880 additions and 15939 deletions
+35 -29
View File
@@ -201,13 +201,13 @@ pub(crate) unsafe fn set_input_undoable(
#[cfg(not(feature = "test-stubs"))]
{
unsafe {
oaknode::ffi::node::oaknode_node_set_input_undoable(
node,
input,
value as *const oaknode::value::OakNodeValue,
out,
)
}
oaknode::ffi::node::oaknode_node_set_input_undoable(
node,
input,
value as *const oaknode::value::OakNodeValue,
out,
)
}
}
}
@@ -229,13 +229,8 @@ pub(crate) unsafe fn set_input_string_undoable(
#[cfg(not(feature = "test-stubs"))]
{
unsafe {
oaknode::ffi::node::oaknode_node_set_input_string_undoable(
node,
input,
value,
out,
)
}
oaknode::ffi::node::oaknode_node_set_input_string_undoable(node, input, value, out)
}
}
}
@@ -313,22 +308,20 @@ pub mod stub {
pub(crate) fn apply(node: usize, input: &str, value: Value, string: Option<String>) {
let mut m = lock();
if let Some(n) = m.get_mut(&node) {
n.insert(
input.to_string(),
StubInput {
value,
string,
},
);
n.insert(input.to_string(), StubInput { value, string });
}
}
/// 读输入当前值(命令创建时取 prev)。
pub(crate) fn current(node: usize, input: &str) -> StubInput {
lock().get(&node).and_then(|n| n.get(input)).cloned().unwrap_or(StubInput {
value: Value::default(),
string: None,
})
lock()
.get(&node)
.and_then(|n| n.get(input))
.cloned()
.unwrap_or(StubInput {
value: Value::default(),
string: None,
})
}
pub(super) unsafe fn node_from_identity_impl(id: usize) -> NodeHandle {
@@ -354,7 +347,10 @@ pub mod stub {
return -30001;
}
let id = node.ctx as usize;
let name = unsafe { CStr::from_ptr(input) }.to_str().map_err(|_| ()).unwrap_or_default();
let name = unsafe { CStr::from_ptr(input) }
.to_str()
.map_err(|_| ())
.unwrap_or_default();
// 输入名必须已存在(node.h: 未知输入 id → OAKNODE_E_NOT_FOUND)。
if !lock().get(&id).is_some_and(|n| n.contains_key(name)) {
return -30004;
@@ -376,12 +372,17 @@ pub mod stub {
return -30001;
}
let id = node.ctx as usize;
let name = unsafe { CStr::from_ptr(input) }.to_str().map_err(|_| ()).unwrap_or_default();
let name = unsafe { CStr::from_ptr(input) }
.to_str()
.map_err(|_| ())
.unwrap_or_default();
// 输入名必须已存在(node.h: 未知输入 id → OAKNODE_E_NOT_FOUND)。
if !lock().get(&id).is_some_and(|n| n.contains_key(name)) {
return -30004;
}
let next = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
let next = unsafe { CStr::from_ptr(value) }
.to_string_lossy()
.into_owned();
let prev = current(id, &name).string.unwrap_or_default();
let h = crate::bridge::undo::stub::create_string(
id,
@@ -408,7 +409,12 @@ mod tests {
unsafe {
assert!(node_from_identity(0xDEAD).is_null(), "未登记身份 → 空句柄");
assert_eq!(
set_input_undoable(NodeHandle::null(), std::ptr::null(), std::ptr::null(), &mut cmd),
set_input_undoable(
NodeHandle::null(),
std::ptr::null(),
std::ptr::null(),
&mut cmd
),
-30001
);
assert_eq!(
+89 -24
View File
@@ -39,7 +39,6 @@ use std::ffi::c_void;
/// oakrender crate's struct (identical layout; include/render/renderer.h:78).
pub type VideoParams = oakrender::ffi::OakRenderVideoParams;
/// olive::PixelFormat::Format 的 f32 值。
pub const PIXEL_FORMAT_F32: i32 = 4;
/// olive::PixelFormat::Format 的 u8 值。
@@ -293,7 +292,9 @@ pub(crate) unsafe fn texture_download(
}
#[cfg(not(feature = "test-stubs"))]
{
unsafe { oakrender::ffi::renderer::oakrender_display_texture_download(texture, pixels, linesize) }
unsafe {
oakrender::ffi::renderer::oakrender_display_texture_download(texture, pixels, linesize)
}
}
}
@@ -337,7 +338,9 @@ pub(crate) unsafe fn texture_upload(
}
#[cfg(not(feature = "test-stubs"))]
{
unsafe { oakrender::ffi::renderer::oakrender_display_texture_upload(texture, pixels, linesize) }
unsafe {
oakrender::ffi::renderer::oakrender_display_texture_upload(texture, pixels, linesize)
}
}
}
@@ -605,7 +608,8 @@ pub mod stub {
if row_bytes == src_linesize && src_linesize == dst_linesize {
dst[d..d + row_bytes].copy_from_slice(&src[s..s + row_bytes]);
} else {
dst[d..d + row_bytes].copy_from_slice(&src[s..s + row_bytes.min(src.len().saturating_sub(s))]);
dst[d..d + row_bytes]
.copy_from_slice(&src[s..s + row_bytes.min(src.len().saturating_sub(s))]);
}
}
}
@@ -660,8 +664,7 @@ pub mod stub {
let f = frame_of(&mut s, frame.ctx as usize);
let len = f.params.width as usize
* f.params.height as usize
* 4
* bytes_per_pixel(f.params.format);
* 4 * bytes_per_pixel(f.params.format);
f.data.resize(len, 0);
0
}
@@ -680,11 +683,15 @@ pub mod stub {
// ---- 渲染器族桩 ---------------------------------------------------------
pub(super) unsafe fn renderer_create_dynamic(backend: *const std::ffi::c_char) -> RendererHandle {
pub(super) unsafe fn renderer_create_dynamic(
backend: *const std::ffi::c_char,
) -> RendererHandle {
if backend.is_null() {
return RendererHandle::null();
}
let id = unsafe { std::ffi::CStr::from_ptr(backend) }.to_str().unwrap_or("");
let id = unsafe { std::ffi::CStr::from_ptr(backend) }
.to_str()
.unwrap_or("");
if id != "opengl" {
return RendererHandle::null();
}
@@ -734,16 +741,29 @@ pub mod stub {
// linesize 字节/行(renderer.h 的 bytes 契约;0 → 紧凑行)。
let bpp = bytes_per_pixel(params.format);
let row_bytes = (params.width as usize) * 4 * bpp;
let src_linesize = if linesize > 0 { linesize as usize } else { row_bytes };
let src = unsafe { std::slice::from_raw_parts(pixels as *const u8, src_linesize * params.height as usize) };
copy_rows(src, src_linesize, &mut data, row_bytes, row_bytes.min(src_linesize), params.height as usize);
let src_linesize = if linesize > 0 {
linesize as usize
} else {
row_bytes
};
let src = unsafe {
std::slice::from_raw_parts(
pixels as *const u8,
src_linesize * params.height as usize,
)
};
copy_rows(
src,
src_linesize,
&mut data,
row_bytes,
row_bytes.min(src_linesize),
params.height as usize,
);
}
s.gl_textures.push(StubGlTexture {
id,
frame: StubFrame {
params,
data,
},
frame: StubFrame { params, data },
});
magic_handle(GL_TEX_BASE + id as usize)
}
@@ -784,10 +804,26 @@ pub mod stub {
}
let bpp = bytes_per_pixel(t.frame.params.format);
let row_bytes = (t.frame.params.width as usize) * 4 * bpp;
let dst_linesize = if linesize > 0 { linesize as usize } else { row_bytes };
let dst = unsafe { std::slice::from_raw_parts_mut(pixels as *mut u8, dst_linesize * t.frame.params.height as usize) };
let dst_linesize = if linesize > 0 {
linesize as usize
} else {
row_bytes
};
let dst = unsafe {
std::slice::from_raw_parts_mut(
pixels as *mut u8,
dst_linesize * t.frame.params.height as usize,
)
};
let src = t.frame.data.clone();
copy_rows(&src, row_bytes, dst, dst_linesize, row_bytes, t.frame.params.height as usize);
copy_rows(
&src,
row_bytes,
dst,
dst_linesize,
row_bytes,
t.frame.params.height as usize,
);
0
}
@@ -808,10 +844,26 @@ pub mod stub {
}
let bpp = bytes_per_pixel(t.frame.params.format);
let row_bytes = (t.frame.params.width as usize) * 4 * bpp;
let src_linesize = if linesize > 0 { linesize as usize } else { row_bytes };
let src = unsafe { std::slice::from_raw_parts(pixels as *const u8, src_linesize * t.frame.params.height as usize) };
let src_linesize = if linesize > 0 {
linesize as usize
} else {
row_bytes
};
let src = unsafe {
std::slice::from_raw_parts(
pixels as *const u8,
src_linesize * t.frame.params.height as usize,
)
};
let mut data = vec![0u8; tight_len(&t.frame.params)];
copy_rows(src, src_linesize, &mut data, row_bytes, row_bytes.min(src_linesize), t.frame.params.height as usize);
copy_rows(
src,
src_linesize,
&mut data,
row_bytes,
row_bytes.min(src_linesize),
t.frame.params.height as usize,
);
t.frame.data = data;
0
}
@@ -849,10 +901,23 @@ pub mod stub {
};
let bpp = bytes_per_pixel(req.format);
let row_bytes = (req.width as usize) * 4 * bpp;
let dst_linesize = if linesize > 0 { linesize as usize } else { row_bytes };
let out = unsafe { std::slice::from_raw_parts_mut(dst as *mut u8, dst_linesize * req.height as usize) };
let dst_linesize = if linesize > 0 {
linesize as usize
} else {
row_bytes
};
let out = unsafe {
std::slice::from_raw_parts_mut(dst as *mut u8, dst_linesize * req.height as usize)
};
let src = t.frame.data.clone();
copy_rows(&src, row_bytes, out, dst_linesize, row_bytes, req.height as usize);
copy_rows(
&src,
row_bytes,
out,
dst_linesize,
row_bytes,
req.height as usize,
);
0
}
}
+4 -1
View File
@@ -322,7 +322,10 @@ pub mod stub {
0
}
pub(super) unsafe fn command_multi_add_child_impl(multi: CommandHandle, child: CommandHandle) -> i32 {
pub(super) unsafe fn command_multi_add_child_impl(
multi: CommandHandle,
child: CommandHandle,
) -> i32 {
if multi.is_null() || child.is_null() {
return -40001;
}
+13 -13
View File
@@ -137,11 +137,7 @@ impl ClipInstance {
/// 挂接输入纹理(oaknode 侧 clip 输入值变化时由 param/render 桥
/// 调用)。`time` 用于多帧纹理选择。空句柄断开。
pub fn set_input_texture(
&self,
texture: crate::bridge::render::TextureHandle,
_time: f64,
) {
pub fn set_input_texture(&self, texture: crate::bridge::render::TextureHandle, _time: f64) {
let mut slot = self.input_texture.lock().unwrap_or_else(|e| e.into_inner());
if texture.is_null() {
*slot = None;
@@ -153,11 +149,7 @@ impl ClipInstance {
/// 挂接输出纹理(render 驱动创建并经句柄传入;C++
/// `setOutputTexture` 的 phase 1 单槽版)。`time` 用于多帧纹理
/// 选择(`// [P2]`)。空句柄断开。
pub fn set_output_texture(
&self,
texture: crate::bridge::render::TextureHandle,
_time: f64,
) {
pub fn set_output_texture(&self, texture: crate::bridge::render::TextureHandle, _time: f64) {
let mut slot = self
.output_texture
.lock()
@@ -215,12 +207,18 @@ impl ClipInstance {
}
let (w, h) = (params.width as f64, params.height as f64);
// 分量按协商结果(getClipPreferences 已写入 clip.props)。
let components = components_from_props(&self.props).unwrap_or(crate::image::Components::Rgba);
let components =
components_from_props(&self.props).unwrap_or(crate::image::Components::Rgba);
let mut image = crate::image::Image::allocate(
crate::image::BitDepth::Float,
components,
OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h },
OfxRectD {
x1: 0.0,
y1: 0.0,
x2: w,
y2: h,
},
);
let src = unsafe { crate::bridge::render::frame_data(frame) };
if src.is_null() {
@@ -318,6 +316,8 @@ impl ClipInstance {
/// (time_base)——随 renderer 桥落地。
pub fn frame_range(&self) -> crate::error::Result<OfxRangeD> {
let _ = OfxRangeD::default();
Err(crate::error::Error::Failed("frame_range 待 renderer 桥".into()))
Err(crate::error::Error::Failed(
"frame_range 待 renderer 桥".into(),
))
}
}
+9 -6
View File
@@ -82,10 +82,7 @@ impl ClipDescriptor {
// ofxColour(M11 §4):clip 色彩空间属性族。Colourspace 由宿主
// 在实例化时写入(输入 clip = 工作空间 ACEScg);Preferred 由
// 插件在 GetClipPreferences 写(宿主侧预置空数组)。
props.set_one(
crate::host::PROP_CLIP_COLOURSPACE,
Value::String(cs("")),
);
props.set_one(crate::host::PROP_CLIP_COLOURSPACE, Value::String(cs("")));
props.define(crate::host::PROP_CLIP_PREFERRED_COLOURSPACES, vec![]);
Self {
props,
@@ -120,11 +117,17 @@ impl EffectDescriptor {
/// 按名找 clip。
pub fn clip(&self, name: &str) -> Option<&ClipDescriptor> {
self.clips.iter().find(|c| c.name == name).map(|b| b.as_ref())
self.clips
.iter()
.find(|c| c.name == name)
.map(|b| b.as_ref())
}
/// 按名找参数定义。
pub fn param(&self, name: &str) -> Option<&ParamDef> {
self.params.iter().find(|p| p.name == name).map(|b| b.as_ref())
self.params
.iter()
.find(|p| p.name == name)
.map(|b| b.as_ref())
}
}
+63 -24
View File
@@ -232,11 +232,7 @@ pub unsafe extern "C" fn oakplugin_host_plugin_label(
/// 消息回调类型(host.h oakplugin_message_fn:
/// `(type, message, userdata) -> OAKPLUGIN_MESSAGE_ANSWER_NO/YES`)。
pub type MessageFn = unsafe extern "C" fn(
*const c_char,
*const c_char,
*mut c_void,
) -> c_int;
pub type MessageFn = unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void) -> c_int;
/// `oakplugin_host_set_message_handler`:facade 注册消息出口
/// (message suite 的最终落点)。
@@ -257,7 +253,9 @@ pub(crate) type InstanceRef = std::sync::Arc<crate::handle::RefBox<crate::instan
/// 实例句柄 → 实例的取回。
fn instance_of(h: &CHandle) -> crate::error::Result<InstanceRef> {
unsafe { get::<InstanceRef>(h) }.cloned().ok_or(crate::error::Error::Invalid)
unsafe { get::<InstanceRef>(h) }
.cloned()
.ok_or(crate::error::Error::Invalid)
}
/// `oakplugin_instance_create`:按插件标识创建实例(计数 1;
@@ -295,10 +293,7 @@ pub unsafe extern "C" fn oakplugin_instance_free(instance: *mut CHandle) {
/// node_value → ParamValue(按参数类型解释;字符串走专用函数)。
/// pub(crate):render 驱动的参数覆盖(apply_param_overrides 移植)
/// 复用同一转换。
pub(crate) fn node_value_to_param(
v: &OakNodeValue,
ofx_type: &str,
) -> Option<ParamValue> {
pub(crate) fn node_value_to_param(v: &OakNodeValue, ofx_type: &str) -> Option<ParamValue> {
match ofx_type {
crate::param::TYPE_DOUBLE => {
if v.r#type == node_value_type::FLOAT {
@@ -329,7 +324,11 @@ pub(crate) fn node_value_to_param(
}
}
crate::param::TYPE_INTEGER2D | crate::param::TYPE_INTEGER3D => {
let dim = if ofx_type == crate::param::TYPE_INTEGER2D { 2 } else { 3 };
let dim = if ofx_type == crate::param::TYPE_INTEGER2D {
2
} else {
3
};
let a = [v.f[0] as i32, v.f[1] as i32, v.f[2] as i32];
Some(ParamValue::Int(a, dim))
}
@@ -447,9 +446,13 @@ pub unsafe extern "C" fn oakplugin_instance_set_param(
.to_str()
.map_err(|_| crate::error::Error::Invalid)?;
let v = unsafe { &*value };
let p = inst.value.params.find(name).ok_or(crate::error::Error::NotFound)?;
let param_value = node_value_to_param(v, &p.def.ofx_type)
.ok_or(crate::error::Error::Invalid)?;
let p = inst
.value
.params
.find(name)
.ok_or(crate::error::Error::NotFound)?;
let param_value =
node_value_to_param(v, &p.def.ofx_type).ok_or(crate::error::Error::Invalid)?;
p.set_ofx(param_value);
Ok(())
})
@@ -470,7 +473,11 @@ pub unsafe extern "C" fn oakplugin_instance_get_param(
let name = unsafe { CStr::from_ptr(param) }
.to_str()
.map_err(|_| crate::error::Error::Invalid)?;
let p = inst.value.params.find(name).ok_or(crate::error::Error::NotFound)?;
let p = inst
.value
.params
.find(name)
.ok_or(crate::error::Error::NotFound)?;
let v = p.get();
let node = param_to_node_value(&v).ok_or(crate::error::Error::Invalid)?;
unsafe { *out = node };
@@ -494,7 +501,11 @@ pub unsafe extern "C" fn oakplugin_instance_set_param_string(
.to_str()
.map_err(|_| crate::error::Error::Invalid)?;
let v = unsafe { CStr::from_ptr(value) }.to_bytes();
let p = inst.value.params.find(name).ok_or(crate::error::Error::NotFound)?;
let p = inst
.value
.params
.find(name)
.ok_or(crate::error::Error::NotFound)?;
match p.def.ofx_type.as_str() {
crate::param::TYPE_STRING => {
p.set_ofx(ParamValue::String(CString::new(v).unwrap()));
@@ -525,7 +536,11 @@ pub unsafe extern "C" fn oakplugin_instance_get_param_string(
let name = unsafe { CStr::from_ptr(param) }
.to_str()
.map_err(|_| crate::error::Error::Invalid)?;
let p = inst.value.params.find(name).ok_or(crate::error::Error::NotFound)?;
let p = inst
.value
.params
.find(name)
.ok_or(crate::error::Error::NotFound)?;
match p.get() {
ParamValue::String(s) | ParamValue::StrChoice(s) => {
Ok(s.to_string_lossy().into_owned())
@@ -596,11 +611,23 @@ pub unsafe extern "C" fn oakplugin_instance_render(
let output = std::sync::Arc::new(crate::image::Image::allocate(
crate::image::BitDepth::Float,
components,
crate::instance::OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h },
crate::instance::OfxRectD {
x1: 0.0,
y1: 0.0,
x2: w,
y2: h,
},
));
let window = crate::instance::OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h };
let window = crate::instance::OfxRectD {
x1: 0.0,
y1: 0.0,
x2: w,
y2: h,
};
let scale = crate::instance::RenderScale { x: 1.0, y: 1.0 };
let render = inst.value.render(time_seconds, scale, window, output.clone());
let render = inst
.value
.render(time_seconds, scale, window, output.clone());
// 结果写回输出帧(行跨度感知;M11 §4 修复:phase 1 假设紧凑
// 行并泄漏 frame 句柄——统一走驱动装配路径)。
let result = match render {
@@ -671,7 +698,10 @@ pub unsafe extern "C" fn oakplugin_instance_render_begin_sequence(
if inst.value.cancel.load(std::sync::atomic::Ordering::Relaxed) {
return Err(crate::error::Error::Failed("已取消".into()));
}
let range = crate::instance::OfxRangeD { min: start_time, max: end_time };
let range = crate::instance::OfxRangeD {
min: start_time,
max: end_time,
};
inst.value.begin_sequence_render(range)
})
}
@@ -687,7 +717,10 @@ pub unsafe extern "C" fn oakplugin_instance_render_end_sequence(
) -> c_int {
guard(|| {
let inst = instance_of(&instance)?;
let range = crate::instance::OfxRangeD { min: start_time, max: end_time };
let range = crate::instance::OfxRangeD {
min: start_time,
max: end_time,
};
inst.value.end_sequence_render(range)
})
}
@@ -779,7 +812,11 @@ pub unsafe extern "C" fn oakplugin_instance_render_job(
effect_input_id,
inputs: job_inputs,
values: job_values,
renderer: if renderer.is_null() { None } else { Some(renderer) },
renderer: if renderer.is_null() {
None
} else {
Some(renderer)
},
clear_destination: clear_destination != 0,
interactive: interactive != 0,
};
@@ -1086,7 +1123,9 @@ pub unsafe extern "C" fn oakplugin_instance_clip_info(
None => return crate::error::OAKPLUGIN_E_NOT_FOUND,
};
unsafe { *optional = prop_int(&clip.props, crate::descriptor::CLIP_OPTIONAL) };
string_query(label, label_size, || Ok(prop_str(&clip.props, crate::param::PROP_LABEL)))
string_query(label, label_size, || {
Ok(prop_str(&clip.props, crate::param::PROP_LABEL))
})
})
}
+64 -26
View File
@@ -448,7 +448,11 @@ impl PluginCache {
paths.push(PathBuf::from("../OFX/Plugins"));
paths.push(PathBuf::from("../share/olive/ofx/Plugins"));
paths.push(PathBuf::from("../lib/olive/ofx/Plugins"));
for var in ["OLIVE_OFX_PLUGIN_PATH", "OLIVE_PLUGIN_PATH", "OFX_PLUGIN_PATH"] {
for var in [
"OLIVE_OFX_PLUGIN_PATH",
"OLIVE_PLUGIN_PATH",
"OFX_PLUGIN_PATH",
] {
if let Ok(raw) = std::env::var(var) {
paths.extend(raw.split(':').filter(|p| !p.is_empty()).map(PathBuf::from));
}
@@ -519,11 +523,7 @@ impl PluginCache {
///
/// # Safety
/// `handle` 必须是有效 dlopen 句柄。
unsafe fn collect_plugins(
&self,
handle: *mut c_void,
bundle: &Path,
) -> Vec<Arc<Plugin>> {
unsafe fn collect_plugins(&self, handle: *mut c_void, bundle: &Path) -> Vec<Arc<Plugin>> {
let get_no: unsafe extern "C" fn() -> c_int =
match unsafe { dlsym_fn(handle, "OfxGetNumberOfPlugins") } {
Some(f) => f,
@@ -571,7 +571,9 @@ impl PluginCache {
let api = if ofx_ref.plugin_api.is_null() {
return None;
} else {
unsafe { CStr::from_ptr(ofx_ref.plugin_api) }.to_str().ok()?
unsafe { CStr::from_ptr(ofx_ref.plugin_api) }
.to_str()
.ok()?
};
if api != "OfxImageEffectPluginAPI" || ofx_ref.api_version != 1 {
return None;
@@ -609,10 +611,7 @@ impl PluginCache {
let empty = PropertySet::new();
// load(HS: ofxhImageEffectAPI.cpp:158-165;OK/ReplyDefault 接受)。
let stat = unsafe {
plugin
.call_action(ACTION_LOAD, std::ptr::null_mut(), &empty, &empty)
};
let stat = unsafe { plugin.call_action(ACTION_LOAD, std::ptr::null_mut(), &empty, &empty) };
if stat != status::OK && stat != status::REPLY_DEFAULT {
return None;
}
@@ -675,10 +674,16 @@ impl PluginCache {
/// 卸载全部(shutdown 路径)。`scanned_paths` 一并清空——
/// 否则再 init 后的扫描会被去重短路(缓存已空但路径仍在)。
pub(crate) fn unload_all(&self) {
self.plugins.lock().unwrap_or_else(|e| e.into_inner()).clear();
self.plugins
.lock()
.unwrap_or_else(|e| e.into_inner())
.clear();
let mut bins = self.binaries.lock().unwrap_or_else(|e| e.into_inner());
bins.clear(); // Drop → dlclose
self.scanned_paths.lock().unwrap_or_else(|e| e.into_inner()).clear();
self.scanned_paths
.lock()
.unwrap_or_else(|e| e.into_inner())
.clear();
}
}
@@ -716,15 +721,27 @@ fn init_descriptor_props(props: &PropertySet, bundle: &Path) {
props.define(PROP_SUPPORTED_CONTEXTS, vec![]);
props.set_one("OfxImageEffectPluginPropGrouping", Value::String(cs("")));
props.set_one("OfxImageEffectPluginPropSingleInstance", Value::Int(0));
props.set_one("OfxImageEffectPluginRenderThreadSafety", Value::String(cs("OfxImageEffectRenderInstanceSafe")));
props.set_one(
"OfxImageEffectPluginRenderThreadSafety",
Value::String(cs("OfxImageEffectRenderInstanceSafe")),
);
props.set_one("OfxImageEffectPluginPropHostFrameThreading", Value::Int(1));
props.set_one("OfxImageEffectPluginPropOverlayInteractV1", Value::Pointer(std::ptr::null_mut()));
props.set_one(
"OfxImageEffectPluginPropOverlayInteractV1",
Value::Pointer(std::ptr::null_mut()),
);
props.set_one("OfxImageEffectPropSupportsMultiResolution", Value::Int(1));
props.set_one(PROP_SUPPORTS_TILES, Value::Int(1));
props.set_one("OfxImageEffectPropTemporalClipAccess", Value::Int(0));
props.define("OfxImageEffectPropSupportedPixelDepths", vec![]);
props.set_one("OfxImageEffectPluginPropFieldRenderTwiceAlways", Value::Int(1));
props.set_one("OfxImageEffectPropSupportsMultipleClipDepths", Value::Int(0));
props.set_one(
"OfxImageEffectPluginPropFieldRenderTwiceAlways",
Value::Int(1),
);
props.set_one(
"OfxImageEffectPropSupportsMultipleClipDepths",
Value::Int(0),
);
props.set_one("OfxImageEffectPropSupportsMultipleClipPARs", Value::Int(0));
props.define("OfxImageEffectPropClipPreferencesSlaveParam", vec![]);
props.set_one(PROP_SEQUENTIAL, Value::Int(0));
@@ -840,8 +857,9 @@ impl Host {
&ctx_desc.props as *const PropertySet,
crate::suites::tag::DESCRIPTOR,
);
let stat =
unsafe { plugin.call_action(ACTION_DESCRIBE_IN_CONTEXT, desc_handle, &in_args, &in_args) };
let stat = unsafe {
plugin.call_action(ACTION_DESCRIBE_IN_CONTEXT, desc_handle, &in_args, &in_args)
};
if stat != status::OK && stat != status::REPLY_DEFAULT {
return Err(crate::error::Error::Failed(format!(
"describeInContext 失败:{stat}"
@@ -930,7 +948,9 @@ impl Host {
if let Some(arc) = w.upgrade() {
// 置 destroyed 标记后再通知:实例随后的 Drop 不得再
// 调已卸载的插件入口(二次 destroyInstance → 悬垂)。
arc.value.destroyed.store(true, std::sync::atomic::Ordering::Relaxed);
arc.value
.destroyed
.store(true, std::sync::atomic::Ordering::Relaxed);
arc.value.notify_destroy();
arc.value
.cancel
@@ -954,8 +974,14 @@ impl Host {
fn init_host_props(props: &PropertySet) {
props.set_one("OfxPropName", Value::String(cs("Oak Video Editor")));
props.set_one("OfxPropLabel", Value::String(cs("Oak Video Editor")));
props.set_one("OfxPropVersionLabel", Value::String(cs(env!("CARGO_PKG_VERSION"))));
props.define("OfxPropVersion", vec![Value::Int(0), Value::Int(0), Value::Int(0)]);
props.set_one(
"OfxPropVersionLabel",
Value::String(cs(env!("CARGO_PKG_VERSION"))),
);
props.define(
"OfxPropVersion",
vec![Value::Int(0), Value::Int(0), Value::Int(0)],
);
// 能力宣告:协商的宿主侧输入(phase 1 全链路 F32+RGBA)。
props.define(
"OfxImageEffectPropSupportedPixelDepths",
@@ -985,10 +1011,22 @@ fn init_host_props(props: &PropertySet) {
fn init_instance_props(props: &PropertySet, instance: &Instance) {
props.set_one(PROP_TYPE, Value::String(cs("OfxTypeImageEffectInstance")));
props.set_one(PROP_CONTEXT, Value::String(cs(&instance.context)));
props.set_one(PROP_PLUGIN_HANDLE, Value::Pointer(instance.plugin.ofx_plugin));
props.define(PROP_PROJECT_SIZE, vec![Value::Double(1920.0), Value::Double(1080.0)]);
props.define(PROP_PROJECT_OFFSET, vec![Value::Double(0.0), Value::Double(0.0)]);
props.define(PROP_PROJECT_EXTENT, vec![Value::Double(1920.0), Value::Double(1080.0)]);
props.set_one(
PROP_PLUGIN_HANDLE,
Value::Pointer(instance.plugin.ofx_plugin),
);
props.define(
PROP_PROJECT_SIZE,
vec![Value::Double(1920.0), Value::Double(1080.0)],
);
props.define(
PROP_PROJECT_OFFSET,
vec![Value::Double(0.0), Value::Double(0.0)],
);
props.define(
PROP_PROJECT_EXTENT,
vec![Value::Double(1920.0), Value::Double(1080.0)],
);
props.set_one(PROP_PROJECT_PAR, Value::Double(1.0));
props.set_one(PROP_EFFECT_DURATION, Value::Double(1.0));
props.set_one(PROP_SEQUENTIAL, Value::Int(0));
+2 -4
View File
@@ -184,10 +184,8 @@ impl Image {
K_IMAGE_PROP_DATA,
vec![Value::Pointer(img.data.as_mut_ptr() as *mut c_void)],
);
img.props.define(
K_IMAGE_PROP_ROW_BYTES,
vec![Value::Int(row_bytes as i32)],
);
img.props
.define(K_IMAGE_PROP_ROW_BYTES, vec![Value::Int(row_bytes as i32)]);
let b = |v: f64| Value::Int(v.round() as i32);
img.props.define(
K_IMAGE_PROP_BOUNDS,
+94 -49
View File
@@ -127,13 +127,15 @@ pub struct Instance {
/// (RefBox 归零时 Drop 触发——action 通知必须在对象析构前发出。)
impl Drop for Instance {
fn drop(&mut self) {
if !self.destroyed.swap(true, std::sync::atomic::Ordering::Relaxed) {
if !self
.destroyed
.swap(true, std::sync::atomic::Ordering::Relaxed)
{
self.notify_destroy();
}
crate::suites::param::unregister_params_of(&self.props as *const _ as usize);
crate::host::instance_registry().unregister(
&self.props as *const crate::property::PropertySet as usize,
);
crate::host::instance_registry()
.unregister(&self.props as *const crate::property::PropertySet as usize);
}
}
@@ -176,11 +178,7 @@ impl Instance {
/// 是否处于编辑事务内(param 桥回写据此决定打包成 multi)。
pub fn in_edit(&self) -> bool {
self.edit
.lock()
.unwrap_or_else(|e| e.into_inner())
.depth
> 0
self.edit.lock().unwrap_or_else(|e| e.into_inner()).depth > 0
}
/// 事务感知的 undo 提交(param 桥回写用;对应 C++
@@ -226,12 +224,12 @@ impl Instance {
/// 4. 回灌:per-clip 分量/位深/像素比写回 clip 实例属性,
/// 输出帧率/fielding/premult 记入实例(HS:1721-1740)。
pub fn get_clip_preferences(&self) -> crate::error::Result<ClipPreferences> {
use crate::property::Value;
use crate::host::{
ACTION_GET_CLIP_PREFERENCES, CLIP_PREF_COMPONENTS, CLIP_PREF_DEPTH, CLIP_PREF_PAR,
PROP_CONTINUOUS_SAMPLES, PROP_FIELD_ORDER, PROP_FRAME_RATE, PROP_FRAME_VARYING,
PROP_PREMULT,
};
use crate::property::Value;
// 1. clipPrefsStuffs(HS:1697-1706 的默认值)。
let out = PropertySet::new();
@@ -243,9 +241,18 @@ impl Instance {
// 2. per-clip 预置(HS:1717-1727;phase 1 全链路 F32+RGBA)。
for clip in &self.clips {
out.set_one(&crate::host::clip_pref_prop(CLIP_PREF_COMPONENTS, &clip.name), Value::String(CString::new("OfxImageComponentRGBA").unwrap()));
out.set_one(&crate::host::clip_pref_prop(CLIP_PREF_DEPTH, &clip.name), Value::String(CString::new("OfxBitDepthFloat").unwrap()));
out.set_one(&crate::host::clip_pref_prop(CLIP_PREF_PAR, &clip.name), Value::Double(1.0));
out.set_one(
&crate::host::clip_pref_prop(CLIP_PREF_COMPONENTS, &clip.name),
Value::String(CString::new("OfxImageComponentRGBA").unwrap()),
);
out.set_one(
&crate::host::clip_pref_prop(CLIP_PREF_DEPTH, &clip.name),
Value::String(CString::new("OfxBitDepthFloat").unwrap()),
);
out.set_one(
&crate::host::clip_pref_prop(CLIP_PREF_PAR, &clip.name),
Value::Double(1.0),
);
}
// 3. action。
@@ -301,10 +308,8 @@ impl Instance {
crate::image::K_IMAGE_EFFECT_PROP_PIXEL_DEPTH,
Value::String(CString::new(depth.clone()).unwrap()),
);
clip.props.set_one(
"OfxImagePropPixelAspectRatio",
Value::Double(par),
);
clip.props
.set_one("OfxImagePropPixelAspectRatio", Value::Double(par));
if clip.name == "Output" {
output_components = comps;
output_bit_depth = depth;
@@ -343,12 +348,15 @@ impl Instance {
time: f64,
scale: RenderScale,
) -> crate::error::Result<OfxRectD> {
use crate::property::Value;
use crate::host::{ACTION_GET_ROD, PROP_RENDER_SCALE, PROP_ROD, PROP_TIME};
use crate::property::Value;
let in_args = PropertySet::new();
in_args.set_one(PROP_TIME, Value::Double(time));
in_args.define(PROP_RENDER_SCALE, vec![Value::Double(scale.x), Value::Double(scale.y)]);
in_args.define(
PROP_RENDER_SCALE,
vec![Value::Double(scale.x), Value::Double(scale.y)],
);
// out args 预定义(HS 行为:宿主先建属性表,插件只管写——
// 缺失属性上插件的 propSet 会失败被忽略)。
let out = PropertySet::new();
@@ -387,12 +395,15 @@ impl Instance {
scale: RenderScale,
region: OfxRectD,
) -> crate::error::Result<Vec<OfxRectD>> {
use crate::property::Value;
use crate::host::{ACTION_GET_ROI, PROP_RENDER_SCALE, PROP_ROI, PROP_TIME};
use crate::property::Value;
let in_args = PropertySet::new();
in_args.set_one(PROP_TIME, Value::Double(time));
in_args.define(PROP_RENDER_SCALE, vec![Value::Double(scale.x), Value::Double(scale.y)]);
in_args.define(
PROP_RENDER_SCALE,
vec![Value::Double(scale.x), Value::Double(scale.y)],
);
in_args.define(
PROP_ROI,
vec![
@@ -446,15 +457,18 @@ impl Instance {
/// renderWindow + fieldToRender;out args = kOfxImageEffectPropIsIdentity
/// (输入 clip 名)+ kOfxPropTime(透传时间,可改)。
pub fn is_identity(&self, time: f64) -> crate::error::Result<Option<(f64, String)>> {
use crate::property::Value;
use crate::host::{
ACTION_IS_IDENTITY, PROP_FIELD_TO_RENDER, PROP_IS_IDENTITY, PROP_RENDER_SCALE,
PROP_RENDER_WINDOW, PROP_TIME,
};
use crate::property::Value;
let in_args = PropertySet::new();
in_args.set_one(PROP_TIME, Value::Double(time));
in_args.define(PROP_RENDER_SCALE, vec![Value::Double(1.0), Value::Double(1.0)]);
in_args.define(
PROP_RENDER_SCALE,
vec![Value::Double(1.0), Value::Double(1.0)],
);
in_args.define(
PROP_RENDER_WINDOW,
vec![
@@ -464,7 +478,10 @@ impl Instance {
Value::Double(0.0),
],
);
in_args.set_one(PROP_FIELD_TO_RENDER, Value::String(CString::new("OfxImageFieldBoth").unwrap()));
in_args.set_one(
PROP_FIELD_TO_RENDER,
Value::String(CString::new("OfxImageFieldBoth").unwrap()),
);
// out args 预定义:IsIdentity(String)+ Time(Double)。
let out = PropertySet::new();
out.set_one(PROP_IS_IDENTITY, Value::String(CString::new("").unwrap()));
@@ -479,7 +496,9 @@ impl Instance {
.call_action(ACTION_IS_IDENTITY, inst_handle, &in_args, &out)
};
if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT {
return Err(crate::error::Error::Failed(format!("isIdentity 失败:{stat}")));
return Err(crate::error::Error::Failed(format!(
"isIdentity 失败:{stat}"
)));
}
let identity = out.get(PROP_IS_IDENTITY, 0);
match identity {
@@ -500,17 +519,25 @@ impl Instance {
/// render action 的 in args 组装(CPU/GL 共用;GL 模式加
/// kOfxImageEffectPropOpenGLEnabled=1,ofxGPURender.h:117)。
fn render_in_args(time: f64, scale: RenderScale, window: OfxRectD, gl_enabled: bool) -> PropertySet {
use crate::property::Value;
fn render_in_args(
time: f64,
scale: RenderScale,
window: OfxRectD,
gl_enabled: bool,
) -> PropertySet {
use crate::host::{
PROP_FIELD_TO_RENDER, PROP_INTERACTIVE_RENDER, PROP_NO_SPATIAL_AWARENESS,
PROP_RENDER_QUALITY_DRAFT, PROP_RENDER_SCALE, PROP_RENDER_WINDOW,
PROP_SEQUENTIAL_RENDER, PROP_TIME,
};
use crate::property::Value;
let in_args = PropertySet::new();
in_args.set_one(PROP_TIME, Value::Double(time));
in_args.define(PROP_RENDER_SCALE, vec![Value::Double(scale.x), Value::Double(scale.y)]);
in_args.define(
PROP_RENDER_SCALE,
vec![Value::Double(scale.x), Value::Double(scale.y)],
);
in_args.define(
PROP_RENDER_WINDOW,
vec![
@@ -520,7 +547,10 @@ impl Instance {
Value::Double(window.y2),
],
);
in_args.set_one(PROP_FIELD_TO_RENDER, Value::String(CString::new("OfxImageFieldBoth").unwrap()));
in_args.set_one(
PROP_FIELD_TO_RENDER,
Value::String(CString::new("OfxImageFieldBoth").unwrap()),
);
in_args.set_one(PROP_SEQUENTIAL_RENDER, Value::Int(0));
in_args.set_one(PROP_INTERACTIVE_RENDER, Value::Int(0));
in_args.set_one(PROP_RENDER_QUALITY_DRAFT, Value::Int(0));
@@ -561,11 +591,7 @@ impl Instance {
.lock()
.unwrap_or_else(|e| e.into_inner())
.unwrap_or(OfxRangeD { min: 0.0, max: 0.0 });
crate::suites::set_render_ctx(Some(crate::suites::RenderCtx {
time,
scale,
range,
}));
crate::suites::set_render_ctx(Some(crate::suites::RenderCtx { time, scale, range }));
crate::suites::set_current_output(Some(output.clone()));
// 进度报告器(facade 回调 → Progress suite)。
@@ -641,11 +667,7 @@ impl Instance {
.lock()
.unwrap_or_else(|e| e.into_inner())
.unwrap_or(OfxRangeD { min: 0.0, max: 0.0 });
crate::suites::set_render_ctx(Some(crate::suites::RenderCtx {
time,
scale,
range,
}));
crate::suites::set_render_ctx(Some(crate::suites::RenderCtx { time, scale, range }));
// GL 纹理位深协商(插件 kOfxOpenGLPropPixelDepth → 管线 F32;
// 调用方已按协商门(render 驱动)决定走 GL)。
let gl_pixel_depth =
@@ -676,8 +698,12 @@ impl Instance {
let empty = PropertySet::new();
// attach(失败仍继续——插件可忽略;规范允许 ReplyDefault)。
let stat = unsafe {
self.plugin
.call_action(crate::host::ACTION_GL_CONTEXT_ATTACHED, inst_handle, &empty, &empty)
self.plugin.call_action(
crate::host::ACTION_GL_CONTEXT_ATTACHED,
inst_handle,
&empty,
&empty,
)
};
if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT {
crate::suites::set_gl_ctx(None);
@@ -696,8 +722,12 @@ impl Instance {
// detach(必须与 attach 配对;ofxGPURender.h:345-346)。
unsafe {
self.plugin
.call_action(crate::host::ACTION_GL_CONTEXT_DETACHED, inst_handle, &empty, &empty)
self.plugin.call_action(
crate::host::ACTION_GL_CONTEXT_DETACHED,
inst_handle,
&empty,
&empty,
)
};
// 兜底:插件遗漏 clipFreeTexture 的输入纹理在此释放。
@@ -707,7 +737,9 @@ impl Instance {
crate::suites::progress::set_current(None);
if stat != crate::suites::status::OK {
return Err(crate::error::Error::Failed(format!("GL render 失败:{stat}")));
return Err(crate::error::Error::Failed(format!(
"GL render 失败:{stat}"
)));
}
Ok(())
}
@@ -722,8 +754,10 @@ impl Instance {
/// 调用方(render 驱动)随后把结果写回输出 clip(
/// [`Instance::set_output_colourspace`])。
pub fn get_output_colourspace(&self, preferred: &[String]) -> crate::error::Result<String> {
use crate::host::{
ACTION_GET_OUTPUT_COLOURSPACE, PROP_CLIP_COLOURSPACE, PROP_CLIP_PREFERRED_COLOURSPACES,
};
use crate::property::Value;
use crate::host::{ACTION_GET_OUTPUT_COLOURSPACE, PROP_CLIP_COLOURSPACE, PROP_CLIP_PREFERRED_COLOURSPACES};
let in_args = PropertySet::new();
let values: Vec<Value> = preferred
@@ -732,7 +766,10 @@ impl Instance {
.collect();
in_args.define(PROP_CLIP_PREFERRED_COLOURSPACES, values);
let out = PropertySet::new();
out.set_one(PROP_CLIP_COLOURSPACE, Value::String(CString::new("").unwrap()));
out.set_one(
PROP_CLIP_COLOURSPACE,
Value::String(CString::new("").unwrap()),
);
let inst_handle = crate::suites::tag::make(
&self.props as *const PropertySet,
@@ -859,13 +896,21 @@ impl Instance {
/// 序列 action 的 in args 组装(begin/end 共用;GL 模式加
/// OpenGLEnabled=1)。
fn sequence_in_args(range: OfxRangeD, gl_enabled: bool) -> PropertySet {
use crate::host::{
PROP_FRAME_RANGE, PROP_FRAME_STEP, PROP_RENDER_SCALE, PROP_SEQUENTIAL_RENDER,
};
use crate::property::Value;
use crate::host::{PROP_FRAME_RANGE, PROP_FRAME_STEP, PROP_RENDER_SCALE, PROP_SEQUENTIAL_RENDER};
let in_args = PropertySet::new();
in_args.define(PROP_FRAME_RANGE, vec![Value::Double(range.min), Value::Double(range.max)]);
in_args.define(
PROP_FRAME_RANGE,
vec![Value::Double(range.min), Value::Double(range.max)],
);
in_args.set_one(PROP_FRAME_STEP, Value::Double(1.0));
in_args.define(PROP_RENDER_SCALE, vec![Value::Double(1.0), Value::Double(1.0)]);
in_args.define(
PROP_RENDER_SCALE,
vec![Value::Double(1.0), Value::Double(1.0)],
);
in_args.set_one(PROP_SEQUENTIAL_RENDER, Value::Int(1));
if gl_enabled {
in_args.set_one(crate::host::PROP_GL_ENABLED, Value::Int(1));
+64 -28
View File
@@ -351,7 +351,10 @@ impl ParamDef {
props.set_one(PROP_LABEL, Value::String(cs(&uname)));
props.set_one(PROP_SHORT_LABEL, Value::String(cs(&uname)));
props.set_one(PROP_LONG_LABEL, Value::String(cs(&uname)));
props.define(PROP_ICON, vec![Value::String(cs("")), Value::String(cs(""))]);
props.define(
PROP_ICON,
vec![Value::String(cs("")), Value::String(cs(""))],
);
// 值类参数(HS addValueParamProps,ofxhParam.cpp:352-380)。
if let Some((kind, dim)) = kind_of_type(ofx_type) {
@@ -360,7 +363,10 @@ impl ParamDef {
props.set_one(P_PERSISTANT, Value::Int(1));
props.set_one(P_EVALUATE_ON_CHANGE, Value::Int(1));
props.set_one(P_CAN_UNDO, Value::Int(1));
props.set_one(P_CACHE_INVALIDATION, Value::String(cs(V_INVALIDATE_VALUE_CHANGE)));
props.set_one(
P_CACHE_INVALIDATION,
Value::String(cs(V_INVALIDATE_VALUE_CHANGE)),
);
// 可动画性(HS ofxhParam.cpp:367-372:custom/string/
// boolean/choice 默认不可动画)。
let animates = ofx_type != TYPE_CUSTOM
@@ -376,11 +382,15 @@ impl ParamDef {
props.define(P_DISPLAY_MAX, numeric_value(ofx_type, kind, dim, true));
props.define(P_MIN, numeric_value(ofx_type, kind, dim, false));
props.define(P_MAX, numeric_value(ofx_type, kind, dim, true));
if matches!(kind, ParamKind::Double | ParamKind::Double2 | ParamKind::Double3) {
if matches!(
kind,
ParamKind::Double | ParamKind::Double2 | ParamKind::Double3
) {
props.set_one(P_INCREMENT, Value::Double(1.0));
props.set_one(P_DIGITS, Value::Int(2));
}
if ofx_type == TYPE_DOUBLE || ofx_type == TYPE_DOUBLE2D || ofx_type == TYPE_DOUBLE3D {
if ofx_type == TYPE_DOUBLE || ofx_type == TYPE_DOUBLE2D || ofx_type == TYPE_DOUBLE3D
{
props.set_one(P_DOUBLE_TYPE, Value::String(cs(V_DOUBLE_TYPE_PLAIN)));
props.set_one(P_DEFAULT_COORD_SYS, Value::String(cs(V_COORD_CANONICAL)));
if dim == 1 {
@@ -423,16 +433,16 @@ impl ParamDef {
// group/page 除外)。
if ofx_type != TYPE_GROUP && ofx_type != TYPE_PAGE {
props.set_one(P_INTERACT_V1, Value::Pointer(std::ptr::null_mut()));
props.define(P_INTERACT_SIZE, vec![Value::Double(0.0), Value::Double(0.0)]);
props.define(
P_INTERACT_SIZE,
vec![Value::Double(0.0), Value::Double(0.0)],
);
props.set_one(P_INTERACT_ASPECT, Value::Double(1.0));
props.define(
P_INTERACT_MIN_SIZE,
vec![Value::Double(10.0), Value::Double(10.0)],
);
props.define(
P_INTERACT_PREF_SIZE,
vec![Value::Int(10), Value::Int(10)],
);
props.define(P_INTERACT_PREF_SIZE, vec![Value::Int(10), Value::Int(10)]);
}
Self {
@@ -452,7 +462,11 @@ impl ParamDef {
/// kOfxParamPropDefault 的属性值(HS ofxhParam.cpp:376-377)。
fn default_values(kind: ParamKind, dim: usize) -> Vec<Value> {
match kind {
ParamKind::Int | ParamKind::Int2 | ParamKind::Int3 | ParamKind::Bool | ParamKind::Choice => {
ParamKind::Int
| ParamKind::Int2
| ParamKind::Int3
| ParamKind::Bool
| ParamKind::Choice => {
vec![Value::Int(0); dim.max(1)]
}
ParamKind::Double
@@ -522,15 +536,22 @@ impl ParamInstance {
use crate::bridge::node::node_value_type as T;
let v = node_value;
let mapped: Option<ParamValue> = match self.def.ofx_type.as_str() {
TYPE_DOUBLE => (v.r#type == T::FLOAT).then(|| ParamValue::Double([v.f[0], 0.0, 0.0], 1)),
TYPE_DOUBLE => {
(v.r#type == T::FLOAT).then(|| ParamValue::Double([v.f[0], 0.0, 0.0], 1))
}
TYPE_DOUBLE2D => {
(v.r#type == T::VEC2).then(|| ParamValue::Double([v.f[0], v.f[1], 0.0], 2))
}
TYPE_DOUBLE3D => (v.r#type == T::VEC3)
.then(|| ParamValue::Double([v.f[0], v.f[1], v.f[2]], 3)),
TYPE_DOUBLE3D => {
(v.r#type == T::VEC3).then(|| ParamValue::Double([v.f[0], v.f[1], v.f[2]], 3))
}
TYPE_INTEGER => (v.r#type == T::INT).then(|| ParamValue::Int([v.num as i32, 0, 0], 1)),
TYPE_INTEGER2D | TYPE_INTEGER3D => {
let dim = if self.def.ofx_type == TYPE_INTEGER2D { 2 } else { 3 };
let dim = if self.def.ofx_type == TYPE_INTEGER2D {
2
} else {
3
};
Some(ParamValue::Int(
[v.f[0] as i32, v.f[1] as i32, v.f[2] as i32],
dim,
@@ -538,8 +559,9 @@ impl ParamInstance {
}
TYPE_BOOLEAN => (v.r#type == T::BOOL).then(|| ParamValue::Bool(v.num != 0)),
TYPE_CHOICE => (v.r#type == T::COMBO).then(|| ParamValue::Choice(v.num as i32)),
TYPE_RGB => (v.r#type == T::COLOR)
.then(|| ParamValue::Color([v.f[0], v.f[1], v.f[2], 0.0], 3)),
TYPE_RGB => {
(v.r#type == T::COLOR).then(|| ParamValue::Color([v.f[0], v.f[1], v.f[2], 0.0], 3))
}
TYPE_RGBA => (v.r#type == T::COLOR)
.then(|| ParamValue::Color([v.f[0], v.f[1], v.f[2], v.f[3]], 4)),
_ => None, // 字符串/无值类:POD 无数据,不改值
@@ -618,7 +640,10 @@ pub struct ParamSetInstance {
impl ParamSetInstance {
/// 按名查找。
pub fn find(&self, name: &str) -> Option<&ParamInstance> {
self.params.iter().find(|p| p.def.name == name).map(|b| b.as_ref())
self.params
.iter()
.find(|p| p.def.name == name)
.map(|b| b.as_ref())
}
/// 快照(内省 C ABI 与快照测试用)。
@@ -679,8 +704,7 @@ pub(crate) fn notify_instance_changed(
match to_node_value(&value) {
Some(nv) => {
let mut cmd = undo::CommandHandle::null();
let r =
unsafe { node::set_input_undoable(node_handle, cname.as_ptr(), &nv, &mut cmd) };
let r = unsafe { node::set_input_undoable(node_handle, cname.as_ptr(), &nv, &mut cmd) };
if r == 0 && !cmd.is_null() {
instance.submit_undo_command(cmd, &label);
}
@@ -752,8 +776,14 @@ mod tests {
let v = to_node_value(&ParamValue::Int([1, 2, 0], 2)).unwrap();
assert_eq!(v, Value::vec(&[1.0, 2.0]));
// Bool / Choice。
assert_eq!(to_node_value(&ParamValue::Bool(true)).unwrap(), Value::bool_(true));
assert_eq!(to_node_value(&ParamValue::Choice(3)).unwrap(), Value::combo(3));
assert_eq!(
to_node_value(&ParamValue::Bool(true)).unwrap(),
Value::bool_(true)
);
assert_eq!(
to_node_value(&ParamValue::Choice(3)).unwrap(),
Value::combo(3)
);
// Color:RGB 补 alpha=1(C++ oracle),RGBA 全透传。
let v = to_node_value(&ParamValue::Color([0.1, 0.2, 0.3, 0.0], 3)).unwrap();
assert_eq!(v, Value::color(0.1, 0.2, 0.3, 1.0));
@@ -771,18 +801,24 @@ mod tests {
/// type_default 的无值类/字节族基线(paramDefine 拒绝前的兜底)。
#[test]
fn type_default_edge_cases() {
assert!(matches!(
type_default(TYPE_BYTES),
ParamValue::Bytes(_)
));
assert!(matches!(type_default(TYPE_BYTES), ParamValue::Bytes(_)));
assert!(matches!(type_default(TYPE_CUSTOM), ParamValue::Bytes(_)));
assert!(matches!(type_default(TYPE_PUSHBUTTON), ParamValue::PushButton));
assert!(matches!(
type_default(TYPE_PUSHBUTTON),
ParamValue::PushButton
));
assert!(matches!(type_default(TYPE_GROUP), ParamValue::Container));
assert!(matches!(type_default(TYPE_PAGE), ParamValue::Container));
// 未知类型占位(paramDefine 已拒绝,此处兜底)。
assert!(matches!(type_default("OfxParamTypeBogus"), ParamValue::Container));
assert!(matches!(
type_default("OfxParamTypeBogus"),
ParamValue::Container
));
// 数值族的默认 0。
assert!(matches!(type_default(TYPE_INTEGER), ParamValue::Int([0, 0, 0], 1)));
assert!(matches!(
type_default(TYPE_INTEGER),
ParamValue::Int([0, 0, 0], 1)
));
assert!(matches!(
type_default(TYPE_DOUBLE2D),
ParamValue::Double([0.0, 0.0, 0.0], 2)
+35 -15
View File
@@ -157,7 +157,8 @@ pub fn render_frame(
Some(r) if unsafe { render::renderer_is_open_gl(r) } == 1 => {
let plugin_gl = plugin_supports_opengl(inst);
let depth_ok =
crate::suites::gl_render::pick_gl_pixel_depth(&inst.plugin.descriptor.props).is_some();
crate::suites::gl_render::pick_gl_pixel_depth(&inst.plugin.descriptor.props)
.is_some();
let dst_id = unsafe { render::texture_id(job.dst) };
plugin_gl && depth_ok && dst_id != 0
}
@@ -225,18 +226,18 @@ pub fn render_frame(
// 输入整帧提供,RoI 只作记录)。
let rois = inst
.get_regions_of_interest(job.time, RenderScale { x: 1.0, y: 1.0 }, region_of_interest)
.unwrap_or_else(|_| {
inst.clips
.iter()
.map(|_| region_of_interest)
.collect()
});
.unwrap_or_else(|_| inst.clips.iter().map(|_| region_of_interest).collect());
// 8. 输出 clip 格式(pluginrenderer.cpp:1686-1697)。
output_clip.set_video_params(render::PIXEL_FORMAT_F32, components.channel_count() as i32);
// 渲染窗口(像素坐标;pluginrenderer.cpp:1699-1704)。
let render_window = OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h };
let render_window = OfxRectD {
x1: 0.0,
y1: 0.0,
x2: w,
y2: h,
};
// 9. isIdentity 短路(ofxRendering "Identity Effects"):插件声明
// 本帧等价于某输入 clip → 直接透传该 clip 在透传时间的帧。
@@ -254,15 +255,31 @@ pub fn render_frame(
let output = std::sync::Arc::new(Image::allocate(
crate::image::BitDepth::Float,
components,
OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h },
OfxRectD {
x1: 0.0,
y1: 0.0,
x2: w,
y2: h,
},
));
inst.render(job.time, RenderScale { x: 1.0, y: 1.0 }, render_window, output.clone())?;
inst.render(
job.time,
RenderScale { x: 1.0, y: 1.0 },
render_window,
output.clone(),
)?;
// 输出装配(pluginrenderer.cpp:1762-1834 的 CPU 路径)。
write_output_frame(job.dst, &output)?;
} else {
// GL 路径:插件直接画进已附着的输出纹理(
// pluginrenderer.cpp:1784-1834 的 GL 分支);无 CPU 回读。
inst.render_gl(job.time, RenderScale { x: 1.0, y: 1.0 }, render_window, job.renderer.unwrap(), job.dst)?;
inst.render_gl(
job.time,
RenderScale { x: 1.0, y: 1.0 },
render_window,
job.renderer.unwrap(),
job.dst,
)?;
}
unsafe { render::frame_free(&mut (dst_frame)) };
@@ -280,7 +297,9 @@ fn zip_rois(inst: &Instance, rois: &[OfxRectD]) -> Vec<(String, OfxRectD)> {
}
/// 读目标纹理的帧与参数(F32 校验)。返回 (帧句柄, 参数, 宽, 高)。
fn read_dst(dst: TextureHandle) -> crate::error::Result<(FrameHandle, render::VideoParams, f64, f64)> {
fn read_dst(
dst: TextureHandle,
) -> crate::error::Result<(FrameHandle, render::VideoParams, f64, f64)> {
use crate::error::Error;
let mut frame = FrameHandle::null();
if unsafe { render::texture_get_frame(dst, &mut frame) } != 0 || frame.is_null() {
@@ -383,7 +402,9 @@ fn passthrough(
/// 走专用桥一致)。
fn apply_param_overrides(inst: &Instance, values: &[(String, crate::ffi::OakNodeValue)]) {
for (key, v) in values {
let Some(p) = inst.params.find(key) else { continue };
let Some(p) = inst.params.find(key) else {
continue;
};
let Some(pv) = crate::ffi::node_value_to_param(v, &p.def.ofx_type) else {
continue;
};
@@ -422,8 +443,7 @@ pub(crate) fn write_output_frame(dst: TextureHandle, image: &Image) -> crate::er
}
let row = unsafe { render::frame_linesize_bytes(frame) } as usize;
let row = if row > 0 { row } else { tight };
let dst_bytes =
unsafe { std::slice::from_raw_parts_mut(dst_ptr as *mut u8, row * h) };
let dst_bytes = unsafe { std::slice::from_raw_parts_mut(dst_ptr as *mut u8, row * h) };
let pixels = image.pixels();
for y in 0..h {
let d = y * row;
+23 -10
View File
@@ -97,7 +97,9 @@ pub(crate) const PREMULT_NONE: &str = "OfxImagePreMultipliedNone";
/// - 列表缺失/为空 → Some(Float)(宿主自选,规范默认);
/// - 列表含 Float → Some(Float);
/// - 列表存在且不含 Float → None(管线约束;GL 模式不可行)。
pub(crate) fn pick_gl_pixel_depth(plugin_props: &crate::property::PropertySet) -> Option<&'static str> {
pub(crate) fn pick_gl_pixel_depth(
plugin_props: &crate::property::PropertySet,
) -> Option<&'static str> {
use crate::property::Value;
let dim = plugin_props.dimension(crate::host::PROP_GL_PIXEL_DEPTH);
if dim == 0 {
@@ -172,7 +174,11 @@ static LIVE_TEXTURES: std::sync::LazyLock<
> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// 登记纹理(clipLoadTexture 内部;`props` 装箱后取地址为句柄基址)。
fn register(props: Box<PropertySet>, texture: crate::bridge::render::TextureHandle, is_output: bool) -> usize {
fn register(
props: Box<PropertySet>,
texture: crate::bridge::render::TextureHandle,
is_output: bool,
) -> usize {
let addr = &*props as *const PropertySet as usize;
LIVE_TEXTURES
.lock()
@@ -199,8 +205,10 @@ pub(crate) fn purge_leftovers() {
/// 公共入口模板:panic 兜底。
fn caught(f: impl FnOnce() -> Result<(), c_int>) -> c_int {
catch_unwind(AssertUnwindSafe(f))
.map_or_else(|_| status::FAILED, |r| r.map_or_else(|c| c, |()| status::OK))
catch_unwind(AssertUnwindSafe(f)).map_or_else(
|_| status::FAILED,
|r| r.map_or_else(|c| c, |()| status::OK),
)
}
/// clip 句柄解析(实例期)。
@@ -223,10 +231,7 @@ fn cs(s: &str) -> CString {
/// 把矩形写成 Int×4 属性(Bounds/ROD 用;OFX 图像属性是像素坐标)。
fn rect_props(props: &PropertySet, name: &str, rect: crate::instance::OfxRectD) {
let b = |v: f64| Value::Int(v.round() as i32);
props.define(
name,
vec![b(rect.x1), b(rect.y1), b(rect.x2), b(rect.y2)],
);
props.define(name, vec![b(rect.x1), b(rect.y1), b(rect.x2), b(rect.y2)]);
}
/// 从 clip 属性读字符串(缺失返回默认)。
@@ -268,13 +273,21 @@ fn make_texture_props(
crate::image::K_IMAGE_EFFECT_PROP_COMPONENTS,
Value::String(cs(components.to_ofx())),
);
props.set_one("OfxImageEffectPropPreMultiplication", Value::String(cs(premult)));
props.set_one(
"OfxImageEffectPropPreMultiplication",
Value::String(cs(premult)),
);
props.define(
crate::host::PROP_RENDER_SCALE,
vec![Value::Double(scale.x), Value::Double(scale.y)],
);
props.set_one("OfxImagePropPixelAspectRatio", Value::Double(par));
let bounds = crate::instance::OfxRectD { x1: 0.0, y1: 0.0, x2: width, y2: height };
let bounds = crate::instance::OfxRectD {
x1: 0.0,
y1: 0.0,
x2: width,
y2: height,
};
rect_props(&props, crate::image::K_IMAGE_PROP_BOUNDS, bounds);
rect_props(&props, crate::image::K_IMAGE_PROP_ROD, bounds);
props.set_one(crate::image::K_IMAGE_PROP_ROW_BYTES, Value::Int(row_bytes));
+58 -17
View File
@@ -54,16 +54,23 @@ pub struct ImageEffectSuiteV1 {
/// clipDefine(describe 期间):定义 clip。
pub clip_define: unsafe extern "C" fn(*mut c_void, *const c_char, *mut *mut c_void) -> c_int,
/// clipGetHandle:按名取 clip。
pub clip_get_handle: unsafe extern "C" fn(*mut c_void, *const c_char, *mut *mut c_void, *mut *mut c_void) -> c_int,
pub clip_get_handle: unsafe extern "C" fn(
*mut c_void,
*const c_char,
*mut *mut c_void,
*mut *mut c_void,
) -> c_int,
/// clipGetPropertySet
pub clip_get_property_set: unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> c_int,
/// clipGetImage:取图像(host 侧增加引用,必须与
/// clipReleaseImage 配对)。
pub clip_get_image: unsafe extern "C" fn(*mut c_void, c_double, *const c_void, *mut *mut c_void) -> c_int,
pub clip_get_image:
unsafe extern "C" fn(*mut c_void, c_double, *const c_void, *mut *mut c_void) -> c_int,
/// clipReleaseImage
pub clip_release_image: unsafe extern "C" fn(*mut c_void) -> c_int,
/// clipGetRegionOfDefinition
pub clip_get_region_of_definition: unsafe extern "C" fn(*mut c_void, c_double, *mut c_void) -> c_int,
pub clip_get_region_of_definition:
unsafe extern "C" fn(*mut c_void, c_double, *mut c_void) -> c_int,
/// abort:查询是否应中止(进度取消透传)。
pub abort: unsafe extern "C" fn(*mut c_void) -> c_int,
/// imageMemoryAlloc / imageMemoryFree / imageMemoryLock /
@@ -84,8 +91,10 @@ static LIVE_IMAGES: std::sync::LazyLock<Mutex<HashMap<usize, std::sync::Arc<Imag
/// 公共入口模板:panic 兜底。
fn caught(f: impl FnOnce() -> Result<(), c_int>) -> c_int {
catch_unwind(AssertUnwindSafe(f))
.map_or_else(|_| status::FAILED, |r| r.map_or_else(|c| c, |()| status::OK))
catch_unwind(AssertUnwindSafe(f)).map_or_else(
|_| status::FAILED,
|r| r.map_or_else(|c| c, |()| status::OK),
)
}
/// 属性名(空指针/非 UTF-8 → ErrValue)。
@@ -111,8 +120,12 @@ fn resolve_effect(handle: *mut c_void) -> Result<EffectRef<'static>, c_int> {
// 两者 props 均在偏移 0(句柄约定)。
unsafe {
match tag::kind(handle) {
tag::DESCRIPTOR => Ok(EffectRef::Descriptor(&mut *(tag::strip(handle) as *mut EffectDescriptor))),
tag::INSTANCE => Ok(EffectRef::Instance(&*(tag::strip(handle) as *const Instance))),
tag::DESCRIPTOR => Ok(EffectRef::Descriptor(
&mut *(tag::strip(handle) as *mut EffectDescriptor),
)),
tag::INSTANCE => Ok(EffectRef::Instance(
&*(tag::strip(handle) as *const Instance),
)),
_ => Err(status::ERR_BAD_HANDLE),
}
}
@@ -161,7 +174,11 @@ unsafe extern "C" fn get_param_set(effect: *mut c_void, out: *mut *mut c_void) -
/// clipDefine(describe 期):定义 clip 并返回其属性集 handle。
/// 重复名 → 整体替换(HS: `defineClip` 的 map 覆盖语义,
/// ofxhImageEffect.cpp:265-271;旧句柄随之失效,与 HS 一致)。
unsafe extern "C" fn clip_define(effect: *mut c_void, name: *const c_char, out: *mut *mut c_void) -> c_int {
unsafe extern "C" fn clip_define(
effect: *mut c_void,
name: *const c_char,
out: *mut *mut c_void,
) -> c_int {
caught(|| {
if out.is_null() {
return Err(status::ERR_BAD_HANDLE);
@@ -180,7 +197,11 @@ unsafe extern "C" fn clip_define(effect: *mut c_void, name: *const c_char, out:
desc.clips.push(Box::new(clip));
}
// 地址取自已入盒的对象(栈上临时变量在移动后失效)。
let clip = desc.clips.iter().find(|c| c.name == n).expect("just stored");
let clip = desc
.clips
.iter()
.find(|c| c.name == n)
.expect("just stored");
let addr = &clip.props as *const _ as usize;
unsafe { *out = tag::make(addr as *const PropertySet, tag::CLIP) };
Ok(())
@@ -203,7 +224,11 @@ unsafe extern "C" fn clip_get_handle(
let n = unsafe { c_name(name)? };
let handle = match resolve_effect(effect)? {
EffectRef::Instance(i) => {
let c = i.clips.iter().find(|c| c.name == n).ok_or(status::ERR_BAD_HANDLE)?;
let c = i
.clips
.iter()
.find(|c| c.name == n)
.ok_or(status::ERR_BAD_HANDLE)?;
let addr = &c.props as *const _ as usize;
tag::make(addr as *const PropertySet, tag::CLIP)
}
@@ -271,7 +296,9 @@ unsafe extern "C" fn clip_get_image(
let scale = crate::suites::render_ctx()
.map(|ctx| ctx.scale)
.unwrap_or(RenderScale { x: 1.0, y: 1.0 });
let image = c.fetch_image(time, scale, region).map_err(|_| status::FAILED)?;
let image = c
.fetch_image(time, scale, region)
.map_err(|_| status::FAILED)?;
let image = std::sync::Arc::new(image);
let addr = &image.props as *const _ as usize;
LIVE_IMAGES
@@ -438,7 +465,6 @@ pub fn suite_v1() -> &'static ImageEffectSuiteV1 {
})
}
#[cfg(test)]
mod tests {
use super::*;
@@ -476,7 +502,10 @@ mod tests {
let optional_prop = cs(crate::descriptor::CLIP_OPTIONAL);
let label = cs("SourceLabel");
unsafe {
assert_eq!((ps.set_string)(clip, label_prop.as_ptr(), 0, label.as_ptr()), 0);
assert_eq!(
(ps.set_string)(clip, label_prop.as_ptr(), 0, label.as_ptr()),
0
);
}
let mut out: *mut c_char = std::ptr::null_mut();
unsafe {
@@ -505,8 +534,14 @@ mod tests {
// 空 out / 空 handle → BadHandle。
unsafe {
assert_eq!((s.clip_define)(h, name.as_ptr(), std::ptr::null_mut()), status::ERR_BAD_HANDLE);
assert_eq!((s.clip_define)(std::ptr::null_mut(), name.as_ptr(), &mut clip2), status::ERR_BAD_HANDLE);
assert_eq!(
(s.clip_define)(h, name.as_ptr(), std::ptr::null_mut()),
status::ERR_BAD_HANDLE
);
assert_eq!(
(s.clip_define)(std::ptr::null_mut(), name.as_ptr(), &mut clip2),
status::ERR_BAD_HANDLE
);
}
}
@@ -565,7 +600,10 @@ mod tests {
let s = suite_v1();
let mut mem: *mut c_void = std::ptr::null_mut();
unsafe {
assert_eq!((s.image_memory_alloc)(std::ptr::null_mut(), 1024, &mut mem), 0);
assert_eq!(
(s.image_memory_alloc)(std::ptr::null_mut(), 1024, &mut mem),
0
);
assert!(!mem.is_null());
// lock 返回数据指针(第 1 期 = 句柄本身)。
let mut ptr: *mut c_void = std::ptr::null_mut();
@@ -573,7 +611,10 @@ mod tests {
assert_eq!(ptr, mem);
assert_eq!((s.image_memory_unlock)(mem), 0);
// 未知指针 free → BadHandle。
assert_eq!((s.image_memory_free)(0xdeadbeef as *mut c_void), status::ERR_BAD_HANDLE);
assert_eq!(
(s.image_memory_free)(0xdeadbeef as *mut c_void),
status::ERR_BAD_HANDLE
);
assert_eq!((s.image_memory_free)(mem), 0);
}
}
+14 -4
View File
@@ -103,7 +103,10 @@ pub unsafe extern "C" fn oak_ofx_message_impl(
};
}
// Headless 默认:stderr(C++ fprintf 的 Rust 等价物)。
eprintln!("OFX message: {}", unsafe { CStr::from_ptr(message) }.to_string_lossy());
eprintln!(
"OFX message: {}",
unsafe { CStr::from_ptr(message) }.to_string_lossy()
);
if is_question(type_) {
status::REPLY_NO
} else {
@@ -194,8 +197,12 @@ mod tests {
) -> c_int {
let v = unsafe { &mut *(userdata as *mut Vec<(String, String)>) };
v.push((
unsafe { CStr::from_ptr(type_) }.to_string_lossy().into_owned(),
unsafe { CStr::from_ptr(message) }.to_string_lossy().into_owned(),
unsafe { CStr::from_ptr(type_) }
.to_string_lossy()
.into_owned(),
unsafe { CStr::from_ptr(message) }
.to_string_lossy()
.into_owned(),
));
1
}
@@ -256,7 +263,10 @@ mod tests {
fn shim_v2_table_entry_resolves() {
let _g = TEST_LOCK.lock().unwrap();
let s = suite_v2();
assert!(!std::ptr::eq(s.message as *const (), ofx_message_shim_v1 as *const ()));
assert!(!std::ptr::eq(
s.message as *const (),
ofx_message_shim_v1 as *const ()
));
}
/// 无出口(headless 默认):非 question → OK;question → REPLY_NO。
+5 -6
View File
@@ -105,10 +105,7 @@ pub mod tag {
pub const IMAGE: usize = 6;
/// 打标签(宿主创建对象句柄用;公开供宿主/测试构造句柄)。
pub fn make(
props: *const crate::property::PropertySet,
t: usize,
) -> *mut std::ffi::c_void {
pub fn make(props: *const crate::property::PropertySet, t: usize) -> *mut std::ffi::c_void {
(props as usize | t) as *mut std::ffi::c_void
}
@@ -238,7 +235,6 @@ fn ptr<T>(p: &'static T) -> *const std::ffi::c_void {
p as *const T as *const std::ffi::c_void
}
#[cfg(test)]
mod tests {
use super::*;
@@ -293,7 +289,10 @@ mod tests {
let ctx = RenderCtx {
time: 1.5,
scale: crate::instance::RenderScale { x: 2.0, y: 2.0 },
range: crate::instance::OfxRangeD { min: 0.0, max: 100.0 },
range: crate::instance::OfxRangeD {
min: 0.0,
max: 100.0,
},
};
set_render_ctx(Some(ctx));
let got = render_ctx().unwrap();
+1 -1
View File
@@ -132,8 +132,8 @@ pub fn suite_v1() -> &'static MultiThreadSuiteV1 {
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Mutex;
/// 每个插件线程把 (index, count) 累计进 userdata。
unsafe extern "C" fn worker(index: c_uint, count: c_uint, arg: *mut c_void) {
+58 -23
View File
@@ -46,9 +46,15 @@ use crate::suites::tag;
#[repr(C)]
pub struct ParameterSuiteV1 {
/// paramDefine(describe 期间):定义参数。
pub param_define: unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, *mut *mut c_void) -> c_int,
pub param_define:
unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char, *mut *mut c_void) -> c_int,
/// paramGetHandle
pub param_get_handle: unsafe extern "C" fn(*mut c_void, *const c_char, *mut *mut c_void, *mut *mut c_void) -> c_int,
pub param_get_handle: unsafe extern "C" fn(
*mut c_void,
*const c_char,
*mut *mut c_void,
*mut *mut c_void,
) -> c_int,
/// paramSetGetPropertySet
pub param_set_get_property_set: unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> c_int,
/// paramGetPropertySet
@@ -70,13 +76,15 @@ pub struct ParameterSuiteV1 {
/// paramGetKeyTime
pub param_get_key_time: unsafe extern "C" fn(*mut c_void, c_int, *mut c_double) -> c_int,
/// paramGetKeyIndex
pub param_get_key_index: unsafe extern "C" fn(*mut c_void, c_double, c_int, *mut c_int) -> c_int,
pub param_get_key_index:
unsafe extern "C" fn(*mut c_void, c_double, c_int, *mut c_int) -> c_int,
/// paramDeleteKey
pub param_delete_key: unsafe extern "C" fn(*mut c_void, c_double) -> c_int,
/// paramDeleteAllKeys
pub param_delete_all_keys: unsafe extern "C" fn(*mut c_void) -> c_int,
/// paramCopy
pub param_copy: unsafe extern "C" fn(*mut c_void, *mut c_void, c_double, c_double, *const c_void) -> c_int,
pub param_copy:
unsafe extern "C" fn(*mut c_void, *mut c_void, c_double, c_double, *const c_void) -> c_int,
/// paramEditBegin / paramEditEnd(编辑事务括号)
pub param_edit_begin: unsafe extern "C" fn(*mut c_void) -> c_int,
/// paramEditEnd
@@ -106,10 +114,12 @@ fn resolve_param_set(handle: *mut c_void) -> Result<ParamSetRef<'static>, c_int>
// 两种对象 props 均在偏移 0(句柄约定)。
unsafe {
match tag::kind(handle) {
tag::DESCRIPTOR => {
Ok(ParamSetRef::Descriptor(&mut *(tag::strip(handle) as *mut EffectDescriptor)))
}
tag::INSTANCE => Ok(ParamSetRef::Instance(&*(tag::strip(handle) as *const Instance))),
tag::DESCRIPTOR => Ok(ParamSetRef::Descriptor(
&mut *(tag::strip(handle) as *mut EffectDescriptor),
)),
tag::INSTANCE => Ok(ParamSetRef::Instance(
&*(tag::strip(handle) as *const Instance),
)),
_ => Err(status::ERR_BAD_HANDLE),
}
}
@@ -123,9 +133,9 @@ fn resolve_param(handle: *mut c_void) -> Result<ParamRef<'static>, c_int> {
unsafe {
match tag::kind(handle) {
tag::PARAM_DEF => Ok(ParamRef::Def(&*(tag::strip(handle) as *const ParamDef))),
tag::PARAM_INSTANCE => {
Ok(ParamRef::Instance(&*(tag::strip(handle) as *const ParamInstance)))
}
tag::PARAM_INSTANCE => Ok(ParamRef::Instance(
&*(tag::strip(handle) as *const ParamInstance),
)),
_ => Err(status::ERR_BAD_HANDLE),
}
}
@@ -143,8 +153,10 @@ unsafe fn c_name<'a>(name: *const c_char) -> Result<&'a str, c_int> {
/// 公共入口模板:panic 兜底。
fn caught(f: impl FnOnce() -> Result<(), c_int>) -> c_int {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f))
.map_or_else(|_| status::FAILED, |r| r.map_or_else(|c| c, |()| status::OK))
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).map_or_else(
|_| status::FAILED,
|r| r.map_or_else(|c| c, |()| status::OK),
)
}
// ---- 变长参数实现(C shim 转发)----------------------------------------
@@ -486,7 +498,9 @@ unsafe extern "C" fn param_define(
// 句柄会指向死内存——Box 负载地址在 Vec 重分配时稳定)。
let def = desc.params.last().expect("just pushed");
let addr = &def.props as *const _ as usize;
unsafe { *property_set = tag::make(addr as *const crate::property::PropertySet, tag::PARAM_DEF) };
unsafe {
*property_set = tag::make(addr as *const crate::property::PropertySet, tag::PARAM_DEF)
};
Ok(())
})
}
@@ -513,7 +527,10 @@ unsafe extern "C" fn param_get_handle(
ParamSetRef::Instance(i) => {
let p = i.params.find(n).ok_or(status::ERR_UNKNOWN)?;
let addr = &p.props as *const _ as usize;
tag::make(addr as *const crate::property::PropertySet, tag::PARAM_INSTANCE)
tag::make(
addr as *const crate::property::PropertySet,
tag::PARAM_INSTANCE,
)
}
};
unsafe { *param = handle };
@@ -568,7 +585,11 @@ unsafe extern "C" fn param_get_num_keys(param: *mut c_void, count: *mut c_int) -
}
/// paramGetKeyTime:无关键帧 → BadIndex。
unsafe extern "C" fn param_get_key_time(param: *mut c_void, _index: c_int, _time: *mut c_double) -> c_int {
unsafe extern "C" fn param_get_key_time(
param: *mut c_void,
_index: c_int,
_time: *mut c_double,
) -> c_int {
caught(|| {
let _ = resolve_param(param)?;
Err(status::ERR_BAD_INDEX)
@@ -700,7 +721,6 @@ pub fn suite_v1() -> &'static ParameterSuiteV1 {
})
}
#[cfg(test)]
mod tests {
use super::*;
@@ -716,7 +736,10 @@ mod tests {
}
fn descriptor_handle(d: &EffectDescriptor) -> *mut c_void {
tag::make(&d.props as *const crate::property::PropertySet, tag::DESCRIPTOR)
tag::make(
&d.props as *const crate::property::PropertySet,
tag::DESCRIPTOR,
)
}
/// 假插件(host::Plugin 的构造只为拿 Arc 喂给 Instance;describe
@@ -743,7 +766,10 @@ mod tests {
}
fn instance_handle(i: &Instance) -> *mut c_void {
tag::make(&i.props as *const crate::property::PropertySet, tag::INSTANCE)
tag::make(
&i.props as *const crate::property::PropertySet,
tag::INSTANCE,
)
}
/// 用 paramDefine 建一个实例(describe 产物 → createInstance)。
@@ -763,10 +789,13 @@ mod tests {
let t = cs(t);
let n = cs(n);
let mut ph: *mut c_void = std::ptr::null_mut();
assert_eq!((s.param_define)(dhandle, t.as_ptr(), n.as_ptr(), &mut ph), 0);
assert_eq!(
(s.param_define)(dhandle, t.as_ptr(), n.as_ptr(), &mut ph),
0
);
}
}
let params = ParamSetInstance {
let params = ParamSetInstance {
params: desc
.params
.iter()
@@ -959,7 +988,10 @@ let params = ParamSetInstance {
assert_eq!((s.param_get_num_keys)(opacity, &mut nkeys), 0);
assert_eq!(nkeys, 0);
let mut kt = 0.0;
assert_eq!((s.param_get_key_time)(opacity, 0, &mut kt), status::ERR_BAD_INDEX);
assert_eq!(
(s.param_get_key_time)(opacity, 0, &mut kt),
status::ERR_BAD_INDEX
);
}
// derivative/integral → MissingHostFeature。
@@ -977,7 +1009,10 @@ let params = ParamSetInstance {
assert_eq!((s.param_edit_end)(opacity), 0);
assert_eq!((s.param_delete_key)(opacity, 1.0), 0);
assert_eq!((s.param_delete_all_keys)(opacity), 0);
assert_eq!((s.param_copy)(opacity, opacity, 1.0, 1.0, std::ptr::null()), 0);
assert_eq!(
(s.param_copy)(opacity, opacity, 1.0, 1.0, std::ptr::null()),
0
);
}
}
}
+7 -2
View File
@@ -152,12 +152,17 @@ mod tests {
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.start)(std::ptr::null_mut(), std::ptr::null()),
status::OK
);
assert_eq!((s.end)(std::ptr::null_mut()), status::OK);
}
// 带取消回调:update → REPLY_NO;is_cancelled 为真。
set_current(Some(unsafe { ProgressReporter::new(cancel_cb, std::ptr::null_mut()) }));
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);
}
+54 -22
View File
@@ -52,27 +52,35 @@ pub struct PropertySuiteV1 {
/// propSetInt
pub set_int: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, c_int) -> c_int,
/// propSetPointerN
pub set_pointer_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *mut c_void) -> c_int,
pub set_pointer_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *mut c_void) -> c_int,
/// propSetStringN
pub set_string_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *const c_char) -> c_int,
pub set_string_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const *const c_char) -> c_int,
/// propSetDoubleN
pub set_double_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_double) -> c_int,
pub set_double_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_double) -> c_int,
/// propSetIntN
pub set_int_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *const c_int) -> c_int,
/// propGetPointer
pub get_pointer: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
pub get_pointer:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
/// propGetString
pub get_string: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
pub get_string:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
/// propGetDouble
pub get_double: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_double) -> c_int,
/// propGetInt
pub get_int: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_int) -> c_int,
/// propGetPointerN
pub get_pointer_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
pub get_pointer_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_void) -> c_int,
/// propGetStringN
pub get_string_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
pub get_string_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut *mut c_char) -> c_int,
/// propGetDoubleN
pub get_double_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_double) -> c_int,
pub get_double_n:
unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_double) -> c_int,
/// propGetIntN
pub get_int_n: unsafe extern "C" fn(*mut c_void, *const c_char, c_int, *mut c_int) -> c_int,
/// propReset
@@ -141,8 +149,16 @@ fn idx(index: c_int) -> Result<usize, c_int> {
/// 先类型后索引的取值(HS 顺序:`fetchTypedProperty` 在
/// `getValueRaw` 之前,ofxhPropertySuite.cpp:787/794/257)。
fn get_value<'a>(props: &'a [Property], name: &str, index: c_int, kind: Kind) -> Result<&'a Value, c_int> {
let p = props.iter().find(|p| p.name == name).ok_or(status::ERR_UNKNOWN)?;
fn get_value<'a>(
props: &'a [Property],
name: &str,
index: c_int,
kind: Kind,
) -> Result<&'a Value, c_int> {
let p = props
.iter()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
// 首元素代理整条属性的类型(宿主只定义同构数组;HS 是定义期
// 固定类型,等价)。
let probe = p.values.first().ok_or(status::ERR_UNKNOWN)?;
@@ -157,7 +173,10 @@ fn get_value<'a>(props: &'a [Property], name: &str, index: c_int, kind: Kind) ->
/// propSet 通用实现:类型不符/未定义 → Unknown;越界 → BadIndex。
fn set_value(set: &PropertySet, name: &str, index: c_int, value: Value) -> Result<(), c_int> {
set.with_locked(|props| {
let p = props.iter_mut().find(|p| p.name == name).ok_or(status::ERR_UNKNOWN)?;
let p = props
.iter_mut()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
let probe = p.values.first().ok_or(status::ERR_UNKNOWN)?;
if Kind::of(probe) != Kind::of(&value) {
return Err(status::ERR_UNKNOWN);
@@ -236,9 +255,17 @@ unsafe extern "C" fn prop_set_int(
/// 从 C 数组读出 `count` 个元素并整体写入;count != 现有维度时
/// 整体替换(HS `setValueN` 的 resize 语义,ofxhPropertySuite.cpp:299-311)。
fn set_values(set: &PropertySet, name: &str, count: c_int, values: Vec<Value>) -> Result<(), c_int> {
fn set_values(
set: &PropertySet,
name: &str,
count: c_int,
values: Vec<Value>,
) -> Result<(), c_int> {
set.with_locked(|props| {
let p = props.iter_mut().find(|p| p.name == name).ok_or(status::ERR_UNKNOWN)?;
let p = props
.iter_mut()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
// count == 0 时无类型可探(HS 仍会做 fetchTypedProperty)。
if let Some(first) = p.values.first() {
if let Some(v) = values.first() {
@@ -381,11 +408,9 @@ unsafe extern "C" fn prop_get_pointer(
/// propGetString:写**内驻** C 串指针(OFX 契约:指向宿主内部存储,
/// 属性被下次修改前有效;克隆的 CString 指针会悬垂,绝不能给插件)。
fn get_string(set: &PropertySet, name: &str, index: c_int) -> Result<*mut c_char, c_int> {
set.with_locked(|props| {
match get_value(props, name, index, Kind::Str)? {
Value::String(s) => Ok(s.as_ptr() as *mut c_char),
_ => Err(status::FAILED),
}
set.with_locked(|props| match get_value(props, name, index, Kind::Str)? {
Value::String(s) => Ok(s.as_ptr() as *mut c_char),
_ => Err(status::FAILED),
})
}
@@ -459,9 +484,18 @@ unsafe extern "C" fn prop_get_int(
/// 批量读取:拷贝 min(count, dimension) 个(HS `getValueNRaw`,
/// ofxhPropertySuite.cpp:271-280,不报错)。
fn get_n(set: &PropertySet, name: &str, count: c_int, kind: Kind, write: impl Fn(&Value, usize)) -> Result<(), c_int> {
fn get_n(
set: &PropertySet,
name: &str,
count: c_int,
kind: Kind,
write: impl Fn(&Value, usize),
) -> Result<(), c_int> {
set.with_locked(|props| {
let p = props.iter().find(|p| p.name == name).ok_or(status::ERR_UNKNOWN)?;
let p = props
.iter()
.find(|p| p.name == name)
.ok_or(status::ERR_UNKNOWN)?;
let probe = p.values.first().ok_or(status::ERR_UNKNOWN)?;
if Kind::of(probe) != kind {
return Err(status::ERR_UNKNOWN);
@@ -615,5 +649,3 @@ pub fn suite_v1() -> &'static PropertySuiteV1 {
get_dimension: prop_get_dimension,
})
}
+13 -4
View File
@@ -99,7 +99,7 @@ pub fn suite_v1() -> &'static TimeLineSuiteV1 {
mod tests {
use super::*;
use crate::instance::{OfxRangeD, RenderScale};
use crate::suites::{RenderCtx, set_render_ctx};
use crate::suites::{set_render_ctx, RenderCtx};
#[test]
fn queries_forward_to_render_ctx() {
@@ -121,7 +121,10 @@ mod tests {
set_render_ctx(Some(RenderCtx {
time: 42.5,
scale: RenderScale { x: 1.0, y: 1.0 },
range: OfxRangeD { min: 10.0, max: 200.0 },
range: OfxRangeD {
min: 10.0,
max: 200.0,
},
}));
unsafe {
assert_eq!((s.get_time)(handle, &mut t), status::OK);
@@ -134,8 +137,14 @@ mod tests {
// 空 handle / 空 out。
unsafe {
assert_eq!((s.get_time)(std::ptr::null_mut(), &mut t), status::ERR_BAD_HANDLE);
assert_eq!((s.get_time)(handle, std::ptr::null_mut()), status::ERR_VALUE);
assert_eq!(
(s.get_time)(std::ptr::null_mut(), &mut t),
status::ERR_BAD_HANDLE
);
assert_eq!(
(s.get_time)(handle, std::ptr::null_mut()),
status::ERR_VALUE
);
}
}
}