From 341283b1cdf6e41c4810519eaed1adfbb652a463 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Sun, 7 Jun 2026 16:42:43 +0800 Subject: [PATCH] Add graphics backend selection --- app/config/config.cpp | 2 + .../tabs/preferencesbehaviortab.cpp | 32 ++++++++++- .../preferences/tabs/preferencesbehaviortab.h | 3 + app/main.cpp | 14 ++++- app/render/rendermanager.cpp | 42 ++++++++++++++ app/render/rendermanager.h | 12 ++++ ...olor-audio-performance-manual-test-plan.md | 55 +++++++++++++++++++ tests/gtest/config_test.cpp | 19 +++++++ 8 files changed, 176 insertions(+), 3 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 988395101..56056b144 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -144,6 +144,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("GraphicsBackend"), NodeValue::kText, + QStringLiteral("opengl")); SetEntryInternal(QStringLiteral("RenderProcessIsolationEnabled"), NodeValue::kBoolean, false); diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index f691ba41c..4e5fac23e 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -22,6 +22,7 @@ #include "preferencesbehaviortab.h" #include +#include #include #include "config/config.h" @@ -36,7 +37,8 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() behavior_tree_ = new QTreeWidget(); layout->addWidget(behavior_tree_); - behavior_tree_->setHeaderLabel(tr("Behavior")); + behavior_tree_->setColumnCount(2); + behavior_tree_->setHeaderLabels({ tr("Behavior"), tr("Setting") }); QTreeWidgetItem *general_group = AddParent(tr("General")); AddItem( @@ -97,7 +99,30 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() "dependencies among clips when copying or splitting them."), node_group); - QTreeWidgetItem *opengl_group = AddParent(tr("OpenGL")); + QTreeWidgetItem *rendering_group = AddParent(tr("Rendering")); + QTreeWidgetItem *graphics_backend_item = + new QTreeWidgetItem({ tr("Graphics Backend") }); + graphics_backend_item->setToolTip( + 0, tr("Selects the graphics API Oak should request on next launch. " + "Vulkan is experimental and currently falls back to the OpenGL " + "renderer for timeline/viewer rendering.")); + rendering_group->addChild(graphics_backend_item); + graphics_backend_combobox_ = new QComboBox(); + graphics_backend_combobox_->addItem(tr("OpenGL"), QStringLiteral("opengl")); + graphics_backend_combobox_->addItem(tr("Vulkan (experimental)"), + QStringLiteral("vulkan")); + const QString current_backend = + OLIVE_CONFIG("GraphicsBackend").toString().toLower(); + const int backend_index = graphics_backend_combobox_->findData( + current_backend.isEmpty() ? QStringLiteral("opengl") + : current_backend); + graphics_backend_combobox_->setCurrentIndex(backend_index >= 0 + ? backend_index + : 0); + behavior_tree_->setItemWidget(graphics_backend_item, 1, + graphics_backend_combobox_); + + QTreeWidgetItem *opengl_group = AddParent(tr("OpenGL"), rendering_group); AddItem(tr("Use glFinish"), QStringLiteral("UseGLFinish"), opengl_group); } @@ -110,6 +135,9 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) OLIVE_CONFIG_STR(iterator.value()) = (iterator.key()->checkState(0) == Qt::Checked); } + + OLIVE_CONFIG("GraphicsBackend") = + graphics_backend_combobox_->currentData().toString(); } QTreeWidgetItem *PreferencesBehaviorTab::AddItem(const QString &text, diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index 60f3b07c4..5e5a5ad04 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -22,6 +22,7 @@ #ifndef PREFERENCESBEHAVIORTAB_H #define PREFERENCESBEHAVIORTAB_H +#include #include #include "dialog/configbase/configdialogbase.h" @@ -49,6 +50,8 @@ private: QMap config_map_; + QComboBox *graphics_backend_combobox_; + QTreeWidget *behavior_tree_; }; diff --git a/app/main.cpp b/app/main.cpp index 364bcdec6..64ddc89b8 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -41,6 +41,7 @@ extern "C" { #include #include +#include "config/config.h" #include "core.h" #include "common/commandlineparser.h" #include "common/debug.h" @@ -300,7 +301,8 @@ int main(int argc, char *argv[]) startup_params.set_startup_project(project_argument->GetSetting()); - // Set OpenGL display profile + // Set OpenGL display profile. Oak's render pipeline still uses OpenGL + // internally even when Vulkan is requested as the Qt graphics backend. QSurfaceFormat format; // Tries to cover all bases. If drivers don't support 3.2, they should fallback to the closest @@ -338,6 +340,16 @@ int main(int argc, char *argv[]) a.reset(new QCoreApplication(argc, argv)); } + olive::Config::Load(); + const QString graphics_backend = + olive::Config::Current()[QStringLiteral("GraphicsBackend")] + .toString() + .toLower(); + qputenv("QSG_RHI_BACKEND", + graphics_backend == QStringLiteral("vulkan") + ? QByteArrayLiteral("vulkan") + : QByteArrayLiteral("opengl")); + if (auto *gui_app = qobject_cast(a.get())) { gui_app->setWindowIcon(QIcon(QStringLiteral(":/graphics/oak-logo.png"))); } diff --git a/app/render/rendermanager.cpp b/app/render/rendermanager.cpp index ab9d8b768..c5254fa9d 100644 --- a/app/render/rendermanager.cpp +++ b/app/render/rendermanager.cpp @@ -40,11 +40,53 @@ namespace olive RenderManager *RenderManager::instance_ = nullptr; const rational RenderManager::kDryRunInterval = rational(10); +RenderManager::Backend RenderManager::BackendFromString(const QString &backend) +{ + const QString lower = backend.toLower(); + if (lower == QStringLiteral("vulkan")) { + return kVulkan; + } + + if (lower == QStringLiteral("multiprocess")) { + return kMultiProcess; + } + + if (lower == QStringLiteral("dummy")) { + return kDummy; + } + + return kOpenGL; +} + +QString RenderManager::BackendToString(Backend backend) +{ + switch (backend) { + case kOpenGL: + return QStringLiteral("opengl"); + case kVulkan: + return QStringLiteral("vulkan"); + case kMultiProcess: + return QStringLiteral("multiprocess"); + case kDummy: + return QStringLiteral("dummy"); + } + + return QStringLiteral("opengl"); +} + RenderManager::RenderManager(QObject *parent) : backend_(kOpenGL) + , requested_backend_(BackendFromString( + OLIVE_CONFIG("GraphicsBackend").toString())) , aggressive_gc_(0) , worker_pool_(nullptr) { + if (requested_backend_ == kVulkan) { + qWarning() + << "Vulkan graphics backend was requested, but the current render " + "pipeline still uses OpenGL. Falling back to OpenGL renderer."; + } + if (backend_ == kOpenGL) { context_ = new OpenGLRenderer(); decoder_cache_ = new DecoderCache(); diff --git a/app/render/rendermanager.h b/app/render/rendermanager.h index f06d0f1b6..e335d034f 100644 --- a/app/render/rendermanager.h +++ b/app/render/rendermanager.h @@ -78,6 +78,9 @@ public: /// Graphics acceleration provided by OpenGL kOpenGL, + /// Vulkan requested by the user. Falls back to OpenGL until VulkanRenderer is implemented. + kVulkan, + /// Video frames are rendered by an external olive-render-worker process. kMultiProcess, @@ -201,6 +204,14 @@ public: return backend_; } + Backend requested_backend() const + { + return requested_backend_; + } + + static Backend BackendFromString(const QString &backend); + static QString BackendToString(Backend backend); + PreviewAutoCacher *GetCacher() const { return auto_cacher_; @@ -228,6 +239,7 @@ private: Renderer *context_; Backend backend_; + Backend requested_backend_; DecoderCache *decoder_cache_; diff --git a/docs/zh/v04-color-audio-performance-manual-test-plan.md b/docs/zh/v04-color-audio-performance-manual-test-plan.md index b567fe483..db1d10415 100644 --- a/docs/zh/v04-color-audio-performance-manual-test-plan.md +++ b/docs/zh/v04-color-audio-performance-manual-test-plan.md @@ -18,8 +18,10 @@ |:--|:--|:--| | Linux + Mesa/AMD 或 Intel | 软件导出、代理、示波器、音频同步 | 当前开发主环境优先 | | Linux + NVIDIA | NVENC、代理、4K/8K 预览 | 需要 NVIDIA 驱动和 ffmpeg 编码器支持 | +| Linux + Vulkan 驱动 | 图形后端选择、Vulkan 请求、OpenGL 回退 | Vulkan 当前为实验入口,需确认不会破坏 OpenGL 渲染 | | macOS Apple Silicon | VideoToolbox、ColorSync/显示路径、代理 | 重点看硬件导出和 UI 响应 | | Windows + NVIDIA/Intel | NVENC/QSV 可用性、路径编码、文件管理器 reveal | 重点看中文路径和空格路径 | +| Windows + Vulkan Runtime | 图形后端选择、Vulkan 请求、驱动缺失回退 | 重点看设置持久化和启动稳定性 | ## 测试素材准备 @@ -357,6 +359,58 @@ 通过标准:内存没有持续不可控增长;播放停止后应用仍可操作和保存。 +## 10. 图形后端选择测试 + +当前版本允许用户在 Preferences 中选择 OpenGL 或 Vulkan。注意:Vulkan 入口目前是实验性图形 API 请求和后续 VulkanRenderer 的接入点;时间线/viewer 渲染仍应安全回退到现有 OpenGL renderer。因此测试重点是“用户可选择、设置可持久化、Vulkan 请求不破坏现有渲染、失败可回退”。 + +### 10.1 默认 OpenGL 后端 + +1. 删除或备份现有用户配置。 +2. 启动 Oak。 +3. 打开 Preferences > Behavior > Rendering。 +4. 检查 Graphics Backend 的默认值。 +5. 导入 `color_chart.mov` 并播放。 + +通过标准:默认值为 OpenGL;viewer、Scope、调色和播放行为与原 OpenGL 路径一致。 + +### 10.2 切换到 Vulkan 并重启 + +1. 在 Preferences > Behavior > Rendering 中选择 `Vulkan (experimental)`。 +2. 确认设置保存。 +3. 关闭 Oak 并重新启动。 +4. 再次打开 Preferences,确认仍显示 Vulkan。 +5. 导入并播放 `4k_camera_a.mov`。 + +通过标准:Vulkan 选择可持久化;重启后应用不崩溃;当前未完成 VulkanRenderer 时应明确回退 OpenGL 渲染,播放仍可用。 + +### 10.3 Vulkan 驱动缺失或不可用 + +1. 在没有 Vulkan Runtime 或驱动不可用的机器上选择 Vulkan。 +2. 重启 Oak。 +3. 打开项目并播放一段视频。 +4. 检查日志或控制台输出。 + +通过标准:应用可以启动;日志应说明 Vulkan 请求不可完全满足或当前回退 OpenGL;用户能回到 Preferences 改回 OpenGL。 + +### 10.4 从 Vulkan 切回 OpenGL + +1. 在 Vulkan 已选中状态下打开 Preferences。 +2. 将 Graphics Backend 改为 OpenGL。 +3. 保存、退出并重启。 +4. 播放同一项目并执行一次软件导出。 + +通过标准:重启后显示 OpenGL;播放和导出正常;不会保留错误的 Vulkan 状态。 + +### 10.5 代理、Scope 与调色组合 + +1. 选择 Vulkan 并重启。 +2. 对 `8k_or_heavy_camera.mov` 生成并启用代理。 +3. 添加 LUT 和三向调色。 +4. 打开 Waveform、Vectorscope、Histogram 依次观察。 +5. 切回 OpenGL 后重复同一段播放。 + +通过标准:Vulkan 请求状态下代理、Scope、调色不崩溃;切回 OpenGL 后项目状态一致;两种选择下导出默认仍使用原片。 + ## 缺陷记录模板 每个失败项记录以下信息: @@ -373,6 +427,7 @@ - 预检、LUT、三向色轮、三类 Scope、波形同步、BWF 时间码、音频表、代理生成/启用/删除、软件导出全部通过。 - 至少一个硬件编码环境通过 NVENC 或 VideoToolbox。 +- OpenGL/Vulkan 图形后端选择、持久化和 Vulkan 请求回退测试通过。 - 批量队列至少通过多任务执行和取消测试。 - 组合回归测试中的完整剪辑链路通过。 - 所有失败项有明确 issue 或文档化限制,不存在“无提示崩溃”级别问题。 diff --git a/tests/gtest/config_test.cpp b/tests/gtest/config_test.cpp index 94322588d..ae14517bf 100644 --- a/tests/gtest/config_test.cpp +++ b/tests/gtest/config_test.cpp @@ -1,6 +1,7 @@ #include #include "config/config.h" +#include "render/rendermanager.h" TEST(Config, DefaultsPresent) { @@ -10,6 +11,8 @@ TEST(Config, DefaultsPresent) EXPECT_TRUE(cfg[QStringLiteral("Style")].isValid()); EXPECT_TRUE(cfg[QStringLiteral("TimecodeDisplay")].isValid()); EXPECT_TRUE(cfg[QStringLiteral("DefaultStillLength")].isValid()); + EXPECT_EQ(cfg[QStringLiteral("GraphicsBackend")].toString(), + QStringLiteral("opengl")); } TEST(Config, SetAndGetValues) @@ -18,3 +21,19 @@ TEST(Config, SetAndGetValues) cfg[QStringLiteral("UnitTestValue")] = 42; EXPECT_EQ(cfg[QStringLiteral("UnitTestValue")].toInt(), 42); } + +TEST(Config, GraphicsBackendStringConversion) +{ + EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("opengl")), + olive::RenderManager::kOpenGL); + EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("vulkan")), + olive::RenderManager::kVulkan); + EXPECT_EQ(olive::RenderManager::BackendToString( + olive::RenderManager::kOpenGL), + QStringLiteral("opengl")); + EXPECT_EQ(olive::RenderManager::BackendToString( + olive::RenderManager::kVulkan), + QStringLiteral("vulkan")); + EXPECT_EQ(olive::RenderManager::BackendFromString(QStringLiteral("bad")), + olive::RenderManager::kOpenGL); +}