feat(render): dynamic OpenGL/Vulkan backend split and backend-neutral viewer
- Extract libolive-rendercore static library to minimize backend link boundary. - Add DynamicRenderer adapter with C ABI (oakgl/oakvulkan shared libs). - Make OAK_ENABLE_DYNAMIC_RENDER_BACKEND default ON with OpenGL fallback. - Implement VulkanRenderer prototype (textures, shaders, UBO blit, readback). - Add backend-neutral viewer readback path (offscreen -> QImage -> QPainter). - Refactor PluginRenderer to be renderer-agnostic; OFX plugins fall back to CPU path on non-OpenGL backends while preserving OpenGL render path. - Add Renderer::AttachOutputTexture/DetachOutputTexture and C ABI forwards. - Update docs/zh/render-backend-dynamic-plan.md for Phase 3/4/5.
This commit is contained in:
+86
-15
@@ -87,7 +87,7 @@ include_directories(../third_party/openfx/include
|
||||
# Remove prefix - prevents CMake calling it "liblibolive-editor"
|
||||
set_target_properties(libolive-editor PROPERTIES PREFIX "")
|
||||
|
||||
option(OAK_ENABLE_DYNAMIC_RENDER_BACKEND "Build and use the experimental dynamic render backend adapter" OFF)
|
||||
option(OAK_ENABLE_DYNAMIC_RENDER_BACKEND "Build and use the dynamic render backend adapter" ON)
|
||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
set_target_properties(libolive-editor PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_compile_definitions(libolive-editor PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
@@ -98,41 +98,94 @@ if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
add_library(oakgl SHARED
|
||||
render/opengl/openglbackend_c.cpp
|
||||
$<TARGET_OBJECTS:libolive-editor>
|
||||
# Render core library: the minimal set of code required by the OpenGL/Vulkan backend
|
||||
# libraries. Keeping this separate from libolive-editor prevents the backends from
|
||||
# dragging in editor-wide state (project, task, cache, UI, etc.).
|
||||
add_library(libolive-rendercore STATIC
|
||||
common/avframeptr.h
|
||||
common/define.h
|
||||
common/filefunctions.cpp
|
||||
common/filefunctions.h
|
||||
common/qtutils.cpp
|
||||
common/qtutils.h
|
||||
config/config.cpp
|
||||
config/config.h
|
||||
node/param.cpp
|
||||
node/param.h
|
||||
node/splitvalue.h
|
||||
node/value.cpp
|
||||
node/value.h
|
||||
node/valuedatabase.cpp
|
||||
node/valuedatabase.h
|
||||
render/backend/dynamicrenderer.cpp
|
||||
render/backend/dynamicrenderer.h
|
||||
render/backend/renderbackend_c.h
|
||||
render/job/acceleratedjob.cpp
|
||||
render/job/acceleratedjob.h
|
||||
render/job/shaderjob.h
|
||||
render/renderer.cpp
|
||||
render/renderer.h
|
||||
render/shadercode.h
|
||||
render/texture.cpp
|
||||
render/texture.h
|
||||
render/videoparams.cpp
|
||||
render/videoparams.h
|
||||
)
|
||||
target_include_directories(oakgl PRIVATE
|
||||
set_target_properties(libolive-rendercore PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(libolive-rendercore PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/app
|
||||
${CMAKE_SOURCE_DIR}/third_party/openfx/include
|
||||
${CMAKE_SOURCE_DIR}/third_party/openfx/HostSupport/include
|
||||
${OLIVE_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(oakgl PRIVATE ${OLIVE_LIBRARIES} OfxHost)
|
||||
target_compile_definitions(oakgl PRIVATE ${OLIVE_DEFINITIONS})
|
||||
target_compile_options(oakgl PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
target_link_libraries(libolive-rendercore PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
||||
target_compile_definitions(libolive-rendercore PUBLIC ${OLIVE_DEFINITIONS})
|
||||
target_compile_options(libolive-rendercore PUBLIC ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
add_library(oakgl SHARED
|
||||
render/opengl/openglbackend_c.cpp
|
||||
render/opengl/openglrenderer.cpp
|
||||
render/opengl/openglrenderer.h
|
||||
)
|
||||
target_link_libraries(oakgl PRIVATE libolive-rendercore)
|
||||
set_target_properties(oakgl PROPERTIES
|
||||
OUTPUT_NAME oakgl
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
install(TARGETS oakgl
|
||||
RUNTIME DESTINATION bin
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
||||
add_library(oakvulkan SHARED
|
||||
render/vulkan/vulkanbackend_c.cpp
|
||||
render/vulkan/vulkanrenderer.cpp
|
||||
render/vulkan/vulkanrenderer.h
|
||||
)
|
||||
target_include_directories(oakvulkan PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/app
|
||||
)
|
||||
target_link_libraries(oakvulkan PRIVATE Qt${QT_VERSION_MAJOR}::Core)
|
||||
target_compile_definitions(oakvulkan PRIVATE ${OLIVE_DEFINITIONS})
|
||||
target_compile_options(oakvulkan PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
target_link_libraries(oakvulkan PRIVATE libolive-rendercore)
|
||||
if(Vulkan_FOUND)
|
||||
target_link_libraries(oakvulkan PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(oakvulkan PRIVATE OAK_HAS_VULKAN)
|
||||
endif()
|
||||
if(SHADERC_FOUND)
|
||||
target_link_libraries(oakvulkan PRIVATE ${SHADERC_LIBRARIES})
|
||||
target_include_directories(oakvulkan PRIVATE ${SHADERC_INCLUDE_DIRS})
|
||||
target_compile_definitions(oakvulkan PRIVATE OAK_HAS_SHADERC)
|
||||
endif()
|
||||
set_target_properties(oakvulkan PROPERTIES
|
||||
OUTPUT_NAME oakvulkan
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
install(TARGETS oakvulkan
|
||||
RUNTIME DESTINATION bin
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(oakgl-cabi-check OBJECT
|
||||
@@ -150,11 +203,25 @@ target_compile_options(oakgl-cabi-check PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
add_library(oakvulkan-cabi-check OBJECT
|
||||
render/vulkan/vulkanbackend_c.cpp
|
||||
render/vulkan/vulkanrenderer.cpp
|
||||
render/vulkan/vulkanrenderer.h
|
||||
)
|
||||
target_include_directories(oakvulkan-cabi-check PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/app
|
||||
${CMAKE_SOURCE_DIR}/third_party/openfx/include
|
||||
${CMAKE_SOURCE_DIR}/third_party/openfx/HostSupport/include
|
||||
${OLIVE_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(oakvulkan-cabi-check PRIVATE Qt${QT_VERSION_MAJOR}::Core)
|
||||
target_link_libraries(oakvulkan-cabi-check PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
if(Vulkan_FOUND)
|
||||
target_link_libraries(oakvulkan-cabi-check PRIVATE Vulkan::Vulkan)
|
||||
target_compile_definitions(oakvulkan-cabi-check PRIVATE OAK_HAS_VULKAN)
|
||||
endif()
|
||||
if(SHADERC_FOUND)
|
||||
target_link_libraries(oakvulkan-cabi-check PRIVATE ${SHADERC_LIBRARIES})
|
||||
target_include_directories(oakvulkan-cabi-check PRIVATE ${SHADERC_INCLUDE_DIRS})
|
||||
target_compile_definitions(oakvulkan-cabi-check PRIVATE OAK_HAS_SHADERC)
|
||||
endif()
|
||||
target_compile_definitions(oakvulkan-cabi-check PRIVATE ${OLIVE_DEFINITIONS})
|
||||
target_compile_options(oakvulkan-cabi-check PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
||||
|
||||
@@ -183,6 +250,10 @@ add_executable(olive-render-worker
|
||||
)
|
||||
target_include_directories(olive-render-worker PUBLIC pluginSupport)
|
||||
target_link_libraries(olive-render-worker PUBLIC OfxHost)
|
||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
target_compile_definitions(olive-render-worker PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||
add_dependencies(olive-render-worker oakgl oakvulkan)
|
||||
endif()
|
||||
# Create docs if doxygen was found
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_PROJECT_NAME "Oak Video Editor")
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef AVFRAMEPTR_H
|
||||
#define AVFRAMEPTR_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
|
||||
inline AVFramePtr CreateAVFramePtr(AVFrame *f)
|
||||
{
|
||||
return std::shared_ptr<AVFrame>(f, [](AVFrame *g) { av_frame_free(&g); });
|
||||
}
|
||||
|
||||
inline AVFramePtr CreateAVFramePtr()
|
||||
{
|
||||
return CreateAVFramePtr(av_frame_alloc());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // AVFRAMEPTR_H
|
||||
@@ -30,6 +30,7 @@ extern "C" {
|
||||
|
||||
#include <olive/core/core.h>
|
||||
|
||||
#include "common/avframeptr.h"
|
||||
#include "render/videoparams.h"
|
||||
|
||||
namespace olive
|
||||
@@ -85,16 +86,6 @@ public:
|
||||
static AVPixelFormat ConvertJPEGSpaceToRegularSpace(AVPixelFormat f);
|
||||
};
|
||||
|
||||
using AVFramePtr = std::shared_ptr<AVFrame>;
|
||||
inline AVFramePtr CreateAVFramePtr(AVFrame *f)
|
||||
{
|
||||
return std::shared_ptr<AVFrame>(f, [](AVFrame *g) { av_frame_free(&g); });
|
||||
}
|
||||
inline AVFramePtr CreateAVFramePtr()
|
||||
{
|
||||
return CreateAVFramePtr(av_frame_alloc());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGABSTRACTION_H
|
||||
|
||||
@@ -30,6 +30,7 @@ set(OLIVE_SOURCES
|
||||
render/backend/dynamicrenderer.h
|
||||
render/backend/renderbackend_c.h
|
||||
render/cancelatom.h
|
||||
render/colormanagement.cpp
|
||||
render/colorprocessor.cpp
|
||||
render/colorprocessor.h
|
||||
render/colorprocessorcache.h
|
||||
@@ -39,6 +40,7 @@ set(OLIVE_SOURCES
|
||||
render/framehashcache.h
|
||||
render/framemanager.cpp
|
||||
render/framemanager.h
|
||||
render/interlacetexture.cpp
|
||||
render/loopmode.h
|
||||
render/managedcolor.cpp
|
||||
render/managedcolor.h
|
||||
|
||||
@@ -45,6 +45,8 @@ QString DynamicRenderer::LibraryFilename() const
|
||||
const QStringList candidates = {
|
||||
app_dir.filePath(filename),
|
||||
app_dir.filePath(QDir(QStringLiteral("render_backends")).filePath(filename)),
|
||||
app_dir.filePath(QDir(QStringLiteral("../lib")).filePath(filename)),
|
||||
app_dir.filePath(QDir(QStringLiteral("../../lib")).filePath(filename)),
|
||||
app_dir.filePath(QDir(QStringLiteral("../app")).filePath(filename)),
|
||||
app_dir.filePath(QDir(QStringLiteral("../../app")).filePath(filename))
|
||||
};
|
||||
@@ -139,6 +141,10 @@ bool DynamicRenderer::ResolveFunctions()
|
||||
RESOLVE(get_pixel_from_texture_, OakBackendGetPixelFromTextureFn,
|
||||
"oak_renderer_get_pixel_from_texture");
|
||||
RESOLVE(blit_, OakBackendBlitFn, "oak_renderer_blit");
|
||||
RESOLVE(attach_output_texture_, OakBackendAttachOutputTextureFn,
|
||||
"oak_renderer_attach_output_texture");
|
||||
RESOLVE(detach_output_texture_, OakBackendDetachOutputTextureFn,
|
||||
"oak_renderer_detach_output_texture");
|
||||
RESOLVE(opengl_context_, OakBackendOpenGLContextFn,
|
||||
"oak_renderer_opengl_context");
|
||||
#undef RESOLVE
|
||||
@@ -184,6 +190,8 @@ void DynamicRenderer::ResetFunctions()
|
||||
flush_ = nullptr;
|
||||
get_pixel_from_texture_ = nullptr;
|
||||
blit_ = nullptr;
|
||||
attach_output_texture_ = nullptr;
|
||||
detach_output_texture_ = nullptr;
|
||||
opengl_context_ = nullptr;
|
||||
}
|
||||
|
||||
@@ -271,6 +279,11 @@ QOpenGLContext *DynamicRenderer::OpenGLContext() const
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool DynamicRenderer::IsOpenGL() const
|
||||
{
|
||||
return backend_ == QStringLiteral("opengl");
|
||||
}
|
||||
|
||||
void DynamicRenderer::Blit(QVariant shader, AcceleratedJob &job,
|
||||
Texture *destination, VideoParams destination_params,
|
||||
bool clear_destination)
|
||||
@@ -301,4 +314,19 @@ void DynamicRenderer::DestroyInternal()
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicRenderer::AttachOutputTexture(Texture *texture)
|
||||
{
|
||||
if (attach_output_texture_ && texture) {
|
||||
QVariant id = texture->id();
|
||||
attach_output_texture_(handle_, &id);
|
||||
}
|
||||
}
|
||||
|
||||
void DynamicRenderer::DetachOutputTexture()
|
||||
{
|
||||
if (detach_output_texture_) {
|
||||
detach_output_texture_(handle_);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ public:
|
||||
const QPointF &pt) override;
|
||||
virtual QOpenGLContext *OpenGLContext() const override;
|
||||
|
||||
virtual bool IsOpenGL() const override;
|
||||
|
||||
virtual void AttachOutputTexture(Texture *texture) override;
|
||||
|
||||
virtual void DetachOutputTexture() override;
|
||||
|
||||
protected:
|
||||
virtual void Blit(QVariant shader, AcceleratedJob &job,
|
||||
Texture *destination, VideoParams destination_params,
|
||||
@@ -84,6 +90,8 @@ private:
|
||||
OakBackendFlushFn flush_ = nullptr;
|
||||
OakBackendGetPixelFromTextureFn get_pixel_from_texture_ = nullptr;
|
||||
OakBackendBlitFn blit_ = nullptr;
|
||||
OakBackendAttachOutputTextureFn attach_output_texture_ = nullptr;
|
||||
OakBackendDetachOutputTextureFn detach_output_texture_ = nullptr;
|
||||
OakBackendOpenGLContextFn opengl_context_ = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@ enum OakRenderBackendCapability {
|
||||
OAK_RENDER_BACKEND_CAP_SHADERS = 1ULL << 1,
|
||||
OAK_RENDER_BACKEND_CAP_BLIT = 1ULL << 2,
|
||||
OAK_RENDER_BACKEND_CAP_READBACK = 1ULL << 3,
|
||||
OAK_RENDER_BACKEND_CAP_VIEWER_CONTEXT = 1ULL << 4
|
||||
OAK_RENDER_BACKEND_CAP_VIEWER_CONTEXT = 1ULL << 4,
|
||||
OAK_RENDER_BACKEND_CAP_INSTANCE = 1ULL << 5,
|
||||
OAK_RENDER_BACKEND_CAP_DEVICE = 1ULL << 6
|
||||
};
|
||||
|
||||
struct OakRenderBackendInfo {
|
||||
@@ -81,6 +83,9 @@ typedef void (*OakBackendBlitFn)(OakRenderBackendHandle handle,
|
||||
void *destination,
|
||||
const void *destination_params,
|
||||
bool clear_destination);
|
||||
typedef void (*OakBackendAttachOutputTextureFn)(OakRenderBackendHandle handle,
|
||||
const void *texture_id);
|
||||
typedef void (*OakBackendDetachOutputTextureFn)(OakRenderBackendHandle handle);
|
||||
typedef void *(*OakBackendOpenGLContextFn)(OakRenderBackendHandle handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "node/node.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
bool Renderer::GetColorContext(const ColorTransformJob &color_job,
|
||||
Renderer::ColorContext *ctx)
|
||||
{
|
||||
QMutexLocker locker(&color_cache_mutex_);
|
||||
|
||||
ColorContext &color_ctx = *ctx;
|
||||
|
||||
QString proc_id = color_job.id();
|
||||
|
||||
if (color_cache_.contains(proc_id)) {
|
||||
color_ctx = color_cache_.value(proc_id);
|
||||
return true;
|
||||
} else {
|
||||
// Create shader description
|
||||
QString ocio_func_name;
|
||||
if (color_job.GetFunctionName().isEmpty()) {
|
||||
ocio_func_name = "OCIODisplay";
|
||||
} else {
|
||||
ocio_func_name = color_job.GetFunctionName();
|
||||
}
|
||||
auto shader_desc = OCIO::GpuShaderDesc::CreateShaderDesc();
|
||||
shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0);
|
||||
shader_desc->setFunctionName(ocio_func_name.toUtf8());
|
||||
shader_desc->setResourcePrefix("ocio_");
|
||||
|
||||
// Generate shader
|
||||
color_job.GetColorProcessor()
|
||||
->GetProcessor()
|
||||
->getDefaultGPUProcessor()
|
||||
->extractGpuShaderInfo(shader_desc);
|
||||
|
||||
ShaderCode code;
|
||||
if (const Node *shader_src = color_job.CustomShaderSource()) {
|
||||
// Use shader code from associated node
|
||||
code = shader_src->GetShaderCode(
|
||||
{ color_job.CustomShaderID(), shader_desc->getShaderText() });
|
||||
} else {
|
||||
// Generate shader code using OCIO stub and our auto-generated name
|
||||
code = FileFunctions::ReadFileAsString(
|
||||
QStringLiteral(":/shaders/colormanage.frag"));
|
||||
code.set_frag_code(
|
||||
code.frag_code().arg(shader_desc->getShaderText()));
|
||||
}
|
||||
|
||||
// Try to compile shader
|
||||
color_ctx.compiled_shader = CreateNativeShader(code);
|
||||
|
||||
if (color_ctx.compiled_shader.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNum3DTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int edge_len = 0;
|
||||
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
|
||||
|
||||
shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len,
|
||||
interpolation);
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!edge_len) {
|
||||
qCritical() << "3D LUT texture data is corrupted";
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->get3DTextureValues(i, values);
|
||||
if (!values) {
|
||||
qCritical() << "3D LUT texture values are missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 3D LUT
|
||||
color_ctx.lut3d_textures[i].texture = CreateTexture(
|
||||
VideoParams(edge_len, edge_len, edge_len, PixelFormat::F32,
|
||||
VideoParams::kRGBChannelCount),
|
||||
values);
|
||||
color_ctx.lut3d_textures[i].name = sampler_name;
|
||||
color_ctx.lut3d_textures[i].interpolation =
|
||||
(interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest :
|
||||
Texture::kLinear;
|
||||
}
|
||||
|
||||
color_ctx.lut1d_textures.resize(shader_desc->getNumTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNumTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int width = 0, height = 0;
|
||||
OCIO::GpuShaderDesc::TextureType channel =
|
||||
OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
|
||||
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
|
||||
#if OCIO_VERSION_MAJOR > 2 || (OCIO_VERSION_MAJOR == 2 && OCIO_VERSION_MINOR >= 3)
|
||||
OCIO::GpuShaderDesc::TextureDimensions dimensions =
|
||||
OCIO::GpuShaderDesc::TEXTURE_2D;
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, dimensions, interpolation);
|
||||
#else
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, interpolation);
|
||||
#endif
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!width) {
|
||||
qCritical() << "1D LUT texture data is corrupted";
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->getTextureValues(i, values);
|
||||
if (!values) {
|
||||
qCritical() << "1D LUT texture values are missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 1D LUT
|
||||
color_ctx.lut1d_textures[i].texture = CreateTexture(
|
||||
VideoParams(width, height, PixelFormat::F32,
|
||||
(channel ==
|
||||
OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ?
|
||||
1 :
|
||||
VideoParams::kRGBChannelCount),
|
||||
values);
|
||||
color_ctx.lut1d_textures[i].name = sampler_name;
|
||||
color_ctx.lut1d_textures[i].interpolation =
|
||||
(interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest :
|
||||
Texture::kLinear;
|
||||
}
|
||||
|
||||
color_cache_.insert(proc_id, color_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::BlitColorManaged(const ColorTransformJob &color_job,
|
||||
Texture *destination, const VideoParams ¶ms)
|
||||
{
|
||||
ColorContext color_ctx;
|
||||
if (!GetColorContext(color_job, &color_ctx)) {
|
||||
ShaderJob fallback_job;
|
||||
fallback_job.Insert(QStringLiteral("ove_maintex"),
|
||||
color_job.GetInputTexture());
|
||||
fallback_job.Insert(
|
||||
QStringLiteral("ove_mvpmat"),
|
||||
NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
|
||||
|
||||
if (destination) {
|
||||
BlitToTexture(GetDefaultShader(), fallback_job, destination,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
} else {
|
||||
Blit(GetDefaultShader(), fallback_job, params,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ShaderJob job;
|
||||
job.Insert(QStringLiteral("ove_maintex"), color_job.GetInputTexture());
|
||||
job.Insert(QStringLiteral("ove_mvpmat"),
|
||||
NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
|
||||
job.Insert(QStringLiteral("ove_cropmatrix"),
|
||||
NodeValue(NodeValue::kMatrix,
|
||||
color_job.GetCropMatrix().inverted()));
|
||||
job.Insert(QStringLiteral("ove_maintex_alpha"),
|
||||
NodeValue(NodeValue::kInt,
|
||||
int(color_job.GetInputAlphaAssociation())));
|
||||
job.Insert(QStringLiteral("ove_force_opaque"),
|
||||
NodeValue(NodeValue::kBoolean, color_job.GetForceOpaque()));
|
||||
job.Insert(color_job.GetValues());
|
||||
|
||||
foreach (const ColorContext::LUT &l, color_ctx.lut3d_textures) {
|
||||
job.Insert(l.name, NodeValue(NodeValue::kTexture,
|
||||
QVariant::fromValue(l.texture)));
|
||||
job.SetInterpolation(l.name, l.interpolation);
|
||||
}
|
||||
foreach (const ColorContext::LUT &l, color_ctx.lut1d_textures) {
|
||||
job.Insert(l.name, NodeValue(NodeValue::kTexture,
|
||||
QVariant::fromValue(l.texture)));
|
||||
job.SetInterpolation(l.name, l.interpolation);
|
||||
}
|
||||
|
||||
if (destination) {
|
||||
BlitToTexture(color_ctx.compiled_shader, job, destination,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
} else {
|
||||
Blit(color_ctx.compiled_shader, job, params,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "node/value.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom,
|
||||
const VideoParams ¶ms)
|
||||
{
|
||||
color_cache_mutex_.lock();
|
||||
if (interlace_texture_.isNull()) {
|
||||
interlace_texture_ =
|
||||
CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(
|
||||
QStringLiteral(":/shaders/interlace.frag"))));
|
||||
}
|
||||
color_cache_mutex_.unlock();
|
||||
|
||||
ShaderJob job;
|
||||
job.Insert(QStringLiteral("top_tex_in"),
|
||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(top)));
|
||||
job.Insert(QStringLiteral("bottom_tex_in"),
|
||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(bottom)));
|
||||
job.Insert(QStringLiteral("resolution_in"),
|
||||
NodeValue(NodeValue::kVec2,
|
||||
QVector2D(params.effective_width(),
|
||||
params.effective_height())));
|
||||
|
||||
TexturePtr output = CreateTexture(params);
|
||||
|
||||
BlitToTexture(interlace_texture_, job, output.get());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,8 @@ public:
|
||||
using olive::OpenGLRenderer::CreateNativeTexture;
|
||||
using olive::OpenGLRenderer::DestroyInternal;
|
||||
using olive::OpenGLRenderer::DestroyNativeTexture;
|
||||
using olive::OpenGLRenderer::AttachTextureAsDestination;
|
||||
using olive::OpenGLRenderer::DetachTextureAsDestination;
|
||||
};
|
||||
|
||||
BackendOpenGLRenderer *Renderer(OakRenderBackendHandle handle)
|
||||
@@ -176,3 +178,15 @@ OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||
{
|
||||
return Renderer(handle)->context();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
||||
OakRenderBackendHandle handle, const void *texture_id)
|
||||
{
|
||||
Renderer(handle)->AttachTextureAsDestination(VariantRef(texture_id));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Renderer(handle)->DetachTextureAsDestination();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "render/job/shaderjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -406,6 +408,18 @@ void OpenGLRenderer::Flush()
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLRenderer::AttachOutputTexture(olive::Texture *texture)
|
||||
{
|
||||
if (texture) {
|
||||
AttachTextureAsDestination(texture->id());
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLRenderer::DetachOutputTexture()
|
||||
{
|
||||
DetachTextureAsDestination();
|
||||
}
|
||||
|
||||
Color OpenGLRenderer::GetPixelFromTexture(Texture *texture, const QPointF &pt)
|
||||
{
|
||||
AttachTextureAsDestination(texture->id());
|
||||
|
||||
@@ -82,6 +82,15 @@ public:
|
||||
return context();
|
||||
}
|
||||
|
||||
virtual bool IsOpenGL() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void AttachOutputTexture(olive::Texture *texture) override;
|
||||
|
||||
virtual void DetachOutputTexture() override;
|
||||
|
||||
bool EnsureContextCurrent(const char *caller);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -1432,9 +1432,10 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::
|
||||
auto *olive_instance =
|
||||
dynamic_cast<olive::plugin::OlivePluginInstance *>(instance);
|
||||
const bool use_opengl =
|
||||
supports_opengl && destination && destination->renderer() &&
|
||||
destination->id().isValid();
|
||||
supports_opengl && renderer_ && renderer_->IsOpenGL() && destination &&
|
||||
destination->renderer() == renderer_ && destination->id().isValid();
|
||||
if (olive_instance) {
|
||||
olive_instance->setOpenGLEnabled(use_opengl);
|
||||
olive_instance->setVideoParam(destination_params);
|
||||
// Ensure all clip instances inherit the project's params so that
|
||||
// getAspectRatio/getFrameRate etc. return valid values before
|
||||
@@ -1798,7 +1799,7 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::
|
||||
renderScale, true, interactive);
|
||||
return;
|
||||
}
|
||||
AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params, this);
|
||||
AVFramePtr converted = ConvertFrameIfNeeded(frame_ptr, destination_params, renderer_);
|
||||
const AVPixelFormat expected_fmt =
|
||||
GetDestinationAVPixelFormat(destination_params);
|
||||
destination->handleFrame(converted);
|
||||
@@ -1837,15 +1838,16 @@ void olive::plugin::PluginRenderer::RenderPlugin(TexturePtr src, olive::plugin::
|
||||
// Purpose: Attach output texture for OFX GL rendering.
|
||||
void olive::plugin::PluginRenderer::AttachOutputTexture(olive::TexturePtr texture)
|
||||
{
|
||||
if (!texture) {
|
||||
return;
|
||||
if (renderer_) {
|
||||
renderer_->AttachOutputTexture(texture.get());
|
||||
}
|
||||
AttachTextureAsDestination(texture->id());
|
||||
}
|
||||
|
||||
// 作用:解除 OFX 的 GL 输出绑定。
|
||||
// Purpose: Detach OFX GL output binding.
|
||||
void olive::plugin::PluginRenderer::DetachOutputTexture()
|
||||
{
|
||||
DetachTextureAsDestination();
|
||||
if (renderer_) {
|
||||
renderer_->DetachOutputTexture();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,12 @@
|
||||
|
||||
#ifndef PLUGINRENDERER_H
|
||||
#define PLUGINRENDERER_H
|
||||
#include <QOpenGLExtraFunctions>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShader>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QThread>
|
||||
#include <QOffscreenSurface>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "render/renderer.h"
|
||||
#include "render/job/pluginjob.h"
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin{
|
||||
@@ -44,11 +39,22 @@ int BytesToPixels(int byte_linesize, const olive::VideoParams ¶ms);
|
||||
}
|
||||
// 作用:OFX 插件渲染器,负责 CPU/GL 路径下的插件调用和纹理桥接。
|
||||
// Purpose: OFX plugin renderer that drives CPU/GL render paths and texture bridging.
|
||||
class PluginRenderer : public olive::OpenGLRenderer{
|
||||
//
|
||||
// 不再继承 OpenGLRenderer,而是持有一个通用的 Renderer 指针。这样当主渲染器
|
||||
// 是 Vulkan 或动态加载的后端时,插件仍可通过 CPU readback/upload 路径工作;
|
||||
// 仅当底层渲染器真正支持 OpenGL 时才走 OFX OpenGL 渲染路径。
|
||||
class PluginRenderer : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PluginRenderer(QObject *parent=nullptr):OpenGLRenderer(parent){};
|
||||
virtual ~PluginRenderer() override{};
|
||||
explicit PluginRenderer(olive::Renderer *renderer, QObject *parent = nullptr)
|
||||
: QObject(parent), renderer_(renderer) {}
|
||||
virtual ~PluginRenderer() override {}
|
||||
|
||||
olive::Renderer *renderer() const
|
||||
{
|
||||
return renderer_;
|
||||
}
|
||||
|
||||
// 作用:将目标纹理绑定为插件输出。
|
||||
// Purpose: Attach destination texture as OFX output.
|
||||
void AttachOutputTexture(olive::TexturePtr texture);
|
||||
@@ -62,6 +68,8 @@ public:
|
||||
olive::VideoParams destination_params,
|
||||
bool clear_destination, bool interactive);
|
||||
|
||||
private:
|
||||
olive::Renderer *renderer_;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-221
@@ -116,34 +116,6 @@ void Renderer::DestroyTexture(Texture *texture)
|
||||
}
|
||||
}
|
||||
|
||||
TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom,
|
||||
const VideoParams ¶ms)
|
||||
{
|
||||
color_cache_mutex_.lock();
|
||||
if (interlace_texture_.isNull()) {
|
||||
interlace_texture_ =
|
||||
CreateNativeShader(ShaderCode(FileFunctions::ReadFileAsString(
|
||||
QStringLiteral(":/shaders/interlace.frag"))));
|
||||
}
|
||||
color_cache_mutex_.unlock();
|
||||
|
||||
ShaderJob job;
|
||||
job.Insert(QStringLiteral("top_tex_in"),
|
||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(top)));
|
||||
job.Insert(QStringLiteral("bottom_tex_in"),
|
||||
NodeValue(NodeValue::kTexture, QVariant::fromValue(bottom)));
|
||||
job.Insert(QStringLiteral("resolution_in"),
|
||||
NodeValue(NodeValue::kVec2,
|
||||
QVector2D(params.effective_width(),
|
||||
params.effective_height())));
|
||||
|
||||
TexturePtr output = CreateTexture(params);
|
||||
|
||||
BlitToTexture(interlace_texture_, job, output.get());
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
QVariant Renderer::GetDefaultShader()
|
||||
{
|
||||
QMutexLocker locker(&color_cache_mutex_);
|
||||
@@ -191,142 +163,6 @@ TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v,
|
||||
return std::make_shared<Texture>(this, v, params, lifetime_);
|
||||
}
|
||||
|
||||
bool Renderer::GetColorContext(const ColorTransformJob &color_job,
|
||||
Renderer::ColorContext *ctx)
|
||||
{
|
||||
QMutexLocker locker(&color_cache_mutex_);
|
||||
|
||||
ColorContext &color_ctx = *ctx;
|
||||
|
||||
QString proc_id = color_job.id();
|
||||
|
||||
if (color_cache_.contains(proc_id)) {
|
||||
color_ctx = color_cache_.value(proc_id);
|
||||
return true;
|
||||
} else {
|
||||
// Create shader description
|
||||
QString ocio_func_name;
|
||||
if (color_job.GetFunctionName().isEmpty()) {
|
||||
ocio_func_name = "OCIODisplay";
|
||||
} else {
|
||||
ocio_func_name = color_job.GetFunctionName();
|
||||
}
|
||||
auto shader_desc = OCIO::GpuShaderDesc::CreateShaderDesc();
|
||||
shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0);
|
||||
shader_desc->setFunctionName(ocio_func_name.toUtf8());
|
||||
shader_desc->setResourcePrefix("ocio_");
|
||||
|
||||
// Generate shader
|
||||
color_job.GetColorProcessor()
|
||||
->GetProcessor()
|
||||
->getDefaultGPUProcessor()
|
||||
->extractGpuShaderInfo(shader_desc);
|
||||
|
||||
ShaderCode code;
|
||||
if (const Node *shader_src = color_job.CustomShaderSource()) {
|
||||
// Use shader code from associated node
|
||||
code = shader_src->GetShaderCode(
|
||||
{ color_job.CustomShaderID(), shader_desc->getShaderText() });
|
||||
} else {
|
||||
// Generate shader code using OCIO stub and our auto-generated name
|
||||
code = FileFunctions::ReadFileAsString(
|
||||
QStringLiteral(":shaders/colormanage.frag"));
|
||||
code.set_frag_code(
|
||||
code.frag_code().arg(shader_desc->getShaderText()));
|
||||
}
|
||||
|
||||
// Try to compile shader
|
||||
color_ctx.compiled_shader = CreateNativeShader(code);
|
||||
|
||||
if (color_ctx.compiled_shader.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNum3DTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int edge_len = 0;
|
||||
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
|
||||
|
||||
shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len,
|
||||
interpolation);
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!edge_len) {
|
||||
qCritical() << "3D LUT texture data is corrupted";
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->get3DTextureValues(i, values);
|
||||
if (!values) {
|
||||
qCritical() << "3D LUT texture values are missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 3D LUT
|
||||
color_ctx.lut3d_textures[i].texture = CreateTexture(
|
||||
VideoParams(edge_len, edge_len, edge_len, PixelFormat::F32,
|
||||
VideoParams::kRGBChannelCount),
|
||||
values);
|
||||
color_ctx.lut3d_textures[i].name = sampler_name;
|
||||
color_ctx.lut3d_textures[i].interpolation =
|
||||
(interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest :
|
||||
Texture::kLinear;
|
||||
}
|
||||
|
||||
color_ctx.lut1d_textures.resize(shader_desc->getNumTextures());
|
||||
for (unsigned int i = 0; i < shader_desc->getNumTextures(); i++) {
|
||||
const char *tex_name = nullptr;
|
||||
const char *sampler_name = nullptr;
|
||||
unsigned int width = 0, height = 0;
|
||||
OCIO::GpuShaderDesc::TextureType channel =
|
||||
OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
|
||||
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
|
||||
#if OCIO_VERSION_MAJOR > 2 || (OCIO_VERSION_MAJOR == 2 && OCIO_VERSION_MINOR >= 3)
|
||||
OCIO::GpuShaderDesc::TextureDimensions dimensions =
|
||||
OCIO::GpuShaderDesc::TEXTURE_2D;
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, dimensions, interpolation);
|
||||
#else
|
||||
shader_desc->getTexture(i, tex_name, sampler_name, width, height,
|
||||
channel, interpolation);
|
||||
#endif
|
||||
|
||||
if (!tex_name || !*tex_name || !sampler_name || !*sampler_name ||
|
||||
!width) {
|
||||
qCritical() << "1D LUT texture data is corrupted";
|
||||
return false;
|
||||
}
|
||||
|
||||
const float *values = nullptr;
|
||||
shader_desc->getTextureValues(i, values);
|
||||
if (!values) {
|
||||
qCritical() << "1D LUT texture values are missing";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate 1D LUT
|
||||
color_ctx.lut1d_textures[i].texture = CreateTexture(
|
||||
VideoParams(width, height, PixelFormat::F32,
|
||||
(channel ==
|
||||
OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ?
|
||||
1 :
|
||||
VideoParams::kRGBChannelCount),
|
||||
values);
|
||||
color_ctx.lut1d_textures[i].name = sampler_name;
|
||||
color_ctx.lut1d_textures[i].interpolation =
|
||||
(interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest :
|
||||
Texture::kLinear;
|
||||
}
|
||||
|
||||
color_cache_.insert(proc_id, color_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::ClearOldTextures()
|
||||
{
|
||||
QMutexLocker locker(&texture_cache_lock_);
|
||||
@@ -342,60 +178,4 @@ void Renderer::ClearOldTextures()
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::BlitColorManaged(const ColorTransformJob &color_job,
|
||||
Texture *destination, const VideoParams ¶ms)
|
||||
{
|
||||
ColorContext color_ctx;
|
||||
if (!GetColorContext(color_job, &color_ctx)) {
|
||||
ShaderJob fallback_job;
|
||||
fallback_job.Insert(QStringLiteral("ove_maintex"),
|
||||
color_job.GetInputTexture());
|
||||
fallback_job.Insert(
|
||||
QStringLiteral("ove_mvpmat"),
|
||||
NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
|
||||
|
||||
if (destination) {
|
||||
BlitToTexture(GetDefaultShader(), fallback_job, destination,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
} else {
|
||||
Blit(GetDefaultShader(), fallback_job, params,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ShaderJob job;
|
||||
job.Insert(QStringLiteral("ove_maintex"), color_job.GetInputTexture());
|
||||
job.Insert(QStringLiteral("ove_mvpmat"),
|
||||
NodeValue(NodeValue::kMatrix, color_job.GetTransformMatrix()));
|
||||
job.Insert(QStringLiteral("ove_cropmatrix"),
|
||||
NodeValue(NodeValue::kMatrix,
|
||||
color_job.GetCropMatrix().inverted()));
|
||||
job.Insert(QStringLiteral("ove_maintex_alpha"),
|
||||
NodeValue(NodeValue::kInt,
|
||||
int(color_job.GetInputAlphaAssociation())));
|
||||
job.Insert(QStringLiteral("ove_force_opaque"),
|
||||
NodeValue(NodeValue::kBoolean, color_job.GetForceOpaque()));
|
||||
job.Insert(color_job.GetValues());
|
||||
|
||||
foreach (const ColorContext::LUT &l, color_ctx.lut3d_textures) {
|
||||
job.Insert(l.name, NodeValue(NodeValue::kTexture,
|
||||
QVariant::fromValue(l.texture)));
|
||||
job.SetInterpolation(l.name, l.interpolation);
|
||||
}
|
||||
foreach (const ColorContext::LUT &l, color_ctx.lut1d_textures) {
|
||||
job.Insert(l.name, NodeValue(NodeValue::kTexture,
|
||||
QVariant::fromValue(l.texture)));
|
||||
job.SetInterpolation(l.name, l.interpolation);
|
||||
}
|
||||
|
||||
if (destination) {
|
||||
BlitToTexture(color_ctx.compiled_shader, job, destination,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
} else {
|
||||
Blit(color_ctx.compiled_shader, job, params,
|
||||
color_job.IsClearDestinationEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace olive
|
||||
|
||||
+31
-4
@@ -29,12 +29,15 @@
|
||||
#include <memory>
|
||||
|
||||
#include "common/define.h"
|
||||
#include "node/node.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
#include "render/shadercode.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "texture.h"
|
||||
#include "job/pluginjob.h"
|
||||
|
||||
// Forward declarations to keep the render core header lightweight
|
||||
namespace olive {
|
||||
class ColorTransformJob;
|
||||
class Node;
|
||||
}
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -117,6 +120,30 @@ public:
|
||||
return lifetime_;
|
||||
}
|
||||
|
||||
virtual bool IsOpenGL() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attach a texture as the current output destination for OFX plugin
|
||||
* OpenGL rendering.
|
||||
*
|
||||
* Default implementation is a no-op. OpenGL-based renderers override this
|
||||
* to bind the texture as a framebuffer render target.
|
||||
*/
|
||||
virtual void AttachOutputTexture(olive::Texture *texture)
|
||||
{
|
||||
(void)texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Detach the current OFX plugin OpenGL output texture.
|
||||
*
|
||||
* Default implementation is a no-op.
|
||||
*/
|
||||
virtual void DetachOutputTexture() {}
|
||||
|
||||
protected:
|
||||
virtual void Blit(QVariant shader, olive::AcceleratedJob& job,
|
||||
olive::Texture *destination,
|
||||
|
||||
@@ -78,21 +78,31 @@ QString RenderManager::BackendToString(Backend backend)
|
||||
}
|
||||
|
||||
RenderManager::RenderManager(QObject *parent)
|
||||
: backend_(kOpenGL)
|
||||
, requested_backend_(BackendFromString(
|
||||
: backend_(BackendFromString(
|
||||
OLIVE_CONFIG("GraphicsBackend").toString()))
|
||||
, requested_backend_(backend_)
|
||||
, aggressive_gc_(0)
|
||||
, worker_pool_(nullptr)
|
||||
{
|
||||
if (requested_backend_ == kVulkan) {
|
||||
qWarning()
|
||||
<< "Vulkan graphics backend was requested, but the current render "
|
||||
"pipeline still uses OpenGL. Falling back to OpenGL renderer.";
|
||||
if (backend_ == kVulkan) {
|
||||
#ifndef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
qWarning() << "Vulkan backend requested but dynamic render backend is not enabled. Falling back to OpenGL.";
|
||||
backend_ = kOpenGL;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (backend_ == kOpenGL) {
|
||||
if (backend_ == kOpenGL || backend_ == kVulkan) {
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
context_ = new DynamicRenderer(BackendToString(requested_backend_));
|
||||
auto *dynamic_renderer = new DynamicRenderer(BackendToString(requested_backend_));
|
||||
if (!dynamic_renderer->Load()) {
|
||||
qWarning() << "Failed to load dynamic render backend" << BackendToString(requested_backend_)
|
||||
<< ", falling back to OpenGL";
|
||||
delete dynamic_renderer;
|
||||
backend_ = kOpenGL;
|
||||
context_ = new OpenGLRenderer();
|
||||
} else {
|
||||
context_ = dynamic_renderer;
|
||||
}
|
||||
#else
|
||||
context_ = new OpenGLRenderer();
|
||||
#endif
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "node/project.h"
|
||||
#include "rendermanager.h"
|
||||
#include "render/opengl/openglcontextprovider.h"
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
#include "render/plugin/pluginrenderer.h"
|
||||
#include "pluginSupport/OliveClip.h"
|
||||
#include "pluginSupport/OliveHost.h"
|
||||
@@ -711,26 +709,8 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
|
||||
return destination;
|
||||
}
|
||||
|
||||
plugin::PluginRenderer *plugin_renderer = nullptr;
|
||||
{
|
||||
static QThreadStorage<std::shared_ptr<plugin::PluginRenderer>>
|
||||
cached_plugin_renderers;
|
||||
if (!cached_plugin_renderers.hasLocalData()) {
|
||||
auto *gl = dynamic_cast<OpenGLContextProvider *>(render_ctx_);
|
||||
if (gl && gl->OpenGLContext()) {
|
||||
auto cached_plugin_renderer =
|
||||
std::make_shared<plugin::PluginRenderer>();
|
||||
cached_plugin_renderer->Init(gl->OpenGLContext());
|
||||
cached_plugin_renderer->PostInit();
|
||||
cached_plugin_renderers.setLocalData(cached_plugin_renderer);
|
||||
}
|
||||
}
|
||||
if (cached_plugin_renderers.hasLocalData()) {
|
||||
plugin_renderer = cached_plugin_renderers.localData().get();
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin_renderer) {
|
||||
plugin::PluginRenderer plugin_renderer(render_ctx_);
|
||||
if (!plugin_renderer.renderer()) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
@@ -782,7 +762,7 @@ TexturePtr RenderProcessor::ProcessPluginJob(TexturePtr texture,
|
||||
}
|
||||
}
|
||||
|
||||
plugin_renderer->RenderPlugin(
|
||||
plugin_renderer.RenderPlugin(
|
||||
src,
|
||||
*plugin_job,
|
||||
destination,
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "texture.h"
|
||||
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "renderer.h"
|
||||
|
||||
namespace olive
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#ifndef RENDERTEXTURE_H
|
||||
#define RENDERTEXTURE_H
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "common/avframeptr.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
@@ -29,6 +29,7 @@ extern "C" {
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "ofxImageEffect.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -120,6 +121,17 @@ VideoParams::VideoParams(int width, int height, int depth, PixelFormat format,
|
||||
set_defaults_for_footage();
|
||||
}
|
||||
|
||||
void VideoParams::set_channel_count(const std::string &ofxComponent)
|
||||
{
|
||||
if (ofxComponent == kOfxImageComponentAlpha) {
|
||||
channel_count_ = 1;
|
||||
} else if (ofxComponent == kOfxImageComponentRGB) {
|
||||
channel_count_ = kRGBChannelCount;
|
||||
} else if (ofxComponent == kOfxImageComponentRGBA) {
|
||||
channel_count_ = kRGBAChannelCount;
|
||||
}
|
||||
}
|
||||
|
||||
VideoParams::VideoParams(int width, int height, const rational &time_base,
|
||||
PixelFormat format, int nb_channels,
|
||||
const rational &pixel_aspect_ratio,
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#ifndef VIDEOPARAMS_H
|
||||
#define VIDEOPARAMS_H
|
||||
|
||||
#include "ofxImageEffect.h"
|
||||
#include <olive/core/core.h>
|
||||
#include <QVector2D>
|
||||
#include <QXmlStreamReader>
|
||||
@@ -178,18 +177,7 @@ public:
|
||||
{
|
||||
channel_count_ = c;
|
||||
}
|
||||
void set_channel_count(std::string ofxComponent)
|
||||
{
|
||||
if (ofxComponent == kOfxImageComponentAlpha){
|
||||
channel_count_ = 1;
|
||||
}
|
||||
else if (ofxComponent == kOfxImageComponentRGB){
|
||||
channel_count_ = kRGBChannelCount;
|
||||
}
|
||||
else if(ofxComponent == kOfxImageComponentRGBA){
|
||||
channel_count_ = kRGBAChannelCount;
|
||||
}
|
||||
}
|
||||
void set_channel_count(const std::string &ofxComponent);
|
||||
const rational &pixel_aspect_ratio() const
|
||||
{
|
||||
return pixel_aspect_ratio_;
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
#include "render/backend/renderbackend_c.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QOpenGLContext>
|
||||
#include <QPointF>
|
||||
#include <QVariant>
|
||||
|
||||
#include "render/job/acceleratedjob.h"
|
||||
#include "render/shadercode.h"
|
||||
#include "render/texture.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "render/vulkan/vulkanrenderer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct VulkanBackend {
|
||||
bool warned = false;
|
||||
class BackendVulkanRenderer : public olive::VulkanRenderer {
|
||||
public:
|
||||
using olive::VulkanRenderer::VulkanRenderer;
|
||||
using olive::VulkanRenderer::Blit;
|
||||
using olive::VulkanRenderer::CreateNativeTexture;
|
||||
using olive::VulkanRenderer::DestroyInternal;
|
||||
using olive::VulkanRenderer::DestroyNativeTexture;
|
||||
};
|
||||
|
||||
VulkanBackend *Backend(OakRenderBackendHandle handle)
|
||||
BackendVulkanRenderer *Renderer(OakRenderBackendHandle handle)
|
||||
{
|
||||
return static_cast<VulkanBackend *>(handle);
|
||||
return static_cast<BackendVulkanRenderer *>(handle);
|
||||
}
|
||||
|
||||
void WarnUnavailable(OakRenderBackendHandle handle, const char *function)
|
||||
const QVariant &VariantRef(const void *variant)
|
||||
{
|
||||
auto *backend = Backend(handle);
|
||||
if (!backend || backend->warned) {
|
||||
return;
|
||||
}
|
||||
backend->warned = true;
|
||||
qWarning() << "Vulkan render backend is a C ABI placeholder; function"
|
||||
<< function << "is not implemented yet";
|
||||
return *static_cast<const QVariant *>(variant);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT OakRenderBackendHandle oak_renderer_create(void *parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return new VulkanBackend();
|
||||
return new BackendVulkanRenderer(static_cast<QObject *>(parent));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy(OakRenderBackendHandle handle)
|
||||
{
|
||||
delete Backend(handle);
|
||||
delete Renderer(handle);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
||||
@@ -46,146 +52,134 @@ OAK_RENDER_BACKEND_EXPORT bool oak_renderer_get_info(
|
||||
}
|
||||
out_info->abi_version = 1;
|
||||
out_info->kind = OAK_RENDER_BACKEND_VULKAN;
|
||||
out_info->capabilities = 0;
|
||||
out_info->capabilities = OAK_RENDER_BACKEND_CAP_TEXTURES |
|
||||
OAK_RENDER_BACKEND_CAP_SHADERS | OAK_RENDER_BACKEND_CAP_BLIT |
|
||||
OAK_RENDER_BACKEND_CAP_READBACK;
|
||||
out_info->name = "vulkan";
|
||||
out_info->status = "placeholder-unavailable";
|
||||
out_info->status = Renderer(handle)->IsAvailable() ? "available" : "unavailable";
|
||||
return true;
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_is_available(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
return false;
|
||||
auto *r = Renderer(handle);
|
||||
if (!r || r->IsAvailable()) {
|
||||
return r && r->IsAvailable();
|
||||
}
|
||||
// Try to initialize if not already available
|
||||
if (r->Init()) {
|
||||
r->PostInit();
|
||||
}
|
||||
return r->IsAvailable();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT bool oak_renderer_init(OakRenderBackendHandle handle)
|
||||
{
|
||||
WarnUnavailable(handle, "init");
|
||||
return false;
|
||||
return Renderer(handle)->Init();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_init_with_context(
|
||||
OakRenderBackendHandle handle, void *context)
|
||||
{
|
||||
Q_UNUSED(context)
|
||||
WarnUnavailable(handle, "init_with_context");
|
||||
Renderer(handle)->Init();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_init(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
Renderer(handle)->PostInit();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_post_destroy(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
Renderer(handle)->PostDestroy();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_internal(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
Renderer(handle)->DestroyInternal();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_clear_destination(
|
||||
OakRenderBackendHandle handle, void *texture, double r, double g, double b,
|
||||
double a)
|
||||
{
|
||||
Q_UNUSED(texture)
|
||||
Q_UNUSED(r)
|
||||
Q_UNUSED(g)
|
||||
Q_UNUSED(b)
|
||||
Q_UNUSED(a)
|
||||
WarnUnavailable(handle, "clear_destination");
|
||||
Renderer(handle)->ClearDestination(static_cast<olive::Texture *>(texture),
|
||||
r, g, b, a);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_texture(
|
||||
OakRenderBackendHandle handle, int width, int height, int depth, int format,
|
||||
int channel_count, const void *data, int linesize, void *out_variant)
|
||||
{
|
||||
Q_UNUSED(width)
|
||||
Q_UNUSED(height)
|
||||
Q_UNUSED(depth)
|
||||
Q_UNUSED(format)
|
||||
Q_UNUSED(channel_count)
|
||||
Q_UNUSED(data)
|
||||
Q_UNUSED(linesize)
|
||||
static_cast<QVariant *>(out_variant)->clear();
|
||||
WarnUnavailable(handle, "create_native_texture");
|
||||
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeTexture(
|
||||
width, height, depth, static_cast<olive::PixelFormat::Format>(format),
|
||||
channel_count, data, linesize);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_texture(
|
||||
OakRenderBackendHandle handle, const void *variant)
|
||||
{
|
||||
Q_UNUSED(variant)
|
||||
WarnUnavailable(handle, "destroy_native_texture");
|
||||
Renderer(handle)->DestroyNativeTexture(VariantRef(variant));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_create_native_shader(
|
||||
OakRenderBackendHandle handle, const void *shader_code, void *out_variant)
|
||||
{
|
||||
Q_UNUSED(shader_code)
|
||||
static_cast<QVariant *>(out_variant)->clear();
|
||||
WarnUnavailable(handle, "create_native_shader");
|
||||
*static_cast<QVariant *>(out_variant) = Renderer(handle)->CreateNativeShader(
|
||||
*static_cast<const olive::ShaderCode *>(shader_code));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_destroy_native_shader(
|
||||
OakRenderBackendHandle handle, const void *variant)
|
||||
{
|
||||
Q_UNUSED(variant)
|
||||
WarnUnavailable(handle, "destroy_native_shader");
|
||||
Renderer(handle)->DestroyNativeShader(VariantRef(variant));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_upload_to_texture(
|
||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||
const void *data, int linesize)
|
||||
{
|
||||
Q_UNUSED(variant)
|
||||
Q_UNUSED(video_params)
|
||||
Q_UNUSED(data)
|
||||
Q_UNUSED(linesize)
|
||||
WarnUnavailable(handle, "upload_to_texture");
|
||||
Renderer(handle)->UploadToTexture(
|
||||
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
|
||||
data, linesize);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_download_from_texture(
|
||||
OakRenderBackendHandle handle, const void *variant, const void *video_params,
|
||||
void *data, int linesize)
|
||||
{
|
||||
Q_UNUSED(variant)
|
||||
Q_UNUSED(video_params)
|
||||
Q_UNUSED(data)
|
||||
Q_UNUSED(linesize)
|
||||
WarnUnavailable(handle, "download_from_texture");
|
||||
Renderer(handle)->DownloadFromTexture(
|
||||
VariantRef(variant), *static_cast<const olive::VideoParams *>(video_params),
|
||||
data, linesize);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_flush(OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
Renderer(handle)->Flush();
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_get_pixel_from_texture(
|
||||
OakRenderBackendHandle handle, void *texture, const void *point,
|
||||
void *out_color)
|
||||
{
|
||||
Q_UNUSED(texture)
|
||||
Q_UNUSED(point)
|
||||
Q_UNUSED(out_color)
|
||||
WarnUnavailable(handle, "get_pixel_from_texture");
|
||||
*static_cast<olive::Color *>(out_color) = Renderer(handle)->GetPixelFromTexture(
|
||||
static_cast<olive::Texture *>(texture), *static_cast<const QPointF *>(point));
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_blit(
|
||||
OakRenderBackendHandle handle, const void *shader, void *job,
|
||||
void *destination, const void *destination_params, bool clear_destination)
|
||||
{
|
||||
Q_UNUSED(shader)
|
||||
Q_UNUSED(job)
|
||||
Q_UNUSED(destination)
|
||||
Q_UNUSED(destination_params)
|
||||
Q_UNUSED(clear_destination)
|
||||
WarnUnavailable(handle, "blit");
|
||||
Renderer(handle)->Blit(
|
||||
VariantRef(shader), *static_cast<olive::AcceleratedJob *>(job),
|
||||
static_cast<olive::Texture *>(destination),
|
||||
*static_cast<const olive::VideoParams *>(destination_params),
|
||||
clear_destination);
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||
@@ -194,3 +188,18 @@ OAK_RENDER_BACKEND_EXPORT void *oak_renderer_opengl_context(
|
||||
Q_UNUSED(handle)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_attach_output_texture(
|
||||
OakRenderBackendHandle handle, const void *texture_id)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
Q_UNUSED(texture_id)
|
||||
// Vulkan does not support OFX OpenGL render output attachment.
|
||||
}
|
||||
|
||||
OAK_RENDER_BACKEND_EXPORT void oak_renderer_detach_output_texture(
|
||||
OakRenderBackendHandle handle)
|
||||
{
|
||||
Q_UNUSED(handle)
|
||||
// Vulkan does not support OFX OpenGL render output attachment.
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,159 @@
|
||||
/***************************************************************************
|
||||
|
||||
Oak Video Editor
|
||||
Copyright (C) 2025 mikesolar
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef VULKANRENDERER_H
|
||||
#define VULKANRENDERER_H
|
||||
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
#include "render/renderer.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class VulkanRenderer : public Renderer {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VulkanRenderer(QObject *parent = nullptr);
|
||||
virtual ~VulkanRenderer() override;
|
||||
|
||||
virtual bool Init() override;
|
||||
virtual void PostInit() override;
|
||||
virtual void PostDestroy() override;
|
||||
|
||||
virtual void ClearDestination(olive::Texture *texture = nullptr,
|
||||
double r = 0.0, double g = 0.0,
|
||||
double b = 0.0, double a = 0.0) override;
|
||||
|
||||
virtual QVariant CreateNativeShader(olive::ShaderCode code) override;
|
||||
virtual void DestroyNativeShader(QVariant shader) override;
|
||||
|
||||
virtual void UploadToTexture(const QVariant &handle,
|
||||
const VideoParams ¶ms, const void *data,
|
||||
int linesize) override;
|
||||
virtual void DownloadFromTexture(const QVariant &handle,
|
||||
const VideoParams ¶ms, void *data,
|
||||
int linesize) override;
|
||||
|
||||
virtual void Flush() override;
|
||||
|
||||
virtual Color GetPixelFromTexture(olive::Texture *texture,
|
||||
const QPointF &pt) override;
|
||||
|
||||
bool IsAvailable() const
|
||||
{
|
||||
return device_ != VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Blit(QVariant shader, olive::AcceleratedJob &job,
|
||||
olive::Texture *destination, VideoParams destination_params,
|
||||
bool clear_destination) override;
|
||||
virtual QVariant CreateNativeTexture(int width, int height, int depth,
|
||||
PixelFormat format, int channel_count,
|
||||
const void *data = nullptr,
|
||||
int linesize = 0) override;
|
||||
virtual void DestroyNativeTexture(QVariant texture) override;
|
||||
virtual void DestroyInternal() override;
|
||||
|
||||
private:
|
||||
struct VulkanTexture;
|
||||
struct VulkanShader;
|
||||
struct UniformInfo;
|
||||
|
||||
bool CreateInstance();
|
||||
bool CreateDevice();
|
||||
bool CreateCommandPool();
|
||||
bool CreateDescriptorPool();
|
||||
bool CreateVertexBuffer();
|
||||
bool CreateLinearSampler();
|
||||
bool CreateStagingBuffer(VkDeviceSize size, VkBuffer *out_buffer,
|
||||
VkDeviceMemory *out_memory);
|
||||
void DestroyStagingBuffer(VkBuffer buffer, VkDeviceMemory memory);
|
||||
|
||||
VkCommandBuffer BeginOneTimeCommands();
|
||||
void EndOneTimeCommands(VkCommandBuffer cmd);
|
||||
|
||||
void TransitionImageLayout(VkCommandBuffer cmd, VkImage image,
|
||||
VkImageLayout old_layout,
|
||||
VkImageLayout new_layout);
|
||||
void CopyBufferToImage(VkCommandBuffer cmd, VkBuffer buffer, VkImage image,
|
||||
uint32_t width, uint32_t height, uint32_t depth);
|
||||
void CopyImageToBuffer(VkCommandBuffer cmd, VkImage image, VkBuffer buffer,
|
||||
uint32_t width, uint32_t height,
|
||||
uint32_t offset_x = 0, uint32_t offset_y = 0);
|
||||
|
||||
VkFormat PixelFormatToVkFormat(PixelFormat format, int channel_count) const;
|
||||
VkDeviceSize AlignSize(VkDeviceSize size, VkDeviceSize alignment) const;
|
||||
|
||||
uint32_t FindMemoryType(uint32_t type_filter,
|
||||
VkMemoryPropertyFlags properties) const;
|
||||
|
||||
bool CompileGlslToSpv(const QString &glsl, VkShaderStageFlagBits stage,
|
||||
QByteArray *out_spv);
|
||||
QString ConvertGlslToVulkan(const QString &glsl, VkShaderStageFlagBits stage);
|
||||
QString ConvertGlslUniformsToUbo(const QString &glsl,
|
||||
QVector<UniformInfo> *out_uniforms);
|
||||
VkDeviceSize GetStd140Size(const QString &type) const;
|
||||
VkDeviceSize GetStd140Alignment(const QString &type) const;
|
||||
|
||||
bool CreatePipelineForShader(VulkanShader *shader,
|
||||
const VideoParams &dest_params,
|
||||
VkFormat render_pass_format);
|
||||
|
||||
VkRenderPass GetOrCreateRenderPass(VkFormat format);
|
||||
|
||||
VkInstance instance_ = VK_NULL_HANDLE;
|
||||
VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
|
||||
VkDevice device_ = VK_NULL_HANDLE;
|
||||
VkQueue graphics_queue_ = VK_NULL_HANDLE;
|
||||
uint32_t graphics_queue_family_ = UINT32_MAX;
|
||||
VkCommandPool command_pool_ = VK_NULL_HANDLE;
|
||||
VkDescriptorPool descriptor_pool_ = VK_NULL_HANDLE;
|
||||
VkSampler linear_sampler_ = VK_NULL_HANDLE;
|
||||
|
||||
QHash<VkFormat, VkRenderPass> render_pass_cache_;
|
||||
|
||||
VkBuffer vertex_buffer_ = VK_NULL_HANDLE;
|
||||
VkDeviceMemory vertex_buffer_memory_ = VK_NULL_HANDLE;
|
||||
|
||||
VkPhysicalDeviceMemoryProperties mem_properties_;
|
||||
VkPhysicalDeviceProperties device_properties_;
|
||||
|
||||
QMutex mutex_;
|
||||
|
||||
// Texture handle counter
|
||||
quint64 next_texture_id_ = 1;
|
||||
QHash<quint64, VulkanTexture *> textures_;
|
||||
|
||||
// Shader handle counter
|
||||
quint64 next_shader_id_ = 1;
|
||||
QHash<quint64, VulkanShader *> shaders_;
|
||||
|
||||
static const int kMaxDescriptorSets = 1024;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VULKANRENDERER_H
|
||||
@@ -39,6 +39,9 @@
|
||||
#include "render/ipc/frameslotpool.h"
|
||||
#include "render/ipc/ipcmessage.h"
|
||||
#include "render/ipc/sharedmemoryregion.h"
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
#include "render/backend/dynamicrenderer.h"
|
||||
#endif
|
||||
#include "render/opengl/openglrenderer.h"
|
||||
#include "render/rendermanager.h"
|
||||
#include "render/renderprocessor.h"
|
||||
@@ -80,7 +83,7 @@ QJsonObject ErrorMessage(const QString &message, qint64 ticket_id = 0)
|
||||
|
||||
class RenderWorker {
|
||||
public:
|
||||
RenderWorker(olive::OpenGLRenderer *renderer, QFile *out)
|
||||
RenderWorker(olive::Renderer *renderer, QFile *out)
|
||||
: renderer_(renderer)
|
||||
, out_(out)
|
||||
{
|
||||
@@ -114,7 +117,16 @@ public:
|
||||
hs.input_slot_data_bytes = 0;
|
||||
|
||||
QJsonObject handshake = hs.ToJson();
|
||||
if (QOpenGLContext *ctx = renderer_->context()) {
|
||||
QOpenGLContext *ctx = nullptr;
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
if (auto *dynamic_renderer = dynamic_cast<olive::DynamicRenderer *>(renderer_)) {
|
||||
ctx = dynamic_renderer->OpenGLContext();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ctx = static_cast<olive::OpenGLRenderer *>(renderer_)->context();
|
||||
}
|
||||
if (ctx) {
|
||||
const QSurfaceFormat fmt = ctx->format();
|
||||
handshake["gl_major"] = fmt.majorVersion();
|
||||
handshake["gl_minor"] = fmt.minorVersion();
|
||||
@@ -427,7 +439,7 @@ private:
|
||||
return Write(ready.ToJson());
|
||||
}
|
||||
|
||||
olive::OpenGLRenderer *renderer_;
|
||||
olive::Renderer *renderer_;
|
||||
QFile *out_;
|
||||
bool shutdown_requested_ = false;
|
||||
std::unique_ptr<olive::Project> project_;
|
||||
@@ -459,15 +471,42 @@ int main(int argc, char *argv[])
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto *renderer = new olive::OpenGLRenderer();
|
||||
olive::Renderer *renderer;
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
auto *dynamic_renderer = new olive::DynamicRenderer(QStringLiteral("opengl"));
|
||||
if (dynamic_renderer->Init()) {
|
||||
dynamic_renderer->PostInit();
|
||||
renderer = dynamic_renderer;
|
||||
} else {
|
||||
delete dynamic_renderer;
|
||||
qWarning() << "Failed to initialize dynamic OpenGL backend, falling back to direct OpenGL renderer";
|
||||
renderer = new olive::OpenGLRenderer();
|
||||
if (!renderer->Init()) {
|
||||
LogError(QStringLiteral("failed to initialize OpenGL renderer"));
|
||||
delete renderer;
|
||||
return 1;
|
||||
}
|
||||
renderer->PostInit();
|
||||
}
|
||||
#else
|
||||
renderer = new olive::OpenGLRenderer();
|
||||
if (!renderer->Init()) {
|
||||
LogError(QStringLiteral("failed to initialize OpenGL renderer"));
|
||||
delete renderer;
|
||||
return 1;
|
||||
}
|
||||
renderer->PostInit();
|
||||
#endif
|
||||
|
||||
QOpenGLContext *ctx = renderer->context();
|
||||
QOpenGLContext *ctx = nullptr;
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
if (auto *loaded_renderer = dynamic_cast<olive::DynamicRenderer *>(renderer)) {
|
||||
ctx = loaded_renderer->OpenGLContext();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ctx = static_cast<olive::OpenGLRenderer *>(renderer)->context();
|
||||
}
|
||||
if (!ctx || !ctx->isValid()) {
|
||||
LogError(QStringLiteral("OpenGL context is not valid after init"));
|
||||
renderer->Destroy();
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const char *StyleManager::kDefaultStyle = "olive-dark";
|
||||
QString StyleManager::current_style_;
|
||||
QMap<QString, QString> StyleManager::available_themes_;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
static void SetStyle(const QString &style_path);
|
||||
|
||||
static const char *kDefaultStyle;
|
||||
inline static const char *kDefaultStyle = "olive-dark";
|
||||
|
||||
static const QMap<QString, QString> &available_themes()
|
||||
{
|
||||
|
||||
@@ -40,13 +40,33 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, color_manager_(nullptr)
|
||||
, color_service_(nullptr)
|
||||
, is_backend_neutral_(false)
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
// Create OpenGL widget
|
||||
// Create renderer
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
{
|
||||
auto *dynamic_renderer = new DynamicRenderer(
|
||||
RenderManager::BackendToString(
|
||||
RenderManager::instance()->requested_backend()),
|
||||
this);
|
||||
if (!dynamic_renderer->Load()) {
|
||||
qWarning() << "Failed to load dynamic render backend for viewer, falling back to OpenGL";
|
||||
delete dynamic_renderer;
|
||||
attached_renderer_ = new OpenGLRenderer(this);
|
||||
} else {
|
||||
attached_renderer_ = dynamic_renderer;
|
||||
}
|
||||
}
|
||||
#else
|
||||
attached_renderer_ = new OpenGLRenderer(this);
|
||||
#endif
|
||||
|
||||
if (attached_renderer_->IsOpenGL()) {
|
||||
// OpenGL path
|
||||
inner_widget_ = new ManagedDisplayWidgetOpenGL();
|
||||
inner_widget_->setAttribute(Qt::WA_TranslucentBackground, false);
|
||||
connect(static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_),
|
||||
@@ -64,16 +84,6 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
|
||||
|
||||
inner_widget_->installEventFilter(this);
|
||||
|
||||
// Create renderer bound to the widget's OpenGL context.
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
attached_renderer_ = new DynamicRenderer(
|
||||
RenderManager::BackendToString(
|
||||
RenderManager::instance()->requested_backend()),
|
||||
this);
|
||||
#else
|
||||
attached_renderer_ = new OpenGLRenderer(this);
|
||||
#endif
|
||||
|
||||
// Create widget wrapper for OpenGL window
|
||||
#ifdef USE_QOPENGLWINDOW
|
||||
wrapper_ = QWidget::createWindowContainer(
|
||||
@@ -83,19 +93,29 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
|
||||
#endif
|
||||
layout->addWidget(wrapper_);
|
||||
} else {
|
||||
inner_widget_ = nullptr;
|
||||
wrapper_ = nullptr;
|
||||
// Backend-neutral path (Vulkan, etc.)
|
||||
is_backend_neutral_ = true;
|
||||
auto *bn_widget = new ManagedDisplayWidgetBackendNeutral(this);
|
||||
inner_widget_ = bn_widget;
|
||||
inner_widget_->setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
inner_widget_->installEventFilter(this);
|
||||
connect(bn_widget, &ManagedDisplayWidgetBackendNeutral::OnPaint, this,
|
||||
&ManagedDisplayWidget::OnPaint, Qt::DirectConnection);
|
||||
wrapper_ = inner_widget_;
|
||||
layout->addWidget(wrapper_);
|
||||
}
|
||||
}
|
||||
|
||||
ManagedDisplayWidget::~ManagedDisplayWidget()
|
||||
{
|
||||
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER;
|
||||
if (!is_backend_neutral_) {
|
||||
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER;
|
||||
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
disconnect(static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_),
|
||||
&ManagedDisplayWidgetOpenGL::OnDestroy, this,
|
||||
&ManagedDisplayWidget::OnDestroy);
|
||||
} else {
|
||||
OnDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +271,7 @@ void ManagedDisplayWidget::SetColorTransform(const ColorTransform &transform)
|
||||
|
||||
void ManagedDisplayWidget::OnInit()
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
if (!is_backend_neutral_) {
|
||||
QOpenGLContext *context =
|
||||
static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_)->context();
|
||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||
@@ -264,6 +284,9 @@ void ManagedDisplayWidget::OnInit()
|
||||
#endif
|
||||
static_cast<OpenGLRenderer *>(attached_renderer_)->Init(context);
|
||||
static_cast<OpenGLRenderer *>(attached_renderer_)->PostInit();
|
||||
} else {
|
||||
attached_renderer_->Init();
|
||||
attached_renderer_->PostInit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,25 +303,21 @@ void ManagedDisplayWidget::ColorProcessorChangedEvent()
|
||||
|
||||
void ManagedDisplayWidget::makeCurrent()
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
if (!is_backend_neutral_) {
|
||||
static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_)->makeCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
void ManagedDisplayWidget::doneCurrent()
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
if (!is_backend_neutral_) {
|
||||
static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_)->doneCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
QPaintDevice *ManagedDisplayWidget::paint_device() const
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
return static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
return inner_widget_;
|
||||
}
|
||||
|
||||
void ManagedDisplayWidget::SetInnerMouseTracking(bool e)
|
||||
@@ -320,8 +339,8 @@ VideoParams ManagedDisplayWidget::GetViewportParams() const
|
||||
|
||||
void ManagedDisplayWidget::update()
|
||||
{
|
||||
if (RenderManager::instance()->backend() == RenderManager::kOpenGL) {
|
||||
static_cast<ManagedDisplayWidgetOpenGL *>(inner_widget_)->update();
|
||||
if (inner_widget_) {
|
||||
inner_widget_->update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,32 @@ private slots:
|
||||
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR_INNER; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Inner widget for backend-neutral rendering paths (e.g. Vulkan).
|
||||
*
|
||||
* It does not own a GL context; instead it forwards Qt paint events to the
|
||||
* ManagedDisplayWidget so that the viewer can render via QPainter.
|
||||
*/
|
||||
class ManagedDisplayWidgetBackendNeutral : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ManagedDisplayWidgetBackendNeutral(QWidget *parent = nullptr)
|
||||
: QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
signals:
|
||||
void OnPaint();
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event) override
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
emit OnPaint();
|
||||
}
|
||||
};
|
||||
|
||||
class ManagedDisplayWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -221,6 +247,11 @@ protected:
|
||||
|
||||
void SetInnerMouseTracking(bool e);
|
||||
|
||||
bool IsBackendNeutral() const
|
||||
{
|
||||
return is_backend_neutral_;
|
||||
}
|
||||
|
||||
QRect GetInnerRect() const
|
||||
{
|
||||
return wrapper_ ? wrapper_->rect() : QRect();
|
||||
@@ -285,6 +316,8 @@ private:
|
||||
*/
|
||||
ColorTransform color_transform_;
|
||||
|
||||
bool is_backend_neutral_ = false;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Sets all color settings to the defaults pertaining to this configuration
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "scopebase.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "render/job/colortransformjob.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
@@ -68,6 +69,11 @@ void ScopeBase::OnInit()
|
||||
|
||||
void ScopeBase::OnPaint()
|
||||
{
|
||||
if (IsBackendNeutral()) {
|
||||
// TODO: implement backend-neutral scope display
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear display surface
|
||||
renderer()->ClearDestination();
|
||||
|
||||
|
||||
@@ -375,19 +375,40 @@ bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e)
|
||||
|
||||
void ViewerDisplayWidget::OnPaint()
|
||||
{
|
||||
// Clear background to empty
|
||||
QColor bg_color = show_widget_background_ ? palette().window().color() :
|
||||
Qt::black;
|
||||
renderer()->ClearDestination(nullptr, bg_color.redF(), bg_color.greenF(),
|
||||
bg_color.blueF());
|
||||
const bool backend_neutral = IsBackendNeutral();
|
||||
|
||||
QPainter bg_painter;
|
||||
bool bg_painter_active = false;
|
||||
|
||||
if (backend_neutral) {
|
||||
// Backend-neutral path: draw background directly with QPainter. The
|
||||
// image itself will be rendered offscreen, downloaded, and painted below.
|
||||
bg_painter.begin(paint_device());
|
||||
bg_painter_active = true;
|
||||
bg_painter.fillRect(GetInnerRect(),
|
||||
show_widget_background_ ? palette().window().color() :
|
||||
Qt::black);
|
||||
} else {
|
||||
// Clear background to empty
|
||||
QColor bg_color = show_widget_background_ ? palette().window().color() :
|
||||
Qt::black;
|
||||
renderer()->ClearDestination(nullptr, bg_color.redF(), bg_color.greenF(),
|
||||
bg_color.blueF());
|
||||
}
|
||||
|
||||
VideoParams device_params;
|
||||
ColorTransformJob ctj;
|
||||
bool have_ctj = false;
|
||||
|
||||
// We only draw if we have a pipeline
|
||||
if (push_mode_ != kPushNull) {
|
||||
// Draw texture through color transform
|
||||
VideoParams device_params = GetViewportParams();
|
||||
device_params = GetViewportParams();
|
||||
|
||||
if (push_mode_ == kPushBlank) {
|
||||
DrawBlank(device_params);
|
||||
if (!backend_neutral) {
|
||||
DrawBlank(device_params);
|
||||
}
|
||||
} else if (color_service()) {
|
||||
if (FramePtr frame = load_frame_.value<FramePtr>()) {
|
||||
// This is a CPU frame, upload it now
|
||||
@@ -408,27 +429,17 @@ void ViewerDisplayWidget::OnPaint()
|
||||
// This is a GPU texture, switch to it directly when possible.
|
||||
if (texture && texture->renderer() &&
|
||||
texture->renderer() != renderer()) {
|
||||
bool copied = false;
|
||||
QOpenGLContext *ctx = QOpenGLContext::currentContext();
|
||||
if (ctx) {
|
||||
QOpenGLFunctions *funcs = ctx->functions();
|
||||
GLuint tex_id = texture->id().value<GLuint>();
|
||||
if (funcs && tex_id && funcs->glIsTexture(tex_id)) {
|
||||
FramePtr frame = Frame::Create();
|
||||
frame->set_video_params(texture->params());
|
||||
if (frame->allocate()) {
|
||||
renderer()->DownloadFromTexture(
|
||||
texture->id(), texture->params(),
|
||||
frame->data(), frame->linesize_pixels());
|
||||
texture_ = renderer()->CreateTexture(
|
||||
frame->video_params(), frame->data(),
|
||||
frame->linesize_pixels());
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!copied) {
|
||||
// Cross-renderer texture: download and re-upload
|
||||
FramePtr frame = Frame::Create();
|
||||
frame->set_video_params(texture->params());
|
||||
if (frame->allocate()) {
|
||||
texture->renderer()->DownloadFromTexture(
|
||||
texture->id(), texture->params(),
|
||||
frame->data(), frame->linesize_pixels());
|
||||
texture_ = renderer()->CreateTexture(
|
||||
frame->video_params(), frame->data(),
|
||||
frame->linesize_pixels());
|
||||
} else {
|
||||
texture_ = texture;
|
||||
}
|
||||
} else {
|
||||
@@ -445,7 +456,9 @@ void ViewerDisplayWidget::OnPaint()
|
||||
TexturePtr texture_to_draw = texture_;
|
||||
|
||||
if (!texture_to_draw || texture_to_draw->IsDummy()) {
|
||||
DrawBlank(device_params);
|
||||
if (!backend_neutral) {
|
||||
DrawBlank(device_params);
|
||||
}
|
||||
} else {
|
||||
if (deinterlace_) {
|
||||
if (deinterlace_shader_.isNull()) {
|
||||
@@ -477,7 +490,6 @@ void ViewerDisplayWidget::OnPaint()
|
||||
texture_to_draw = deinterlace_texture_;
|
||||
}
|
||||
|
||||
ColorTransformJob ctj;
|
||||
ctj.SetColorProcessor(color_service());
|
||||
ctj.SetInputTexture(texture_to_draw);
|
||||
ctj.SetInputAlphaAssociation(
|
||||
@@ -489,13 +501,25 @@ void ViewerDisplayWidget::OnPaint()
|
||||
ctj.SetCropMatrix(crop_matrix_);
|
||||
ctj.SetForceOpaque(true);
|
||||
|
||||
renderer()->BlitColorManaged(ctj, device_params);
|
||||
have_ctj = true;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "[VIEWER] OnPaint no color_service, skipping texture draw";
|
||||
}
|
||||
}
|
||||
|
||||
if (have_ctj) {
|
||||
if (backend_neutral) {
|
||||
DrawBackendNeutral(ctj, &bg_painter);
|
||||
} else {
|
||||
renderer()->BlitColorManaged(ctj, device_params);
|
||||
}
|
||||
}
|
||||
|
||||
if (bg_painter_active) {
|
||||
bg_painter.end();
|
||||
}
|
||||
|
||||
// Draw gizmos if we have any
|
||||
if (gizmos_) {
|
||||
QPainter p(paint_device());
|
||||
@@ -633,6 +657,8 @@ void ViewerDisplayWidget::OnDestroy()
|
||||
|
||||
texture_ = nullptr;
|
||||
deinterlace_texture_ = nullptr;
|
||||
backend_neutral_texture_ = nullptr;
|
||||
backend_neutral_buffer_.clear();
|
||||
if (load_frame_.isNull()) {
|
||||
push_mode_ = kPushNull;
|
||||
} else {
|
||||
@@ -1364,6 +1390,55 @@ void ViewerDisplayWidget::DrawBlank(const VideoParams &device_params)
|
||||
renderer()->Blit(blank_shader_, job, device_params, false);
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::DrawBackendNeutral(const ColorTransformJob &ctj,
|
||||
QPainter *painter)
|
||||
{
|
||||
if (!painter || !painter->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int texture_width =
|
||||
static_cast<int>(width() * devicePixelRatioF());
|
||||
const int texture_height =
|
||||
static_cast<int>(height() * devicePixelRatioF());
|
||||
|
||||
const VideoParams offscreen_params(
|
||||
texture_width, texture_height, PixelFormat::U8,
|
||||
VideoParams::kRGBAChannelCount);
|
||||
|
||||
if (!backend_neutral_texture_ ||
|
||||
backend_neutral_texture_->params() != offscreen_params) {
|
||||
backend_neutral_texture_ = renderer()->CreateTexture(offscreen_params);
|
||||
backend_neutral_buffer_.resize(
|
||||
texture_width * texture_height *
|
||||
VideoParams::GetBytesPerPixel(PixelFormat::U8,
|
||||
VideoParams::kRGBAChannelCount));
|
||||
}
|
||||
|
||||
if (!backend_neutral_texture_ || backend_neutral_texture_->IsDummy()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ColorTransformJob local_ctj = ctj;
|
||||
local_ctj.SetClearDestinationEnabled(true);
|
||||
|
||||
renderer()->BlitColorManaged(local_ctj, backend_neutral_texture_.get());
|
||||
|
||||
backend_neutral_texture_->Download(backend_neutral_buffer_.data(), 0);
|
||||
|
||||
const int bytes_per_pixel = VideoParams::GetBytesPerPixel(
|
||||
PixelFormat::U8, VideoParams::kRGBAChannelCount);
|
||||
|
||||
QImage img(reinterpret_cast<const uchar *>(
|
||||
backend_neutral_buffer_.constData()),
|
||||
texture_width, texture_height,
|
||||
texture_width * bytes_per_pixel,
|
||||
QImage::Format_RGBA8888_Premultiplied);
|
||||
img.setDevicePixelRatio(devicePixelRatioF());
|
||||
|
||||
painter->drawImage(QPoint(0, 0), img);
|
||||
}
|
||||
|
||||
void ViewerDisplayWidget::SetShowFPS(bool e)
|
||||
{
|
||||
show_fps_ = e;
|
||||
|
||||
@@ -327,6 +327,8 @@ private:
|
||||
|
||||
void DrawBlank(const VideoParams &device_params);
|
||||
|
||||
void DrawBackendNeutral(const ColorTransformJob &ctj, QPainter *painter);
|
||||
|
||||
/**
|
||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
||||
*/
|
||||
@@ -337,6 +339,19 @@ private:
|
||||
*/
|
||||
TexturePtr deinterlace_texture_;
|
||||
|
||||
/**
|
||||
* @brief Offscreen texture for backend-neutral viewer rendering.
|
||||
*
|
||||
* The texture is rendered at device resolution and read back to a QImage so
|
||||
* it can be painted with QPainter on the plain QWidget inner surface.
|
||||
*/
|
||||
TexturePtr backend_neutral_texture_;
|
||||
|
||||
/**
|
||||
* @brief CPU readback buffer for backend_neutral_texture_.
|
||||
*/
|
||||
QByteArray backend_neutral_buffer_;
|
||||
|
||||
/**
|
||||
* @brief Deinterlace shader
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user