Add graphics backend selection
This commit is contained in:
@@ -144,6 +144,8 @@ void Config::SetDefaults()
|
|||||||
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"),
|
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"),
|
||||||
NodeValue::kBoolean, false);
|
NodeValue::kBoolean, false);
|
||||||
SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false);
|
SetEntryInternal(QStringLiteral("UseGLFinish"), NodeValue::kBoolean, false);
|
||||||
|
SetEntryInternal(QStringLiteral("GraphicsBackend"), NodeValue::kText,
|
||||||
|
QStringLiteral("opengl"));
|
||||||
SetEntryInternal(QStringLiteral("RenderProcessIsolationEnabled"),
|
SetEntryInternal(QStringLiteral("RenderProcessIsolationEnabled"),
|
||||||
NodeValue::kBoolean, false);
|
NodeValue::kBoolean, false);
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "preferencesbehaviortab.h"
|
#include "preferencesbehaviortab.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QComboBox>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
@@ -36,7 +37,8 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
|
|||||||
behavior_tree_ = new QTreeWidget();
|
behavior_tree_ = new QTreeWidget();
|
||||||
layout->addWidget(behavior_tree_);
|
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"));
|
QTreeWidgetItem *general_group = AddParent(tr("General"));
|
||||||
AddItem(
|
AddItem(
|
||||||
@@ -97,7 +99,30 @@ PreferencesBehaviorTab::PreferencesBehaviorTab()
|
|||||||
"dependencies among clips when copying or splitting them."),
|
"dependencies among clips when copying or splitting them."),
|
||||||
node_group);
|
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);
|
AddItem(tr("Use glFinish"), QStringLiteral("UseGLFinish"), opengl_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +135,9 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command)
|
|||||||
OLIVE_CONFIG_STR(iterator.value()) =
|
OLIVE_CONFIG_STR(iterator.value()) =
|
||||||
(iterator.key()->checkState(0) == Qt::Checked);
|
(iterator.key()->checkState(0) == Qt::Checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OLIVE_CONFIG("GraphicsBackend") =
|
||||||
|
graphics_backend_combobox_->currentData().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QTreeWidgetItem *PreferencesBehaviorTab::AddItem(const QString &text,
|
QTreeWidgetItem *PreferencesBehaviorTab::AddItem(const QString &text,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#ifndef PREFERENCESBEHAVIORTAB_H
|
#ifndef PREFERENCESBEHAVIORTAB_H
|
||||||
#define PREFERENCESBEHAVIORTAB_H
|
#define PREFERENCESBEHAVIORTAB_H
|
||||||
|
|
||||||
|
#include <QComboBox>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
|
|
||||||
#include "dialog/configbase/configdialogbase.h"
|
#include "dialog/configbase/configdialogbase.h"
|
||||||
@@ -49,6 +50,8 @@ private:
|
|||||||
|
|
||||||
QMap<QTreeWidgetItem *, QString> config_map_;
|
QMap<QTreeWidgetItem *, QString> config_map_;
|
||||||
|
|
||||||
|
QComboBox *graphics_backend_combobox_;
|
||||||
|
|
||||||
QTreeWidget *behavior_tree_;
|
QTreeWidget *behavior_tree_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -41,6 +41,7 @@ extern "C" {
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
|
||||||
|
#include "config/config.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "common/commandlineparser.h"
|
#include "common/commandlineparser.h"
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
@@ -300,7 +301,8 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
startup_params.set_startup_project(project_argument->GetSetting());
|
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;
|
QSurfaceFormat format;
|
||||||
|
|
||||||
// Tries to cover all bases. If drivers don't support 3.2, they should fallback to the closest
|
// 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));
|
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<QGuiApplication *>(a.get())) {
|
if (auto *gui_app = qobject_cast<QGuiApplication *>(a.get())) {
|
||||||
gui_app->setWindowIcon(QIcon(QStringLiteral(":/graphics/oak-logo.png")));
|
gui_app->setWindowIcon(QIcon(QStringLiteral(":/graphics/oak-logo.png")));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,53 @@ namespace olive
|
|||||||
RenderManager *RenderManager::instance_ = nullptr;
|
RenderManager *RenderManager::instance_ = nullptr;
|
||||||
const rational RenderManager::kDryRunInterval = rational(10);
|
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)
|
RenderManager::RenderManager(QObject *parent)
|
||||||
: backend_(kOpenGL)
|
: backend_(kOpenGL)
|
||||||
|
, requested_backend_(BackendFromString(
|
||||||
|
OLIVE_CONFIG("GraphicsBackend").toString()))
|
||||||
, aggressive_gc_(0)
|
, aggressive_gc_(0)
|
||||||
, worker_pool_(nullptr)
|
, 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) {
|
if (backend_ == kOpenGL) {
|
||||||
context_ = new OpenGLRenderer();
|
context_ = new OpenGLRenderer();
|
||||||
decoder_cache_ = new DecoderCache();
|
decoder_cache_ = new DecoderCache();
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ public:
|
|||||||
/// Graphics acceleration provided by OpenGL
|
/// Graphics acceleration provided by OpenGL
|
||||||
kOpenGL,
|
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.
|
/// Video frames are rendered by an external olive-render-worker process.
|
||||||
kMultiProcess,
|
kMultiProcess,
|
||||||
|
|
||||||
@@ -201,6 +204,14 @@ public:
|
|||||||
return backend_;
|
return backend_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Backend requested_backend() const
|
||||||
|
{
|
||||||
|
return requested_backend_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Backend BackendFromString(const QString &backend);
|
||||||
|
static QString BackendToString(Backend backend);
|
||||||
|
|
||||||
PreviewAutoCacher *GetCacher() const
|
PreviewAutoCacher *GetCacher() const
|
||||||
{
|
{
|
||||||
return auto_cacher_;
|
return auto_cacher_;
|
||||||
@@ -228,6 +239,7 @@ private:
|
|||||||
Renderer *context_;
|
Renderer *context_;
|
||||||
|
|
||||||
Backend backend_;
|
Backend backend_;
|
||||||
|
Backend requested_backend_;
|
||||||
|
|
||||||
DecoderCache *decoder_cache_;
|
DecoderCache *decoder_cache_;
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
|:--|:--|:--|
|
|:--|:--|:--|
|
||||||
| Linux + Mesa/AMD 或 Intel | 软件导出、代理、示波器、音频同步 | 当前开发主环境优先 |
|
| Linux + Mesa/AMD 或 Intel | 软件导出、代理、示波器、音频同步 | 当前开发主环境优先 |
|
||||||
| Linux + NVIDIA | NVENC、代理、4K/8K 预览 | 需要 NVIDIA 驱动和 ffmpeg 编码器支持 |
|
| Linux + NVIDIA | NVENC、代理、4K/8K 预览 | 需要 NVIDIA 驱动和 ffmpeg 编码器支持 |
|
||||||
|
| Linux + Vulkan 驱动 | 图形后端选择、Vulkan 请求、OpenGL 回退 | Vulkan 当前为实验入口,需确认不会破坏 OpenGL 渲染 |
|
||||||
| macOS Apple Silicon | VideoToolbox、ColorSync/显示路径、代理 | 重点看硬件导出和 UI 响应 |
|
| macOS Apple Silicon | VideoToolbox、ColorSync/显示路径、代理 | 重点看硬件导出和 UI 响应 |
|
||||||
| Windows + NVIDIA/Intel | NVENC/QSV 可用性、路径编码、文件管理器 reveal | 重点看中文路径和空格路径 |
|
| 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 时间码、音频表、代理生成/启用/删除、软件导出全部通过。
|
- 预检、LUT、三向色轮、三类 Scope、波形同步、BWF 时间码、音频表、代理生成/启用/删除、软件导出全部通过。
|
||||||
- 至少一个硬件编码环境通过 NVENC 或 VideoToolbox。
|
- 至少一个硬件编码环境通过 NVENC 或 VideoToolbox。
|
||||||
|
- OpenGL/Vulkan 图形后端选择、持久化和 Vulkan 请求回退测试通过。
|
||||||
- 批量队列至少通过多任务执行和取消测试。
|
- 批量队列至少通过多任务执行和取消测试。
|
||||||
- 组合回归测试中的完整剪辑链路通过。
|
- 组合回归测试中的完整剪辑链路通过。
|
||||||
- 所有失败项有明确 issue 或文档化限制,不存在“无提示崩溃”级别问题。
|
- 所有失败项有明确 issue 或文档化限制,不存在“无提示崩溃”级别问题。
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
|
#include "render/rendermanager.h"
|
||||||
|
|
||||||
TEST(Config, DefaultsPresent)
|
TEST(Config, DefaultsPresent)
|
||||||
{
|
{
|
||||||
@@ -10,6 +11,8 @@ TEST(Config, DefaultsPresent)
|
|||||||
EXPECT_TRUE(cfg[QStringLiteral("Style")].isValid());
|
EXPECT_TRUE(cfg[QStringLiteral("Style")].isValid());
|
||||||
EXPECT_TRUE(cfg[QStringLiteral("TimecodeDisplay")].isValid());
|
EXPECT_TRUE(cfg[QStringLiteral("TimecodeDisplay")].isValid());
|
||||||
EXPECT_TRUE(cfg[QStringLiteral("DefaultStillLength")].isValid());
|
EXPECT_TRUE(cfg[QStringLiteral("DefaultStillLength")].isValid());
|
||||||
|
EXPECT_EQ(cfg[QStringLiteral("GraphicsBackend")].toString(),
|
||||||
|
QStringLiteral("opengl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Config, SetAndGetValues)
|
TEST(Config, SetAndGetValues)
|
||||||
@@ -18,3 +21,19 @@ TEST(Config, SetAndGetValues)
|
|||||||
cfg[QStringLiteral("UnitTestValue")] = 42;
|
cfg[QStringLiteral("UnitTestValue")] = 42;
|
||||||
EXPECT_EQ(cfg[QStringLiteral("UnitTestValue")].toInt(), 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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user