use std::cell::RefCell;
use std::collections::HashSet;
use half::f16;
use log::trace;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::attr_schemas::{COMP_SCHEMA, LAYER_SCHEMA};
use super::attrs::{AttrValue, Attrs};
use super::effects::Effect;
use super::compositor::{BlendMode, CpuCompositor};
use super::transform;
use super::frame::{Frame, FrameStatus, PixelBuffer, PixelFormat};
use super::keys::*;
use super::node::{ComputeContext, Node};
thread_local! {
static THREAD_COMPOSITOR: RefCell<CpuCompositor> = const { RefCell::new(CpuCompositor) };
static COMPOSE_STACK: RefCell<HashSet<Uuid>> = RefCell::new(HashSet::new());
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Layer {
pub attrs: Attrs,
#[serde(default)]
pub effects: Vec<Effect>,
}
impl Layer {
pub fn new(source_uuid: Uuid, name: &str, start: i32, duration: i32, dim: (usize, usize)) -> Self {
let mut attrs = Attrs::with_schema(&*LAYER_SCHEMA);
attrs.set_uuid(A_UUID, Uuid::new_v4());
attrs.set_uuid("source_uuid", source_uuid);
attrs.set(A_NAME, AttrValue::Str(name.to_string()));
attrs.set(A_IN, AttrValue::Int(start));
attrs.set(A_OUT, AttrValue::Int(start + duration));
attrs.set(A_SRC_LEN, AttrValue::Int(duration));
attrs.set(A_TRIM_IN, AttrValue::Int(0));
attrs.set(A_TRIM_OUT, AttrValue::Int(0));
attrs.set(A_OPACITY, AttrValue::Float(1.0));
attrs.set(A_VISIBLE, AttrValue::Bool(true));
attrs.set("renderable", AttrValue::Bool(true));
attrs.set(A_SOLO, AttrValue::Bool(false));
attrs.set(A_BLEND_MODE, AttrValue::Str("normal".to_string()));
attrs.set(A_SPEED, AttrValue::Float(1.0));
attrs.set(A_WIDTH, AttrValue::UInt(dim.0 as u32));
attrs.set(A_HEIGHT, AttrValue::UInt(dim.1 as u32));
attrs.set(A_POSITION, AttrValue::Vec3([0.0, 0.0, 0.0]));
attrs.set(A_ROTATION, AttrValue::Vec3([0.0, 0.0, 0.0]));
attrs.set(A_SCALE, AttrValue::Vec3([1.0, 1.0, 1.0]));
attrs.set(A_PIVOT, AttrValue::Vec3([0.0, 0.0, 0.0]));
attrs.clear_dirty();
Self { attrs, effects: Vec::new() }
}
pub fn uuid(&self) -> Uuid {
self.attrs.get_uuid(A_UUID).unwrap_or_else(Uuid::nil)
}
pub fn source_uuid(&self) -> Uuid {
self.attrs.get_uuid("source_uuid").unwrap_or_else(Uuid::nil)
}
pub fn from_attrs(source_uuid: Uuid, mut attrs: Attrs) -> Self {
attrs.set_uuid(A_UUID, Uuid::new_v4());
attrs.set_uuid("source_uuid", source_uuid);
Self { attrs, effects: Vec::new() }
}
pub fn attach_schema(&mut self) {
self.attrs.attach_schema(&*LAYER_SCHEMA);
}
pub fn start(&self) -> i32 {
self.attrs.get_i32(A_IN).unwrap_or(0)
}
pub fn end(&self) -> i32 {
let start = self.start();
let src_len = self.attrs.get_i32("src_len").unwrap_or(1);
let speed = self.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
start + ((src_len as f32 / speed) as i32) - 1
}
pub fn work_area(&self) -> (i32, i32) {
let trim_in = self.attrs.get_i32(A_TRIM_IN).unwrap_or(0); let trim_out = self.attrs.get_i32(A_TRIM_OUT).unwrap_or(0); let speed = self.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
let trim_in_scaled = (trim_in as f32 / speed) as i32; let trim_out_scaled = (trim_out as f32 / speed) as i32;
(self.start() + trim_in_scaled, self.end() - trim_out_scaled)
}
pub fn parent_to_local(&self, parent_frame: i32) -> i32 {
let start = self.start(); let speed = self.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
let offset = parent_frame - start;
(offset as f32 * speed) as i32
}
pub fn is_visible(&self) -> bool {
self.attrs.get_bool(A_VISIBLE).unwrap_or(true)
}
pub fn opacity(&self) -> f32 {
self.attrs.get_float(A_OPACITY).unwrap_or(1.0)
}
pub fn blend_mode(&self) -> BlendMode {
self.attrs.get_str(A_BLEND_MODE)
.map(|s| match s {
"screen" => BlendMode::Screen,
"add" => BlendMode::Add,
"subtract" => BlendMode::Subtract,
"multiply" => BlendMode::Multiply,
"divide" => BlendMode::Divide,
"difference" => BlendMode::Difference,
"overlay" => BlendMode::Overlay,
_ => BlendMode::Normal,
})
.unwrap_or(BlendMode::Normal)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CompNode {
pub attrs: Attrs,
pub layers: Vec<Layer>,
#[serde(default)]
pub layer_selection: Vec<Uuid>,
#[serde(default)]
pub layer_selection_anchor: Option<Uuid>,
#[serde(skip)]
pub hovered_layer: Option<Uuid>,
}
impl CompNode {
pub fn new(name: &str, start: i32, end: i32, fps: f32) -> Self {
let mut attrs = Attrs::with_schema(&*COMP_SCHEMA);
let uuid = Uuid::new_v4();
attrs.set_uuid(A_UUID, uuid);
attrs.set(A_NAME, AttrValue::Str(name.to_string()));
attrs.set(A_IN, AttrValue::Int(start));
attrs.set(A_OUT, AttrValue::Int(end));
attrs.set(A_TRIM_IN, AttrValue::Int(0));
attrs.set(A_TRIM_OUT, AttrValue::Int(0));
attrs.set(A_FPS, AttrValue::Float(fps));
attrs.set(A_SPEED, AttrValue::Float(1.0));
attrs.set(A_FRAME, AttrValue::Int(start));
attrs.set(A_WIDTH, AttrValue::UInt(1920));
attrs.set(A_HEIGHT, AttrValue::UInt(1080));
attrs.clear_dirty();
Self {
attrs,
layers: Vec::new(),
layer_selection: Vec::new(),
layer_selection_anchor: None,
hovered_layer: None,
}
}
pub fn with_uuid(mut self, uuid: Uuid) -> Self {
self.attrs.set_uuid(A_UUID, uuid);
self
}
pub fn attach_schema(&mut self) {
self.attrs.attach_schema(&*COMP_SCHEMA);
for layer in &mut self.layers {
layer.attach_schema();
}
}
pub fn name(&self) -> &str {
self.attrs.get_str(A_NAME).unwrap_or("Untitled")
}
pub fn set_frame(&mut self, frame: i32) {
self.attrs.set(A_FRAME, super::attrs::AttrValue::Int(frame));
}
pub fn play_range(&self, _use_work_area: bool) -> (i32, i32) {
self.work_area()
}
pub fn play_frame_count(&self) -> i32 {
let (start, end) = self.play_range(true);
(end - start + 1).max(0)
}
pub fn set_comp_play_start(&mut self, start: i32) {
let trim_in = (start - self._in()).max(0); self.attrs.set(A_TRIM_IN, super::attrs::AttrValue::Int(trim_in));
}
pub fn set_comp_play_end(&mut self, end: i32) {
let trim_out = (self._out() - end).max(0); self.attrs.set(A_TRIM_OUT, super::attrs::AttrValue::Int(trim_out));
}
pub fn on_activate(&mut self) {
self.rebound();
}
pub fn bounds(&self, use_trim: bool, selection_only: bool, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> (i32, i32) {
if self.layers.is_empty() {
return (0, 100);
}
let use_selection = selection_only && !self.layer_selection.is_empty();
let mut min_start = i32::MAX;
let mut max_end = i32::MIN;
for layer in &self.layers {
if !layer.is_visible() {
continue;
}
if use_selection && !self.layer_selection.contains(&layer.uuid()) {
continue;
}
let (start, end) = if use_trim {
self.get_layer_work_area(layer, media)
} else {
(layer.start(), self.get_layer_end(layer, media))
};
min_start = min_start.min(start);
max_end = max_end.max(end);
}
if min_start == i32::MAX || max_end == i32::MIN {
(0, 100)
} else {
(min_start, max_end)
}
}
pub fn get_first_size(&self) -> Option<(usize, usize)> {
let mut earliest: Option<(i32, &Layer)> = None;
for layer in &self.layers {
if !layer.is_visible() {
continue;
}
let (start, _) = layer.work_area();
if earliest.is_none_or(|(s, _)| start < s) {
earliest = Some((start, layer));
}
}
earliest.map(|(_, layer)| {
let w = layer.attrs.get_u32(A_WIDTH).unwrap_or(64) as usize;
let h = layer.attrs.get_u32(A_HEIGHT).unwrap_or(64) as usize;
(w.max(1), h.max(1))
})
}
fn bounds_internal(&self, use_trim: bool) -> (i32, i32) {
if self.layers.is_empty() {
return (0, 100);
}
let mut min_start = i32::MAX;
let mut max_end = i32::MIN;
for layer in &self.layers {
if !layer.is_visible() { continue; }
let (start, end) = if use_trim { layer.work_area() } else { (layer.start(), layer.end()) };
min_start = min_start.min(start);
max_end = max_end.max(end);
}
if min_start == i32::MAX { (0, 100) } else { (min_start, max_end) }
}
pub fn rebound(&mut self) {
let old_bounds = (self._in(), self._out());
let old_work = self.work_area();
let (new_start, new_end) = self.bounds_internal(true);
self.attrs.set(A_IN, AttrValue::Int(new_start));
self.attrs.set(A_OUT, AttrValue::Int(new_end));
if let Some((w, h)) = self.get_first_size() {
self.attrs.set(A_WIDTH, AttrValue::UInt(w as u32));
self.attrs.set(A_HEIGHT, AttrValue::UInt(h as u32));
}
if old_work == old_bounds {
self.attrs.set(A_TRIM_IN, AttrValue::Int(0));
self.attrs.set(A_TRIM_OUT, AttrValue::Int(0));
}
trace!(
"rebound: comp={}, old=[{}..{}], new=[{}..{}]",
self.name(), old_bounds.0, old_bounds.1, new_start, new_end
);
}
pub fn get_frame(&self, frame_idx: i32, project: &super::project::Project, blocking: bool) -> Option<Frame> {
let cache = project.global_cache.as_ref()?;
if let Some(frame) = cache.get(self.uuid(), frame_idx) {
return Some(frame);
}
if !blocking {
return None;
}
let media = project.media.read().expect("media lock");
let ctx = super::node::ComputeContext {
cache: cache.as_ref(),
cache_arc: None, media: &media,
media_arc: None,
workers: None,
epoch: 0,
};
self.compute(frame_idx, &ctx)
}
pub fn add_layer(&mut self, layer: Layer, position: Option<usize>) {
if let Some(idx) = position {
self.layers.insert(idx.min(self.layers.len()), layer);
} else {
self.layers.push(layer);
}
self.mark_dirty();
self.rebound();
}
pub fn remove_layer(&mut self, layer_uuid: Uuid) -> Option<Layer> {
if let Some(idx) = self.layers.iter().position(|l| l.uuid() == layer_uuid) {
let layer = self.layers.remove(idx);
self.mark_dirty();
self.rebound();
Some(layer)
} else {
None
}
}
pub fn get_layer(&self, layer_uuid: Uuid) -> Option<&Layer> {
self.layers.iter().find(|l| l.uuid() == layer_uuid)
}
pub fn get_layer_mut(&mut self, layer_uuid: Uuid) -> Option<&mut Layer> {
self.layers.iter_mut().find(|l| l.uuid() == layer_uuid)
}
pub fn layers_by_source(&self, source_uuid: Uuid) -> Vec<&Layer> {
self.layers.iter().filter(|l| l.source_uuid() == source_uuid).collect()
}
pub fn active_camera<'a>(
&self,
frame_idx: i32,
media: &'a std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>,
) -> Option<(&'a super::camera_node::CameraNode, [f32; 3], [f32; 3])> {
for layer in self.layers.iter().rev() {
if !layer.is_visible() {
continue;
}
let (play_start, play_end) = self.get_layer_work_area(layer, media);
if frame_idx < play_start || frame_idx > play_end {
continue;
}
if let Some(source) = media.get(&layer.source_uuid()) {
if let Some(camera) = source.as_camera() {
let pos = layer.attrs.get_vec3(A_POSITION).unwrap_or([0.0, 0.0, -1000.0]);
let rot = layer.attrs.get_vec3(A_ROTATION).unwrap_or([0.0, 0.0, 0.0]);
return Some((camera, pos, rot));
}
}
}
None
}
pub fn has_3d_content(&self, frame_idx: i32, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> bool {
for layer in &self.layers {
if !layer.is_visible() {
continue;
}
let (play_start, play_end) = self.get_layer_work_area(layer, media);
if frame_idx < play_start || frame_idx > play_end {
continue;
}
let pos = layer.attrs.get_vec3(super::keys::A_POSITION).unwrap_or([0.0, 0.0, 0.0]);
let rot = layer.attrs.get_vec3(super::keys::A_ROTATION).unwrap_or([0.0, 0.0, 0.0]);
if pos[2].abs() > 0.001 || rot[0].abs() > 0.001 || rot[1].abs() > 0.001 {
return true;
}
}
false
}
pub fn remove_child(&mut self, layer_uuid: Uuid) -> Option<Layer> {
self.remove_layer(layer_uuid)
}
pub fn get_children(&self) -> Vec<(Uuid, &Attrs)> {
self.layers.iter().map(|l| (l.uuid(), &l.attrs)).collect()
}
pub fn get_children_sources(&self) -> Vec<(Uuid, Uuid)> {
self.layers.iter().map(|l| (l.uuid(), l.source_uuid())).collect()
}
pub fn set_fps(&mut self, fps: f32) {
self.attrs.set(A_FPS, super::attrs::AttrValue::Float(fps));
}
pub fn idx_to_uuid(&self, idx: usize) -> Option<Uuid> {
self.layers.get(idx).map(|l| l.uuid())
}
pub fn uuid_to_idx(&self, uuid: Uuid) -> Option<usize> {
self.layers.iter().position(|l| l.uuid() == uuid)
}
pub fn is_multi_selected(&self) -> bool {
self.layer_selection.len() > 1
}
pub fn uuids_to_indices(&self, uuids: &[Uuid]) -> Vec<usize> {
uuids.iter().filter_map(|u| self.uuid_to_idx(*u)).collect()
}
pub fn child_in(&self, layer_uuid: Uuid) -> Option<i32> {
self.get_layer(layer_uuid).and_then(|l| l.attrs.get_i32(A_IN))
}
pub fn child_start(&self, layer_uuid: Uuid) -> Option<i32> {
self.get_layer(layer_uuid).map(|l| l.work_area().0)
}
pub fn child_end(&self, layer_uuid: Uuid) -> Option<i32> {
self.get_layer(layer_uuid).map(|l| l.work_area().1)
}
pub fn child_full_end(&self, layer_uuid: Uuid) -> Option<i32> {
self.get_layer(layer_uuid).map(|l| l.end())
}
pub fn child_work_area_abs(&self, layer_uuid: Uuid) -> Option<(i32, i32)> {
self.get_layer(layer_uuid).map(|l| l.work_area())
}
pub fn get_layer_src_len(&self, layer: &Layer, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> i32 {
media.get(&layer.source_uuid())
.map(|source| source.play_frame_count())
.unwrap_or_else(|| layer.attrs.get_i32(super::keys::A_SRC_LEN).unwrap_or(1))
}
pub fn get_layer_end(&self, layer: &Layer, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> i32 {
let src_len = self.get_layer_src_len(layer, media);
let speed = layer.attrs.get_float(super::keys::A_SPEED).unwrap_or(1.0).abs().max(0.001);
layer.start() + ((src_len as f32 / speed) as i32) - 1
}
pub fn get_layer_work_area(&self, layer: &Layer, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> (i32, i32) {
let src_len = self.get_layer_src_len(layer, media);
let speed = layer.attrs.get_float(super::keys::A_SPEED).unwrap_or(1.0).abs().max(0.001);
let trim_in = layer.attrs.get_i32(super::keys::A_TRIM_IN).unwrap_or(0);
let trim_out = layer.attrs.get_i32(super::keys::A_TRIM_OUT).unwrap_or(0);
let layer_start = layer.start();
let trim_in_scaled = (trim_in as f32 / speed) as i32;
let trim_out_scaled = (trim_out as f32 / speed) as i32;
let layer_end = layer_start + ((src_len as f32 / speed) as i32) - 1;
(layer_start + trim_in_scaled, layer_end - trim_out_scaled)
}
pub fn set_child_attrs(&mut self, layer_uuid: Uuid, attrs: Vec<(&str, super::attrs::AttrValue)>) {
if let Some(layer) = self.get_layer_mut(layer_uuid) {
for (key, value) in attrs {
layer.attrs.set(key, value);
}
if layer.attrs.is_dirty() {
self.mark_dirty();
}
}
}
pub fn move_layers(&mut self, layer_uuids: &[Uuid], delta: i32) {
log::trace!("move_layers: uuids={:?}, delta={}", layer_uuids, delta);
for uuid in layer_uuids {
if let Some(layer) = self.get_layer_mut(*uuid) {
let current_in = layer.attrs.get_i32(A_IN).unwrap_or(0);
layer.attrs.set(A_IN, super::attrs::AttrValue::Int(current_in + delta));
log::trace!("move_layers: moved layer {} from {} to {}", uuid, current_in, current_in + delta);
} else {
log::warn!("move_layers: layer {} not found!", uuid);
}
}
self.mark_dirty();
self.rebound();
log::trace!("move_layers: comp marked dirty, is_dirty={}", self.is_dirty(None));
}
pub fn trim_layers(&mut self, layer_uuids: &[Uuid], edge: &str, delta: i32) {
for uuid in layer_uuids {
if let Some(layer) = self.get_layer_mut(*uuid) {
let speed = layer.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
let delta_source = (delta as f32 * speed).round() as i32;
match edge {
"in" | "start" => {
let current = layer.attrs.get_i32(A_TRIM_IN).unwrap_or(0);
layer.attrs.set(A_TRIM_IN, super::attrs::AttrValue::Int(current + delta_source));
}
"out" | "end" => {
let current = layer.attrs.get_i32(A_TRIM_OUT).unwrap_or(0);
layer.attrs.set(A_TRIM_OUT, super::attrs::AttrValue::Int(current - delta_source));
}
_ => {}
}
}
}
self.mark_dirty();
self.rebound();
}
pub fn add_child_layer(
&mut self,
source_uuid: Uuid,
name: &str,
start_frame: i32,
duration: i32,
insert_idx: Option<usize>,
source_dim: (usize, usize),
renderable: bool,
initial_position: Option<[f32; 3]>,
) -> anyhow::Result<Uuid> {
let mut layer = Layer::new(source_uuid, name, start_frame, duration, source_dim);
layer.attrs.set("renderable", AttrValue::Bool(renderable));
if let Some(pos) = initial_position {
layer.attrs.set(A_POSITION, AttrValue::Vec3(pos));
}
let uuid = layer.uuid();
self.add_layer(layer, insert_idx);
Ok(uuid)
}
pub fn trim_in(&self) -> i32 {
self.attrs.get_i32(A_TRIM_IN).unwrap_or(0)
}
pub fn trim_out(&self) -> i32 {
self.attrs.get_i32(A_TRIM_OUT).unwrap_or(0)
}
pub fn is_file_mode(&self) -> bool {
false
}
pub fn layers_uuids_vec(&self) -> Vec<Uuid> {
self.layers.iter().map(|l| l.uuid()).collect()
}
pub fn layers_attrs_get(&self, uuid: &Uuid) -> Option<&Attrs> {
self.layers.iter().find(|l| l.uuid() == *uuid).map(|l| &l.attrs)
}
pub fn layers_attrs_get_mut(&mut self, uuid: &Uuid) -> Option<&mut Attrs> {
self.layers.iter_mut().find(|l| l.uuid() == *uuid).map(|l| &mut l.attrs)
}
pub fn get_child_edges(&self) -> Vec<(i32, bool)> {
let mut edges = Vec::new();
for layer in &self.layers {
let start = layer.attrs.layer_start();
let end = layer.attrs.layer_end();
if start <= end {
edges.push((start, true));
edges.push((end, false));
}
}
edges.sort_by_key(|(frame, _)| *frame);
edges.dedup_by_key(|(frame, _)| *frame);
edges
}
pub fn compute_layer_rows(&self, child_order: &[usize]) -> std::collections::HashMap<usize, usize> {
use std::collections::HashMap;
let mut layer_rows: HashMap<usize, usize> = HashMap::new();
let mut occupied_rows: HashMap<usize, Vec<(i32, i32)>> = HashMap::new();
for &idx in child_order {
let Some(layer) = self.layers.get(idx) else { continue };
let start = layer.attrs.full_bar_start();
let end = layer.attrs.full_bar_end();
let mut row = 0;
loop {
let mut row_free = true;
if let Some(ranges) = occupied_rows.get(&row) {
for (occ_start, occ_end) in ranges {
if start <= *occ_end && end >= *occ_start {
row_free = false;
break;
}
}
}
if row_free {
occupied_rows.entry(row).or_default().push((start, end));
layer_rows.insert(idx, row);
break;
}
row += 1;
}
}
layer_rows
}
pub fn check_collisions(
&self,
potential_child: Uuid,
media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>,
hier: bool,
) -> bool {
let my_uuid = self.uuid();
if potential_child == my_uuid {
return true;
}
if !hier {
return self.layers.iter().any(|l| l.source_uuid() == potential_child);
}
let mut stack = vec![potential_child];
let mut visited = HashSet::new();
while let Some(current) = stack.pop() {
if current == my_uuid {
return true;
}
if !visited.insert(current) {
continue;
}
if let Some(node) = media.get(¤t) {
for input in node.inputs() {
stack.push(input);
}
}
}
false
}
pub fn cache_frame_statuses(&self, global_cache: Option<&std::sync::Arc<crate::core::global_cache::GlobalFrameCache>>) -> Option<Vec<FrameStatus>> {
let duration = self.frame_count();
if duration <= 0 {
return None;
}
let Some(cache) = global_cache else {
return Some(vec![FrameStatus::Placeholder; duration as usize]);
};
let comp_uuid = self.uuid();
let comp_start = self._in();
let mut statuses = Vec::with_capacity(duration as usize);
for frame_offset in 0..duration {
let frame_idx = comp_start + frame_offset;
let status = cache
.get_status(comp_uuid, frame_idx)
.unwrap_or(FrameStatus::Placeholder);
statuses.push(status);
}
Some(statuses)
}
pub fn move_child(&mut self, layer_idx: usize, new_start: i32) -> anyhow::Result<()> {
let layer = self.layers.get_mut(layer_idx)
.ok_or_else(|| anyhow::anyhow!("Layer index out of bounds"))?;
layer.attrs.set(A_IN, AttrValue::Int(new_start));
self.mark_dirty();
self.rebound();
Ok(())
}
pub fn set_child_start(&mut self, layer_idx: usize, new_play_start: i32) -> anyhow::Result<()> {
let layer = self.layers.get_mut(layer_idx)
.ok_or_else(|| anyhow::anyhow!("Layer index out of bounds"))?;
let layer_in = layer.attrs.get_i32(A_IN).unwrap_or(0);
let speed = layer.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
let new_trim_in = ((new_play_start - layer_in) as f32 * speed) as i32;
layer.attrs.set(A_TRIM_IN, AttrValue::Int(new_trim_in));
self.mark_dirty();
self.rebound();
Ok(())
}
pub fn set_child_end(&mut self, layer_idx: usize, new_play_end: i32) -> anyhow::Result<()> {
let layer = self.layers.get_mut(layer_idx)
.ok_or_else(|| anyhow::anyhow!("Layer index out of bounds"))?;
let layer_end = layer.end();
let speed = layer.attrs.get_float(A_SPEED).unwrap_or(1.0).abs().max(0.001);
let new_trim_out = ((layer_end - new_play_end) as f32 * speed) as i32;
layer.attrs.set(A_TRIM_OUT, AttrValue::Int(new_trim_out));
self.mark_dirty();
self.rebound();
Ok(())
}
fn compose_internal(&self, frame_idx: i32, ctx: &ComputeContext) -> Option<Frame> {
let my_uuid = self.uuid();
let is_cycle = COMPOSE_STACK.with(|stack| {
let mut s = stack.borrow_mut();
if s.contains(&my_uuid) {
log::warn!("Cycle detected in compose: {}", my_uuid);
true
} else {
s.insert(my_uuid);
false
}
});
if is_cycle {
return Some(self.placeholder_frame());
}
let mut source_frames: Vec<(Frame, f32, BlendMode, [f32; 9])> = Vec::new();
let identity_matrix = super::compositor::IDENTITY_TRANSFORM;
let mut target_format = PixelFormat::Rgba8;
let mut all_loaded = true;
let has_solo = self.layers.iter().any(|l| l.attrs.get_bool(A_SOLO).unwrap_or(false));
let mut renderable_layers: Vec<(usize, f32)> = Vec::new();
for (idx, layer) in self.layers.iter().enumerate() {
let (play_start, play_end) = self.get_layer_work_area(layer, ctx.media);
if frame_idx < play_start || frame_idx > play_end {
continue;
}
if !layer.is_visible() {
continue;
}
if has_solo && !layer.attrs.get_bool(A_SOLO).unwrap_or(false) {
continue;
}
if !layer.attrs.get_bool("renderable").unwrap_or(true) {
continue;
}
let pos = layer.attrs.get_vec3(A_POSITION).unwrap_or([0.0, 0.0, 0.0]);
renderable_layers.push((idx, pos[2]));
}
renderable_layers.sort_by(|a, b| {
match a.1.partial_cmp(&b.1) {
Some(std::cmp::Ordering::Equal) | None => {
b.0.cmp(&a.0)
}
Some(ord) => ord,
}
});
let view_projection: Option<glam::Mat4> = self
.active_camera(frame_idx, ctx.media)
.map(|(cam, pos, rot)| {
let dim = self.dim();
let aspect = dim.0 as f32 / dim.1 as f32;
let comp_height = dim.1 as f32;
cam.view_projection_matrix(pos, rot, aspect, comp_height)
});
for (layer_idx, _z) in renderable_layers {
let layer = &self.layers[layer_idx];
let source = ctx.media.get(&layer.source_uuid());
let Some(source_node) = source else {
continue;
};
let local_frame = layer.parent_to_local(frame_idx);
let source_in = source_node.attrs().get_i32(A_IN).unwrap_or(0);
let source_out = source_node.attrs().get_i32(A_OUT).unwrap_or(0);
let source_frame = (source_in + local_frame).clamp(source_in, source_out);
if let Some(mut frame) = source_node.compute(source_frame, ctx) {
if frame.status() != FrameStatus::Loaded {
all_loaded = false;
}
if !layer.effects.is_empty() {
if let Some(fx_frame) = super::effects::apply_all(frame.clone(), &layer.effects) {
frame = fx_frame;
}
}
let pos = layer.attrs.get_vec3(A_POSITION).unwrap_or([0.0, 0.0, 0.0]);
let rot = layer.attrs.get_vec3(A_ROTATION).unwrap_or([0.0, 0.0, 0.0]);
let scl = layer.attrs.get_vec3(A_SCALE).unwrap_or([1.0, 1.0, 1.0]);
let pvt = layer.attrs.get_vec3(A_PIVOT).unwrap_or([0.0, 0.0, 0.0]);
let rot_rad = [rot[0].to_radians(), rot[1].to_radians(), rot[2].to_radians()];
let src_size = (frame.width(), frame.height());
let canvas = self.dim();
let needs_transform = !transform::is_identity(pos, rot_rad, scl, pvt)
|| view_projection.is_some()
|| src_size != canvas;
if needs_transform {
frame = transform::transform_frame_with_camera(
&frame, canvas, pos, rot_rad, scl, pvt, view_projection
);
}
let inv_matrix = if transform::is_identity(pos, rot_rad, scl, pvt) {
identity_matrix
} else {
transform::build_inverse_matrix_3x3(pos, rot_rad[2], scl, pvt, src_size)
};
let opacity = layer.opacity();
let blend = layer.blend_mode();
source_frames.push((frame, opacity, blend, inv_matrix));
target_format = match (target_format, source_frames.last().unwrap().0.pixel_format()) {
(PixelFormat::RgbaF32, _) | (_, PixelFormat::RgbaF32) => PixelFormat::RgbaF32,
(PixelFormat::RgbaF16, _) | (_, PixelFormat::RgbaF16) => PixelFormat::RgbaF16,
_ => PixelFormat::Rgba8,
};
}
}
let dim = self.get_first_size().unwrap_or_else(|| self.dim());
for (frame, _, _, _) in source_frames.iter_mut() {
*frame = promote_frame(frame, target_format);
}
let base = create_base_frame(dim, target_format);
source_frames.insert(0, (base, 1.0, BlendMode::Normal, identity_matrix));
trace!(
"CompNode::compose {} frames, dim={}x{}, all_loaded={}",
source_frames.len(), dim.0, dim.1, all_loaded
);
let result = THREAD_COMPOSITOR.with(|comp| {
comp.borrow_mut().blend_with_dim(source_frames, dim)
});
COMPOSE_STACK.with(|stack| {
stack.borrow_mut().remove(&my_uuid);
});
result.inspect(|frame| {
if !all_loaded {
let _ = frame.set_status(FrameStatus::Composing);
}
})
}
}
impl Node for CompNode {
fn uuid(&self) -> Uuid {
self.attrs.get_uuid(A_UUID).unwrap_or_else(Uuid::nil)
}
fn name(&self) -> &str {
self.attrs.get_str(A_NAME).unwrap_or("Untitled")
}
fn node_type(&self) -> &'static str {
"Comp"
}
fn attrs(&self) -> &Attrs {
&self.attrs
}
fn attrs_mut(&mut self) -> &mut Attrs {
&mut self.attrs
}
fn inputs(&self) -> Vec<Uuid> {
self.layers.iter().map(|l| l.source_uuid()).collect()
}
fn compute(&self, frame_idx: i32, ctx: &ComputeContext) -> Option<Frame> {
let (work_start, work_end) = self.work_area();
if frame_idx < work_start || frame_idx > work_end {
return None;
}
let is_dirty = self.is_dirty(Some(ctx));
let cached_frame = ctx.cache.get(self.uuid(), frame_idx);
let cache_is_loading = cached_frame.as_ref()
.map(|f| f.status() != FrameStatus::Loaded)
.unwrap_or(false);
let needs_recompute = is_dirty || cached_frame.is_none() || cache_is_loading;
if is_dirty {
trace!(
"compute() dirty: comp={}, frame={}, dirty={}, cache_loading={}",
self.name(), frame_idx, is_dirty, cache_is_loading
);
}
if !needs_recompute
&& let Some(frame) = cached_frame {
return Some(frame);
}
let composed = self.compose_internal(frame_idx, ctx)?;
ctx.cache.insert(self.uuid(), frame_idx, composed.clone());
self.attrs.clear_dirty();
for layer in &self.layers {
layer.attrs.clear_dirty();
}
Some(composed)
}
fn is_dirty(&self, ctx: Option<&ComputeContext>) -> bool {
let self_dirty = self.attrs.is_dirty() || self.layers.iter().any(|l| l.attrs.is_dirty());
if self_dirty {
return true;
}
if let Some(ctx) = ctx {
for layer in &self.layers {
if let Some(source) = ctx.media.get(&layer.source_uuid()) {
if source.is_dirty(Some(ctx)) {
return true;
}
}
}
}
false
}
fn mark_dirty(&self) {
self.attrs.mark_dirty()
}
fn clear_dirty(&self) {
self.attrs.clear_dirty();
for layer in &self.layers {
layer.attrs.clear_dirty();
}
}
fn play_range(&self, use_work_area: bool) -> (i32, i32) {
CompNode::play_range(self, use_work_area)
}
fn bounds(&self, use_trim: bool, selection_only: bool, media: &std::collections::HashMap<Uuid, std::sync::Arc<super::node_kind::NodeKind>>) -> (i32, i32) {
CompNode::bounds(self, use_trim, selection_only, media)
}
fn preload(&self, center: i32, radius: i32, ctx: &ComputeContext) {
use super::frame::FrameStatus;
if self.layers.is_empty() {
return;
}
let Some(workers) = ctx.workers else {
return;
};
let (play_start, play_end) = self.work_area();
if play_end < play_start {
return;
}
trace!(
"CompNode::preload: comp={}, center={}, work_area=[{}..{}], layers={}",
self.name(), center, play_start, play_end, self.layers.len()
);
let Some(media_arc) = &ctx.media_arc else {
log::warn!("preload: no media_arc in context");
return;
};
let Some(cache_arc) = &ctx.cache_arc else {
log::warn!("preload: no cache_arc in context");
return;
};
let uuid = self.uuid();
let enqueue_compute = |frame_idx: i32| {
let cache = std::sync::Arc::clone(cache_arc);
let media = std::sync::Arc::clone(media_arc);
let epoch = ctx.epoch;
workers.execute_with_epoch(epoch, Box::new(move || {
if let Some(status) = cache.get_status(uuid, frame_idx)
&& matches!(status, FrameStatus::Loaded | FrameStatus::Loading) {
return;
}
let media_snapshot: std::collections::HashMap<uuid::Uuid, std::sync::Arc<super::node_kind::NodeKind>> = {
let guard = media.read().expect("media lock");
guard.clone() };
let Some(node_arc) = media_snapshot.get(&uuid) else { return; };
let Some(comp) = node_arc.as_comp() else { return; };
let compute_ctx = ComputeContext {
cache: cache.as_ref(),
cache_arc: None, media: &media_snapshot,
media_arc: None,
workers: None,
epoch,
};
comp.compute(frame_idx, &compute_ctx);
}));
};
let max_offset = radius.min(play_end - play_start);
for offset in 0..=max_offset {
if center >= offset {
let idx = center - offset;
if idx >= play_start && idx <= play_end {
enqueue_compute(idx);
}
}
if offset > 0 {
let idx = center + offset;
if idx >= play_start && idx <= play_end {
enqueue_compute(idx);
}
}
}
}
}
impl CompNode {
pub fn set_event_emitter(&mut self, _emitter: crate::core::event_bus::CompEventEmitter) {
}
pub fn emit_attrs_changed(&self) {
self.mark_dirty();
}
pub fn signal_preload(
&self,
workers: &crate::core::workers::Workers,
project: &crate::entities::Project,
radius: i32,
) {
use super::node::ComputeContext;
if self.layers.is_empty() {
return;
}
let global_cache = match &project.global_cache {
Some(cache) => cache,
None => return,
};
let epoch = project.cache_manager()
.map(|m| m.current_epoch())
.unwrap_or(0);
let center = self.frame();
let (play_start, play_end) = self.work_area();
if play_end < play_start {
return;
}
trace!(
"signal_preload: comp={}, center={}, work_area=[{}..{}], layers={}",
self.name(), center, play_start, play_end, self.layers.len()
);
let media = project.media.read().expect("media lock");
let ctx = ComputeContext {
cache: global_cache.as_ref(),
cache_arc: Some(std::sync::Arc::clone(global_cache) as std::sync::Arc<dyn super::traits::FrameCache + Send + Sync>),
media: &media,
media_arc: Some(std::sync::Arc::clone(&project.media)),
workers: Some(workers),
epoch,
};
self.preload(center, radius, &ctx);
}
}
fn promote_frame(frame: &Frame, target: PixelFormat) -> Frame {
match (frame.pixel_format(), target) {
(PixelFormat::Rgba8, PixelFormat::Rgba8)
| (PixelFormat::RgbaF16, PixelFormat::RgbaF16)
| (PixelFormat::RgbaF32, PixelFormat::RgbaF32) => frame.clone(),
(PixelFormat::Rgba8, PixelFormat::RgbaF16) => {
if let PixelBuffer::U8(buf) = &*frame.buffer() {
let out: Vec<f16> = buf.iter()
.map(|&b| f16::from_f32(b as f32 / 255.0))
.collect();
Frame::from_f16_buffer(out, frame.width(), frame.height())
} else {
frame.clone()
}
}
(PixelFormat::Rgba8, PixelFormat::RgbaF32) => {
if let PixelBuffer::U8(buf) = &*frame.buffer() {
let out: Vec<f32> = buf.iter()
.map(|&b| b as f32 / 255.0)
.collect();
Frame::from_f32_buffer(out, frame.width(), frame.height())
} else {
frame.clone()
}
}
(PixelFormat::RgbaF16, PixelFormat::RgbaF32) => {
if let PixelBuffer::F16(buf) = &*frame.buffer() {
let out: Vec<f32> = buf.iter().map(|f| f.to_f32()).collect();
Frame::from_f32_buffer(out, frame.width(), frame.height())
} else {
frame.clone()
}
}
_ => frame.clone(),
}
}
fn create_base_frame(dim: (usize, usize), format: PixelFormat) -> Frame {
match format {
PixelFormat::RgbaF32 => {
let mut buf = vec![0.0f32; dim.0 * dim.1 * 4];
for px in buf.chunks_exact_mut(4) {
px[3] = 1.0;
}
Frame::from_f32_buffer(buf, dim.0, dim.1)
}
PixelFormat::RgbaF16 => {
let mut buf = vec![f16::from_f32(0.0); dim.0 * dim.1 * 4];
for px in buf.chunks_exact_mut(4) {
px[3] = f16::from_f32(1.0);
}
Frame::from_f16_buffer(buf, dim.0, dim.1)
}
PixelFormat::Rgba8 => {
let mut buf = vec![0u8; dim.0 * dim.1 * 4];
for px in buf.chunks_exact_mut(4) {
px[3] = 255;
}
Frame::from_buffer(PixelBuffer::U8(buf), PixelFormat::Rgba8, dim.0, dim.1)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_comp_node_creation() {
let node = CompNode::new("Test Comp", 0, 100, 24.0);
assert_eq!(node.name(), "Test Comp");
assert_eq!(node._in(), 0);
assert_eq!(node._out(), 100);
assert_eq!(node.fps(), 24.0);
assert!(node.layers.is_empty());
}
#[test]
fn test_layer_creation() {
let source_uuid = Uuid::new_v4();
let layer = Layer::new(source_uuid, "Layer 1", 10, 50, (1920, 1080));
assert_eq!(layer.source_uuid(), source_uuid);
assert_eq!(layer.start(), 10);
assert_eq!(layer.end(), 59); }
#[test]
fn test_add_remove_layer() {
let mut node = CompNode::new("Test", 0, 100, 24.0);
let source_uuid = Uuid::new_v4();
let layer = Layer::new(source_uuid, "Layer 1", 0, 50, (1920, 1080));
let layer_uuid = layer.uuid();
node.add_layer(layer, None);
assert_eq!(node.layers.len(), 1);
let removed = node.remove_layer(layer_uuid);
assert!(removed.is_some());
assert!(node.layers.is_empty());
}
#[test]
fn test_node_trait() {
let node = CompNode::new("Test", 0, 100, 24.0);
assert_eq!(node.node_type(), "Comp");
assert!(node.inputs().is_empty());
}
}