diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 9dc24a912..54904c3e7 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -148,6 +148,16 @@ target_link_libraries(oakgl-cabi-check PRIVATE ${OLIVE_LIBRARIES} OfxHost) target_compile_definitions(oakgl-cabi-check PRIVATE ${OLIVE_DEFINITIONS}) target_compile_options(oakgl-cabi-check PRIVATE ${OLIVE_COMPILE_OPTIONS}) +add_library(oakvulkan-cabi-check OBJECT + render/vulkan/vulkanbackend_c.cpp +) +target_include_directories(oakvulkan-cabi-check PRIVATE + ${CMAKE_SOURCE_DIR}/app +) +target_link_libraries(oakvulkan-cabi-check PRIVATE Qt${QT_VERSION_MAJOR}::Core) +target_compile_definitions(oakvulkan-cabi-check PRIVATE ${OLIVE_DEFINITIONS}) +target_compile_options(oakvulkan-cabi-check PRIVATE ${OLIVE_COMPILE_OPTIONS}) + # Add application add_executable(olive-editor main.cpp @@ -156,7 +166,7 @@ add_executable(olive-editor ) target_include_directories(olive-editor PUBLIC pluginSupport) target_link_libraries(olive-editor PUBLIC OfxHost) -add_dependencies(olive-editor oakgl-cabi-check) +add_dependencies(olive-editor oakgl-cabi-check oakvulkan-cabi-check) if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND) add_dependencies(olive-editor oakgl oakvulkan) endif() diff --git a/app/render/backend/dynamicrenderer.cpp b/app/render/backend/dynamicrenderer.cpp index 5b7df3d19..70603bbf9 100644 --- a/app/render/backend/dynamicrenderer.cpp +++ b/app/render/backend/dynamicrenderer.cpp @@ -142,6 +142,8 @@ bool DynamicRenderer::ResolveFunctions() RESOLVE(opengl_context_, OakBackendOpenGLContextFn, "oak_renderer_opengl_context"); #undef RESOLVE + get_info_ = reinterpret_cast( + library_.resolve("oak_renderer_get_info")); is_available_ = reinterpret_cast( library_.resolve("oak_renderer_is_available")); return true; @@ -165,6 +167,7 @@ void DynamicRenderer::ResetFunctions() { create_ = nullptr; destroy_ = nullptr; + get_info_ = nullptr; is_available_ = nullptr; init_ = nullptr; init_with_context_ = nullptr; @@ -184,6 +187,11 @@ void DynamicRenderer::ResetFunctions() opengl_context_ = nullptr; } +bool DynamicRenderer::GetBackendInfo(OakRenderBackendInfo *out_info) const +{ + return handle_ && get_info_ && out_info && get_info_(handle_, out_info); +} + bool DynamicRenderer::Init() { return Load() && init_(handle_); diff --git a/app/render/backend/dynamicrenderer.h b/app/render/backend/dynamicrenderer.h index 5d83c31c9..fba0bbd51 100644 --- a/app/render/backend/dynamicrenderer.h +++ b/app/render/backend/dynamicrenderer.h @@ -19,6 +19,11 @@ public: bool Load(); bool InitWithOpenGLContext(QOpenGLContext *context); + bool GetBackendInfo(OakRenderBackendInfo *out_info) const; + QString backend_name() const + { + return backend_; + } virtual bool Init() override; virtual void PostDestroy() override; @@ -62,6 +67,7 @@ private: OakBackendCreateFn create_ = nullptr; OakBackendDestroyFn destroy_ = nullptr; + OakBackendGetInfoFn get_info_ = nullptr; OakBackendIsAvailableFn is_available_ = nullptr; OakBackendInitFn init_ = nullptr; OakBackendInitWithContextFn init_with_context_ = nullptr; diff --git a/app/render/backend/renderbackend_c.h b/app/render/backend/renderbackend_c.h index 31bfc9869..47ac79ecc 100644 --- a/app/render/backend/renderbackend_c.h +++ b/app/render/backend/renderbackend_c.h @@ -2,6 +2,7 @@ #define RENDERBACKEND_C_H #include +#include #ifdef _WIN32 #define OAK_RENDER_BACKEND_EXPORT extern "C" __declspec(dllexport) @@ -15,8 +16,32 @@ extern "C" { typedef void *OakRenderBackendHandle; +enum OakRenderBackendKind { + OAK_RENDER_BACKEND_UNKNOWN = 0, + OAK_RENDER_BACKEND_OPENGL = 1, + OAK_RENDER_BACKEND_VULKAN = 2 +}; + +enum OakRenderBackendCapability { + OAK_RENDER_BACKEND_CAP_TEXTURES = 1ULL << 0, + OAK_RENDER_BACKEND_CAP_SHADERS = 1ULL << 1, + OAK_RENDER_BACKEND_CAP_BLIT = 1ULL << 2, + OAK_RENDER_BACKEND_CAP_READBACK = 1ULL << 3, + OAK_RENDER_BACKEND_CAP_VIEWER_CONTEXT = 1ULL << 4 +}; + +struct OakRenderBackendInfo { + uint32_t abi_version; + uint32_t kind; + uint64_t capabilities; + const char *name; + const char *status; +}; + typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent); typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle); +typedef bool (*OakBackendGetInfoFn)(OakRenderBackendHandle handle, + struct OakRenderBackendInfo *out_info); typedef bool (*OakBackendIsAvailableFn)(OakRenderBackendHandle handle); typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle); typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle, diff --git a/app/render/opengl/openglbackend_c.cpp b/app/render/opengl/openglbackend_c.cpp index 59a61b092..80ed2423a 100644 --- a/app/render/opengl/openglbackend_c.cpp +++ b/app/render/opengl/openglbackend_c.cpp @@ -43,6 +43,23 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handl delete Renderer(handle); } +OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info( + OakRenderBackendHandle handle, OakRenderBackendInfo *out_info) +{ + if (!handle || !out_info) { + return false; + } + out_info->abi_version = 1; + out_info->kind = OAK_RENDER_BACKEND_OPENGL; + out_info->capabilities = OAK_RENDER_BACKEND_CAP_TEXTURES | + OAK_RENDER_BACKEND_CAP_SHADERS | OAK_RENDER_BACKEND_CAP_BLIT | + OAK_RENDER_BACKEND_CAP_READBACK | + OAK_RENDER_BACKEND_CAP_VIEWER_CONTEXT; + out_info->name = "opengl"; + out_info->status = "available"; + return true; +} + OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available( OakRenderBackendHandle handle) { diff --git a/app/render/vulkan/vulkanbackend_c.cpp b/app/render/vulkan/vulkanbackend_c.cpp index d1f286452..b4f38a8f8 100644 --- a/app/render/vulkan/vulkanbackend_c.cpp +++ b/app/render/vulkan/vulkanbackend_c.cpp @@ -38,6 +38,20 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handl delete Backend(handle); } +OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info( + OakRenderBackendHandle handle, OakRenderBackendInfo *out_info) +{ + if (!handle || !out_info) { + return false; + } + out_info->abi_version = 1; + out_info->kind = OAK_RENDER_BACKEND_VULKAN; + out_info->capabilities = 0; + out_info->name = "vulkan"; + out_info->status = "placeholder-unavailable"; + return true; +} + OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available( OakRenderBackendHandle handle) { diff --git a/docs/zh/render-backend-dynamic-plan.md b/docs/zh/render-backend-dynamic-plan.md index 4a1d159d6..8df1069f6 100644 --- a/docs/zh/render-backend-dynamic-plan.md +++ b/docs/zh/render-backend-dynamic-plan.md @@ -61,6 +61,8 @@ 已新增可选 `oak_renderer_is_available` C ABI:后端库可以在被加载和 create 后报告自身是否可用。OpenGL 后端返回可用;Vulkan 占位后端返回不可用,适配器随后卸载它并回退 OpenGL。这把“后端存在”和“后端可用于渲染”分开,避免后续最小化链接边界时把不可用实现误接入渲染路径。 +已新增 `oak_renderer_get_info` C ABI:后端库可以报告 ABI 版本、后端类型、能力位和状态字符串。默认构建也会编译检查 OpenGL/Vulkan 两个 C wrapper,避免只在实验构建中发现 C ABI 破损。 + 因此动态后端成为默认路径前,仍需要把 OpenGL 后端库的链接边界收敛到最小集合: - 后端库只拥有 OpenGL/Vulkan native 操作和必要的后端私有状态。 diff --git a/tests/gtest/dynamic_render_backend_test.cpp b/tests/gtest/dynamic_render_backend_test.cpp index ff056a32b..1895bdb8f 100644 --- a/tests/gtest/dynamic_render_backend_test.cpp +++ b/tests/gtest/dynamic_render_backend_test.cpp @@ -10,6 +10,15 @@ TEST(DynamicRenderBackend, LoadsExperimentalOpenGLBackend) olive::DynamicRenderer renderer(QStringLiteral("opengl")); ASSERT_TRUE(renderer.Load()); EXPECT_EQ(renderer.OpenGLContext(), nullptr); + OakRenderBackendInfo info = {}; + ASSERT_TRUE(renderer.GetBackendInfo(&info)); + EXPECT_EQ(info.abi_version, 1U); + EXPECT_EQ(info.kind, OAK_RENDER_BACKEND_OPENGL); + EXPECT_STREQ(info.name, "opengl"); + EXPECT_TRUE(info.capabilities & OAK_RENDER_BACKEND_CAP_TEXTURES); + EXPECT_TRUE(info.capabilities & OAK_RENDER_BACKEND_CAP_SHADERS); + EXPECT_TRUE(info.capabilities & OAK_RENDER_BACKEND_CAP_BLIT); + EXPECT_TRUE(info.capabilities & OAK_RENDER_BACKEND_CAP_READBACK); #endif } @@ -20,6 +29,11 @@ TEST(DynamicRenderBackend, FallsBackWhenExperimentalVulkanUnavailable) #else olive::DynamicRenderer renderer(QStringLiteral("vulkan")); ASSERT_TRUE(renderer.Load()); + EXPECT_EQ(renderer.backend_name(), QStringLiteral("opengl")); EXPECT_EQ(renderer.OpenGLContext(), nullptr); + OakRenderBackendInfo info = {}; + ASSERT_TRUE(renderer.GetBackendInfo(&info)); + EXPECT_EQ(info.kind, OAK_RENDER_BACKEND_OPENGL); + EXPECT_STREQ(info.name, "opengl"); #endif }