Add Vulkan backend placeholder with dynamic fallback

This commit is contained in:
2026-07-13 10:19:29 +08:00
parent 76a98e5a93
commit 38c5afb13d
9 changed files with 280 additions and 2 deletions
+17 -1
View File
@@ -117,6 +117,22 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(oakvulkan SHARED
render/vulkan/vulkanbackend_c.cpp
)
target_include_directories(oakvulkan PRIVATE
${CMAKE_SOURCE_DIR}/app
)
target_link_libraries(oakvulkan PRIVATE Qt${QT_VERSION_MAJOR}::Core)
target_compile_definitions(oakvulkan PRIVATE ${OLIVE_DEFINITIONS})
target_compile_options(oakvulkan PRIVATE ${OLIVE_COMPILE_OPTIONS})
set_target_properties(oakvulkan PROPERTIES
OUTPUT_NAME oakvulkan
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
endif()
add_library(oakgl-cabi-check OBJECT
@@ -142,7 +158,7 @@ target_include_directories(olive-editor PUBLIC pluginSupport)
target_link_libraries(olive-editor PUBLIC OfxHost)
add_dependencies(olive-editor oakgl-cabi-check)
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
add_dependencies(olive-editor oakgl)
add_dependencies(olive-editor oakgl oakvulkan)
endif()
# Add render worker process (olive-render-worker).
+55
View File
@@ -86,11 +86,27 @@ bool DynamicRenderer::Load()
}
handle_ = create_(this->parent());
if (!handle_) {
library_.unload();
return false;
}
if (is_available_ && !is_available_(handle_)) {
qWarning() << "Render backend is not available" << backend_
<< library_.fileName();
if (backend_ == QStringLiteral("vulkan")) {
return FallbackToOpenGL();
}
destroy_(handle_);
handle_ = nullptr;
library_.unload();
return false;
}
return handle_ != nullptr;
}
bool DynamicRenderer::ResolveFunctions()
{
ResetFunctions();
#define RESOLVE(member, type, symbol) \
member = reinterpret_cast<type>(library_.resolve(symbol)); \
if (!member) return false
@@ -126,9 +142,48 @@ bool DynamicRenderer::ResolveFunctions()
RESOLVE(opengl_context_, OakBackendOpenGLContextFn,
"oak_renderer_opengl_context");
#undef RESOLVE
is_available_ = reinterpret_cast<OakBackendIsAvailableFn>(
library_.resolve("oak_renderer_is_available"));
return true;
}
bool DynamicRenderer::FallbackToOpenGL()
{
if (handle_ && destroy_) {
destroy_(handle_);
handle_ = nullptr;
}
if (library_.isLoaded()) {
library_.unload();
}
ResetFunctions();
backend_ = QStringLiteral("opengl");
return Load();
}
void DynamicRenderer::ResetFunctions()
{
create_ = nullptr;
destroy_ = nullptr;
is_available_ = nullptr;
init_ = nullptr;
init_with_context_ = nullptr;
post_init_ = nullptr;
post_destroy_ = nullptr;
destroy_internal_ = nullptr;
clear_destination_ = nullptr;
create_native_texture_ = nullptr;
destroy_native_texture_ = nullptr;
create_native_shader_ = nullptr;
destroy_native_shader_ = nullptr;
upload_to_texture_ = nullptr;
download_from_texture_ = nullptr;
flush_ = nullptr;
get_pixel_from_texture_ = nullptr;
blit_ = nullptr;
opengl_context_ = nullptr;
}
bool DynamicRenderer::Init()
{
return Load() && init_(handle_);
+3
View File
@@ -52,6 +52,8 @@ protected:
private:
bool ResolveFunctions();
bool FallbackToOpenGL();
void ResetFunctions();
QString LibraryFilename() const;
QString backend_;
@@ -60,6 +62,7 @@ private:
OakBackendCreateFn create_ = nullptr;
OakBackendDestroyFn destroy_ = nullptr;
OakBackendIsAvailableFn is_available_ = nullptr;
OakBackendInitFn init_ = nullptr;
OakBackendInitWithContextFn init_with_context_ = nullptr;
OakBackendPostInitFn post_init_ = nullptr;
+1
View File
@@ -17,6 +17,7 @@ typedef void *OakRenderBackendHandle;
typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent);
typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle);
typedef bool (*OakBackendIsAvailableFn)(OakRenderBackendHandle handle);
typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle);
typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle,
void *context);
+6
View File
@@ -43,6 +43,12 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handl
delete Renderer(handle);
}
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
OakRenderBackendHandle handle)
{
return handle != nullptr;
}
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
{
return Renderer(handle)->Init();
+182
View File
@@ -0,0 +1,182 @@
#include "render/backend/renderbackend_c.h"
#include <QDebug>
#include <QVariant>
namespace {
struct VulkanBackend {
bool warned = false;
};
VulkanBackend *Backend(OakRenderBackendHandle handle)
{
return static_cast<VulkanBackend *>(handle);
}
void WarnUnavailable(OakRenderBackendHandle handle, const char *function)
{
auto *backend = Backend(handle);
if (!backend || backend->warned) {
return;
}
backend->warned = true;
qWarning() << "Vulkan render backend is a C ABI placeholder; function"
<< function << "is not implemented yet";
}
} // namespace
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
{
Q_UNUSED(parent)
return new VulkanBackend();
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
{
delete Backend(handle);
}
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
return false;
}
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
{
WarnUnavailable(handle, "init");
return false;
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
OakRenderBackendHandle handle, void *context)
{
Q_UNUSED(context)
WarnUnavailable(handle, "init_with_context");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(
OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(
OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
double a)
{
Q_UNUSED(texture)
Q_UNUSED(r)
Q_UNUSED(g)
Q_UNUSED(b)
Q_UNUSED(a)
WarnUnavailable(handle, "clear_destination");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
OakRenderBackendHandle handle, int width, int height, int depth, int format,
int channel_count, const void *data, int linesize, void *out_variant)
{
Q_UNUSED(width)
Q_UNUSED(height)
Q_UNUSED(depth)
Q_UNUSED(format)
Q_UNUSED(channel_count)
Q_UNUSED(data)
Q_UNUSED(linesize)
static_cast<QVariant *>(out_variant)->clear();
WarnUnavailable(handle, "create_native_texture");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
OakRenderBackendHandle handle, const void *variant)
{
Q_UNUSED(variant)
WarnUnavailable(handle, "destroy_native_texture");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
{
Q_UNUSED(shader_code)
static_cast<QVariant *>(out_variant)->clear();
WarnUnavailable(handle, "create_native_shader");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
OakRenderBackendHandle handle, const void *variant)
{
Q_UNUSED(variant)
WarnUnavailable(handle, "destroy_native_shader");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
const void *data, int linesize)
{
Q_UNUSED(variant)
Q_UNUSED(video_params)
Q_UNUSED(data)
Q_UNUSED(linesize)
WarnUnavailable(handle, "upload_to_texture");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
OakRenderBackendHandle handle, const void *variant, const void *video_params,
void *data, int linesize)
{
Q_UNUSED(variant)
Q_UNUSED(video_params)
Q_UNUSED(data)
Q_UNUSED(linesize)
WarnUnavailable(handle, "download_from_texture");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
OakRenderBackendHandle handle, void *texture, const void *point,
void *out_color)
{
Q_UNUSED(texture)
Q_UNUSED(point)
Q_UNUSED(out_color)
WarnUnavailable(handle, "get_pixel_from_texture");
}
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
OakRenderBackendHandle handle, const void *shader, void *job,
void *destination, const void *destination_params, bool clear_destination)
{
Q_UNUSED(shader)
Q_UNUSED(job)
Q_UNUSED(destination)
Q_UNUSED(destination_params)
Q_UNUSED(clear_destination)
WarnUnavailable(handle, "blit");
}
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
OakRenderBackendHandle handle)
{
Q_UNUSED(handle)
return nullptr;
}
+4
View File
@@ -59,6 +59,8 @@
已新增 `DynamicRenderBackend.LoadsExperimentalOpenGLBackend` smoke test:默认构建跳过,实验构建会实际加载 `liboakgl` 并验证 create/destroy C ABI 路径。
已新增可选 `oak_renderer_is_available` C ABI:后端库可以在被加载和 create 后报告自身是否可用。OpenGL 后端返回可用;Vulkan 占位后端返回不可用,适配器随后卸载它并回退 OpenGL。这把“后端存在”和“后端可用于渲染”分开,避免后续最小化链接边界时把不可用实现误接入渲染路径。
因此动态后端成为默认路径前,仍需要把 OpenGL 后端库的链接边界收敛到最小集合:
- 后端库只拥有 OpenGL/Vulkan native 操作和必要的后端私有状态。
@@ -69,6 +71,8 @@
## 阶段 3Vulkan 后端
- 新增 Vulkan 后端库。
- 已新增 `liboakvulkan.so` 实验占位库:内部使用 C++,对外导出与 OpenGL 后端一致的 C ABI;当前 `oak_renderer_is_available` 返回 `false`,请求 Vulkan 时由 `DynamicRenderer` 自动回退 OpenGL。
- 已新增 `DynamicRenderBackend.FallsBackWhenExperimentalVulkanUnavailable` smoke test:默认构建跳过,实验构建验证 Vulkan 占位后端可加载且不会被误用为可渲染后端。
- 实现 Vulkan instance/device/swapchain 或 offscreen image 管理。
- 实现 texture 创建、上传、下载、shader 编译/缓存、blit、clear、flush。
- 提供 OpenGL 辅助格式转换函数的 Vulkan 替代版本。
+1 -1
View File
@@ -83,7 +83,7 @@ target_compile_options(
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
target_compile_definitions(olive-gtest PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
add_dependencies(olive-gtest oakgl)
add_dependencies(olive-gtest oakgl oakvulkan)
endif()
if (MSVC)
@@ -12,3 +12,14 @@ TEST(DynamicRenderBackend, LoadsExperimentalOpenGLBackend)
EXPECT_EQ(renderer.OpenGLContext(), nullptr);
#endif
}
TEST(DynamicRenderBackend, FallsBackWhenExperimentalVulkanUnavailable)
{
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
GTEST_SKIP() << "Dynamic render backend is not enabled in this build";
#else
olive::DynamicRenderer renderer(QStringLiteral("vulkan"));
ASSERT_TRUE(renderer.Load());
EXPECT_EQ(renderer.OpenGLContext(), nullptr);
#endif
}