- Extract libolive-rendercore static library to minimize backend link boundary. - Add DynamicRenderer adapter with C ABI (oakgl/oakvulkan shared libs). - Make OAK_ENABLE_DYNAMIC_RENDER_BACKEND default ON with OpenGL fallback. - Implement VulkanRenderer prototype (textures, shaders, UBO blit, readback). - Add backend-neutral viewer readback path (offscreen -> QImage -> QPainter). - Refactor PluginRenderer to be renderer-agnostic; OFX plugins fall back to CPU path on non-OpenGL backends while preserving OpenGL render path. - Add Renderer::AttachOutputTexture/DetachOutputTexture and C ABI forwards. - Update docs/zh/render-backend-dynamic-plan.md for Phase 3/4/5.
141 lines
11 KiB
Markdown
141 lines
11 KiB
Markdown
# 动态渲染后端拆分计划
|
||
|
||
## 目标
|
||
|
||
将当前强绑定 OpenGL 的渲染实现拆成可动态加载的后端库,使主程序只依赖一个轻量适配器:
|
||
|
||
- OpenGL 后端封装到私有动态库。
|
||
- Vulkan 后端封装到独立动态库。
|
||
- 后端库内部继续使用 C++ 实现。
|
||
- 后端库对外只导出 C ABI。
|
||
- C ABI 使用不透明 handle 表示 C++ 对象。
|
||
- 每个 C 函数对应一个后端类成员函数。
|
||
- 构造函数导出为特殊 create 函数,析构函数导出为特殊 destroy 函数。
|
||
- 主程序适配器构造时按配置显式加载后端库并调用 create/init,析构时调用 destroy 并卸载库。
|
||
|
||
## 命名约束
|
||
|
||
用户期望后端名为 `libgl.so` 和 `libvulkan.so`。Linux 系统上 `libGL.so`/`libgl.so` 容易和系统 OpenGL loader 混淆,因此工程实现应优先使用私有库名或私有目录,例如:
|
||
|
||
- `liboakgl.so`
|
||
- `liboakvulkan.so`
|
||
- 或 `render_backends/libgl.so`、`render_backends/libvulkan.so`
|
||
|
||
适配器只从 Oak 私有后端目录查找,避免加载到系统图形库。
|
||
|
||
## 阶段 1:OpenGL 动态后端骨架
|
||
|
||
- 新增稳定 C ABI 头:`app/render/backend/renderbackend_c.h`。
|
||
- 新增 `DynamicRenderer` 适配器,继承现有 `Renderer`,内部用 `QLibrary` 加载后端。
|
||
- 将现有 `OpenGLRenderer` 包装成 OpenGL 后端导出函数。
|
||
- `RenderManager` 按 `GraphicsBackend` 选择加载 OpenGL 或 Vulkan 后端。
|
||
- 在 Vulkan 后端未实现前,请求 Vulkan 时加载占位后端或回退 OpenGL,并记录明确 warning。
|
||
|
||
## 阶段 2:两层适配器 ABI
|
||
|
||
本计划不是把 OpenGL 代码用 C 重写。动态库一侧继续保留现有 C++ `OpenGLRenderer`/未来 `VulkanRenderer` 实现,只在导出边界增加一层 C wrapper;主程序和渲染进程一侧再用 `DynamicRenderer` 把 C 函数封回 C++ `Renderer` 接口。
|
||
|
||
第一阶段 C ABI 可以用 `void *` 承载现有 C++ 对象指针,例如 `QVariant`、`VideoParams`、`ShaderCode`、`Texture`、`AcceleratedJob`。C 函数内部只做类型转换并调用对应 C++ 成员函数。这样两侧代码都不用大改,但有一个前提:后端库和主程序必须用同一套头文件、编译器 ABI 和 Qt/FFmpeg/OpenFX 依赖构建。
|
||
|
||
长期要把 ABI 稳定下来时,再逐步引入更明确的 C 结构,避免跨库暴露 Qt/C++ 类型:
|
||
|
||
- texture handle:`OakBackendTextureHandle`
|
||
- shader handle:`OakBackendShaderHandle`
|
||
- video params C struct:宽、高、depth、pixel format、channel count、linesize
|
||
- shader code C struct:vertex/fragment 字符串
|
||
- blit job C struct:输入 texture handle、uniform 数组、输出 texture handle
|
||
- readback/upload 使用裸指针和 stride
|
||
|
||
这一步是 ABI 稳定化,不是把后端内部实现改成 C。
|
||
|
||
## 阶段 2.5:最小化 OpenGL/Vulkan 后端链接边界(已完成)
|
||
|
||
此前 `oakgl`/`oakvulkan` 通过 `$<TARGET_OBJECTS:libolive-editor>` 把整个 editor 对象库链进动态库,导致后端库包含项目、节点、任务、cache、UI 等大量 editor 代码和全局状态。
|
||
|
||
本次已完成链接边界收敛:
|
||
|
||
- 新增静态库 `libolive-rendercore`,仅包含渲染核心代码:
|
||
- 渲染器基类与数据类型:`Renderer`、`Texture`、`VideoParams`、`ShaderCode`、`AcceleratedJob`、`ShaderJob`。
|
||
- 动态适配器:`DynamicRenderer`、`renderbackend_c.h`。
|
||
- 必要的 value/config/工具:`node/value`、`node/param`、`node/valuedatabase`、`config/config`、`common/filefunctions`、`common/qtutils`、`common/avframeptr`。
|
||
- `oakgl`/`oakvulkan` 现在只链接 `libolive-rendercore`,不再链接完整 `libolive-editor`。
|
||
- `liboakgl.so` / `liboakvulkan.so` 体积从约 21 MB 降至约 600 KB。
|
||
- 为隔离依赖做的头文件清理:
|
||
- `renderer.h` 移除 `node/node.h`、`render/colorprocessor.h`、`render/job/colortransformjob.h`、`job/pluginjob.h`,改为前向声明。
|
||
- `videoparams.h` 移除 `ofxImageEffect.h`,OFX 字符串 setter 实现下移到 `videoparams.cpp`。
|
||
- `texture.h` 用新增的 `common/avframeptr.h` 替代 `common/ffmpegutils.h`,避免后端拉入大量 FFmpeg 工具代码。
|
||
- `renderer.cpp` 的颜色管理(`GetColorContext` / `BlitColorManaged`)和隔行(`InterlaceTexture`)实现分别拆到 `render/colormanagement.cpp` 和 `render/interlacetexture.cpp`,这两个文件仍由 editor/worker 链接,但不进入后端库。
|
||
- 修复了拆分过程中暴露的 `StyleManager::kDefaultStyle` 跨库符号问题:改为 header 内 `inline static` 定义,使 `config.cpp` 在后端库中自包含。
|
||
|
||
剩余优化空间:
|
||
- 长远可将 `libolive-editor` 也改为依赖 `libolive-rendercore`,彻底消除渲染核心代码在主程序与后端库之间的重复编译/重复链接。当前阶段先保证后端边界干净、主程序保持兼容。
|
||
|
||
## 阶段 3:Vulkan 后端
|
||
|
||
- 新增 Vulkan 后端库 `liboakvulkan.so`。
|
||
- 新增 `VulkanRenderer` 类,继承 `Renderer`,使用原生 Vulkan API 实现 offscreen 渲染管线。
|
||
- CMake 集成:根目录查找 `Vulkan` 和 `shaderc`(可选);`oakvulkan` 目标链接 `Vulkan::Vulkan` 与 `shaderc_shared`。
|
||
- 实现 Vulkan instance/device/queue/command pool 管理。
|
||
- 实现 offscreen image/texture 管理(`CreateNativeTexture` / `DestroyNativeTexture`),支持 2D/3D、多种 pixel format(U8/U16/F16/F32 × 1/2/3/4 channel)。
|
||
- 实现 staging buffer 上传/下载(`UploadToTexture` / `DownloadFromTexture`)。
|
||
- 实现 `ClearDestination`(`vkCmdClearColorImage`)。
|
||
- 实现 `Flush`(`vkDeviceWaitIdle`)。
|
||
- 实现 GLSL → SPIR-V 运行时编译(通过 `shaderc`),支持自动 uniform binding。
|
||
- 实现基础 graphics pipeline 用于 `Blit`(全屏 quad、顶点缓冲、固定 render pass、combined image sampler descriptor set)。
|
||
- 提供 `GetPixelFromTexture`(基于 `DownloadFromTexture` 的简化实现)。
|
||
- `oak_renderer_is_available` 现在会在首次检查时尝试 `Init()`,成功后报告 Vulkan 可用。
|
||
- 测试更新:
|
||
- `LoadsExperimentalVulkanBackendWhenAvailable`:验证 Vulkan 后端可加载、初始化、报告能力位。
|
||
- `FallsBackWhenExperimentalVulkanUnavailable`:在 Vulkan 不可用的系统上验证回退 OpenGL;在 Vulkan 可用的系统上自动 SKIP。
|
||
- **已修复的限制**:
|
||
- `Blit` 中的 uniform/push constant 传递:已实现完整的 UBO 路径。`CreateNativeShader` 编译前自动将 GLSL 独立 uniform 转换为 `layout(set=0, binding=0) uniform UniformBuffer` 块;`Blit` 遍历 `ShaderJob` values 按 std140 布局填充 UBO 数据并绑定到 descriptor set。
|
||
- `GetPixelFromTexture`:已优化为仅下载 1×1 像素区域。
|
||
- RenderPass 格式缓存:已删除固定 `R32G32B32A32_SFLOAT` render pass,改为按 `VkFormat` 缓存;`CreatePipelineForShader` 按 (shader, render_pass_format) 缓存 pipeline。
|
||
- **已知限制 / 待完善**:
|
||
- 链接边界已最小化,`liboakvulkan.so` 现在只依赖 `libolive-rendercore`。
|
||
- 尚未在 proxy、thumbnail/cache 等完整渲染路径上验证 Vulkan 输出一致性。
|
||
|
||
## 阶段 4:Viewer 双后端(默认路径已切换)
|
||
|
||
- 当前 viewer display 基于 OpenGL widget 和 GL texture id。
|
||
- 默认构建下 Viewer 的 managed display 现在使用 `DynamicRenderer` 创建 renderer,并把现有 `QOpenGLContext` 传入动态后端;若动态后端加载失败则回退到 `OpenGLRenderer`。
|
||
- `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 默认改为 `ON`,保留 `OFF` 作为应急开关。
|
||
- 新增 backend-neutral viewer path 骨架:
|
||
- `ManagedDisplayWidget` 支持非 OpenGL inner widget(普通 `QWidget`),通过 `Renderer::IsOpenGL()` 判断。
|
||
- `RenderManager` 不再在 `requested_backend_ == kVulkan` 时强制 fallback。
|
||
- `ViewerDisplayWidget` 已移除 `glIsTexture()` 的直接 OpenGL 依赖,改为通用的跨 renderer texture 拷贝。
|
||
- `ScopeBase` 在 backend-neutral 时安全跳过(TODO:完整 scope display 路径)。
|
||
- OpenGL 使用现有 `QOpenGLWidget/QOpenGLWindow`。
|
||
- Vulkan viewer 完整 readback display 路径(offscreen texture → download → QImage → QPainter)已实现:
|
||
- 新增 `ManagedDisplayWidgetBackendNeutral`,在普通 `QWidget` 的 `paintEvent` 中转发到 `ManagedDisplayWidget::OnPaint`。
|
||
- `ViewerDisplayWidget::OnPaint` 在 backend-neutral 模式下改用 `QPainter` 填充背景,将颜色管理后的画面渲染到 U8 RGBA offscreen texture,再 `Download` 到 CPU buffer,最后用 `QImage::Format_RGBA8888_Premultiplied` + `setDevicePixelRatio` 绘制到 inner widget。
|
||
- OpenGL 路径保持原有 `BlitColorManaged` 直接到 widget 不变。
|
||
- Viewer 只消费后端 texture handle 或 readback frame,不直接假设 GL texture id。
|
||
|
||
## 阶段 5:OpenFX 处理边界(已完成)
|
||
|
||
- OpenFX 插件 OpenGL 渲染路径保留 OpenGL 依赖,不强行改写。
|
||
- `PluginRenderer` 不再继承 `OpenGLRenderer`,改为持有通用的 `Renderer *`:
|
||
- OpenGL 渲染路径仅在 `renderer_->IsOpenGL()` 为 true 时启用,并正确调用 `OlivePluginInstance::setOpenGLEnabled(use_opengl)`。
|
||
- 非 OpenGL 渲染器(Vulkan、DynamicRenderer 加载的任意后端)自动回退到 CPU readback/upload 路径,不再因缺少 OpenGL context 而直接跳过插件渲染。
|
||
- 将 OFX 输出纹理绑定/解绑抽象为 `Renderer::AttachOutputTexture` / `DetachOutputTexture`:
|
||
- `OpenGLRenderer` 实现为 `AttachTextureAsDestination` / `DetachTextureAsDestination`。
|
||
- C ABI 新增 `oak_renderer_attach_output_texture` / `oak_renderer_detach_output_texture`。
|
||
- `DynamicRenderer` 通过 C ABI 转发,使动态 OpenGL 后端也能支持 OFX OpenGL 渲染。
|
||
- `VulkanRenderer` 默认 no-op,Vulkan 项目中的 OFX 插件回退到 CPU 路径。
|
||
- 格式转换(`ConvertFrameIfNeeded`、`ConvertTextureForParams`)、readback(`ReadbackTextureToFrame`)、upload 等辅助函数保持后端无关,通过 `Renderer` 接口调用,无需移入后端库。
|
||
- `RenderProcessor::ProcessPluginJob` 不再要求 `render_ctx_` 实现 `OpenGLContextProvider`,任何 `Renderer` 都能驱动插件渲染。
|
||
- 更新相关 gtest:`PluginRenderer` 构造函数现在需要传入 renderer 指针,测试传入 `nullptr` 验证纯 CPU 路径。
|
||
|
||
## 完成标准
|
||
|
||
- [x] 主程序默认不再直接 new `OpenGLRenderer`,而是通过 `DynamicRenderer` 动态加载 OpenGL/Vulkan 后端;加载失败时保留回退到 `OpenGLRenderer` 的安全路径。
|
||
- [x] `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 默认 `ON`,`liboakgl.so` / `liboakvulkan.so` 默认构建并安装。
|
||
- [x] OpenGL 后端库可单独构建、加载、初始化、销毁。
|
||
- [x] 用户能在配置中选择 OpenGL/Vulkan。
|
||
- [x] Vulkan 不可用时自动回退到 OpenGL,不崩溃。
|
||
- [x] 链接边界已最小化:`oakgl` / `oakvulkan` 现在只链接独立的 `libolive-rendercore`,不再拉入完整 editor 代码;库体积从约 21 MB 降至约 600 KB。
|
||
- [x] Vulkan viewer 完整 readback display 路径已实现(offscreen texture → download → QImage → QPainter)。
|
||
- [x] OpenFX 插件渲染边界已处理:`PluginRenderer` 后端无关化,非 OpenGL 渲染器自动回退 CPU 路径,动态 OpenGL 后端通过 C ABI 支持 OFX OpenGL 输出绑定。
|
||
- [ ] 手工测试计划覆盖 viewer、proxy、scope、导出等完整路径。
|