Add dynamic render backend adapter scaffold
This commit is contained in:
@@ -87,6 +87,24 @@ include_directories(../third_party/openfx/include
|
|||||||
# Remove prefix - prevents CMake calling it "liblibolive-editor"
|
# Remove prefix - prevents CMake calling it "liblibolive-editor"
|
||||||
set_target_properties(libolive-editor PROPERTIES PREFIX "")
|
set_target_properties(libolive-editor PROPERTIES PREFIX "")
|
||||||
|
|
||||||
|
option(OAK_ENABLE_DYNAMIC_RENDER_BACKEND "Build and use the experimental dynamic render backend adapter" OFF)
|
||||||
|
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||||
|
message(FATAL_ERROR "OAK_ENABLE_DYNAMIC_RENDER_BACKEND is scaffolded but not linkable yet. Split the OpenGL backend dependency boundary before enabling it.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(oakgl-cabi-check OBJECT
|
||||||
|
render/opengl/openglbackend_c.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(oakgl-cabi-check PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/app
|
||||||
|
${CMAKE_SOURCE_DIR}/third_party/openfx/include
|
||||||
|
${CMAKE_SOURCE_DIR}/third_party/openfx/HostSupport/include
|
||||||
|
${OLIVE_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
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 application
|
# Add application
|
||||||
add_executable(olive-editor
|
add_executable(olive-editor
|
||||||
main.cpp
|
main.cpp
|
||||||
@@ -95,6 +113,7 @@ add_executable(olive-editor
|
|||||||
)
|
)
|
||||||
target_include_directories(olive-editor PUBLIC pluginSupport)
|
target_include_directories(olive-editor PUBLIC pluginSupport)
|
||||||
target_link_libraries(olive-editor PUBLIC OfxHost)
|
target_link_libraries(olive-editor PUBLIC OfxHost)
|
||||||
|
add_dependencies(olive-editor oakgl-cabi-check)
|
||||||
|
|
||||||
# Add render worker process (olive-render-worker).
|
# Add render worker process (olive-render-worker).
|
||||||
# Reuses the libolive-editor object library so the worker shares the exact same render/node/codec
|
# Reuses the libolive-editor object library so the worker shares the exact same render/node/codec
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ set(OLIVE_SOURCES
|
|||||||
render/audioplaybackcache.h
|
render/audioplaybackcache.h
|
||||||
render/audiowaveformcache.cpp
|
render/audiowaveformcache.cpp
|
||||||
render/audiowaveformcache.h
|
render/audiowaveformcache.h
|
||||||
|
render/backend/dynamicrenderer.cpp
|
||||||
|
render/backend/dynamicrenderer.h
|
||||||
|
render/backend/renderbackend_c.h
|
||||||
render/cancelatom.h
|
render/cancelatom.h
|
||||||
render/colorprocessor.cpp
|
render/colorprocessor.cpp
|
||||||
render/colorprocessor.h
|
render/colorprocessor.h
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
#include "dynamicrenderer.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
|
||||||
|
namespace olive
|
||||||
|
{
|
||||||
|
|
||||||
|
DynamicRenderer::DynamicRenderer(const QString &backend, QObject *parent)
|
||||||
|
: Renderer(parent)
|
||||||
|
, backend_(backend.toLower())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicRenderer::~DynamicRenderer()
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
PostDestroy();
|
||||||
|
if (handle_ && destroy_) {
|
||||||
|
destroy_(handle_);
|
||||||
|
handle_ = nullptr;
|
||||||
|
}
|
||||||
|
if (library_.isLoaded()) {
|
||||||
|
library_.unload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DynamicRenderer::LibraryFilename() const
|
||||||
|
{
|
||||||
|
const QString base = backend_ == QStringLiteral("vulkan")
|
||||||
|
? QStringLiteral("oakvulkan")
|
||||||
|
: QStringLiteral("oakgl");
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
const QString filename = base + QStringLiteral(".dll");
|
||||||
|
#elif defined(Q_OS_MAC)
|
||||||
|
const QString filename = QStringLiteral("lib") + base + QStringLiteral(".dylib");
|
||||||
|
#else
|
||||||
|
const QString filename = QStringLiteral("lib") + base + QStringLiteral(".so");
|
||||||
|
#endif
|
||||||
|
return QDir(QCoreApplication::applicationDirPath()).filePath(
|
||||||
|
filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DynamicRenderer::Load()
|
||||||
|
{
|
||||||
|
if (handle_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
library_.setFileName(LibraryFilename());
|
||||||
|
if (!library_.load()) {
|
||||||
|
if (backend_ == QStringLiteral("vulkan")) {
|
||||||
|
qWarning() << "Failed to load Vulkan render backend"
|
||||||
|
<< library_.fileName() << library_.errorString()
|
||||||
|
<< "falling back to OpenGL backend";
|
||||||
|
backend_ = QStringLiteral("opengl");
|
||||||
|
library_.setFileName(LibraryFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!library_.load()) {
|
||||||
|
qWarning() << "Failed to load render backend" << backend_
|
||||||
|
<< library_.fileName() << library_.errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ResolveFunctions()) {
|
||||||
|
qWarning() << "Render backend is missing required symbols" << backend_;
|
||||||
|
library_.unload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_ = create_(this->parent());
|
||||||
|
return handle_ != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DynamicRenderer::ResolveFunctions()
|
||||||
|
{
|
||||||
|
#define RESOLVE(member, type, symbol) \
|
||||||
|
member = reinterpret_cast<type>(library_.resolve(symbol)); \
|
||||||
|
if (!member) return false
|
||||||
|
|
||||||
|
RESOLVE(create_, OakBackendCreateFn, "oak_renderer_create");
|
||||||
|
RESOLVE(destroy_, OakBackendDestroyFn, "oak_renderer_destroy");
|
||||||
|
RESOLVE(init_, OakBackendInitFn, "oak_renderer_init");
|
||||||
|
RESOLVE(init_with_context_, OakBackendInitWithContextFn,
|
||||||
|
"oak_renderer_init_with_context");
|
||||||
|
RESOLVE(post_init_, OakBackendPostInitFn, "oak_renderer_post_init");
|
||||||
|
RESOLVE(post_destroy_, OakBackendPostDestroyFn,
|
||||||
|
"oak_renderer_post_destroy");
|
||||||
|
RESOLVE(destroy_internal_, OakBackendDestroyInternalFn,
|
||||||
|
"oak_renderer_destroy_internal");
|
||||||
|
RESOLVE(clear_destination_, OakBackendClearDestinationFn,
|
||||||
|
"oak_renderer_clear_destination");
|
||||||
|
RESOLVE(create_native_texture_, OakBackendCreateNativeTextureFn,
|
||||||
|
"oak_renderer_create_native_texture");
|
||||||
|
RESOLVE(destroy_native_texture_, OakBackendDestroyNativeTextureFn,
|
||||||
|
"oak_renderer_destroy_native_texture");
|
||||||
|
RESOLVE(create_native_shader_, OakBackendCreateNativeShaderFn,
|
||||||
|
"oak_renderer_create_native_shader");
|
||||||
|
RESOLVE(destroy_native_shader_, OakBackendDestroyNativeShaderFn,
|
||||||
|
"oak_renderer_destroy_native_shader");
|
||||||
|
RESOLVE(upload_to_texture_, OakBackendUploadToTextureFn,
|
||||||
|
"oak_renderer_upload_to_texture");
|
||||||
|
RESOLVE(download_from_texture_, OakBackendDownloadFromTextureFn,
|
||||||
|
"oak_renderer_download_from_texture");
|
||||||
|
RESOLVE(flush_, OakBackendFlushFn, "oak_renderer_flush");
|
||||||
|
RESOLVE(get_pixel_from_texture_, OakBackendGetPixelFromTextureFn,
|
||||||
|
"oak_renderer_get_pixel_from_texture");
|
||||||
|
RESOLVE(blit_, OakBackendBlitFn, "oak_renderer_blit");
|
||||||
|
RESOLVE(opengl_context_, OakBackendOpenGLContextFn,
|
||||||
|
"oak_renderer_opengl_context");
|
||||||
|
#undef RESOLVE
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DynamicRenderer::Init()
|
||||||
|
{
|
||||||
|
return Load() && init_(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DynamicRenderer::InitWithOpenGLContext(QOpenGLContext *context)
|
||||||
|
{
|
||||||
|
if (!Load()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
init_with_context_(handle_, context);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::PostDestroy()
|
||||||
|
{
|
||||||
|
if (handle_ && post_destroy_) {
|
||||||
|
post_destroy_(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::PostInit()
|
||||||
|
{
|
||||||
|
if (handle_) {
|
||||||
|
post_init_(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::ClearDestination(Texture *texture, double r, double g,
|
||||||
|
double b, double a)
|
||||||
|
{
|
||||||
|
clear_destination_(handle_, texture, r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DynamicRenderer::CreateNativeShader(ShaderCode code)
|
||||||
|
{
|
||||||
|
QVariant out;
|
||||||
|
create_native_shader_(handle_, &code, &out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::DestroyNativeShader(QVariant shader)
|
||||||
|
{
|
||||||
|
destroy_native_shader_(handle_, &shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::UploadToTexture(const QVariant &handle,
|
||||||
|
const VideoParams ¶ms, const void *data,
|
||||||
|
int linesize)
|
||||||
|
{
|
||||||
|
upload_to_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::DownloadFromTexture(const QVariant &handle,
|
||||||
|
const VideoParams ¶ms, void *data,
|
||||||
|
int linesize)
|
||||||
|
{
|
||||||
|
download_from_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::Flush()
|
||||||
|
{
|
||||||
|
flush_(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color DynamicRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
||||||
|
{
|
||||||
|
Color out;
|
||||||
|
get_pixel_from_texture_(handle_, texture, &pt, &out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QOpenGLContext *DynamicRenderer::OpenGLContext() const
|
||||||
|
{
|
||||||
|
return opengl_context_ && handle_
|
||||||
|
? static_cast<QOpenGLContext *>(opengl_context_(handle_))
|
||||||
|
: nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
|
||||||
|
Texture *destination, VideoParams destination_params,
|
||||||
|
bool clear_destination)
|
||||||
|
{
|
||||||
|
blit_(handle_, &shader, &job, destination, &destination_params,
|
||||||
|
clear_destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant DynamicRenderer::CreateNativeTexture(int width, int height, int depth,
|
||||||
|
PixelFormat format, int channel_count,
|
||||||
|
const void *data, int linesize)
|
||||||
|
{
|
||||||
|
QVariant out;
|
||||||
|
create_native_texture_(handle_, width, height, depth, format, channel_count,
|
||||||
|
data, linesize, &out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::DestroyNativeTexture(QVariant texture)
|
||||||
|
{
|
||||||
|
destroy_native_texture_(handle_, &texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DynamicRenderer::DestroyInternal()
|
||||||
|
{
|
||||||
|
if (handle_) {
|
||||||
|
destroy_internal_(handle_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#ifndef DYNAMICRENDERER_H
|
||||||
|
#define DYNAMICRENDERER_H
|
||||||
|
|
||||||
|
#include <QLibrary>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "render/backend/renderbackend_c.h"
|
||||||
|
#include "render/opengl/openglcontextprovider.h"
|
||||||
|
#include "render/renderer.h"
|
||||||
|
|
||||||
|
namespace olive
|
||||||
|
{
|
||||||
|
|
||||||
|
class DynamicRenderer : public Renderer, public OpenGLContextProvider {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit DynamicRenderer(const QString &backend, QObject *parent = nullptr);
|
||||||
|
virtual ~DynamicRenderer() override;
|
||||||
|
|
||||||
|
bool Load();
|
||||||
|
bool InitWithOpenGLContext(QOpenGLContext *context);
|
||||||
|
|
||||||
|
virtual bool Init() override;
|
||||||
|
virtual void PostDestroy() override;
|
||||||
|
virtual void PostInit() override;
|
||||||
|
virtual void ClearDestination(Texture *texture = nullptr,
|
||||||
|
double r = 0.0, double g = 0.0,
|
||||||
|
double b = 0.0, double a = 0.0) override;
|
||||||
|
virtual QVariant CreateNativeShader(ShaderCode code) override;
|
||||||
|
virtual void DestroyNativeShader(QVariant shader) override;
|
||||||
|
virtual void UploadToTexture(const QVariant &handle,
|
||||||
|
const VideoParams ¶ms, const void *data,
|
||||||
|
int linesize) override;
|
||||||
|
virtual void DownloadFromTexture(const QVariant &handle,
|
||||||
|
const VideoParams ¶ms, void *data,
|
||||||
|
int linesize) override;
|
||||||
|
virtual void Flush() override;
|
||||||
|
virtual Color GetPixelFromTexture(Texture *texture,
|
||||||
|
const QPointF &pt) override;
|
||||||
|
virtual QOpenGLContext *OpenGLContext() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void Blit(QVariant shader, AcceleratedJob &job,
|
||||||
|
Texture *destination, VideoParams destination_params,
|
||||||
|
bool clear_destination) override;
|
||||||
|
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
||||||
|
PixelFormat format, int channel_count,
|
||||||
|
const void *data = nullptr,
|
||||||
|
int linesize = 0) override;
|
||||||
|
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||||
|
virtual void DestroyInternal() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ResolveFunctions();
|
||||||
|
QString LibraryFilename() const;
|
||||||
|
|
||||||
|
QString backend_;
|
||||||
|
QLibrary library_;
|
||||||
|
OakRenderBackendHandle handle_ = nullptr;
|
||||||
|
|
||||||
|
OakBackendCreateFn create_ = nullptr;
|
||||||
|
OakBackendDestroyFn destroy_ = nullptr;
|
||||||
|
OakBackendInitFn init_ = nullptr;
|
||||||
|
OakBackendInitWithContextFn init_with_context_ = nullptr;
|
||||||
|
OakBackendPostInitFn post_init_ = nullptr;
|
||||||
|
OakBackendPostDestroyFn post_destroy_ = nullptr;
|
||||||
|
OakBackendDestroyInternalFn destroy_internal_ = nullptr;
|
||||||
|
OakBackendClearDestinationFn clear_destination_ = nullptr;
|
||||||
|
OakBackendCreateNativeTextureFn create_native_texture_ = nullptr;
|
||||||
|
OakBackendDestroyNativeTextureFn destroy_native_texture_ = nullptr;
|
||||||
|
OakBackendCreateNativeShaderFn create_native_shader_ = nullptr;
|
||||||
|
OakBackendDestroyNativeShaderFn destroy_native_shader_ = nullptr;
|
||||||
|
OakBackendUploadToTextureFn upload_to_texture_ = nullptr;
|
||||||
|
OakBackendDownloadFromTextureFn download_from_texture_ = nullptr;
|
||||||
|
OakBackendFlushFn flush_ = nullptr;
|
||||||
|
OakBackendGetPixelFromTextureFn get_pixel_from_texture_ = nullptr;
|
||||||
|
OakBackendBlitFn blit_ = nullptr;
|
||||||
|
OakBackendOpenGLContextFn opengl_context_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DYNAMICRENDERER_H
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef RENDERBACKEND_C_H
|
||||||
|
#define RENDERBACKEND_C_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define OAK_RENDER_BACKEND_EXPORT extern "C" __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define OAK_RENDER_BACKEND_EXPORT extern "C" __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void *OakRenderBackendHandle;
|
||||||
|
|
||||||
|
typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent);
|
||||||
|
typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle,
|
||||||
|
void *context);
|
||||||
|
typedef void (*OakBackendPostInitFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef void (*OakBackendPostDestroyFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef void (*OakBackendDestroyInternalFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef void (*OakBackendClearDestinationFn)(OakRenderBackendHandle handle,
|
||||||
|
void *texture, double r, double g,
|
||||||
|
double b, double a);
|
||||||
|
typedef void (*OakBackendCreateNativeTextureFn)(OakRenderBackendHandle handle,
|
||||||
|
int width, int height, int depth,
|
||||||
|
int format, int channel_count,
|
||||||
|
const void *data, int linesize,
|
||||||
|
void *out_variant);
|
||||||
|
typedef void (*OakBackendDestroyNativeTextureFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *variant);
|
||||||
|
typedef void (*OakBackendCreateNativeShaderFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *shader_code,
|
||||||
|
void *out_variant);
|
||||||
|
typedef void (*OakBackendDestroyNativeShaderFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *variant);
|
||||||
|
typedef void (*OakBackendUploadToTextureFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *variant,
|
||||||
|
const void *video_params,
|
||||||
|
const void *data, int linesize);
|
||||||
|
typedef void (*OakBackendDownloadFromTextureFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *variant,
|
||||||
|
const void *video_params,
|
||||||
|
void *data, int linesize);
|
||||||
|
typedef void (*OakBackendFlushFn)(OakRenderBackendHandle handle);
|
||||||
|
typedef void (*OakBackendGetPixelFromTextureFn)(OakRenderBackendHandle handle,
|
||||||
|
void *texture, const void *point,
|
||||||
|
void *out_color);
|
||||||
|
typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
|
||||||
|
const void *shader, void *job,
|
||||||
|
void *destination,
|
||||||
|
const void *destination_params,
|
||||||
|
bool clear_destination);
|
||||||
|
typedef void *(*OakBackendOpenGLContextFn)(OakRenderBackendHandle handle);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // RENDERBACKEND_C_H
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
#include "render/backend/renderbackend_c.h"
|
||||||
|
|
||||||
|
#include <QOpenGLContext>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "render/job/acceleratedjob.h"
|
||||||
|
#include "render/opengl/openglrenderer.h"
|
||||||
|
#include "render/shadercode.h"
|
||||||
|
#include "render/texture.h"
|
||||||
|
#include "render/videoparams.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class BackendOpenGLRenderer : public olive::OpenGLRenderer {
|
||||||
|
public:
|
||||||
|
using olive::OpenGLRenderer::OpenGLRenderer;
|
||||||
|
using olive::OpenGLRenderer::Blit;
|
||||||
|
using olive::OpenGLRenderer::CreateNativeTexture;
|
||||||
|
using olive::OpenGLRenderer::DestroyInternal;
|
||||||
|
using olive::OpenGLRenderer::DestroyNativeTexture;
|
||||||
|
};
|
||||||
|
|
||||||
|
BackendOpenGLRenderer *Renderer(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
return static_cast<BackendOpenGLRenderer *>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariant &VariantRef(const void *variant)
|
||||||
|
{
|
||||||
|
return *static_cast<const QVariant *>(variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
||||||
|
{
|
||||||
|
return new BackendOpenGLRenderer(static_cast<QObject *>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
delete Renderer(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
return Renderer(handle)->Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
||||||
|
OakRenderBackendHandle handle, void *context)
|
||||||
|
{
|
||||||
|
Renderer(handle)->Init(static_cast<QOpenGLContext *>(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
Renderer(handle)->PostInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
Renderer(handle)->PostDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
||||||
|
OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
Renderer(handle)->DestroyInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
||||||
|
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
||||||
|
double a)
|
||||||
|
{
|
||||||
|
Renderer(handle)->ClearDestination(static_cast<olive::Texture *>(texture),
|
||||||
|
r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeTexture(
|
||||||
|
width, height, depth, static_cast<olive::PixelFormat::Format>(format),
|
||||||
|
channel_count, data, linesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
||||||
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
|
{
|
||||||
|
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
||||||
|
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
||||||
|
{
|
||||||
|
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeShader(
|
||||||
|
*static_cast<const olive::ShaderCode *>(shader_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
||||||
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
|
{
|
||||||
|
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
||||||
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
|
const void *data, int linesize)
|
||||||
|
{
|
||||||
|
Renderer(handle)->UploadToTexture(
|
||||||
|
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
|
||||||
|
data, linesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
||||||
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
|
void *data, int linesize)
|
||||||
|
{
|
||||||
|
Renderer(handle)->DownloadFromTexture(
|
||||||
|
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
|
||||||
|
data, linesize);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
Renderer(handle)->Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
||||||
|
OakRenderBackendHandle handle, void *texture, const void *point,
|
||||||
|
void *out_color)
|
||||||
|
{
|
||||||
|
*static_cast<olive::Color *>(out_color) = Renderer(handle)->GetPixelFromTexture(
|
||||||
|
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
||||||
|
OakRenderBackendHandle handle, const void *shader, void *job,
|
||||||
|
void *destination, const void *destination_params, bool clear_destination)
|
||||||
|
{
|
||||||
|
Renderer(handle)->Blit(
|
||||||
|
VariantRef(shader), *static_cast<olive::AcceleratedJob *>(job),
|
||||||
|
static_cast<olive::Texture *>(destination),
|
||||||
|
*static_cast<const olive::VideoParams *>(destination_params),
|
||||||
|
clear_destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||||
|
OakRenderBackendHandle handle)
|
||||||
|
{
|
||||||
|
return Renderer(handle)->context();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef OPENGLCONTEXTPROVIDER_H
|
||||||
|
#define OPENGLCONTEXTPROVIDER_H
|
||||||
|
|
||||||
|
class QOpenGLContext;
|
||||||
|
|
||||||
|
namespace olive
|
||||||
|
{
|
||||||
|
|
||||||
|
class OpenGLContextProvider {
|
||||||
|
public:
|
||||||
|
virtual ~OpenGLContextProvider() = default;
|
||||||
|
virtual QOpenGLContext *OpenGLContext() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // OPENGLCONTEXTPROVIDER_H
|
||||||
@@ -30,12 +30,13 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "render/opengl/openglcontextprovider.h"
|
||||||
#include "render/renderer.h"
|
#include "render/renderer.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
|
|
||||||
class OpenGLRenderer : public Renderer {
|
class OpenGLRenderer : public Renderer, public OpenGLContextProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OpenGLRenderer(QObject *parent = nullptr);
|
OpenGLRenderer(QObject *parent = nullptr);
|
||||||
@@ -76,6 +77,11 @@ public:
|
|||||||
return context_;
|
return context_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual QOpenGLContext *OpenGLContext() const override
|
||||||
|
{
|
||||||
|
return context();
|
||||||
|
}
|
||||||
|
|
||||||
bool EnsureContextCurrent(const char *caller);
|
bool EnsureContextCurrent(const char *caller);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -27,6 +27,9 @@
|
|||||||
|
|
||||||
#include "config/config.h"
|
#include "config/config.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
|
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
|
#include "render/backend/dynamicrenderer.h"
|
||||||
|
#endif
|
||||||
#include "render/opengl/openglrenderer.h"
|
#include "render/opengl/openglrenderer.h"
|
||||||
#include "renderprocessor.h"
|
#include "renderprocessor.h"
|
||||||
#include "renderworkerpool.h"
|
#include "renderworkerpool.h"
|
||||||
@@ -88,7 +91,11 @@ RenderManager::RenderManager(QObject *parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (backend_ == kOpenGL) {
|
if (backend_ == kOpenGL) {
|
||||||
|
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
|
context_ = new DynamicRenderer(BackendToString(requested_backend_));
|
||||||
|
#else
|
||||||
context_ = new OpenGLRenderer();
|
context_ = new OpenGLRenderer();
|
||||||
|
#endif
|
||||||
decoder_cache_ = new DecoderCache();
|
decoder_cache_ = new DecoderCache();
|
||||||
shader_cache_ = new ShaderCache();
|
shader_cache_ = new ShaderCache();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
|
#include <QThreadStorage>
|
||||||
#include <QVector2D>
|
#include <QVector2D>
|
||||||
#include <QVector3D>
|
#include <QVector3D>
|
||||||
#include <QVector4D>
|
#include <QVector4D>
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
#include "node/block/transition/transition.h"
|
#include "node/block/transition/transition.h"
|
||||||
#include "node/project.h"
|
#include "node/project.h"
|
||||||
#include "rendermanager.h"
|
#include "rendermanager.h"
|
||||||
|
#include "render/opengl/openglcontextprovider.h"
|
||||||
#include "render/opengl/openglrenderer.h"
|
#include "render/opengl/openglrenderer.h"
|
||||||
#include "render/plugin/pluginrenderer.h"
|
#include "render/plugin/pluginrenderer.h"
|
||||||
#include "pluginSupport/OliveClip.h"
|
#include "pluginSupport/OliveClip.h"
|
||||||
@@ -711,18 +713,21 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
|
|||||||
|
|
||||||
plugin::PluginRenderer *plugin_renderer = nullptr;
|
plugin::PluginRenderer *plugin_renderer = nullptr;
|
||||||
{
|
{
|
||||||
thread_local static std::shared_ptr<plugin::PluginRenderer>
|
static QThreadStorage<std::shared_ptr<plugin::PluginRenderer>>
|
||||||
cached_plugin_renderer;
|
cached_plugin_renderers;
|
||||||
if (!cached_plugin_renderer) {
|
if (!cached_plugin_renderers.hasLocalData()) {
|
||||||
auto *gl = dynamic_cast<OpenGLRenderer *>(render_ctx_);
|
auto *gl = dynamic_cast<OpenGLContextProvider *>(render_ctx_);
|
||||||
if (gl && gl->context()) {
|
if (gl && gl->OpenGLContext()) {
|
||||||
cached_plugin_renderer =
|
auto cached_plugin_renderer =
|
||||||
std::make_shared<plugin::PluginRenderer>();
|
std::make_shared<plugin::PluginRenderer>();
|
||||||
cached_plugin_renderer->Init(gl->context());
|
cached_plugin_renderer->Init(gl->OpenGLContext());
|
||||||
cached_plugin_renderer->PostInit();
|
cached_plugin_renderer->PostInit();
|
||||||
|
cached_plugin_renderers.setLocalData(cached_plugin_renderer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugin_renderer = cached_plugin_renderer.get();
|
if (cached_plugin_renderers.hasLocalData()) {
|
||||||
|
plugin_renderer = cached_plugin_renderers.localData().get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!plugin_renderer) {
|
if (!plugin_renderer) {
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# 动态渲染后端拆分计划
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
将当前强绑定 OpenGL 的渲染实现拆成可动态加载的后端库,使主程序只依赖一个轻量适配器:
|
||||||
|
|
||||||
|
- OpenGL 后端封装到私有动态库。
|
||||||
|
- Vulkan 后端封装到独立动态库。
|
||||||
|
- 后端库内部继续使用 C++ 实现。
|
||||||
|
- 后端库对外只导出 C ABI。
|
||||||
|
- C ABI 使用不透明 handle 表示 C++ 对象。
|
||||||
|
- 每个 C 函数对应一个后端类成员函数。
|
||||||
|
- 构造函数导出为特殊 create 函数,析构函数导出为特殊 destroy 函数。
|
||||||
|
- 主程序适配器构造时按配置显式加载后端库并调用 create/init,析构时调用 destroy 并卸载库。
|
||||||
|
|
||||||
|
## 命名约束
|
||||||
|
|
||||||
|
用户期望后端名为 `libgl.so` 和 `libvulkan.so`。Linux 系统上 `libGL.so`/`libgl.so` 容易和系统 OpenGL loader 混淆,因此工程实现应优先使用私有库名或私有目录,例如:
|
||||||
|
|
||||||
|
- `liboakgl.so`
|
||||||
|
- `liboakvulkan.so`
|
||||||
|
- 或 `render_backends/libgl.so`、`render_backends/libvulkan.so`
|
||||||
|
|
||||||
|
适配器只从 Oak 私有后端目录查找,避免加载到系统图形库。
|
||||||
|
|
||||||
|
## 阶段 1:OpenGL 动态后端骨架
|
||||||
|
|
||||||
|
- 新增稳定 C ABI 头:`app/render/backend/renderbackend_c.h`。
|
||||||
|
- 新增 `DynamicRenderer` 适配器,继承现有 `Renderer`,内部用 `QLibrary` 加载后端。
|
||||||
|
- 将现有 `OpenGLRenderer` 包装成 OpenGL 后端导出函数。
|
||||||
|
- `RenderManager` 按 `GraphicsBackend` 选择加载 OpenGL 或 Vulkan 后端。
|
||||||
|
- 在 Vulkan 后端未实现前,请求 Vulkan 时加载占位后端或回退 OpenGL,并记录明确 warning。
|
||||||
|
|
||||||
|
## 阶段 2:两层适配器 ABI
|
||||||
|
|
||||||
|
本计划不是把 OpenGL 代码用 C 重写。动态库一侧继续保留现有 C++ `OpenGLRenderer`/未来 `VulkanRenderer` 实现,只在导出边界增加一层 C wrapper;主程序和渲染进程一侧再用 `DynamicRenderer` 把 C 函数封回 C++ `Renderer` 接口。
|
||||||
|
|
||||||
|
第一阶段 C ABI 可以用 `void *` 承载现有 C++ 对象指针,例如 `QVariant`、`VideoParams`、`ShaderCode`、`Texture`、`AcceleratedJob`。C 函数内部只做类型转换并调用对应 C++ 成员函数。这样两侧代码都不用大改,但有一个前提:后端库和主程序必须用同一套头文件、编译器 ABI 和 Qt/FFmpeg/OpenFX 依赖构建。
|
||||||
|
|
||||||
|
长期要把 ABI 稳定下来时,再逐步引入更明确的 C 结构,避免跨库暴露 Qt/C++ 类型:
|
||||||
|
|
||||||
|
- texture handle:`OakBackendTextureHandle`
|
||||||
|
- shader handle:`OakBackendShaderHandle`
|
||||||
|
- video params C struct:宽、高、depth、pixel format、channel count、linesize
|
||||||
|
- shader code C struct:vertex/fragment 字符串
|
||||||
|
- blit job C struct:输入 texture handle、uniform 数组、输出 texture handle
|
||||||
|
- readback/upload 使用裸指针和 stride
|
||||||
|
|
||||||
|
这一步是 ABI 稳定化,不是把后端内部实现改成 C。
|
||||||
|
|
||||||
|
## 阶段 2.5:最小化 OpenGL 后端链接边界
|
||||||
|
|
||||||
|
当前工程大量代码和静态依赖被编译进 `libolive-editor` object 库。直接把整个 object 库塞进 `liboakgl.so` 会带来两个问题:
|
||||||
|
|
||||||
|
- 非 PIC 静态依赖会阻塞共享库链接,例如 `KDDockWidgets`。
|
||||||
|
- 主程序和后端库会复制全局状态,增加配置、cache、单例和 Qt meta-object 的一致性风险。
|
||||||
|
|
||||||
|
因此动态后端接管默认 renderer 前,需要把 OpenGL 后端库的链接边界收敛到最小集合:
|
||||||
|
|
||||||
|
- 后端库只拥有 OpenGL/Vulkan native 操作和必要的后端私有状态。
|
||||||
|
- 主程序侧保留项目、节点、任务、cache、OpenFX host 等 editor 状态。
|
||||||
|
- 如果某个后端函数需要主程序创建 `Texture` 或访问 cache,用 C callback table 从后端回调主程序,而不是把完整 editor 链进后端库。
|
||||||
|
- `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 打开前必须完成最小链接边界拆分并通过 CI。
|
||||||
|
|
||||||
|
## 阶段 3:Vulkan 后端
|
||||||
|
|
||||||
|
- 新增 Vulkan 后端库。
|
||||||
|
- 实现 Vulkan instance/device/swapchain 或 offscreen image 管理。
|
||||||
|
- 实现 texture 创建、上传、下载、shader 编译/缓存、blit、clear、flush。
|
||||||
|
- 提供 OpenGL 辅助格式转换函数的 Vulkan 替代版本。
|
||||||
|
- 确认 CPU readback、proxy preview、scope、thumbnail/cache 输出一致。
|
||||||
|
|
||||||
|
## 阶段 4:Viewer 双后端
|
||||||
|
|
||||||
|
- 当前 viewer display 基于 OpenGL widget 和 GL texture id。
|
||||||
|
- 新增 backend-neutral viewer path。
|
||||||
|
- OpenGL 使用现有 `QOpenGLWidget/QOpenGLWindow`。
|
||||||
|
- Vulkan 使用 `QVulkanWindow` 或 Qt RHI/QRhi 显示路径。
|
||||||
|
- Viewer 只消费后端 texture handle 或 readback frame,不直接假设 GL texture id。
|
||||||
|
|
||||||
|
## 阶段 5:OpenFX 处理边界
|
||||||
|
|
||||||
|
- OpenFX 插件 OpenGL 渲染路径保留 OpenGL 依赖,不强行改写。
|
||||||
|
- 格式转换、readback、upload 等辅助函数移入后端库。
|
||||||
|
- Vulkan 后端提供等价辅助函数。
|
||||||
|
- 如果某个 OFX 插件必须走 OpenGL,则在 Vulkan 项目里明确使用 OpenGL 兼容路径或回退。
|
||||||
|
|
||||||
|
## 完成标准
|
||||||
|
|
||||||
|
- 主程序不直接 new `OpenGLRenderer` 作为默认渲染路径,而是通过动态后端适配器创建。
|
||||||
|
- OpenGL 后端库可单独构建、加载、初始化、销毁。
|
||||||
|
- 用户能在配置中选择 OpenGL/Vulkan。
|
||||||
|
- Vulkan 未实现完整 renderer 前,请求 Vulkan 不崩溃,并有明确回退。
|
||||||
|
- 手工测试计划覆盖 OpenGL/Vulkan 选择、重启持久化、回退、viewer、proxy、scope、导出。
|
||||||
@@ -361,7 +361,7 @@
|
|||||||
|
|
||||||
## 10. 图形后端选择测试
|
## 10. 图形后端选择测试
|
||||||
|
|
||||||
当前版本允许用户在 Preferences 中选择 OpenGL 或 Vulkan。注意:Vulkan 入口目前是实验性图形 API 请求和后续 VulkanRenderer 的接入点;时间线/viewer 渲染仍应安全回退到现有 OpenGL renderer。因此测试重点是“用户可选择、设置可持久化、Vulkan 请求不破坏现有渲染、失败可回退”。
|
当前版本允许用户在 Preferences 中选择 OpenGL 或 Vulkan。注意:Vulkan 入口目前是实验性图形 API 请求和后续 VulkanRenderer 的接入点;默认构建下时间线/viewer 渲染仍应安全回退到现有 OpenGL renderer。动态后端适配器通过 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 实验开关接入,测试重点是“用户可选择、设置可持久化、Vulkan 请求不破坏现有渲染、失败可回退”。
|
||||||
|
|
||||||
### 10.1 默认 OpenGL 后端
|
### 10.1 默认 OpenGL 后端
|
||||||
|
|
||||||
@@ -411,6 +411,34 @@
|
|||||||
|
|
||||||
通过标准:Vulkan 请求状态下代理、Scope、调色不崩溃;切回 OpenGL 后项目状态一致;两种选择下导出默认仍使用原片。
|
通过标准:Vulkan 请求状态下代理、Scope、调色不崩溃;切回 OpenGL 后项目状态一致;两种选择下导出默认仍使用原片。
|
||||||
|
|
||||||
|
### 10.6 动态 OpenGL 后端加载
|
||||||
|
|
||||||
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
|
2. 确认应用目录存在 Oak 私有 OpenGL 后端库,例如 `liboakgl.so`、`liboakgl.dylib` 或 `oakgl.dll`。
|
||||||
|
3. 在 Preferences 中选择 OpenGL 并重启。
|
||||||
|
4. 导入 `color_chart.mov`,播放、拖动时间线并打开 Scope。
|
||||||
|
5. 关闭应用,确认退出过程没有崩溃。
|
||||||
|
|
||||||
|
通过标准:日志显示动态 OpenGL 后端加载成功;viewer、Scope、调色和播放行为与默认 OpenGL 路径一致;退出时执行 destroy/unload 无崩溃。
|
||||||
|
|
||||||
|
### 10.7 动态后端缺失或损坏
|
||||||
|
|
||||||
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
|
2. 临时移走或重命名 Oak 私有 OpenGL 后端库。
|
||||||
|
3. 启动 Oak 并打开一个已有项目。
|
||||||
|
4. 观察日志、Preferences 和播放行为。
|
||||||
|
|
||||||
|
通过标准:应用不能静默崩溃;日志明确说明后端库加载失败;用户能够恢复库文件或切回默认构建继续打开项目。
|
||||||
|
|
||||||
|
### 10.8 Vulkan 动态后端占位
|
||||||
|
|
||||||
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
|
2. 在 Preferences 中选择 Vulkan 并重启。
|
||||||
|
3. 如果 `liboakvulkan` 尚未实现,观察回退行为。
|
||||||
|
4. 切回 OpenGL 并重启。
|
||||||
|
|
||||||
|
通过标准:Vulkan 后端未实现时不崩溃;日志明确说明 Vulkan 后端缺失并回退或拒绝初始化;切回 OpenGL 后项目可播放。
|
||||||
|
|
||||||
## 缺陷记录模板
|
## 缺陷记录模板
|
||||||
|
|
||||||
每个失败项记录以下信息:
|
每个失败项记录以下信息:
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ TEST(OpenGLRenderer, DownloadFromTextureWithoutCurrentContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QOpenGLContext context;
|
QOpenGLContext context;
|
||||||
ASSERT_TRUE(context.create());
|
if (!context.create()) {
|
||||||
|
GTEST_SKIP() << "Skipping OpenGL test because no context can be created";
|
||||||
|
}
|
||||||
ASSERT_EQ(QOpenGLContext::currentContext(), nullptr);
|
ASSERT_EQ(QOpenGLContext::currentContext(), nullptr);
|
||||||
|
|
||||||
olive::OpenGLRenderer renderer;
|
olive::OpenGLRenderer renderer;
|
||||||
|
|||||||
Reference in New Issue
Block a user