diff --git a/crates/oak-node/src/footage.rs b/crates/oak-node/src/footage.rs index 507a1a722..d6139a402 100644 --- a/crates/oak-node/src/footage.rs +++ b/crates/oak-node/src/footage.rs @@ -390,11 +390,11 @@ impl NodeBehavior for FootageBehavior { } else { (self.filename.clone(), stream.index) }; - let payload = crate::jobs::FootageJobPayload { + let payload = crate::jobs::Job::FootageJob(crate::jobs::FootageJobPayload { filename, stream_index, time, - }; + }); table.push( ValueType::Texture, NodeValue::Texture(crate::handle::make_owned(payload)), @@ -880,7 +880,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::footage_job(handle) } .expect("footage output boxes a FootageJobPayload"); assert_eq!(payload.filename, "clip.mov"); assert_eq!(payload.stream_index, 1); diff --git a/crates/oak-node/src/jobs.rs b/crates/oak-node/src/jobs.rs index 18426a3e2..b8b71acc4 100644 --- a/crates/oak-node/src/jobs.rs +++ b/crates/oak-node/src/jobs.rs @@ -24,19 +24,28 @@ use oak_core::color::ColorProcessor; use oak_core::Rational; use crate::id::NodeId; use crate::nodes::plugin::PluginJobPayload; -use crate::value::NodeValueRow; +use crate::value::{NodeValue, NodeValueRow}; -/// Job types +/// The render-job enum (C++ the `RenderJob` class hierarchy of +/// `app/render/job/*.h`). /// -/// The payloads travel boxed inside `Texture` values during graph -/// evaluation (the renderer probes the box's payload type at resolve -/// time); the graph-shaped job model lands in v0.6. - -pub enum Job{ +/// One `Job` travels boxed inside a texture-typed [`NodeValue`] during +/// graph evaluation; the render seam probes the box for this type, +/// recurses into the payload's input values, then dispatches on the +/// variant. Wrapping every payload in one enum lets the seam recognize +/// and recurse into job boxes without knowing which node produced them. +#[derive(Clone, Debug)] +pub enum Job { + /// A footage decode request ([`FootageJobPayload`]). FootageJob(FootageJobPayload), + /// A GPU shader pass ([`ShaderJobPayload`]). ShaderJob(ShaderJobPayload), + /// An OFX plugin render ([`PluginJobPayload`]). PluginJob(PluginJobPayload), - ColorTransformJob(ColorTransformJobPayload) + /// An OCIO color transform ([`ColorTransformJobPayload`]). + ColorTransformJob(ColorTransformJobPayload), + /// A disk frame-cache read ([`CacheJobPayload`]). + CacheJob(CacheJobPayload), } /// C++ `FootageJob` payload: the decode request a footage node emits at @@ -87,7 +96,7 @@ pub struct ShaderJobPayload { /// the same immutable instance. The input texture value rides along /// (C++ `t->to_job(job)` wraps the texture the job applies to). #[derive(Clone)] -pub struct ColorTransformJobPayload{ +pub struct ColorTransformJobPayload { /// The OCIO processor to apply (C++ `ColorTransformJob::processor`). pub color_processor: std::sync::Arc, /// The input texture value (C++ the texture `to_job` was called on). @@ -96,6 +105,33 @@ pub struct ColorTransformJobPayload{ pub time: Rational, } +impl std::fmt::Debug for ColorTransformJobPayload { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ColorTransformJobPayload") + .field("input", &self.input) + .field("time", &self.time) + .finish_non_exhaustive() + } +} + +/// C++ `CacheJob` payload (`app/render/job/cachejob.h`): read one frame +/// of the disk frame cache. The seam loads the frame stored at `path` +/// (as named by `oakrender::cache::PlaybackCache::frame_cache_path`); +/// when the file is missing or unreadable it substitutes `fallback`, +/// the value the cache node was fed, which the seam has already +/// resolved by the time the load is attempted. +#[derive(Clone, Debug)] +pub struct CacheJobPayload { + /// Frame-cache file path (C++ `CacheJob::filename()`). + pub path: String, + /// Request time in media seconds. + pub time: Rational, + /// Value to substitute when the cache file cannot be read (C++ + /// `CacheJob::fallback()`); boxed so the job can carry a + /// texture-typed value without bloating the enum. + pub fallback: Box, +} + impl Default for FootageJobPayload { fn default() -> Self { FootageJobPayload { @@ -120,3 +156,106 @@ impl Default for ShaderJobPayload { } } } + +impl Job { + /// Borrow the payload when this is a [`Job::FootageJob`]. + pub fn as_footage(&self) -> Option<&FootageJobPayload> { + match self { + Job::FootageJob(payload) => Some(payload), + _ => None, + } + } + + /// Borrow the payload when this is a [`Job::ShaderJob`]. + pub fn as_shader(&self) -> Option<&ShaderJobPayload> { + match self { + Job::ShaderJob(payload) => Some(payload), + _ => None, + } + } + + /// Borrow the payload when this is a [`Job::PluginJob`]. + pub fn as_plugin(&self) -> Option<&PluginJobPayload> { + match self { + Job::PluginJob(payload) => Some(payload), + _ => None, + } + } + + /// Borrow the payload when this is a [`Job::ColorTransformJob`]. + pub fn as_color_transform(&self) -> Option<&ColorTransformJobPayload> { + match self { + Job::ColorTransformJob(payload) => Some(payload), + _ => None, + } + } + + /// Borrow the payload when this is a [`Job::CacheJob`]. + pub fn as_cache(&self) -> Option<&CacheJobPayload> { + match self { + Job::CacheJob(payload) => Some(payload), + _ => None, + } + } +} + +/// Borrow the [`Job`] boxed in `h`; `None` when the handle is empty or +/// boxes anything else (a real texture, say) — the probe the render +/// seam makes on texture-channel values. +/// +/// # Safety +/// `h` must be empty or a live handle created by +/// [`crate::handle::make_owned`]/[`crate::handle::make_owned_with`] for +/// the duration of the call. +pub unsafe fn job_ref(h: &crate::handle::CHandle) -> Option<&Job> { + // SAFETY: the caller guarantees liveness; `get_checked` adds the + // boxed-type check and returns `None` for empty handles. + unsafe { crate::handle::get_checked::(h) } +} + +/// Borrow the [`FootageJobPayload`] boxed in `h` (a +/// [`Job::FootageJob`] probe); `None` for empty handles or any other +/// payload. +/// +/// # Safety +/// Same contract as [`job_ref`]. +pub unsafe fn footage_job(h: &crate::handle::CHandle) -> Option<&FootageJobPayload> { + unsafe { job_ref(h) }.and_then(Job::as_footage) +} + +/// Borrow the [`ShaderJobPayload`] boxed in `h` (a [`Job::ShaderJob`] +/// probe); `None` for empty handles or any other payload. +/// +/// # Safety +/// Same contract as [`job_ref`]. +pub unsafe fn shader_job(h: &crate::handle::CHandle) -> Option<&ShaderJobPayload> { + unsafe { job_ref(h) }.and_then(Job::as_shader) +} + +/// Borrow the [`PluginJobPayload`] boxed in `h` (a [`Job::PluginJob`] +/// probe); `None` for empty handles or any other payload. +/// +/// # Safety +/// Same contract as [`job_ref`]. +pub unsafe fn plugin_job(h: &crate::handle::CHandle) -> Option<&PluginJobPayload> { + unsafe { job_ref(h) }.and_then(Job::as_plugin) +} + +/// Borrow the [`ColorTransformJobPayload`] boxed in `h` (a +/// [`Job::ColorTransformJob`] probe); `None` for empty handles or any +/// other payload. +/// +/// # Safety +/// Same contract as [`job_ref`]. +pub unsafe fn color_transform_job(h: &crate::handle::CHandle) -> Option<&ColorTransformJobPayload> { + unsafe { job_ref(h) }.and_then(Job::as_color_transform) +} + +/// Borrow the [`CacheJobPayload`] boxed in `h` (a [`Job::CacheJob`] +/// probe); `None` for empty handles or any other payload. +/// +/// # Safety +/// Same contract as [`job_ref`]. +pub unsafe fn cache_job(h: &crate::handle::CHandle) -> Option<&CacheJobPayload> { + unsafe { job_ref(h) }.and_then(Job::as_cache) +} diff --git a/crates/oak-node/src/nodes/blur.rs b/crates/oak-node/src/nodes/blur.rs index 2e87abb22..a3ea0a4ff 100644 --- a/crates/oak-node/src/nodes/blur.rs +++ b/crates/oak-node/src/nodes/blur.rs @@ -18,7 +18,7 @@ //! `olive::BlurFilterNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -390,7 +390,7 @@ impl NodeBehavior for BlurFilterNode { // the main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations, @@ -399,7 +399,7 @@ impl NodeBehavior for BlurFilterNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -684,7 +684,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("texture expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.blur"); assert_eq!( diff --git a/crates/oak-node/src/nodes/checkerboard.rs b/crates/oak-node/src/nodes/checkerboard.rs index 5ef30788e..f4f9e30a0 100644 --- a/crates/oak-node/src/nodes/checkerboard.rs +++ b/crates/oak-node/src/nodes/checkerboard.rs @@ -26,7 +26,7 @@ //! upstream `color1`/`color2`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Base texture input id (the shared generator-with-merge base). Type: @@ -153,7 +153,7 @@ impl NodeBehavior for CheckerBoardNode { } } - let job = crate::handle::make_owned(ShaderJobPayload { + let job = crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -162,7 +162,7 @@ impl NodeBehavior for CheckerBoardNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - }); + })); super::generatorwithmerge::GeneratorWithMerge::push_mergable_job(inputs, job, table); } @@ -290,7 +290,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.checkerboard"); assert_eq!(payload.shader_id, "checkerboard"); @@ -325,7 +325,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!( payload.params.get(SIZE_INPUT), @@ -350,7 +350,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let merge = unsafe { crate::handle::get_checked::(&handle) } + let merge = unsafe { crate::jobs::shader_job(&handle) } .expect("merge job payload expected"); assert_eq!(merge.shader_id, "mrg"); assert_eq!(merge.iterations, 1); @@ -362,7 +362,7 @@ mod tests { // The checkerboard job is nested as the merge's blend texture. match merge.params.get(crate::nodes::merge::BLEND_INPUT) { Some(NodeValue::Texture(blend)) => { - let nested = unsafe { crate::handle::get_checked::(blend) } + let nested = unsafe { crate::jobs::shader_job(blend) } .expect("nested job payload boxed"); assert_eq!(nested.shader_id, "checkerboard"); assert_eq!( diff --git a/crates/oak-node/src/nodes/chromakey.rs b/crates/oak-node/src/nodes/chromakey.rs index b3ad904e6..cdf67fa65 100644 --- a/crates/oak-node/src/nodes/chromakey.rs +++ b/crates/oak-node/src/nodes/chromakey.rs @@ -18,7 +18,7 @@ //! `olive::ChromaKeyNode`, derived from `olive::OCIOBaseNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Key color input id (C++ `k_color_input`). Type: color; default @@ -290,7 +290,7 @@ impl NodeBehavior for ChromaKeyNode { } table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -299,7 +299,7 @@ impl NodeBehavior for ChromaKeyNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } @@ -566,7 +566,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.chromakey"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/clamp.rs b/crates/oak-node/src/nodes/clamp.rs index 1e188545e..7f0a774d7 100644 --- a/crates/oak-node/src/nodes/clamp.rs +++ b/crates/oak-node/src/nodes/clamp.rs @@ -41,7 +41,7 @@ //! resolve). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -149,7 +149,7 @@ impl NodeBehavior for ClampNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -158,7 +158,7 @@ impl NodeBehavior for ClampNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -280,7 +280,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.clamp"); assert_eq!(payload.effect_input, TEXTURE_INPUT); @@ -305,7 +305,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.params.get(MAX_INPUT), Some(&NodeValue::Float(0.5))); } diff --git a/crates/oak-node/src/nodes/colorbars.rs b/crates/oak-node/src/nodes/colorbars.rs index 6c34263b9..94b37f613 100644 --- a/crates/oak-node/src/nodes/colorbars.rs +++ b/crates/oak-node/src/nodes/colorbars.rs @@ -29,7 +29,7 @@ //! `standard_in` combo (75% / 100%). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Base texture input id (the shared generator-with-merge base). Type: @@ -200,7 +200,7 @@ impl NodeBehavior for ColorBarsNode { ); } - let job = crate::handle::make_owned(ShaderJobPayload { + let job = crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -209,7 +209,7 @@ impl NodeBehavior for ColorBarsNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - }); + })); super::generatorwithmerge::GeneratorWithMerge::push_mergable_job(inputs, job, table); } @@ -303,7 +303,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.colorbars"); assert_eq!(payload.shader_id, "colorbars"); @@ -326,7 +326,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!( payload.params.get(STANDARD_INPUT), @@ -347,13 +347,13 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let merge = unsafe { crate::handle::get_checked::(&handle) } + let merge = unsafe { crate::jobs::shader_job(&handle) } .expect("merge job payload expected"); assert_eq!(merge.shader_id, "mrg"); assert_eq!(merge.effect_input, BASE_INPUT); match merge.params.get(crate::nodes::merge::BLEND_INPUT) { Some(NodeValue::Texture(blend)) => { - let nested = unsafe { crate::handle::get_checked::(blend) } + let nested = unsafe { crate::jobs::shader_job(blend) } .expect("nested job payload boxed"); assert_eq!(nested.shader_id, "colorbars"); } diff --git a/crates/oak-node/src/nodes/colorcorrect.rs b/crates/oak-node/src/nodes/colorcorrect.rs index 17dfc99b5..8f47eec73 100644 --- a/crates/oak-node/src/nodes/colorcorrect.rs +++ b/crates/oak-node/src/nodes/colorcorrect.rs @@ -42,7 +42,7 @@ //! gamma is the reciprocal exponent of the reference's gamma pass. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -207,7 +207,7 @@ impl NodeBehavior for ColorCorrectNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -216,7 +216,7 @@ impl NodeBehavior for ColorCorrectNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -378,7 +378,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.colorcorrect"); assert_eq!(payload.shader_id, ""); @@ -419,7 +419,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!( payload.params.get(CONTRAST_INPUT), diff --git a/crates/oak-node/src/nodes/colordifferencekey.rs b/crates/oak-node/src/nodes/colordifferencekey.rs index 1f7b69d5c..b0ba8cc25 100644 --- a/crates/oak-node/src/nodes/colordifferencekey.rs +++ b/crates/oak-node/src/nodes/colordifferencekey.rs @@ -19,7 +19,7 @@ //! `olive::ColorDifferenceKeyNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -207,7 +207,7 @@ impl NodeBehavior for ColorDifferenceKeyNode { } table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -216,7 +216,7 @@ impl NodeBehavior for ColorDifferenceKeyNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } @@ -389,7 +389,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.colordifferencekey"); assert_eq!(payload.iterations, 1); diff --git a/crates/oak-node/src/nodes/colormatrix.rs b/crates/oak-node/src/nodes/colormatrix.rs index 0b11aaadc..032bebcee 100644 --- a/crates/oak-node/src/nodes/colormatrix.rs +++ b/crates/oak-node/src/nodes/colormatrix.rs @@ -19,7 +19,7 @@ //! semantics only, no code copied). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -142,7 +142,7 @@ impl NodeBehavior for ColorMatrixNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -151,7 +151,7 @@ impl NodeBehavior for ColorMatrixNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -255,7 +255,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); let mut identity = [0.0f64; 16]; for i in 0..4 { @@ -287,7 +287,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!( payload.params.get(MATRIX_UNIFORM), @@ -305,7 +305,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); let Some(NodeValue::Matrix(m)) = payload.params.get(MATRIX_UNIFORM) else { panic!("matrix uniform expected"); diff --git a/crates/oak-node/src/nodes/cornerpindistortnode.rs b/crates/oak-node/src/nodes/cornerpindistortnode.rs index 67e6c93d2..c789d662b 100644 --- a/crates/oak-node/src/nodes/cornerpindistortnode.rs +++ b/crates/oak-node/src/nodes/cornerpindistortnode.rs @@ -19,7 +19,7 @@ //! `olive::CornerPinDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -331,7 +331,7 @@ impl NodeBehavior for CornerPinDistortNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -340,7 +340,7 @@ impl NodeBehavior for CornerPinDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -600,7 +600,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::shader_job(handle) } .expect("cornerpin output boxes a ShaderJobPayload"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.cornerpin"); assert_eq!(payload.shader_id, ""); @@ -624,7 +624,7 @@ mod tests { panic!("texture expected"); }; assert!( - unsafe { crate::handle::get_checked::(h) }.is_some(), + unsafe { crate::jobs::shader_job(h) }.is_some(), "a moved corner must push the cornerpin shader job" ); } diff --git a/crates/oak-node/src/nodes/cropdistortnode.rs b/crates/oak-node/src/nodes/cropdistortnode.rs index bce898a23..5570df164 100644 --- a/crates/oak-node/src/nodes/cropdistortnode.rs +++ b/crates/oak-node/src/nodes/cropdistortnode.rs @@ -19,7 +19,7 @@ //! `olive::CropDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -216,7 +216,7 @@ impl NodeBehavior for CropDistortNode { // locates the main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -225,7 +225,7 @@ impl NodeBehavior for CropDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: TEXTURE_INPUT.to_string(), - })), + }))), None, ); } else { @@ -503,7 +503,7 @@ mod tests { NodeValue::Texture(h) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.crop"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/despill.rs b/crates/oak-node/src/nodes/despill.rs index c35d96077..00692ce18 100644 --- a/crates/oak-node/src/nodes/despill.rs +++ b/crates/oak-node/src/nodes/despill.rs @@ -18,7 +18,7 @@ //! `olive::DespillNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -205,7 +205,7 @@ impl NodeBehavior for DespillNode { ); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -214,7 +214,7 @@ impl NodeBehavior for DespillNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -351,7 +351,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.despill"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/dilate.rs b/crates/oak-node/src/nodes/dilate.rs index 124984c3c..cb98b1de4 100644 --- a/crates/oak-node/src/nodes/dilate.rs +++ b/crates/oak-node/src/nodes/dilate.rs @@ -19,7 +19,7 @@ //! semantics only, no code copied). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -140,7 +140,7 @@ impl NodeBehavior for DilateNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -149,7 +149,7 @@ impl NodeBehavior for DilateNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -271,7 +271,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.dilate"); assert_eq!(payload.shader_id, ""); @@ -293,7 +293,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!( payload.params.get(RADIUS_INPUT), diff --git a/crates/oak-node/src/nodes/dirblur.rs b/crates/oak-node/src/nodes/dirblur.rs index 39096a349..574f02f3e 100644 --- a/crates/oak-node/src/nodes/dirblur.rs +++ b/crates/oak-node/src/nodes/dirblur.rs @@ -27,7 +27,7 @@ //! `fading = 0` case). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -183,7 +183,7 @@ impl NodeBehavior for DirBlurNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -192,7 +192,7 @@ impl NodeBehavior for DirBlurNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -259,7 +259,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/displaytransform.rs b/crates/oak-node/src/nodes/displaytransform.rs index c1758ef69..08d2dc4cb 100644 --- a/crates/oak-node/src/nodes/displaytransform.rs +++ b/crates/oak-node/src/nodes/displaytransform.rs @@ -384,7 +384,7 @@ mod tests { panic!("job row expected"); }; assert!(unsafe { - crate::handle::get_checked::(handle) + crate::jobs::color_transform_job(handle) } .is_some()); } diff --git a/crates/oak-node/src/nodes/dissolve.rs b/crates/oak-node/src/nodes/dissolve.rs index c3c743502..a409e6d1a 100644 --- a/crates/oak-node/src/nodes/dissolve.rs +++ b/crates/oak-node/src/nodes/dissolve.rs @@ -32,7 +32,7 @@ //! alphabetically first key of the job's `BTreeMap` row). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// First ("from") texture input id. Type: texture; flags: @@ -167,7 +167,7 @@ impl NodeBehavior for DissolveNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -176,7 +176,7 @@ impl NodeBehavior for DissolveNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -260,7 +260,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/dropshadowfilter.rs b/crates/oak-node/src/nodes/dropshadowfilter.rs index 37cdc944a..22781fc0f 100644 --- a/crates/oak-node/src/nodes/dropshadowfilter.rs +++ b/crates/oak-node/src/nodes/dropshadowfilter.rs @@ -19,7 +19,7 @@ //! `olive::DropShadowFilter`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -273,7 +273,7 @@ impl NodeBehavior for DropShadowFilter { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations, @@ -282,7 +282,7 @@ impl NodeBehavior for DropShadowFilter { effect_input: core.effect_input.clone(), params, iterative_input, - })), + }))), None, ); } @@ -434,7 +434,7 @@ mod tests { behavior.value(&core, &inputs, Rational::new(0, 1), &mut table); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.dropshadow"); assert_eq!(payload.shader_id, ""); @@ -458,7 +458,7 @@ mod tests { behavior.value(&core, &inputs, Rational::new(0, 1), &mut table); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.iterations, 1); assert_eq!(payload.iterative_input, ""); diff --git a/crates/oak-node/src/nodes/edgedetect.rs b/crates/oak-node/src/nodes/edgedetect.rs index 983d8f11a..68366de92 100644 --- a/crates/oak-node/src/nodes/edgedetect.rs +++ b/crates/oak-node/src/nodes/edgedetect.rs @@ -19,7 +19,7 @@ //! parameter semantics only, no code copied). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -137,7 +137,7 @@ impl NodeBehavior for EdgeDetectNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -146,7 +146,7 @@ impl NodeBehavior for EdgeDetectNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -207,7 +207,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/erode.rs b/crates/oak-node/src/nodes/erode.rs index 04c63592b..cd0df79d4 100644 --- a/crates/oak-node/src/nodes/erode.rs +++ b/crates/oak-node/src/nodes/erode.rs @@ -19,7 +19,7 @@ //! semantics only, no code copied). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -140,7 +140,7 @@ impl NodeBehavior for ErodeNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -149,7 +149,7 @@ impl NodeBehavior for ErodeNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -271,7 +271,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.erode"); assert_eq!(payload.shader_id, ""); @@ -293,7 +293,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!( payload.params.get(RADIUS_INPUT), diff --git a/crates/oak-node/src/nodes/flipdistortnode.rs b/crates/oak-node/src/nodes/flipdistortnode.rs index 1f198cc42..965198069 100644 --- a/crates/oak-node/src/nodes/flipdistortnode.rs +++ b/crates/oak-node/src/nodes/flipdistortnode.rs @@ -19,7 +19,7 @@ //! `olive::FlipDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -138,7 +138,7 @@ impl NodeBehavior for FlipDistortNode { // input key locates the main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -147,7 +147,7 @@ impl NodeBehavior for FlipDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: TEXTURE_INPUT.to_string(), - })), + }))), None, ); } else { @@ -271,7 +271,7 @@ mod tests { NodeValue::Texture(h) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.flip"); assert_eq!(payload.shader_id, ""); @@ -294,7 +294,7 @@ mod tests { NodeValue::Texture(h) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.flip"); assert_eq!(payload.iterations, 1); diff --git a/crates/oak-node/src/nodes/gamma.rs b/crates/oak-node/src/nodes/gamma.rs index 4fcb839e3..d795e3d91 100644 --- a/crates/oak-node/src/nodes/gamma.rs +++ b/crates/oak-node/src/nodes/gamma.rs @@ -32,7 +32,7 @@ //! minimum so the reciprocal exponent never divides by zero. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -144,7 +144,7 @@ impl NodeBehavior for GammaNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -153,7 +153,7 @@ impl NodeBehavior for GammaNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -285,7 +285,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.gamma"); assert_eq!(payload.effect_input, TEXTURE_INPUT); @@ -309,7 +309,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.params.get(GAMMA_INPUT), Some(&NodeValue::Float(2.0))); } diff --git a/crates/oak-node/src/nodes/generatorwithmerge.rs b/crates/oak-node/src/nodes/generatorwithmerge.rs index 8216a6a0c..5e7964ac4 100644 --- a/crates/oak-node/src/nodes/generatorwithmerge.rs +++ b/crates/oak-node/src/nodes/generatorwithmerge.rs @@ -22,7 +22,7 @@ //! base texture. Not instantiable, so this is a helper module, not a //! [`NodeBehavior`] implementation. -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::value::NodeValue; /// Base texture input id (C++ `k_base_input`). Type: texture; flags: @@ -108,7 +108,7 @@ impl GeneratorWithMerge { // `base->to_job(ShaderJob("mrg"))` — an alpha-over merge // of the generated texture over the base. // `// CPP-PARITY: generatorwithmerge.cpp` push_mergable_job. - match unsafe { crate::handle::get_checked::(&job) } { + match unsafe { crate::jobs::shader_job(&job) } { Some(gen_job) => { let mut params = crate::value::NodeValueRow::new(); params.insert( @@ -124,7 +124,7 @@ impl GeneratorWithMerge { ); table.push( crate::value::ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time: gen_job.time, iterations: 1, @@ -133,7 +133,7 @@ impl GeneratorWithMerge { effect_input: BASE_INPUT.to_string(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -168,12 +168,12 @@ mod tests { #[test] fn push_job_without_base_pushes_job_unchanged() { - let job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { + let job = crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { type_id: "org.olivevideoeditor.Olive.solidgenerator".to_string(), time: Rational::new(2, 1), shader_id: "1".to_string(), ..Default::default() - }); + })); let mut table = NodeValueTable::default(); GeneratorWithMerge::push_mergable_job( &crate::value::NodeValueRow::default(), @@ -185,7 +185,7 @@ mod tests { _ => panic!("texture expected"), }; let payload = unsafe { - crate::handle::get_checked::(&handle) + crate::jobs::shader_job(&handle) } .expect("job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.solidgenerator"); @@ -210,12 +210,13 @@ mod tests { #[test] fn push_job_with_base_boxes_merge_payload() { - let gen_job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { - type_id: "org.olivevideoeditor.Olive.solidgenerator".to_string(), - time: Rational::new(2, 1), - shader_id: "1".to_string(), - ..Default::default() - }); + let gen_job = + crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { + type_id: "org.olivevideoeditor.Olive.solidgenerator".to_string(), + time: Rational::new(2, 1), + shader_id: "1".to_string(), + ..Default::default() + })); let base = NodeValue::Texture(crate::handle::make_owned::(7)); let inputs = crate::value::NodeValueRow::from([(BASE_INPUT.to_string(), base.clone())]); let mut table = NodeValueTable::default(); @@ -226,7 +227,7 @@ mod tests { _ => panic!("texture expected"), }; let merge = unsafe { - crate::handle::get_checked::(&handle) + crate::jobs::shader_job(&handle) } .expect("merge job payload boxed"); assert_eq!(merge.shader_id, "mrg"); @@ -240,7 +241,7 @@ mod tests { _ => panic!("texture expected"), }; let blend_job = unsafe { - crate::handle::get_checked::(&blend) + crate::jobs::shader_job(&blend) } .expect("blend job payload boxed"); assert_eq!(blend_job.type_id, "org.olivevideoeditor.Olive.solidgenerator"); diff --git a/crates/oak-node/src/nodes/grade.rs b/crates/oak-node/src/nodes/grade.rs index 7e64bf163..873789066 100644 --- a/crates/oak-node/src/nodes/grade.rs +++ b/crates/oak-node/src/nodes/grade.rs @@ -46,7 +46,7 @@ //! slope at `0.0` instead of dividing by zero. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -199,7 +199,7 @@ impl NodeBehavior for GradeNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -208,7 +208,7 @@ impl NodeBehavior for GradeNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -347,7 +347,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.grade"); assert_eq!(payload.effect_input, TEXTURE_INPUT); @@ -381,7 +381,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!( payload.params.get(BLACKPOINT_INPUT), diff --git a/crates/oak-node/src/nodes/invert.rs b/crates/oak-node/src/nodes/invert.rs index 889dc9108..35a1a3674 100644 --- a/crates/oak-node/src/nodes/invert.rs +++ b/crates/oak-node/src/nodes/invert.rs @@ -31,7 +31,7 @@ //! parameters), each toggle is a separate node input here. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -161,7 +161,7 @@ impl NodeBehavior for InvertNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -170,7 +170,7 @@ impl NodeBehavior for InvertNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -298,7 +298,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.invert"); assert_eq!(payload.effect_input, TEXTURE_INPUT); @@ -329,7 +329,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!( payload.params.get(INVERT_A_INPUT), diff --git a/crates/oak-node/src/nodes/keymix.rs b/crates/oak-node/src/nodes/keymix.rs index 7e08a333d..994996bfa 100644 --- a/crates/oak-node/src/nodes/keymix.rs +++ b/crates/oak-node/src/nodes/keymix.rs @@ -35,7 +35,7 @@ //! alphabetically first key of the job's `BTreeMap` row). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Background texture input id. Type: texture; flags: not-keyframable. @@ -164,7 +164,7 @@ impl NodeBehavior for KeyMixNode { (Some(NodeValue::Texture(_)), Some(NodeValue::Texture(_))) => { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -173,7 +173,7 @@ impl NodeBehavior for KeyMixNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } @@ -236,7 +236,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/mask.rs b/crates/oak-node/src/nodes/mask.rs index fc155f4ac..4dbb6de87 100644 --- a/crates/oak-node/src/nodes/mask.rs +++ b/crates/oak-node/src/nodes/mask.rs @@ -424,7 +424,7 @@ impl NodeBehavior for MaskDistortNode { params.insert(id.to_string(), core.value_at_time(id, -1, time)); } } - let job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { + let job = crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -433,7 +433,7 @@ impl NodeBehavior for MaskDistortNode { effect_input: crate::nodes::generatorwithmerge::BASE_INPUT.to_string(), params, iterative_input: String::new(), - }); + })); table.push( crate::value::ValueType::Texture, crate::value::NodeValue::Texture(job), @@ -643,7 +643,7 @@ mod tests { panic!("pushed value is not a texture handle"); }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("real shader job"); assert_eq!(job.shader_id, "mask"); diff --git a/crates/oak-node/src/nodes/math.rs b/crates/oak-node/src/nodes/math.rs index 7426c1eef..abad0eb7e 100644 --- a/crates/oak-node/src/nodes/math.rs +++ b/crates/oak-node/src/nodes/math.rs @@ -417,7 +417,7 @@ mod tests { _ => panic!("texture expected"), }; let payload = unsafe { - crate::handle::get_checked::(&handle) + crate::jobs::shader_job(&handle) } .expect("shader job payload boxed in the pushed texture"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.math"); @@ -453,7 +453,7 @@ mod tests { _ => panic!("texture expected"), }; let payload = unsafe { - crate::handle::get_checked::(&handle) + crate::jobs::shader_job(&handle) }; assert!(payload.is_none(), "no shader job for a null texture"); } diff --git a/crates/oak-node/src/nodes/mathbase.rs b/crates/oak-node/src/nodes/mathbase.rs index 38feb3e32..b3b1d0866 100644 --- a/crates/oak-node/src/nodes/mathbase.rs +++ b/crates/oak-node/src/nodes/mathbase.rs @@ -21,7 +21,7 @@ //! the pairing heuristic, and the static eval/shader helpers shared //! by `MathNode` (and conceptually other binary math nodes). -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::NodeCore; use crate::value::{NodeValue, NodeValueRow, NodeValueTable, ValueType}; @@ -632,7 +632,7 @@ impl MathNodeBase { ); output.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -644,7 +644,7 @@ impl MathNodeBase { (param_b_in.to_string(), val_b.clone()), ]), iterative_input: String::new(), - })), + }))), None, ); } @@ -1487,7 +1487,7 @@ mod tests { _ => panic!("texture expected"), }; let payload = unsafe { - crate::handle::get_checked::(&handle) + crate::jobs::shader_job(&handle) } .expect("shader job payload boxed in the pushed texture"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.math"); diff --git a/crates/oak-node/src/nodes/merge.rs b/crates/oak-node/src/nodes/merge.rs index 7aad846f5..69f3a2053 100644 --- a/crates/oak-node/src/nodes/merge.rs +++ b/crates/oak-node/src/nodes/merge.rs @@ -18,7 +18,7 @@ //! `olive::MergeNode`): alpha-over composites two textures. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Base (background) texture input id (C++ `k_base_in`). Type: @@ -154,7 +154,7 @@ impl NodeBehavior for MergeNode { let _ = (b, bl); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -163,7 +163,7 @@ impl NodeBehavior for MergeNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } @@ -287,7 +287,7 @@ mod tests { behavior.value(&core, &inputs, Rational::new(0, 1), &mut table); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.merge"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/mirror.rs b/crates/oak-node/src/nodes/mirror.rs index a38c3843e..ddb6d0dbb 100644 --- a/crates/oak-node/src/nodes/mirror.rs +++ b/crates/oak-node/src/nodes/mirror.rs @@ -27,7 +27,7 @@ //! the shader's uniforms. No second shader is duplicated. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Main texture input id. Type: texture; flags: not-keyframable; this is @@ -132,7 +132,7 @@ impl NodeBehavior for MirrorNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -141,7 +141,7 @@ impl NodeBehavior for MirrorNode { effect_input: core.effect_input.clone(), params, iterative_input: TEXTURE_INPUT.to_string(), - })), + }))), None, ); } @@ -266,7 +266,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.mirror"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/mosaicfilternode.rs b/crates/oak-node/src/nodes/mosaicfilternode.rs index 0a1b98f1f..10ba99d50 100644 --- a/crates/oak-node/src/nodes/mosaicfilternode.rs +++ b/crates/oak-node/src/nodes/mosaicfilternode.rs @@ -19,7 +19,7 @@ //! `olive::MosaicFilterNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -151,7 +151,7 @@ impl NodeBehavior for MosaicFilterNode { let params = inputs.clone(); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -160,7 +160,7 @@ impl NodeBehavior for MosaicFilterNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -279,7 +279,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.mosaicfilter"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/noise.rs b/crates/oak-node/src/nodes/noise.rs index 4c466a9ad..4d01674d4 100644 --- a/crates/oak-node/src/nodes/noise.rs +++ b/crates/oak-node/src/nodes/noise.rs @@ -18,7 +18,7 @@ //! `olive::NoiseGeneratorNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Base texture input id (C++ `k_base_in`). Type: texture; flags: @@ -152,7 +152,7 @@ impl NodeBehavior for NoiseGeneratorNode { ); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -161,7 +161,7 @@ impl NodeBehavior for NoiseGeneratorNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -264,7 +264,7 @@ mod tests { unreachable!() }; let job = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("noise output boxes a ShaderJobPayload"); assert_eq!(job.type_id, "org.olivevideoeditor.Olive.noise"); assert_eq!(job.shader_id, ""); @@ -288,7 +288,7 @@ mod tests { unreachable!() }; let job = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("noise output boxes a ShaderJobPayload"); assert_eq!(job.type_id, "org.olivevideoeditor.Olive.noise"); assert!(job.params.contains_key(BASE_INPUT)); diff --git a/crates/oak-node/src/nodes/ociobase.rs b/crates/oak-node/src/nodes/ociobase.rs index 21c0bda85..43d265329 100644 --- a/crates/oak-node/src/nodes/ociobase.rs +++ b/crates/oak-node/src/nodes/ociobase.rs @@ -118,13 +118,13 @@ impl OcioBase { match self.processor() { Some(processor) => table.push( crate::value::ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned( + NodeValue::Texture(crate::handle::make_owned(crate::jobs::Job::ColorTransformJob( crate::jobs::ColorTransformJobPayload { color_processor: processor, input: tex.clone(), time, }, - )), + ))), None, ), None => table.push(crate::value::ValueType::Texture, tex.clone(), None), @@ -219,7 +219,7 @@ mod tests { panic!("job row expected"); }; let payload = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::color_transform_job(handle) } .expect("a boxed ColorTransformJobPayload"); assert!(std::sync::Arc::ptr_eq( diff --git a/crates/oak-node/src/nodes/ociogradingtransformlinear.rs b/crates/oak-node/src/nodes/ociogradingtransformlinear.rs index 4c53100be..b12fc5d3a 100644 --- a/crates/oak-node/src/nodes/ociogradingtransformlinear.rs +++ b/crates/oak-node/src/nodes/ociogradingtransformlinear.rs @@ -439,7 +439,7 @@ impl NodeBehavior for OCIOGradingTransformLinearNode { crate::value::NodeValue::Boolean(false), ); - let job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { + let job = crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -448,7 +448,7 @@ impl NodeBehavior for OCIOGradingTransformLinearNode { effect_input: crate::nodes::ociobase::TEXTURE_INPUT.to_string(), params, iterative_input: String::new(), - }); + })); table.push( crate::value::ValueType::Texture, crate::value::NodeValue::Texture(job), @@ -916,7 +916,7 @@ mod tests { panic!("pushed value is not a texture handle"); }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("real shader job"); assert_eq!(job.shader_id, "rgb"); @@ -986,7 +986,7 @@ mod tests { panic!("pushed value is not a texture handle"); }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("real shader job"); // White <= black is raised to black + 0.000001. diff --git a/crates/oak-node/src/nodes/ociogradingtransformlog.rs b/crates/oak-node/src/nodes/ociogradingtransformlog.rs index 7db1e129d..004961a55 100644 --- a/crates/oak-node/src/nodes/ociogradingtransformlog.rs +++ b/crates/oak-node/src/nodes/ociogradingtransformlog.rs @@ -470,7 +470,7 @@ impl NodeBehavior for OCIOGradingTransformLogNode { crate::value::NodeValue::Boolean(false), ); - let job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { + let job = crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -479,7 +479,7 @@ impl NodeBehavior for OCIOGradingTransformLogNode { effect_input: crate::nodes::ociobase::TEXTURE_INPUT.to_string(), params, iterative_input: String::new(), - }); + })); table.push( crate::value::ValueType::Texture, crate::value::NodeValue::Texture(job), @@ -903,7 +903,7 @@ mod tests { panic!("pushed value is not a texture handle"); }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("real shader job"); assert_eq!(job.shader_id, "rgb"); diff --git a/crates/oak-node/src/nodes/ociolut.rs b/crates/oak-node/src/nodes/ociolut.rs index 73915d396..b0f537cca 100644 --- a/crates/oak-node/src/nodes/ociolut.rs +++ b/crates/oak-node/src/nodes/ociolut.rs @@ -726,7 +726,7 @@ mod tests { panic!("job row expected"); }; assert!(unsafe { - crate::handle::get_checked::(handle) + crate::jobs::color_transform_job(handle) } .is_some()); } diff --git a/crates/oak-node/src/nodes/opacity.rs b/crates/oak-node/src/nodes/opacity.rs index 9382accbc..c23a4ad55 100644 --- a/crates/oak-node/src/nodes/opacity.rs +++ b/crates/oak-node/src/nodes/opacity.rs @@ -18,7 +18,7 @@ //! `olive::OpacityEffect`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -157,7 +157,7 @@ impl NodeBehavior for OpacityEffect { // input key locates the main texture inside the params row. `time` // is diagnostics-only (C++ jobs keep the request timestamp). let job = |shader_id: &str| -> crate::value::NodeValue { - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -166,7 +166,7 @@ impl NodeBehavior for OpacityEffect { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })) + }))) }; match inputs.get(VALUE_INPUT) { @@ -322,7 +322,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("texture expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.opacity"); assert_eq!(payload.shader_id, ""); @@ -344,7 +344,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("texture expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.opacity"); assert_eq!( @@ -386,7 +386,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("texture expected"); }; - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job pushed"); assert_eq!(payload.shader_id, "rgbmult"); } diff --git a/crates/oak-node/src/nodes/plugin.rs b/crates/oak-node/src/nodes/plugin.rs index 94527f792..07cf9d5be 100644 --- a/crates/oak-node/src/nodes/plugin.rs +++ b/crates/oak-node/src/nodes/plugin.rs @@ -35,6 +35,7 @@ use std::sync::{Arc, Mutex, OnceLock}; use crate::factory::NodeMeta; +use crate::jobs::Job; use crate::node::{Category, NodeBehavior, NodeCore}; use crate::value::{NodeValue, NodeValueRow, NodeValueTable}; use oak_core::{Rational, TimeRange}; @@ -235,12 +236,12 @@ impl NodeBehavior for PluginNode { .or_else(|| inputs.values().find(|v| matches!(v, NodeValue::Texture(_)))); if tex.is_some() && !self.instance.is_null() { - let payload = PluginJobPayload { + let payload = Job::PluginJob(PluginJobPayload { instance: self.instance, time, effect_input_id: core.effect_input.clone(), values: inputs.clone(), - }; + }); table.push( crate::value::ValueType::Texture, NodeValue::Texture(crate::handle::make_owned(payload)), @@ -474,7 +475,7 @@ mod tests { }; // SAFETY: the handle was created by value() boxing a // PluginJobPayload. - let payload = unsafe { crate::handle::get::(h) } + let payload = unsafe { crate::jobs::plugin_job(h) } .expect("texture handle must box a PluginJobPayload"); assert_eq!(payload.instance, PluginInstanceHandle(1)); assert_eq!(payload.time, time); diff --git a/crates/oak-node/src/nodes/polygon.rs b/crates/oak-node/src/nodes/polygon.rs index d7eb2fe47..cd26f8ed7 100644 --- a/crates/oak-node/src/nodes/polygon.rs +++ b/crates/oak-node/src/nodes/polygon.rs @@ -232,7 +232,7 @@ impl NodeBehavior for PolygonGenerator { core.value_at_time(COLOR_INPUT, -1, time), ); } - let job = crate::handle::make_owned(crate::jobs::ShaderJobPayload { + let job = crate::handle::make_owned(crate::jobs::Job::ShaderJob(crate::jobs::ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -241,7 +241,7 @@ impl NodeBehavior for PolygonGenerator { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - }); + })); super::generatorwithmerge::GeneratorWithMerge::push_mergable_job(inputs, job, table); } @@ -440,7 +440,7 @@ mod tests { panic!("pushed value is not a texture handle"); }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("real shader job"); assert_eq!(job.shader_id, "rgb"); @@ -473,7 +473,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("texture expected"); }; - let mrg = unsafe { crate::handle::get_checked::(h) } + let mrg = unsafe { crate::jobs::shader_job(h) } .expect("merge job pushed"); assert_eq!(mrg.shader_id, "mrg"); assert_eq!(mrg.effect_input, super::super::generatorwithmerge::BASE_INPUT); @@ -482,7 +482,7 @@ mod tests { Some(NodeValue::Texture(b)) => *b, other => panic!("blend_in must carry the nested job: {other:?}"), }; - let blend_job = unsafe { crate::handle::get_checked::(&blend) } + let blend_job = unsafe { crate::jobs::shader_job(&blend) } .expect("nested generator job"); assert_eq!(blend_job.shader_id, "rgb"); assert_eq!(blend_job.type_id, "org.olivevideoeditor.Olive.polygon"); diff --git a/crates/oak-node/src/nodes/position.rs b/crates/oak-node/src/nodes/position.rs index c94cdaa97..487ce4e4e 100644 --- a/crates/oak-node/src/nodes/position.rs +++ b/crates/oak-node/src/nodes/position.rs @@ -20,7 +20,7 @@ //! upstream code copied). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Main texture input id (upstream `PositionPlugin`'s image input). Type: @@ -141,7 +141,7 @@ impl NodeBehavior for PositionNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -150,7 +150,7 @@ impl NodeBehavior for PositionNode { effect_input: core.effect_input.clone(), params, iterative_input: TEXTURE_INPUT.to_string(), - })), + }))), None, ); } @@ -251,7 +251,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.position"); assert_eq!(payload.shader_id, ""); @@ -280,7 +280,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!( payload.params.get(OFFSET_INPUT), diff --git a/crates/oak-node/src/nodes/premult.rs b/crates/oak-node/src/nodes/premult.rs index f0094d726..a3f1747b7 100644 --- a/crates/oak-node/src/nodes/premult.rs +++ b/crates/oak-node/src/nodes/premult.rs @@ -31,7 +31,7 @@ //! one sampler by name. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -177,7 +177,7 @@ impl NodeBehavior for PremultiplyNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -186,7 +186,7 @@ impl NodeBehavior for PremultiplyNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -247,7 +247,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/ramp.rs b/crates/oak-node/src/nodes/ramp.rs index fc84b3b64..ac29584c7 100644 --- a/crates/oak-node/src/nodes/ramp.rs +++ b/crates/oak-node/src/nodes/ramp.rs @@ -26,7 +26,7 @@ //! ramp across a 200px-wide frame). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Base texture input id (the shared generator-with-merge base). Type: @@ -155,7 +155,7 @@ impl NodeBehavior for RampNode { } } - let job = crate::handle::make_owned(ShaderJobPayload { + let job = crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -164,7 +164,7 @@ impl NodeBehavior for RampNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - }); + })); super::generatorwithmerge::GeneratorWithMerge::push_mergable_job(inputs, job, table); } @@ -294,7 +294,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.ramp"); assert_eq!(payload.shader_id, "ramp"); @@ -325,7 +325,7 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!( payload.params.get(POINT0_INPUT), @@ -355,13 +355,13 @@ mod tests { Some(NodeValue::Texture(h)) => *h, _ => panic!("texture expected"), }; - let merge = unsafe { crate::handle::get_checked::(&handle) } + let merge = unsafe { crate::jobs::shader_job(&handle) } .expect("merge job payload expected"); assert_eq!(merge.shader_id, "mrg"); assert_eq!(merge.effect_input, BASE_INPUT); match merge.params.get(crate::nodes::merge::BLEND_INPUT) { Some(NodeValue::Texture(blend)) => { - let nested = unsafe { crate::handle::get_checked::(blend) } + let nested = unsafe { crate::jobs::shader_job(blend) } .expect("nested job payload boxed"); assert_eq!(nested.shader_id, "ramp"); } diff --git a/crates/oak-node/src/nodes/rippledistortnode.rs b/crates/oak-node/src/nodes/rippledistortnode.rs index 27af01015..2fd54ae7d 100644 --- a/crates/oak-node/src/nodes/rippledistortnode.rs +++ b/crates/oak-node/src/nodes/rippledistortnode.rs @@ -19,7 +19,7 @@ //! `olive::RippleDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -180,7 +180,7 @@ impl NodeBehavior for RippleDistortNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -189,7 +189,7 @@ impl NodeBehavior for RippleDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -381,7 +381,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::shader_job(handle) } .expect("ripple output boxes a ShaderJobPayload"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.ripple"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/saturation.rs b/crates/oak-node/src/nodes/saturation.rs index 710cb320a..b30576bd7 100644 --- a/crates/oak-node/src/nodes/saturation.rs +++ b/crates/oak-node/src/nodes/saturation.rs @@ -34,7 +34,7 @@ //! no "changed" test in the reference. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is @@ -135,7 +135,7 @@ impl NodeBehavior for SaturationNode { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -144,7 +144,7 @@ impl NodeBehavior for SaturationNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -276,7 +276,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.saturation"); assert_eq!(payload.effect_input, TEXTURE_INPUT); @@ -303,7 +303,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("shader job pushed"); assert_eq!( payload.params.get(SATURATION_INPUT), diff --git a/crates/oak-node/src/nodes/shapenode.rs b/crates/oak-node/src/nodes/shapenode.rs index 04d6ba705..5985d0dd6 100644 --- a/crates/oak-node/src/nodes/shapenode.rs +++ b/crates/oak-node/src/nodes/shapenode.rs @@ -20,7 +20,7 @@ //! [`super::generatorwithmerge`]). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Shape type input id (C++ `k_type_input`). Type: combo; prepended @@ -202,7 +202,7 @@ impl NodeBehavior for ShapeNode { ) { // The `"shape"` shader job (C++ `value()`: `ShaderJob job(value);` // `Insert("resolution_in")`; `SetShaderID("shape")`). - let shape_job = crate::value::NodeValue::Texture(crate::handle::make_owned( + let shape_job = crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob( ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, @@ -213,7 +213,7 @@ impl NodeBehavior for ShapeNode { params: inputs.clone(), iterative_input: String::new(), }, - )); + ))); match inputs.get(super::generatorwithmerge::BASE_INPUT) { Some(base @ crate::value::NodeValue::Texture(_)) => { @@ -233,7 +233,7 @@ impl NodeBehavior for ShapeNode { params.insert(crate::nodes::merge::BLEND_INPUT.to_string(), shape_job); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned( + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob( ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, @@ -244,7 +244,7 @@ impl NodeBehavior for ShapeNode { params, iterative_input: String::new(), }, - )), + ))), None, ); } @@ -431,7 +431,7 @@ mod tests { ); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.shape"); assert_eq!(payload.shader_id, "shape"); @@ -457,7 +457,7 @@ mod tests { behavior.value(&core, &inputs, Rational::new(0, 1), &mut table); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let merge = unsafe { crate::handle::get_checked::(h) } + let merge = unsafe { crate::jobs::shader_job(h) } .expect("merge job payload boxed"); assert_eq!(merge.shader_id, "mrg"); assert_eq!(merge.iterations, 1); @@ -468,7 +468,7 @@ mod tests { match merge.params.get(crate::nodes::merge::BLEND_INPUT) { Some(NodeValue::Texture(blend)) => { let shape = - unsafe { crate::handle::get_checked::(blend) } + unsafe { crate::jobs::shader_job(blend) } .expect("nested shape job payload boxed"); assert_eq!(shape.shader_id, "shape"); } diff --git a/crates/oak-node/src/nodes/sharpen.rs b/crates/oak-node/src/nodes/sharpen.rs index 76514c071..af295dc0f 100644 --- a/crates/oak-node/src/nodes/sharpen.rs +++ b/crates/oak-node/src/nodes/sharpen.rs @@ -27,7 +27,7 @@ //! this node trades for a single-pass small-kernel mask. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -158,7 +158,7 @@ impl NodeBehavior for SharpenNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -167,7 +167,7 @@ impl NodeBehavior for SharpenNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -226,7 +226,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/solid.rs b/crates/oak-node/src/nodes/solid.rs index 1f7d582c0..055b74260 100644 --- a/crates/oak-node/src/nodes/solid.rs +++ b/crates/oak-node/src/nodes/solid.rs @@ -18,7 +18,7 @@ //! `olive::SolidGenerator`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Color input id (C++ `k_color_input`). Type: color; default @@ -95,7 +95,7 @@ impl NodeBehavior for SolidGenerator { ) { table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -104,7 +104,7 @@ impl NodeBehavior for SolidGenerator { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } @@ -179,7 +179,7 @@ mod tests { unreachable!() }; let job = unsafe { - crate::handle::get_checked::(handle) + crate::jobs::shader_job(handle) } .expect("solid output boxes a ShaderJobPayload"); assert_eq!( diff --git a/crates/oak-node/src/nodes/stroke.rs b/crates/oak-node/src/nodes/stroke.rs index 3203f01ba..b30c83436 100644 --- a/crates/oak-node/src/nodes/stroke.rs +++ b/crates/oak-node/src/nodes/stroke.rs @@ -18,7 +18,7 @@ //! `olive::StrokeFilterNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -209,7 +209,7 @@ impl NodeBehavior for StrokeFilterNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -218,7 +218,7 @@ impl NodeBehavior for StrokeFilterNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -385,7 +385,7 @@ mod tests { behavior.value(&core, &inputs, Rational::new(0, 1), &mut table); match table.get(ValueType::Texture) { Some(NodeValue::Texture(h)) => { - let payload = unsafe { crate::handle::get_checked::(h) } + let payload = unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.stroke"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/swirldistortnode.rs b/crates/oak-node/src/nodes/swirldistortnode.rs index 7ca4b916b..49e9afdcd 100644 --- a/crates/oak-node/src/nodes/swirldistortnode.rs +++ b/crates/oak-node/src/nodes/swirldistortnode.rs @@ -19,7 +19,7 @@ //! `olive::SwirlDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -164,7 +164,7 @@ impl NodeBehavior for SwirlDistortNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -173,7 +173,7 @@ impl NodeBehavior for SwirlDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -367,7 +367,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::shader_job(handle) } .expect("swirl output boxes a ShaderJobPayload"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.swirl"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/textv3.rs b/crates/oak-node/src/nodes/textv3.rs index 5d2406049..c988ce37e 100644 --- a/crates/oak-node/src/nodes/textv3.rs +++ b/crates/oak-node/src/nodes/textv3.rs @@ -44,7 +44,7 @@ //! behavior. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; use crate::value::{NodeValue, NodeValueRow, NodeValueTable}; use oak_core::frame::VideoParamsPod; @@ -928,7 +928,7 @@ impl TextGeneratorV3 { iterations: i32, params: NodeValueRow, ) -> crate::handle::CHandle { - crate::handle::make_owned(ShaderJobPayload { + crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations, @@ -937,7 +937,7 @@ impl TextGeneratorV3 { effect_input: effect_input.to_string(), params, iterative_input: String::new(), - }) + })) } /// Box a `"mrg"` job drawing `blend` (the top layer) over `base` (the @@ -2184,7 +2184,7 @@ mod tests { /// The job payload boxed by a deferred texture handle. fn job_of(handle: &CHandle) -> &ShaderJobPayload { - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("handle carries a ShaderJobPayload") } diff --git a/crates/oak-node/src/nodes/threewaycolor.rs b/crates/oak-node/src/nodes/threewaycolor.rs index c43cd579c..cdb987094 100644 --- a/crates/oak-node/src/nodes/threewaycolor.rs +++ b/crates/oak-node/src/nodes/threewaycolor.rs @@ -19,7 +19,7 @@ //! `olive::ThreeWayColorNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -189,7 +189,7 @@ impl NodeBehavior for ThreeWayColorNode { ); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -198,7 +198,7 @@ impl NodeBehavior for ThreeWayColorNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -375,7 +375,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.threewaycolor"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/tiledistortnode.rs b/crates/oak-node/src/nodes/tiledistortnode.rs index 8140bef19..7ec98f510 100644 --- a/crates/oak-node/src/nodes/tiledistortnode.rs +++ b/crates/oak-node/src/nodes/tiledistortnode.rs @@ -19,7 +19,7 @@ //! `olive::TileDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -253,7 +253,7 @@ impl NodeBehavior for TileDistortNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -262,7 +262,7 @@ impl NodeBehavior for TileDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -456,7 +456,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::shader_job(handle) } .expect("tile output boxes a ShaderJobPayload"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.tile"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/transformdistortnode.rs b/crates/oak-node/src/nodes/transformdistortnode.rs index 3ec9192f5..9092d10e0 100644 --- a/crates/oak-node/src/nodes/transformdistortnode.rs +++ b/crates/oak-node/src/nodes/transformdistortnode.rs @@ -54,7 +54,7 @@ void main(void) { use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, Gizmo, NodeBehavior, NodeCore}; /// Parent matrix input id (C++ `k_parent_input`). Type: matrix; no @@ -431,7 +431,7 @@ impl NodeBehavior for TransformDistortNode { ); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -440,7 +440,7 @@ impl NodeBehavior for TransformDistortNode { effect_input: core.effect_input.clone(), params, iterative_input: TEXTURE_INPUT.to_string(), - })), + }))), None, ); } @@ -794,7 +794,7 @@ mod tests { NodeValue::Texture(h) => *h, _ => panic!("texture expected"), }; - let payload = unsafe { crate::handle::get_checked::(&handle) } + let payload = unsafe { crate::jobs::shader_job(&handle) } .expect("shader job payload expected"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.transform"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/transitionfx.rs b/crates/oak-node/src/nodes/transitionfx.rs index 8e5cb52ed..8d02e17e7 100644 --- a/crates/oak-node/src/nodes/transitionfx.rs +++ b/crates/oak-node/src/nodes/transitionfx.rs @@ -47,7 +47,7 @@ //! `direction_in` sweep direction. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Chain texture input id. Type: texture; flags: not-keyframable. This @@ -367,7 +367,7 @@ impl NodeBehavior for TransitionFxNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -376,7 +376,7 @@ impl NodeBehavior for TransitionFxNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -496,7 +496,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/transitions.rs b/crates/oak-node/src/nodes/transitions.rs index 673f0d42c..4037a7121 100644 --- a/crates/oak-node/src/nodes/transitions.rs +++ b/crates/oak-node/src/nodes/transitions.rs @@ -33,7 +33,7 @@ //! renderer binds `tex_in`/`blend_in` by name. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Outgoing ("from") texture input id. Type: texture; flags: @@ -314,7 +314,7 @@ impl NodeBehavior for TransitionNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -323,7 +323,7 @@ impl NodeBehavior for TransitionNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -420,7 +420,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/unpremult.rs b/crates/oak-node/src/nodes/unpremult.rs index db72dfd99..dc6b9e230 100644 --- a/crates/oak-node/src/nodes/unpremult.rs +++ b/crates/oak-node/src/nodes/unpremult.rs @@ -32,7 +32,7 @@ //! one, leaving the image unchanged. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id. Type: texture; flags: not-keyframable; this is the @@ -186,7 +186,7 @@ impl NodeBehavior for UnpremultiplyNode { table.push( ValueType::Texture, - NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -195,7 +195,7 @@ impl NodeBehavior for UnpremultiplyNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -256,7 +256,7 @@ mod tests { let Some(NodeValue::Texture(h)) = table.get(ValueType::Texture) else { panic!("shader job expected"); }; - unsafe { crate::handle::get_checked::(h) } + unsafe { crate::jobs::shader_job(h) } .expect("shader job payload boxed") .clone() } diff --git a/crates/oak-node/src/nodes/wavedistortnode.rs b/crates/oak-node/src/nodes/wavedistortnode.rs index 1a781e88a..05671b86d 100644 --- a/crates/oak-node/src/nodes/wavedistortnode.rs +++ b/crates/oak-node/src/nodes/wavedistortnode.rs @@ -19,7 +19,7 @@ //! `olive::WaveDistortNode`). use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -170,7 +170,7 @@ impl NodeBehavior for WaveDistortNode { // main texture inside the params row. table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -179,7 +179,7 @@ impl NodeBehavior for WaveDistortNode { effect_input: core.effect_input.clone(), params: inputs.clone(), iterative_input: String::new(), - })), + }))), None, ); } else { @@ -318,7 +318,7 @@ mod tests { let NodeValue::Texture(handle) = table.get(ValueType::Texture).unwrap() else { unreachable!() }; - let payload = unsafe { crate::handle::get_checked::(handle) } + let payload = unsafe { crate::jobs::shader_job(handle) } .expect("wave output boxes a ShaderJobPayload"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.wave"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-node/src/nodes/whitebalance.rs b/crates/oak-node/src/nodes/whitebalance.rs index 01edab49c..7c4d11e2c 100644 --- a/crates/oak-node/src/nodes/whitebalance.rs +++ b/crates/oak-node/src/nodes/whitebalance.rs @@ -25,7 +25,7 @@ //! the green-magenta axis. use crate::factory::NodeMeta; -use crate::jobs::ShaderJobPayload; +use crate::jobs::{Job, ShaderJobPayload}; use crate::node::{Category, NodeBehavior, NodeCore}; /// Texture input id (C++ `k_texture_input`). Type: texture; flags: @@ -195,7 +195,7 @@ impl NodeBehavior for WhiteBalanceNode { params.insert(GAIN_INPUT.to_string(), crate::value::NodeValue::Vec3(gain)); table.push( crate::value::ValueType::Texture, - crate::value::NodeValue::Texture(crate::handle::make_owned(ShaderJobPayload { + crate::value::NodeValue::Texture(crate::handle::make_owned(Job::ShaderJob(ShaderJobPayload { node_id: crate::id::NodeId::INVALID, time, iterations: 1, @@ -204,7 +204,7 @@ impl NodeBehavior for WhiteBalanceNode { effect_input: core.effect_input.clone(), params, iterative_input: String::new(), - })), + }))), None, ); } @@ -397,7 +397,7 @@ mod tests { panic!("expected a texture-typed value"); }; let payload = - unsafe { crate::handle::get_checked::(handle) } + unsafe { crate::jobs::shader_job(handle) } .expect("payload boxed behind the handle"); assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.whitebalance"); assert_eq!(payload.shader_id, ""); diff --git a/crates/oak-render/COVERAGE.md b/crates/oak-render/COVERAGE.md index d6d62cede..723bc9236 100644 --- a/crates/oak-render/COVERAGE.md +++ b/crates/oak-render/COVERAGE.md @@ -87,7 +87,7 @@ |---|---| | `ColorProcessor`(create 两族/convert_frame 两族/convert_color/id/get_processor) | `color::ColorProcessor`(OCIO 薄封装) | | `ColorProcessor::Direction` | `color::Direction` | -| `ManagedColor` / `ColorTransformJob` / `ShaderCode` / `ShaderJob` / `GenerateJob` / `CacheJob` / `FootageJob` / `SampleJob` / `AcceleratedJob`(job 族) | `eval::JobSpec`(enum 闭合:Shader/ColorTransform/Generate/Cache/Footage/Sample);job 对象不再跨模块流通,只是求值期的内部记录 | +| `ManagedColor` / `ColorTransformJob` / `ShaderCode` / `ShaderJob` / `GenerateJob` / `CacheJob` / `FootageJob` / `SampleJob` / `AcceleratedJob`(job 族) | `oak_node::jobs::Job`(enum 闭合:Footage/Shader/Plugin/ColorTransform/Cache;节点在求值期把 payload 装箱推进 value table);`eval::RenderEvalHooks::resolve` 单趟遍历输出表、先递归解析嵌套 job 再分发执行;`eval::JobSpec` 只保留执行器面(Shader/ColorTransform/Generate/Footage/Sample/Plugin) | | `LUTLibrary`(supported_extensions/is_supported_extension) | `color::lut` | | `ColorManager` 静态面(default config/display/view/reference) | **归 oaknode crate 的 colormanager.rs**(所有者);render 只保留 `color::default_config` 客户端查询(bridge::node) | @@ -109,7 +109,7 @@ | C++ | Rust 落点 | |---|---| | `generate_database` / `run` / `process` (static) | `eval.rs`:oaknode traverser 引擎 + `RenderEvalHooks` | -| `process_video_footage` / `process_audio_footage` / `process_shader` / `process_samples` / `process_color_transform` / `process_frame_generation` / `process_plugin_job` / `process_video_cache_job` | `eval::RenderEvalHooks` 的各 hook 方法(plugin job 转发给 oakplugin crate C ABI——render 不再认识 OFX) | +| `process_video_footage` / `process_audio_footage` / `process_shader` / `process_samples` / `process_color_transform` / `process_frame_generation` / `process_plugin_job` / `process_video_cache_job` | `eval::RenderEvalHooks` 的各 hook 方法(plugin job 转发给 oakplugin crate C ABI——render 不再认识 OFX;cache job 真读盘:`frameio` 自描述容器,缺失/损坏回退到节点输入) | | `create_texture` / `create_sample_buffer` / `generate_texture` / `generate_frame` / `convert_to_reference_space` / `resolve_decoder_from_input` / `use_cache` | `eval.rs` + `texture.rs` + `bridge::codec` | ## 10. ProjectCopier(projectcopier.h,33 方法) diff --git a/crates/oak-render/README.md b/crates/oak-render/README.md index 43de3dede..99a7bc41b 100644 --- a/crates/oak-render/README.md +++ b/crates/oak-render/README.md @@ -25,7 +25,8 @@ frozen, implemented verbatim by `src/ffi.rs`. oaknode crate) through the C ABI. `copier.rs` here is a thin client. 2. **RenderProcessor's inheritance disappears.** C++ `RenderProcessor : NodeTraverser` becomes `eval.rs` (the closed - `JobSpec` set + the CPU-side hook implementations; graph traversal + `JobSpec` executor set + the CPU-side hook implementations; the job + payload enum itself lives in oaknode's `jobs.rs`, graph traversal stays in oaknode). 3. **Ticket/watchers.** C++ RenderTicket/RenderTicketWatcher (Qt signals) become a ticket arena with completion callbacks — diff --git a/crates/oak-render/src/eval.rs b/crates/oak-render/src/eval.rs index ee464eb70..b830c12ba 100644 --- a/crates/oak-render/src/eval.rs +++ b/crates/oak-render/src/eval.rs @@ -27,8 +27,14 @@ //! through when no adapter is available); color transform jobs apply //! their OCIO processor (CPU frames convert for real; the GPU //! color-managed blit is deferred at the backend and passes through); -//! the disk frame-cache payload I/O remains deferred. +//! cache jobs read their frame from the self-describing container in +//! [`crate::frameio`] (liboakoiio EXR/JPEG pending) and otherwise +//! substitute the value the cache node was fed. Resolution is one pass +//! over the output table ([`RenderHooks::resolve`]) that runs each +//! boxed [`oak_node::jobs::Job`] — and, first, the jobs nested in its +//! inputs — then replaces the box with the resulting texture. +use std::collections::HashSet; use std::sync::{Arc, Mutex}; use crate::error::{Error, Result}; @@ -42,7 +48,10 @@ use oak_core::color::ColorProcessor; use oak_core::frame::VideoParamsPod; use oak_core::texture::{Frame, Texture}; use oak_core::{PixelFormat, Rational, TimeRange}; -use oak_node::jobs::{ColorTransformJobPayload, FootageJobPayload, ShaderJobPayload}; +use oak_node::jobs::{ + CacheJobPayload, ColorTransformJobPayload, FootageJobPayload, Job, ShaderJobPayload, +}; +use oak_node::nodes::plugin::PluginJobPayload; use oak_node::value::{NodeValue, NodeValueRow, NodeValueTable}; /// Static mapping of OCIO-based node shaders to the OCIO function they @@ -118,11 +127,6 @@ pub enum JobSpec { }, /// Direct frame generation (CPU nodes). Generate, - /// Disk cache read (C++ CacheJob). - Cache { - /// Cache file path. - path: String, - }, /// Footage decode (C++ FootageJob; decode via bridge::codec). Footage { /// Decoder/stream id. @@ -387,207 +391,243 @@ impl RenderEvalHooks { } } - /// C++ process_video_cache_job. - fn process_video_cache_job(&mut self, spec: &JobSpec) -> Result { - let JobSpec::Cache { path } = spec else { - return Err(Error::Invalid); - }; - let _ = path; - Err(Error::Failed( - "disk frame-cache load deferred: oakcodec EXR/JPEG decode pending".into(), - )) + /// C++ process_video_cache_job: read the frame the cache wrote at + /// `payload.path` through the disk frame-cache container + /// ([`crate::frameio`]). A missing or unreadable file is an `Err` — + /// the caller then substitutes the job's fallback value. The + /// payload's request time is already spelled into `path` (the cache + /// writes one file per frame), so the read itself ignores it. + fn process_cache_job(&mut self, payload: &CacheJobPayload) -> Result { + let frame = crate::frameio::load_cache_frame(&payload.path)?; + Ok(Texture::wrap_frame(frame)) } - /// Executes the deferred plugin payloads a [`oak_node::nodes::plugin::PluginNode`] - /// pushed into its output table (C++ JobEnginePlugin processing in - /// jobmanager.cpp): unwraps each [`oak_node::nodes::plugin::PluginJobPayload`] - /// box, splits it into input textures and tagged param values, and - /// replaces the box with the rendered texture. - fn resolve_plugin_jobs(&mut self, table: &mut NodeValueTable) { - for (_, value, _) in table.rows_mut() { - let NodeValue::Texture(handle) = value else { - continue; - }; - if handle.ctx.is_null() { - continue; + /// Resolve one texture-channel value in place (the C++ JobEngine's + /// per-value walk): a boxed [`Job`] recurses into its own inputs and + /// runs, then the box is replaced by the resulting texture; a genuine + /// texture — or any non-texture value — passes through untouched. + /// `depth` bounds the nesting; `in_flight` holds the job boxes on the + /// current walk so a job reachable from itself stops instead of + /// recursing forever (the C++ `resolved_texture_cache_` + /// de-duplication, kept as a path set so the same handle reached from + /// two rows still resolves per row). + fn resolve_value( + &mut self, + value: &mut NodeValue, + depth: usize, + in_flight: &mut HashSet, + ) { + /// Job-nesting recursion ceiling (defensive; real graphs nest a + /// generator job inside a merge job and stop there). + const MAX_JOB_DEPTH: usize = 64; + if depth >= MAX_JOB_DEPTH { + return; + } + let NodeValue::Texture(handle) = value else { + return; + }; + if handle.ctx.is_null() { + return; + } + let key = handle.ctx as usize; + if !in_flight.insert(key) { + return; + } + let job = (unsafe { oak_node::jobs::job_ref(handle) }).cloned(); + if let Some(job) = job { + if let Some(resolved) = self.process_job(&job, depth + 1, in_flight) { + *value = resolved; } - let payload = unsafe { - oak_node::handle::get_checked::(handle) - } - .cloned(); - let Some(payload) = payload else { - // A genuine texture box (e.g. a source node's frame): - // not a plugin job, leave it alone. - continue; - }; + } + in_flight.remove(&key); + } - let mut inputs: Vec<(String, Texture)> = Vec::new(); - let mut values: Vec<(String, NodeValue)> = Vec::new(); - for (key, v) in payload.values.iter() { - match v { - NodeValue::Texture(h) if !h.ctx.is_null() => { - match unsafe { oak_node::handle::get_checked::(h) }.cloned() { - Some(texture) => inputs.push((key.clone(), texture)), - None => eprintln!("plugin job input '{key}' is not a texture box"), - } + /// Run one resolved [`Job`] (the C++ `process_*` virtual, dispatched on + /// the payload type). `None` when the job produced no replacement + /// value — the row then keeps its box. + fn process_job( + &mut self, + job: &Job, + depth: usize, + in_flight: &mut HashSet, + ) -> Option { + match job { + Job::FootageJob(payload) => self.process_footage_job_value(payload), + Job::ShaderJob(payload) => { + Some(self.process_shader_job_value(payload, depth, in_flight)) + } + Job::PluginJob(payload) => self.process_plugin_job_value(payload, depth, in_flight), + Job::ColorTransformJob(payload) => { + Some(self.process_color_transform_job_value(payload, depth, in_flight)) + } + Job::CacheJob(payload) => { + Some(self.process_cache_job_value(payload, depth, in_flight)) + } + } + } + + /// Resolve one footage job (C++ FootageJob processing in + /// jobmanager.cpp): decode the frame at the job's request time and + /// replace the box with the resulting texture. `None` on a decode + /// failure — the row keeps its box, so the failure stays visible and + /// is retried rather than cached as a hole. + fn process_footage_job_value(&mut self, payload: &FootageJobPayload) -> Option { + let size = self.frame_size.unwrap_or((0, 0)); + match render_footage_frame( + &payload.filename, + payload.stream_index, + payload.time, + size, + PixelFormat::F32, + ) { + Ok(texture) => Some(texture_value(texture)), + Err(err) => { + eprintln!("footage job decode failed: {err:#}"); + None + } + } + } + + /// Resolve one shader job (C++ ShaderJob processing in jobmanager.cpp): + /// recurse into the param row's job boxes (the generator layer of an + /// `mrg` chain), execute the pass, and replace the box with the result + /// texture. A failed or un-runnable job falls back to the effect input + /// texture from the now-resolved param row (a pass-through — C++ leaves + /// the failed shader's output as its input); a row without the effect + /// input resolves to `NodeValue::None`. + fn process_shader_job_value( + &mut self, + payload: &ShaderJobPayload, + depth: usize, + in_flight: &mut HashSet, + ) -> NodeValue { + let mut payload = payload.clone(); + for (_, value) in payload.params.iter_mut() { + self.resolve_value(value, depth, in_flight); + } + match self.process_shader_job(&payload) { + Some(texture) => texture_value(texture), + None => payload + .params + .get(&payload.effect_input) + .cloned() + .unwrap_or(NodeValue::None), + } + } + + /// Resolve one plugin job (C++ JobEnginePlugin processing in + /// jobmanager.cpp): recurse into the payload's tagged values, split + /// them into clip input textures and scalar param overrides, then + /// dispatch the render through the installed executor. A failed render + /// leaves the box in the row (the executor has already reported it). + fn process_plugin_job_value( + &mut self, + payload: &PluginJobPayload, + depth: usize, + in_flight: &mut HashSet, + ) -> Option { + let mut payload = payload.clone(); + for (_, value) in payload.values.iter_mut() { + self.resolve_value(value, depth, in_flight); + } + + let mut inputs: Vec<(String, Texture)> = Vec::new(); + let mut values: Vec<(String, NodeValue)> = Vec::new(); + for (key, v) in payload.values.iter() { + match v { + NodeValue::Texture(h) if !h.ctx.is_null() => { + match unsafe { oak_node::handle::get_checked::(h) }.cloned() { + Some(texture) => inputs.push((key.clone(), texture)), + None => eprintln!("plugin job input '{key}' is not a texture box"), } - NodeValue::Texture(_) | NodeValue::None => {} - other => values.push((key.clone(), other.clone())), } + NodeValue::Texture(_) | NodeValue::None => {} + other => values.push((key.clone(), other.clone())), } + } - // Fallback order mirrors pluginrenderer.cpp's effect input - // resolution: the declared effect input, else the first - // available clip texture. - let effect_src = if payload.effect_input_id.is_empty() { + // Fallback order mirrors pluginrenderer.cpp's effect input + // resolution: the declared effect input, else the first + // available clip texture. + let effect_src = if payload.effect_input_id.is_empty() { + None + } else { + inputs + .iter() + .find(|(key, _)| key == &payload.effect_input_id) + .map(|(_, t)| t.clone()) + }; + let src = effect_src + .or_else(|| inputs.first().map(|(_, t)| t.clone())) + .unwrap_or_else(Texture::dummy); + + let spec = JobSpec::Plugin { + instance: payload.instance.0, + time: payload.time.to_f64(), + effect_input_id: if payload.effect_input_id.is_empty() { None } else { - inputs - .iter() - .find(|(key, _)| key == &payload.effect_input_id) - .map(|(_, t)| t.clone()) - }; - let src = effect_src - .or_else(|| inputs.first().map(|(_, t)| t.clone())) - .unwrap_or_else(Texture::dummy); - - let spec = JobSpec::Plugin { - instance: payload.instance.0, - time: payload.time.to_f64(), - effect_input_id: if payload.effect_input_id.is_empty() { - None - } else { - Some(payload.effect_input_id.clone()) - }, - inputs, - values, - }; - match self.process_plugin_job(src, &spec) { - Ok(texture) => { - *value = NodeValue::Texture(oak_node::handle::make_owned(texture)); - } - Err(err) => { - eprintln!("plugin job resolve failed: {err:#}"); - } + Some(payload.effect_input_id.clone()) + }, + inputs, + values, + }; + match self.process_plugin_job(src, &spec) { + Ok(texture) => Some(texture_value(texture)), + Err(err) => { + eprintln!("plugin job resolve failed: {err:#}"); + None } } } - /// Resolve the footage payloads a footage node pushed into its output - /// table (C++ FootageJob processing in jobmanager.cpp): decodes each - /// boxed [`FootageJobPayload`] at its request time and replaces the - /// box with the resulting texture. Genuine textures pass through. - fn resolve_footage_jobs(&mut self, table: &mut NodeValueTable) { - let size = self.frame_size.unwrap_or((0, 0)); - for (_, value, _) in table.rows_mut() { - let NodeValue::Texture(handle) = value else { - continue; - }; - if handle.ctx.is_null() { - continue; - } - let payload = unsafe { - oak_node::handle::get_checked::(handle) - } - .cloned(); - let Some(payload) = payload else { - continue; - }; - match render_footage_frame( - &payload.filename, - payload.stream_index, - payload.time, - size, - PixelFormat::F32, - ) { - Ok(texture) => { - *value = NodeValue::Texture(oak_node::handle::make_owned(texture)); - } - Err(err) => { - eprintln!("footage job decode failed: {err:#}"); - } + /// Resolve one color transform job (C++ ColorTransformJob processing in + /// jobmanager.cpp): recurse into the input value, apply the processor, + /// and replace the box with the result. A failure falls back to the + /// job's resolved input texture (a pass-through — the C++ renderer + /// leaves the failed transform's output as its input). + fn process_color_transform_job_value( + &mut self, + payload: &ColorTransformJobPayload, + depth: usize, + in_flight: &mut HashSet, + ) -> NodeValue { + let mut payload = payload.clone(); + self.resolve_value(&mut payload.input, depth, in_flight); + match self.process_color_transform_job(&payload) { + Ok(texture) => texture_value(texture), + Err(err) => { + eprintln!("color transform job failed: {err:#}"); + payload.input.clone() } } } - /// Resolve the shader payloads an effect node pushed into its output - /// table (C++ ShaderJob processing in jobmanager.cpp): execute each - /// boxed [`ShaderJobPayload`] on the shared GPU context and replace - /// the box with the result texture. Failed or un-runnable jobs fall - /// back to the effect input texture from the params row (a pass- - /// through — C++ leaves the failed shader's output as its input); - /// a missing input resolves to `NodeValue::None`. - fn resolve_shader_jobs(&mut self, table: &mut NodeValueTable) { - // Collect the boxes up front: replacing a row while iterating - // `rows_mut` would alias the table. - let jobs: Vec<(usize, ShaderJobPayload)> = table - .rows_mut() - .iter_mut() - .enumerate() - .filter_map(|(i, (_, value, _))| { - let NodeValue::Texture(handle) = value else { - return None; - }; - if handle.ctx.is_null() { - return None; + /// Resolve one cache job (C++ CacheJob processing in jobmanager.cpp): + /// recurse into the fallback value first, then read the frame the cache + /// wrote at the job's path. A missing or unreadable file substitutes + /// the fallback — a real texture by then, not another job box — and is + /// logged once per path (a cache miss repeats every frame). + fn process_cache_job_value( + &mut self, + payload: &CacheJobPayload, + depth: usize, + in_flight: &mut HashSet, + ) -> NodeValue { + let mut payload = payload.clone(); + self.resolve_value(&mut payload.fallback, depth, in_flight); + match self.process_cache_job(&payload) { + Ok(texture) => texture_value(texture), + Err(err) => { + let key = format!("cache:{}", payload.path); + if unsupported_warned().insert(key) { + eprintln!( + "cache job \"{}\" failed, using the cache node's input: {err:#}", + payload.path + ); } - let payload = unsafe { - oak_node::handle::get_checked::(handle) - } - .cloned(); - payload.map(|p| (i, p)) - }) - .collect(); - for (i, payload) in jobs { - let resolved = match self.process_shader_job(&payload) { - Some(texture) => NodeValue::Texture(oak_node::handle::make_owned(texture)), - None => payload - .params - .get(&payload.effect_input) - .cloned() - .unwrap_or(NodeValue::None), - }; - table.rows_mut()[i].1 = resolved; - } - } - - /// Resolve the color-transform payloads an OCIO node pushed into its - /// output table (C++ ColorTransformJob processing in jobmanager.cpp): - /// apply each boxed [`ColorTransformJobPayload`]'s processor to its - /// input texture and replace the box with the result. Failures fall - /// back to the job's input texture (a pass-through — the C++ renderer - /// leaves the failed transform's output as its input); genuine - /// textures pass through. - fn resolve_color_transform_jobs(&mut self, table: &mut NodeValueTable) { - // Collect the boxes up front: replacing a row while iterating - // `rows_mut` would alias the table. - let jobs: Vec<(usize, ColorTransformJobPayload)> = table - .rows_mut() - .iter_mut() - .enumerate() - .filter_map(|(i, (_, value, _))| { - let NodeValue::Texture(handle) = value else { - return None; - }; - if handle.ctx.is_null() { - return None; - } - let payload = unsafe { - oak_node::handle::get_checked::(handle) - } - .cloned(); - payload.map(|p| (i, p)) - }) - .collect(); - for (i, payload) in jobs { - let resolved = match self.process_color_transform_job(&payload) { - Ok(texture) => NodeValue::Texture(oak_node::handle::make_owned(texture)), - Err(err) => { - eprintln!("color transform job failed: {err:#}"); - payload.input.clone() - } - }; - table.rows_mut()[i].1 = resolved; + (*payload.fallback).clone() + } } } @@ -744,10 +784,7 @@ impl RenderEvalHooks { if depth >= MAX_JOB_DEPTH { return None; } - let nested = (unsafe { - oak_node::handle::get_checked::(handle) - }) - .cloned()?; + let nested = (unsafe { oak_node::jobs::shader_job(handle) }).cloned()?; Some(self.process_shader_job_depth(&nested, depth + 1)?) }); let tex = tex?; @@ -923,10 +960,14 @@ impl oak_node::traverser::RenderHooks for RenderEvalHooks { table: &mut NodeValueTable, ) { let _ = node; - self.resolve_plugin_jobs(table); - self.resolve_footage_jobs(table); - self.resolve_shader_jobs(table); - self.resolve_color_transform_jobs(table); + // One pass over the table: every texture-channel value that boxes + // a job resolves in place (jobs nested in its inputs first). A + // job that produces no value leaves its box, so a later row + // probing the same box still sees the unresolved request. + let mut in_flight = HashSet::new(); + for (_, value, _) in table.rows_mut() { + self.resolve_value(value, 0, &mut in_flight); + } } } @@ -2704,13 +2745,140 @@ mod tests { assert_eq!(tex.format(), PixelFormat::F32); } - #[test] - fn hooks_fail_explainably_for_deferred_jobs() { - let mut hooks = RenderEvalHooks::new(); + /// A temporary disk frame-cache path for the cache-job tests. + fn cache_job_temp_path(tag: &str) -> String { + static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + std::env::temp_dir() + .join(format!( + "oakrender_cache_{}_{n}_{tag}.bin", + std::process::id() + )) + .to_string_lossy() + .into_owned() + } - assert!(hooks - .process_video_cache_job(&JobSpec::Cache { path: "p".into() }) - .is_err()); + /// The resolved texture in the table's texture channel; panics when the + /// value is still a job box (resolution did not run). + fn resolved_texture(table: &NodeValueTable) -> Texture { + let Some(NodeValue::Texture(handle)) = table.get(oak_node::value::ValueType::Texture) + else { + panic!("no texture in the table"); + }; + assert!(!handle.ctx.is_null(), "null texture box"); + (unsafe { oak_node::handle::get_checked::(handle) }) + .cloned() + .expect("value is still a job box (unresolved)") + } + + /// A frame-cache job box around `payload`. + fn cache_job_box(payload: CacheJobPayload) -> NodeValue { + NodeValue::Texture(oak_node::handle::make_owned(Job::CacheJob(payload))) + } + + /// A 4x3 F32 frame filled with `rgba`. + fn cache_test_frame(rgba: [f32; 4]) -> Frame { + let mut frame = generate_frame(Rational::new(3, 1), (4, 3), PixelFormat::F32).unwrap(); + for px in frame.data.chunks_exact_mut(16) { + for (c, v) in px.chunks_exact_mut(4).zip(rgba) { + c.copy_from_slice(&v.to_le_bytes()); + } + } + frame + } + + #[test] + fn cache_job_reads_the_saved_frame() { + use oak_node::traverser::RenderHooks; + + let path = cache_job_temp_path("hit"); + let frame = cache_test_frame([0.25, 0.5, 0.75, 1.0]); + crate::frameio::save_cache_frame(&path, &frame).unwrap(); + + let mut table = NodeValueTable::default(); + table.push( + oak_node::value::ValueType::Texture, + cache_job_box(CacheJobPayload { + path: path.clone(), + time: Rational::new(3, 1), + fallback: Box::new(NodeValue::None), + }), + None, + ); + let mut hooks = RenderEvalHooks::new(); + hooks.resolve(oak_node::id::NodeId::INVALID, &NodeValueRow::new(), &mut table); + + let out = resolved_texture(&table); + assert_eq!(out.size(), (4, 3)); + assert_eq!(first_pixel(&out), [0.25, 0.5, 0.75, 1.0]); + let _ = std::fs::remove_file(&path); + } + + #[test] + fn cache_job_missing_file_substitutes_its_input() { + use oak_node::traverser::RenderHooks; + + // The file is never written: the load fails and the fallback — a + // real texture box — must end up in the table. + let path = cache_job_temp_path("miss"); + let fallback = filled_frame((2, 2), [0.1, 0.2, 0.3, 0.4]); + let mut table = NodeValueTable::default(); + table.push( + oak_node::value::ValueType::Texture, + cache_job_box(CacheJobPayload { + path, + time: Rational::new(0, 1), + fallback: Box::new(NodeValue::Texture(oak_node::handle::make_owned(fallback))), + }), + None, + ); + let mut hooks = RenderEvalHooks::new(); + hooks.resolve(oak_node::id::NodeId::INVALID, &NodeValueRow::new(), &mut table); + + let out = resolved_texture(&table); + assert_eq!(out.size(), (2, 2)); + assert_eq!(first_pixel(&out), [0.1, 0.2, 0.3, 0.4]); + } + + #[test] + fn nested_cache_job_resolves_through_the_outer_shader_job() { + use oak_node::traverser::RenderHooks; + + let path = cache_job_temp_path("nested"); + let frame = cache_test_frame([0.5, 0.25, 0.125, 1.0]); + crate::frameio::save_cache_frame(&path, &frame).unwrap(); + + // The outer shader names a type nobody registered, so the pass + // cannot run and falls back to its effect input — the nested cache + // job, which must already have resolved to the frame from disk. + let mut params = NodeValueRow::new(); + params.insert( + "tex_in".into(), + cache_job_box(CacheJobPayload { + path: path.clone(), + time: Rational::new(3, 1), + fallback: Box::new(NodeValue::None), + }), + ); + let outer = Job::ShaderJob(ShaderJobPayload { + type_id: "org.olivevideoeditor.Olive.thisdoesnotexist".into(), + effect_input: "tex_in".into(), + params, + ..ShaderJobPayload::default() + }); + let mut table = NodeValueTable::default(); + table.push( + oak_node::value::ValueType::Texture, + NodeValue::Texture(oak_node::handle::make_owned(outer)), + None, + ); + let mut hooks = RenderEvalHooks::new(); + hooks.resolve(oak_node::id::NodeId::INVALID, &NodeValueRow::new(), &mut table); + + let out = resolved_texture(&table); + assert_eq!(out.size(), (4, 3)); + assert_eq!(first_pixel(&out), [0.5, 0.25, 0.125, 1.0]); + let _ = std::fs::remove_file(&path); } #[test] @@ -2870,12 +3038,13 @@ mod tests { let mut table = NodeValueTable::default(); table.push( oak_node::value::ValueType::Texture, - NodeValue::Texture(oak_node::handle::make_owned(payload)), + NodeValue::Texture(oak_node::handle::make_owned(Job::PluginJob(payload))), None, ); + use oak_node::traverser::RenderHooks; let mut hooks = RenderEvalHooks::new(); - hooks.resolve_plugin_jobs(&mut table); + hooks.resolve(oak_node::id::NodeId::INVALID, &NodeValueRow::new(), &mut table); let NodeValue::Texture(handle) = table.get(oak_node::value::ValueType::Texture).unwrap() else { @@ -3119,7 +3288,7 @@ mod tests { }; table.push( oak_node::value::ValueType::Texture, - NodeValue::Texture(oak_node::handle::make_owned(payload)), + NodeValue::Texture(oak_node::handle::make_owned(Job::FootageJob(payload))), None, ); let genuine = Texture::wrap_frame(generate_frame(Rational::new(0, 1), (4, 4), PixelFormat::F32).unwrap()); @@ -3129,9 +3298,10 @@ mod tests { None, ); + use oak_node::traverser::RenderHooks; let mut hooks = RenderEvalHooks::new(); hooks.frame_size = Some((32, 32)); - hooks.resolve_footage_jobs(&mut table); + hooks.resolve(oak_node::id::NodeId::INVALID, &NodeValueRow::new(), &mut table); assert_eq!(table.count(), 2, "both rows stay, only the payload is replaced"); let rows = table.rows(); @@ -3198,7 +3368,7 @@ mod tests { let mut table = NodeValueTable::default(); table.push( oak_node::value::ValueType::Texture, - NodeValue::Texture(oak_node::handle::make_owned(payload)), + NodeValue::Texture(oak_node::handle::make_owned(Job::ColorTransformJob(payload))), None, ); @@ -3244,7 +3414,7 @@ mod tests { let mut table = NodeValueTable::default(); table.push( oak_node::value::ValueType::Texture, - NodeValue::Texture(oak_node::handle::make_owned(payload)), + NodeValue::Texture(oak_node::handle::make_owned(Job::ColorTransformJob(payload))), None, ); diff --git a/crates/oak-render/src/frameio.rs b/crates/oak-render/src/frameio.rs new file mode 100644 index 000000000..a2c3efd77 --- /dev/null +++ b/crates/oak-render/src/frameio.rs @@ -0,0 +1,269 @@ +// 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 . +// +//! The disk frame-cache container: a minimal self-describing raw-file +//! format used by the disk frame cache until the liboakoiio/EXR path +//! lands (oakcodec's `oiio.rs` is a stub, so the payload I/O is +//! implemented here). +//! +//! The file layout is: 9-byte magic `OAKCACHE1`, `u32` LE container +//! version (1), `i32` LE width/height/format/channels, `i64` LE +//! timestamp numerator/denominator, `u64` LE payload length, then the +//! tightly packed pixel payload (the frame's `data` bytes verbatim). +//! All multi-byte integers are little-endian. +//! +//! The video metadata beyond dims/format is not persisted: loaded frames +//! carry default [`VideoParamsPod`] values with width/height/format +//! mirrored from the header (cache frames are full-resolution internal +//! F32 RGBA, which is all the payload needs). + +use crate::error::{Error, Result}; +use oak_core::frame::VideoParamsPod; +use oak_core::texture::Frame; +use oak_core::{PixelFormat, Rational}; + +/// Container magic (`OAKCACHE1`, one digit per container revision). +const MAGIC: &[u8; 9] = b"OAKCACHE1"; + +/// Container version written by this implementation. +const VERSION: u32 = 1; + +/// Fixed header length in bytes (magic + version + w/h + format + +/// channels + timestamp + payload length). +const HEADER_LEN: usize = MAGIC.len() + 4 + 4 + 4 + 4 + 4 + 8 + 8 + 8; + +/// Serialize `frame` to `path` (atomically: a `.tmp` sibling is written +/// then renamed over the destination). +/// +/// Fails with [`Error::Failed`] when the parent directory does not exist +/// or the write/rename fails. +pub fn save_cache_frame(path: &str, frame: &Frame) -> Result<()> { + let mut buf = Vec::with_capacity(HEADER_LEN + frame.data.len()); + buf.extend_from_slice(MAGIC); + buf.extend_from_slice(&VERSION.to_le_bytes()); + buf.extend_from_slice(&frame.width.to_le_bytes()); + buf.extend_from_slice(&frame.height.to_le_bytes()); + buf.extend_from_slice(&(frame.format as i32).to_le_bytes()); + buf.extend_from_slice(&frame.channels.to_le_bytes()); + buf.extend_from_slice(&frame.timestamp.numerator().to_le_bytes()); + buf.extend_from_slice(&frame.timestamp.denominator().to_le_bytes()); + buf.extend_from_slice(&(frame.data.len() as u64).to_le_bytes()); + buf.extend_from_slice(&frame.data); + + let tmp = format!("{path}.tmp"); + std::fs::write(&tmp, &buf) + .map_err(|e| Error::Failed(format!("frame cache write failed: {tmp}: {e}")))?; + std::fs::rename(&tmp, path) + .map_err(|e| Error::Failed(format!("frame cache rename failed: {tmp}: {e}")))?; + Ok(()) +} + +/// Deserialize a frame saved by [`save_cache_frame`]. +/// +/// Fails with [`Error::NotFound`] when the file does not exist and +/// [`Error::Failed`] when it is too short, has the wrong magic/version, +/// carries invalid metadata or a payload length that does not match +/// `height × linesize`. +pub fn load_cache_frame(path: &str) -> Result { + let bytes = std::fs::read(path).map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + Error::NotFound + } else { + Error::Failed(format!("frame cache read failed: {path}: {e}")) + } + })?; + if bytes.len() < HEADER_LEN { + return Err(Error::Failed(format!( + "frame cache truncated header: {path} ({} bytes)", + bytes.len() + ))); + } + if &bytes[0..MAGIC.len()] != MAGIC { + return Err(Error::Failed(format!("frame cache bad magic: {path}"))); + } + let mut off = MAGIC.len(); + let version = read_u32(&bytes, &mut off); + if version != VERSION { + return Err(Error::Failed(format!( + "frame cache unsupported version {version}: {path}" + ))); + } + let width = read_i32(&bytes, &mut off); + let height = read_i32(&bytes, &mut off); + let format = match read_i32(&bytes, &mut off) { + f if f == PixelFormat::U8 as i32 => PixelFormat::U8, + f if f == PixelFormat::U10 as i32 => PixelFormat::U10, + f if f == PixelFormat::U16 as i32 => PixelFormat::U16, + f if f == PixelFormat::F16 as i32 => PixelFormat::F16, + f if f == PixelFormat::F32 as i32 => PixelFormat::F32, + f => return Err(Error::Failed(format!("frame cache bad format {f}: {path}"))), + }; + let channels = read_i32(&bytes, &mut off); + let ts_num = read_i64(&bytes, &mut off); + let ts_den = read_i64(&bytes, &mut off); + let payload_len = read_u64(&bytes, &mut off); + + if width < 0 || height < 0 { + return Err(Error::Failed(format!( + "frame cache negative dimensions {width}x{height}: {path}" + ))); + } + if channels <= 0 { + return Err(Error::Failed(format!( + "frame cache bad channel count {channels}: {path}" + ))); + } + let linesize = (width as usize) + .checked_mul(channels as usize) + .and_then(|n| n.checked_mul(format.bytes_per_channel())) + .ok_or_else(|| Error::Failed(format!("frame cache linesize overflow: {path}")))?; + let expected = (height as usize) + .checked_mul(linesize) + .ok_or_else(|| Error::Failed(format!("frame cache size overflow: {path}")))?; + if payload_len != expected as u64 { + return Err(Error::Failed(format!( + "frame cache payload length mismatch: {path} \ + (header {payload_len}, expected {expected})" + ))); + } + let payload = &bytes[off..]; + if payload.len() != expected { + return Err(Error::Failed(format!( + "frame cache truncated payload: {path} ({} bytes, expected {expected})", + payload.len() + ))); + } + + Ok(Frame { + width, + height, + format, + channels, + timestamp: Rational::new(ts_num, ts_den), + data: payload.to_vec(), + params: VideoParamsPod { + width, + height, + format: format as i32, + ..VideoParamsPod::default() + }, + }) +} + +fn read_i32(bytes: &[u8], off: &mut usize) -> i32 { + let v = i32::from_le_bytes(bytes[*off..*off + 4].try_into().unwrap()); + *off += 4; + v +} + +fn read_u32(bytes: &[u8], off: &mut usize) -> u32 { + let v = u32::from_le_bytes(bytes[*off..*off + 4].try_into().unwrap()); + *off += 4; + v +} + +fn read_i64(bytes: &[u8], off: &mut usize) -> i64 { + let v = i64::from_le_bytes(bytes[*off..*off + 8].try_into().unwrap()); + *off += 8; + v +} + +fn read_u64(bytes: &[u8], off: &mut usize) -> u64 { + let v = u64::from_le_bytes(bytes[*off..*off + 8].try_into().unwrap()); + *off += 8; + v +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::Path; + use std::sync::atomic::{AtomicU32, Ordering}; + + static COUNTER: AtomicU32 = AtomicU32::new(0); + + fn temp_path(tag: &str) -> String { + let n = COUNTER.fetch_add(1, Ordering::Relaxed); + std::env::temp_dir() + .join(format!("oakcache_io_{}_{n}_{tag}.bin", std::process::id())) + .to_string_lossy() + .into_owned() + } + + fn sample_frame(width: i32, height: i32) -> Frame { + let format = PixelFormat::F32; + let channels = VideoParamsPod::INTERNAL_CHANNEL_COUNT; + let size = + (width as usize) * (height as usize) * (channels as usize) * format.bytes_per_channel(); + let data = (0..size).map(|i| (i * 7 % 251) as u8).collect(); + Frame { + width, + height, + format, + channels, + timestamp: Rational::new(5, 2), + data, + params: VideoParamsPod { + width, + height, + format: format as i32, + ..VideoParamsPod::default() + }, + } + } + + fn cleanup(path: &str) { + let _ = std::fs::remove_file(path); + let _ = std::fs::remove_file(format!("{path}.tmp")); + } + + #[test] + fn save_load_round_trips_every_field() { + let path = temp_path("roundtrip"); + let frame = sample_frame(4, 3); + save_cache_frame(&path, &frame).expect("save"); + let loaded = load_cache_frame(&path).expect("load"); + assert_eq!(loaded, frame); + assert_eq!(loaded.data, frame.data); + cleanup(&path); + } + + #[test] + fn load_missing_file_is_err() { + let path = temp_path("missing"); + assert!(load_cache_frame(&path).is_err()); + assert!(!Path::new(&path).exists()); + } + + #[test] + fn load_truncated_payload_is_err() { + let path = temp_path("truncated"); + let frame = sample_frame(4, 3); + save_cache_frame(&path, &frame).expect("save"); + let full = std::fs::read(&path).expect("read back"); + std::fs::write(&path, &full[..full.len() - 8]).expect("truncate"); + assert!(load_cache_frame(&path).is_err()); + cleanup(&path); + } + + #[test] + fn load_bad_magic_is_err() { + let path = temp_path("badmagic"); + std::fs::write(&path, b"NOTACACHE.................").expect("write"); + assert!(load_cache_frame(&path).is_err()); + cleanup(&path); + } +} diff --git a/crates/oak-render/src/lib.rs b/crates/oak-render/src/lib.rs index 1b02c5015..c59a2570c 100644 --- a/crates/oak-render/src/lib.rs +++ b/crates/oak-render/src/lib.rs @@ -28,6 +28,7 @@ //! - `manager` — RenderManager singleton + disk cache //! - `autocacher` — PreviewAutoCacher //! - `eval` — the evaluation seam (RenderHooks) +//! - `frameio` — the disk frame-cache container (liboakoiio pending) //! - `shaderfx` — effect GLSL→WGSL translation + uniform packing //! - `copier` — render-side project-copy client (oaknode C ABI) //! - `cancelatom` — the cancellation primitive @@ -46,6 +47,7 @@ pub mod cancelatom; pub mod copier; pub mod error; pub mod eval; +pub mod frameio; pub mod handle; pub mod ipc; pub mod manager;