16 KiB
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
#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_frame(OliveFrame*,定义在 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_buffer(OliveSampleBuffer*,定义在 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 实现要点
// 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 改造
# 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 API(2 天)
- 实现
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。 |