use std::time::Duration;
use ff_preview::FrameSink;
use crate::graph::RenderGraph;
#[cfg(feature = "wgpu")]
pub struct TextureHandle {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub width: u32,
pub height: u32,
}
pub struct GpuFrameSink {
graph: RenderGraph,
downstream: Box<dyn FrameSink>,
}
impl GpuFrameSink {
#[must_use]
pub fn new(graph: RenderGraph, downstream: Box<dyn FrameSink>) -> Self {
Self { graph, downstream }
}
}
impl FrameSink for GpuFrameSink {
fn push_frame(&mut self, rgba: &[u8], width: u32, height: u32, pts: Duration) {
#[cfg(feature = "wgpu")]
{
match self.graph.process_gpu(rgba, width, height) {
Ok(processed) => {
self.downstream.push_frame(&processed, width, height, pts);
return;
}
Err(e) => {
log::warn!("GpuFrameSink GPU processing failed, using CPU fallback error={e}");
}
}
}
let processed = self.graph.process_cpu(rgba, width, height);
self.downstream.push_frame(&processed, width, height, pts);
}
fn flush(&mut self) {
self.downstream.flush();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
use crate::nodes::ColorGradeNode;
struct CollectSink(Arc<Mutex<Vec<Vec<u8>>>>);
impl FrameSink for CollectSink {
fn push_frame(&mut self, rgba: &[u8], _w: u32, _h: u32, _pts: Duration) {
self.0
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(rgba.to_vec());
}
}
#[test]
fn gpu_frame_sink_cpu_path_should_forward_processed_frame() {
let graph = RenderGraph::new_cpu().push_cpu(ColorGradeNode::new(0.5, 1.0, 1.0, 0.0, 0.0));
let collected = Arc::new(Mutex::new(Vec::new()));
let downstream = Box::new(CollectSink(Arc::clone(&collected)));
let mut sink = GpuFrameSink::new(graph, downstream);
let pts = Duration::from_millis(0);
sink.push_frame(&[128u8, 128, 128, 255], 1, 1, pts);
let guard = collected
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert_eq!(guard.len(), 1, "exactly one frame must be forwarded");
assert!(
guard[0][0] > 128,
"brightness +0.5 must increase R channel; got {}",
guard[0][0]
);
}
#[test]
fn gpu_frame_sink_flush_should_propagate_to_downstream() {
struct FlushTracker(Arc<Mutex<bool>>);
impl FrameSink for FlushTracker {
fn push_frame(&mut self, _: &[u8], _: u32, _: u32, _: Duration) {}
fn flush(&mut self) {
*self
.0
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = true;
}
}
let flushed = Arc::new(Mutex::new(false));
let mut sink = GpuFrameSink::new(
RenderGraph::new_cpu(),
Box::new(FlushTracker(Arc::clone(&flushed))),
);
sink.flush();
assert!(
*flushed
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner),
"flush must propagate to downstream"
);
}
#[test]
fn gpu_frame_sink_should_be_send() {
fn assert_send<T: Send>() {}
assert_send::<GpuFrameSink>();
}
}