Files
oak-editor/crates/oakplugin/src/render.rs
T
Mike-Solar ab1a2e9c7b refactor: drop internal bridge/ffi layers; exporter family lands
Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are
gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/
oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain
Rust, CHandle marshalling shrinks to the oakengine boundary, and tests
call the Rust APIs directly (pure C-ABI wrapper tests removed where
the domain layer already covers the behavior).

exporter.h family implemented: oakengine_export_render (CLI contract),
oakengine_export_render_with_params (was a stub), last_error and
progress callback; synchronous path reuses task_create_export +
start_sync. Fixes on the way: oaktask video ticket self-deadlock,
audio params dropped on the export path, codec encoder AAC slicing and
H.264 time base. Real-mp4 tests cover both entry points, progress and
the illegal-argument matrix.

Also: oakstorage session maps null project handles to None (version-
info path), configstore test double literal 3.14 -> 3.15 (clippy PI
lint), oakaudio output callback scratch buffer + env-aware P1 test,
cli media round-trip test uses a generated 16-frame clip (no more
minute-long debug runs).
2026-08-16 00:33:45 +08:00

235 lines
8.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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/>.
//! 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<dyn oakrender::backend::GpuContextLike>`
//! (渲染器即 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 命名空间),恒 0GL 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<dyn oakrender::backend::GpuContextLike>;
/// 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<Frame> {
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<Texture> {
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 等价物。恒 0GL suite 的 `OpenGLTextureIndex` 属性与
/// render 驱动的 use_opengl 决策据此回退 CPU 路径;GPU 上传若落地
/// 走 `oakrender::backend::GpuContextLike::upload` 的 wgpu token
/// 不暴露 GL id)。
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<Frame> {
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 F323*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(&params, &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(&params, &padded, row as i32).expect("带填充行应可创建");
let frame = texture_get_frame(&tex).unwrap();
assert_eq!(frame.data, pixels, "填充行应剥离为紧凑存储");
// 像素不足 → Invalid。
assert!(texture_create(&params, &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(&params, &[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");
}
}