Files
oak-editor/docs/zh/render-backend-dynamic-plan.md
T
2026-07-13 10:19:30 +08:00

162 lines
15 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.
# 动态渲染后端拆分计划
## 目标
将当前强绑定 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 私有后端目录查找,避免加载到系统图形库。
## 阶段 1OpenGL 动态后端骨架
- 新增稳定 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 structvertex/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` 不再链接完整 editor 对象库;实际体积取决于构建类型、符号表和系统依赖链接方式,当前 debug 构建仍会显著大于 release/strip 后体积。
- 为隔离依赖做的头文件清理:
- `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`,彻底消除渲染核心代码在主程序与后端库之间的重复编译/重复链接。当前阶段先保证后端边界干净、主程序保持兼容。
## 阶段 3Vulkan 后端(offscreen 核心已实现,运行时依赖可用 Vulkan ICD)
- 新增 Vulkan 后端库 `liboakvulkan.so`(当系统安装了 Vulkan 头文件/库时构建;无 Vulkan 环境时 CMake 自动跳过)。
- 新增 `VulkanRenderer` 类,继承 `Renderer`,使用原生 Vulkan API 实现 offscreen 渲染管线;代码已合入,并在本机 NVIDIA Vulkan 驱动上通过了基础端到端渲染测试。
- CMake 集成:根目录查找 `Vulkan``shaderc`(可选);`oakvulkan` 目标链接 `Vulkan::Vulkan``shaderc_shared`;若 `Vulkan` 未找到则不构建该库,避免无 Vulkan 头文件时编译失败。
- 实现 Vulkan instance/device/queue/command pool 管理(代码层完成)。
- 实现 offscreen image/texture 管理(`CreateNativeTexture` / `DestroyNativeTexture`),支持 2D/3D、多种 pixel formatU8/U16/F16/F32 × 1/2/3/4 channel);3-channel 格式会探测 `COLOR_ATTACHMENT` 支持并自动回退到 4-channel 等价格式。
- 实现 staging buffer 上传/下载(`UploadToTexture` / `DownloadFromTexture`)。
- 实现 `ClearDestination``vkCmdClearColorImage`)。
- 实现 `Flush``vkDeviceWaitIdle`)。
- 实现 GLSL → SPIR-V 运行时编译(通过 `shaderc`),支持顶点/片段共享 UBO、显式 sampler binding、顶点 uniform(如 `ove_mvpmat`)和常用 varyings。
- 实现基础 graphics pipeline 用于 `Blit`(全屏 quad、顶点缓冲、按格式缓存的 render pass、combined image sampler descriptor set、persistent linear/nearest sampler、per-texture framebuffer cache)。
- 提供 `GetPixelFromTexture`(基于 `DownloadFromTexture` 的简化实现)。
- `oak_renderer_is_available` 现在会在首次检查时尝试 `Init()`,成功后报告 Vulkan 可用。
- 测试更新:
- `LoadsExperimentalVulkanBackendWhenAvailable`:验证 Vulkan 后端可加载、初始化、报告能力位。
- `FallsBackWhenExperimentalVulkanUnavailable`:在 Vulkan 不可用的系统上验证回退 OpenGL;在 Vulkan 可用的系统上自动 SKIP。
- `VulkanUploadBlitDownload`:创建 Vulkan backend,上传 U8 RGBA 纹理,经默认 pass-through shader Blit 到目标纹理,再下载并验证像素一致;该测试在当前开发环境的真实 Vulkan 驱动上通过。
- **已修复的明显问题(代码层)**:
- 初始化幂等性:`Init()` / `PostInit()` 可安全重复调用。
- `Blit` 中的 descriptor/sampler 生命周期:sampler 与 descriptor set 在 `EndOneTimeCommands` 后统一释放。
- sampler binding:从数组绑定改为显式 `layout(set=0, binding=N)`,避免跨驱动 array-of-samplers 行为不一致。
- image layout 跟踪:输入纹理在绘制前被过渡到 `SHADER_READ_ONLY_OPTIMAL`
- viewport/scissor:改为 dynamic state,避免 pipeline 缓存 key 遗漏视口尺寸。
- render pass clear`clear_destination` 为 true 时 `loadOp` 设为 `CLEAR`
- 格式支持探测:通过 `vkGetPhysicalDeviceFormatProperties` 检查 `COLOR_ATTACHMENT` 能力,3-channel 不支持时回退到 4-channel(上传/下载的 CPU 侧通道对齐仍待完善)。
- framebuffer / sampler 缓存:每张纹理延迟创建并复用 framebuffer;按插值模式复用 linear/nearest sampler。
- 单通道纹理 swizzleimage view 组件映射为 R→RGB、A=1,匹配 OpenGL 灰度行为。
- 纹理启用标志:为声明 `NAME_enabled` 的 shader 自动设置 0/1。
- **已修复 / 已实现**
- 链接边界已最小化,`liboakvulkan.so` 现在只依赖 `libolive-rendercore`
- 单通道/3-channel 格式的上传/下载 CPU 侧对齐:当 GPU 回退格式(如 3→4 channel)与请求格式不一致时,staging buffer 按实际 `VkFormat` 大小分配,并在 CPU 侧进行通道数转换(alpha 填最大值)。
- `Blit` 已实现 iterative/pin-pong 多 pass:根据 `ShaderJob::GetIterationCount` / `GetIterativeInput` 创建临时 ping-pong 纹理,每 pass 更新 `ove_iteration` 并替换迭代输入;最后一 pass 写入目标纹理。
- null-destination Blit 实现为渲染到临时 offscreen texture,保证调用不崩溃。
- 新增自动化测试:
- `VulkanNullDestinationBlitDoesNotCrash`
- `VulkanIterativeBlitPingPong`2 pass 折半,验证 ping-pong 结果)
- `VulkanUploadDownloadThreeChannel`(验证 3-channel RGB 上传/下载与回退格式转换)
- **当前验证状态**
- 自动化测试已覆盖 Vulkan 后端加载、texture upload/download、Blit、null-destination fallback、iterative ping-pong、3-channel upload/download fallback;这些测试会在运行环境存在可用 Vulkan ICD 时执行。
- 当前开发环境可找到 Vulkan loader/headers,但运行时 loader 只发现不可用的 NVIDIA ICD`vkCreateInstance``Found no drivers`;因此 Vulkan 用例会按设计 SKIP,不能作为 Vulkan 渲染通过的证据。
- Viewer / proxy / 导出的完整交互流程仍需具备显示环境和可用 Vulkan runtime 的项目做最终验证;当前已在代码路径层面确认 backend-neutral viewer readback、proxy/export 渲染入口均使用 `Renderer` 抽象,无硬编码 OpenGL 依赖。
## 阶段 4Viewer 双后端(backend-neutral 路径已落地,Vulkan 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 / backend-neutral 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。
- **状态说明**backend-neutral 代码已合并;VulkanRenderer 现在可完成单 pass BlitViewer 的 backend-neutral readback 路径在代码层面可工作,但尚未在完整 UI 播放/导出流程中验证。
## 阶段 5:OpenFX 处理边界(边界框架已完成,Vulkan 路径待验证)
- 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-opVulkan 项目中的 OFX 插件回退到 CPU 路径。
- 格式转换(`ConvertFrameIfNeeded``ConvertTextureForParams`)、readback`ReadbackTextureToFrame`)、upload 等辅助函数保持后端无关,通过 `Renderer` 接口调用,无需移入后端库。
- `RenderProcessor::ProcessPluginJob` 不再要求 `render_ctx_` 实现 `OpenGLContextProvider`,任何 `Renderer` 都能驱动插件渲染。
- 更新相关 gtest`PluginRenderer` 构造函数现在需要传入 renderer 指针,测试传入 `nullptr` 验证纯 CPU 路径。
- **状态说明**:后端无关的边界框架和 OpenGL 动态路径已可编译并通过现有测试;Vulkan 下的 OFX CPU 回退路径代码已就位,并在 Vulkan 可完成基础 Blit 的当前版本上具备验证条件。
## 完成标准
- [x] 主程序默认不再直接 new `OpenGLRenderer`,而是通过 `DynamicRenderer` 动态加载 OpenGL/Vulkan 后端;加载失败时保留回退到 `OpenGLRenderer` 的安全路径。
- [x] `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 默认 `ON``liboakgl.so` 默认构建并安装;`liboakvulkan.so` 在检测到 Vulkan 开发库时构建并安装。
- [x] OpenGL 后端库可单独构建、加载、初始化、销毁。
- [x] 用户能在配置中选择 OpenGL/Vulkan。
- [x] Vulkan 不可用时自动回退到 OpenGL,不崩溃;`RenderManager::backend()` 会在 `DynamicRenderer` 内部回退后同步为实际运行后端。
- [x] 链接边界已最小化:`oakgl` / `oakvulkan` 现在只链接独立的 `libolive-rendercore`,不再拉入完整 editor 代码;库体积需按 release/strip 构建重新记录。
- [ ] Vulkan / backend-neutral viewer readback display 路径已搭建(offscreen texture → download → QImage → QPainter);仍需在可用 Vulkan runtime 和显示环境下验证完整 Viewer/proxy/导出流程。
- [x] OpenFX 插件渲染边界已处理:`PluginRenderer` 后端无关化,非 OpenGL 渲染器自动回退 CPU 路径,动态 OpenGL 后端通过 C ABI 支持 OFX OpenGL 输出绑定。
- [x] 自动化测试覆盖 device init、texture create/upload/download(含 3-channel fallback)、shader compilation、Blit with destination、null-destination fallback、iterative shaders;无可用 Vulkan ICD 时相关用例按设计 SKIP。
- [ ] 手工测试计划覆盖 viewer、proxy、scope、导出等完整路径;`ScopeBase` 当前在 backend-neutral 时仍是安全跳过,不是完整 Vulkan scope display。