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).
This commit is contained in:
2026-08-16 00:33:45 +08:00
parent 2248be8567
commit ab1a2e9c7b
293 changed files with 27826 additions and 90128 deletions
+28 -49
View File
@@ -34,7 +34,7 @@
//! 已把输出纹理附着为渲染器输出目标(等价 C++ 的
//! `PluginRenderer::attach_output_texture`)。clipFreeTexture 对
//! Output 只释放句柄、不删纹理(宿主还要读它)。
//! - 输入纹理经 [`crate::bridge::render`] 在渲染器上创建(CPU 帧 →
//! - 输入纹理经 [`crate::render`] 在渲染器上创建(CPU 帧 →
//! GL 上传)。纹理格式:全链路 F32 约束下,像素深度按 clip 协商
//! 结果(恒 F32);`format` 参数(kOfxImageEffectGLFormat*)若
//! 请求的分量与协商分量不符,Phase 2 不做转换 → Failed(规范要求
@@ -164,19 +164,21 @@ mod tests {
// ---- 存活纹理表 -----------------------------------------------------------
/// 存活 GL 纹理表:clipLoadTexture 产出(props 地址 → 属性集 +
/// 纹理强引用 + 是否输出 clip);clipFreeTexture 摘除即释放——对应
/// 纹理值 + 是否输出 clip);clipFreeTexture 摘除即释放——对应
/// HS 的 get/release 配对(HS: ofxhImageEffect.cpp:2336-2351)。
/// 纹理是 oakrender 值(drop 自动释放后端 token;原
/// `texture_free` 调用面随值模型删除)。
///
/// 属性集必须**装箱**(Box 稳定堆地址):纹理句柄指向它,函数返回后
/// 必须仍存活;栈上临时变量会悬垂(phase-2 实现初版的 bug)。
static LIVE_TEXTURES: std::sync::LazyLock<
Mutex<HashMap<usize, (Box<PropertySet>, crate::bridge::render::TextureHandle, bool)>>,
Mutex<HashMap<usize, (Box<PropertySet>, crate::render::Texture, bool)>>,
> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// 登记纹理(clipLoadTexture 内部;`props` 装箱后取地址为句柄基址)。
fn register(
props: Box<PropertySet>,
texture: crate::bridge::render::TextureHandle,
texture: crate::render::Texture,
is_output: bool,
) -> usize {
let addr = &*props as *const PropertySet as usize;
@@ -191,16 +193,8 @@ fn register(
/// 插件在 action 返回前 clipFreeTexture 全部句柄;遗漏的输入纹理在
/// 此释放,输出纹理保留——宿主还要读它)。
pub(crate) fn purge_leftovers() {
let leftovers: Vec<(crate::bridge::render::TextureHandle, bool)> = {
let mut live = LIVE_TEXTURES.lock().unwrap_or_else(|e| e.into_inner());
live.drain().map(|(_k, (_p, t, o))| (t, o)).collect()
};
for (texture, is_output) in leftovers {
if !is_output && !texture.is_null() {
let mut t = texture;
unsafe { crate::bridge::render::texture_free(&mut t) };
}
}
let mut live = LIVE_TEXTURES.lock().unwrap_or_else(|e| e.into_inner());
live.retain(|_, (_, _, is_output)| *is_output);
}
/// 公共入口模板:panic 兜底。
@@ -246,10 +240,12 @@ fn clip_string(clip: &ClipInstance, name: &str, default: &str) -> String {
}
/// 构造纹理属性集(ofxGPURender.h 规定的属性;输入与输出共用,
/// 只是数据来源不同)。
/// 只是数据来源不同)。`OpenGLTextureIndex` 经
/// [`crate::render::texture_id`](桩恒 0——wgpu 后端无 GL 命名空间;
/// CPU 回退下插件按规范回退,纹理内容仍可经 clipGetImage 取用)。
#[allow(clippy::too_many_arguments)]
fn make_texture_props(
texture: crate::bridge::render::TextureHandle,
texture: &crate::render::Texture,
width: f64,
height: f64,
components: crate::image::Components,
@@ -262,7 +258,7 @@ fn make_texture_props(
let props = PropertySet::new();
props.set_one(
GL_TEXTURE_INDEX,
Value::Int(unsafe { crate::bridge::render::texture_id(texture) }),
Value::Int(crate::render::texture_id(texture)),
);
props.set_one(GL_TEXTURE_TARGET, Value::Int(GL_TEXTURE_2D));
props.set_one(
@@ -342,16 +338,13 @@ unsafe extern "C" fn clip_load_texture(
if c.name == "Output" {
// Output:返回已附着的输出纹理(format 忽略;渲染目标
// 绑定由调用方契约保证——等价 C++ attach_output_texture)。
let tex = gl.output_texture;
if tex.is_null() {
return Err(status::ERR_BAD_HANDLE);
}
let tex = gl.output_texture.clone();
let (w, h) = texture_size(&tex);
if w <= 0.0 || h <= 0.0 {
return Err(status::ERR_BAD_HANDLE);
}
let props = make_texture_props(
tex,
&tex,
w,
h,
crate::image::Components::Rgba,
@@ -393,25 +386,16 @@ unsafe extern "C" fn clip_load_texture(
if w <= 0.0 || h <= 0.0 {
return Err(status::FAILED);
}
let params = crate::bridge::render::VideoParams {
let params = crate::render::VideoParams {
width: w as i32,
height: h as i32,
format: crate::bridge::render::PIXEL_FORMAT_F32,
format: crate::render::PIXEL_FORMAT_F32,
..Default::default()
};
let tex = unsafe {
crate::bridge::render::texture_create(
gl.renderer,
&params,
image.pixels().as_ptr() as *const c_void,
image.row_bytes() as i32,
)
};
if tex.is_null() {
return Err(status::ERR_MEMORY);
}
let tex = crate::render::texture_create(&params, image.pixels(), image.row_bytes() as i32)
.map_err(|_| status::ERR_MEMORY)?;
let props = make_texture_props(
tex,
&tex,
w,
h,
components,
@@ -427,10 +411,10 @@ unsafe extern "C" fn clip_load_texture(
})
}
/// 纹理尺寸(经 texture_get_params;失败回退 0,0)。
fn texture_size(tex: &crate::bridge::render::TextureHandle) -> (f64, f64) {
let mut p = crate::bridge::render::VideoParams::default();
if unsafe { crate::bridge::render::texture_get_params(*tex, &mut p) } == 0 && p.width > 0 {
/// 纹理尺寸(经 [`crate::render::texture_get_params`];失败回退 0,0)。
fn texture_size(tex: &crate::render::Texture) -> (f64, f64) {
let p = crate::render::texture_get_params(tex);
if p.width > 0 {
(p.width as f64, p.height as f64)
} else {
(0.0, 0.0)
@@ -456,8 +440,9 @@ fn clip_par(c: &ClipInstance) -> f64 {
.unwrap_or(1.0)
}
/// clipFreeTexture:释放纹理句柄(输入 clip 删除 GL 纹理;Output 只
/// 释放句柄不删纹理——宿主还要读它)。
/// clipFreeTexture:释放纹理(输入 clip 删除纹理值;Output 只释放
/// 句柄不删纹理——宿主还要读它)。纹理是值:摘除表条目即 drop
/// (原 `texture_free` 调用面随值模型删除)。
unsafe extern "C" fn clip_free_texture(texture_handle: *mut c_void) -> c_int {
caught(|| {
if texture_handle.is_null() {
@@ -469,13 +454,7 @@ unsafe extern "C" fn clip_free_texture(texture_handle: *mut c_void) -> c_int {
live.remove(&addr)
};
match entry {
Some((_props, texture, is_output)) => {
if !is_output && !texture.is_null() {
let mut t = texture;
unsafe { crate::bridge::render::texture_free(&mut t) };
}
Ok(())
}
Some((_props, _texture, _is_output)) => Ok(()),
None => Err(status::ERR_BAD_HANDLE),
}
})
+3 -2
View File
@@ -29,7 +29,7 @@
//! 语义,第 1 期账本不需要 → OK no-op,见 memory.rs 文档)。
//!
//! `// TODO(clip)`:clipGetImage 依赖 [`crate::clip::ClipInstance::fetch_image`]
//! (bridge::render 帧访问 C ABI 未冻结),代码已齐、运行时待 clip.rs。
//! (oakrender 帧访问随单库化改为本地桩,见 [`crate::render`]),代码已齐、运行时待 clip.rs。
use std::collections::HashMap;
use std::ffi::{c_char, c_double, c_int, c_void, CStr};
@@ -258,7 +258,8 @@ unsafe extern "C" fn clip_get_property_set(clip: *mut c_void, out: *mut *mut c_v
/// clipGetImage:抓取输入图像并登记到存活表,返回图像属性集 handle
/// (HS:2003-2049;`getImage` 失败 → Failed)。
///
/// `// TODO(clip)`:fetch_image 待 bridge::render 帧访问 C ABI。
/// `// TODO(clip)`:fetch_image 待 clip 迁移到
/// `oakrender::texture::Texture` 值模型(当前帧访问为本地桩)。
unsafe extern "C" fn clip_get_image(
clip: *mut c_void,
time: c_double,
+5 -9
View File
@@ -23,10 +23,8 @@
//! 到本模块的 `oak_ofx_message_impl`。
//!
//! 消息出口 = facade 注册的 `oakplugin_message_fn`
//! (include/plugin/host.h)。注意:骨架 ffi.rs 声明的 `MessageFn`
//! 与头文件不符(头文件是 `(type, message, userdata)`,骨架是
//! `(userdata, level, message)`)——做 ffi.rs 时以头文件为准修正。
//! 本模块按头文件契约建模。
//! (include/plugin/host.h)。本模块按头文件契约建模。
//! (原 C ABI 出口层已随单库化删除;注册点由 facade 直接调用。)
use std::ffi::{c_char, c_int, c_void, CStr};
@@ -37,14 +35,12 @@ use crate::suites::status;
pub(crate) type MessageHandler =
unsafe extern "C" fn(*const c_char, *const c_char, *mut c_void) -> c_int;
/// 消息出口注册表(ffi 层经 `oakplugin_host_set_message_handler`
/// 写入,suite 读;userdata 以 usize 存,避免裸指针破坏 static 的
/// Send/Sync 推导)。
/// 消息出口注册表(facade 写入,suite 读;userdata 以 usize 存,
/// 避免裸指针破坏 static 的 Send/Sync 推导)。
static HANDLER: std::sync::Mutex<(Option<MessageHandler>, usize)> =
std::sync::Mutex::new((None, 0));
/// 注册/注销消息出口(ffi 层 `oakplugin_host_set_message_handler`
/// 调用;公开:测试直接注入捕获器)。
/// 注册/注销消息出口(facade 调用;公开:测试直接注入捕获器)。
pub fn set_handler(f: Option<MessageHandler>, userdata: *mut c_void) {
let mut h = HANDLER.lock().unwrap_or_else(|e| e.into_inner());
*h = (f, userdata as usize);
+41 -11
View File
@@ -171,13 +171,14 @@ pub(crate) fn current_output() -> Option<std::sync::Arc<crate::image::Image>> {
/// 纹理(对应 C++ 里实例渲染期的 GL 状态;ofxGPURender.h
/// "OpenGL Current Context" 一节要求宿主在 Render 期间持有 GL
/// 上下文——本设计的约定是调用方(oakrender 的 PluginJob 路径)在
/// 进入 render_job 前已把渲染器上下文置为 current,本表只传递句柄)。
#[derive(Clone, Copy)]
/// 进入 render_job 前已把渲染器上下文置为 current,本表只传递
/// 渲染器引用与输出纹理值)。
#[derive(Clone)]
pub struct GlCtx {
/// 当前渲染器(oakrender 句柄)。
pub renderer: crate::bridge::render::RendererHandle,
/// 当前渲染器(oakrender 后端上下文)。
pub renderer: crate::render::Renderer,
/// 已附着的输出纹理(渲染目标;GL 模式下插件把结果画进它)。
pub output_texture: crate::bridge::render::TextureHandle,
pub output_texture: crate::render::Texture,
/// 当前 GL 纹理的实际像素深度(kOfxBitDepth* 静态串;Phase 2
/// 全链路 F32,由 render_gl 按插件 kOfxOpenGLPropPixelDepth 协商
/// 后填入——纹理句柄的 kOfxImageEffectPropPixelDepth 以它为准)。
@@ -197,7 +198,7 @@ pub fn set_gl_ctx(ctx: Option<GlCtx>) {
/// 读取 GL 渲染上下文(无上下文返回 None——clipLoadTexture 在非
/// GL 渲染期调用时按规范返回 kOfxStatErrMissingHostFeature)。
pub(crate) fn gl_ctx() -> Option<GlCtx> {
GL_CTX.with(|c| *c.borrow())
GL_CTX.with(|c| c.borrow().clone())
}
/// 宿主进程身份(fetchSuite 的 version 检查用;= OFX API 1.5)。
@@ -307,16 +308,45 @@ mod tests {
#[test]
fn gl_ctx_tls() {
assert!(gl_ctx().is_none());
let renderer = crate::handle::CHandle::null();
let tex = crate::handle::CHandle::null();
// 最小 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: &oakrender::texture::Frame,
) -> oakrender::error::Result<()> {
Ok(())
}
fn download(&self, _token: u64) -> oakrender::error::Result<oakrender::texture::Frame> {
Ok(oakrender::texture::Frame::new())
}
fn blit(
&self,
_src: u64,
_dst: u64,
_processor: Option<&oakrender::color::ColorProcessor>,
) -> oakrender::error::Result<()> {
Ok(())
}
}
let renderer: crate::render::Renderer = std::sync::Arc::new(FakeGpu);
let tex = crate::render::Texture::dummy();
set_gl_ctx(Some(GlCtx {
renderer,
output_texture: tex,
output_texture: tex.clone(),
gl_pixel_depth: "OfxBitDepthFloat",
}));
let got = gl_ctx().unwrap();
assert!(got.renderer.is_null());
assert!(got.output_texture.is_null());
assert_eq!(
got.renderer.kind(),
oakrender::backend::BackendKind::Cpu
);
assert!(got.output_texture.is_dummy());
assert_eq!(got.gl_pixel_depth, "OfxBitDepthFloat");
set_gl_ctx(None);
assert!(gl_ctx().is_none());