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

16 KiB
Raw Blame History

liboliverender.so — 渲染引擎抽象

依赖libolivecore.so, libolivecodec.so, libolivenode.so
外部依赖Qt::Core, Qt::OpenGL, OpenColorIO, OpenGL
包含源码app/render/(不含 OpenGL 具体后端平台代码的抽象层)
当前状态:单体 OBJECT 库的一部分,直接管理 RenderThreadRenderProcessorOpenGLRenderer
改造难度(困难,与 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 包含完整的渲染逻辑(包括 RenderProcessorRenderer 抽象),但主进程中的 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_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 实现要点

// 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 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 深度依赖 NodeTraverserC API 难以表达遍历逻辑 olive_render_frame_sync 是高阶封装,内部直接使用原有的 C++ RenderProcessorC 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