render: Job enum in the tables, single-loop match resolve, real CacheJob
M0a of the render-pipeline plan (docs/zh/plans/render-pipeline-threads.md): - oak-node: every payload push site (58 across footage.rs, plugin.rs and the nodes/* effects) now boxes the Job enum instead of the raw payload. The enum gains CacheJob with a CacheJobPayload (path + time + fallback value, the C++ cachejob.h shape), plus safe as_* accessors and unsafe probe helpers beside job_ref. - oak-render: RenderEvalHooks::resolve is one loop over the table — a single get_checked::<Job> probe per texture value, a match dispatch to process_footage/shader/plugin/color_transform/cache, and recursive resolution of the job boxes embedded in a payload's inputs (depth-capped, cycle-guarded) — replacing the four sequential full-table scans (resolve_*_jobs, deleted). - The disk frame cache is real: frameio.rs implements a minimal self-describing F32 container (magic/version/dims/format/timestamp + payload, tmp-write + atomic rename, full header validation on load) because the OIIO bridge is a stub and EXR is unavailable in this build; process_cache_job genuinely reads the file before falling back to the job's (already resolved) fallback value. - Tests: CacheJob roundtrip (save -> resolve -> pixel equality), missing-file fallback, nested cache-job-through-shader resolution, plus four frameio container tests. 2330 passed, 0 failed across the workspace.
This commit is contained in:
@@ -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::<crate::jobs::FootageJobPayload>(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);
|
||||
|
||||
+148
-9
@@ -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<ColorProcessor>,
|
||||
/// 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<NodeValue>,
|
||||
}
|
||||
|
||||
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::<Job>(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)
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(h) }
|
||||
let payload = unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.blur");
|
||||
assert_eq!(
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(blend) }
|
||||
let nested = unsafe { crate::jobs::shader_job(blend) }
|
||||
.expect("nested job payload boxed");
|
||||
assert_eq!(nested.shader_id, "checkerboard");
|
||||
assert_eq!(
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(payload.params.get(MAX_INPUT), Some(&NodeValue::Float(0.5)));
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(blend) }
|
||||
let nested = unsafe { crate::jobs::shader_job(blend) }
|
||||
.expect("nested job payload boxed");
|
||||
assert_eq!(nested.shader_id, "colorbars");
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(
|
||||
payload.params.get(CONTRAST_INPUT),
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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);
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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::<ShaderJobPayload>(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::<ShaderJobPayload>(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");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(h) }.is_some(),
|
||||
unsafe { crate::jobs::shader_job(h) }.is_some(),
|
||||
"a moved corner must push the cornerpin shader job"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(&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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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::<ShaderJobPayload>(h) }
|
||||
let payload = unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed");
|
||||
assert_eq!(
|
||||
payload.params.get(RADIUS_INPUT),
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ mod tests {
|
||||
panic!("job row expected");
|
||||
};
|
||||
assert!(unsafe {
|
||||
crate::handle::get_checked::<crate::jobs::ColorTransformJobPayload>(handle)
|
||||
crate::jobs::color_transform_job(handle)
|
||||
}
|
||||
.is_some());
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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::<ShaderJobPayload>(h) }
|
||||
let payload = unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed");
|
||||
assert_eq!(payload.iterations, 1);
|
||||
assert_eq!(payload.iterative_input, "");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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::<ShaderJobPayload>(h) }
|
||||
let payload = unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed");
|
||||
assert_eq!(
|
||||
payload.params.get(RADIUS_INPUT),
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(&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::<crate::jobs::ShaderJobPayload>(&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);
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(payload.params.get(GAMMA_INPUT), Some(&NodeValue::Float(2.0)));
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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::<crate::jobs::ShaderJobPayload>(&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::<u8>(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::<crate::jobs::ShaderJobPayload>(&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::<crate::jobs::ShaderJobPayload>(&blend)
|
||||
crate::jobs::shader_job(&blend)
|
||||
}
|
||||
.expect("blend job payload boxed");
|
||||
assert_eq!(blend_job.type_id, "org.olivevideoeditor.Olive.solidgenerator");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(
|
||||
payload.params.get(BLACKPOINT_INPUT),
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(
|
||||
payload.params.get(INVERT_A_INPUT),
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(handle)
|
||||
crate::jobs::shader_job(handle)
|
||||
}
|
||||
.expect("real shader job");
|
||||
assert_eq!(job.shader_id, "mask");
|
||||
|
||||
@@ -417,7 +417,7 @@ mod tests {
|
||||
_ => panic!("texture expected"),
|
||||
};
|
||||
let payload = unsafe {
|
||||
crate::handle::get_checked::<crate::jobs::ShaderJobPayload>(&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::<crate::jobs::ShaderJobPayload>(&handle)
|
||||
crate::jobs::shader_job(&handle)
|
||||
};
|
||||
assert!(payload.is_none(), "no shader job for a null texture");
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(&handle)
|
||||
crate::jobs::shader_job(&handle)
|
||||
}
|
||||
.expect("shader job payload boxed in the pushed texture");
|
||||
assert_eq!(payload.type_id, "org.olivevideoeditor.Olive.math");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(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));
|
||||
|
||||
@@ -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::<crate::jobs::ColorTransformJobPayload>(handle)
|
||||
crate::jobs::color_transform_job(handle)
|
||||
}
|
||||
.expect("a boxed ColorTransformJobPayload");
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle)
|
||||
crate::jobs::shader_job(handle)
|
||||
}
|
||||
.expect("real shader job");
|
||||
// White <= black is raised to black + 0.000001.
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(handle)
|
||||
crate::jobs::shader_job(handle)
|
||||
}
|
||||
.expect("real shader job");
|
||||
assert_eq!(job.shader_id, "rgb");
|
||||
|
||||
@@ -726,7 +726,7 @@ mod tests {
|
||||
panic!("job row expected");
|
||||
};
|
||||
assert!(unsafe {
|
||||
crate::handle::get_checked::<crate::jobs::ColorTransformJobPayload>(handle)
|
||||
crate::jobs::color_transform_job(handle)
|
||||
}
|
||||
.is_some());
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(h) }
|
||||
let payload = unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(payload.shader_id, "rgbmult");
|
||||
}
|
||||
|
||||
@@ -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::<PluginJobPayload>(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);
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(&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");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&handle) }
|
||||
let payload = unsafe { crate::jobs::shader_job(&handle) }
|
||||
.expect("shader job payload expected");
|
||||
assert_eq!(
|
||||
payload.params.get(OFFSET_INPUT),
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(&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::<ShaderJobPayload>(blend) }
|
||||
let nested = unsafe { crate::jobs::shader_job(blend) }
|
||||
.expect("nested job payload boxed");
|
||||
assert_eq!(nested.shader_id, "ramp");
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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::<crate::jobs::ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("shader job pushed");
|
||||
assert_eq!(
|
||||
payload.params.get(SATURATION_INPUT),
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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::<ShaderJobPayload>(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::<ShaderJobPayload>(blend) }
|
||||
unsafe { crate::jobs::shader_job(blend) }
|
||||
.expect("nested shape job payload boxed");
|
||||
assert_eq!(shape.shader_id, "shape");
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(handle)
|
||||
crate::jobs::shader_job(handle)
|
||||
}
|
||||
.expect("solid output boxes a ShaderJobPayload");
|
||||
assert_eq!(
|
||||
|
||||
@@ -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::<ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(handle) }
|
||||
unsafe { crate::jobs::shader_job(handle) }
|
||||
.expect("handle carries a ShaderJobPayload")
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(&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, "");
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<ShaderJobPayload>(h) }
|
||||
unsafe { crate::jobs::shader_job(h) }
|
||||
.expect("shader job payload boxed")
|
||||
.clone()
|
||||
}
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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::<crate::jobs::ShaderJobPayload>(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, "");
|
||||
|
||||
@@ -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 方法)
|
||||
|
||||
@@ -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 —
|
||||
|
||||
+379
-209
@@ -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<Texture> {
|
||||
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<Texture> {
|
||||
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<usize>,
|
||||
) {
|
||||
/// 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::<oak_node::nodes::plugin::PluginJobPayload>(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::<Texture>(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<usize>,
|
||||
) -> Option<NodeValue> {
|
||||
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<NodeValue> {
|
||||
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<usize>,
|
||||
) -> 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<usize>,
|
||||
) -> Option<NodeValue> {
|
||||
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::<Texture>(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::<FootageJobPayload>(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<usize>,
|
||||
) -> 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<usize>,
|
||||
) -> 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::<ShaderJobPayload>(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::<ColorTransformJobPayload>(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::<ShaderJobPayload>(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::<Texture>(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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
//! 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<Frame> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user