app: project-explorer new-sequence button, 4K presets, keying node fix, color-picker canvases
- Project explorer header gains a 新建序列 button (opens the existing new-sequence dialog, seeded like the menu action). - Sequence presets gain 4K UHD (3840x2160@25) and 4K DCI (4096x2160@24); the sequence-properties dialog re-selects them on reopen. Format fields are also seedable from a probed footage format (the drop flow). - Chroma Key (and Color Difference Key) value() now box a ShaderJobPayload like Despill: the old OCIO-processor gate pushed nothing (the processor is never populated without the render bridge), so the traverser handed the clip NodeValue::None and the rendered frame lost the clip. The renderer resolves the OCIO stub at compile time from OCIO_SHADER_STUBS. End-to-end graph test: green key on a green frame keys out, red key keeps it. - The OFX color picker's SV palette / hue bar / preview / swatch canvases get size_full(): the bare canvases collapsed to zero height in the block layout, so the palette painted nothing (the reported 色板没显示). Regression test clicks the palette center and expects mid s/v.
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
use crate::factory::NodeMeta;
|
||||
use crate::node::{Category, NodeBehavior, NodeCore};
|
||||
use crate::nodes::jobs::ShaderJobPayload;
|
||||
|
||||
/// Key color input id (C++ `k_color_input`). Type: color; default
|
||||
/// `Color(0.0, 1.0, 0.0, 1.0)` (opaque green).
|
||||
@@ -64,14 +65,12 @@ pub const HIGHLIGHTS_INPUT: &str = "highlights_in";
|
||||
/// The C++ class derives from `OCIOBaseNode`, which owns the `tex_in`
|
||||
/// texture input (C++ `OCIOBaseNode::k_texture_input = "tex_in"`, the
|
||||
/// effect input), the color manager pointer, and the OCIO color
|
||||
/// processor handle; that state is held here via the shared
|
||||
/// `crate::nodes::ociobase` helper. The class has no other own members (the
|
||||
/// private `generate_processor()` is a method, not state).
|
||||
pub struct ChromaKeyNode {
|
||||
/// OCIO base state (C++ base class `OCIOBaseNode`: `manager_` and
|
||||
/// `processor_`).
|
||||
base: crate::nodes::ociobase::OcioBase,
|
||||
}
|
||||
/// processor handle. In the Rust model the OCIO shader stub is
|
||||
/// generated by the renderer at compile time (the `%1` marker is a
|
||||
/// type-id lookup in oakrender's OCIO shader-stub table), so the node
|
||||
/// needs no processor state: it behaves like the other keying nodes
|
||||
/// and pushes a plain shader job payload from `value()`.
|
||||
pub struct ChromaKeyNode;
|
||||
|
||||
/// Fragment shader (C++ loads the `:/shaders/chromakey.frag` resource
|
||||
/// in `get_shader_code`). Text copied verbatim from
|
||||
@@ -205,22 +204,6 @@ impl ChromaKeyNode {
|
||||
fn shader_frag() -> &'static str {
|
||||
SHADER_FRAG
|
||||
}
|
||||
|
||||
/// (Re)build the OCIO color processor (C++ `generate_processor()`):
|
||||
/// converts to the `cie_xyz_d65_interchange` output color space via
|
||||
/// `oakrender_color_processor_create_transform` and stores the result
|
||||
/// with [`OcioBase::set_processor`] when creation succeeds.
|
||||
fn generate_processor(&mut self, _core: &mut NodeCore) {
|
||||
// The C++ wraps the color manager, builds a transform to the
|
||||
// "cie_xyz_d65_interchange" output color space and creates the
|
||||
// processor through `oakrender_color_processor_create_transform`,
|
||||
// storing it with OcioBase::set_processor when `processor.ctx` is
|
||||
// non-null. Without a manager (the Rust model reaches the manager
|
||||
// through the oakrender bridge, absent here) the C++ guard
|
||||
// `if (manager())` fails, so this is a no-op and the processor
|
||||
// stays empty — `value()` then pushes nothing.
|
||||
// `// CPP-PARITY: chromakey.cpp` generate_processor.
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeBehavior for ChromaKeyNode {
|
||||
@@ -267,30 +250,33 @@ impl NodeBehavior for ChromaKeyNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Input value changed (C++ `InputValueChangedEvent`): the lower
|
||||
/// tolerance branch that would update the upper tolerance's `min`
|
||||
/// property is disabled in C++ (FIXME); unconditionally
|
||||
/// regenerates the OCIO color processor
|
||||
/// (`generate_processor()`).
|
||||
fn input_value_changed(&mut self, core: &mut NodeCore, input: &str, element: i32) {
|
||||
/// Input value changed (C++ `InputValueChangedEvent`): the C++ runs
|
||||
/// the lower-tolerance branch (commented out upstream, FIXME) and
|
||||
/// regenerates the OCIO processor. This node has no processor state
|
||||
/// left (the renderer resolves the OCIO stub at compile time), so
|
||||
/// nothing to do here.
|
||||
fn input_value_changed(&mut self, _core: &mut NodeCore, input: &str, element: i32) {
|
||||
let _ = (input, element);
|
||||
// The C++ lower-tolerance branch that mirrors the lower tolerance
|
||||
// into the upper tolerance's `min` property is commented out
|
||||
// (FIXME: breaks when the lower tolerance is keyframed/connected),
|
||||
// so only the processor regeneration remains.
|
||||
self.generate_processor(core);
|
||||
}
|
||||
|
||||
/// Evaluate outputs (C++ `value()`): no texture on `tex_in` ->
|
||||
/// push nothing; texture present and a valid OCIO processor ->
|
||||
/// push a `ColorTransformJob` wired with the processor, the input
|
||||
/// texture, this node as the custom-shader provider, and the
|
||||
/// function name `SceneLinearToCIEXYZ_d65`.
|
||||
/// push nothing; texture present -> push a `ShaderJob` over the
|
||||
/// whole input row.
|
||||
///
|
||||
/// The C++ class also overrides `config_changed()` (pure virtual
|
||||
/// on `OCIOBaseNode`) to regenerate the processor when the OCIO
|
||||
/// config changes; `NodeBehavior` has no equivalent hook — that
|
||||
/// wiring belongs to the facade/event layer.
|
||||
/// The C++ gates the job on a valid OCIO processor (a
|
||||
/// `ColorTransformJob` carrying the processor handle — the
|
||||
/// `%1` marker is filled with the processor's generated shader
|
||||
/// code). The Rust model has no processor bridge; the renderer
|
||||
/// resolves the OCIO stub itself: `oak_render::eval` looks the
|
||||
/// node's type id up in its OCIO shader-stub table and generates
|
||||
/// `SceneLinearToCIEXYZ_d65` at compile time, so the node pushes
|
||||
/// the plain job payload exactly like the other keying nodes
|
||||
/// (despill / color-difference key). The old gate — a null handle
|
||||
/// when `processor().is_some()` and *nothing* otherwise — left the
|
||||
/// node's output table empty (the processor is never populated
|
||||
/// without the bridge), so the traverser handed the downstream
|
||||
/// clip `NodeValue::None` and the rendered frame lost the whole
|
||||
/// clip.
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
@@ -298,29 +284,24 @@ impl NodeBehavior for ChromaKeyNode {
|
||||
time: oak_core::Rational,
|
||||
table: &mut crate::value::NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
match inputs.get(crate::nodes::ociobase::TEXTURE_INPUT) {
|
||||
Some(crate::value::NodeValue::Texture(_)) => {
|
||||
if self.base.processor().is_some() {
|
||||
// `// CPP-PARITY: chromakey.cpp` `value()` — the C++
|
||||
// builds a ColorTransformJob with the processor, the
|
||||
// input texture, this node as the custom-shader
|
||||
// provider and the function name
|
||||
// `SceneLinearToCIEXYZ_d65`. The Rust model has no
|
||||
// color-transform job payload: the renderer seam
|
||||
// resolves the deferred job from this null handle.
|
||||
table.push(
|
||||
crate::value::ValueType::Texture,
|
||||
crate::value::NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
// Texture present but no processor: the C++ pushes
|
||||
// nothing (unlike the base class, which would pass the
|
||||
// texture through).
|
||||
}
|
||||
_ => {}
|
||||
Some(crate::value::NodeValue::Texture(_)) => {}
|
||||
_ => return,
|
||||
}
|
||||
table.push(
|
||||
crate::value::ValueType::Texture,
|
||||
crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload {
|
||||
node_id: crate::id::NodeId::INVALID,
|
||||
time,
|
||||
iterations: 1,
|
||||
type_id: self.type_id().to_string(),
|
||||
shader_id: String::new(),
|
||||
effect_input: core.effect_input.clone(),
|
||||
params: inputs.clone(),
|
||||
iterative_input: String::new(),
|
||||
})),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
/// Shader code request (C++ `get_shader_code()`): reads the
|
||||
@@ -345,12 +326,7 @@ impl NodeBehavior for ChromaKeyNode {
|
||||
|
||||
/// Deep copy (C++ `copy()` via `NODE_DEFAULT_FUNCTIONS`).
|
||||
fn duplicate(&self, _core: &NodeCore) -> Option<Box<dyn NodeBehavior>> {
|
||||
// The C++ copy constructor copies the embedded OCIO base state;
|
||||
// a fresh base with no processor is the safe Rust port (the
|
||||
// processor is never populated without the render bridge).
|
||||
Some(Box::new(ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
}))
|
||||
Some(Box::new(ChromaKeyNode))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,12 +425,7 @@ pub fn create() -> (NodeCore, Box<dyn NodeBehavior>) {
|
||||
crate::value::NodeValue::Boolean(false),
|
||||
));
|
||||
|
||||
(
|
||||
core,
|
||||
Box::new(ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
}),
|
||||
)
|
||||
(core, Box::new(ChromaKeyNode))
|
||||
}
|
||||
|
||||
/// Register this node type (C++ factory entry for
|
||||
@@ -476,9 +447,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn input_names() {
|
||||
let n = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
};
|
||||
let n = ChromaKeyNode;
|
||||
assert_eq!(n.input_name(crate::nodes::ociobase::TEXTURE_INPUT), "Input");
|
||||
assert_eq!(n.input_name(GARBAGE_MATTE_INPUT), "Garbage Matte");
|
||||
assert_eq!(n.input_name(CORE_MATTE_INPUT), "Core Matte");
|
||||
@@ -542,9 +511,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shader_code_replaces_stub_marker() {
|
||||
let n = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
};
|
||||
let n = ChromaKeyNode;
|
||||
let stub = "float SceneLinearToCIEXYZ_d65(vec4 c){ return 0.0; }";
|
||||
let code = n.shader_code(stub).unwrap();
|
||||
assert!(!code.contains("%1"));
|
||||
@@ -553,9 +520,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn legacy_input_ids_remap_misspellings() {
|
||||
let n = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
};
|
||||
let n = ChromaKeyNode;
|
||||
assert_eq!(
|
||||
n.map_legacy_input_id("upper_tolerence_in"),
|
||||
UPPER_TOLERANCE_INPUT
|
||||
@@ -584,34 +549,33 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_texture_without_processor_pushes_nothing() {
|
||||
// Unlike the OCIO base, chroma key has no pass-through branch:
|
||||
// without a processor the C++ pushes nothing.
|
||||
fn value_texture_pushes_shader_job_payload() {
|
||||
let (core, behavior) = create();
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
crate::nodes::ociobase::TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
)]);
|
||||
// The payload carries the whole input row (the renderer packs the
|
||||
// uniforms from it), with the full node identity + effect input.
|
||||
let inputs = crate::value::NodeValueRow::from([
|
||||
(
|
||||
crate::nodes::ociobase::TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
),
|
||||
(COLOR_INPUT.to_string(), NodeValue::Color([1.0, 0.0, 0.0, 1.0])),
|
||||
]);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &inputs, Rational::new(0, 1), &mut table);
|
||||
assert!(table.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_texture_with_processor_pushes_deferred_job() {
|
||||
let core = NodeCore::new();
|
||||
let mut node = ChromaKeyNode {
|
||||
base: crate::nodes::ociobase::OcioBase::new(),
|
||||
let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else {
|
||||
panic!("expected a texture-typed value");
|
||||
};
|
||||
node.base
|
||||
.set_processor(Some(crate::handle::CHandle::null()));
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
crate::nodes::ociobase::TEXTURE_INPUT.to_string(),
|
||||
NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
)]);
|
||||
let mut table = NodeValueTable::default();
|
||||
node.value(&core, &inputs, Rational::new(0, 1), &mut table);
|
||||
assert!(table.get(ValueType::Texture).is_some());
|
||||
let payload =
|
||||
unsafe { crate::handle::get_checked::<ShaderJobPayload>(handle) }
|
||||
.expect("payload boxed behind the handle");
|
||||
assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.chromakey");
|
||||
assert_eq!(payload.shader_id, "");
|
||||
assert_eq!(payload.iterations, 1);
|
||||
assert_eq!(payload.effect_input, crate::nodes::ociobase::TEXTURE_INPUT);
|
||||
// The whole input row rides along, so the renderer can pack the
|
||||
// Key Color + tolerances uniforms from it.
|
||||
assert_eq!(payload.params.get(COLOR_INPUT), Some(&NodeValue::Color([1.0, 0.0, 0.0, 1.0])));
|
||||
assert!(payload.params.contains_key(crate::nodes::ociobase::TEXTURE_INPUT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
use crate::factory::NodeMeta;
|
||||
use crate::node::{Category, NodeBehavior, NodeCore};
|
||||
use crate::nodes::jobs::ShaderJobPayload;
|
||||
|
||||
/// Texture input id (C++ `k_texture_input`). Type: texture; flags:
|
||||
/// not-keyframable; this is the node's effect input.
|
||||
@@ -186,6 +187,13 @@ impl NodeBehavior for ColorDifferenceKeyNode {
|
||||
/// Evaluate outputs (C++ `value()`): no texture on `tex_in` ->
|
||||
/// push nothing; texture present -> push a `ShaderJob` with the
|
||||
/// whole input row inserted.
|
||||
///
|
||||
/// The job is boxed here as a [`ShaderJobPayload`] that the
|
||||
/// renderer's resolve hook executes and replaces with the result
|
||||
/// texture (exactly like despill). The previous null-handle
|
||||
/// "deferred job" marker was never resolved by the Rust renderer,
|
||||
/// so the node's output table handed the downstream clip a null
|
||||
/// texture and the rendered frame lost the whole clip.
|
||||
fn value(
|
||||
&self,
|
||||
core: &NodeCore,
|
||||
@@ -193,21 +201,22 @@ impl NodeBehavior for ColorDifferenceKeyNode {
|
||||
time: oak_core::Rational,
|
||||
table: &mut crate::value::NodeValueTable,
|
||||
) {
|
||||
let _ = (core, time);
|
||||
match inputs.get(TEXTURE_INPUT) {
|
||||
Some(crate::value::NodeValue::Texture(_)) => {}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// `// CPP-PARITY: colordifferencekey.cpp` `value()` — the C++
|
||||
// builds a ShaderJob over the whole input row and pushes
|
||||
// `tex->to_job(job)`. The Rust model has no shader-job payload:
|
||||
// the renderer seam resolves the deferred job (and the
|
||||
// `garbage_in_enabled`/`core_in_enabled` uniforms derived from
|
||||
// input presence) from this null handle.
|
||||
table.push(
|
||||
crate::value::ValueType::Texture,
|
||||
crate::value::NodeValue::Texture(crate::handle::CHandle::null()),
|
||||
crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload {
|
||||
node_id: crate::id::NodeId::INVALID,
|
||||
time,
|
||||
iterations: 1,
|
||||
type_id: self.type_id().to_string(),
|
||||
shader_id: String::new(),
|
||||
effect_input: core.effect_input.clone(),
|
||||
params: inputs.clone(),
|
||||
iterative_input: String::new(),
|
||||
})),
|
||||
None,
|
||||
);
|
||||
}
|
||||
@@ -368,7 +377,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_with_texture_pushes_deferred_shader_job() {
|
||||
fn value_with_texture_pushes_shader_job_payload() {
|
||||
let (core, behavior) = create();
|
||||
let inputs = crate::value::NodeValueRow::from([(
|
||||
TEXTURE_INPUT.to_string(),
|
||||
@@ -376,7 +385,16 @@ mod tests {
|
||||
)]);
|
||||
let mut table = NodeValueTable::default();
|
||||
behavior.value(&core, &inputs, Rational::new(0, 1), &mut table);
|
||||
assert!(table.get(ValueType::Texture).is_some());
|
||||
let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else {
|
||||
panic!("expected a texture-typed value");
|
||||
};
|
||||
let payload =
|
||||
unsafe { crate::handle::get_checked::<ShaderJobPayload>(handle) }
|
||||
.expect("payload boxed behind the handle");
|
||||
assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.colordifferencekey");
|
||||
assert_eq!(payload.iterations, 1);
|
||||
assert_eq!(payload.effect_input, TEXTURE_INPUT);
|
||||
assert!(payload.params.contains_key(TEXTURE_INPUT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user