// Oak Video Editor - Non-Linear Video Editor // Copyright (C) 2026 Oak Team // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! 插件实例:action 调用面。 //! //! 对应 C++ 的 `OlivePluginInstance`。语义参照: //! HS: ofxhImageEffect.cpp `Instance`(action 调用序列与参数集组装)。 use std::ffi::CString; use std::sync::Arc; use crate::clip::ClipInstance; use crate::host::Plugin; use crate::param::ParamSetInstance; use crate::property::PropertySet; /// paramEditBegin/End 的编辑事务状态(undo 分组)。对应 C++ /// oliveplugininstance.cpp 的 `edit_depth_`/`edit_command_`: /// 事务内的多次参数回写合并为一条 multi 命令,editEnd 时整体 /// redo 后释放。 #[repr(C)] pub struct EditTransaction { /// 嵌套深度(editBegin/End 必须配对)。 depth: i32, /// 事务累积的 multi 命令(None = 尚无子命令)。 multi: Option, /// 事务内回写次数(标签计数)。 param_count: i32, /// 第一条回写的标签。 first_label: String, } impl EditTransaction { /// 空事务(无嵌套、无累积命令)。 pub fn new() -> Self { Self { depth: 0, multi: None, param_count: 0, first_label: String::new(), } } } /// 时间范围(OFX 规范双精度秒)。 #[derive(Clone, Copy, Debug, Default)] pub struct OfxRangeD { /// 起始(含)。 pub min: f64, /// 结束(含)。 pub max: f64, } /// 矩形(规范坐标,double)。 #[derive(Clone, Copy, Debug, Default)] pub struct OfxRectD { /// 左。 pub x1: f64, /// 上。 pub y1: f64, /// 右。 pub x2: f64, /// 下。 pub y2: f64, } /// 像素比/场序等渲染参量(render action 的 in_args 子集)。 #[derive(Clone, Copy, Debug)] pub struct RenderScale { /// x 方向渲染比例。 pub x: f64, /// y 方向渲染比例。 pub y: f64, } /// 插件实例。`Arc>` 管理生命周期(`RefBox` 为 facade /// 边界类型,见 [`crate::handle`]);param 桥按 props 地址映射反查 /// ([`crate::suites::param`]),节点绑定经 [`crate::node`] 身份注册表。 /// /// `#[repr(C)]` + props 在偏移 0(句柄约定,见 [`crate::suites::tag`]; /// 实例期 effect/param-set handle 即 `&props`)。 #[repr(C)] pub struct Instance { /// 实例级属性集(偏移 0,句柄约定)。 pub props: PropertySet, /// 所属插件。 pub plugin: Arc, /// 上下文(kOfxImageEffectContext*)。 pub context: String, /// 参数实例集(createInstance 后由 describe 产物实例化)。 pub params: ParamSetInstance, /// clip 实例集(含 "Output";Box 保证句柄地址稳定)。 pub clips: Vec>, /// 绑定的 oaknode 节点身份(param 桥写入;0 = 未绑定)。 pub node_identity: std::sync::atomic::AtomicUsize, /// destroyInstance action 是否已通知(析构幂等门;Drop 驱动; /// 公开:宿主 shutdown 与测试都要置位)。 pub destroyed: std::sync::atomic::AtomicBool, /// beginSequenceRender 的时间域(timeline getTimeBounds 用; /// endSequenceRender 清除)。 pub sequence_range: std::sync::Mutex>, /// facade 注册的进度回调(oakplugin_instance_set_progress_cb)。 pub progress_cb: std::sync::Mutex>, /// facade 取消标记(oakplugin_instance_cancel)。 pub cancel: std::sync::atomic::AtomicBool, /// paramEditBegin/End 的编辑事务(undo 分组)。 pub edit: std::sync::Mutex, /// 实例级渲染串行化(对应 C++ `OlivePluginInstance::mutex`; /// pluginrenderer.cpp:1436-1444 的 instance_lock——渲染路径非 /// 线程安全,并发 render 必须互斥)。 pub render_lock: std::sync::Mutex<()>, } /// 实例销毁路径:先通知 destroyInstance action,再摘除 param 回写登记。 /// (`Arc>` 归零时 Drop 触发——action 通知必须在 /// 对象析构前发出。) impl Drop for Instance { fn drop(&mut self) { 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); } } impl Instance { /// 绑定 oaknode 节点(C++ `set_node_handle` 的 Rust 侧;装配期由 /// facade/测试调用)。`identity` 为 [`crate::node::register_node`] /// 返回的 oaknode 节点身份([`oaknode::id::NodeId::identity`] 的 /// 打包值,经 [`crate::node::node_from_identity`] 反查); /// 0 解除绑定。 pub fn bind_node(&self, identity: usize) { self.node_identity .store(identity, std::sync::atomic::Ordering::Relaxed); } /// paramEditBegin:进入编辑事务(可嵌套;首次进入重置事务状态)。 pub fn edit_begin(&self) { let mut e = self.edit.lock().unwrap_or_else(|e| e.into_inner()); e.depth += 1; if e.depth == 1 { e.multi = None; e.param_count = 0; e.first_label.clear(); } } /// paramEditEnd:退出编辑事务;最外层结束时把累积的 multi 命令 /// redo 生效并释放(本 crate 无 undo 栈——命令在 oakundo 侧 /// 提交,见 C++ `submit_undo_command` 的 fallback 分支)。 pub fn edit_end(&self) { let mut e = self.edit.lock().unwrap_or_else(|e| e.into_inner()); if e.depth > 0 { e.depth -= 1; } if e.depth == 0 { if let Some(mut multi) = e.multi.take() { multi.redo_now(); // drop(multi):释放 multi 与子命令(命令值语义)。 drop(multi); } e.param_count = 0; e.first_label.clear(); } } /// 是否处于编辑事务内(param 桥回写据此决定打包成 multi)。 pub fn in_edit(&self) -> bool { self.edit.lock().unwrap_or_else(|e| e.into_inner()).depth > 0 } /// 事务感知的 undo 提交(param 桥回写用;对应 C++ /// oliveplugininstance.cpp:390-414 `submit_undo_command`): /// 编辑事务内并入 multi(子命令立即 redo 生效,值即时可见), /// 否则单命令 redo 后释放。 pub(crate) fn submit_undo_command(&self, mut cmd: oakundo::undocommand::UndoCommand, label: &str) { if self.in_edit() { let mut e = self.edit.lock().unwrap_or_else(|e| e.into_inner()); if e.multi.is_none() { e.multi = Some(oakundo::undocommand::UndoCommand::multi()); } e.param_count += 1; if e.first_label.is_empty() { e.first_label = label.to_string(); } cmd.redo_now(); e.multi .as_mut() .expect("multi 已在上面初始化") .multi_add_child(cmd); } else { cmd.redo_now(); drop(cmd); } } /// getClipPreferences action,返回协商结果(输出 clip 的分量/ /// 位深/像素比/field 与帧率)。协商顺序逐行对照 /// HS: ofxhImageEffect.cpp `Instance::getClipPreferences`—— /// 当年调试重灾区,实现时必须附行号注释。 /// /// 协商流程(HS: ofxhImageEffect.cpp:1686-1740): /// 1. out args 预置 clipPrefsStuffs(frameRate=1、premult、 /// fieldOrder、continuousSamples、frameVarying,HS:1697-1710); /// 2. 预置 per-clip 的 "OfxImageClipPropComponents_" 等 /// (默认 RGBA/Float/PAR=1,HS:1717-1727); /// 3. 调 action(out args 即协商产物,HS:1708-1718); /// 4. 回灌:per-clip 分量/位深/像素比写回 clip 实例属性, /// 输出帧率/fielding/premult 记入实例(HS:1721-1740)。 pub fn get_clip_preferences(&self) -> crate::error::Result { 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(); out.set_one(PROP_FRAME_RATE, Value::Double(1.0)); out.set_one(PROP_PREMULT, Value::String(CString::new("").unwrap())); out.set_one(PROP_FIELD_ORDER, Value::String(CString::new("").unwrap())); out.set_one(PROP_CONTINUOUS_SAMPLES, Value::Int(0)); out.set_one(PROP_FRAME_VARYING, Value::Int(0)); // 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), ); } // 3. action。 let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let empty = PropertySet::new(); let stat = unsafe { self.plugin .call_action(ACTION_GET_CLIP_PREFERENCES, inst_handle, &empty, &out) }; if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT { return Err(crate::error::Error::Failed(format!( "getClipPreferences 失败:{stat}" ))); } // 4. 回灌(HS:1721-1740:每 clip 的协商结果写回实例属性)。 let mut output_components = "OfxImageComponentRGBA".to_string(); let mut output_bit_depth = "OfxBitDepthFloat".to_string(); let mut output_par = 1.0; for clip in &self.clips { let comp_name = crate::host::clip_pref_prop(CLIP_PREF_COMPONENTS, &clip.name); let depth_name = crate::host::clip_pref_prop(CLIP_PREF_DEPTH, &clip.name); let par_name = crate::host::clip_pref_prop(CLIP_PREF_PAR, &clip.name); let comps = out .get(&comp_name, 0) .map(|v| match v { Value::String(s) => s.to_string_lossy().into_owned(), _ => "OfxImageComponentRGBA".to_string(), }) .unwrap_or_else(|| "OfxImageComponentRGBA".to_string()); let depth = out .get(&depth_name, 0) .map(|v| match v { Value::String(s) => s.to_string_lossy().into_owned(), _ => "OfxBitDepthFloat".to_string(), }) .unwrap_or_else(|| "OfxBitDepthFloat".to_string()); let par = out .get(&par_name, 0) .and_then(|v| match v { Value::Double(d) => Some(d), _ => None, }) .unwrap_or(1.0); clip.props.set_one( crate::image::K_IMAGE_EFFECT_PROP_COMPONENTS, Value::String(CString::new(comps.clone()).unwrap()), ); clip.props.set_one( crate::image::K_IMAGE_EFFECT_PROP_PIXEL_DEPTH, Value::String(CString::new(depth.clone()).unwrap()), ); clip.props .set_one("OfxImagePropPixelAspectRatio", Value::Double(par)); if clip.name == "Output" { output_components = comps; output_bit_depth = depth; output_par = par; } } let frame_rate = out .get(PROP_FRAME_RATE, 0) .and_then(|v| match v { Value::Double(d) => Some(d), _ => None, }) .unwrap_or(24.0); let field = out .get(PROP_FIELD_ORDER, 0) .map(|v| match v { Value::String(s) => s.to_string_lossy().into_owned(), _ => String::new(), }) .unwrap_or_default(); Ok(ClipPreferences { output_components, output_bit_depth, pixel_aspect_ratio: output_par, frame_rate, field, }) } /// getRegionOfDefinition action(HS: ofxhImageEffect.cpp:1087-1130: /// in args = time + renderScale;out args = RegionOfDefinition)。 pub fn get_region_of_definition( &self, time: f64, scale: RenderScale, ) -> crate::error::Result { 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)], ); // out args 预定义(HS 行为:宿主先建属性表,插件只管写—— // 缺失属性上插件的 propSet 会失败被忽略)。 let out = PropertySet::new(); out.define( PROP_ROD, vec![ Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), ], ); let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let stat = unsafe { self.plugin .call_action(ACTION_GET_ROD, inst_handle, &in_args, &out) }; if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT { return Err(crate::error::Error::Failed(format!("getRoD 失败:{stat}"))); } read_rect(&out, PROP_ROD).ok_or(crate::error::Error::Failed("getRoD 无输出".into())) } /// getRegionsOfInterest action:输入 clip 的 RoI 写入 out_args /// 属性集,返回值即各 clip 的 RoI 列表(与 `clips` 顺序一致)。 /// /// 约定名 "OfxImageEffectPropRegionOfInterest_"(HS: /// ofxhImageEffect.cpp getRegionsOfInterest 的 per-clip 前缀)。 pub fn get_regions_of_interest( &self, time: f64, scale: RenderScale, region: OfxRectD, ) -> crate::error::Result> { 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_ROI, vec![ Value::Double(region.x1), Value::Double(region.y1), Value::Double(region.x2), Value::Double(region.y2), ], ); // out args 预定义 per-clip ROI 属性(HS 约定名前缀)。 let out = PropertySet::new(); for clip in &self.clips { let name = format!("OfxImageEffectPropRegionOfInterest_{}", clip.name); out.define( &name, vec![ Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), ], ); } let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let stat = unsafe { self.plugin .call_action(ACTION_GET_ROI, inst_handle, &in_args, &out) }; if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT { return Err(crate::error::Error::Failed(format!("getRoI 失败:{stat}"))); } let mut rois = Vec::with_capacity(self.clips.len()); for clip in &self.clips { let name = format!("OfxImageEffectPropRegionOfInterest_{}", clip.name); let rect = read_rect(&out, &name) // 未设置 → 默认整个 region(OFX 语义:插件可不写)。 .unwrap_or(region); rois.push(rect); } Ok(rois) } /// isIdentity action:返回 Some((time, input_clip_name)) 表示本帧 /// 直接透传该输入 clip;None 表示需要真正 render。 /// /// 参照 HS: ofxhImageEffect.cpp:1378-1450:in args = time + scale + /// renderWindow + fieldToRender;out args = kOfxImageEffectPropIsIdentity /// (输入 clip 名)+ kOfxPropTime(透传时间,可改)。 pub fn is_identity(&self, time: f64) -> crate::error::Result> { 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_WINDOW, vec![ Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), Value::Double(0.0), ], ); 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())); out.set_one(PROP_TIME, Value::Double(time)); let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let stat = unsafe { self.plugin .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}" ))); } let identity = out.get(PROP_IS_IDENTITY, 0); match identity { Some(Value::String(s)) if !s.is_empty() => { let clip_name = s.to_string_lossy().into_owned(); let t = out .get(PROP_TIME, 0) .and_then(|v| match v { Value::Double(d) => Some(d), _ => None, }) .unwrap_or(time); Ok(Some((t, clip_name))) } _ => Ok(None), } } /// 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::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_WINDOW, vec![ Value::Double(window.x1), Value::Double(window.y1), Value::Double(window.x2), Value::Double(window.y2), ], ); 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)); in_args.set_one(PROP_NO_SPATIAL_AWARENESS, Value::Int(0)); if gl_enabled { in_args.set_one(crate::host::PROP_GL_ENABLED, Value::Int(1)); } in_args } /// render action(CPU 路径)。`output` 为已按协商格式分配的输出 /// 图像(Arc:插件经 clipGetImage(Output) 取用,render 驱动与 /// suite 各自持强引用);输入 clip 的图像由调用方经 /// [`ClipInstance::fetch_image`] 备好。GL 路径见 /// [`Instance::render_gl`]。 /// /// 声明原为 `&mut Image`:输出图像需与 suite 共享强引用(TLS + /// LIVE_IMAGES 表),&mut 无法表达——改为 `Arc`。 pub fn render( &self, time: f64, scale: RenderScale, window: OfxRectD, output: std::sync::Arc, ) -> crate::error::Result<()> { use crate::host::ACTION_RENDER; let in_args = Self::render_in_args(time, scale, window, false); // facade 取消标记:render 入口即短路(多帧循环的帧间取消)。 if self.cancel.load(std::sync::atomic::Ordering::Relaxed) { return Err(crate::error::Error::Failed("已取消".into())); } // 渲染上下文(TLS):timeline/progress/clipGetImage 读取。 let range = self .sequence_range .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_current_output(Some(output.clone())); // 进度报告器(facade 回调 → Progress suite)。 if let Some((cb, userdata)) = self .progress_cb .lock() .unwrap_or_else(|e| e.into_inner()) .clone() { crate::suites::progress::set_current(Some(unsafe { crate::progress::ProgressReporter::new(cb, userdata as *mut std::ffi::c_void) })); } let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let out = PropertySet::new(); let stat = unsafe { self.plugin .call_action(ACTION_RENDER, inst_handle, &in_args, &out) }; // 清理 TLS(action 返回后渲染上下文必须消失)。 crate::suites::set_current_output(None); crate::suites::set_render_ctx(None); crate::suites::progress::set_current(None); if stat != crate::suites::status::OK { return Err(crate::error::Error::Failed(format!("render 失败:{stat}"))); } Ok(()) } /// GL render action(M11 §4;ofxGPURender.h 的 Render 动作 GL /// 模式)。与 [`Instance::render`](CPU)并存。 /// /// 前置契约(render 驱动遵守):调用方已把 `renderer` 的 GL /// 上下文置为 current(ofxGPURender.h "OpenGL Current Context": /// 宿主只在 Render/Begin/EndSequenceRender/Attach/Detach 期间 /// 要求上下文 current——本实现的约定是 oakrender 的 PluginJob /// 路径在进入前做好),且 `output_texture` 已附着为渲染器输出 /// 目标(等价 C++ `PluginRenderer::attach_output_texture`)。 /// /// action 序列:kOfxActionOpenGLContextAttached → render(in args /// 带 kOfxImageEffectPropOpenGLEnabled=1)→ /// kOfxActionOpenGLContextDetached(ofxGPURender.h:345-371; /// attach/detach 必须配对)。渲染结果留在 GL 输出纹理上(插件 /// 直接画进附着目标),宿主不做 CPU 回读;GL 模式下 /// clipGetImage(Output) 不可用(插件按规范走 OpenGL suite—— /// ofxGPURender.h "the effect SHOULD access all its images through /// the OpenGL suite")。render 返回前对未释放的输入 GL 纹理做 /// 兜底清理([`crate::suites::gl_render::purge_leftovers`])。 pub fn render_gl( &self, time: f64, scale: RenderScale, window: OfxRectD, renderer: crate::render::Renderer, output_texture: crate::render::Texture, ) -> crate::error::Result<()> { use crate::host::ACTION_RENDER; let in_args = Self::render_in_args(time, scale, window, true); if self.cancel.load(std::sync::atomic::Ordering::Relaxed) { return Err(crate::error::Error::Failed("已取消".into())); } let range = self .sequence_range .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 })); // GL 纹理位深协商(插件 kOfxOpenGLPropPixelDepth → 管线 F32; // 调用方已按协商门(render 驱动)决定走 GL)。 let gl_pixel_depth = crate::suites::gl_render::pick_gl_pixel_depth(&self.plugin.descriptor.props) .unwrap_or("OfxBitDepthFloat"); crate::suites::set_gl_ctx(Some(crate::suites::GlCtx { renderer, output_texture, gl_pixel_depth, })); // GL 模式无 CPU 输出图像(current_output 保持 None)。 if let Some((cb, userdata)) = self .progress_cb .lock() .unwrap_or_else(|e| e.into_inner()) .clone() { crate::suites::progress::set_current(Some(unsafe { crate::progress::ProgressReporter::new(cb, userdata as *mut std::ffi::c_void) })); } let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let empty = PropertySet::new(); // attach(失败仍继续——插件可忽略;规范允许 ReplyDefault)。 let stat = unsafe { 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); crate::suites::set_render_ctx(None); crate::suites::progress::set_current(None); return Err(crate::error::Error::Failed(format!( "OpenGLContextAttached 失败:{stat}" ))); } let out = PropertySet::new(); let stat = unsafe { self.plugin .call_action(ACTION_RENDER, inst_handle, &in_args, &out) }; // detach(必须与 attach 配对;ofxGPURender.h:345-346)。 unsafe { self.plugin.call_action( crate::host::ACTION_GL_CONTEXT_DETACHED, inst_handle, &empty, &empty, ) }; // 兜底:插件遗漏 clipFreeTexture 的输入纹理在此释放。 crate::suites::gl_render::purge_leftovers(); crate::suites::set_gl_ctx(None); crate::suites::set_render_ctx(None); crate::suites::progress::set_current(None); if stat != crate::suites::status::OK { return Err(crate::error::Error::Failed(format!( "GL render 失败:{stat}" ))); } Ok(()) } /// GetOutputColourspace action(M11 §4;ofxColour.h:243-283): /// 宿主给插件一份偏好色彩空间列表,插件回写输出 clip 的色彩 /// 空间(可为 "OfxColourspace_" 交叉引用)。 /// /// 返回解析后的输出色彩空间:交叉引用解析为所引 clip 的实际 /// 色彩空间(ofxColour.h:142-141 的跨 clip 引用约定); /// REPLY_DEFAULT → 第一个输入 clip 的色彩空间(ofxColour.h:278-279)。 /// 调用方(render 驱动)随后把结果写回输出 clip( /// [`Instance::set_output_colourspace`])。 pub fn get_output_colourspace(&self, preferred: &[String]) -> crate::error::Result { use crate::host::{ ACTION_GET_OUTPUT_COLOURSPACE, PROP_CLIP_COLOURSPACE, PROP_CLIP_PREFERRED_COLOURSPACES, }; use crate::property::Value; let in_args = PropertySet::new(); let values: Vec = preferred .iter() .map(|s| Value::String(CString::new(s.as_str()).unwrap())) .collect(); in_args.define(PROP_CLIP_PREFERRED_COLOURSPACES, values); let out = PropertySet::new(); out.set_one( PROP_CLIP_COLOURSPACE, Value::String(CString::new("").unwrap()), ); let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let stat = unsafe { self.plugin .call_action(ACTION_GET_OUTPUT_COLOURSPACE, inst_handle, &in_args, &out) }; if stat == crate::suites::status::OK { let value = out .get(PROP_CLIP_COLOURSPACE, 0) .map(|v| match v { Value::String(s) => s.to_string_lossy().into_owned(), _ => String::new(), }) .unwrap_or_default(); if value.is_empty() { return Err(crate::error::Error::Failed( "GetOutputColourspace 未回写色彩空间".into(), )); } Ok(self.resolve_colourspace(value)) } else if stat == crate::suites::status::REPLY_DEFAULT { // 插件未实现 → 用第一个输入 clip 的色彩空间(规范默认)。 let first_input = self .clips .iter() .find(|c| c.name != "Output") .map(|c| { c.props .get(PROP_CLIP_COLOURSPACE, 0) .map(|v| match v { Value::String(s) => s.to_string_lossy().into_owned(), _ => String::new(), }) .unwrap_or_default() }) .unwrap_or_default(); Ok(first_input) } else { Err(crate::error::Error::Failed(format!( "GetOutputColourspace 失败:{stat}" ))) } } /// 把输出 clip 的色彩空间写回(GetOutputColourspace 后的落点; /// ofxColour.h:262-264 "the host must set kOfxImageClipPropColourspace /// on the instance's output clip to the value from outArgs")。 pub fn set_output_colourspace(&self, colourspace: &str) { use crate::property::Value; if let Some(output) = self.clips.iter().find(|c| c.name == "Output") { output.props.set_one( crate::host::PROP_CLIP_COLOURSPACE, Value::String(CString::new(colourspace).unwrap()), ); } } /// 解析 "OfxColourspace_" 交叉引用为所引 clip 的实际色彩 /// 空间;非交叉引用原样返回。 pub fn resolve_colourspace(&self, value: String) -> String { const PREFIX: &str = "OfxColourspace_"; if let Some(clip_name) = value.strip_prefix(PREFIX) { self.clips .iter() .find(|c| c.name == clip_name) .map(|c| { c.props .get(crate::host::PROP_CLIP_COLOURSPACE, 0) .map(|v| match v { crate::property::Value::String(s) => s.to_string_lossy().into_owned(), _ => value.clone(), }) .unwrap_or(value.clone()) }) .unwrap_or(value) } else { value } } /// begin/endSequenceRender 配对(帧序列渲染的前后括号; /// HS: ofxhImageEffect.cpp:473-513 的 in args:frameRange + /// frameStep + renderScale + sequentialRenderStatus)。GL 模式 /// 变体 [`Instance::begin_sequence_render_gl`] 额外带 /// kOfxImageEffectPropOpenGLEnabled(ofxGPURender.h:117)。 pub fn begin_sequence_render(&self, range: OfxRangeD) -> crate::error::Result<()> { self.begin_sequence_inner(range, false) } /// GL 模式的 beginSequenceRender(in args 带 OpenGLEnabled=1)。 pub fn begin_sequence_render_gl(&self, range: OfxRangeD) -> crate::error::Result<()> { self.begin_sequence_inner(range, true) } fn begin_sequence_inner(&self, range: OfxRangeD, gl_enabled: bool) -> crate::error::Result<()> { use crate::host::ACTION_BEGIN_SEQUENCE; let in_args = Self::sequence_in_args(range, gl_enabled); self.sequence_range .lock() .unwrap_or_else(|e| e.into_inner()) .replace(range); let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let out = PropertySet::new(); let stat = unsafe { self.plugin .call_action(ACTION_BEGIN_SEQUENCE, inst_handle, &in_args, &out) }; if stat != crate::suites::status::OK { return Err(crate::error::Error::Failed(format!( "beginSequenceRender 失败:{stat}" ))); } Ok(()) } /// 序列 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; let in_args = PropertySet::new(); 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.set_one(PROP_SEQUENTIAL_RENDER, Value::Int(1)); if gl_enabled { in_args.set_one(crate::host::PROP_GL_ENABLED, Value::Int(1)); } in_args } /// 见 [`Instance::begin_sequence_render`]。 pub fn end_sequence_render(&self, range: OfxRangeD) -> crate::error::Result<()> { self.end_sequence_inner(range, false) } /// GL 模式的 endSequenceRender。 pub fn end_sequence_render_gl(&self, range: OfxRangeD) -> crate::error::Result<()> { self.end_sequence_inner(range, true) } fn end_sequence_inner(&self, range: OfxRangeD, gl_enabled: bool) -> crate::error::Result<()> { use crate::host::ACTION_END_SEQUENCE; let in_args = Self::sequence_in_args(range, gl_enabled); let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let out = PropertySet::new(); let stat = unsafe { self.plugin .call_action(ACTION_END_SEQUENCE, inst_handle, &in_args, &out) }; self.sequence_range .lock() .unwrap_or_else(|e| e.into_inner()) .take(); if stat != crate::suites::status::OK { return Err(crate::error::Error::Failed(format!( "endSequenceRender 失败:{stat}" ))); } Ok(()) } /// 销毁(destroyInstance action)。析构由 `Arc>` /// 归零驱动;此处只做 action 通知,幂等([`Instance::drop`] 的 /// `destroyed` 门保证只发一次)。 pub(crate) fn notify_destroy(&self) { use crate::host::ACTION_DESTROY_INSTANCE; let inst_handle = crate::suites::tag::make( &self.props as *const PropertySet, crate::suites::tag::INSTANCE, ); let empty = PropertySet::new(); // 通知失败只记日志(销毁路径不可回滚)。 let stat = unsafe { self.plugin .call_action(ACTION_DESTROY_INSTANCE, inst_handle, &empty, &empty) }; if stat != crate::suites::status::OK && stat != crate::suites::status::REPLY_DEFAULT { eprintln!("destroyInstance 通知失败:{stat}"); } } } /// 从属性集读矩形(Double×4)。 fn read_rect(props: &PropertySet, name: &str) -> Option { use crate::property::Value; let x1 = match props.get(name, 0)? { Value::Double(d) => d, _ => return None, }; let y1 = match props.get(name, 1)? { Value::Double(d) => d, _ => return None, }; let x2 = match props.get(name, 2)? { Value::Double(d) => d, _ => return None, }; let y2 = match props.get(name, 3)? { Value::Double(d) => d, _ => return None, }; Some(OfxRectD { x1, y1, x2, y2 }) } /// getClipPreferences 的协商结果。 #[derive(Clone, Debug)] pub struct ClipPreferences { /// 输出分量(kOfxImageComponentRGBA 等)。 pub output_components: String, /// 输出位深(kOfxBitDepthFloat 等;全链路 F32 下恒为 float)。 pub output_bit_depth: String, /// 像素比。 pub pixel_aspect_ratio: f64, /// 帧率。 pub frame_rate: f64, /// field 处理模式(kOfxImageField*)。 pub field: String, }