// 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 . //! oakrender 桥(single-lib unification):纹理/帧值类型与渲染调用面。 //! //! oakrender 的 C ABI 已删除(单库化):纹理是 //! [`oakrender::texture::Texture`](value enum,无句柄),CPU 帧是 //! [`oakrender::texture::Frame`]。本 crate 的 render 驱动与 GL suite //! 直接持值类型: //! //! - [`Texture`] = [`oakrender::texture::Texture`](值别名; //! clone 即引用语义,drop 自动释放后端 token——原 `texture_free`/ //! `frame_free` 调用面随值模型删除); //! - [`Frame`] = [`oakrender::texture::Frame`](值别名); //! - [`Renderer`] = `Arc` //! (渲染器即 oakrender 后端上下文,facade 经 //! [`oakrender::backend::GpuContext::create`] 创建); //! - [`VideoParams`] 直接别名 oakrender 的 //! [`oakrender::frame::VideoParamsPod`](同布局 POD); //! - 像素格式常量直接别名 [`oakcore_rs::PixelFormat`]。 //! //! 保留桩(GPU 相关、wgpu 模型无直接 Rust 等价物): //! [`texture_id`]——wgpu 后端没有 OpenGL 纹理名(旧 C ABI 的 //! `oakrender_texture_id` 语义是 GL 命名空间),恒 0;GL suite 的 //! `OpenGLTextureIndex` 属性与 render 驱动的 use_opengl 决策据此 //! 回退 CPU 路径。 /// `oakrender_video_params` POD — single-lib unification: aliases the /// oakrender crate's struct (identical layout; /// include/render/renderer.h:78). pub type VideoParams = oakrender::frame::VideoParamsPod; /// olive::PixelFormat::Format 的 f32 值。 pub const PIXEL_FORMAT_F32: i32 = oakcore_rs::PixelFormat::F32 as i32; /// olive::PixelFormat::Format 的 u8 值。 pub const PIXEL_FORMAT_U8: i32 = oakcore_rs::PixelFormat::U8 as i32; /// oakrender 渲染器(后端上下文;Arc 共享,GPU 纹理据此 upload/ /// download/blit——无需独立渲染器句柄)。 pub type Renderer = std::sync::Arc; /// oakrender 纹理(值型;GPU 或 CPU 包装)。 pub type Texture = oakrender::texture::Texture; /// oakrender CPU 帧(值型)。 pub type Frame = oakrender::texture::Frame; // ---- 桥调用面(值型实现;原 CHandle 桩随单库化重写)---------------------- /// 纹理是否占位(dummy:透明黑、从未上传)。 pub fn texture_is_dummy(texture: &Texture) -> bool { texture.is_dummy() } /// 纹理的 CPU 帧:GPU 纹理经后端下载,CPU 纹理克隆 /// (原 `texture_get_frame` 的 out 参数形态改为值返回;错误即旧 /// "纹理无 CPU 帧" 失败路径)。 pub fn texture_get_frame(texture: &Texture) -> crate::error::Result { texture.to_frame().map_err(|e| { crate::error::Error::Failed(format!("纹理 readback 失败:{e}")) }) } /// 纹理的视频参数(尺寸 + 像素格式;原 `texture_get_params` 的 /// out 参数形态改为值返回)。 pub fn texture_get_params(texture: &Texture) -> VideoParams { let (w, h) = texture.size(); let mut p = VideoParams::default(); p.width = w; p.height = h; p.format = texture.format() as i32; p } /// 从视频参数与像素数据构造 CPU 纹理(原 `texture_create` 的 /// renderer+参数+像素形态改为值形态):按 `linesize` 行跨度做行优先 /// 拷贝(0 = 紧凑行),F32/RGBA 4 通道为管线常规。GPU 上传由后端 /// 延迟(wrapped frame 语义,`Texture::wrap_frame`)。 pub fn texture_create( params: &VideoParams, pixels: &[u8], linesize: i32, ) -> crate::error::Result { use crate::error::Error; let mut frame = Frame::new(); frame.set_video_params(*params); let w = frame.width.max(0) as usize; let h = frame.height.max(0) as usize; if w == 0 || h == 0 { return Err(Error::Invalid); } let bpc = frame.bytes_per_channel(); if bpc == 0 { return Err(Error::Invalid); } // 行跨度推导通道数(RGB 3 通道等非常规分量)。 if linesize > 0 && w > 0 && (linesize as usize) % (w * bpc) == 0 { frame.channels = (linesize as usize / (w * bpc)) as i32; } let tight = frame.linesize_bytes(); let row = if linesize > 0 { linesize as usize } else { tight }; if pixels.len() < row.saturating_mul(h) { return Err(Error::Invalid); } frame.data = vec![0u8; tight * h]; for y in 0..h { let s = y * row; let d = y * tight; frame.data[d..d + tight].copy_from_slice(&pixels[s..s + tight]); } Ok(Texture::wrap_frame(frame)) } /// STUB: oakrender C ABI deleted (single-lib),且 wgpu 后端没有 /// OpenGL 纹理名——旧 `oakrender_texture_id` 的 GL 命名空间语义无 /// Rust 等价物。恒 0(GL suite 的 `OpenGLTextureIndex` 属性与 /// render 驱动的 use_opengl 决策据此回退 CPU 路径;GPU 上传若落地 /// 走 `oakrender::backend::GpuContextLike::upload` 的 wgpu token, /// 不暴露 GL id)。真实化的评估与方案见 [`crate::gl_bridge`] /// (阶段 6a spike:方案 A 不可行,方案 B 暂缓)。 pub fn texture_id(_texture: &Texture) -> i32 { 0 } /// 渲染器是否为 OpenGL 后端([`oakrender::backend::BackendKind::Gl`]; /// 原 `renderer_is_open_gl` 的句柄形态改为后端上下文 kind 查询)。 pub fn renderer_is_open_gl(renderer: &Renderer) -> bool { renderer.kind() == oakrender::backend::BackendKind::Gl } #[cfg(test)] mod tests { use super::*; /// 测试渲染器:最小 GpuContextLike 假实现(无 GPU 适配器需求)。 struct FakeGpu; impl oakrender::backend::GpuContextLike for FakeGpu { fn kind(&self) -> oakrender::backend::BackendKind { oakrender::backend::BackendKind::Cpu } fn destroy_texture(&self, _token: u64) {} fn upload(&self, _token: u64, _frame: &Frame) -> oakrender::error::Result<()> { Ok(()) } fn download(&self, _token: u64) -> oakrender::error::Result { Ok(Frame::new()) } fn blit( &self, _src: u64, _dst: u64, _processor: Option<&oakrender::color::ColorProcessor>, ) -> oakrender::error::Result<()> { Ok(()) } } /// 纹理创建:参数 + 行跨度感知拷贝往返(含 RGB 3 通道推导)。 #[test] fn texture_create_linesize_aware() { let mut params = VideoParams::default(); params.width = 3; params.height = 2; params.format = PIXEL_FORMAT_F32; // 紧凑 3×2 RGBA F32:3*4*4 = 48 B/行。 let tight = 3 * 4 * 4; let mut pixels = vec![0u8; tight * 2]; for (i, b) in pixels.iter_mut().enumerate() { *b = (i % 251) as u8; } let tex = texture_create(¶ms, &pixels, 0).expect("紧凑行应可创建"); assert!(!texture_is_dummy(&tex)); let frame = texture_get_frame(&tex).expect("CPU 帧应可读"); assert_eq!(frame.width, 3); assert_eq!(frame.height, 2); assert_eq!(frame.channels, 4); assert_eq!(frame.linesize_bytes(), tight); assert_eq!(frame.data, pixels); // 行跨度变体(每行 4 字节填充)。 let row = tight + 4; let mut padded = vec![0u8; row * 2]; for y in 0..2 { padded[y * row..y * row + tight].copy_from_slice(&pixels[y * tight..(y + 1) * tight]); } let tex = texture_create(¶ms, &padded, row as i32).expect("带填充行应可创建"); let frame = texture_get_frame(&tex).unwrap(); assert_eq!(frame.data, pixels, "填充行应剥离为紧凑存储"); // 像素不足 → Invalid。 assert!(texture_create(¶ms, &padded[..row * 2 - 1], row as i32).is_err()); // 无效尺寸 → Invalid。 let mut bad = params; bad.width = 0; assert!(texture_create(&bad, &pixels, 0).is_err()); } /// 纹理参数查询与 dummy 语义。 #[test] fn texture_params_and_dummy() { let dummy = Texture::dummy(); assert!(texture_is_dummy(&dummy)); let p = texture_get_params(&dummy); assert_eq!((p.width, p.height), (0, 0)); let mut params = VideoParams::default(); params.width = 8; params.height = 4; params.format = PIXEL_FORMAT_F32; let tex = texture_create(¶ms, &[0u8; 8 * 4 * 4 * 4], 0).unwrap(); let p = texture_get_params(&tex); assert_eq!((p.width, p.height), (8, 4)); assert_eq!(p.format, PIXEL_FORMAT_F32); } /// 渲染器 kind 查询与 GL 判断;texture_id 桩恒 0(无 GL 命名空间)。 #[test] fn renderer_kind_and_gl_id_stub() { let r: Renderer = std::sync::Arc::new(FakeGpu); assert!(!renderer_is_open_gl(&r)); let dummy = Texture::dummy(); assert_eq!(texture_id(&dummy), 0, "wgpu 无 GL 纹理名:桩恒 0"); } }