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:
@@ -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!(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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。
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user