// 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 . //! clip 实例:clip ↔ oakrender 纹理桥。 //! //! 对应 C++ 的 `OliveClipInstance`。纹理数据经 //! [`crate::render`](oakrender 值类型:`Texture`/`Frame`)流动; //! OFX 侧只看到 [`crate::image::Image`](CPU 路径)。 //! //! `#[repr(C)]` + props 在偏移 0(句柄约定,见 [`crate::suites::tag`]; //! clip handle 即 `&props`)。 //! //! 单库化后 oakrender 的 ffi 已删除:帧访问走 //! [`oakrender::texture::Texture::to_frame`] 值路径(GPU 纹理经后端 //! 下载、CPU 纹理克隆),帧释放随值 drop 自动发生(原 //! `texture_get_frame`/`frame_free` 句柄调用面随桩删除)。 use crate::instance::{OfxRangeD, OfxRectD, RenderScale}; use crate::property::PropertySet; /// clip 实例。 #[repr(C)] pub struct ClipInstance { /// 实例级 clip 属性(当前分量/位深/像素比,协商结果写入; /// 偏移 0,句柄约定)。 pub props: PropertySet, /// clip 名。 pub name: String, /// 当前输入纹理(oakrender 值;输出 clip 为 None)。 input_texture: std::sync::Mutex>, /// 当前输出纹理(C++ `output_textures_` 的 phase 1 单槽; /// [`store_output_image`](Self::store_output_image) 的回写目标; /// 输入 clip 为 None)。 output_texture: std::sync::Mutex>, } /// 从 clip 属性读协商分量(getClipPreferences 写入)。 fn components_from_props(props: &PropertySet) -> Option { use crate::property::Value; match props.get(crate::image::K_IMAGE_EFFECT_PROP_COMPONENTS, 0)? { Value::String(s) => match s.to_string_lossy().as_ref() { "OfxImageComponentRGBA" => Some(crate::image::Components::Rgba), "OfxImageComponentRGB" => Some(crate::image::Components::Rgb), "OfxImageComponentAlpha" => Some(crate::image::Components::Alpha), _ => None, }, _ => None, } } impl ClipInstance { /// 按描述符实例化(createInstance 路径调用;公开:宿主与测试 /// 都需要构造 clip 实例)。实例 props 是描述符 props 的深拷贝 /// (HS: ClipBase 的实例构造,ofxhClip.cpp:57-70——插件在实例期 /// 读 supported components 等)。 /// /// ofxColour(M11 §4):输入 clip 的 kOfxImageClipPropColourspace /// 由宿主写为工作空间(ACEScg,ofxColour.h "Hosts should set this /// property to the colourspace of the input clip. Typically it will /// be set to the working colourspace");输出 clip 由 /// GetOutputColourspace action 后写。 pub fn from_descriptor(desc: &crate::descriptor::ClipDescriptor) -> Self { let props = desc.props.clone(); let name = desc.name.clone(); if name != "Output" { props.set_one( crate::host::PROP_CLIP_COLOURSPACE, crate::property::Value::String( std::ffi::CString::new(crate::host::WORKING_COLOURSPACE).unwrap(), ), ); } Self { props, name, input_texture: std::sync::Mutex::new(None), output_texture: std::sync::Mutex::new(None), } } /// 写协商后的像素格式(C++ `OliveClipInstance::setParams` 的 props /// 侧:setPixelDepth/setComponents,oliveclip.cpp:700-709)。 /// `format` 为 olive::PixelFormat::Format(0=u8, 2=u16, 3=f16, /// 4=f32);`channels` 为分量数。 pub fn set_video_params(&self, format: i32, channels: i32) { let depth = match format { 0 => "OfxBitDepthByte", 2 => "OfxBitDepthShort", 3 => "OfxBitDepthHalf", _ => "OfxBitDepthFloat", }; let comps = match channels { 1 => "OfxImageComponentAlpha", 3 => "OfxImageComponentRGB", _ => "OfxImageComponentRGBA", }; self.props.set_one( crate::image::K_IMAGE_EFFECT_PROP_PIXEL_DEPTH, crate::property::Value::String(std::ffi::CString::new(depth).unwrap()), ); self.props.set_one( crate::image::K_IMAGE_EFFECT_PROP_COMPONENTS, crate::property::Value::String(std::ffi::CString::new(comps).unwrap()), ); } /// 写协商 RoD(C++ `setRegionOfDefinition` 的单槽版, /// oliveclip.cpp:674-678——C++ 按 time 存 map,本驱动一帧一槽; /// 落点与 image effect suite 的 clipGetRegionOfDefinition 读取处 /// 一致)。 pub fn set_region_of_definition(&self, rod: OfxRectD, _time: f64) { use crate::property::Value; self.props.define( "OfxImageEffectPropRegionOfDefinition", vec![ Value::Double(rod.x1), Value::Double(rod.y1), Value::Double(rod.x2), Value::Double(rod.y2), ], ); } /// 挂接输入纹理(oaknode 侧 clip 输入值变化时由 param/render 桥 /// 调用)。`time` 用于多帧纹理选择。None 断开。 pub fn set_input_texture(&self, texture: Option, _time: f64) { *self.input_texture.lock().unwrap_or_else(|e| e.into_inner()) = texture; } /// 挂接输出纹理(render 驱动创建并经值传入;C++ /// `setOutputTexture` 的 phase 1 单槽版)。`time` 用于多帧纹理 /// 选择(`// [P2]`)。None 断开。 pub fn set_output_texture(&self, texture: Option, _time: f64) { *self .output_texture .lock() .unwrap_or_else(|e| e.into_inner()) = texture; } /// 抓取本 clip 在 `time` 的图像(OFX clipGetImage 的宿主侧)。 /// CPU 路径:把 oakrender 纹理 readback 成 [`crate::image::Image`] /// (像素格式按协商结果,全链路 F32)。 /// `// [P2]` GL 路径:clipLoadTexture 语义在此扩展。 /// /// 第 1 期约束:帧必须是 f32 格式(全链路 F32);`region` 只支持 /// None(整帧)——转换(u8→f32 等)与子区域随 renderer 桥落地。 pub fn fetch_image( &self, time: f64, scale: RenderScale, region: Option, ) -> crate::error::Result { use crate::render::PIXEL_FORMAT_F32; use crate::error::Error; let _ = (time, scale); if region.is_some() { return Err(Error::Failed("fetch_image 子区域第 1 期不支持".into())); } let texture = self .input_texture .lock() .unwrap_or_else(|e| e.into_inner()) .clone() .ok_or(Error::NotFound)?; // 占位纹理(dummy):视作无输入。 if texture.is_dummy() { return Err(Error::NotFound); } // 纹理 → CPU 帧(GPU 纹理后端下载;帧随 drop 释放)。 let frame = crate::render::texture_get_frame(&texture)?; let params = frame.video_params(); if params.format != PIXEL_FORMAT_F32 { return Err(Error::Failed(format!( "输入帧格式 {} 非 F32(第 1 期约束)", params.format ))); } let (w, h) = (params.width as f64, params.height as f64); // 分量按协商结果(getClipPreferences 已写入 clip.props)。 let components = components_from_props(&self.props).unwrap_or(crate::image::Components::Rgba); let mut image = crate::image::Image::allocate( crate::image::BitDepth::Float, components, OfxRectD { x1: 0.0, y1: 0.0, x2: w, y2: h, }, ); let src = frame.data(); if src.is_null() { return Err(Error::Failed("帧无数据".into())); } // 行优先拷贝(帧行跨度经 linesize 读取——真实 oakrender 帧可 // 有行填充;目标 Image 恒紧凑。M11 §4 修复:phase 1 假设紧凑 // 行,对真实 oakrender 的填充帧会写错列)。 let channels = components.channel_count(); let tight = (w as usize) * channels * 4; let row = frame.linesize_bytes(); let row = if row > 0 { row } else { tight }; let src_bytes = unsafe { std::slice::from_raw_parts(src, row * h as usize) }; let dst = image.pixels_mut(); for y in 0..h as usize { let s = y * row; let d = y * tight; dst[d..d + tight].copy_from_slice(&src_bytes[s..s + tight]); } Ok(image) } /// 把输出图像回写为 oakrender 纹理(render 完成后由 /// [`crate::instance::Instance::render`] 的调用方使用)。 /// /// 输出纹理由 oakrender 侧创建并经 [`Self::set_output_texture`] /// 挂入——本函数取该纹理的 CPU 帧(GPU 纹理经后端下载,写回后 /// 对 `Texture::Gpu` 再经 /// [`oakrender::backend::GpuContextLike::upload`] 上传),按帧 /// 参数校验 F32 与尺寸后整帧拷贝图像像素(全链路 F32;C++ /// pluginrenderer 的 `readback/wrap` 路径第 1 期以 CPU 拷贝表达, /// GL 走 [`crate::render`] 的 `// [P2]`)。未挂输出纹理 /// 或纹理为占位(dummy)→ [`crate::error::Error::NotFound`]。 /// 成功返回纹理值(克隆,随 drop 释放)。 pub fn store_output_image( &self, image: &crate::image::Image, ) -> crate::error::Result { use crate::render::{texture_get_frame, PIXEL_FORMAT_F32}; use crate::error::Error; let texture = self .output_texture .lock() .unwrap_or_else(|e| e.into_inner()) .clone() .ok_or(Error::NotFound)?; if texture.is_dummy() { return Err(Error::NotFound); } let mut frame = texture_get_frame(&texture)?; let params = frame.video_params(); if params.format != PIXEL_FORMAT_F32 { return Err(Error::Failed(format!( "输出帧格式 {} 非 F32(第 1 期约束)", params.format ))); } let (w, h) = (params.width as usize, params.height as usize); // 图像与帧必须同尺寸(全链路 F32;宽高/行宽/总长逐项校验)。 let tight = w * image.components().channel_count() * 4; if tight != image.row_bytes() || tight * h != image.pixels().len() { return Err(Error::Failed("图像尺寸与输出帧不一致".into())); } let dst = frame.data_mut(); if dst.is_null() { return Err(Error::Failed("输出帧无数据".into())); } // 行优先拷贝(目标帧行跨度经 linesize 读取——真实 oakrender // 帧可有行填充;M11 §4 修复同 fetch_image)。 let row = frame.linesize_bytes(); let row = if row > 0 { row } else { tight }; let dst_bytes = unsafe { std::slice::from_raw_parts_mut(dst, row * h) }; let pixels = image.pixels(); for y in 0..h { let d = y * row; let s = y * tight; dst_bytes[d..d + tight].copy_from_slice(&pixels[s..s + tight]); } // GPU 目标纹理:拷贝只落在下载帧上,经后端 upload 回写 // (CPU 纹理无需上传)。 if let crate::render::Texture::Gpu { token, ctx, .. } = &texture { ctx.upload(*token, &frame) .map_err(|e| Error::Failed(format!("输出纹理上传失败:{e}")))?; } Ok(texture) } /// 本 clip 的时间域(clipGetFrameRange)。 /// /// `// TODO(value-model)`:输入范围经 oakrender 帧的时间基推导 /// (time_base)——随 clip 迁移到 `oakrender::texture::Texture` /// 值模型落地。 pub fn frame_range(&self) -> crate::error::Result { let _ = OfxRangeD::default(); Err(crate::error::Error::Failed( "frame_range 待 renderer 桥".into(), )) } }