Files
oak-editor/crates/oakplugin/src/instance.rs
T
Mike-Solar b36cbd6b6f refactor: purge CHandle from module internals (M14 R5)
Module-internal object references are Rust types now (values, Arc,
Mutex); CHandle remains only at the oakengine C-ABI boundary:

- oakundo: the global stack holds UndoStack/UndoCommand values
  directly (stack token is the static's address)
- oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands
  share the same allocation through Arc clones (readers in oakengine
  stubs and the app's graphops updated to lock)
- oaktask/oakstorage: sessions, write-through bindings and the
  database backend pass ProjectArc; the Session drops its manual
  release bookkeeping; nodeutil keeps the CHandle<->Arc boundary
  conversion (release_project restored for the app)
- oakcodec: handle.rs deleted outright (no facade entry needed it);
  texture/block placeholders are unit structs
- oakrender: copier's project handle is an identity u64; alive-count
  machinery removed; handle.rs is make_owned/get/get_mut only
- oakplugin: the instance registry is gone (its unregister key never
  matched, leaking weak entries); handle.rs is the RefBox boundary type
- oaknode/oakcommon: only dead guard/borrow helpers removed; external
  payload handles (texture/processor) documented as the boundary

Flake hunts landed along the way: the audio recording test serializes
on the shared manager lock with a normalized state; the autocacher
cancel test uses a slow producer so cancellation is deterministic.
2026-08-17 16:40:15 +08:00

1014 lines
33 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//! 插件实例: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<oakundo::undocommand::UndoCommand>,
/// 事务内回写次数(标签计数)。
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<Instance>>` 管理生命周期(`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<Plugin>,
/// 上下文(kOfxImageEffectContext*)。
pub context: String,
/// 参数实例集(createInstance 后由 describe 产物实例化)。
pub params: ParamSetInstance,
/// clip 实例集(含 "Output"Box 保证句柄地址稳定)。
pub clips: Vec<Box<ClipInstance>>,
/// 绑定的 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<Option<OfxRangeD>>,
/// facade 注册的进度回调(oakplugin_instance_set_progress_cb)。
pub progress_cb: std::sync::Mutex<Option<(crate::progress::ProgressFn, usize)>>,
/// facade 取消标记(oakplugin_instance_cancel)。
pub cancel: std::sync::atomic::AtomicBool,
/// paramEditBegin/End 的编辑事务(undo 分组)。
pub edit: std::sync::Mutex<EditTransaction>,
/// 实例级渲染串行化(对应 C++ `OlivePluginInstance::mutex`
/// pluginrenderer.cpp:1436-1444 的 instance_lock——渲染路径非
/// 线程安全,并发 render 必须互斥)。
pub render_lock: std::sync::Mutex<()>,
}
/// 实例销毁路径:先通知 destroyInstance action,再摘除 param 回写登记。
/// `Arc<RefBox<Instance>>` 归零时 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 预置 clipPrefsStuffsframeRate=1、premult、
/// fieldOrder、continuousSamples、frameVaryingHS:1697-1710);
/// 2. 预置 per-clip 的 "OfxImageClipPropComponents_<name>" 等
/// (默认 RGBA/Float/PAR=1HS:1717-1727);
/// 3. 调 actionout args 即协商产物,HS:1708-1718);
/// 4. 回灌:per-clip 分量/位深/像素比写回 clip 实例属性,
/// 输出帧率/fielding/premult 记入实例(HS:1721-1740)。
pub fn get_clip_preferences(&self) -> crate::error::Result<ClipPreferences> {
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. clipPrefsStuffsHS: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-1727phase 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 actionHS: ofxhImageEffect.cpp:1087-1130
/// in args = time + renderScaleout args = RegionOfDefinition)。
pub fn get_region_of_definition(
&self,
time: f64,
scale: RenderScale,
) -> crate::error::Result<OfxRectD> {
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_<clipname>"HS:
/// ofxhImageEffect.cpp getRegionsOfInterest 的 per-clip 前缀)。
pub fn get_regions_of_interest(
&self,
time: f64,
scale: RenderScale,
region: OfxRectD,
) -> crate::error::Result<Vec<OfxRectD>> {
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)) 表示本帧
/// 直接透传该输入 clipNone 表示需要真正 render。
///
/// 参照 HS: ofxhImageEffect.cpp:1378-1450in args = time + scale +
/// renderWindow + fieldToRenderout args = kOfxImageEffectPropIsIdentity
/// (输入 clip 名)+ kOfxPropTime(透传时间,可改)。
pub fn is_identity(&self, time: f64) -> crate::error::Result<Option<(f64, String)>> {
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 预定义:IsIdentityString+ TimeDouble)。
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=1ofxGPURender.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 actionCPU 路径)。`output` 为已按协商格式分配的输出
/// 图像(Arc:插件经 clipGetImage(Output) 取用,render 驱动与
/// suite 各自持强引用);输入 clip 的图像由调用方经
/// [`ClipInstance::fetch_image`] 备好。GL 路径见
/// [`Instance::render_gl`]。
///
/// 声明原为 `&mut Image`:输出图像需与 suite 共享强引用(TLS +
/// LIVE_IMAGES 表),&mut 无法表达——改为 `Arc<Image>`。
pub fn render(
&self,
time: f64,
scale: RenderScale,
window: OfxRectD,
output: std::sync::Arc<crate::image::Image>,
) -> 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 actionM11 §4ofxGPURender.h 的 Render 动作 GL
/// 模式)。与 [`Instance::render`]CPU)并存。
///
/// 前置契约(render 驱动遵守):调用方已把 `renderer` 的 GL
/// 上下文置为 currentofxGPURender.h "OpenGL Current Context"
/// 宿主只在 Render/Begin/EndSequenceRender/Attach/Detach 期间
/// 要求上下文 current——本实现的约定是 oakrender 的 PluginJob
/// 路径在进入前做好),且 `output_texture` 已附着为渲染器输出
/// 目标(等价 C++ `PluginRenderer::attach_output_texture`)。
///
/// action 序列:kOfxActionOpenGLContextAttached → renderin args
/// 带 kOfxImageEffectPropOpenGLEnabled=1)→
/// kOfxActionOpenGLContextDetachedofxGPURender.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 actionM11 §4ofxColour.h:243-283):
/// 宿主给插件一份偏好色彩空间列表,插件回写输出 clip 的色彩
/// 空间(可为 "OfxColourspace_<clip>" 交叉引用)。
///
/// 返回解析后的输出色彩空间:交叉引用解析为所引 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<String> {
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<Value> = 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>" 交叉引用为所引 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 argsframeRange +
/// frameStep + renderScale + sequentialRenderStatus)。GL 模式
/// 变体 [`Instance::begin_sequence_render_gl`] 额外带
/// kOfxImageEffectPropOpenGLEnabledofxGPURender.h:117)。
pub fn begin_sequence_render(&self, range: OfxRangeD) -> crate::error::Result<()> {
self.begin_sequence_inner(range, false)
}
/// GL 模式的 beginSequenceRenderin 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<RefBox<Instance>>`
/// 归零驱动;此处只做 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<OfxRectD> {
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,
}