plugin: instantiate more real-world plugins (FieldOrder, UIColour, context fallback)
Three independent host-side gaps kept real plugins from instantiating: - clips now define OfxImageClipPropFieldOrder (default OfxFieldNone): ofxs Clip::getFieldOrder() is a strong read with no default, so field-aware plugins (Mirror) threw PropertyUnknownToHost -> MissingHostFeature from createInstance - OfxParamPropParametricUIColour is no longer predefined as an empty array: the OFX implicit-create semantics let the plugin's first propSetDouble create the property and grow it index by index, while the empty predefined array rejected every write at index >= 1 with BadIndex (ColorLookup, HueCorrect describeInContext) - Host::create_instance_preferred: context selection with fallback (filter -> general -> tracker -> paint -> rest). TrackerPM advertises the filter context but its createInstance fetches a Mask clip that its describe only defines for tracker/general/paint; Natron simply instantiates it in the tracker context, and now so do we. Both the node-registration scan and the shared instance factory use it. Still unsupported, by design: Premult/Unpremult (this openfx-misc build hard-requires the Nuke multi-plane suite + dynamic choices) and the stereo view plugins (Switch/anaglyph/joinViews/etc. need Natron's isNatron multi-clip folding or view rendering). Verified: ColorLookup, HueCorrect, Mirror, TrackerPM now instantiate; full oak-plugin suite green.
This commit is contained in:
@@ -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 由
|
||||
|
||||
@@ -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<RefBox<Instance>>)> {
|
||||
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)与
|
||||
|
||||
@@ -753,27 +753,13 @@ pub fn register_plugin_nodes() -> Vec<String> {
|
||||
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<u64> {
|
||||
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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user