add Comments
This commit is contained in:
@@ -9,12 +9,16 @@
|
|||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Stores the requested backend name; the actual backend may later become
|
||||||
|
// OpenGL if loading or availability checks require a Vulkan fallback.
|
||||||
DynamicRenderer::DynamicRenderer(const QString &backend, QObject *parent)
|
DynamicRenderer::DynamicRenderer(const QString &backend, QObject *parent)
|
||||||
: Renderer(parent)
|
: Renderer(parent)
|
||||||
, backend_(backend.toLower())
|
, backend_(backend.toLower())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tears down the backend in the reverse order used by Load(): release renderer
|
||||||
|
// resources, destroy the opaque backend object, then unload the shared library.
|
||||||
DynamicRenderer::~DynamicRenderer()
|
DynamicRenderer::~DynamicRenderer()
|
||||||
{
|
{
|
||||||
Destroy();
|
Destroy();
|
||||||
@@ -28,6 +32,9 @@ DynamicRenderer::~DynamicRenderer()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Builds the private backend library path for the current platform.
|
||||||
|
// The search is intentionally restricted to Oak-controlled directories so a
|
||||||
|
// system libGL/libvulkan loader is never mistaken for an Oak render backend.
|
||||||
QString DynamicRenderer::LibraryFilename() const
|
QString DynamicRenderer::LibraryFilename() const
|
||||||
{
|
{
|
||||||
const QString base = backend_ == QStringLiteral("vulkan")
|
const QString base = backend_ == QStringLiteral("vulkan")
|
||||||
@@ -58,6 +65,9 @@ QString DynamicRenderer::LibraryFilename() const
|
|||||||
return candidates.first();
|
return candidates.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loads the selected backend, resolves its C ABI table, creates the opaque
|
||||||
|
// backend object, and optionally falls back from Vulkan to OpenGL when runtime
|
||||||
|
// availability checks fail.
|
||||||
bool DynamicRenderer::Load()
|
bool DynamicRenderer::Load()
|
||||||
{
|
{
|
||||||
if (handle_) {
|
if (handle_) {
|
||||||
@@ -106,6 +116,8 @@ bool DynamicRenderer::Load()
|
|||||||
return handle_ != nullptr;
|
return handle_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolves the mandatory C ABI entry points from the loaded shared library.
|
||||||
|
// Optional information probes are resolved after the required render interface.
|
||||||
bool DynamicRenderer::ResolveFunctions()
|
bool DynamicRenderer::ResolveFunctions()
|
||||||
{
|
{
|
||||||
ResetFunctions();
|
ResetFunctions();
|
||||||
@@ -155,6 +167,8 @@ bool DynamicRenderer::ResolveFunctions()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discards a partially-created backend and restarts loading with the OpenGL
|
||||||
|
// backend. This keeps RenderManager's fallback path inside the adapter.
|
||||||
bool DynamicRenderer::FallbackToOpenGL()
|
bool DynamicRenderer::FallbackToOpenGL()
|
||||||
{
|
{
|
||||||
if (handle_ && destroy_) {
|
if (handle_ && destroy_) {
|
||||||
@@ -169,6 +183,8 @@ bool DynamicRenderer::FallbackToOpenGL()
|
|||||||
return Load();
|
return Load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears all cached C function pointers so a failed backend cannot leave stale
|
||||||
|
// call targets behind for a later fallback load.
|
||||||
void DynamicRenderer::ResetFunctions()
|
void DynamicRenderer::ResetFunctions()
|
||||||
{
|
{
|
||||||
create_ = nullptr;
|
create_ = nullptr;
|
||||||
@@ -195,16 +211,20 @@ void DynamicRenderer::ResetFunctions()
|
|||||||
opengl_context_ = nullptr;
|
opengl_context_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns backend metadata exposed by the dynamic library when available.
|
||||||
bool DynamicRenderer::GetBackendInfo(OakRenderBackendInfo *out_info) const
|
bool DynamicRenderer::GetBackendInfo(OakRenderBackendInfo *out_info) const
|
||||||
{
|
{
|
||||||
return handle_ && get_info_ && out_info && get_info_(handle_, out_info);
|
return handle_ && get_info_ && out_info && get_info_(handle_, out_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes the loaded backend using its own context/device creation path.
|
||||||
bool DynamicRenderer::Init()
|
bool DynamicRenderer::Init()
|
||||||
{
|
{
|
||||||
return Load() && init_(handle_);
|
return Load() && init_(handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes an OpenGL backend against an existing widget context; non-OpenGL
|
||||||
|
// backends may ignore the context on the library side.
|
||||||
bool DynamicRenderer::InitWithOpenGLContext(QOpenGLContext *context)
|
bool DynamicRenderer::InitWithOpenGLContext(QOpenGLContext *context)
|
||||||
{
|
{
|
||||||
if (!Load()) {
|
if (!Load()) {
|
||||||
@@ -214,6 +234,8 @@ bool DynamicRenderer::InitWithOpenGLContext(QOpenGLContext *context)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forwards post-destroy cleanup to the backend while the library is still
|
||||||
|
// loaded and its symbols are still valid.
|
||||||
void DynamicRenderer::PostDestroy()
|
void DynamicRenderer::PostDestroy()
|
||||||
{
|
{
|
||||||
if (handle_ && post_destroy_) {
|
if (handle_ && post_destroy_) {
|
||||||
@@ -221,6 +243,8 @@ void DynamicRenderer::PostDestroy()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs backend post-initialization after Init/InitWithOpenGLContext has
|
||||||
|
// established the device or GL context.
|
||||||
void DynamicRenderer::PostInit()
|
void DynamicRenderer::PostInit()
|
||||||
{
|
{
|
||||||
if (handle_) {
|
if (handle_) {
|
||||||
@@ -228,12 +252,15 @@ void DynamicRenderer::PostInit()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forwards render target clearing through the C ABI.
|
||||||
void DynamicRenderer::ClearDestination(Texture *texture, double r, double g,
|
void DynamicRenderer::ClearDestination(Texture *texture, double r, double g,
|
||||||
double b, double a)
|
double b, double a)
|
||||||
{
|
{
|
||||||
clear_destination_(handle_, texture, r, g, b, a);
|
clear_destination_(handle_, texture, r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a backend-native shader and receives the result as an opaque QVariant
|
||||||
|
// because this first-generation ABI still shares C++/Qt types between modules.
|
||||||
QVariant DynamicRenderer::CreateNativeShader(ShaderCode code)
|
QVariant DynamicRenderer::CreateNativeShader(ShaderCode code)
|
||||||
{
|
{
|
||||||
QVariant out;
|
QVariant out;
|
||||||
@@ -241,11 +268,13 @@ QVariant DynamicRenderer::CreateNativeShader(ShaderCode code)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases a backend-native shader handle.
|
||||||
void DynamicRenderer::DestroyNativeShader(QVariant shader)
|
void DynamicRenderer::DestroyNativeShader(QVariant shader)
|
||||||
{
|
{
|
||||||
destroy_native_shader_(handle_, &shader);
|
destroy_native_shader_(handle_, &shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uploads CPU pixel data into a backend texture through the dynamic ABI.
|
||||||
void DynamicRenderer::UploadToTexture(const QVariant &handle,
|
void DynamicRenderer::UploadToTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, const void *data,
|
const VideoParams ¶ms, const void *data,
|
||||||
int linesize)
|
int linesize)
|
||||||
@@ -253,6 +282,7 @@ void DynamicRenderer::UploadToTexture(const QVariant &handle,
|
|||||||
upload_to_texture_(handle_, &handle, ¶ms, data, linesize);
|
upload_to_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Downloads backend texture data into a caller-provided CPU buffer.
|
||||||
void DynamicRenderer::DownloadFromTexture(const QVariant &handle,
|
void DynamicRenderer::DownloadFromTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, void *data,
|
const VideoParams ¶ms, void *data,
|
||||||
int linesize)
|
int linesize)
|
||||||
@@ -260,11 +290,13 @@ void DynamicRenderer::DownloadFromTexture(const QVariant &handle,
|
|||||||
download_from_texture_(handle_, &handle, ¶ms, data, linesize);
|
download_from_texture_(handle_, &handle, ¶ms, data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Waits for backend work to become visible to subsequent CPU or GPU consumers.
|
||||||
void DynamicRenderer::Flush()
|
void DynamicRenderer::Flush()
|
||||||
{
|
{
|
||||||
flush_(handle_);
|
flush_(handle_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads a single pixel through the backend-provided readback hook.
|
||||||
Color DynamicRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
Color DynamicRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
||||||
{
|
{
|
||||||
Color out;
|
Color out;
|
||||||
@@ -272,6 +304,8 @@ Color DynamicRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exposes the wrapped OpenGL context when the backend is OpenGL; Vulkan returns
|
||||||
|
// null so callers can avoid GL-only paths.
|
||||||
QOpenGLContext *DynamicRenderer::OpenGLContext() const
|
QOpenGLContext *DynamicRenderer::OpenGLContext() const
|
||||||
{
|
{
|
||||||
return opengl_context_ && handle_
|
return opengl_context_ && handle_
|
||||||
@@ -279,11 +313,13 @@ QOpenGLContext *DynamicRenderer::OpenGLContext() const
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reports the effective backend after any load-time fallback has completed.
|
||||||
bool DynamicRenderer::IsOpenGL() const
|
bool DynamicRenderer::IsOpenGL() const
|
||||||
{
|
{
|
||||||
return backend_ == QStringLiteral("opengl");
|
return backend_ == QStringLiteral("opengl");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dispatches a shader blit to the loaded backend.
|
||||||
void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
|
void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
|
||||||
Texture *destination, VideoParams destination_params,
|
Texture *destination, VideoParams destination_params,
|
||||||
bool clear_destination)
|
bool clear_destination)
|
||||||
@@ -292,6 +328,7 @@ void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
|
|||||||
clear_destination);
|
clear_destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocates a backend-native texture and wraps its opaque handle in QVariant.
|
||||||
QVariant DynamicRenderer::CreateNativeTexture(int width, int height, int depth,
|
QVariant DynamicRenderer::CreateNativeTexture(int width, int height, int depth,
|
||||||
PixelFormat format, int channel_count,
|
PixelFormat format, int channel_count,
|
||||||
const void *data, int linesize)
|
const void *data, int linesize)
|
||||||
@@ -302,11 +339,14 @@ QVariant DynamicRenderer::CreateNativeTexture(int width, int height, int depth,
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases a backend-native texture handle.
|
||||||
void DynamicRenderer::DestroyNativeTexture(QVariant texture)
|
void DynamicRenderer::DestroyNativeTexture(QVariant texture)
|
||||||
{
|
{
|
||||||
destroy_native_texture_(handle_, &texture);
|
destroy_native_texture_(handle_, &texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases renderer-owned backend resources before the backend object itself is
|
||||||
|
// destroyed.
|
||||||
void DynamicRenderer::DestroyInternal()
|
void DynamicRenderer::DestroyInternal()
|
||||||
{
|
{
|
||||||
if (handle_) {
|
if (handle_) {
|
||||||
@@ -314,6 +354,7 @@ void DynamicRenderer::DestroyInternal()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exposes OFX OpenGL output binding through the dynamic backend when supported.
|
||||||
void DynamicRenderer::AttachOutputTexture(Texture *texture)
|
void DynamicRenderer::AttachOutputTexture(Texture *texture)
|
||||||
{
|
{
|
||||||
if (attach_output_texture_ && texture) {
|
if (attach_output_texture_ && texture) {
|
||||||
@@ -322,6 +363,7 @@ void DynamicRenderer::AttachOutputTexture(Texture *texture)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears any OFX output texture binding owned by the backend.
|
||||||
void DynamicRenderer::DetachOutputTexture()
|
void DynamicRenderer::DetachOutputTexture()
|
||||||
{
|
{
|
||||||
if (detach_output_texture_) {
|
if (detach_output_texture_) {
|
||||||
|
|||||||
@@ -11,62 +11,92 @@
|
|||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// C++ Renderer adapter that loads an Oak render backend shared library and
|
||||||
|
// forwards Renderer calls through the backend's C ABI.
|
||||||
class DynamicRenderer : public Renderer, public OpenGLContextProvider {
|
class DynamicRenderer : public Renderer, public OpenGLContextProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
// Stores the requested backend name; Load() may change it after fallback.
|
||||||
explicit DynamicRenderer(const QString &backend, QObject *parent = nullptr);
|
explicit DynamicRenderer(const QString &backend, QObject *parent = nullptr);
|
||||||
|
// Destroys backend resources and unloads the dynamic library.
|
||||||
virtual ~DynamicRenderer() override;
|
virtual ~DynamicRenderer() override;
|
||||||
|
|
||||||
using Renderer::Blit;
|
using Renderer::Blit;
|
||||||
|
|
||||||
|
// Loads the backend library, resolves C ABI symbols, and creates the handle.
|
||||||
bool Load();
|
bool Load();
|
||||||
|
// Initializes an OpenGL backend with a caller-owned viewer context.
|
||||||
bool InitWithOpenGLContext(QOpenGLContext *context);
|
bool InitWithOpenGLContext(QOpenGLContext *context);
|
||||||
|
// Retrieves backend metadata through the optional info entry point.
|
||||||
bool GetBackendInfo(OakRenderBackendInfo *out_info) const;
|
bool GetBackendInfo(OakRenderBackendInfo *out_info) const;
|
||||||
|
// Returns the effective backend after any load-time fallback.
|
||||||
QString backend_name() const
|
QString backend_name() const
|
||||||
{
|
{
|
||||||
return backend_;
|
return backend_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes the backend using its default device/context path.
|
||||||
virtual bool Init() override;
|
virtual bool Init() override;
|
||||||
|
// Runs backend post-destroy cleanup.
|
||||||
virtual void PostDestroy() override;
|
virtual void PostDestroy() override;
|
||||||
|
// Runs backend post-init setup.
|
||||||
virtual void PostInit() override;
|
virtual void PostInit() override;
|
||||||
|
// Clears either a native texture destination or the backend output target.
|
||||||
virtual void ClearDestination(Texture *texture = nullptr,
|
virtual void ClearDestination(Texture *texture = nullptr,
|
||||||
double r = 0.0, double g = 0.0,
|
double r = 0.0, double g = 0.0,
|
||||||
double b = 0.0, double a = 0.0) override;
|
double b = 0.0, double a = 0.0) override;
|
||||||
|
// Creates a native shader through the dynamic backend.
|
||||||
virtual QVariant CreateNativeShader(ShaderCode code) override;
|
virtual QVariant CreateNativeShader(ShaderCode code) override;
|
||||||
|
// Destroys a native shader through the dynamic backend.
|
||||||
virtual void DestroyNativeShader(QVariant shader) override;
|
virtual void DestroyNativeShader(QVariant shader) override;
|
||||||
|
// Uploads CPU pixels to a backend texture.
|
||||||
virtual void UploadToTexture(const QVariant &handle,
|
virtual void UploadToTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, const void *data,
|
const VideoParams ¶ms, const void *data,
|
||||||
int linesize) override;
|
int linesize) override;
|
||||||
|
// Downloads backend texture pixels to CPU memory.
|
||||||
virtual void DownloadFromTexture(const QVariant &handle,
|
virtual void DownloadFromTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, void *data,
|
const VideoParams ¶ms, void *data,
|
||||||
int linesize) override;
|
int linesize) override;
|
||||||
|
// Waits for backend work to complete.
|
||||||
virtual void Flush() override;
|
virtual void Flush() override;
|
||||||
|
// Reads one pixel from a backend texture.
|
||||||
virtual Color GetPixelFromTexture(Texture *texture,
|
virtual Color GetPixelFromTexture(Texture *texture,
|
||||||
const QPointF &pt) override;
|
const QPointF &pt) override;
|
||||||
|
// Returns the wrapped OpenGL context for OpenGL backends.
|
||||||
virtual QOpenGLContext *OpenGLContext() const override;
|
virtual QOpenGLContext *OpenGLContext() const override;
|
||||||
|
|
||||||
|
// Reports whether the effective backend is OpenGL.
|
||||||
virtual bool IsOpenGL() const override;
|
virtual bool IsOpenGL() const override;
|
||||||
|
|
||||||
|
// Attaches a texture for OFX OpenGL output when supported.
|
||||||
virtual void AttachOutputTexture(Texture *texture) override;
|
virtual void AttachOutputTexture(Texture *texture) override;
|
||||||
|
|
||||||
|
// Detaches any OFX output texture binding when supported.
|
||||||
virtual void DetachOutputTexture() override;
|
virtual void DetachOutputTexture() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Dispatches a shader blit through the dynamic backend.
|
||||||
virtual void Blit(QVariant shader, AcceleratedJob &job,
|
virtual void Blit(QVariant shader, AcceleratedJob &job,
|
||||||
Texture *destination, VideoParams destination_params,
|
Texture *destination, VideoParams destination_params,
|
||||||
bool clear_destination) override;
|
bool clear_destination) override;
|
||||||
|
// Allocates a native texture through the dynamic backend.
|
||||||
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
||||||
PixelFormat format, int channel_count,
|
PixelFormat format, int channel_count,
|
||||||
const void *data = nullptr,
|
const void *data = nullptr,
|
||||||
int linesize = 0) override;
|
int linesize = 0) override;
|
||||||
|
// Releases a native texture through the dynamic backend.
|
||||||
virtual void DestroyNativeTexture(QVariant texture) override;
|
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||||
|
// Releases backend-owned renderer resources.
|
||||||
virtual void DestroyInternal() override;
|
virtual void DestroyInternal() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Resolves required backend C ABI symbols.
|
||||||
bool ResolveFunctions();
|
bool ResolveFunctions();
|
||||||
|
// Replaces a failed Vulkan backend with OpenGL.
|
||||||
bool FallbackToOpenGL();
|
bool FallbackToOpenGL();
|
||||||
|
// Clears all cached function pointers.
|
||||||
void ResetFunctions();
|
void ResetFunctions();
|
||||||
|
// Resolves the private backend library path.
|
||||||
QString LibraryFilename() const;
|
QString LibraryFilename() const;
|
||||||
|
|
||||||
QString backend_;
|
QString backend_;
|
||||||
|
|||||||
@@ -14,14 +14,17 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Opaque pointer to the backend-owned C++ renderer object. */
|
||||||
typedef void *OakRenderBackendHandle;
|
typedef void *OakRenderBackendHandle;
|
||||||
|
|
||||||
|
/* Identifies the concrete backend behind a dynamically loaded library. */
|
||||||
enum OakRenderBackendKind {
|
enum OakRenderBackendKind {
|
||||||
OAK_RENDER_BACKEND_UNKNOWN = 0,
|
OAK_RENDER_BACKEND_UNKNOWN = 0,
|
||||||
OAK_RENDER_BACKEND_OPENGL = 1,
|
OAK_RENDER_BACKEND_OPENGL = 1,
|
||||||
OAK_RENDER_BACKEND_VULKAN = 2
|
OAK_RENDER_BACKEND_VULKAN = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Capability bits advertised by a backend through oak_renderer_get_info(). */
|
||||||
enum OakRenderBackendCapability {
|
enum OakRenderBackendCapability {
|
||||||
OAK_RENDER_BACKEND_CAP_TEXTURES = 1ULL << 0,
|
OAK_RENDER_BACKEND_CAP_TEXTURES = 1ULL << 0,
|
||||||
OAK_RENDER_BACKEND_CAP_SHADERS = 1ULL << 1,
|
OAK_RENDER_BACKEND_CAP_SHADERS = 1ULL << 1,
|
||||||
@@ -32,6 +35,7 @@ enum OakRenderBackendCapability {
|
|||||||
OAK_RENDER_BACKEND_CAP_DEVICE = 1ULL << 6
|
OAK_RENDER_BACKEND_CAP_DEVICE = 1ULL << 6
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Static and runtime metadata returned by the backend. */
|
||||||
struct OakRenderBackendInfo {
|
struct OakRenderBackendInfo {
|
||||||
uint32_t abi_version;
|
uint32_t abi_version;
|
||||||
uint32_t kind;
|
uint32_t kind;
|
||||||
@@ -40,52 +44,74 @@ struct OakRenderBackendInfo {
|
|||||||
const char *status;
|
const char *status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Creates a backend renderer object. */
|
||||||
typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent);
|
typedef OakRenderBackendHandle (*OakBackendCreateFn)(void *parent);
|
||||||
|
/* Destroys a backend renderer object created by OakBackendCreateFn. */
|
||||||
typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendDestroyFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Queries backend metadata and capability bits. */
|
||||||
typedef bool (*OakBackendGetInfoFn)(OakRenderBackendHandle handle,
|
typedef bool (*OakBackendGetInfoFn)(OakRenderBackendHandle handle,
|
||||||
struct OakRenderBackendInfo *out_info);
|
struct OakRenderBackendInfo *out_info);
|
||||||
|
/* Checks whether the backend can run on the current machine. */
|
||||||
typedef bool (*OakBackendIsAvailableFn)(OakRenderBackendHandle handle);
|
typedef bool (*OakBackendIsAvailableFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Initializes backend-owned device/context resources. */
|
||||||
typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle);
|
typedef bool (*OakBackendInitFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Initializes the backend against a caller-supplied GL context when applicable. */
|
||||||
typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendInitWithContextFn)(OakRenderBackendHandle handle,
|
||||||
void *context);
|
void *context);
|
||||||
|
/* Runs backend post-initialization after the device/context exists. */
|
||||||
typedef void (*OakBackendPostInitFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendPostInitFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Runs backend post-destroy cleanup before the library unloads. */
|
||||||
typedef void (*OakBackendPostDestroyFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendPostDestroyFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Destroys renderer-owned native resources. */
|
||||||
typedef void (*OakBackendDestroyInternalFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendDestroyInternalFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Clears a texture destination or implicit output target. */
|
||||||
typedef void (*OakBackendClearDestinationFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendClearDestinationFn)(OakRenderBackendHandle handle,
|
||||||
void *texture, double r, double g,
|
void *texture, double r, double g,
|
||||||
double b, double a);
|
double b, double a);
|
||||||
|
/* Creates a native texture and writes a QVariant-compatible handle. */
|
||||||
typedef void (*OakBackendCreateNativeTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendCreateNativeTextureFn)(OakRenderBackendHandle handle,
|
||||||
int width, int height, int depth,
|
int width, int height, int depth,
|
||||||
int format, int channel_count,
|
int format, int channel_count,
|
||||||
const void *data, int linesize,
|
const void *data, int linesize,
|
||||||
void *out_variant);
|
void *out_variant);
|
||||||
|
/* Destroys a native texture represented by a QVariant-compatible handle. */
|
||||||
typedef void (*OakBackendDestroyNativeTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendDestroyNativeTextureFn)(OakRenderBackendHandle handle,
|
||||||
const void *variant);
|
const void *variant);
|
||||||
|
/* Creates a native shader and writes a QVariant-compatible handle. */
|
||||||
typedef void (*OakBackendCreateNativeShaderFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendCreateNativeShaderFn)(OakRenderBackendHandle handle,
|
||||||
const void *shader_code,
|
const void *shader_code,
|
||||||
void *out_variant);
|
void *out_variant);
|
||||||
|
/* Destroys a native shader represented by a QVariant-compatible handle. */
|
||||||
typedef void (*OakBackendDestroyNativeShaderFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendDestroyNativeShaderFn)(OakRenderBackendHandle handle,
|
||||||
const void *variant);
|
const void *variant);
|
||||||
|
/* Uploads CPU pixel data to a native texture. */
|
||||||
typedef void (*OakBackendUploadToTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendUploadToTextureFn)(OakRenderBackendHandle handle,
|
||||||
const void *variant,
|
const void *variant,
|
||||||
const void *video_params,
|
const void *video_params,
|
||||||
const void *data, int linesize);
|
const void *data, int linesize);
|
||||||
|
/* Downloads native texture pixels into caller-owned CPU memory. */
|
||||||
typedef void (*OakBackendDownloadFromTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendDownloadFromTextureFn)(OakRenderBackendHandle handle,
|
||||||
const void *variant,
|
const void *variant,
|
||||||
const void *video_params,
|
const void *video_params,
|
||||||
void *data, int linesize);
|
void *data, int linesize);
|
||||||
|
/* Waits for backend work that must be visible to later operations. */
|
||||||
typedef void (*OakBackendFlushFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendFlushFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Reads one pixel from a texture. */
|
||||||
typedef void (*OakBackendGetPixelFromTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendGetPixelFromTextureFn)(OakRenderBackendHandle handle,
|
||||||
void *texture, const void *point,
|
void *texture, const void *point,
|
||||||
void *out_color);
|
void *out_color);
|
||||||
|
/* Executes a shader blit job. */
|
||||||
typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
|
||||||
const void *shader, void *job,
|
const void *shader, void *job,
|
||||||
void *destination,
|
void *destination,
|
||||||
const void *destination_params,
|
const void *destination_params,
|
||||||
bool clear_destination);
|
bool clear_destination);
|
||||||
|
/* Attaches an output texture for OFX OpenGL rendering when supported. */
|
||||||
typedef void (*OakBackendAttachOutputTextureFn)(OakRenderBackendHandle handle,
|
typedef void (*OakBackendAttachOutputTextureFn)(OakRenderBackendHandle handle,
|
||||||
const void *texture_id);
|
const void *texture_id);
|
||||||
|
/* Detaches an OFX output texture when supported. */
|
||||||
typedef void (*OakBackendDetachOutputTextureFn)(OakRenderBackendHandle handle);
|
typedef void (*OakBackendDetachOutputTextureFn)(OakRenderBackendHandle handle);
|
||||||
|
/* Returns the backend OpenGL context, or null for non-OpenGL backends. */
|
||||||
typedef void *(*OakBackendOpenGLContextFn)(OakRenderBackendHandle handle);
|
typedef void *(*OakBackendOpenGLContextFn)(OakRenderBackendHandle handle);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ public:
|
|||||||
using olive::OpenGLRenderer::DetachTextureAsDestination;
|
using olive::OpenGLRenderer::DetachTextureAsDestination;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Converts the opaque C ABI handle back to the C++ renderer used internally.
|
||||||
BackendOpenGLRenderer *Renderer(OakRenderBackendHandle handle)
|
BackendOpenGLRenderer *Renderer(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return static_cast<BackendOpenGLRenderer *>(handle);
|
return static_cast<BackendOpenGLRenderer *>(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interprets ABI QVariant payloads without copying; both modules are built
|
||||||
|
// against the same Qt/C++ ABI in this first-generation dynamic backend.
|
||||||
const QVariant &VariantRef(const void *variant)
|
const QVariant &VariantRef(const void *variant)
|
||||||
{
|
{
|
||||||
return *static_cast<const QVariant *>(variant);
|
return *static_cast<const QVariant *>(variant);
|
||||||
@@ -35,16 +38,19 @@ const QVariant &VariantRef(const void *variant)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Creates the backend object and returns it as an opaque C handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
||||||
{
|
{
|
||||||
return new BackendOpenGLRenderer(static_cast<QObject *>(parent));
|
return new BackendOpenGLRenderer(static_cast<QObject *>(parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys the opaque backend object created by oak_renderer_create().
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
delete Renderer(handle);
|
delete Renderer(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reports static OpenGL backend capabilities to the adapter.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
||||||
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
|
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
|
||||||
{
|
{
|
||||||
@@ -62,39 +68,47 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenGL availability is context-dependent, so object creation is the minimum
|
||||||
|
// availability signal for this backend.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return handle != nullptr;
|
return handle != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes an offscreen OpenGL context for non-viewer users.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return Renderer(handle)->Init();
|
return Renderer(handle)->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes the backend against a caller-owned viewer OpenGL context.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
||||||
OakRenderBackendHandle handle, void *context)
|
OakRenderBackendHandle handle, void *context)
|
||||||
{
|
{
|
||||||
Renderer(handle)->Init(static_cast<QOpenGLContext *>(context));
|
Renderer(handle)->Init(static_cast<QOpenGLContext *>(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs renderer post-initialization once the GL context is available.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->PostInit();
|
Renderer(handle)->PostInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases post-init OpenGL surface/context state.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->PostDestroy();
|
Renderer(handle)->PostDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases renderer-owned GL resources before object destruction.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyInternal();
|
Renderer(handle)->DestroyInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears either the widget framebuffer or a texture destination.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
||||||
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
||||||
double a)
|
double a)
|
||||||
@@ -103,6 +117,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
|||||||
r, g, b, a);
|
r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates an OpenGL texture and writes its QVariant handle to out_variant.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
||||||
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
||||||
int channel_count, const void *data, int linesize, void *out_variant)
|
int channel_count, const void *data, int linesize, void *out_variant)
|
||||||
@@ -112,12 +127,14 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
|||||||
channel_count, data, linesize);
|
channel_count, data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys an OpenGL texture represented by a QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant)
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compiles an OpenGL shader program and returns its QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
||||||
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
||||||
{
|
{
|
||||||
@@ -125,12 +142,14 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
|||||||
*static_cast<const olive::ShaderCode *>(shader_code));
|
*static_cast<const olive::ShaderCode *>(shader_code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys an OpenGL shader program represented by a QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
||||||
OakRenderBackendHandle handle, const void *variant)
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uploads CPU pixel data into an OpenGL texture.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
const void *data, int linesize)
|
const void *data, int linesize)
|
||||||
@@ -140,6 +159,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
|||||||
data, linesize);
|
data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads an OpenGL texture back to CPU memory.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
void *data, int linesize)
|
void *data, int linesize)
|
||||||
@@ -149,11 +169,13 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
|||||||
data, linesize);
|
data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flushes/waits for pending OpenGL work as required by the renderer.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->Flush();
|
Renderer(handle)->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads one pixel from an OpenGL texture.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
||||||
OakRenderBackendHandle handle, void *texture, const void *point,
|
OakRenderBackendHandle handle, void *texture, const void *point,
|
||||||
void *out_color)
|
void *out_color)
|
||||||
@@ -162,6 +184,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
|||||||
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executes a shader blit through the wrapped C++ OpenGL renderer.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
||||||
OakRenderBackendHandle handle, const void *shader, void *job,
|
OakRenderBackendHandle handle, const void *shader, void *job,
|
||||||
void *destination, const void *destination_params, bool clear_destination)
|
void *destination, const void *destination_params, bool clear_destination)
|
||||||
@@ -173,18 +196,21 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
|||||||
clear_destination);
|
clear_destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exposes the wrapped OpenGL context for GL-specific integrations.
|
||||||
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return Renderer(handle)->context();
|
return Renderer(handle)->context();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Binds an output texture for OFX OpenGL rendering.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
||||||
OakRenderBackendHandle handle, const void *texture_id)
|
OakRenderBackendHandle handle, const void *texture_id)
|
||||||
{
|
{
|
||||||
Renderer(handle)->AttachTextureAsDestination(VariantRef(texture_id));
|
Renderer(handle)->AttachTextureAsDestination(VariantRef(texture_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detaches any OFX OpenGL output texture binding.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -408,6 +408,8 @@ void OpenGLRenderer::Flush()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adapts the generic Renderer output attachment hook to OpenGL's framebuffer
|
||||||
|
// attachment path used by OFX OpenGL rendering.
|
||||||
void OpenGLRenderer::AttachOutputTexture(olive::Texture *texture)
|
void OpenGLRenderer::AttachOutputTexture(olive::Texture *texture)
|
||||||
{
|
{
|
||||||
if (texture) {
|
if (texture) {
|
||||||
@@ -415,6 +417,7 @@ void OpenGLRenderer::AttachOutputTexture(olive::Texture *texture)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears the framebuffer attachment installed by AttachOutputTexture().
|
||||||
void OpenGLRenderer::DetachOutputTexture()
|
void OpenGLRenderer::DetachOutputTexture()
|
||||||
{
|
{
|
||||||
DetachTextureAsDestination();
|
DetachTextureAsDestination();
|
||||||
@@ -987,6 +990,9 @@ bool OpenGLRenderer::EnsureContextCurrent(const char *caller)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QOpenGLContext may only be made current from its owning thread. Viewer
|
||||||
|
// paint code can receive textures produced by a render-thread OpenGL
|
||||||
|
// renderer, so guard here before makeCurrent() can crash inside Qt/GL.
|
||||||
if (context_->thread() != QThread::currentThread()) {
|
if (context_->thread() != QThread::currentThread()) {
|
||||||
qWarning() << caller << "called from the wrong thread for this OpenGL context";
|
qWarning() << caller << "called from the wrong thread for this OpenGL context";
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ public:
|
|||||||
using olive::VulkanRenderer::DestroyNativeTexture;
|
using olive::VulkanRenderer::DestroyNativeTexture;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Converts the opaque C ABI handle back to the C++ Vulkan renderer.
|
||||||
BackendVulkanRenderer *Renderer(OakRenderBackendHandle handle)
|
BackendVulkanRenderer *Renderer(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return static_cast<BackendVulkanRenderer *>(handle);
|
return static_cast<BackendVulkanRenderer *>(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interprets ABI QVariant payloads without copying; this ABI version assumes
|
||||||
|
// the host and backend are built with the same Qt/C++ ABI.
|
||||||
const QVariant &VariantRef(const void *variant)
|
const QVariant &VariantRef(const void *variant)
|
||||||
{
|
{
|
||||||
return *static_cast<const QVariant *>(variant);
|
return *static_cast<const QVariant *>(variant);
|
||||||
@@ -34,16 +37,19 @@ const QVariant &VariantRef(const void *variant)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Creates the Vulkan backend object and returns it as an opaque C handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
||||||
{
|
{
|
||||||
return new BackendVulkanRenderer(static_cast<QObject *>(parent));
|
return new BackendVulkanRenderer(static_cast<QObject *>(parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys the opaque backend object created by oak_renderer_create().
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
delete Renderer(handle);
|
delete Renderer(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reports Vulkan backend capabilities and runtime availability status.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
||||||
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
|
OakRenderBackendHandle handle, OakRenderBackendInfo *out_info)
|
||||||
{
|
{
|
||||||
@@ -60,6 +66,8 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Probes runtime availability by trying Init() once; this lets missing ICDs or
|
||||||
|
// unusable drivers fall back before normal rendering starts.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
@@ -74,11 +82,13 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
|||||||
return r->IsAvailable();
|
return r->IsAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes the Vulkan device path.
|
||||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
return Renderer(handle)->Init();
|
return Renderer(handle)->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vulkan does not use a QOpenGLContext; the argument is accepted for ABI parity.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
||||||
OakRenderBackendHandle handle, void *context)
|
OakRenderBackendHandle handle, void *context)
|
||||||
{
|
{
|
||||||
@@ -86,24 +96,28 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
|||||||
Renderer(handle)->Init();
|
Renderer(handle)->Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates reusable Vulkan resources after device initialization.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->PostInit();
|
Renderer(handle)->PostInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserved for API symmetry; Vulkan cleanup is handled by destroy_internal.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->PostDestroy();
|
Renderer(handle)->PostDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases all Vulkan resources owned by the renderer.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyInternal();
|
Renderer(handle)->DestroyInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears a Vulkan texture destination.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
||||||
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
||||||
double a)
|
double a)
|
||||||
@@ -112,6 +126,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
|||||||
r, g, b, a);
|
r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a Vulkan texture and writes its QVariant handle to out_variant.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
||||||
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
||||||
int channel_count, const void *data, int linesize, void *out_variant)
|
int channel_count, const void *data, int linesize, void *out_variant)
|
||||||
@@ -121,12 +136,14 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
|||||||
channel_count, data, linesize);
|
channel_count, data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys a Vulkan texture represented by a QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant)
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compiles a Vulkan shader and returns its QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
||||||
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
||||||
{
|
{
|
||||||
@@ -134,12 +151,14 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
|||||||
*static_cast<const olive::ShaderCode *>(shader_code));
|
*static_cast<const olive::ShaderCode *>(shader_code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys a Vulkan shader represented by a QVariant handle.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
||||||
OakRenderBackendHandle handle, const void *variant)
|
OakRenderBackendHandle handle, const void *variant)
|
||||||
{
|
{
|
||||||
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uploads CPU pixel data into a Vulkan texture.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
const void *data, int linesize)
|
const void *data, int linesize)
|
||||||
@@ -149,6 +168,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
|||||||
data, linesize);
|
data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Downloads a Vulkan texture to CPU memory.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
||||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||||
void *data, int linesize)
|
void *data, int linesize)
|
||||||
@@ -158,11 +178,13 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
|||||||
data, linesize);
|
data, linesize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Waits for all queued Vulkan work to finish.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
Renderer(handle)->Flush();
|
Renderer(handle)->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads one pixel from a Vulkan texture.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
||||||
OakRenderBackendHandle handle, void *texture, const void *point,
|
OakRenderBackendHandle handle, void *texture, const void *point,
|
||||||
void *out_color)
|
void *out_color)
|
||||||
@@ -171,6 +193,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
|||||||
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executes a shader blit through the wrapped Vulkan renderer.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
||||||
OakRenderBackendHandle handle, const void *shader, void *job,
|
OakRenderBackendHandle handle, const void *shader, void *job,
|
||||||
void *destination, const void *destination_params, bool clear_destination)
|
void *destination, const void *destination_params, bool clear_destination)
|
||||||
@@ -182,6 +205,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
|||||||
clear_destination);
|
clear_destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vulkan has no OpenGL context; return null so callers avoid GL-only paths.
|
||||||
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
@@ -189,6 +213,7 @@ OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OFX OpenGL output attachment is unsupported in Vulkan and intentionally no-op.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
||||||
OakRenderBackendHandle handle, const void *texture_id)
|
OakRenderBackendHandle handle, const void *texture_id)
|
||||||
{
|
{
|
||||||
@@ -197,6 +222,7 @@ OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
|||||||
// Vulkan does not support OFX OpenGL render output attachment.
|
// Vulkan does not support OFX OpenGL render output attachment.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OFX OpenGL output detachment is unsupported in Vulkan and intentionally no-op.
|
||||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
||||||
OakRenderBackendHandle handle)
|
OakRenderBackendHandle handle)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,16 +60,21 @@ static const float kBlitVertices[] = {
|
|||||||
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Constructs the renderer; Vulkan resources are created lazily so unavailable
|
||||||
|
// Vulkan systems can still instantiate the object and report fallback state.
|
||||||
VulkanRenderer::VulkanRenderer(QObject *parent) : Renderer(parent)
|
VulkanRenderer::VulkanRenderer(QObject *parent) : Renderer(parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures Vulkan resources are destroyed before the QObject hierarchy goes away.
|
||||||
VulkanRenderer::~VulkanRenderer()
|
VulkanRenderer::~VulkanRenderer()
|
||||||
{
|
{
|
||||||
Destroy();
|
Destroy();
|
||||||
PostDestroy();
|
PostDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializes the Vulkan instance/device path once. Repeated calls are accepted
|
||||||
|
// because backend availability probes may call Init() before normal rendering.
|
||||||
bool VulkanRenderer::Init()
|
bool VulkanRenderer::Init()
|
||||||
{
|
{
|
||||||
if (instance_ != VK_NULL_HANDLE) {
|
if (instance_ != VK_NULL_HANDLE) {
|
||||||
@@ -79,6 +84,7 @@ bool VulkanRenderer::Init()
|
|||||||
CreateDescriptorPool();
|
CreateDescriptorPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates reusable draw resources after Init() has a valid logical device.
|
||||||
void VulkanRenderer::PostInit()
|
void VulkanRenderer::PostInit()
|
||||||
{
|
{
|
||||||
if (vertex_buffer_ != VK_NULL_HANDLE) {
|
if (vertex_buffer_ != VK_NULL_HANDLE) {
|
||||||
@@ -89,10 +95,15 @@ void VulkanRenderer::PostInit()
|
|||||||
CreateNearestSampler();
|
CreateNearestSampler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserved for Renderer API symmetry; Vulkan teardown is centralized in
|
||||||
|
// DestroyInternal() so object destruction and explicit Destroy() share a path.
|
||||||
void VulkanRenderer::PostDestroy()
|
void VulkanRenderer::PostDestroy()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys all Vulkan objects in dependency order. The device is idled first so
|
||||||
|
// cached textures, pipelines, descriptor pools, and command pools are no longer
|
||||||
|
// referenced by in-flight work.
|
||||||
void VulkanRenderer::DestroyInternal()
|
void VulkanRenderer::DestroyInternal()
|
||||||
{
|
{
|
||||||
if (device_ != VK_NULL_HANDLE) {
|
if (device_ != VK_NULL_HANDLE) {
|
||||||
@@ -185,6 +196,7 @@ void VulkanRenderer::DestroyInternal()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates the minimal Vulkan instance needed for offscreen rendering.
|
||||||
bool VulkanRenderer::CreateInstance()
|
bool VulkanRenderer::CreateInstance()
|
||||||
{
|
{
|
||||||
VkApplicationInfo app_info = {};
|
VkApplicationInfo app_info = {};
|
||||||
@@ -208,6 +220,8 @@ bool VulkanRenderer::CreateInstance()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Selects the first physical device with a graphics queue and creates a logical
|
||||||
|
// device without swapchain extensions because viewer output is CPU readback.
|
||||||
bool VulkanRenderer::CreateDevice()
|
bool VulkanRenderer::CreateDevice()
|
||||||
{
|
{
|
||||||
VkResult result = vkEnumeratePhysicalDevices(instance_, &physical_device_count_, nullptr);
|
VkResult result = vkEnumeratePhysicalDevices(instance_, &physical_device_count_, nullptr);
|
||||||
@@ -284,6 +298,8 @@ bool VulkanRenderer::CreateDevice()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates the command pool used for short-lived transfer and draw command
|
||||||
|
// buffers.
|
||||||
bool VulkanRenderer::CreateCommandPool()
|
bool VulkanRenderer::CreateCommandPool()
|
||||||
{
|
{
|
||||||
VkCommandPoolCreateInfo pool_info = {};
|
VkCommandPoolCreateInfo pool_info = {};
|
||||||
@@ -299,6 +315,8 @@ bool VulkanRenderer::CreateCommandPool()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a pool large enough for transient per-blit descriptor sets. Descriptor
|
||||||
|
// sets are freed after each pass, so this is capacity rather than lifetime count.
|
||||||
bool VulkanRenderer::CreateDescriptorPool()
|
bool VulkanRenderer::CreateDescriptorPool()
|
||||||
{
|
{
|
||||||
VkDescriptorPoolSize pool_sizes[2] = {};
|
VkDescriptorPoolSize pool_sizes[2] = {};
|
||||||
@@ -323,6 +341,7 @@ bool VulkanRenderer::CreateDescriptorPool()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a cached render pass keyed by color format and load operation.
|
||||||
VkRenderPass VulkanRenderer::GetOrCreateRenderPass(VkFormat format, bool clear)
|
VkRenderPass VulkanRenderer::GetOrCreateRenderPass(VkFormat format, bool clear)
|
||||||
{
|
{
|
||||||
const quint64 key = (static_cast<quint64>(format) << 1) | (clear ? 1ULL : 0ULL);
|
const quint64 key = (static_cast<quint64>(format) << 1) | (clear ? 1ULL : 0ULL);
|
||||||
@@ -387,6 +406,7 @@ VkRenderPass VulkanRenderer::GetOrCreateRenderPass(VkFormat format, bool clear)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Uploads a fullscreen quad to device-local memory through a staging buffer.
|
||||||
bool VulkanRenderer::CreateVertexBuffer()
|
bool VulkanRenderer::CreateVertexBuffer()
|
||||||
{
|
{
|
||||||
VkDeviceSize buffer_size = sizeof(kBlitVertices);
|
VkDeviceSize buffer_size = sizeof(kBlitVertices);
|
||||||
@@ -486,6 +506,7 @@ bool VulkanRenderer::CreateVertexBuffer()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates the persistent linear sampler shared by all texture bindings.
|
||||||
bool VulkanRenderer::CreateLinearSampler()
|
bool VulkanRenderer::CreateLinearSampler()
|
||||||
{
|
{
|
||||||
VkSamplerCreateInfo sampler_info = {};
|
VkSamplerCreateInfo sampler_info = {};
|
||||||
@@ -512,6 +533,7 @@ bool VulkanRenderer::CreateLinearSampler()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates the persistent nearest sampler shared by all texture bindings.
|
||||||
bool VulkanRenderer::CreateNearestSampler()
|
bool VulkanRenderer::CreateNearestSampler()
|
||||||
{
|
{
|
||||||
VkSamplerCreateInfo sampler_info = {};
|
VkSamplerCreateInfo sampler_info = {};
|
||||||
@@ -538,6 +560,7 @@ bool VulkanRenderer::CreateNearestSampler()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maps Oak interpolation settings to persistent Vulkan sampler objects.
|
||||||
VkSampler VulkanRenderer::GetSampler(Texture::Interpolation interpolation) const
|
VkSampler VulkanRenderer::GetSampler(Texture::Interpolation interpolation) const
|
||||||
{
|
{
|
||||||
switch (interpolation) {
|
switch (interpolation) {
|
||||||
@@ -550,6 +573,7 @@ VkSampler VulkanRenderer::GetSampler(Texture::Interpolation interpolation) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocates host-visible coherent memory for one upload/download transfer.
|
||||||
bool VulkanRenderer::CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
bool VulkanRenderer::CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
||||||
VkDeviceMemory *out_memory)
|
VkDeviceMemory *out_memory)
|
||||||
{
|
{
|
||||||
@@ -596,6 +620,7 @@ bool VulkanRenderer::CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Releases a staging buffer and its memory allocation.
|
||||||
void VulkanRenderer::DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory)
|
void VulkanRenderer::DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory)
|
||||||
{
|
{
|
||||||
if (buffer != VK_NULL_HANDLE) {
|
if (buffer != VK_NULL_HANDLE) {
|
||||||
@@ -606,6 +631,7 @@ void VulkanRenderer::DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts a primary command buffer intended for immediate submit-and-wait use.
|
||||||
VkCommandBuffer VulkanRenderer::BeginOneTimeCommands()
|
VkCommandBuffer VulkanRenderer::BeginOneTimeCommands()
|
||||||
{
|
{
|
||||||
VkCommandBufferAllocateInfo alloc_info = {};
|
VkCommandBufferAllocateInfo alloc_info = {};
|
||||||
@@ -625,6 +651,7 @@ VkCommandBuffer VulkanRenderer::BeginOneTimeCommands()
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Submits a one-time command buffer and waits synchronously for completion.
|
||||||
void VulkanRenderer::EndOneTimeCommands(VkCommandBuffer cmd)
|
void VulkanRenderer::EndOneTimeCommands(VkCommandBuffer cmd)
|
||||||
{
|
{
|
||||||
vkEndCommandBuffer(cmd);
|
vkEndCommandBuffer(cmd);
|
||||||
@@ -640,6 +667,8 @@ void VulkanRenderer::EndOneTimeCommands(VkCommandBuffer cmd)
|
|||||||
vkFreeCommandBuffers(device_, command_pool_, 1, &cmd);
|
vkFreeCommandBuffers(device_, command_pool_, 1, &cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Emits a conservative barrier for the image layout transitions used by this
|
||||||
|
// renderer: upload, shader read, color attachment, clear, and readback.
|
||||||
void VulkanRenderer::TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
void VulkanRenderer::TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
||||||
VkImageLayout old_layout,
|
VkImageLayout old_layout,
|
||||||
VkImageLayout new_layout)
|
VkImageLayout new_layout)
|
||||||
@@ -748,6 +777,7 @@ void VulkanRenderer::TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Records a buffer-to-image copy for tightly packed texture uploads.
|
||||||
void VulkanRenderer::CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer,
|
void VulkanRenderer::CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer,
|
||||||
VkImage image, uint32_t width,
|
VkImage image, uint32_t width,
|
||||||
uint32_t height, uint32_t depth)
|
uint32_t height, uint32_t depth)
|
||||||
@@ -767,6 +797,7 @@ void VulkanRenderer::CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer,
|
|||||||
1, ®ion);
|
1, ®ion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Records an image-to-buffer copy for full texture downloads or one-pixel reads.
|
||||||
void VulkanRenderer::CopyImageToBuffer(VkCommandBuffer cmd, VkImage image,
|
void VulkanRenderer::CopyImageToBuffer(VkCommandBuffer cmd, VkImage image,
|
||||||
VkBuffer buffer, uint32_t width,
|
VkBuffer buffer, uint32_t width,
|
||||||
uint32_t height,
|
uint32_t height,
|
||||||
@@ -787,6 +818,7 @@ void VulkanRenderer::CopyImageToBuffer(VkCommandBuffer cmd, VkImage image,
|
|||||||
1, ®ion);
|
1, ®ion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converts Oak's pixel format/channel count pair to the closest Vulkan format.
|
||||||
VkFormat VulkanRenderer::PixelFormatToVkFormat(PixelFormat format,
|
VkFormat VulkanRenderer::PixelFormatToVkFormat(PixelFormat format,
|
||||||
int channel_count) const
|
int channel_count) const
|
||||||
{
|
{
|
||||||
@@ -830,6 +862,7 @@ VkFormat VulkanRenderer::PixelFormatToVkFormat(PixelFormat format,
|
|||||||
return VK_FORMAT_UNDEFINED;
|
return VK_FORMAT_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks color-attachment support before selecting renderable image formats.
|
||||||
bool VulkanRenderer::IsColorAttachmentSupported(VkFormat format) const
|
bool VulkanRenderer::IsColorAttachmentSupported(VkFormat format) const
|
||||||
{
|
{
|
||||||
VkFormatProperties props;
|
VkFormatProperties props;
|
||||||
@@ -838,6 +871,8 @@ bool VulkanRenderer::IsColorAttachmentSupported(VkFormat format) const
|
|||||||
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chooses a renderable Vulkan format and falls back from RGB to RGBA when a
|
||||||
|
// driver does not expose 3-channel color attachment support.
|
||||||
VkFormat VulkanRenderer::PickRenderableFormat(PixelFormat format,
|
VkFormat VulkanRenderer::PickRenderableFormat(PixelFormat format,
|
||||||
int channel_count) const
|
int channel_count) const
|
||||||
{
|
{
|
||||||
@@ -858,6 +893,7 @@ VkFormat VulkanRenderer::PickRenderableFormat(PixelFormat format,
|
|||||||
return VK_FORMAT_UNDEFINED;
|
return VK_FORMAT_UNDEFINED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the packed texel size for the VkFormat values generated above.
|
||||||
int VulkanRenderer::GetVkFormatBytesPerPixel(VkFormat format) const
|
int VulkanRenderer::GetVkFormatBytesPerPixel(VkFormat format) const
|
||||||
{
|
{
|
||||||
switch (format) {
|
switch (format) {
|
||||||
@@ -898,6 +934,7 @@ int VulkanRenderer::GetVkFormatBytesPerPixel(VkFormat format) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the alpha fill value used when expanding formats without alpha.
|
||||||
float VulkanRenderer::GetFormatMaxAlpha(PixelFormat format) const
|
float VulkanRenderer::GetFormatMaxAlpha(PixelFormat format) const
|
||||||
{
|
{
|
||||||
if (format == PixelFormat::U8) {
|
if (format == PixelFormat::U8) {
|
||||||
@@ -908,6 +945,8 @@ float VulkanRenderer::GetFormatMaxAlpha(PixelFormat format) const
|
|||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copies tightly-packed pixels while changing channel count. This handles the
|
||||||
|
// common Vulkan fallback where requested RGB data is stored as RGBA on the GPU.
|
||||||
void VulkanRenderer::CopyPixelsWithChannelConversion(const void *src, void *dst,
|
void VulkanRenderer::CopyPixelsWithChannelConversion(const void *src, void *dst,
|
||||||
int width, int height, int depth,
|
int width, int height, int depth,
|
||||||
int src_channels, int dst_channels,
|
int src_channels, int dst_channels,
|
||||||
@@ -958,12 +997,14 @@ void VulkanRenderer::CopyPixelsWithChannelConversion(const void *src, void *dst,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rounds size up to the next multiple of alignment.
|
||||||
VkDeviceSize VulkanRenderer::AlignSize(VkDeviceSize size,
|
VkDeviceSize VulkanRenderer::AlignSize(VkDeviceSize size,
|
||||||
VkDeviceSize alignment) const
|
VkDeviceSize alignment) const
|
||||||
{
|
{
|
||||||
return (size + alignment - 1) & ~(alignment - 1);
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finds a compatible memory type satisfying Vulkan's bitmask and property flags.
|
||||||
uint32_t VulkanRenderer::FindMemoryType(uint32_t type_filter,
|
uint32_t VulkanRenderer::FindMemoryType(uint32_t type_filter,
|
||||||
VkMemoryPropertyFlags properties) const
|
VkMemoryPropertyFlags properties) const
|
||||||
{
|
{
|
||||||
@@ -977,6 +1018,7 @@ uint32_t VulkanRenderer::FindMemoryType(uint32_t type_filter,
|
|||||||
return UINT32_MAX;
|
return UINT32_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a Vulkan image, memory allocation, and image view for an Oak texture.
|
||||||
QVariant VulkanRenderer::CreateNativeTexture(int width, int height, int depth,
|
QVariant VulkanRenderer::CreateNativeTexture(int width, int height, int depth,
|
||||||
PixelFormat format, int channel_count,
|
PixelFormat format, int channel_count,
|
||||||
const void *data, int linesize)
|
const void *data, int linesize)
|
||||||
@@ -1164,6 +1206,7 @@ QVariant VulkanRenderer::CreateNativeTexture(int width, int height, int depth,
|
|||||||
return QVariant::fromValue(tex->id);
|
return QVariant::fromValue(tex->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Destroys a texture handle and all Vulkan objects owned by that texture.
|
||||||
void VulkanRenderer::DestroyNativeTexture(QVariant texture)
|
void VulkanRenderer::DestroyNativeTexture(QVariant texture)
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&mutex_);
|
QMutexLocker lock(&mutex_);
|
||||||
@@ -1187,6 +1230,8 @@ void VulkanRenderer::DestroyNativeTexture(QVariant texture)
|
|||||||
delete tex;
|
delete tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uploads CPU pixels to an existing image. The staging layout is based on the
|
||||||
|
// selected GPU VkFormat, then CPU data is repacked when channel counts differ.
|
||||||
void VulkanRenderer::UploadToTexture(const QVariant &handle,
|
void VulkanRenderer::UploadToTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, const void *data,
|
const VideoParams ¶ms, const void *data,
|
||||||
int linesize)
|
int linesize)
|
||||||
@@ -1273,6 +1318,8 @@ void VulkanRenderer::UploadToTexture(const QVariant &handle,
|
|||||||
tex->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
tex->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Downloads an image to CPU memory. When the GPU format is wider than the
|
||||||
|
// requested CPU format, the staging data is compacted back to the caller layout.
|
||||||
void VulkanRenderer::DownloadFromTexture(const QVariant &handle,
|
void VulkanRenderer::DownloadFromTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, void *data,
|
const VideoParams ¶ms, void *data,
|
||||||
int linesize)
|
int linesize)
|
||||||
@@ -1355,6 +1402,7 @@ void VulkanRenderer::DownloadFromTexture(const QVariant &handle,
|
|||||||
tex->current_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
tex->current_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blocks until the device is idle so later CPU readback or teardown is safe.
|
||||||
void VulkanRenderer::Flush()
|
void VulkanRenderer::Flush()
|
||||||
{
|
{
|
||||||
if (device_ != VK_NULL_HANDLE) {
|
if (device_ != VK_NULL_HANDLE) {
|
||||||
@@ -1362,6 +1410,8 @@ void VulkanRenderer::Flush()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clears a texture with vkCmdClearColorImage; null destinations are ignored
|
||||||
|
// because this backend has no implicit swapchain framebuffer.
|
||||||
void VulkanRenderer::ClearDestination(olive::Texture *texture, double r, double g,
|
void VulkanRenderer::ClearDestination(olive::Texture *texture, double r, double g,
|
||||||
double b, double a)
|
double b, double a)
|
||||||
{
|
{
|
||||||
@@ -1399,6 +1449,7 @@ void VulkanRenderer::ClearDestination(olive::Texture *texture, double r, double
|
|||||||
EndOneTimeCommands(cmd);
|
EndOneTimeCommands(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reads one pixel by copying a 1x1 image region into a staging buffer.
|
||||||
Color VulkanRenderer::GetPixelFromTexture(olive::Texture *texture,
|
Color VulkanRenderer::GetPixelFromTexture(olive::Texture *texture,
|
||||||
const QPointF &pt)
|
const QPointF &pt)
|
||||||
{
|
{
|
||||||
@@ -1465,6 +1516,7 @@ Color VulkanRenderer::GetPixelFromTexture(olive::Texture *texture,
|
|||||||
// Shader compilation (GLSL -> SPIR-V via shaderc)
|
// Shader compilation (GLSL -> SPIR-V via shaderc)
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Returns true for GLSL sampler uniforms that must become explicit descriptors.
|
||||||
static bool IsSamplerType(const QString &type)
|
static bool IsSamplerType(const QString &type)
|
||||||
{
|
{
|
||||||
static const QRegularExpression sampler_re(
|
static const QRegularExpression sampler_re(
|
||||||
@@ -1472,6 +1524,8 @@ static bool IsSamplerType(const QString &type)
|
|||||||
return sampler_re.match(type).hasMatch();
|
return sampler_re.match(type).hasMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures GLSL has a Vulkan-compatible version directive before shaderc compiles
|
||||||
|
// it as GLSL 450.
|
||||||
QString VulkanRenderer::EnsureGlslVersion450(const QString &glsl) const
|
QString VulkanRenderer::EnsureGlslVersion450(const QString &glsl) const
|
||||||
{
|
{
|
||||||
QString result = glsl.trimmed();
|
QString result = glsl.trimmed();
|
||||||
@@ -1483,6 +1537,9 @@ QString VulkanRenderer::EnsureGlslVersion450(const QString &glsl) const
|
|||||||
return QStringLiteral("#version 450 core\n") + result;
|
return QStringLiteral("#version 450 core\n") + result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converts legacy Oak/OpenGL GLSL into Vulkan GLSL. The conversion keeps shader
|
||||||
|
// semantics but replaces implicit attributes/varyings and texture sampling with
|
||||||
|
// explicit layouts that Vulkan requires.
|
||||||
QString VulkanRenderer::ConvertGlslToVulkan(const QString &glsl,
|
QString VulkanRenderer::ConvertGlslToVulkan(const QString &glsl,
|
||||||
VkShaderStageFlagBits stage)
|
VkShaderStageFlagBits stage)
|
||||||
{
|
{
|
||||||
@@ -1513,6 +1570,7 @@ QString VulkanRenderer::ConvertGlslToVulkan(const QString &glsl,
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the std140 storage size for scalar, vector, color, and matrix values.
|
||||||
VkDeviceSize VulkanRenderer::GetStd140Size(const QString &type) const
|
VkDeviceSize VulkanRenderer::GetStd140Size(const QString &type) const
|
||||||
{
|
{
|
||||||
if (type == QStringLiteral("float")) return 4;
|
if (type == QStringLiteral("float")) return 4;
|
||||||
@@ -1524,6 +1582,7 @@ VkDeviceSize VulkanRenderer::GetStd140Size(const QString &type) const
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns std140 base alignment so generated UBO offsets match GPU layout rules.
|
||||||
VkDeviceSize VulkanRenderer::GetStd140Alignment(const QString &type) const
|
VkDeviceSize VulkanRenderer::GetStd140Alignment(const QString &type) const
|
||||||
{
|
{
|
||||||
if (type == QStringLiteral("float")) return 4;
|
if (type == QStringLiteral("float")) return 4;
|
||||||
@@ -1535,6 +1594,8 @@ VkDeviceSize VulkanRenderer::GetStd140Alignment(const QString &type) const
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scans GLSL uniform declarations and splits them into samplers and values. This
|
||||||
|
// is intentionally narrow and targets the shader style generated by Oak nodes.
|
||||||
void VulkanRenderer::ExtractUniforms(const QString &glsl,
|
void VulkanRenderer::ExtractUniforms(const QString &glsl,
|
||||||
QVector<UniformInfo> *out_uniforms,
|
QVector<UniformInfo> *out_uniforms,
|
||||||
QVector<QString> *out_samplers) const
|
QVector<QString> *out_samplers) const
|
||||||
@@ -1573,6 +1634,7 @@ void VulkanRenderer::ExtractUniforms(const QString &glsl,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Computes std140 offsets in declaration order and records the total UBO size.
|
||||||
void VulkanRenderer::ComputeUniformLayout(QVector<UniformInfo> *uniforms) const
|
void VulkanRenderer::ComputeUniformLayout(QVector<UniformInfo> *uniforms) const
|
||||||
{
|
{
|
||||||
VkDeviceSize offset = 0;
|
VkDeviceSize offset = 0;
|
||||||
@@ -1585,6 +1647,7 @@ void VulkanRenderer::ComputeUniformLayout(QVector<UniformInfo> *uniforms) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates the uniform block source inserted into rewritten shaders.
|
||||||
QString VulkanRenderer::BuildUboBlock(const QVector<UniformInfo> &uniforms) const
|
QString VulkanRenderer::BuildUboBlock(const QVector<UniformInfo> &uniforms) const
|
||||||
{
|
{
|
||||||
if (uniforms.isEmpty()) {
|
if (uniforms.isEmpty()) {
|
||||||
@@ -1599,6 +1662,8 @@ QString VulkanRenderer::BuildUboBlock(const QVector<UniformInfo> &uniforms) cons
|
|||||||
return ubo;
|
return ubo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rewrites GLSL so non-sampler uniforms live in set=0,binding=0 and sampler
|
||||||
|
// uniforms get deterministic explicit bindings after the UBO.
|
||||||
QString VulkanRenderer::RewriteShaderWithUbo(
|
QString VulkanRenderer::RewriteShaderWithUbo(
|
||||||
const QString &glsl,
|
const QString &glsl,
|
||||||
const QVector<UniformInfo> &all_uniforms,
|
const QVector<UniformInfo> &all_uniforms,
|
||||||
@@ -1658,6 +1723,8 @@ QString VulkanRenderer::RewriteShaderWithUbo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Compiles Vulkan GLSL into SPIR-V using shaderc. Without shaderc this backend
|
||||||
|
// can initialize but cannot create shaders.
|
||||||
bool VulkanRenderer::CompileGlslToSpv(const QString &glsl,
|
bool VulkanRenderer::CompileGlslToSpv(const QString &glsl,
|
||||||
VkShaderStageFlagBits stage,
|
VkShaderStageFlagBits stage,
|
||||||
QByteArray *out_spv)
|
QByteArray *out_spv)
|
||||||
@@ -1724,6 +1791,7 @@ bool VulkanRenderer::CompileGlslToSpv(const QString &glsl,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Converts, compiles, and stores a shader pair plus descriptor metadata.
|
||||||
QVariant VulkanRenderer::CreateNativeShader(olive::ShaderCode code)
|
QVariant VulkanRenderer::CreateNativeShader(olive::ShaderCode code)
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&mutex_);
|
QMutexLocker lock(&mutex_);
|
||||||
@@ -1893,6 +1961,7 @@ QVariant VulkanRenderer::CreateNativeShader(olive::ShaderCode code)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Releases shader modules, descriptor layout, pipeline layout, and pipelines.
|
||||||
void VulkanRenderer::DestroyNativeShader(QVariant shader)
|
void VulkanRenderer::DestroyNativeShader(QVariant shader)
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&mutex_);
|
QMutexLocker lock(&mutex_);
|
||||||
@@ -1922,6 +1991,8 @@ void VulkanRenderer::DestroyNativeShader(QVariant shader)
|
|||||||
delete sh;
|
delete sh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates a graphics pipeline for the destination render format. Viewport and
|
||||||
|
// scissor are dynamic so one pipeline can handle multiple target sizes.
|
||||||
bool VulkanRenderer::CreatePipelineForShader(VulkanShader *shader,
|
bool VulkanRenderer::CreatePipelineForShader(VulkanShader *shader,
|
||||||
const VideoParams &dest_params,
|
const VideoParams &dest_params,
|
||||||
|
|
||||||
@@ -2045,6 +2116,8 @@ bool VulkanRenderer::CreatePipelineForShader(VulkanShader *shader,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executes one fullscreen draw pass. Texture descriptors and a transient UBO are
|
||||||
|
// allocated per pass so iterative shaders can update bindings cheaply.
|
||||||
void VulkanRenderer::BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
|
void VulkanRenderer::BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
|
||||||
const QVector<TextureBinding> &bindings,
|
const QVector<TextureBinding> &bindings,
|
||||||
const QByteArray &ubo_data,
|
const QByteArray &ubo_data,
|
||||||
@@ -2257,6 +2330,8 @@ void VulkanRenderer::BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs a shader job. Multi-iteration jobs ping-pong between temporary textures
|
||||||
|
// and replace the configured iterative input with the previous pass output.
|
||||||
void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
void VulkanRenderer::Blit(QVariant shader_variant, olive::AcceleratedJob &a_job,
|
||||||
olive::Texture *destination,
|
olive::Texture *destination,
|
||||||
VideoParams destination_params,
|
VideoParams destination_params,
|
||||||
|
|||||||
@@ -35,29 +35,45 @@ namespace olive
|
|||||||
class VulkanRenderer : public Renderer {
|
class VulkanRenderer : public Renderer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
// Creates a renderer object; Vulkan objects are created lazily in Init().
|
||||||
explicit VulkanRenderer(QObject *parent = nullptr);
|
explicit VulkanRenderer(QObject *parent = nullptr);
|
||||||
|
// Releases Vulkan objects through the normal Renderer destruction path.
|
||||||
virtual ~VulkanRenderer() override;
|
virtual ~VulkanRenderer() override;
|
||||||
|
|
||||||
|
// Creates the Vulkan instance, logical device, command pool, and descriptor
|
||||||
|
// pool required for offscreen rendering.
|
||||||
virtual bool Init() override;
|
virtual bool Init() override;
|
||||||
|
// Creates reusable GPU resources that require a fully initialized device.
|
||||||
virtual void PostInit() override;
|
virtual void PostInit() override;
|
||||||
|
// Reserved for symmetry with OpenGLRenderer; Vulkan cleanup is handled by
|
||||||
|
// DestroyInternal().
|
||||||
virtual void PostDestroy() override;
|
virtual void PostDestroy() override;
|
||||||
|
|
||||||
|
// Clears either a texture render target or the currently bound output target.
|
||||||
virtual void ClearDestination(olive::Texture *texture = nullptr,
|
virtual void ClearDestination(olive::Texture *texture = nullptr,
|
||||||
double r = 0.0, double g = 0.0,
|
double r = 0.0, double g = 0.0,
|
||||||
double b = 0.0, double a = 0.0) override;
|
double b = 0.0, double a = 0.0) override;
|
||||||
|
|
||||||
|
// Compiles GLSL to SPIR-V, creates shader modules, and prepares descriptor
|
||||||
|
// metadata for later blits.
|
||||||
virtual QVariant CreateNativeShader(olive::ShaderCode code) override;
|
virtual QVariant CreateNativeShader(olive::ShaderCode code) override;
|
||||||
|
// Destroys shader modules, descriptor layout, pipeline layout, and cached
|
||||||
|
// pipelines associated with a shader handle.
|
||||||
virtual void DestroyNativeShader(QVariant shader) override;
|
virtual void DestroyNativeShader(QVariant shader) override;
|
||||||
|
|
||||||
|
// Uploads CPU pixel data to a Vulkan image via a staging buffer.
|
||||||
virtual void UploadToTexture(const QVariant &handle,
|
virtual void UploadToTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, const void *data,
|
const VideoParams ¶ms, const void *data,
|
||||||
int linesize) override;
|
int linesize) override;
|
||||||
|
// Downloads a Vulkan image to CPU memory via a staging buffer.
|
||||||
virtual void DownloadFromTexture(const QVariant &handle,
|
virtual void DownloadFromTexture(const QVariant &handle,
|
||||||
const VideoParams ¶ms, void *data,
|
const VideoParams ¶ms, void *data,
|
||||||
int linesize) override;
|
int linesize) override;
|
||||||
|
|
||||||
|
// Waits for outstanding device work to complete.
|
||||||
virtual void Flush() override;
|
virtual void Flush() override;
|
||||||
|
|
||||||
|
// Reads a single texture pixel using a one-pixel transfer readback.
|
||||||
virtual Color GetPixelFromTexture(olive::Texture *texture,
|
virtual Color GetPixelFromTexture(olive::Texture *texture,
|
||||||
const QPointF &pt) override;
|
const QPointF &pt) override;
|
||||||
|
|
||||||
@@ -67,14 +83,19 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Runs one or more fullscreen shader passes into the destination texture.
|
||||||
virtual void Blit(QVariant shader, olive::AcceleratedJob &job,
|
virtual void Blit(QVariant shader, olive::AcceleratedJob &job,
|
||||||
olive::Texture *destination, VideoParams destination_params,
|
olive::Texture *destination, VideoParams destination_params,
|
||||||
bool clear_destination) override;
|
bool clear_destination) override;
|
||||||
|
// Creates a Vulkan image/view/memory bundle and optionally uploads initial
|
||||||
|
// pixel data.
|
||||||
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
||||||
PixelFormat format, int channel_count,
|
PixelFormat format, int channel_count,
|
||||||
const void *data = nullptr,
|
const void *data = nullptr,
|
||||||
int linesize = 0) override;
|
int linesize = 0) override;
|
||||||
|
// Releases a Vulkan texture bundle.
|
||||||
virtual void DestroyNativeTexture(QVariant texture) override;
|
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||||
|
// Releases all Vulkan device resources owned by this renderer.
|
||||||
virtual void DestroyInternal() override;
|
virtual void DestroyInternal() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -82,62 +103,99 @@ private:
|
|||||||
struct VulkanShader;
|
struct VulkanShader;
|
||||||
struct UniformInfo;
|
struct UniformInfo;
|
||||||
|
|
||||||
|
// Creates the Vulkan instance used for all offscreen work.
|
||||||
bool CreateInstance();
|
bool CreateInstance();
|
||||||
|
// Chooses a graphics-capable physical device and creates the logical device.
|
||||||
bool CreateDevice();
|
bool CreateDevice();
|
||||||
|
// Creates a command pool for short-lived command buffers.
|
||||||
bool CreateCommandPool();
|
bool CreateCommandPool();
|
||||||
|
// Creates the descriptor pool used for per-blit UBO/sampler sets.
|
||||||
bool CreateDescriptorPool();
|
bool CreateDescriptorPool();
|
||||||
|
// Uploads the fullscreen quad vertex buffer used by BlitPass().
|
||||||
bool CreateVertexBuffer();
|
bool CreateVertexBuffer();
|
||||||
|
// Creates the persistent linear sampler.
|
||||||
bool CreateLinearSampler();
|
bool CreateLinearSampler();
|
||||||
|
// Creates the persistent nearest-neighbor sampler.
|
||||||
bool CreateNearestSampler();
|
bool CreateNearestSampler();
|
||||||
|
// Returns the persistent sampler matching the requested interpolation mode.
|
||||||
VkSampler GetSampler(Texture::Interpolation interpolation) const;
|
VkSampler GetSampler(Texture::Interpolation interpolation) const;
|
||||||
|
// Allocates a host-visible staging buffer for upload/download transfers.
|
||||||
bool CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
bool CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
||||||
VkDeviceMemory *out_memory);
|
VkDeviceMemory *out_memory);
|
||||||
|
// Destroys a staging buffer pair allocated by CreateStagingBuffer().
|
||||||
void DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory);
|
void DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory);
|
||||||
|
|
||||||
|
// Begins a one-shot command buffer and records it immediately.
|
||||||
VkCommandBuffer BeginOneTimeCommands();
|
VkCommandBuffer BeginOneTimeCommands();
|
||||||
|
// Submits and waits for a one-shot command buffer.
|
||||||
void EndOneTimeCommands(VkCommandBuffer cmd);
|
void EndOneTimeCommands(VkCommandBuffer cmd);
|
||||||
|
|
||||||
|
// Emits an image memory barrier for the subset of layouts this renderer uses.
|
||||||
void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
||||||
VkImageLayout old_layout,
|
VkImageLayout old_layout,
|
||||||
VkImageLayout new_layout);
|
VkImageLayout new_layout);
|
||||||
|
// Records a tightly packed buffer-to-image copy.
|
||||||
void CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer, VkImage image,
|
void CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer, VkImage image,
|
||||||
uint32_t width, uint32_t height, uint32_t depth);
|
uint32_t width, uint32_t height, uint32_t depth);
|
||||||
|
// Records an image-to-buffer copy, optionally reading one pixel offset.
|
||||||
void CopyImageToBuffer(VkCommandBuffer cmd, VkImage image, VkBuffer buffer,
|
void CopyImageToBuffer(VkCommandBuffer cmd, VkImage image, VkBuffer buffer,
|
||||||
uint32_t width, uint32_t height,
|
uint32_t width, uint32_t height,
|
||||||
uint32_t offset_x = 0, uint32_t offset_y = 0);
|
uint32_t offset_x = 0, uint32_t offset_y = 0);
|
||||||
|
|
||||||
|
// Converts Oak pixel format/channel metadata to a preferred Vulkan format.
|
||||||
VkFormat PixelFormatToVkFormat(PixelFormat format, int channel_count) const;
|
VkFormat PixelFormatToVkFormat(PixelFormat format, int channel_count) const;
|
||||||
|
// Picks a color-attachment-capable format, falling back from RGB to RGBA
|
||||||
|
// where drivers do not support 3-channel render targets.
|
||||||
VkFormat PickRenderableFormat(PixelFormat format, int channel_count) const;
|
VkFormat PickRenderableFormat(PixelFormat format, int channel_count) const;
|
||||||
|
// Checks whether a format can be used as a render target.
|
||||||
bool IsColorAttachmentSupported(VkFormat format) const;
|
bool IsColorAttachmentSupported(VkFormat format) const;
|
||||||
|
// Returns the packed byte size for supported VkFormat values.
|
||||||
int GetVkFormatBytesPerPixel(VkFormat format) const;
|
int GetVkFormatBytesPerPixel(VkFormat format) const;
|
||||||
|
// Returns the alpha fill value used when expanding RGB data to RGBA.
|
||||||
float GetFormatMaxAlpha(PixelFormat format) const;
|
float GetFormatMaxAlpha(PixelFormat format) const;
|
||||||
|
// Repackages tightly packed pixels when the requested CPU channel count
|
||||||
|
// differs from the selected GPU format channel count.
|
||||||
void CopyPixelsWithChannelConversion(const void *src, void *dst,
|
void CopyPixelsWithChannelConversion(const void *src, void *dst,
|
||||||
int width, int height, int depth,
|
int width, int height, int depth,
|
||||||
int src_channels, int dst_channels,
|
int src_channels, int dst_channels,
|
||||||
PixelFormat format) const;
|
PixelFormat format) const;
|
||||||
|
// Rounds a size up to the requested alignment.
|
||||||
VkDeviceSize AlignSize(VkDeviceSize size, VkDeviceSize alignment) const;
|
VkDeviceSize AlignSize(VkDeviceSize size, VkDeviceSize alignment) const;
|
||||||
|
|
||||||
|
// Finds a Vulkan memory type matching the requested properties.
|
||||||
uint32_t FindMemoryType(uint32_t type_filter,
|
uint32_t FindMemoryType(uint32_t type_filter,
|
||||||
VkMemoryPropertyFlags properties) const;
|
VkMemoryPropertyFlags properties) const;
|
||||||
|
|
||||||
|
// Compiles GLSL source into SPIR-V using shaderc when available.
|
||||||
bool CompileGlslToSpv(const QString &glsl, VkShaderStageFlagBits stage,
|
bool CompileGlslToSpv(const QString &glsl, VkShaderStageFlagBits stage,
|
||||||
QByteArray *out_spv);
|
QByteArray *out_spv);
|
||||||
|
// Rewrites an Oak GLSL shader into Vulkan-compatible GLSL.
|
||||||
QString ConvertGlslToVulkan(const QString &glsl, VkShaderStageFlagBits stage);
|
QString ConvertGlslToVulkan(const QString &glsl, VkShaderStageFlagBits stage);
|
||||||
|
// Ensures a shader declares a Vulkan-compatible GLSL version.
|
||||||
QString EnsureGlslVersion450(const QString &glsl) const;
|
QString EnsureGlslVersion450(const QString &glsl) const;
|
||||||
|
// Extracts uniforms and sampler names from GLSL declarations.
|
||||||
void ExtractUniforms(const QString &glsl, QVector<UniformInfo> *out_uniforms,
|
void ExtractUniforms(const QString &glsl, QVector<UniformInfo> *out_uniforms,
|
||||||
QVector<QString> *out_samplers) const;
|
QVector<QString> *out_samplers) const;
|
||||||
|
// Computes std140 offsets and total UBO size for extracted uniforms.
|
||||||
void ComputeUniformLayout(QVector<UniformInfo> *uniforms) const;
|
void ComputeUniformLayout(QVector<UniformInfo> *uniforms) const;
|
||||||
|
// Builds the generated uniform block used by rewritten shaders.
|
||||||
QString BuildUboBlock(const QVector<UniformInfo> &uniforms) const;
|
QString BuildUboBlock(const QVector<UniformInfo> &uniforms) const;
|
||||||
|
// Rewrites standalone uniforms and samplers into explicit UBO/sampler
|
||||||
|
// bindings accepted by Vulkan GLSL.
|
||||||
QString RewriteShaderWithUbo(const QString &glsl,
|
QString RewriteShaderWithUbo(const QString &glsl,
|
||||||
const QVector<UniformInfo> &all_uniforms,
|
const QVector<UniformInfo> &all_uniforms,
|
||||||
const QHash<QString, int> &sampler_bindings) const;
|
const QHash<QString, int> &sampler_bindings) const;
|
||||||
|
// Returns std140 storage size for a supported GLSL type.
|
||||||
VkDeviceSize GetStd140Size(const QString &type) const;
|
VkDeviceSize GetStd140Size(const QString &type) const;
|
||||||
|
// Returns std140 alignment for a supported GLSL type.
|
||||||
VkDeviceSize GetStd140Alignment(const QString &type) const;
|
VkDeviceSize GetStd140Alignment(const QString &type) const;
|
||||||
|
|
||||||
|
// Creates or retrieves the graphics pipeline for a shader/render format pair.
|
||||||
bool CreatePipelineForShader(VulkanShader *shader,
|
bool CreatePipelineForShader(VulkanShader *shader,
|
||||||
const VideoParams &dest_params,
|
const VideoParams &dest_params,
|
||||||
VkFormat render_pass_format);
|
VkFormat render_pass_format);
|
||||||
|
|
||||||
|
// Caches simple single-color-attachment render passes by format/clear mode.
|
||||||
VkRenderPass GetOrCreateRenderPass(VkFormat format, bool clear);
|
VkRenderPass GetOrCreateRenderPass(VkFormat format, bool clear);
|
||||||
|
|
||||||
struct TextureBinding {
|
struct TextureBinding {
|
||||||
@@ -146,6 +204,7 @@ private:
|
|||||||
Texture::Interpolation interp;
|
Texture::Interpolation interp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Executes one fullscreen pass with the provided texture bindings and UBO.
|
||||||
void BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
|
void BlitPass(VulkanShader *shader, VulkanTexture *dest_tex,
|
||||||
const QVector<TextureBinding> &bindings,
|
const QVector<TextureBinding> &bindings,
|
||||||
const QByteArray &ubo_data,
|
const QByteArray &ubo_data,
|
||||||
|
|||||||
@@ -430,6 +430,10 @@ void ViewerDisplayWidget::OnPaint()
|
|||||||
if (texture && texture->renderer() &&
|
if (texture && texture->renderer() &&
|
||||||
texture->renderer() != renderer()) {
|
texture->renderer() != renderer()) {
|
||||||
if (texture->renderer()->IsOpenGL() && renderer()->IsOpenGL()) {
|
if (texture->renderer()->IsOpenGL() && renderer()->IsOpenGL()) {
|
||||||
|
// Shared OpenGL contexts can display the producer texture
|
||||||
|
// directly. Avoid readback here because the producer
|
||||||
|
// renderer may belong to a render thread whose context
|
||||||
|
// cannot be made current from the GUI paint callback.
|
||||||
texture_ = texture;
|
texture_ = texture;
|
||||||
} else {
|
} else {
|
||||||
// Cross-backend texture: download and re-upload
|
// Cross-backend texture: download and re-upload
|
||||||
@@ -1394,6 +1398,8 @@ void ViewerDisplayWidget::DrawBlank(const VideoParams &device_params)
|
|||||||
renderer()->Blit(blank_shader_, job, device_params, false);
|
renderer()->Blit(blank_shader_, job, device_params, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renders a backend-neutral frame by drawing into an offscreen backend texture,
|
||||||
|
// downloading it to CPU memory, then painting that image with QPainter.
|
||||||
void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
||||||
QPainter *painter)
|
QPainter *painter)
|
||||||
{
|
{
|
||||||
@@ -1412,6 +1418,8 @@ void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
|||||||
|
|
||||||
if (!backend_neutral_texture_ ||
|
if (!backend_neutral_texture_ ||
|
||||||
backend_neutral_texture_->params() != offscreen_params) {
|
backend_neutral_texture_->params() != offscreen_params) {
|
||||||
|
// The offscreen texture is sized in device pixels so high-DPI widgets
|
||||||
|
// draw one downloaded pixel per device pixel after setDevicePixelRatio().
|
||||||
backend_neutral_texture_ = renderer()->CreateTexture(offscreen_params);
|
backend_neutral_texture_ = renderer()->CreateTexture(offscreen_params);
|
||||||
backend_neutral_buffer_.resize(
|
backend_neutral_buffer_.resize(
|
||||||
texture_width * texture_height *
|
texture_width * texture_height *
|
||||||
@@ -1426,6 +1434,8 @@ void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
|||||||
ColorTransformJob local_ctj = ctj;
|
ColorTransformJob local_ctj = ctj;
|
||||||
local_ctj.SetClearDestinationEnabled(true);
|
local_ctj.SetClearDestinationEnabled(true);
|
||||||
|
|
||||||
|
// Reuse the normal color-management shader path, but render into a texture
|
||||||
|
// instead of an OpenGL widget framebuffer.
|
||||||
renderer()->BlitColorManaged(local_ctj, backend_neutral_texture_.get());
|
renderer()->BlitColorManaged(local_ctj, backend_neutral_texture_.get());
|
||||||
|
|
||||||
backend_neutral_texture_->Download(backend_neutral_buffer_.data(), 0);
|
backend_neutral_texture_->Download(backend_neutral_buffer_.data(), 0);
|
||||||
@@ -1440,6 +1450,8 @@ void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
|||||||
QImage::Format_RGBA8888_Premultiplied);
|
QImage::Format_RGBA8888_Premultiplied);
|
||||||
img.setDevicePixelRatio(devicePixelRatioF());
|
img.setDevicePixelRatio(devicePixelRatioF());
|
||||||
|
|
||||||
|
// QImage references backend_neutral_buffer_ directly; draw it before the
|
||||||
|
// buffer can be resized or reused by a later paint.
|
||||||
painter->drawImage(QPoint(0, 0), img);
|
painter->drawImage(QPoint(0, 0), img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +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 渲染 |
|
| Linux + Vulkan 驱动 | Vulkan 后端加载、viewer readback、代理、导出、OpenGL 回退 | 需先用 `vulkaninfo --summary` 确认可创建 Vulkan instance/device |
|
||||||
| 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 请求、驱动缺失回退 | 重点看设置持久化和启动稳定性 |
|
| Windows + Vulkan Runtime | Vulkan 后端加载、viewer readback、驱动缺失回退 | 重点看设置持久化、启动稳定性和显卡驱动兼容性 |
|
||||||
|
|
||||||
## 测试素材准备
|
## 测试素材准备
|
||||||
|
|
||||||
@@ -359,9 +359,14 @@
|
|||||||
|
|
||||||
通过标准:内存没有持续不可控增长;播放停止后应用仍可操作和保存。
|
通过标准:内存没有持续不可控增长;播放停止后应用仍可操作和保存。
|
||||||
|
|
||||||
## 10. 图形后端选择测试
|
## 10. 图形后端与 Vulkan 手工测试
|
||||||
|
|
||||||
当前版本允许用户在 Preferences 中选择 OpenGL 或 Vulkan。注意:Vulkan 入口目前是实验性图形 API 请求和后续 VulkanRenderer 的接入点;默认构建下时间线/viewer 渲染仍应安全回退到现有 OpenGL renderer。动态后端适配器通过 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 实验开关接入,测试重点是“用户可选择、设置可持久化、Vulkan 请求不破坏现有渲染、失败可回退”。
|
当前版本允许用户在 Preferences 中选择 OpenGL 或 Vulkan。动态后端适配器通过 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 接入 `liboakgl` / `liboakvulkan`。Vulkan 后端已具备 offscreen texture、upload/download、shader Blit、viewer backend-neutral readback 的代码路径,但完整 UI 播放、代理、导出、Scope 和 OpenFX CPU 回退仍需要在真实显示环境与可用 Vulkan runtime 上手工验收。
|
||||||
|
|
||||||
|
Vulkan 测试必须先区分两类环境:
|
||||||
|
|
||||||
|
- 可用 Vulkan 环境:`vulkaninfo --summary` 能成功列出 instance、physical device、driver 和 queue family。
|
||||||
|
- 不可用 Vulkan 环境:缺少 runtime/ICD、驱动损坏,或 `vulkaninfo --summary` 报 `Found no drivers` / `ERROR_INCOMPATIBLE_DRIVER`。这类环境只测试回退,不应把 Vulkan 渲染用例记为通过。
|
||||||
|
|
||||||
### 10.1 默认 OpenGL 后端
|
### 10.1 默认 OpenGL 后端
|
||||||
|
|
||||||
@@ -373,7 +378,17 @@
|
|||||||
|
|
||||||
通过标准:默认值为 OpenGL;viewer、Scope、调色和播放行为与原 OpenGL 路径一致。
|
通过标准:默认值为 OpenGL;viewer、Scope、调色和播放行为与原 OpenGL 路径一致。
|
||||||
|
|
||||||
### 10.2 切换到 Vulkan 并重启
|
### 10.2 Vulkan 运行时预检
|
||||||
|
|
||||||
|
1. 在待测机器上运行 `vulkaninfo --summary`。
|
||||||
|
2. 记录 GPU 型号、Vulkan API 版本、driver 版本、ICD 文件路径。
|
||||||
|
3. 确认应用构建产物中存在 Oak 私有 Vulkan 后端库:Linux 为 `liboakvulkan.so`,macOS 为 `liboakvulkan.dylib`,Windows 为 `oakvulkan.dll`。
|
||||||
|
4. 运行动态后端 gtest:`olive-gtest --gtest_filter='DynamicRenderBackend.*'`。
|
||||||
|
5. 检查 Vulkan 用例是实际执行还是 SKIP。
|
||||||
|
|
||||||
|
通过标准:可用 Vulkan 环境下 `vulkaninfo` 成功,`liboakvulkan` 存在,Vulkan gtest 至少执行 backend load、upload/download、Blit 相关用例;不可用 Vulkan 环境下测试必须明确记录 driver/runtime 错误,Vulkan gtest 可 SKIP,但 OpenGL fallback 用例必须通过。
|
||||||
|
|
||||||
|
### 10.3 切换到 Vulkan 并重启
|
||||||
|
|
||||||
1. 在 Preferences > Behavior > Rendering 中选择 `Vulkan (experimental)`。
|
1. 在 Preferences > Behavior > Rendering 中选择 `Vulkan (experimental)`。
|
||||||
2. 确认设置保存。
|
2. 确认设置保存。
|
||||||
@@ -382,9 +397,81 @@
|
|||||||
5. 导入并播放 `4k_camera_a.mov`。
|
5. 导入并播放 `4k_camera_a.mov`。
|
||||||
6. 检查日志,确认 `RenderManager` 报告的实际后端与回退结果一致。
|
6. 检查日志,确认 `RenderManager` 报告的实际后端与回退结果一致。
|
||||||
|
|
||||||
通过标准:Vulkan 选择可持久化;重启后应用不崩溃;当前 Vulkan 未完整验证时应明确回退 OpenGL 渲染,`RenderManager::backend()` 必须反映实际运行后端(回退后应为 OpenGL),播放仍可用。
|
通过标准:Vulkan 选择可持久化;重启后应用不崩溃;可用 Vulkan 环境下日志显示动态 Vulkan 后端加载并初始化成功,`RenderManager::backend()` 报告 Vulkan;不可用 Vulkan 环境下应明确回退 OpenGL,`RenderManager::backend()` 必须反映实际运行后端,播放仍可用。
|
||||||
|
|
||||||
### 10.3 Vulkan 驱动缺失或不可用
|
### 10.4 Vulkan Viewer 基础播放
|
||||||
|
|
||||||
|
1. 在可用 Vulkan 环境中选择 Vulkan 并重启。
|
||||||
|
2. 导入 `color_chart.mov` 和 `4k_camera_a.mov`。
|
||||||
|
3. 将两个 clip 放入时间线,打开 Viewer。
|
||||||
|
4. 播放 10 秒,期间执行暂停、继续播放、逐帧前进、拖动时间线、缩放 Viewer。
|
||||||
|
5. 打开/关闭全屏或浮动 Viewer 窗口。
|
||||||
|
6. 观察画面是否黑屏、闪烁、残留上一帧或颜色明显错误。
|
||||||
|
|
||||||
|
通过标准:Viewer 通过 Vulkan backend-neutral readback 路径正常显示,播放和 seek 不崩溃;画面比例、裁切、缩放和 device pixel ratio 正常;没有长期黑屏、上一帧残留或 UI 死锁。
|
||||||
|
|
||||||
|
### 10.5 Vulkan 调色/LUT 显示一致性
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 将 `color_chart.mov` 放入时间线。
|
||||||
|
3. 加载 `lut_valid.cube`,再做一次三向色轮明显调整。
|
||||||
|
4. 在同一帧记录 Viewer 截图或视觉观察结果。
|
||||||
|
5. 切回 OpenGL 重启,打开同一项目并定位同一帧。
|
||||||
|
6. 对比 Vulkan 和 OpenGL 的画面颜色、亮度和透明度表现。
|
||||||
|
|
||||||
|
通过标准:Vulkan 与 OpenGL 预览颜色方向一致,LUT 和三向色轮均生效;不要求像素完全一致,但不能出现通道错乱、alpha 错误、明显 gamma 反转或 LUT 失效。
|
||||||
|
|
||||||
|
### 10.6 Vulkan 代理媒体与重素材播放
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 对 `8k_or_heavy_camera.mov` 生成代理并启用代理。
|
||||||
|
3. 播放代理路径 30 秒,期间拖动时间线和缩放 Viewer。
|
||||||
|
4. 关闭代理,播放原片路径 10 秒。
|
||||||
|
5. 保存、关闭并重开项目,确认代理状态仍正确。
|
||||||
|
|
||||||
|
通过标准:启用代理后 Viewer 可播放且不崩溃;禁用代理后回到原片路径;保存重开后代理状态一致;Vulkan 路径不应把导出源降级为代理。
|
||||||
|
|
||||||
|
### 10.7 Vulkan 软件导出
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 创建 10 秒 sequence,包含 `color_chart.mov`、LUT、三向调色、一个代理 clip 和一段音频。
|
||||||
|
3. 执行软件编码导出 H.264 或 ProRes。
|
||||||
|
4. 用播放器检查导出文件。
|
||||||
|
5. 使用 OpenGL 后端重复导出同一段作为对照。
|
||||||
|
|
||||||
|
通过标准:Vulkan 下导出成功,输出可播放,音画同步不超过 1 帧;颜色处理和 OpenGL 导出方向一致;启用代理时导出仍使用原片质量路径;失败时有明确错误,不生成损坏的完成文件。
|
||||||
|
|
||||||
|
### 10.8 Vulkan Scope 行为
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 打开 Waveform、Vectorscope、Histogram。
|
||||||
|
3. 播放 `color_chart.mov` 并调整 LUT/三向色轮。
|
||||||
|
4. 观察 Scope 面板行为。
|
||||||
|
5. 切回 OpenGL 后重复同一操作。
|
||||||
|
|
||||||
|
通过标准:当前 backend-neutral Scope 若仍是安全跳过,应明确记录为已知限制,且不能崩溃或卡死;OpenGL 下 Scope 必须正常更新。若 Vulkan Scope 已实现,则三类 Scope 必须随当前帧和调色变化更新。
|
||||||
|
|
||||||
|
### 10.9 Vulkan OpenFX CPU 回退
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 在 clip 上添加一个已知可用的 OFX 插件,优先选择支持 CPU 渲染且效果明显的插件。
|
||||||
|
3. 播放并导出 5 秒片段。
|
||||||
|
4. 检查日志中 OpenGL OFX render 是否被禁用,插件是否走 CPU readback/upload 路径。
|
||||||
|
5. 切回 OpenGL,确认支持 OpenGL render 的插件仍能走 OpenGL 输出绑定路径。
|
||||||
|
|
||||||
|
通过标准:Vulkan 下 OFX 插件不因缺少 OpenGL context 而被跳过或崩溃;CPU 回退输出可见且可导出;OpenGL 下原有 OFX OpenGL 路径不回退或失效。
|
||||||
|
|
||||||
|
### 10.10 Vulkan 后端长时间稳定性
|
||||||
|
|
||||||
|
1. 选择 Vulkan 并重启。
|
||||||
|
2. 打开包含 4K/8K、LUT、代理、音频和至少 10 个 clip 的项目。
|
||||||
|
3. 循环播放 20 分钟。
|
||||||
|
4. 期间反复 seek、切换代理、打开/关闭 Viewer、打开/关闭导出窗口。
|
||||||
|
5. 观察日志、显存/内存占用和 UI 响应。
|
||||||
|
|
||||||
|
通过标准:无崩溃、无持续不可控内存增长、无明显 Vulkan validation/driver error;停止播放后仍可保存项目和退出应用。
|
||||||
|
|
||||||
|
### 10.11 Vulkan 驱动缺失或不可用
|
||||||
|
|
||||||
1. 在没有 Vulkan Runtime 或驱动不可用的机器上选择 Vulkan。
|
1. 在没有 Vulkan Runtime 或驱动不可用的机器上选择 Vulkan。
|
||||||
2. 重启 Oak。
|
2. 重启 Oak。
|
||||||
@@ -394,7 +481,7 @@
|
|||||||
|
|
||||||
通过标准:应用可以启动;日志应说明 Vulkan 请求不可完全满足或当前回退 OpenGL;`RenderManager::backend()` 必须与实际运行后端一致;用户能回到 Preferences 改回 OpenGL。
|
通过标准:应用可以启动;日志应说明 Vulkan 请求不可完全满足或当前回退 OpenGL;`RenderManager::backend()` 必须与实际运行后端一致;用户能回到 Preferences 改回 OpenGL。
|
||||||
|
|
||||||
### 10.4 从 Vulkan 切回 OpenGL
|
### 10.12 从 Vulkan 切回 OpenGL
|
||||||
|
|
||||||
1. 在 Vulkan 已选中状态下打开 Preferences。
|
1. 在 Vulkan 已选中状态下打开 Preferences。
|
||||||
2. 将 Graphics Backend 改为 OpenGL。
|
2. 将 Graphics Backend 改为 OpenGL。
|
||||||
@@ -403,7 +490,7 @@
|
|||||||
|
|
||||||
通过标准:重启后显示 OpenGL;播放和导出正常;不会保留错误的 Vulkan 状态。
|
通过标准:重启后显示 OpenGL;播放和导出正常;不会保留错误的 Vulkan 状态。
|
||||||
|
|
||||||
### 10.5 代理、Scope 与调色组合
|
### 10.13 代理、Scope 与调色组合回归
|
||||||
|
|
||||||
1. 选择 Vulkan 并重启。
|
1. 选择 Vulkan 并重启。
|
||||||
2. 对 `8k_or_heavy_camera.mov` 生成并启用代理。
|
2. 对 `8k_or_heavy_camera.mov` 生成并启用代理。
|
||||||
@@ -413,7 +500,7 @@
|
|||||||
|
|
||||||
通过标准:Vulkan 请求状态下代理、Scope、调色不崩溃;切回 OpenGL 后项目状态一致;两种选择下导出默认仍使用原片。
|
通过标准:Vulkan 请求状态下代理、Scope、调色不崩溃;切回 OpenGL 后项目状态一致;两种选择下导出默认仍使用原片。
|
||||||
|
|
||||||
### 10.6 动态 OpenGL 后端加载
|
### 10.14 动态 OpenGL 后端加载
|
||||||
|
|
||||||
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
2. 确认应用目录存在 Oak 私有 OpenGL 后端库,例如 `liboakgl.so`、`liboakgl.dylib` 或 `oakgl.dll`。
|
2. 确认应用目录存在 Oak 私有 OpenGL 后端库,例如 `liboakgl.so`、`liboakgl.dylib` 或 `oakgl.dll`。
|
||||||
@@ -423,7 +510,7 @@
|
|||||||
|
|
||||||
通过标准:日志显示动态 OpenGL 后端加载成功;viewer、Scope、调色和播放行为与默认 OpenGL 路径一致;退出时执行 destroy/unload 无崩溃。
|
通过标准:日志显示动态 OpenGL 后端加载成功;viewer、Scope、调色和播放行为与默认 OpenGL 路径一致;退出时执行 destroy/unload 无崩溃。
|
||||||
|
|
||||||
### 10.7 动态后端缺失或损坏
|
### 10.15 动态后端缺失或损坏
|
||||||
|
|
||||||
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
2. 临时移走或重命名 Oak 私有 OpenGL 后端库。
|
2. 临时移走或重命名 Oak 私有 OpenGL 后端库。
|
||||||
@@ -432,14 +519,35 @@
|
|||||||
|
|
||||||
通过标准:应用不能静默崩溃;日志明确说明后端库加载失败;用户能够恢复库文件或切回默认构建继续打开项目。
|
通过标准:应用不能静默崩溃;日志明确说明后端库加载失败;用户能够恢复库文件或切回默认构建继续打开项目。
|
||||||
|
|
||||||
### 10.8 Vulkan 动态后端占位
|
### 10.16 Vulkan 动态后端库缺失或不可加载
|
||||||
|
|
||||||
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
1. 使用开启 `OAK_ENABLE_DYNAMIC_RENDER_BACKEND` 的实验构建。
|
||||||
2. 在 Preferences 中选择 Vulkan 并重启。
|
2. 在 Preferences 中选择 Vulkan 并重启。
|
||||||
3. 如果 `liboakvulkan` 尚未实现,观察回退行为。
|
3. 临时移走、重命名或替换为损坏的 `liboakvulkan`。
|
||||||
4. 切回 OpenGL 并重启。
|
4. 再次启动 Oak 并打开项目。
|
||||||
|
5. 观察回退行为。
|
||||||
|
6. 恢复 `liboakvulkan` 后切回 OpenGL 并重启。
|
||||||
|
|
||||||
通过标准:Vulkan 后端未实现时不崩溃;日志明确说明 Vulkan 后端缺失并回退或拒绝初始化;切回 OpenGL 后项目可播放。
|
通过标准:Vulkan 后端库缺失、损坏或符号不完整时不崩溃;日志明确说明 Vulkan 后端加载失败并回退或拒绝初始化;切回 OpenGL 后项目可播放。
|
||||||
|
|
||||||
|
### 10.17 Vulkan 与 OpenGL 结果记录
|
||||||
|
|
||||||
|
1. 对同一项目分别在 Vulkan 和 OpenGL 下执行 Viewer 播放、5 秒软件导出、代理启用导出。
|
||||||
|
2. 记录每个环境的实际 backend、GPU、driver、Vulkan API 版本和是否发生回退。
|
||||||
|
3. 对比导出文件的分辨率、帧率、时长、音频流和视觉结果。
|
||||||
|
4. 将差异记录到缺陷模板。
|
||||||
|
|
||||||
|
通过标准:每次测试结果能明确区分“真实 Vulkan 后端通过”、“请求 Vulkan 但回退 OpenGL 通过”和“Vulkan 后端失败”;不能把回退 OpenGL 的结果记为 Vulkan 渲染通过。
|
||||||
|
|
||||||
|
### 10.18 回退链路恢复
|
||||||
|
|
||||||
|
1. 在可用 Vulkan 环境中选择 Vulkan 并确认实际使用 Vulkan。
|
||||||
|
2. 退出应用,临时破坏 Vulkan runtime 或移走 `liboakvulkan`。
|
||||||
|
3. 启动应用并确认回退 OpenGL。
|
||||||
|
4. 切回 OpenGL 并重启。
|
||||||
|
5. 恢复 Vulkan runtime 和 `liboakvulkan`,再次选择 Vulkan 重启。
|
||||||
|
|
||||||
|
通过标准:回退和恢复路径都不破坏用户配置和项目文件;日志能解释每次实际使用的 backend;用户始终能回到可播放的 OpenGL 状态。
|
||||||
|
|
||||||
## 缺陷记录模板
|
## 缺陷记录模板
|
||||||
|
|
||||||
@@ -452,12 +560,13 @@
|
|||||||
- 预期结果和实际结果。
|
- 预期结果和实际结果。
|
||||||
- 是否可稳定复现。
|
- 是否可稳定复现。
|
||||||
- 如果涉及导出,附 ffprobe 输出和导出设置截图。
|
- 如果涉及导出,附 ffprobe 输出和导出设置截图。
|
||||||
|
- 如果涉及 Vulkan,附 `vulkaninfo --summary` 输出、实际 backend 日志、是否发生 OpenGL 回退。
|
||||||
|
|
||||||
## 发布前最低通过线
|
## 发布前最低通过线
|
||||||
|
|
||||||
- 预检、LUT、三向色轮、三类 Scope、波形同步、BWF 时间码、音频表、代理生成/启用/删除、软件导出全部通过。
|
- 预检、LUT、三向色轮、三类 Scope、波形同步、BWF 时间码、音频表、代理生成/启用/删除、软件导出全部通过。
|
||||||
- 至少一个硬件编码环境通过 NVENC 或 VideoToolbox。
|
- 至少一个硬件编码环境通过 NVENC 或 VideoToolbox。
|
||||||
- OpenGL/Vulkan 图形后端选择、持久化和 Vulkan 请求回退测试通过。
|
- OpenGL/Vulkan 图形后端选择、持久化、Vulkan 可用环境实渲染和 Vulkan 不可用环境回退测试通过;若无可用 Vulkan 环境,发布记录必须明确标注 Vulkan 实渲染未验收。
|
||||||
- 批量队列至少通过多任务执行和取消测试。
|
- 批量队列至少通过多任务执行和取消测试。
|
||||||
- 组合回归测试中的完整剪辑链路通过。
|
- 组合回归测试中的完整剪辑链路通过。
|
||||||
- 所有失败项有明确 issue 或文档化限制,不存在“无提示崩溃”级别问题。
|
- 所有失败项有明确 issue 或文档化限制,不存在“无提示崩溃”级别问题。
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
#include "render/texture.h"
|
#include "render/texture.h"
|
||||||
#include "render/videoparams.h"
|
#include "render/videoparams.h"
|
||||||
|
|
||||||
|
// Verifies that the dynamic adapter can load the private OpenGL backend and
|
||||||
|
// query its advertised C ABI capabilities without creating a viewer context.
|
||||||
TEST(DynamicRenderBackend, LoadsExperimentalOpenGLBackend)
|
TEST(DynamicRenderBackend, LoadsExperimentalOpenGLBackend)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -30,6 +32,8 @@ TEST(DynamicRenderBackend, LoadsExperimentalOpenGLBackend)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies Vulkan backend discovery on systems with a working Vulkan ICD. The
|
||||||
|
// test skips when the runtime correctly reports Vulkan as unavailable.
|
||||||
TEST(DynamicRenderBackend, LoadsExperimentalVulkanBackendWhenAvailable)
|
TEST(DynamicRenderBackend, LoadsExperimentalVulkanBackendWhenAvailable)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -52,6 +56,8 @@ TEST(DynamicRenderBackend, LoadsExperimentalVulkanBackendWhenAvailable)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies that requesting Vulkan on systems without a usable runtime falls
|
||||||
|
// back to OpenGL and reports the effective backend name.
|
||||||
TEST(DynamicRenderBackend, FallsBackWhenExperimentalVulkanUnavailable)
|
TEST(DynamicRenderBackend, FallsBackWhenExperimentalVulkanUnavailable)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -71,6 +77,8 @@ TEST(DynamicRenderBackend, FallsBackWhenExperimentalVulkanUnavailable)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exercises the minimal Vulkan render loop: upload a texture, run a pass-through
|
||||||
|
// shader blit, then download the destination and verify pixel data.
|
||||||
TEST(DynamicRenderBackend, VulkanUploadBlitDownload)
|
TEST(DynamicRenderBackend, VulkanUploadBlitDownload)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -147,6 +155,8 @@ TEST(DynamicRenderBackend, VulkanUploadBlitDownload)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures the Vulkan backend handles null-destination blits by rendering to a
|
||||||
|
// temporary offscreen target instead of dereferencing a missing framebuffer.
|
||||||
TEST(DynamicRenderBackend, VulkanNullDestinationBlitDoesNotCrash)
|
TEST(DynamicRenderBackend, VulkanNullDestinationBlitDoesNotCrash)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -210,6 +220,8 @@ TEST(DynamicRenderBackend, VulkanNullDestinationBlitDoesNotCrash)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies iterative shader support: the first pass writes to a temporary
|
||||||
|
// texture and the second pass samples it before writing the final destination.
|
||||||
TEST(DynamicRenderBackend, VulkanIterativeBlitPingPong)
|
TEST(DynamicRenderBackend, VulkanIterativeBlitPingPong)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
@@ -289,6 +301,8 @@ TEST(DynamicRenderBackend, VulkanIterativeBlitPingPong)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verifies RGB upload/download when the Vulkan driver stores the texture in a
|
||||||
|
// wider renderable format such as RGBA.
|
||||||
TEST(DynamicRenderBackend, VulkanUploadDownloadThreeChannel)
|
TEST(DynamicRenderBackend, VulkanUploadDownloadThreeChannel)
|
||||||
{
|
{
|
||||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// Configures process-wide Qt/OCIO state before running gtest. Tests default to
|
||||||
|
// the offscreen QPA plugin so headless or invalid DISPLAY sessions do not abort
|
||||||
|
// before gtest can report skips/failures.
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
Q_INIT_RESOURCE(ocioconf);
|
Q_INIT_RESOURCE(ocioconf);
|
||||||
|
|||||||
Reference in New Issue
Block a user