Add Vulkan backend placeholder with dynamic fallback
This commit is contained in:
+17
-1
@@ -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).
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 @@
|
||||
## 阶段 3:Vulkan 后端
|
||||
|
||||
- 新增 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 替代版本。
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user