Files
oak-editor/docs/zh/modularization-plan/06-oliverender.md
T

407 lines
16 KiB
Markdown
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.
# liboliverender.so — 渲染引擎抽象
> **依赖**`libolivecore.so`, `libolivecodec.so`, `libolivenode.so`
> **外部依赖**Qt::Core, Qt::OpenGL, OpenColorIO, OpenGL
> **包含源码**`app/render/`(不含 OpenGL 具体后端平台代码的抽象层)
> **当前状态**:单体 OBJECT 库的一部分,直接管理 `RenderThread`、`RenderProcessor`、`OpenGLRenderer`
> **改造难度**:⭐⭐⭐⭐(困难,与 Node 耦合深)
---
## 1. 当前状态分析
`app/render/` 是渲染系统的核心,负责将节点图转换为可显示的帧/音频。当前架构:
| 组件 | 说明 |
|---|---|
| `rendermanager.h/cpp` | 渲染管理单例,管理 `RenderThread` 和缓存 |
| `renderprocessor.h/cpp` | 节点图遍历 + 渲染作业生成(继承 `NodeTraverser` |
| `renderer.h/cpp` | 渲染器抽象基类(`Renderer` |
| `opengl/openglrenderer.h/cpp` | OpenGL 渲染后端 |
| `job/*.h` | 各种渲染作业类型(ShaderJob, FootageJob, GenerateJob 等) |
| `previewautocacher.h/cpp` | 预览自动缓存 |
| `rendercache.h/cpp` | 渲染缓存框架 |
**关键设计决策**
本方案中,**实际的 GPU 渲染发生在 `olive-renderer` 子进程中**,不在主进程的 `liboliverender.so` 中。因此 `liboliverender.so` 的角色需要重新定位:
- **在主进程中**`liboliverender.so` 提供轻量的 **渲染客户端** 功能:节点图序列化、渲染参数打包、共享内存创建、子进程启动协调。
- **在子进程中**`olive-renderer` 可执行文件链接 `liboliverender.so`(或静态链接其代码),执行实际的 `RenderProcessor` + `OpenGLRenderer`
也就是说,`liboliverender.so` 既服务于主进程(IPC 客户端),也服务于子进程(渲染服务端)。但通过编译选项或子目录拆分,可以在主进程中只包含轻量客户端代码。
**简化方案**`liboliverender.so` 包含完整的渲染逻辑(包括 `RenderProcessor``Renderer` 抽象),但主进程中的 `RenderManager` 不再直接调用它,而是通过 C API 启动 `olive-renderer` 子进程。子进程自身可以静态链接或动态链接 `liboliverender.so` 来执行渲染。
---
## 2. C API 设计
### 2.1 头文件:`c_api/include/olive/render_api.h`
```c
#ifndef OLIVE_RENDER_API_H
#define OLIVE_RENDER_API_H
#ifdef __cplusplus
extern "C" {
#endif
#include "core_api.h"
#include "node_api.h"
#define OLIVE_RENDER_API_VERSION 1
#ifdef OLIVE_BUILDING_RENDER
# define OLIVE_RENDER_API __attribute__((visibility("default")))
#else
# define OLIVE_RENDER_API
#endif
/* ========== 枚举 ========== */
typedef enum {
OLIVE_RENDER_MODE_OFFLINE = 0, // 最高质量(导出)
OLIVE_RENDER_MODE_ONLINE, // 实时预览(允许降低精度)
} OliveRenderMode;
typedef enum {
OLIVE_RENDER_BACKEND_OPENGL = 0,
OLIVE_RENDER_BACKEND_DUMMY,
} OliveRenderBackend;
/* ========== 不透明类型 ========== */
typedef struct OliveRenderContext OliveRenderContext;
typedef struct OliveRenderTicket OliveRenderTicket;
typedef struct OliveRenderParams OliveRenderParams;
/* ========== 渲染参数结构体 ========== */
typedef struct {
OliveNodeGraph* node_graph;
const char* output_node_id; // 通常为 ViewerOutput 的 ID
OliveRational time;
OliveVideoParams video_params;
OliveAudioParams audio_params;
OliveRenderMode mode;
OliveRenderBackend backend;
const char* color_reference_space; // 可为 nullptr
const char* color_display_space; // 可为 nullptr
OliveSize force_size; // {0,0} 表示不强制
OlivePixelFormat force_format; // INVALID 表示不强制
} OliveRenderFrameParams;
typedef struct {
OliveNodeGraph* node_graph;
const char* output_node_id;
OliveRational start;
OliveRational duration;
OliveAudioParams audio_params;
OliveRenderMode mode;
} OliveRenderAudioParams;
/* ========== API 版本 ========== */
OLIVE_RENDER_API int olive_render_api_version(void);
/* ========== 渲染上下文(用于本地/同进程渲染,或子进程内部) ========== */
OLIVE_RENDER_API OliveRenderContext* olive_render_context_create(OliveRenderBackend backend);
OLIVE_RENDER_API void olive_render_context_destroy(OliveRenderContext* ctx);
OLIVE_RENDER_API int olive_render_context_init(OliveRenderContext* ctx);
/* ========== 同步渲染(单帧) ========== */
// 渲染视频帧,结果写入 out_frameOliveFrame*,定义在 codec_api.h
OLIVE_RENDER_API int olive_render_frame_sync(OliveRenderContext* ctx,
const OliveRenderFrameParams* params,
void** out_frame_data, // 原始像素数据,需 olive_core_free
size_t* out_frame_size,
int* out_width,
int* out_height,
OlivePixelFormat* out_format);
// 渲染音频,结果写入 out_bufferOliveSampleBuffer*,定义在 core_api.h
OLIVE_RENDER_API int olive_render_audio_sync(OliveRenderContext* ctx,
const OliveRenderAudioParams* params,
OliveSampleBuffer** out_buffer);
/* ========== 异步渲染接口(用于子进程模型中的本地队列) ========== */
OLIVE_RENDER_API OliveRenderTicket* olive_render_frame_async(OliveRenderContext* ctx,
const OliveRenderFrameParams* params);
OLIVE_RENDER_API OliveRenderTicket* olive_render_audio_async(OliveRenderContext* ctx,
const OliveRenderAudioParams* params);
OLIVE_RENDER_API int olive_render_ticket_wait(OliveRenderTicket* ticket, int timeout_ms);
OLIVE_RENDER_API int olive_render_ticket_get_result_frame(OliveRenderTicket* ticket,
void** out_frame_data,
size_t* out_frame_size,
int* out_width,
int* out_height,
OlivePixelFormat* out_format);
OLIVE_RENDER_API void olive_render_ticket_destroy(OliveRenderTicket* ticket);
OLIVE_RENDER_API void olive_render_cancel_ticket(OliveRenderTicket* ticket);
/* ========== 节点图序列化辅助(供子进程使用) ========== */
// 将节点图序列化为适合渲染子进程消费的紧凑格式
OLIVE_RENDER_API char* olive_render_serialize_graph_for_render(OliveNodeGraph* graph,
const char* output_node_id,
size_t* out_len);
#ifdef __cplusplus
}
#endif
#endif // OLIVE_RENDER_API_H
```
### 2.2 实现要点
```cpp
// c_api/src/render_api.cpp
#include "olive/render_api.h"
#include "render/rendermanager.h"
#include "render/renderprocessor.h"
#include "render/renderer.h"
#include "render/opengl/openglrenderer.h"
#include "render/job/shaderjob.h"
#include "render/job/generatejob.h"
#include "render/job/footagejob.h"
#include "render/job/colortransformjob.h"
#include "render/job/samplejob.h"
#include "codec/frame.h"
#include "node/output/viewer/viewer.h"
#include "node/project.h"
struct OliveRenderContext {
olive::Renderer* renderer = nullptr;
olive::DecoderCache* decoder_cache = nullptr;
olive::ShaderCache* shader_cache = nullptr;
};
struct OliveRenderTicket {
olive::RenderTicketPtr impl;
};
extern "C" {
OliveRenderContext* olive_render_context_create(OliveRenderBackend backend) {
try {
auto* ctx = new OliveRenderContext();
if (backend == OLIVE_RENDER_BACKEND_OPENGL) {
ctx->renderer = new olive::OpenGLRenderer();
} else {
// ctx->renderer = new olive::DummyRenderer();
}
ctx->decoder_cache = new olive::DecoderCache();
ctx->shader_cache = new olive::ShaderCache();
return ctx;
} catch (...) {
return nullptr;
}
}
void olive_render_context_destroy(OliveRenderContext* ctx) {
if (!ctx) return;
delete ctx->shader_cache;
delete ctx->decoder_cache;
if (ctx->renderer) {
ctx->renderer->Destroy();
delete ctx->renderer;
}
delete ctx;
}
int olive_render_context_init(OliveRenderContext* ctx) {
if (!ctx || !ctx->renderer) return OLIVE_ERROR_INVALID;
try {
if (!ctx->renderer->Init()) return OLIVE_ERROR_GENERIC;
ctx->renderer->PostInit();
return OLIVE_OK;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
int olive_render_frame_sync(OliveRenderContext* ctx,
const OliveRenderFrameParams* params,
void** out_frame_data,
size_t* out_frame_size,
int* out_width,
int* out_height,
OlivePixelFormat* out_format) {
if (!ctx || !params) return OLIVE_ERROR_INVALID;
try {
// 1. 找到输出节点
olive::Node* output_node = nullptr;
{
auto* cpp_graph = static_cast<olive::NodeGraph*>(params->node_graph); // 需要内部转换
// ... 查找 output_node_id 对应的节点 ...
}
// 2. 构造 RenderVideoParams
olive::RenderManager::RenderVideoParams vparams(
output_node,
ConvertToCpp(params->video_params),
ConvertToCpp(params->audio_params),
olive::Rational(params->time.num, params->time.den),
nullptr, // ColorManager,需从 graph 获取或传入
params->mode == OLIVE_RENDER_MODE_OFFLINE ? olive::RenderMode::kOffline : olive::RenderMode::kOnline
);
// 3. 创建 ticket 并执行
auto ticket = std::make_shared<olive::RenderTicket>();
ticket->Start();
olive::RenderProcessor::Process(ticket, ctx->renderer, ctx->decoder_cache, ctx->shader_cache);
// 4. 等待结果
ticket->WaitForFinished();
if (!ticket->HasResult()) return OLIVE_ERROR_GENERIC;
// 5. 提取帧数据
olive::FramePtr frame = ticket->Get().value<olive::FramePtr>();
if (!frame) return OLIVE_ERROR_GENERIC;
*out_width = frame->width();
*out_height = frame->height();
*out_format = ConvertToC(frame->format());
size_t data_size = frame->allocated_size();
void* data = malloc(data_size);
memcpy(data, frame->data(), data_size);
*out_frame_data = data;
*out_frame_size = data_size;
return OLIVE_OK;
} catch (...) {
return OLIVE_ERROR_GENERIC;
}
}
// ... 其他函数类似封装 ...
} // extern "C"
```
---
## 3. CMake 改造
```cmake
# app/render/CMakeLists.txt
set(RENDER_INTERNAL_SOURCES
rendermanager.cpp rendermanager.h
renderprocessor.cpp renderprocessor.h
renderer.cpp renderer.h
renderticket.cpp renderticket.h
rendercache.cpp rendercache.h
previewautocacher.cpp previewautocacher.h
colorprocessor.cpp colorprocessor.h
colorprocessorcache.cpp colorprocessorcache.h
diskmanager.cpp diskmanager.h
# ... job/ 目录下的所有文件
job/shaderjob.cpp job/shaderjob.h
job/generatejob.cpp job/generatejob.h
job/footagejob.cpp job/footagejob.h
job/colortransformjob.cpp job/colortransformjob.h
job/samplejob.cpp job/samplejob.h
job/cachejob.cpp job/cachejob.h
job/pluginjob.cpp job/pluginjob.h
job/acceleratedjob.cpp job/acceleratedjob.h
# ... opengl/ 目录
opengl/openglrenderer.cpp opengl/openglrenderer.h
opengl/openglshader.cpp opengl/openglshader.h
opengl/opengltexture.cpp opengl/opengltexture.h
# ... plugin/ 目录(OFX 插件专用渲染器)
plugin/pluginrenderer.cpp plugin/pluginrenderer.h
)
set(RENDER_API_SOURCES
${CMAKE_SOURCE_DIR}/c_api/src/render_api.cpp
)
add_library(oliverender SHARED
${RENDER_INTERNAL_SOURCES}
${RENDER_API_SOURCES}
)
target_compile_definitions(oliverender PRIVATE OLIVE_BUILDING_RENDER)
target_include_directories(oliverender
PRIVATE
${CMAKE_SOURCE_DIR}/app
${CMAKE_SOURCE_DIR}/third_party/openfx/include
${CMAKE_SOURCE_DIR}/third_party/openfx/HostSupport/include
${CMAKE_SOURCE_DIR}/c_api/include
PUBLIC
$<INSTALL_INTERFACE:include>
)
target_link_libraries(oliverender
PUBLIC
olivenode
olivecodec
olivecore
oliveplugin
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::OpenGL
${OCIO_LIBRARIES}
OpenGL::GL
)
set_target_properties(oliverender PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
install(TARGETS oliverender DESTINATION lib)
install(FILES ${CMAKE_SOURCE_DIR}/c_api/include/olive/render_api.h DESTINATION include/olive)
```
---
## 4. 小步快跑实施步骤
### Step 0: 分离渲染客户端与服务端代码(2 天)
- [ ] 分析 `render/` 中哪些代码主进程需要(RenderManager 的排队/协调逻辑),哪些仅子进程需要(OpenGLRenderer、RenderProcessor)。
- [ ] 创建 `app/render/client/` 子目录,放置主进程专用的轻量代码(如 `RenderProcessLauncher`)。
- [ ] 确保 `render/` 的现有代码仍然可以编译为完整的库(子进程使用)。
**验收标准**`liboliverender.so` 编译成功,包含完整的渲染逻辑。
### Step 1: 将 render/ 独立为动态库(1 天)
- [ ] 修改 `app/render/CMakeLists.txt`,将 `render/` 从主 OBJECT 库移出,构建为 `oliverender SHARED`
- [ ] 处理 `shaders/` 目录的资源文件路径问题(子进程需要知道着色器文件位置)。
**验收标准**`liboliverender.so` 编译成功。
### Step 2: 最小 C API2 天)
- [ ] 实现 `olive_render_context_create/destroy/init`
- [ ] 实现 `olive_render_frame_sync`(同步渲染单帧)。
- [ ] 此 C API 主要供 `olive-renderer` 子进程内部使用(子进程加载 `liboliverender.so` 后调用)。
**验收标准**:可以编写一个命令行测试程序,加载 `liboliverender.so`,初始化 OpenGL,渲染一帧纯色。
### Step 3: 节点图序列化辅助(1 天)
- [ ] 实现 `olive_render_serialize_graph_for_render`
- [ ] 此函数供主进程调用,将目标 `ViewerOutput` 及其上游节点序列化为紧凑 XML。
**验收标准**:给定一个包含 ViewerOutput 的图,序列化后的 XML 可以被 `ProjectSerializer` 重新加载。
### Step 4: 异步 Ticket 接口(2 天)
- [ ] 实现 `olive_render_frame_async`, `olive_render_ticket_wait`, `olive_render_ticket_get_result_frame`
- [ ] 此接口用于子进程内部的并发渲染(一个子进程内可同时渲染多帧)。
---
## 5. 风险与回退
| 风险 | 对策 |
|---|---|
| `RenderProcessor` 深度依赖 `NodeTraverser`C API 难以表达遍历逻辑 | `olive_render_frame_sync` 是高阶封装,内部直接使用原有的 C++ `RenderProcessor`,C API 调用者无需了解遍历细节。 |
| OpenGL 上下文初始化在不同平台差异大 | 在子进程中处理平台差异(子进程使用 `QOffscreenSurface` + `QOpenGLContext`)。C API 中 `backend` 参数暂时只支持 `"opengl"`。 |
| `PreviewAutoCacher` 的复杂缓存逻辑 | `PreviewAutoCacher` 保留在主进程中(或完全移除,因为"用完即弃"的渲染模型下,缓存策略由主进程重新设计)。 |
| 子进程需要访问 `app/shaders/` 下的 GLSL 文件 | 通过命令行参数 `--shader-path` 将资源路径传递给子进程。打包时确保着色器文件与可执行文件一同分发。 |
| `ColorManager` 和 OCIO 配置 | 通过 C API 参数 `color_reference_space` / `color_display_space` 传递,子进程内部重建 `ColorManager`。 |