diff --git a/crates/oak-plugin/src/descriptor.rs b/crates/oak-plugin/src/descriptor.rs index 5656128f2..10685f62a 100644 --- a/crates/oak-plugin/src/descriptor.rs +++ b/crates/oak-plugin/src/descriptor.rs @@ -78,6 +78,14 @@ impl ClipDescriptor { props.set_one(CLIP_OPTIONAL, Value::Int(0)); props.set_one(CLIP_IS_MASK, Value::Int(0)); props.set_one(CLIP_FIELD_EXTRACTION, Value::String(cs(CLIP_FIELD_DOUBLED))); + // kOfxImageClipPropFieldOrder(ofxhClip.cpp 的 clip 描述符必备 + // 属性;ofxs `Clip::getFieldOrder()` 无默认值强读——Mirror 这类 + // 场感知插件缺它直接 MissingHostFeature)。取值是 + // kOfxImageField* 族(ofxImageEffect.h:1288),默认无场。 + props.set_one( + "OfxImageClipPropFieldOrder", + Value::String(cs("OfxFieldNone")), + ); props.set_one(CLIP_SUPPORTS_TILES, Value::Int(1)); // ofxColour(M11 §4):clip 色彩空间属性族。Colourspace 由宿主 // 在实例化时写入(输入 clip = 工作空间 ACEScg);Preferred 由 diff --git a/crates/oak-plugin/src/host.rs b/crates/oak-plugin/src/host.rs index 89bc30c11..f05a071d5 100644 --- a/crates/oak-plugin/src/host.rs +++ b/crates/oak-plugin/src/host.rs @@ -1232,6 +1232,44 @@ impl Host { }) } + /// 上下文选择 + 实例化(filter 优先;实例化失败时按 general → + /// tracker → paint → 其余支持上下文的顺序回退——TrackerPM 这类 + /// 插件 describe 支持 filter,但其 createInstance 在非 + /// paint/tracker/general 上下文抓不到 Mask clip 会炸 + /// BadHandle;Natron 上它天然以 tracker 上下文实例化)。 + /// 返回实际使用的上下文与实例;所有上下文都失败时返回最后一个 + /// 错误。 + pub fn create_instance_preferred( + &self, + identifier: &str, + ) -> crate::error::Result<(String, Arc>)> { + let plugin = self.cache.find(identifier).ok_or(crate::error::Error::NotFound)?; + let mut order: Vec<&str> = Vec::with_capacity(plugin.contexts.len()); + for pref in [ + "OfxImageEffectContextFilter", + "OfxImageEffectContextGeneral", + "OfxImageEffectContextTracker", + "OfxImageEffectContextPaint", + ] { + if plugin.contexts.iter().any(|c| c == pref) { + order.push(pref); + } + } + for c in &plugin.contexts { + if !order.contains(&c.as_str()) { + order.push(c); + } + } + let mut last_err = crate::error::Error::Failed("插件无支持上下文".into()); + for ctx in order { + match self.create_instance(identifier, Some(ctx)) { + Ok(inst) => return Ok((ctx.to_string(), inst)), + Err(e) => last_err = e, + } + } + Err(last_err) + } + /// 按标识创建实例(describeInContext → createInstance)。 /// 参照 HS: ofxhImageEffectAPI.cpp:200-238(describeInContext 的 /// in-args 只有 kOfxImageEffectPropContext)与 diff --git a/crates/oak-plugin/src/node_factory.rs b/crates/oak-plugin/src/node_factory.rs index 358aa5696..393924114 100644 --- a/crates/oak-plugin/src/node_factory.rs +++ b/crates/oak-plugin/src/node_factory.rs @@ -753,27 +753,13 @@ pub fn register_plugin_nodes() -> Vec { continue; }; - // 上下文选择:filter 优先,否则第一个(factory.cpp:171-177)。 - let context = if plugin.contexts.iter().any(|c| c == "OfxImageEffectContextFilter") { - "OfxImageEffectContextFilter".to_string() - } else { - match plugin.contexts.first() { - Some(c) => c.clone(), - None => { - eprintln!( - "Skipping OFX plugin with no contexts: {}", - plugin.identifier - ); - continue; - } - } - }; - - // 元数据实例(name/description;建完即弃)。describeInContext - // 失败的插件无法实例化(常见于只支持 Vegas 立体声等厂商套件 - // 的插件)——记录原因,不静默跳过。 - let inst = match host.create_instance(&plugin.identifier, Some(&context)) { - Ok(inst) => inst, + // 元数据实例(name/description;建完即弃)。上下文选择: + // filter 优先、实例化失败按序回退(TrackerPM 这类插件的 + // createInstance 只在 tracker/general/paint 下能跑)。所有 + // 上下文都失败的插件无法实例化(常见于只支持 Vegas 立体声 + // 等厂商套件的插件)——记录原因,不静默跳过。 + let (context, inst) = match host.create_instance_preferred(&plugin.identifier) { + Ok(pair) => pair, Err(e) => { eprintln!("[ofx] {}: instance creation failed: {e}", plugin.identifier); continue; @@ -955,15 +941,13 @@ fn shared_plugin_instance(identifier: &str) -> Option { return Some(id); } let host = Host::global(); - let plugin = host.cache.find(identifier)?; - // 上下文选择与 [`register_plugin_nodes`] 一致(filter 优先,否则 - // 首个支持上下文;factory.cpp:171-177)。 - let context = if plugin.contexts.iter().any(|c| c == "OfxImageEffectContextFilter") { - "OfxImageEffectContextFilter".to_string() - } else { - plugin.contexts.first()?.clone() - }; - let inst = host.create_instance(identifier, Some(&context)).ok()?; + if host.cache.find(identifier).is_none() { + return None; + } + // 上下文选择:filter 优先、实例化失败按序回退(与 + // [`register_plugin_nodes`] 同一策略,见 + // [`Host::create_instance_preferred`])。 + let (_context, inst) = host.create_instance_preferred(identifier).ok()?; let id = register_instance(inst); cache .lock() diff --git a/crates/oak-plugin/src/param.rs b/crates/oak-plugin/src/param.rs index a8670920c..ece53a59e 100644 --- a/crates/oak-plugin/src/param.rs +++ b/crates/oak-plugin/src/param.rs @@ -479,7 +479,10 @@ impl ParamDef { ); props.set_one(P_ANIMATES, Value::Int(1)); props.set_one(P_PARAMETRIC_DIMENSION, Value::Int(1)); - props.define(P_PARAMETRIC_UI_COLOUR, vec![]); + // UIColour 默认未设:不预定义(插件首写时按 OFX 语义 + // 隐式创建并允许逐位追加;预定义空数组会让 index >= 1 + // 的写入炸 BadIndex——ColorLookup/HueCorrect 的 + // describeInContext 失败根因)。 props.set_one(P_PARAMETRIC_INTERACT_BG, Value::Pointer(std::ptr::null_mut())); props.define( P_PARAMETRIC_RANGE, @@ -913,7 +916,9 @@ mod tests { d.props.get(P_PARAMETRIC_RANGE, 1), Some(Value::Double(1.0)) )); - assert_eq!(d.props.dimension(P_PARAMETRIC_UI_COLOUR), 0); + // UIColour 默认未设:属性不存在(插件首写隐式创建),而不是 + // 预定义的空数组(空数组会把 index >= 1 的写入挡成 BadIndex)。 + assert!(d.props.get(P_PARAMETRIC_UI_COLOUR, 0).is_none()); assert!(matches!( d.props.get(P_PARAMETRIC_INTERACT_BG, 0), Some(Value::Pointer(p)) if p.is_null() diff --git a/crates/oak-plugin/src/suites/property.rs b/crates/oak-plugin/src/suites/property.rs index 247b91dce..417341105 100644 --- a/crates/oak-plugin/src/suites/property.rs +++ b/crates/oak-plugin/src/suites/property.rs @@ -224,6 +224,9 @@ fn set_value(set: &PropertySet, name: &str, index: c_int, value: Value) -> Resul // 没有类型探针:按 HS 的 index == size 追加语义直接 push。 if p.values.is_empty() { if idx != 0 { + if std::env::var_os("OAK_OFX_TRACE").is_some() { + eprintln!("[ofx] propSet into empty predefined array: {name}[{idx}]"); + } return Err(status::ERR_BAD_INDEX); } p.values.push(value); @@ -239,6 +242,9 @@ fn set_value(set: &PropertySet, name: &str, index: c_int, value: Value) -> Resul p.values.push(value); return Ok(()); } + if idx >= p.values.len() && std::env::var_os("OAK_OFX_TRACE").is_some() { + eprintln!("[ofx] propSet out of range: {name}[{idx}] (dim {})", p.values.len()); + } let slot = p.values.get_mut(idx).ok_or(status::ERR_BAD_INDEX)?; *slot = value; Ok(())