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:
@@ -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