R7: pure C ABI display boundary + engine visibility closure

R7-A (Qwen 3.8 Max): oakengine/display.h rewritten to the POD contract
from r7-pure-abi-plan.md - oak_video_params everywhere, opaque
texture/frame handles with retain/free protocol (engine-heap control
blocks), OakSharedBuffer refcounted wrapper for the QVariant playback
path. All TexturePtr/FramePtr gone from app (47 sites).

R7-B (Qwen 3.8 Max): liboakengine.so exports 3486 -> 19 C++ symbols
(version script oakengine.ver: oakengine_* plus the documented
oakgl/oakvulkan dlopen plugin ABI). oakengine-obj OBJECT library feeds
both the shared lib and the test binaries (-rdynamic so dlopen'd
backends resolve engine objects).

Fix (Kimi K3): producer/consumer type mismatch - viewerdisplay
unpacks OakSharedBufferPtr but viewer.cpp pushed raw void* handles,
so no frame ever reached the display widget (all 5 vulkan viewer
tests timed out with 'never received a texture'). Producers now wrap
with oak_make_shared_frame / oak_make_shared_texture(retain).

Verified: build 0 errors, ctest 45/45, nm U _ZN5olive = 0 in
oak-editor/oak-render-worker/oak-cli, 19 exported C++ symbols in
liboakengine.so (all documented plugin ABI).
This commit is contained in:
2026-07-27 00:54:46 +08:00
parent 9b25772267
commit eb634b53ef
33 changed files with 1692 additions and 668 deletions
+5 -23
View File
@@ -21,33 +21,10 @@ set(OLIVE_SOURCES
core.cpp
engineeventbridge.h
engineeventbridge.cpp
common/htmlapp.cpp
common/filefunctionsapp.cpp
common/colorcodingapp.h
common/colorcodingapp.cpp
common/xmlutilsapp.cpp
common/hashstreamapp.cpp
common/qtutilsapp.cpp
common/nodevaluehandle.h
)
# The app-side *app.cpp helpers define symbols whose qualified names also
# exist in liboakengine.so (B10 moved utilities). Build them with hidden
# visibility so the executable's definitions are not ELF-interposable: the
# engine library keeps binding to its own copies and static data members
# (ColorCoding::colors, Html::k_block_tags) are not double-initialized and
# double-destroyed at process exit (was the exit-time heap corruption in
# timeline-tests / olive-gtest).
set_source_files_properties(
common/htmlapp.cpp
common/filefunctionsapp.cpp
common/colorcodingapp.cpp
common/xmlutilsapp.cpp
common/hashstreamapp.cpp
common/qtutilsapp.cpp
PROPERTIES COMPILE_OPTIONS "-fvisibility=hidden"
)
#set(OLIVE_RESOURCES)
# Add subdirectories, which will populate the above variables
@@ -191,6 +168,11 @@ endif ()
# Set link libraries
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
target_link_libraries(libolive-editor PRIVATE ${OLIVE_LIBRARIES})
# The app is an internal consumer that still uses engine C++ classes directly.
# Link the object library to bypass the version-script restrictions on
# liboakengine.so (which only exposes the C ABI for external consumers).
target_link_libraries(olive-editor PRIVATE oakengine-obj)
target_link_libraries(libolive-editor PRIVATE oakengine-obj)
# The ffmpeg_bridge shared library ships next to the binaries: inside the
# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in the
+1 -1
View File
@@ -127,7 +127,7 @@ void ScopePanel::set_viewer_panel(ViewerPanelBase *vp)
}
}
void ScopePanel::set_reference_buffer(TexturePtr frame)
void ScopePanel::set_reference_buffer(void *frame)
{
histogram_->set_buffer(frame);
vectorscope_->set_buffer(frame);
+1 -1
View File
@@ -59,7 +59,7 @@ public:
}
public slots:
void set_reference_buffer(TexturePtr frame);
void set_reference_buffer(void *frame);
void set_color_manager(OakEngineColorManager *manager);
+1 -1
View File
@@ -107,7 +107,7 @@ signals:
/**
* @brief Signal emitted when a new frame is loaded
*/
void texture_changed(TexturePtr t);
void texture_changed(void *t);
/**
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
+10 -11
View File
@@ -54,22 +54,22 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
return oakengine_render_manager_backend_to_string(backend, buf,
sz);
});
attached_renderer_ = static_cast<Renderer *>(
attached_renderer_ =
oakengine_display_renderer_create_dynamic(
backend.toUtf8().constData(), this));
backend.toUtf8().constData(), this);
if (!attached_renderer_) {
qWarning()
<< "Failed to load dynamic render backend for viewer, falling back to OpenGL";
attached_renderer_ = static_cast<Renderer *>(
oakengine_display_renderer_create_opengl(this));
attached_renderer_ =
oakengine_display_renderer_create_opengl(this);
}
}
#else
attached_renderer_ = static_cast<Renderer *>(
oakengine_display_renderer_create_opengl(this));
attached_renderer_ =
oakengine_display_renderer_create_opengl(this);
#endif
if (attached_renderer_->is_open_gl()) {
if (oakengine_display_renderer_is_open_gl(attached_renderer_)) {
// OpenGL path
inner_widget_ = new ManagedDisplayWidgetOpenGL();
inner_widget_->setAttribute(Qt::WA_TranslucentBackground, false);
@@ -363,17 +363,16 @@ void ManagedDisplayWidget::set_inner_mouse_tracking(bool e)
}
}
VideoParams ManagedDisplayWidget::get_viewport_params() const
oak_video_params ManagedDisplayWidget::get_viewport_params() const
{
int device_width = width() * devicePixelRatioF();
int device_height = height() * devicePixelRatioF();
PixelFormat device_format = static_cast<PixelFormat::Format>(
OAK_CONFIG("OfflinePixelFormat").toInt());
int device_format = OAK_CONFIG("OfflinePixelFormat").toInt();
oak_video_params pod = {};
pod.width = device_width;
pod.height = device_height;
pod.format = device_format;
return video_params_from_pod(pod);
return pod;
}
void ManagedDisplayWidget::update()
+4 -4
View File
@@ -33,10 +33,10 @@
#endif
#include "oakengine/color.h"
#include "oakengine/display.h"
#include "oakengine/events.h"
#include "render/colorprocessor.h"
#include "render/colortransform.h"
#include "render/renderer.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/menu/menu.h"
@@ -222,7 +222,7 @@ protected:
*/
virtual void color_processor_changed_event();
Renderer *renderer() const
void *renderer() const
{
return attached_renderer_;
}
@@ -261,7 +261,7 @@ protected:
return wrapper_ ? wrapper_->rect() : QRect();
}
VideoParams get_viewport_params() const;
oak_video_params get_viewport_params() const;
protected slots:
/**
@@ -303,7 +303,7 @@ private:
/**
* @brief Renderer abstraction
*/
Renderer *attached_renderer_;
void *attached_renderer_;
/**
* @brief Connected color manager
+37 -20
View File
@@ -32,6 +32,7 @@ namespace olive
MulticamDisplay::MulticamDisplay(QWidget *parent)
: super(parent)
, node_(nullptr)
, shader_(nullptr)
, rows_(0)
, cols_(0)
{
@@ -70,47 +71,63 @@ void MulticamDisplay::on_paint()
void MulticamDisplay::on_destroy()
{
shader_ = QVariant();
if (shader_) {
oakengine_display_renderer_destroy_shader(renderer(), shader_);
shader_ = nullptr;
}
}
TexturePtr MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
void *MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
{
if (v.canConvert<QVector<TexturePtr>>()) {
QVector<TexturePtr> tex = v.value<QVector<TexturePtr>>();
if (v.canConvert<QVector<void *>>()) {
QVector<void *> tex = v.value<QVector<void *>>();
TexturePtr main;
const VideoParams main_params = this->get_viewport_params();
oakengine_display_renderer_create_texture(renderer(), &main_params,
nullptr, 0, &main);
oak_video_params main_params = this->get_viewport_params();
void *main_tex = oakengine_display_texture_create(
renderer(), &main_params, nullptr, 0);
int rows, cols;
oakengine_multicam_get_rows_and_columns(tex.size(), &rows, &cols);
if (shader_.isNull() || rows_ != rows || cols_ != cols) {
if (!shader_.isNull()) {
renderer()->destroy_native_shader(shader_);
if (!shader_ || rows_ != rows || cols_ != cols) {
if (shader_) {
oakengine_display_renderer_destroy_shader(renderer(), shader_);
}
shader_ = renderer()->create_native_shader(
ShaderCode(generate_shader_code(rows, cols)));
QString code = generate_shader_code(rows, cols);
shader_ = oakengine_display_renderer_create_shader(
renderer(), code.toUtf8().constData(), nullptr);
rows_ = rows;
cols_ = cols;
}
ShaderJob job;
// Build name and texture arrays for multi-texture blit
const int count = tex.size();
QVector<QByteArray> name_storage(count);
QVector<const char *> names(count);
QVector<void *> textures(count);
for (int i = 0; i < tex.size(); i++) {
for (int i = 0; i < count; i++) {
int c, r;
oakengine_multicam_index_to_row_cols(i, rows, cols, &r, &c);
job.insert(QStringLiteral("tex_%1_%2")
.arg(QString::number(r), QString::number(c)),
NodeValue(NodeValue::k_texture, tex.at(i)));
name_storage[i] = QStringLiteral("tex_%1_%2")
.arg(QString::number(r), QString::number(c))
.toUtf8();
names[i] = name_storage[i].constData();
textures[i] = tex.at(i);
}
renderer()->blit_to_texture(shader_, job, main.get());
oakengine_display_renderer_blit_shader_multi(
renderer(), shader_, names.data(), textures.data(), count,
main_tex);
return main;
// Release input texture handles (they were retained by the engine)
for (int i = 0; i < count; i++) {
oakengine_display_texture_free(tex.at(i));
}
return main_tex;
} else {
return super::load_custom_texture_from_frame(v);
}
+2 -2
View File
@@ -40,14 +40,14 @@ protected:
virtual void on_destroy() override;
virtual TexturePtr load_custom_texture_from_frame(const QVariant &v) override;
virtual void *load_custom_texture_from_frame(const QVariant &v) override;
private:
static QString generate_shader_code(int rows, int cols);
MultiCamNode *node_;
QVariant shader_;
void *shader_;
int rows_;
int cols_;
};
+54 -41
View File
@@ -1,3 +1,4 @@
/***
Olive - Non-Linear Video Editor
@@ -24,12 +25,10 @@
#include <array>
#include <QPainter>
#include <QtMath>
#include <QVector2D>
#include "common/qtutils.h"
#include "node/node.h"
#include "common/filefunctions.h"
#include "oakengine/display.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -38,6 +37,8 @@ namespace olive
HistogramScope::HistogramScope(QWidget *parent)
: super(parent)
, pipeline_secondary_(nullptr)
, texture_row_sums_(nullptr)
{
}
@@ -45,29 +46,37 @@ void HistogramScope::on_init()
{
super::on_init();
ShaderCode secondary_code(
FileFunctions::read_file_as_string(
":/shaders/rgbhistogram_secondary.frag"),
FileFunctions::read_file_as_string(":/shaders/rgbhistogram.vert"));
pipeline_secondary_ = renderer()->create_native_shader(secondary_code);
QString frag = FileFunctions::read_file_as_string(
":/shaders/rgbhistogram_secondary.frag");
QString vert = FileFunctions::read_file_as_string(
":/shaders/rgbhistogram.vert");
pipeline_secondary_ = oakengine_display_renderer_create_shader(
renderer(), frag.toUtf8().constData(), vert.toUtf8().constData());
}
void HistogramScope::on_destroy()
{
pipeline_secondary_.clear();
texture_row_sums_ = nullptr;
if (pipeline_secondary_) {
oakengine_display_renderer_destroy_shader(renderer(),
pipeline_secondary_);
pipeline_secondary_ = nullptr;
}
if (texture_row_sums_) {
oakengine_display_texture_free(texture_row_sums_);
texture_row_sums_ = nullptr;
}
super::on_destroy();
}
ShaderCode HistogramScope::generate_shader_code()
ScopeShaderCode HistogramScope::generate_shader_code()
{
return ShaderCode(
return ScopeShaderCode{
FileFunctions::read_file_as_string(":/shaders/rgbhistogram.frag"),
FileFunctions::read_file_as_string(":/shaders/default.vert"));
FileFunctions::read_file_as_string(":/shaders/default.vert")};
}
void HistogramScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
void HistogramScope::draw_scope(void *managed_tex, void *pipeline)
{
float histogram_scale = 0.80f;
// This value is eyeballed for usefulness. Until we have a geometry
@@ -76,40 +85,45 @@ void HistogramScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
float histogram_base = 2.5f;
float histogram_power = 1.0f / histogram_base;
ShaderJob shader_job;
// Set up uniforms shared by both passes
oak_shader_uniform uniforms[3];
uniforms[0] = {"viewport", 1,
{static_cast<float>(width()), static_cast<float>(height())}};
uniforms[1] = {"histogram_scale", 0, {histogram_scale, 0.0f}};
uniforms[2] = {"histogram_power", 0, {histogram_power, 0.0f}};
shader_job.insert(QStringLiteral("viewport"),
NodeValue(NodeValue::k_vec2,
QVector2D(width(), height())));
shader_job.insert(QStringLiteral("histogram_scale"),
NodeValue(NodeValue::k_float, histogram_scale));
shader_job.insert(QStringLiteral("histogram_power"),
NodeValue(NodeValue::k_float, histogram_power));
// Recreate row-sums texture if size changed
bool need_recreate = false;
if (!texture_row_sums_) {
need_recreate = true;
} else if (oakengine_display_texture_width(texture_row_sums_) != width() ||
oakengine_display_texture_height(texture_row_sums_) !=
height()) {
need_recreate = true;
}
if (!texture_row_sums_ || texture_row_sums_->width() != this->width() ||
texture_row_sums_->height() != this->height()) {
if (need_recreate) {
if (texture_row_sums_) {
oakengine_display_texture_free(texture_row_sums_);
}
oak_video_params pod = {};
pod.width = width();
pod.height = height();
pod.format = managed_tex->format();
const VideoParams row_sums_params = video_params_from_pod(pod);
oakengine_display_renderer_create_texture(renderer(),
&row_sums_params, nullptr, 0,
&texture_row_sums_);
pod.format = oakengine_display_texture_format(managed_tex);
texture_row_sums_ =
oakengine_display_texture_create(renderer(), &pod, nullptr, 0);
}
// Draw managed texture to a sums texture
shader_job.insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(managed_tex)));
renderer()->blit_to_texture(pipeline, shader_job, texture_row_sums_.get());
// Pass 1: Draw managed texture to row-sums texture
oakengine_display_renderer_blit_shader_uniforms(
renderer(), pipeline, managed_tex, uniforms, 3, texture_row_sums_,
nullptr);
// Draw sums into a histogram
shader_job.insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(texture_row_sums_)));
renderer()->blit(pipeline_secondary_, shader_job,
texture_row_sums_->params());
// Pass 2: Draw row-sums into a histogram on screen
oak_video_params vp = get_viewport_params();
oakengine_display_renderer_blit_shader_uniforms(
renderer(), pipeline_secondary_, texture_row_sums_, uniforms, 3,
nullptr, &vp);
// Draw line overlays
QPainter p(paint_device());
@@ -135,7 +149,6 @@ void HistogramScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
float histogram_start_dim_y = ((height() - 1.0) - histogram_dim_y) / 2.0f;
float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x;
// for (int i=0; i <= histogram_steps; i++) {
for (std::vector<float>::iterator it = histogram_increments.begin();
it != histogram_increments.end(); it++) {
histogram_lines[it - histogram_increments.begin()].setLine(
+4 -5
View File
@@ -40,16 +40,15 @@ protected slots:
virtual void on_destroy() override;
protected:
virtual ShaderCode generate_shader_code() override;
QVariant create_secondary_shader();
virtual ScopeShaderCode generate_shader_code() override;
virtual void draw_scope(TexturePtr managed_tex, QVariant pipeline) override;
virtual void draw_scope(void *managed_tex, void *pipeline) override;
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
private:
QVariant pipeline_secondary_;
TexturePtr texture_row_sums_;
void *pipeline_secondary_;
void *texture_row_sums_;
};
}
+123 -69
View File
@@ -26,9 +26,6 @@
#include "common/configwrapper.h"
#include "oakengine/display.h"
#include "oakengine/videoparams.h"
#include "render/job/colortransformjob.h"
#include "widget/viewer/vieweroutpututils.h"
#include "render/job/shaderjob.h"
namespace olive
{
@@ -37,16 +34,23 @@ namespace olive
ScopeBase::ScopeBase(QWidget *parent)
: super(parent)
, pipeline_(nullptr)
, texture_(nullptr)
, managed_tex_(nullptr)
, managed_tex_up_to_date_(false)
, software_tex_(nullptr)
, software_image_up_to_date_(false)
, local_texture_(nullptr)
{
enable_default_context_menu();
}
void ScopeBase::set_buffer(TexturePtr frame)
void ScopeBase::set_buffer(void *frame)
{
texture_ = frame;
if (texture_) {
oakengine_display_texture_free(texture_);
}
texture_ = oakengine_display_texture_retain(frame);
managed_tex_up_to_date_ = false;
software_image_up_to_date_ = false;
update();
@@ -57,20 +61,17 @@ void ScopeBase::showEvent(QShowEvent *e)
super::showEvent(e);
}
void ScopeBase::draw_scope(TexturePtr managed_tex, QVariant pipeline)
void ScopeBase::draw_scope(void *managed_tex, void *pipeline)
{
ShaderJob job;
job.insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(managed_tex)));
renderer()->blit(pipeline, job, get_viewport_params());
oak_video_params vp = get_viewport_params();
oakengine_display_renderer_blit_shader(renderer(), pipeline, managed_tex,
&vp);
}
void ScopeBase::update_software_image()
{
if (!texture_ || texture_->is_dummy() || !renderer()) {
if (!texture_ || oakengine_display_texture_is_dummy(texture_) ||
!renderer()) {
software_image_ = QImage();
software_image_up_to_date_ = true;
return;
@@ -80,23 +81,29 @@ void ScopeBase::update_software_image()
// separate Vulkan device). The reference texture emitted by the viewer lives
// in the viewer's renderer, so we must download it to the CPU and re-upload
// it into this scope's renderer before we can sample it.
TexturePtr source_tex = texture_;
if (texture_->renderer() && texture_->renderer() != renderer()) {
FramePtr temp_frame;
oakengine_codec_frame_create(&temp_frame);
oakengine_codec_frame_set_video_params(temp_frame.get(),
&texture_->params());
oakengine_codec_frame_allocate(temp_frame.get());
oakengine_display_texture_download(texture_.get(), temp_frame->data(),
temp_frame->linesize_pixels());
void *source_tex = texture_;
void *tex_renderer = oakengine_display_texture_renderer(texture_);
if (tex_renderer && tex_renderer != renderer()) {
void *temp_frame = oakengine_codec_frame_create();
oak_video_params tex_params = {};
oakengine_display_texture_get_params(texture_, &tex_params);
oakengine_codec_frame_set_video_params(temp_frame, &tex_params);
oakengine_codec_frame_allocate(temp_frame);
oakengine_display_texture_download(
texture_, oakengine_codec_frame_data(temp_frame),
oakengine_codec_frame_linesize(temp_frame));
oakengine_display_renderer_create_texture(
renderer(), &temp_frame->video_params(), temp_frame->data(),
temp_frame->linesize_pixels(), &local_texture_);
if (local_texture_) {
oakengine_display_texture_free(local_texture_);
}
local_texture_ = oakengine_display_texture_create(
renderer(), &tex_params, oakengine_codec_frame_const_data(temp_frame),
oakengine_codec_frame_linesize(temp_frame));
oakengine_codec_frame_free(temp_frame);
source_tex = local_texture_;
}
if (!source_tex || source_tex->is_dummy()) {
if (!source_tex || oakengine_display_texture_is_dummy(source_tex)) {
software_image_ = QImage();
software_image_up_to_date_ = true;
return;
@@ -105,46 +112,60 @@ void ScopeBase::update_software_image()
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
oak_video_params pod = {};
pod.width = texture_width;
pod.height = texture_height;
pod.format = PixelFormat::u8;
const VideoParams offscreen_params(video_params_from_pod(pod));
oak_video_params offscreen_pod = {};
offscreen_pod.width = texture_width;
offscreen_pod.height = texture_height;
offscreen_pod.format = 0; // PixelFormat::u8
if (!software_tex_ || software_tex_->params() != offscreen_params) {
oakengine_display_renderer_create_texture(renderer(),
&offscreen_params, nullptr, 0,
&software_tex_);
software_buffer_.resize(
texture_width * texture_height *
oakengine_video_params_bytes_per_pixel(PixelFormat::u8,
4));
bool need_recreate = false;
if (!software_tex_) {
need_recreate = true;
} else {
oak_video_params cur = {};
oakengine_display_texture_get_params(software_tex_, &cur);
if (cur.width != offscreen_pod.width ||
cur.height != offscreen_pod.height ||
cur.format != offscreen_pod.format) {
need_recreate = true;
}
}
if (!software_tex_ || software_tex_->is_dummy()) {
if (need_recreate) {
if (software_tex_) {
oakengine_display_texture_free(software_tex_);
}
software_tex_ = oakengine_display_texture_create(
renderer(), &offscreen_pod, nullptr, 0);
software_buffer_.resize(texture_width * texture_height *
oakengine_video_params_bytes_per_pixel(0, 4));
}
if (!software_tex_ || oakengine_display_texture_is_dummy(software_tex_)) {
software_image_ = QImage();
software_image_up_to_date_ = true;
return;
}
ColorTransformJob job;
oakengine_color_transform_job_set_processor(&job, color_service().get());
job.set_input_texture(source_tex);
job.set_input_alpha_association(k_alpha_none);
job.set_clear_destination_enabled(true);
job.set_force_opaque(true);
oak_color_transform_job job = {};
job.processor = color_service().get();
job.input_texture = source_tex;
job.input_alpha_association = 0; // k_alpha_none
job.clear_destination = 1;
job.force_opaque = 1;
oakengine_display_renderer_blit_color_managed(renderer(), &job,
software_tex_.get(), nullptr);
renderer()->download_from_texture(software_tex_->id(),
software_tex_->params(),
software_buffer_.data(), 0);
software_tex_, nullptr);
int sw_id = oakengine_display_texture_id(software_tex_);
oak_video_params sw_params = {};
oakengine_display_texture_get_params(software_tex_, &sw_params);
oakengine_display_renderer_download_from_texture(
renderer(), sw_id, &sw_params, software_buffer_.data(), 0);
software_image_ = QImage(
reinterpret_cast<const uchar *>(software_buffer_.constData()),
texture_width, texture_height,
texture_width * oakengine_video_params_bytes_per_pixel(
PixelFormat::u8, 4),
texture_width * oakengine_video_params_bytes_per_pixel(0, 4),
QImage::Format_RGBA8888_Premultiplied);
software_image_.setDevicePixelRatio(devicePixelRatioF());
@@ -156,7 +177,11 @@ void ScopeBase::on_init()
super::on_init();
if (!is_backend_neutral()) {
pipeline_ = renderer()->create_native_shader(generate_shader_code());
ScopeShaderCode code = generate_shader_code();
pipeline_ = oakengine_display_renderer_create_shader(
renderer(), code.frag.toUtf8().constData(),
code.vert.isEmpty() ? nullptr
: code.vert.toUtf8().constData());
}
}
@@ -177,22 +202,36 @@ void ScopeBase::on_paint()
}
// Clear display surface
renderer()->clear_destination();
oakengine_display_renderer_clear(renderer(), 0.0, 0.0, 0.0);
if (texture_) {
// Convert reference frame to display space
if (!managed_tex_ || !managed_tex_up_to_date_ ||
managed_tex_->params() != texture_->params()) {
oakengine_display_renderer_create_texture(
renderer(), &texture_->params(), nullptr, 0, &managed_tex_);
bool need_recreate = false;
if (!managed_tex_ || !managed_tex_up_to_date_) {
need_recreate = true;
} else if (!oakengine_display_texture_params_equal(managed_tex_,
texture_)) {
need_recreate = true;
}
ColorTransformJob job;
oakengine_color_transform_job_set_processor(&job, color_service().get());
job.set_input_texture(texture_);
job.set_input_alpha_association(k_alpha_none);
if (need_recreate) {
if (managed_tex_) {
oakengine_display_texture_free(managed_tex_);
}
oak_video_params tex_params = {};
oakengine_display_texture_get_params(texture_, &tex_params);
managed_tex_ = oakengine_display_texture_create(
renderer(), &tex_params, nullptr, 0);
oak_color_transform_job job = {};
job.processor = color_service().get();
job.input_texture = texture_;
job.input_alpha_association = 0; // k_alpha_none
job.clear_destination = 0;
job.force_opaque = 0;
oakengine_display_renderer_blit_color_managed(
renderer(), &job, managed_tex_.get(), nullptr);
renderer(), &job, managed_tex_, nullptr);
managed_tex_up_to_date_ = true;
}
@@ -202,13 +241,28 @@ void ScopeBase::on_paint()
void ScopeBase::on_destroy()
{
local_texture_ = nullptr;
software_tex_ = nullptr;
if (local_texture_) {
oakengine_display_texture_free(local_texture_);
local_texture_ = nullptr;
}
if (software_tex_) {
oakengine_display_texture_free(software_tex_);
software_tex_ = nullptr;
}
software_buffer_.clear();
software_image_ = QImage();
managed_tex_ = nullptr;
texture_ = nullptr;
pipeline_.clear();
if (managed_tex_) {
oakengine_display_texture_free(managed_tex_);
managed_tex_ = nullptr;
}
if (texture_) {
oakengine_display_texture_free(texture_);
texture_ = nullptr;
}
if (pipeline_) {
oakengine_display_renderer_destroy_shader(renderer(), pipeline_);
pipeline_ = nullptr;
}
super::on_destroy();
}
+16 -10
View File
@@ -22,13 +22,19 @@
#ifndef OAK_SCOPEBASE_H
#define OAK_SCOPEBASE_H
#include "codec/frame.h"
#include "render/colorprocessor.h"
#include <QString>
#include "widget/manageddisplay/manageddisplay.h"
namespace olive
{
/** @brief Fragment + vertex shader source pair returned by scope subclasses. */
struct ScopeShaderCode {
QString frag;
QString vert;
};
class ScopeBase : public ManagedDisplayWidget {
public:
ScopeBase(QWidget *parent = nullptr);
@@ -36,7 +42,7 @@ public:
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(ScopeBase)
public slots:
void set_buffer(TexturePtr frame);
void set_buffer(void *frame);
protected slots:
virtual void on_init() override;
@@ -48,14 +54,14 @@ protected slots:
protected:
virtual void showEvent(QShowEvent *e) override;
virtual ShaderCode generate_shader_code() = 0;
virtual ScopeShaderCode generate_shader_code() = 0;
/**
* @brief GPU-accelerated draw function used on OpenGL backends.
*
* Override this if your sub-class scope needs extra drawing.
*/
virtual void draw_scope(TexturePtr managed_tex, QVariant pipeline);
virtual void draw_scope(void *managed_tex, void *pipeline);
/**
* @brief Software draw function used on backend-neutral paths (e.g. Vulkan).
@@ -68,20 +74,20 @@ protected:
private:
void update_software_image();
QVariant pipeline_;
void *pipeline_;
TexturePtr texture_;
void *texture_;
TexturePtr managed_tex_;
void *managed_tex_;
bool managed_tex_up_to_date_;
TexturePtr software_tex_;
void *software_tex_;
QByteArray software_buffer_;
QImage software_image_;
bool software_image_up_to_date_;
TexturePtr local_texture_;
void *local_texture_;
};
}
+28 -31
View File
@@ -23,11 +23,11 @@
#include <QPainter>
#include <QtMath>
#include <QVector2D>
#include <QVector3D>
#include "common/qtutils.h"
#include "node/node.h"
#include "common/filefunctions.h"
#include "oakengine/color.h"
#include "oakengine/display.h"
namespace olive
{
@@ -39,14 +39,14 @@ VectorscopeScope::VectorscopeScope(QWidget *parent)
{
}
ShaderCode VectorscopeScope::generate_shader_code()
ScopeShaderCode VectorscopeScope::generate_shader_code()
{
return ShaderCode(
return ScopeShaderCode{
FileFunctions::read_file_as_string(":/shaders/rgbvectorscope.frag"),
FileFunctions::read_file_as_string(":/shaders/rgbvectorscope.vert"));
FileFunctions::read_file_as_string(":/shaders/rgbvectorscope.vert")};
}
void VectorscopeScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
void VectorscopeScope::draw_scope(void *managed_tex, void *pipeline)
{
float vectorscope_scale = 0.80f;
float vectorscope_gain = 1.45f;
@@ -54,34 +54,31 @@ void VectorscopeScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
float vectorscope_intensity = 0.035f;
float vectorscope_sample_grid = 28.0f;
ShaderJob job;
job.insert(QStringLiteral("viewport"),
NodeValue(NodeValue::k_vec2, QVector2D(width(), height())));
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
job.insert(
QStringLiteral("luma_coeffs"),
NodeValue(NodeValue::k_vec3,
QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2])));
job.insert(QStringLiteral("vectorscope_scale"),
NodeValue(NodeValue::k_float, vectorscope_scale));
job.insert(QStringLiteral("vectorscope_gain"),
NodeValue(NodeValue::k_float, vectorscope_gain));
job.insert(QStringLiteral("vectorscope_point_radius"),
NodeValue(NodeValue::k_float, vectorscope_point_radius));
job.insert(QStringLiteral("vectorscope_intensity"),
NodeValue(NodeValue::k_float, vectorscope_intensity));
job.insert(QStringLiteral("vectorscope_sample_grid"),
NodeValue(NodeValue::k_float, vectorscope_sample_grid));
oak_shader_uniform uniforms[7];
uniforms[0] = {"viewport", 1,
{static_cast<float>(width()), static_cast<float>(height()),
0.0f, 0.0f}};
uniforms[1] = {"luma_coeffs", 3,
{static_cast<float>(luma_coeffs[0]),
static_cast<float>(luma_coeffs[1]),
static_cast<float>(luma_coeffs[2]), 0.0f}};
uniforms[2] = {"vectorscope_scale", 0,
{vectorscope_scale, 0.0f, 0.0f, 0.0f}};
uniforms[3] = {"vectorscope_gain", 0,
{vectorscope_gain, 0.0f, 0.0f, 0.0f}};
uniforms[4] = {"vectorscope_point_radius", 0,
{vectorscope_point_radius, 0.0f, 0.0f, 0.0f}};
uniforms[5] = {"vectorscope_intensity", 0,
{vectorscope_intensity, 0.0f, 0.0f, 0.0f}};
uniforms[6] = {"vectorscope_sample_grid", 0,
{vectorscope_sample_grid, 0.0f, 0.0f, 0.0f}};
job.insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(managed_tex)));
renderer()->blit(pipeline, job, get_viewport_params());
oak_video_params vp = get_viewport_params();
oakengine_display_renderer_blit_shader_uniforms(
renderer(), pipeline, managed_tex, uniforms, 7, nullptr, &vp);
QPainter p(paint_device());
QFont font = p.font();
+2 -2
View File
@@ -35,9 +35,9 @@ public:
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(VectorscopeScope)
protected:
virtual ShaderCode generate_shader_code() override;
virtual ScopeShaderCode generate_shader_code() override;
virtual void draw_scope(TexturePtr managed_tex, QVariant pipeline) override;
virtual void draw_scope(void *managed_tex, void *pipeline) override;
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
};
+22 -33
View File
@@ -26,13 +26,12 @@
#include <QMenu>
#include <QPainter>
#include <QtMath>
#include <QDebug>
#include <QVector2D>
#include <QVector3D>
#include "common/qtutils.h"
#include "common/configwrapper.h"
#include "node/node.h"
#include "common/filefunctions.h"
#include "oakengine/color.h"
#include "oakengine/display.h"
namespace olive
{
@@ -62,46 +61,36 @@ void WaveformScope::contextMenuEvent(QContextMenuEvent *event)
menu.exec(event->globalPos());
}
ShaderCode WaveformScope::generate_shader_code()
ScopeShaderCode WaveformScope::generate_shader_code()
{
return ShaderCode(
return ScopeShaderCode{
FileFunctions::read_file_as_string(":/shaders/rgbwaveform.frag"),
FileFunctions::read_file_as_string(":/shaders/rgbwaveform.vert"));
FileFunctions::read_file_as_string(":/shaders/rgbwaveform.vert")};
}
void WaveformScope::draw_scope(TexturePtr managed_tex, QVariant pipeline)
void WaveformScope::draw_scope(void *managed_tex, void *pipeline)
{
float waveform_scale = 0.80f;
// Draw waveform through shader
ShaderJob job;
// Set viewport size
job.insert(QStringLiteral("viewport"),
NodeValue(NodeValue::k_vec2, QVector2D(width(), height())));
// Set luma coefficients
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
job.insert(
QStringLiteral("luma_coeffs"),
NodeValue(NodeValue::k_vec3,
QVector3D(luma_coeffs[0], luma_coeffs[1], luma_coeffs[2])));
// Scale of the waveform relative to the viewport surface.
job.insert(QStringLiteral("waveform_scale"),
NodeValue(NodeValue::k_float, waveform_scale));
// Set up uniforms
oak_shader_uniform uniforms[4];
uniforms[0] = {"viewport", 1,
{static_cast<float>(width()), static_cast<float>(height()),
0.0f, 0.0f}};
uniforms[1] = {"luma_coeffs", 3,
{static_cast<float>(luma_coeffs[0]),
static_cast<float>(luma_coeffs[1]),
static_cast<float>(luma_coeffs[2]), 0.0f}};
uniforms[2] = {"waveform_scale", 0, {waveform_scale, 0.0f, 0.0f, 0.0f}};
uniforms[3] = {"parade_mode", 0,
{parade_mode_ ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f}};
// Overlay vs. RGB parade display
job.insert(QStringLiteral("parade_mode"),
NodeValue(NodeValue::k_float, parade_mode_ ? 1.0f : 0.0f));
// Insert source texture
job.insert(QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(managed_tex)));
renderer()->blit(pipeline, job, get_viewport_params());
oak_video_params vp = get_viewport_params();
oakengine_display_renderer_blit_shader_uniforms(
renderer(), pipeline, managed_tex, uniforms, 4, nullptr, &vp);
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
+2 -2
View File
@@ -42,9 +42,9 @@ public:
void set_parade_mode(bool enabled);
protected:
virtual ShaderCode generate_shader_code() override;
virtual ScopeShaderCode generate_shader_code() override;
virtual void draw_scope(TexturePtr managed_tex, QVariant pipeline) override;
virtual void draw_scope(void *managed_tex, void *pipeline) override;
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
+71
View File
@@ -0,0 +1,71 @@
/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
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 OAK_DISPLAYBUFFER_H
#define OAK_DISPLAYBUFFER_H
#include <memory>
#include <QMetaType>
#include <QVariant>
#include "oakengine/display.h"
namespace olive
{
/**
* @brief Refcounted wrapper around an opaque display handle (texture or frame).
*
* Stored inside QVariant for playback queue / set_image() path. The deleter
* calls the matching facade free function when the last shared_ptr copy dies.
*/
struct OakSharedBuffer {
void *handle = nullptr;
enum Type { k_texture, k_frame } type = k_frame;
};
using OakSharedBufferPtr = std::shared_ptr<OakSharedBuffer>;
inline OakSharedBufferPtr oak_make_shared_texture(void *handle)
{
return std::shared_ptr<OakSharedBuffer>(
new OakSharedBuffer{handle, OakSharedBuffer::k_texture},
[](OakSharedBuffer *b) {
oakengine_display_texture_free(b->handle);
delete b;
});
}
inline OakSharedBufferPtr oak_make_shared_frame(void *handle)
{
return std::shared_ptr<OakSharedBuffer>(
new OakSharedBuffer{handle, OakSharedBuffer::k_frame},
[](OakSharedBuffer *b) {
oakengine_codec_frame_free(b->handle);
delete b;
});
}
} // namespace olive
Q_DECLARE_METATYPE(olive::OakSharedBufferPtr)
#endif // OAK_DISPLAYBUFFER_H
+30 -30
View File
@@ -44,6 +44,7 @@
#include "node/block/gap/gap.h"
#include "oakengine/encoding.h"
#include "oakengine/display.h"
#include "widget/viewer/displaybuffer.h"
#include "oakengine/viewer.h"
#include "oakengine/videoparams.h"
#include "node/generator/shape/shapenodebase.h"
@@ -570,7 +571,9 @@ void ViewerWidget::set_full_screen(QScreen *screen)
}
vw->display_widget()->set_image(
QVariant::fromValue(display_widget()->get_current_texture()));
QVariant::fromValue(oak_make_shared_texture(
oakengine_display_texture_retain(
display_widget()->get_current_texture()))));
playback_devices_.append(vw->display_widget());
@@ -1472,41 +1475,39 @@ void ViewerWidget::set_display_image(OakEnginePreviewRequest *req)
if (dynamic_cast<MulticamDisplay *>(dw)) {
// Multicam: use the default frame for now
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
FramePtr f;
oakengine_codec_frame_create(&f);
void *f = oakengine_codec_frame_create();
{
oak_video_params pod = {};
pod.width = frame.width;
pod.height = frame.height;
pod.format = static_cast<PixelFormat::Format>(frame.format);
const VideoParams vp = video_params_from_pod(pod);
oakengine_codec_frame_set_video_params(f.get(), &vp);
pod.format = frame.format;
oakengine_codec_frame_set_video_params(f, &pod);
}
oakengine_codec_frame_allocate(f.get());
if (frame.data && frame.linesize > 0 && f->linesize_bytes() > 0) {
memcpy(f->data(), frame.data,
qMin(f->linesize_bytes(), frame.linesize) * frame.height);
oakengine_codec_frame_allocate(f);
int fls = oakengine_codec_frame_linesize_bytes(f);
if (frame.data && frame.linesize > 0 && fls > 0) {
memcpy(oakengine_codec_frame_data(f), frame.data,
qMin(fls, frame.linesize) * frame.height);
}
push = QVariant::fromValue(f);
push = QVariant::fromValue(oak_make_shared_frame(f));
}
} else {
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
FramePtr f;
oakengine_codec_frame_create(&f);
void *f = oakengine_codec_frame_create();
{
oak_video_params pod = {};
pod.width = frame.width;
pod.height = frame.height;
pod.format = static_cast<PixelFormat::Format>(frame.format);
const VideoParams vp = video_params_from_pod(pod);
oakengine_codec_frame_set_video_params(f.get(), &vp);
pod.format = frame.format;
oakengine_codec_frame_set_video_params(f, &pod);
}
oakengine_codec_frame_allocate(f.get());
if (frame.data && frame.linesize > 0 && f->linesize_bytes() > 0) {
memcpy(f->data(), frame.data,
qMin(f->linesize_bytes(), frame.linesize) * frame.height);
oakengine_codec_frame_allocate(f);
int fls = oakengine_codec_frame_linesize_bytes(f);
if (frame.data && frame.linesize > 0 && fls > 0) {
memcpy(oakengine_codec_frame_data(f), frame.data,
qMin(fls, frame.linesize) * frame.height);
}
push = QVariant::fromValue(f);
push = QVariant::fromValue(oak_make_shared_frame(f));
}
}
}
@@ -1717,20 +1718,19 @@ void ViewerWidget::renderer_generated_frame_for_queue()
// Ignore this signal if we've paused now
if (is_playing() || prequeuing_video_) {
if (!drop_frame && has_frame) {
FramePtr f;
oakengine_codec_frame_create(&f);
void *f = oakengine_codec_frame_create();
{
oak_video_params pod = {};
pod.width = pf.width;
pod.height = pf.height;
pod.format = static_cast<PixelFormat::Format>(pf.format);
const VideoParams vp = video_params_from_pod(pod);
oakengine_codec_frame_set_video_params(f.get(), &vp);
pod.format = pf.format;
oakengine_codec_frame_set_video_params(f, &pod);
}
oakengine_codec_frame_allocate(f.get());
if (pf.data && pf.linesize > 0 && f->linesize_bytes() > 0) {
memcpy(f->data(), pf.data,
qMin(f->linesize_bytes(), pf.linesize) * pf.height);
oakengine_codec_frame_allocate(f);
int fls = oakengine_codec_frame_linesize_bytes(f);
if (pf.data && pf.linesize > 0 && fls > 0) {
memcpy(oakengine_codec_frame_data(f), pf.data,
qMin(fls, pf.linesize) * pf.height);
}
QVariant frame = QVariant::fromValue(f);
+1 -1
View File
@@ -184,7 +184,7 @@ signals:
/**
* @brief Signal emitted when a new frame is loaded
*/
void texture_changed(TexturePtr t);
void texture_changed(void *t);
/**
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
+207 -170
View File
@@ -35,6 +35,8 @@
#include <QScreen>
#include <QTextEdit>
#include <cstring>
#include "audio/audiomanager.h"
#include "common/define.h"
#include "common/html.h"
@@ -47,12 +49,10 @@
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/subtitle/subtitle.h"
#include "codec/frame.h"
#include "node/gizmo/path.h"
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
#include "render/job/colortransformjob.h"
#include "window/mainwindow/mainwindow.h"
namespace olive
@@ -62,7 +62,14 @@ namespace olive
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
: super(parent)
, texture_(nullptr)
, deinterlace_texture_(nullptr)
, backend_neutral_texture_(nullptr)
, backend_neutral_cpu_display_frame_(nullptr)
, backend_neutral_cpu_source_frame_(nullptr)
, backend_neutral_cpu_source_texture_(nullptr)
, deinterlace_shader_(nullptr)
, blank_shader_(nullptr)
, signal_cursor_color_(false)
, gizmos_(nullptr)
, current_gizmo_(nullptr)
@@ -172,11 +179,14 @@ void ViewerDisplayWidget::set_deinterlacing(bool e)
deinterlace_ = e;
if (!deinterlace_) {
if (!deinterlace_shader_.isNull()) {
renderer()->destroy_native_shader(deinterlace_shader_);
deinterlace_shader_.clear();
if (deinterlace_shader_) {
oakengine_display_renderer_destroy_shader(renderer(), deinterlace_shader_);
deinterlace_shader_ = nullptr;
}
if (deinterlace_texture_) {
oakengine_display_texture_free(deinterlace_texture_);
deinterlace_texture_ = nullptr;
}
deinterlace_texture_ = nullptr;
}
update();
@@ -405,12 +415,12 @@ void ViewerDisplayWidget::on_paint()
// Clear background to empty
QColor bg_color = show_widget_background_ ? palette().window().color() :
Qt::black;
renderer()->clear_destination(nullptr, bg_color.redF(),
bg_color.greenF(), bg_color.blueF());
oakengine_display_renderer_clear(renderer(), bg_color.redF(),
bg_color.greenF(), bg_color.blueF());
}
VideoParams device_params = empty_video_params();
ColorTransformJob ctj;
oak_video_params device_params = {};
oak_color_transform_job ctj = {};
bool have_ctj = false;
// We only draw if we have a pipeline
@@ -424,52 +434,65 @@ void ViewerDisplayWidget::on_paint()
}
} else if (color_service()) {
bool drew_backend_neutral_frame = false;
if (FramePtr frame = load_frame_.value<FramePtr>()) {
// Extract handle from QVariant (stored as OakSharedBufferPtr)
OakSharedBufferPtr buf = load_frame_.value<OakSharedBufferPtr>();
void *load_handle = buf ? buf->handle : nullptr;
int load_type = buf ? static_cast<int>(buf->type) : -1;
if (load_handle && load_type == OakSharedBuffer::k_frame) {
// CPU frame path: upload to GPU texture
oak_video_params frame_vp = {};
oakengine_codec_frame_get_params(load_handle, &frame_vp);
if (!drew_backend_neutral_frame &&
(!texture_ ||
texture_->renderer() !=
renderer() // Some implementations don't like it if we upload to a texture created in another (albeit shared) context
|| texture_->width() != frame->width() ||
texture_->height() != frame->height() ||
texture_->format() != frame->format() ||
texture_->channel_count() != frame->channel_count())) {
oakengine_display_renderer_create_texture(
renderer(), &frame->video_params(), frame->data(),
frame->linesize_pixels(), &texture_);
oakengine_display_texture_renderer(texture_) !=
renderer() ||
oakengine_display_texture_width(texture_) != frame_vp.width ||
oakengine_display_texture_height(texture_) != frame_vp.height ||
oakengine_display_texture_format(texture_) != frame_vp.format ||
oakengine_display_texture_channel_count(texture_) != 4)) {
texture_ = oakengine_display_texture_create(
renderer(), &frame_vp,
oakengine_codec_frame_data(load_handle),
oakengine_codec_frame_linesize(load_handle));
} else if (!drew_backend_neutral_frame) {
oakengine_display_texture_upload(texture_.get(), frame->data(),
frame->linesize_pixels());
oakengine_display_texture_upload(
texture_, oakengine_codec_frame_data(load_handle),
oakengine_codec_frame_linesize(load_handle));
}
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
// This is a GPU texture, switch to it directly when possible.
if (!drew_backend_neutral_frame && texture &&
texture->renderer() && texture->renderer() != renderer()) {
if (texture->renderer()->is_open_gl() &&
renderer()->is_open_gl()) {
// Shared OpenGL contexts can display the producer texture
// directly. Avoid readback here because the producer
// renderer may belong to a render thread whose context
// cannot be made current from the GUI paint callback.
texture_ = texture;
} else if (load_handle && load_type == OakSharedBuffer::k_texture) {
// GPU texture path
void *src_ren = oakengine_display_texture_renderer(load_handle);
if (!drew_backend_neutral_frame && load_handle &&
src_ren && src_ren != renderer()) {
if (oakengine_display_renderer_is_open_gl(src_ren) &&
oakengine_display_renderer_is_open_gl(renderer())) {
texture_ = load_handle;
} else {
// Cross-backend texture: download and re-upload
FramePtr frame;
oakengine_codec_frame_create(&frame);
oakengine_codec_frame_set_video_params(
frame.get(), &texture->params());
if (oakengine_codec_frame_allocate(frame.get())) {
texture->renderer()->download_from_texture(
texture->id(), texture->params(), frame->data(),
frame->linesize_pixels());
oakengine_display_renderer_create_texture(
renderer(), &frame->video_params(), frame->data(),
frame->linesize_pixels(), &texture_);
// Cross-backend: download and re-upload
void *tmp_frame = oakengine_codec_frame_create();
oak_video_params tex_params = {};
oakengine_display_texture_get_params(load_handle, &tex_params);
oakengine_codec_frame_set_video_params(tmp_frame, &tex_params);
if (oakengine_codec_frame_allocate(tmp_frame)) {
oakengine_display_renderer_download_from_texture(
src_ren,
oakengine_display_texture_id(load_handle),
&tex_params,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame));
texture_ = oakengine_display_texture_create(
renderer(), &tex_params,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame));
} else {
texture_ = texture;
texture_ = load_handle;
}
oakengine_codec_frame_free(tmp_frame);
}
} else if (!drew_backend_neutral_frame) {
texture_ = texture;
texture_ = load_handle;
}
} else {
texture_ = load_custom_texture_from_frame(load_frame_);
@@ -484,59 +507,58 @@ void ViewerDisplayWidget::on_paint()
push_mode_ = k_push_unnecessary;
if (!drew_backend_neutral_frame) {
TexturePtr texture_to_draw = texture_;
void *texture_to_draw = texture_;
if (!texture_to_draw || texture_to_draw->is_dummy()) {
if (!texture_to_draw ||
oakengine_display_texture_is_dummy(texture_to_draw)) {
if (!backend_neutral) {
draw_blank(device_params);
}
} else {
if (deinterlace_) {
if (deinterlace_shader_.isNull()) {
if (!deinterlace_shader_) {
QString src = FileFunctions::read_file_as_string(
QStringLiteral(":/shaders/deinterlace.frag"));
deinterlace_shader_ =
renderer()->create_native_shader(
ShaderCode(FileFunctions::read_file_as_string(
QStringLiteral(
":/shaders/deinterlace.frag"))));
oakengine_display_renderer_create_shader(
renderer(), src.toUtf8().constData(), nullptr);
}
if (!deinterlace_texture_ ||
deinterlace_texture_->params() !=
texture_to_draw->params()) {
// (Re)create texture
oakengine_display_renderer_create_texture(
renderer(), &texture_to_draw->params(), nullptr,
0, &deinterlace_texture_);
!oakengine_display_texture_params_equal(
deinterlace_texture_, texture_to_draw)) {
if (deinterlace_texture_) {
oakengine_display_texture_free(deinterlace_texture_);
}
oak_video_params tex_params = {};
oakengine_display_texture_get_params(texture_to_draw,
&tex_params);
deinterlace_texture_ =
oakengine_display_texture_create(
renderer(), &tex_params, nullptr, 0);
}
ShaderJob job;
job.insert(
QStringLiteral("resolution_in"),
NodeValue(NodeValue::k_vec2,
QVector2D(texture_to_draw->width(),
texture_to_draw->height())));
job.insert(
QStringLiteral("ove_maintex"),
NodeValue(NodeValue::k_texture,
QVariant::fromValue(texture_to_draw)));
renderer()->blit_to_texture(deinterlace_shader_, job,
deinterlace_texture_.get());
oakengine_display_renderer_blit_shader_vec2_to_texture(
renderer(), deinterlace_shader_, texture_to_draw,
"resolution_in",
static_cast<float>(oakengine_display_texture_width(texture_to_draw)),
static_cast<float>(oakengine_display_texture_height(texture_to_draw)),
deinterlace_texture_);
texture_to_draw = deinterlace_texture_;
}
oakengine_color_transform_job_set_processor(
&ctj, color_service().get());
ctj.set_input_texture(texture_to_draw);
ctj.set_input_alpha_association(
OAK_CONFIG("ReassocLinToNonLin").toBool() ?
k_alpha_associated :
k_alpha_none);
ctj.set_clear_destination_enabled(false);
ctj.set_transform_matrix(combined_matrix_flipped_);
ctj.set_crop_matrix(crop_matrix_);
ctj.set_force_opaque(true);
// Build color transform job POD
ctj.processor = color_service().get();
ctj.input_texture = texture_to_draw;
ctj.input_alpha_association =
OAK_CONFIG("ReassocLinToNonLin").toBool() ? 1 : 0;
ctj.clear_destination = 0;
ctj.force_opaque = 1;
memcpy(ctj.matrix, combined_matrix_flipped_.constData(),
16 * sizeof(float));
memcpy(ctj.crop_matrix, crop_matrix_.constData(),
16 * sizeof(float));
have_ctj = true;
}
@@ -689,25 +711,31 @@ void ViewerDisplayWidget::on_paint()
void ViewerDisplayWidget::on_destroy()
{
if (!deinterlace_shader_.isNull()) {
renderer()->destroy_native_shader(deinterlace_shader_);
deinterlace_shader_.clear();
if (deinterlace_shader_) {
oakengine_display_renderer_destroy_shader(renderer(), deinterlace_shader_);
deinterlace_shader_ = nullptr;
}
if (!blank_shader_.isNull()) {
renderer()->destroy_native_shader(blank_shader_);
blank_shader_.clear();
if (blank_shader_) {
oakengine_display_renderer_destroy_shader(renderer(), blank_shader_);
blank_shader_ = nullptr;
}
super::on_destroy();
texture_ = nullptr;
deinterlace_texture_ = nullptr;
backend_neutral_texture_ = nullptr;
if (deinterlace_texture_) {
oakengine_display_texture_free(deinterlace_texture_);
deinterlace_texture_ = nullptr;
}
if (backend_neutral_texture_) {
oakengine_display_texture_free(backend_neutral_texture_);
backend_neutral_texture_ = nullptr;
}
backend_neutral_buffer_.clear();
backend_neutral_cpu_image_ = QImage();
backend_neutral_cpu_display_frame_.reset();
backend_neutral_cpu_source_frame_.reset();
backend_neutral_cpu_source_texture_.reset();
backend_neutral_cpu_display_frame_ = nullptr;
backend_neutral_cpu_source_frame_ = nullptr;
backend_neutral_cpu_source_texture_ = nullptr;
backend_neutral_cpu_color_id_.clear();
if (load_frame_.isNull()) {
push_mode_ = k_push_null;
@@ -765,7 +793,7 @@ void ViewerDisplayWidget::update_matrix()
// up. Vulkan's framebuffer and texture coordinate origins are both top-left,
// so the same flip would invert the image. Default to the OpenGL flip when
// no renderer is available yet.
if (!renderer() || !renderer()->is_vulkan()) {
if (!renderer() || !oakengine_display_renderer_is_vulkan(renderer())) {
QMatrix4x4 flip;
flip.scale(1.0f, -1.0f, 1.0f);
combined_matrix_flipped_ = flip * combined_matrix_flipped_;
@@ -1200,12 +1228,18 @@ void ViewerDisplayWidget::emit_color_at_cursor(QMouseEvent *e)
if (texture_) {
QPointF pixel_pos =
generate_display_transform().inverted().map(e->pos());
pixel_pos /= texture_->params().divider();
oak_video_params tp = {};
oakengine_display_texture_get_params(texture_, &tp);
pixel_pos /= (tp.divider > 0 ? tp.divider : 1);
make_current();
reference =
renderer()->get_pixel_from_texture(texture_.get(), pixel_pos);
double rgba[4] = {};
oakengine_display_renderer_get_pixel(
renderer(), texture_,
static_cast<int>(pixel_pos.x()),
static_cast<int>(pixel_pos.y()), rgba);
reference = Color(rgba[0], rgba[1], rgba[2], rgba[3]);
if (color_service()) {
display = oak_convert_color(color_service(), reference);
} else {
@@ -1455,33 +1489,31 @@ void ViewerDisplayWidget::generate_gizmo_transforms()
gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted();
}
void ViewerDisplayWidget::draw_blank(const VideoParams &device_params)
void ViewerDisplayWidget::draw_blank(const oak_video_params &device_params)
{
if (blank_shader_.isNull()) {
blank_shader_ = renderer()->create_native_shader(ShaderCode());
if (!blank_shader_) {
blank_shader_ = oakengine_display_renderer_create_blank_shader(renderer());
}
ShaderJob job;
job.insert(QStringLiteral("ove_mvpmat"),
NodeValue(NodeValue::k_matrix, combined_matrix_flipped_));
job.insert(QStringLiteral("ove_cropmatrix"),
NodeValue(NodeValue::k_matrix, crop_matrix_));
renderer()->blit(blank_shader_, job, device_params, false);
oakengine_display_renderer_blit_blank(
renderer(), blank_shader_,
combined_matrix_flipped_.constData(),
crop_matrix_.constData(),
&device_params);
}
bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
bool ViewerDisplayWidget::draw_backend_neutral_frame(void *frame,
QPainter *painter)
{
if (!frame || !frame->is_allocated() || !painter || !painter->isActive() ||
!color_service()) {
if (!frame || !oakengine_codec_frame_is_allocated(frame) || !painter ||
!painter->isActive() || !color_service()) {
return false;
}
const QString color_id = oak_query_string([this](char *buf, int size) {
return oakengine_color_processor_id(color_service().get(), buf, size);
});
if (backend_neutral_cpu_source_frame_.get() == frame.get() &&
if (backend_neutral_cpu_source_frame_ == frame &&
backend_neutral_cpu_color_id_ == color_id &&
!backend_neutral_cpu_image_.isNull()) {
painter->save();
@@ -1496,47 +1528,43 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
// not safe to apply on this GUI path and a crash here kills preview. Worker
// frames tagged with display:<processor-id> have already been color managed;
// untagged frames are drawn directly as a safe fallback.
FramePtr display_frame = frame;
const int frame_fmt = oakengine_codec_frame_format(frame);
const int frame_ch = oakengine_codec_frame_channel_count(frame);
const int frame_w = oakengine_codec_frame_width(frame);
const int frame_h = oakengine_codec_frame_height(frame);
const int frame_ls = oakengine_codec_frame_linesize_bytes(frame);
const char *frame_data =
reinterpret_cast<const char *>(oakengine_codec_frame_const_data(frame));
QImage source_image;
if (display_frame->format() == PixelFormat::u8 &&
display_frame->channel_count() == 4) {
backend_neutral_cpu_display_frame_ = display_frame;
backend_neutral_cpu_image_ =
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
display_frame->width(), display_frame->height(),
display_frame->linesize_bytes(), QImage::Format_RGBA8888);
if (frame_fmt == PixelFormat::u8 && frame_ch == 4) {
backend_neutral_cpu_display_frame_ = frame;
backend_neutral_cpu_image_ = QImage(
reinterpret_cast<const uchar *>(frame_data), frame_w, frame_h,
frame_ls, QImage::Format_RGBA8888);
source_image = backend_neutral_cpu_image_;
} else if (display_frame->format() == PixelFormat::u8 &&
display_frame->channel_count() ==
3) {
backend_neutral_cpu_display_frame_ = display_frame;
backend_neutral_cpu_image_ =
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
display_frame->width(), display_frame->height(),
display_frame->linesize_bytes(), QImage::Format_RGB888);
} else if (frame_fmt == PixelFormat::u8 && frame_ch == 3) {
backend_neutral_cpu_display_frame_ = frame;
backend_neutral_cpu_image_ = QImage(
reinterpret_cast<const uchar *>(frame_data), frame_w, frame_h,
frame_ls, QImage::Format_RGB888);
source_image = backend_neutral_cpu_image_;
} else {
backend_neutral_cpu_display_frame_.reset();
const int bytes_per_pixel =
oakengine_video_params_bytes_per_pixel(
static_cast<int>(display_frame->video_params().format()),
display_frame->video_params().channel_count());
if (backend_neutral_cpu_image_.size() !=
QSize(display_frame->width(), display_frame->height()) ||
backend_neutral_cpu_display_frame_ = nullptr;
const int bpp =
oakengine_video_params_bytes_per_pixel(frame_fmt, frame_ch);
if (backend_neutral_cpu_image_.size() != QSize(frame_w, frame_h) ||
backend_neutral_cpu_image_.format() != QImage::Format_RGBA8888) {
backend_neutral_cpu_image_ = QImage(display_frame->width(),
display_frame->height(),
QImage::Format_RGBA8888);
backend_neutral_cpu_image_ =
QImage(frame_w, frame_h, QImage::Format_RGBA8888);
}
for (int y = 0; y < display_frame->height(); ++y) {
for (int y = 0; y < frame_h; ++y) {
uchar *dst = backend_neutral_cpu_image_.scanLine(y);
const char *src = display_frame->const_data() +
y * display_frame->linesize_bytes();
for (int x = 0; x < display_frame->width(); ++x) {
Color c(src + x * bytes_per_pixel, display_frame->format(),
display_frame->channel_count());
const char *src = frame_data + y * frame_ls;
for (int x = 0; x < frame_w; ++x) {
Color c(src + x * bpp,
static_cast<PixelFormat::Format>(frame_fmt), frame_ch);
dst[x * 4 + 0] =
static_cast<uchar>(qBound(0, int(c.red() * 255.0), 255));
dst[x * 4 + 1] =
@@ -1560,10 +1588,11 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
return true;
}
bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture,
bool ViewerDisplayWidget::draw_backend_neutral_texture(void *texture,
QPainter *painter)
{
if (!texture || texture->is_dummy() || !texture->renderer() || !painter ||
if (!texture || oakengine_display_texture_is_dummy(texture) ||
!oakengine_display_texture_renderer(texture) || !painter ||
!painter->isActive() || !color_service()) {
return false;
}
@@ -1571,7 +1600,7 @@ bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture
const QString color_id = oak_query_string([this](char *buf, int size) {
return oakengine_color_processor_id(color_service().get(), buf, size);
});
if (backend_neutral_cpu_source_texture_.get() == texture.get() &&
if (backend_neutral_cpu_source_texture_ == texture &&
backend_neutral_cpu_color_id_ == color_id &&
!backend_neutral_cpu_image_.isNull()) {
painter->save();
@@ -1582,28 +1611,33 @@ bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture
return true;
}
FramePtr frame;
oakengine_codec_frame_create(&frame);
oakengine_codec_frame_set_video_params(frame.get(), &texture->params());
if (!oakengine_codec_frame_allocate(frame.get())) {
void *tmp_frame = oakengine_codec_frame_create();
oak_video_params tex_params = {};
oakengine_display_texture_get_params(texture, &tex_params);
oakengine_codec_frame_set_video_params(tmp_frame, &tex_params);
if (!oakengine_codec_frame_allocate(tmp_frame)) {
oakengine_codec_frame_free(tmp_frame);
return false;
}
oakengine_display_texture_download(texture.get(), frame->data(),
frame->linesize_pixels());
oakengine_display_texture_download(texture,
oakengine_codec_frame_data(tmp_frame),
oakengine_codec_frame_linesize(tmp_frame));
if (!draw_backend_neutral_frame(frame, painter)) {
if (!draw_backend_neutral_frame(tmp_frame, painter)) {
oakengine_codec_frame_free(tmp_frame);
return false;
}
backend_neutral_cpu_source_texture_ = texture;
backend_neutral_cpu_color_id_ = color_id;
oakengine_codec_frame_free(tmp_frame);
return true;
}
// Renders a backend-neutral frame by drawing into an offscreen backend texture,
// downloading it to CPU memory, then painting that image with QPainter.
void ViewerDisplayWidget::draw_backend_neutral(const ColorTransformJob &ctj,
void ViewerDisplayWidget::draw_backend_neutral(const oak_color_transform_job &ctj,
QPainter *painter)
{
if (!painter || !painter->isActive()) {
@@ -1613,38 +1647,41 @@ void ViewerDisplayWidget::draw_backend_neutral(const ColorTransformJob &ctj,
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
oak_video_params pod = {};
pod.width = texture_width;
pod.height = texture_height;
pod.format = PixelFormat::u8;
const VideoParams offscreen_params(video_params_from_pod(pod));
oak_video_params offscreen_pod = {};
offscreen_pod.width = texture_width;
offscreen_pod.height = texture_height;
offscreen_pod.format = PixelFormat::u8;
if (!backend_neutral_texture_ ||
backend_neutral_texture_->params() != offscreen_params) {
oakengine_display_texture_width(backend_neutral_texture_) != texture_width ||
oakengine_display_texture_height(backend_neutral_texture_) != texture_height) {
// The offscreen texture is sized in device pixels so high-DPI widgets
// draw one downloaded pixel per device pixel after setDevicePixelRatio().
oakengine_display_renderer_create_texture(renderer(), &offscreen_params,
nullptr, 0,
&backend_neutral_texture_);
if (backend_neutral_texture_) {
oakengine_display_texture_free(backend_neutral_texture_);
}
backend_neutral_texture_ = oakengine_display_texture_create(
renderer(), &offscreen_pod, nullptr, 0);
backend_neutral_buffer_.resize(
texture_width * texture_height *
oakengine_video_params_bytes_per_pixel(0, // PixelFormat::u8
4));
}
if (!backend_neutral_texture_ || backend_neutral_texture_->is_dummy()) {
if (!backend_neutral_texture_ ||
oakengine_display_texture_is_dummy(backend_neutral_texture_)) {
return;
}
ColorTransformJob local_ctj = ctj;
local_ctj.set_clear_destination_enabled(true);
oak_color_transform_job local_ctj = ctj;
local_ctj.clear_destination = 1;
// Reuse the normal color-management shader path, but render into a texture
// instead of an OpenGL widget framebuffer.
oakengine_display_renderer_blit_color_managed(
renderer(), &local_ctj, backend_neutral_texture_.get(), nullptr);
renderer(), &local_ctj, backend_neutral_texture_, nullptr);
oakengine_display_texture_download(backend_neutral_texture_.get(),
oakengine_display_texture_download(backend_neutral_texture_,
backend_neutral_buffer_.data(), 0);
const int bytes_per_pixel = oakengine_video_params_bytes_per_pixel(0, 4); // u8, RGBA
+16 -17
View File
@@ -26,7 +26,6 @@
#include <QMatrix4x4>
#include <QRubberBand>
#include "codec/frame.h"
#include "engineeventbridge.h"
#include "node/color/colormanager/colormanager.h"
#include "node/gizmo/text.h"
@@ -41,6 +40,7 @@
#include "viewertexteditor.h"
#include "widget/manageddisplay/manageddisplay.h"
#include "widget/timetarget/timetarget.h"
#include "widget/viewer/displaybuffer.h"
namespace olive
{
@@ -136,7 +136,7 @@ public:
fps_timer_update_count_++;
}
TexturePtr get_current_texture() const
void *get_current_texture() const
{
return texture_;
}
@@ -242,7 +242,7 @@ signals:
void dropped(QDropEvent *event);
void texture_changed(TexturePtr texture);
void texture_changed(void *texture);
void queue_starved();
@@ -270,7 +270,7 @@ protected:
node_time + gizmo_params_.frame_rate_as_time_base());
}
virtual TexturePtr load_custom_texture_from_frame(const QVariant &v)
virtual void *load_custom_texture_from_frame(const QVariant &v)
{
return nullptr;
}
@@ -333,22 +333,21 @@ private:
void generate_gizmo_transforms();
void draw_blank(const VideoParams &device_params);
void draw_blank(const oak_video_params &device_params);
void draw_backend_neutral(const ColorTransformJob &ctj, QPainter *painter);
bool draw_backend_neutral_frame(const FramePtr &frame, QPainter *painter);
bool draw_backend_neutral_texture(const TexturePtr &texture,
QPainter *painter);
void draw_backend_neutral(const oak_color_transform_job &ctj, QPainter *painter);
bool draw_backend_neutral_frame(void *frame, QPainter *painter);
bool draw_backend_neutral_texture(void *texture, QPainter *painter);
/**
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
*/
TexturePtr texture_;
void *texture_;
/**
* @brief Internal texture to deinterlace to
*/
TexturePtr deinterlace_texture_;
void *deinterlace_texture_;
/**
* @brief Offscreen texture for backend-neutral viewer rendering.
@@ -356,27 +355,27 @@ private:
* 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_;
void *backend_neutral_texture_;
/**
* @brief CPU readback buffer for backend_neutral_texture_.
*/
QByteArray backend_neutral_buffer_;
QImage backend_neutral_cpu_image_;
FramePtr backend_neutral_cpu_display_frame_;
FramePtr backend_neutral_cpu_source_frame_;
TexturePtr backend_neutral_cpu_source_texture_;
void *backend_neutral_cpu_display_frame_;
void *backend_neutral_cpu_source_frame_;
void *backend_neutral_cpu_source_texture_;
QString backend_neutral_cpu_color_id_;
/**
* @brief Deinterlace shader
*/
QVariant deinterlace_shader_;
void *deinterlace_shader_;
/**
* @brief Blank shader
*/
QVariant blank_shader_;
void *blank_shader_;
/**
* @brief Translation only matrix (defaults to identity).
+3 -3
View File
@@ -26,13 +26,13 @@
#include <QMutex>
#include <QMutexLocker>
#include "codec/frame.h"
#include <olive/core/core.h>
namespace olive
{
struct ViewerPlaybackFrame {
Rational timestamp;
core::Rational timestamp;
QVariant frame;
};
@@ -73,7 +73,7 @@ public:
}
}
void purge_before(const Rational &time, int playback_speed)
void purge_before(const core::Rational &time, int playback_speed)
{
QMutexLocker locker(mutex_);
while (!this->empty() &&