Files
oak-editor/crates/oakplugin/src/render.rs
T
Mike-Solar 2db1615453 feat(oakplugin): wire OpenFX plugins into the node graph and renderer
- oaknode: dynamic node factory registration, PluginNode value model
  pushing PluginJobPayload, traverser texture passthrough for texture
  inputs, type-stamped RefBox::get_checked.
- oakrender: PluginExecutor dependency-inversion slot; eval resolves
  and executes plugin jobs, purple frame on failure.
- oakplugin: node_factory with full OFX param -> node input
  translation (15 types, color semantics heuristic, combo ordering,
  secret/ui_group/ui_page, clip inputs), plugin instance registry,
  render executor + duplicator installation, progress reporter and
  active-viewer provider injection points, U8/U16/F16 input
  conversion with NaN scrubbing, in-place output frame writeback fix.
- gl_bridge.rs documents the wgpu<->GL interop spike: Metal-first on
  macOS rules out wgpu-hal GL interop; offscreen GL context deferred.

End-to-end tests cover registration, param translation, CPU render
pixel assertions, identity passthrough and NaN fallback.
2026-08-18 17:15:13 +08:00

236 lines
8.9 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)。真实化的评估与方案见 [`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<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");
}
}