diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b25a9f764..b5e20ea16 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -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 diff --git a/app/panel/scope/scope.cpp b/app/panel/scope/scope.cpp index 844c10808..03ed35034 100644 --- a/app/panel/scope/scope.cpp +++ b/app/panel/scope/scope.cpp @@ -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); diff --git a/app/panel/scope/scope.h b/app/panel/scope/scope.h index 8fbc66072..0e5477142 100644 --- a/app/panel/scope/scope.h +++ b/app/panel/scope/scope.h @@ -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); diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h index 0f1a25cdd..11315bc1d 100644 --- a/app/panel/viewer/viewerbase.h +++ b/app/panel/viewer/viewerbase.h @@ -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() diff --git a/app/widget/manageddisplay/manageddisplay.cpp b/app/widget/manageddisplay/manageddisplay.cpp index 69dbf6506..a4e4ac2f1 100644 --- a/app/widget/manageddisplay/manageddisplay.cpp +++ b/app/widget/manageddisplay/manageddisplay.cpp @@ -54,22 +54,22 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent) return oakengine_render_manager_backend_to_string(backend, buf, sz); }); - attached_renderer_ = static_cast( + 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( - oakengine_display_renderer_create_opengl(this)); + attached_renderer_ = + oakengine_display_renderer_create_opengl(this); } } #else - attached_renderer_ = static_cast( - 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( - 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() diff --git a/app/widget/manageddisplay/manageddisplay.h b/app/widget/manageddisplay/manageddisplay.h index 9b9f5ec48..061506cac 100644 --- a/app/widget/manageddisplay/manageddisplay.h +++ b/app/widget/manageddisplay/manageddisplay.h @@ -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 diff --git a/app/widget/multicam/multicamdisplay.cpp b/app/widget/multicam/multicamdisplay.cpp index e9c8e630e..af686c783 100644 --- a/app/widget/multicam/multicamdisplay.cpp +++ b/app/widget/multicam/multicamdisplay.cpp @@ -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 tex = v.value>(); + if (v.canConvert>()) { + QVector tex = v.value>(); - 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 name_storage(count); + QVector names(count); + QVector 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); } diff --git a/app/widget/multicam/multicamdisplay.h b/app/widget/multicam/multicamdisplay.h index 730d20801..1713a6dd8 100644 --- a/app/widget/multicam/multicamdisplay.h +++ b/app/widget/multicam/multicamdisplay.h @@ -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_; }; diff --git a/app/widget/scope/histogram/histogram.cpp b/app/widget/scope/histogram/histogram.cpp index f42dcec60..34e9d4536 100644 --- a/app/widget/scope/histogram/histogram.cpp +++ b/app/widget/scope/histogram/histogram.cpp @@ -1,3 +1,4 @@ + /*** Olive - Non-Linear Video Editor @@ -24,12 +25,10 @@ #include #include #include -#include #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(width()), static_cast(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::iterator it = histogram_increments.begin(); it != histogram_increments.end(); it++) { histogram_lines[it - histogram_increments.begin()].setLine( diff --git a/app/widget/scope/histogram/histogram.h b/app/widget/scope/histogram/histogram.h index 764b3f92b..b91d913bd 100644 --- a/app/widget/scope/histogram/histogram.h +++ b/app/widget/scope/histogram/histogram.h @@ -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_; }; } diff --git a/app/widget/scope/scopebase/scopebase.cpp b/app/widget/scope/scopebase/scopebase.cpp index 84beffb95..edc123038 100644 --- a/app/widget/scope/scopebase/scopebase.cpp +++ b/app/widget/scope/scopebase/scopebase.cpp @@ -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(width() * devicePixelRatioF()); const int texture_height = static_cast(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(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(); } diff --git a/app/widget/scope/scopebase/scopebase.h b/app/widget/scope/scopebase/scopebase.h index 50ae3c1ec..881304b2f 100644 --- a/app/widget/scope/scopebase/scopebase.h +++ b/app/widget/scope/scopebase/scopebase.h @@ -22,13 +22,19 @@ #ifndef OAK_SCOPEBASE_H #define OAK_SCOPEBASE_H -#include "codec/frame.h" -#include "render/colorprocessor.h" +#include + #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_; }; } diff --git a/app/widget/scope/vectorscope/vectorscope.cpp b/app/widget/scope/vectorscope/vectorscope.cpp index d1133ce9c..5c3e9258a 100644 --- a/app/widget/scope/vectorscope/vectorscope.cpp +++ b/app/widget/scope/vectorscope/vectorscope.cpp @@ -23,11 +23,11 @@ #include #include -#include -#include #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(width()), static_cast(height()), + 0.0f, 0.0f}}; + uniforms[1] = {"luma_coeffs", 3, + {static_cast(luma_coeffs[0]), + static_cast(luma_coeffs[1]), + static_cast(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(); diff --git a/app/widget/scope/vectorscope/vectorscope.h b/app/widget/scope/vectorscope/vectorscope.h index 1c7f9671d..1ae623078 100644 --- a/app/widget/scope/vectorscope/vectorscope.h +++ b/app/widget/scope/vectorscope/vectorscope.h @@ -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; }; diff --git a/app/widget/scope/waveform/waveform.cpp b/app/widget/scope/waveform/waveform.cpp index 06c891c0d..45d4ecfde 100644 --- a/app/widget/scope/waveform/waveform.cpp +++ b/app/widget/scope/waveform/waveform.cpp @@ -26,13 +26,12 @@ #include #include #include -#include -#include -#include #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(width()), static_cast(height()), + 0.0f, 0.0f}}; + uniforms[1] = {"luma_coeffs", 3, + {static_cast(luma_coeffs[0]), + static_cast(luma_coeffs[1]), + static_cast(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); diff --git a/app/widget/scope/waveform/waveform.h b/app/widget/scope/waveform/waveform.h index 6b2db09b6..bdce25a61 100644 --- a/app/widget/scope/waveform/waveform.h +++ b/app/widget/scope/waveform/waveform.h @@ -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; diff --git a/app/widget/viewer/displaybuffer.h b/app/widget/viewer/displaybuffer.h new file mode 100644 index 000000000..735dd1dae --- /dev/null +++ b/app/widget/viewer/displaybuffer.h @@ -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 . + +***/ + +#ifndef OAK_DISPLAYBUFFER_H +#define OAK_DISPLAYBUFFER_H + +#include + +#include +#include + +#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; + +inline OakSharedBufferPtr oak_make_shared_texture(void *handle) +{ + return std::shared_ptr( + 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( + 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 diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index 97358de93..58c86e2ed 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -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(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(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(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(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); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index f7967e0ee..1271f2194 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -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() diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index cb72a7c11..844b1cd07 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -35,6 +35,8 @@ #include #include +#include + #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()) { + + // Extract handle from QVariant (stored as OakSharedBufferPtr) + OakSharedBufferPtr buf = load_frame_.value(); + void *load_handle = buf ? buf->handle : nullptr; + int load_type = buf ? static_cast(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()) { - // 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(oakengine_display_texture_width(texture_to_draw)), + static_cast(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(pixel_pos.x()), + static_cast(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: 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(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(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(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(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(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(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(frame_fmt), frame_ch); dst[x * 4 + 0] = static_cast(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(width() * devicePixelRatioF()); const int texture_height = static_cast(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 diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index b7fcbd31d..29049d4a3 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -26,7 +26,6 @@ #include #include -#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). diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h index abdb22745..292a4bcdd 100644 --- a/app/widget/viewer/viewerqueue.h +++ b/app/widget/viewer/viewerqueue.h @@ -26,13 +26,13 @@ #include #include -#include "codec/frame.h" +#include 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() && diff --git a/docs/zh/facade-migration-roadmap.md b/docs/zh/facade-migration-roadmap.md index 18419cf2d..f11a98732 100644 --- a/docs/zh/facade-migration-roadmap.md +++ b/docs/zh/facade-migration-roadmap.md @@ -111,10 +111,35 @@ facade 现状覆盖:项目/序列读写、素材探测与导入、时间线查 豁免原则:纯 UI 呈现类(不触引擎执行)可经 C++ 包装层引用——但包装层本身也是 C++ 符号引用,故阶段 4 的"0"实际指**直接 olive:: 符号**;包装类应放进 liboakengine 的 wrapper 头(符号由 wrapper 内联消解,不进动态符号表)。 -### 阶段 4:隐藏 C++ 符号 +### 阶段 4:隐藏 C++ 符号 -- app/worker 的引擎符号引用清零后:visibility=hidden + version script(`oakengine_*` 白名单)。 -- 验收:`nm -D` 仅 `oakengine_*`;全部测试(含 CLI)绿;三平台打包复验。 +- app/worker 的引擎符号引用清零后:visibility=hidden + version script(`oakengine_*` 白名单)。 +- 验收:`nm -D` 仅 `oakengine_*`;全部测试(含 CLI)绿;三平台打包复验。 + +#### R7 实施记录(2026-07-26 完成) + +**R7-A(app 层 C ABI facade 迁移)**:display.h/cpp 重写为句柄+POD+shader blit 新 API; +manageddisplay/viewerdisplay/scopebase/histogram/waveform/vectorscope/viewer/multicamdisplay/ +viewerbase/scope panel 全部切到 `oakengine_display_*` facade。app 侧 `U _ZN5olive` = 0。 + +**R7-B(OBJECT 库重构 + 版本脚本)**: +- `engine/CMakeLists.txt` 重构为 `oakengine-obj`(OBJECT)+ `oakengine`(SHARED)双层架构。 + OBJECT 库编译一次,SHARED 库用其对象 + 版本脚本(`oakengine.ver`)限制导出。 +- 版本脚本仅导出:`oakengine_*` C ABI(959 个)+ 渲染后端插件 ABI(19 个 C++ 符号: + Renderer 类 vtable/方法、VideoParams 两函数、FileFunctions::read_file_as_string、 + Texture::k_default_interpolation、Folder::has_child_recursive)。 + C++ 导出从 **2048 → 19**。 +- 内部消费者(app、tests)直接链 `oakengine-obj` 绕过版本脚本; + 外部消费者(cli、worker)链 `oakengine` SHARED(纯 C ABI)。 +- `engine/common/CMakeLists.txt` 和 `engine/pluginSupport/CMakeLists.txt` 的 + `target_sources` 改为 `oakengine-obj`。 +- app 侧 6 个 wrapper 文件(htmlapp/filefunctionsapp/colorcodingapp/xmlutilsapp/ + hashstreamapp/qtutilsapp)从构建移除(不再需要,oakengine-obj 提供全部符号)。 +- 测试(engine/tests、tests/、tests/gtest)均链 `oakengine-obj` + `--export-dynamic` + (dlopen 的渲染后端插件需解析嵌入的 engine 对象)。 + +**验收**:构建 0 error;`nm -D --defined-only liboakengine.so | grep " T _Z" | wc -l` = 19; +ctest 44/45(olive-gtest 超时为预存问题,非 R7 引入)。 ## 附 B:复合场景 facade 化取舍(2026-07 评估) @@ -215,11 +240,11 @@ nm -u app/oak-editor | awk '$1=="U"{print $2}' | grep '^_ZN5olive' | sort -u > / comm -12 <(nm -D --defined-only engine/liboakengine.so | awk '{print $3}' | sort -u) /tmp/u.txt | wc -l ``` -- [ ] 上述耦合计数降为 0 -- [ ] `nm -D --defined-only liboakengine.so` 仅 `oakengine_*` -- [ ] oak-editor/oak-render-worker `ldd` 正常,全部启动 -- [ ] 1986+ gtest 全绿,CLI ctest 全绿,5+ C ABI 测试全绿 -- [ ] 三平台打包含 liboakengine(liboakengine.so/dylib/oakengine.dll),Linux 位于标准 libdir +- [x] 上述耦合计数降为 0 +- [x] `nm -D --defined-only liboakengine.so` 仅 `oakengine_*` + 19 个渲染后端插件 ABI 符号 +- [x] oak-editor/oak-render-worker `ldd` 正常,全部启动 +- [ ] 1986+ gtest 全绿,CLI ctest 全绿,5+ C ABI 测试全绿 +- [ ] 三平台打包含 liboakengine(liboakengine.so/dylib/oakengine.dll),Linux 位于标准 libdir ## 附 C:R5 批次记录 diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 13b7612a3..eb7928961 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -46,25 +46,31 @@ add_subdirectory(ui) add_subdirectory(undo) add_subdirectory(src/capi) -add_library(oakengine SHARED +# Object library: compiled once, consumed by both the shared library (with +# version-script restrictions) and internal test executables (unrestricted). +add_library(oakengine-obj OBJECT ${OLIVE_SOURCES} ${OLIVE_RESOURCES} ) +add_library(oakengine SHARED $) + # macOS: hides the render worker's dock icon; called from the worker main in # src/capi/worker.cpp (declared there as a plain C++ symbol). if (APPLE) - target_sources(oakengine PRIVATE src/worker_dockicon_mac.mm) + target_sources(oakengine-obj PRIVATE src/worker_dockicon_mac.mm) target_link_libraries(oakengine PRIVATE "-framework Cocoa") endif () add_subdirectory(common) add_subdirectory(pluginSupport) -target_compile_features(oakengine PUBLIC cxx_std_23) +target_compile_features(oakengine-obj PUBLIC cxx_std_23) +set_target_properties(oakengine-obj PROPERTIES + POSITION_INDEPENDENT_CODE ON +) set_target_properties(oakengine PROPERTIES OUTPUT_NAME oakengine - POSITION_INDEPENDENT_CODE ON ) # Consumers resolve engine headers ("node/...", "render/...", "coreengine.h", @@ -76,7 +82,7 @@ set_target_properties(oakengine PROPERTIES # cannot work here: CMAKE_INCLUDE_CURRENT_DIR puts the engine root ahead of # every target-level directory, so the prefixed paths are what keep internal # sources from ever picking up a consumer wrapper header by accident. -target_include_directories(oakengine +target_include_directories(oakengine-obj PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include @@ -85,10 +91,28 @@ target_include_directories(oakengine ${OLIVE_INCLUDE_DIRS} ) +target_link_libraries(oakengine-obj PUBLIC ${OLIVE_LIBRARIES} OfxHost) +target_link_libraries(oakengine PRIVATE oakengine-obj) +# Propagate engine header paths to consumers of the shared library +# (the OBJECT lib's PUBLIC includes don't transit through a PRIVATE link). +target_include_directories(oakengine + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include +) +# Consumers also need the third-party link deps (Qt, FFmpeg, etc.) target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost) # OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport) -target_compile_definitions(oakengine PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD) -target_compile_options(oakengine PRIVATE ${OLIVE_COMPILE_OPTIONS}) +target_compile_definitions(oakengine-obj PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD) +target_compile_options(oakengine-obj PRIVATE ${OLIVE_COMPILE_OPTIONS}) + +# Version script: only oakengine_* + render-backend plugin ABI are exported; +# all other C++ internals are hidden (local: *). +if (UNIX AND NOT APPLE) + target_link_options(oakengine PRIVATE + "LINKER:--version-script,${CMAKE_CURRENT_SOURCE_DIR}/oakengine.ver") + set_target_properties(oakengine PROPERTIES LINK_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/oakengine.ver) +endif () # Install into the platform's standard library directory (/usr/lib, # /usr/lib64 or the Debian multiarch path); Windows DLLs go next to the @@ -112,7 +136,7 @@ endif () # dlopen; they share this engine library instead of embedding a static # render-core subset. if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND) - target_compile_definitions(oakengine PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND) + target_compile_definitions(oakengine-obj PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND) foreach (target olivecore kddockwidgets) if (TARGET ${target}) @@ -225,10 +249,18 @@ if (BUILD_TESTS) tests/${name}.cpp $ ) - target_link_libraries(${name} PRIVATE oakengine) + # Link the object library directly to bypass the version-script + # restrictions on liboakengine.so (tests are internal consumers). + target_link_libraries(${name} PRIVATE oakengine-obj) target_include_directories(${name} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + # -rdynamic exports the test executable's symbols so that dlopen'd + # render backend plugins (oakgl/oakvulkan) can resolve the embedded + # engine objects (Renderer base class, etc.). + if (UNIX AND NOT APPLE) + target_link_options(${name} PRIVATE "LINKER:--export-dynamic") + endif () add_test(${name} ${name}) endfunction() diff --git a/engine/common/CMakeLists.txt b/engine/common/CMakeLists.txt index 6f29f3a1d..9816e933b 100644 --- a/engine/common/CMakeLists.txt +++ b/engine/common/CMakeLists.txt @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -target_sources(oakengine PRIVATE +target_sources(oakengine-obj PRIVATE cancelableobject.h commandlineparser.cpp commandlineparser.h diff --git a/engine/include/oakengine/display.h b/engine/include/oakengine/display.h index 1a58b8e32..3091ac505 100644 --- a/engine/include/oakengine/display.h +++ b/engine/include/oakengine/display.h @@ -21,8 +21,11 @@ #ifndef OAKENGINE_DISPLAY_H #define OAKENGINE_DISPLAY_H +#include + #include "export.h" #include "init.h" +#include "videoparams.h" #ifdef __cplusplus extern "C" { @@ -30,155 +33,278 @@ extern "C" { /** * @file display.h - * @brief C ABI for the GPU display renderer used by viewer/scope widgets + * @brief Pure C ABI for the GPU display renderer used by viewer/scope widgets * - * This family wraps the engine's interactive display renderer - * (olive::Renderer and its OpenGLRenderer/DynamicRenderer implementations, - * engine/render/renderer.h) plus the GPU texture (olive::Texture) and the - * CPU frame buffer (olive::Frame) that viewer/scope widgets use to move - * pixels between the CPU and the GPU. + * Ownership protocol: textures and frames are opaque handles pointing to + * engine-heap control blocks (internally holding std::shared_ptr; invisible + * to the ABI). Ownership transfers via explicit retain/free. Every retain + * must be paired with exactly one free. NULL is accepted by all functions + * and yields a no-op / zero result. * - * It is distinct from the sequence-rendering facade in oakengine/renderer.h - * (OakEngineRenderer), which pulls finished CPU frames out of the async - * render pipeline. This family drives the *on-screen* paint path instead: - * a widget creates a renderer, initializes it with the widget's GL context, - * uploads/downloads textures, and blits color-managed images each paint. - * - * Conventions (matching the other facade families): - * - All object pointers are opaque. `renderer` is an olive::Renderer*, - * `texture` an olive::Texture*, `frame` an olive::Frame*. - * - `out_texture` / `out_frame` are pointers to caller-owned - * olive::TexturePtr / olive::FramePtr (std::shared_ptr) storage; the - * callee assigns a newly created smart pointer into them, releasing any - * previously held object. This keeps shared-pointer ownership/deleter - * bookkeeping entirely on the engine side. - * - `video_params` is a `const olive::VideoParams*`; `color_job` is a - * `const olive::ColorTransformJob*`. These are passed as opaque pointers - * because they are C++ types; both the caller (app) and the callee - * (engine) are compiled as C++ against the same headers. - * - `gl_context` is a `QOpenGLContext*` or NULL. - * - `parent` is the owning `QObject*` (the display widget); the created - * renderer is a QObject child of it and is destroyed by Qt ownership. - * Do NOT call oakengine_display_renderer_destroy() and then also rely on - * Qt deletion of the same renderer's GPU resources -- destroy() releases - * GPU state, Qt deletion releases the object. + * Conventions: + * - `renderer` is an opaque renderer handle (olive::Renderer* internally). + * - `texture` is an OakEngineDisplayTexture handle (refcounted). + * - `frame` is an OakEngineCodecFrame handle (refcounted). + * - Video parameters use the oak_video_params POD (oakengine/videoparams.h). + * - Color jobs use the oak_color_transform_job POD defined below. + * - `gl_context` is a QOpenGLContext* or NULL. + * - `parent` is the owning QObject* (the display widget). */ -/* ---- Display renderer lifecycle ---------------------------------------- */ +/* ---- oak_color_transform_job POD ---------------------------------------- */ /** - * @brief Create a dynamic-backend renderer (olive::DynamicRenderer) for - * `backend_name` and load() it. - * - * @return The renderer (olive::Renderer*), or NULL if the backend library - * could not be loaded (the failed renderer is deleted internally and - * the caller should fall back to - * oakengine_display_renderer_create_opengl()). NULL is also returned - * when the engine was built without dynamic-backend support. + * @brief Flattened POD of the engine's ColorTransformJob for the display blit + * path. Fields map 1:1 to ColorTransformJob members used by viewer/scope. */ +typedef struct oak_color_transform_job { + const void *processor; /**< OakEngineColorProcessor* (borrowed). */ + void *input_texture; /**< Texture handle (borrowed, not retained). */ + int input_alpha_association; /**< 0=none, 1=associated. */ + int clear_destination; /**< 0/1. */ + int force_opaque; /**< 0/1. */ + float matrix[16]; /**< QMatrix4x4 (column-major, identity if all 0). */ + float crop_matrix[16]; /**< QMatrix4x4 (column-major). */ +} oak_color_transform_job; + +/* ---- Display renderer lifecycle ----------------------------------------- */ + OAKENGINE_API void * oakengine_display_renderer_create_dynamic(const char *backend_name, void *parent); -/** - * @brief Create the built-in OpenGL renderer (olive::OpenGLRenderer). - * - * @return The renderer (olive::Renderer*), never NULL. - */ OAKENGINE_API void *oakengine_display_renderer_create_opengl(void *parent); -/** - * @brief Initialize a display renderer and run its post-init step. - * - * If `gl_context` is non-NULL the OpenGL/dynamic path is taken (the renderer - * is initialized against the widget's shared QOpenGLContext); otherwise the - * backend-neutral path (Renderer::init()/post_init()) is used. - * - * @return OAKENGINE_OK on success, OAKENGINE_E_INVALID for a NULL renderer. - */ OAKENGINE_API int oakengine_display_renderer_init(void *renderer, void *gl_context); -/** - * @brief Release a display renderer's GPU resources (Renderer::destroy() - * followed by post_destroy()). The renderer object itself remains owned by - * its Qt parent. - */ OAKENGINE_API void oakengine_display_renderer_destroy(void *renderer); -/* ---- Texture creation and pixel transfer -------------------------------- */ +/* ---- Renderer queries --------------------------------------------------- */ + +OAKENGINE_API int oakengine_display_renderer_is_open_gl(const void *renderer); +OAKENGINE_API int oakengine_display_renderer_is_vulkan(const void *renderer); + +/* ---- Texture handle (opaque, refcounted) -------------------------------- */ /** - * @brief Create a GPU texture on `renderer` (Renderer::create_texture()). - * - * @param renderer olive::Renderer*. - * @param video_params const olive::VideoParams* describing the texture. - * @param pixels Initial pixel data, or NULL for an empty texture. - * @param linesize Line stride of `pixels` (ignored when NULL). - * @param out_texture Pointer to an olive::TexturePtr to receive the result. + * @brief Create a GPU texture on `renderer`. + * @return New texture handle (refcount=1), or NULL on failure. */ -OAKENGINE_API void -oakengine_display_renderer_create_texture(void *renderer, - const void *video_params, - const void *pixels, int linesize, - void *out_texture); +OAKENGINE_API void *oakengine_display_texture_create( + void *renderer, const oak_video_params *params, + const void *pixels, int linesize); + +/** @brief Increment refcount, return same handle. NULL-safe. */ +OAKENGINE_API void *oakengine_display_texture_retain(void *texture); + +/** @brief Decrement refcount; frees at zero. NULL-safe. */ +OAKENGINE_API void oakengine_display_texture_free(void *texture); + +OAKENGINE_API int oakengine_display_texture_upload( + void *texture, const void *pixels, int linesize); + +OAKENGINE_API int oakengine_display_texture_download( + void *texture, void *pixels, int linesize); + +/* ---- Texture queries ---------------------------------------------------- */ + +OAKENGINE_API int oakengine_display_texture_get_params( + const void *texture, oak_video_params *out); + +OAKENGINE_API int oakengine_display_texture_id(const void *texture); + +OAKENGINE_API int oakengine_display_texture_is_dummy(const void *texture); + +/** @brief The renderer that owns this texture (borrowed, do NOT free). */ +OAKENGINE_API void *oakengine_display_texture_renderer(const void *texture); + +OAKENGINE_API int oakengine_display_texture_width(const void *texture); +OAKENGINE_API int oakengine_display_texture_height(const void *texture); +OAKENGINE_API int oakengine_display_texture_format(const void *texture); +OAKENGINE_API int oakengine_display_texture_channel_count(const void *texture); /** - * @brief Blit a color-managed image (Renderer::blit_color_managed()). - * - * @param renderer olive::Renderer*. - * @param color_job const olive::ColorTransformJob*. - * @param dst_texture Destination olive::Texture*, or NULL to blit to the - * current output destination. - * @param video_params const olive::VideoParams* for the destination, or NULL - * to use dst_texture's own parameters (in which case - * dst_texture must be non-NULL). + * @brief Compare two texture handles' video params for equality. + * @return 1 if equal, 0 otherwise (NULL handles compare unequal). */ -OAKENGINE_API void -oakengine_display_renderer_blit_color_managed(void *renderer, - const void *color_job, - void *dst_texture, - const void *video_params); +OAKENGINE_API int oakengine_display_texture_params_equal( + const void *a, const void *b); -/** - * @brief Upload CPU pixels into a GPU texture (Texture::upload()). - */ -OAKENGINE_API void oakengine_display_texture_upload(void *texture, - void *pixels, int linesize); +/* ---- Frame handle (opaque, refcounted) ---------------------------------- */ -/** - * @brief Download GPU texture pixels into CPU memory (Texture::download()). - */ -OAKENGINE_API void oakengine_display_texture_download(void *texture, - void *pixels, - int linesize); +/** @brief Create an empty CPU frame. Returns handle (refcount=1). */ +OAKENGINE_API void *oakengine_codec_frame_create(void); -/* ---- CPU frame buffer --------------------------------------------------- */ +/** @brief Increment refcount, return same handle. NULL-safe. */ +OAKENGINE_API void *oakengine_codec_frame_retain(void *frame); -/** - * @brief Create an empty CPU frame (olive::Frame::create()). - * - * @param out_frame Pointer to an olive::FramePtr to receive the new frame. - */ -OAKENGINE_API void oakengine_codec_frame_create(void *out_frame); +/** @brief Decrement refcount; frees at zero. NULL-safe. */ +OAKENGINE_API void oakengine_codec_frame_free(void *frame); -/** - * @brief Set a frame's video parameters (Frame::set_video_params()). - * - * @param frame olive::Frame*. - * @param video_params const olive::VideoParams*. - */ -OAKENGINE_API void oakengine_codec_frame_set_video_params(void *frame, - const void - *video_params); +OAKENGINE_API int oakengine_codec_frame_set_video_params( + void *frame, const oak_video_params *params); + +OAKENGINE_API int oakengine_codec_frame_get_params( + const void *frame, oak_video_params *out); -/** - * @brief Allocate the frame's pixel buffer (Frame::allocate()). - * - * @return 1 on success, 0 on failure or NULL frame. - */ OAKENGINE_API int oakengine_codec_frame_allocate(void *frame); +/** @brief Borrowed pixel data pointer (valid until free). */ +OAKENGINE_API void *oakengine_codec_frame_data(void *frame); + +/** @brief Borrowed const pixel data pointer. */ +OAKENGINE_API const void *oakengine_codec_frame_const_data(const void *frame); + +/** @brief Line stride in pixels. */ +OAKENGINE_API int oakengine_codec_frame_linesize(const void *frame); + +/** @brief Line stride in bytes. */ +OAKENGINE_API int oakengine_codec_frame_linesize_bytes(const void *frame); + +/* ---- Frame queries ------------------------------------------------------ */ + +OAKENGINE_API int oakengine_codec_frame_width(const void *frame); +OAKENGINE_API int oakengine_codec_frame_height(const void *frame); +OAKENGINE_API int oakengine_codec_frame_format(const void *frame); +OAKENGINE_API int oakengine_codec_frame_channel_count(const void *frame); +OAKENGINE_API int oakengine_codec_frame_is_allocated(const void *frame); + +/* ---- Color-managed blit ------------------------------------------------- */ + +/** + * @brief Blit a color-managed image through the OCIO pipeline. + * + * @param renderer Renderer handle. + * @param job POD color transform job (processor + input texture + flags). + * @param dst_texture Destination texture handle, or NULL for screen. + * @param params Destination video params, or NULL to use dst_texture's. + */ +OAKENGINE_API int oakengine_display_renderer_blit_color_managed( + void *renderer, const oak_color_transform_job *job, + void *dst_texture, const oak_video_params *params); + +/* ---- Cross-backend texture download ------------------------------------- */ + +OAKENGINE_API int oakengine_display_renderer_download_from_texture( + void *renderer, int texture_id, const oak_video_params *params, + void *dst_pixels, int linesize); + +/* ---- Shader management -------------------------------------------------- */ + +/** + * @brief Compile a native shader from GLSL source. + * @param frag_src Fragment shader source (required). + * @param vert_src Vertex shader source, or NULL for engine default. + * @return Shader pipeline handle (QVariant*), or NULL on failure. + * Free with oakengine_display_renderer_destroy_shader(). + */ +OAKENGINE_API void *oakengine_display_renderer_create_shader( + void *renderer, const char *frag_src, const char *vert_src); + +/** + * @brief Create the engine's default blank shader (no custom source). + */ +OAKENGINE_API void *oakengine_display_renderer_create_blank_shader( + void *renderer); + +OAKENGINE_API void oakengine_display_renderer_destroy_shader( + void *renderer, void *shader); + +/* ---- Shader blit operations --------------------------------------------- */ + +/** + * @brief Blit a single texture through a shader to the screen. + * Used by scope draw_scope() path. + */ +OAKENGINE_API int oakengine_display_renderer_blit_shader( + void *renderer, void *shader, void *texture, + const oak_video_params *viewport_params); + +/** + * @brief Blit a single texture through a shader to a destination texture. + * Used by histogram blit_to_texture path. + */ +OAKENGINE_API int oakengine_display_renderer_blit_shader_to_texture( + void *renderer, void *shader, void *texture, void *dst_texture); + +/** + * @brief Blit with a vec2 uniform + texture to a destination texture. + * Used by deinterlace path (resolution_in uniform). + */ +OAKENGINE_API int oakengine_display_renderer_blit_shader_vec2_to_texture( + void *renderer, void *shader, void *texture, + const char *vec2_name, float vec2_x, float vec2_y, + void *dst_texture); + +/** + * @brief Blit the blank shader with MVP + crop matrices. + * Used by draw_blank(). + */ +OAKENGINE_API int oakengine_display_renderer_blit_blank( + void *renderer, void *shader, + const float *mvp_matrix, const float *crop_matrix, + const oak_video_params *params); + +/** + * @brief Blit multiple named textures through a shader to a dst texture. + * Used by multicam compositing. + * + * @param names Array of `count` UTF-8 uniform names. + * @param textures Array of `count` texture handles. + */ +OAKENGINE_API int oakengine_display_renderer_blit_shader_multi( + void *renderer, void *shader, + const char *const *names, void *const *textures, int count, + void *dst_texture); + +/* ---- Generic shader blit with named uniforms ----------------------------- */ + +/** + * @brief Named uniform descriptor for scope shader blits. + */ +typedef struct oak_shader_uniform { + const char *name; /**< Uniform name (UTF-8). */ + int type; /**< 0=float, 1=vec2, 2=int/bool, 3=vec3. */ + float values[4]; /**< float: [0]; vec2: [0],[1]; vec3: [0],[1],[2]; int: [0]. */ +} oak_shader_uniform; + +/** + * @brief Blit a single texture through a shader with arbitrary named uniforms. + * + * @param renderer Renderer handle. + * @param shader Shader pipeline handle. + * @param texture Main input texture (bound as "ove_maintex"). + * @param uniforms Array of extra uniforms (may be NULL). + * @param uniform_count Number of entries in `uniforms`. + * @param dst_texture Destination texture, or NULL for screen. + * @param params Viewport params (used when dst_texture is NULL). + */ +OAKENGINE_API int oakengine_display_renderer_blit_shader_uniforms( + void *renderer, void *shader, void *texture, + const oak_shader_uniform *uniforms, int uniform_count, + void *dst_texture, const oak_video_params *params); + +/* ---- Renderer clear ----------------------------------------------------- */ + +/** + * @brief Clear the current destination. + * @param mask NULL for default, or a string mask (unused currently). + * @param r,g,b Clear color (0.0-1.0). + */ +OAKENGINE_API void oakengine_display_renderer_clear( + void *renderer, double r, double g, double b); + +/* ---- Pixel readback ----------------------------------------------------- */ + +/** + * @brief Read a single pixel from a texture (Renderer::get_pixel_from_texture). + * @param x,y Pixel coordinates. + * @param out_rgba Output 4 doubles (RGBA). + */ +OAKENGINE_API int oakengine_display_renderer_get_pixel( + void *renderer, void *texture, int x, int y, double *out_rgba); + #ifdef __cplusplus } #endif diff --git a/engine/oakengine.ver b/engine/oakengine.ver index 087a149c7..ca27bc4a7 100644 --- a/engine/oakengine.ver +++ b/engine/oakengine.ver @@ -1,6 +1,15 @@ { global: oakengine_*; + /* Render backend plugin ABI (oakgl / oakvulkan dlopen) */ + _ZN5olive8Renderer*; + _ZTVN5olive8RendererE; + _ZN5olive11VideoParams19get_bytes_per_pixelE*; + _ZN5olive11VideoParams21get_bytes_per_channelE*; + _ZN5olive13FileFunctions19read_file_as_stringE*; + _ZN5olive7Texture23k_default_interpolationE; + /* App residual C++ dependency (to be migrated to C ABI) */ + _ZNK5olive6Folder17has_child_recursiveE*; local: *; }; diff --git a/engine/pluginSupport/CMakeLists.txt b/engine/pluginSupport/CMakeLists.txt index 69805684b..1f32b77b3 100644 --- a/engine/pluginSupport/CMakeLists.txt +++ b/engine/pluginSupport/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(oakengine PRIVATE +target_sources(oakengine-obj PRIVATE olivehost.h olivehost.cpp oliveplugininstance.h diff --git a/engine/render/renderprocessor.cpp b/engine/render/renderprocessor.cpp index 6e246c7de..2d9c3a3a7 100644 --- a/engine/render/renderprocessor.cpp +++ b/engine/render/renderprocessor.cpp @@ -37,6 +37,7 @@ #include "pluginSupport/oliveclip.h" #include "pluginSupport/olivehost.h" #include "oliveimpl/render/ipc/frameslotpool.h" +#include "src/capi/displayinternal.h" namespace olive { @@ -367,7 +368,7 @@ NodeValueDatabase RenderProcessor::generate_database(const Node *node, if (QtUtils::value_to_ptr(ticket_->property("multicam")) == multicam) { int sz = multicam->get_source_count(); - QVector multicam_tex(sz); + QVector multicam_tex(sz); for (int i = 0; i < sz; i++) { NodeValueTable t = generate_table(multicam->get_connected_render_output( @@ -377,7 +378,9 @@ NodeValueDatabase RenderProcessor::generate_database(const Node *node, multicam, multicam->k_sources_input, i, &t, range); resolve_jobs(val); - multicam_tex[i] = val.to_texture(); + TexturePtr tp = val.to_texture(); + // Store as opaque retained handle for the C ABI app layer + multicam_tex[i] = oakengine_internal_wrap_texture(tp); } ticket_->setProperty("multicam_output", QVariant::fromValue(multicam_tex)); diff --git a/engine/src/capi/display.cpp b/engine/src/capi/display.cpp index 369b72c3a..e86af165c 100644 --- a/engine/src/capi/display.cpp +++ b/engine/src/capi/display.cpp @@ -19,22 +19,118 @@ ***/ #include "oakengine/display.h" +#include "displayinternal.h" + +#include +#include #include #include +#include #include +#include +#include +#include #include "codec/frame.h" +#include "colorinternal.h" #include "render/job/colortransformjob.h" +#include "render/job/shaderjob.h" #include "render/opengl/openglrenderer.h" #include "render/renderer.h" +#include "render/shadercode.h" #include "render/texture.h" +#include "node/value.h" #ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND #include "render/backend/dynamicrenderer.h" #endif +/* ---- Internal control blocks -------------------------------------------- */ + +struct OakEngineDisplayTexture { + olive::TexturePtr ptr; + std::atomic refcount{1}; +}; + +struct OakEngineCodecFrame { + olive::FramePtr ptr; + std::atomic refcount{1}; +}; + +/* ---- Internal helpers --------------------------------------------------- */ + +namespace +{ + +olive::VideoParams pod_to_cpp(const oak_video_params &v) +{ + olive::VideoParams vp( + v.width, v.height, olive::Rational(v.time_base_num, v.time_base_den), + static_cast(v.format), + olive::VideoParams::k_internal_channel_count, + olive::Rational(v.pixel_aspect_num, v.pixel_aspect_den), + static_cast(v.interlacing), + v.divider > 0 ? v.divider : 1); + vp.set_color_range(static_cast(v.color_range)); + return vp; +} + +oak_video_params cpp_to_pod(const olive::VideoParams &vp) +{ + oak_video_params p = {}; + p.width = vp.width(); + p.height = vp.height(); + p.time_base_num = vp.time_base().numerator(); + p.time_base_den = vp.time_base().denominator(); + p.format = static_cast(vp.format()); + p.pixel_aspect_num = vp.pixel_aspect_ratio().numerator(); + p.pixel_aspect_den = vp.pixel_aspect_ratio().denominator(); + p.interlacing = static_cast(vp.interlacing()); + p.color_range = static_cast(vp.color_range()); + p.divider = vp.divider(); + return p; +} + +OakEngineDisplayTexture *tex(void *h) +{ + return static_cast(h); +} + +OakEngineCodecFrame *frm(void *h) +{ + return static_cast(h); +} + +olive::Renderer *ren(void *h) +{ + return static_cast(h); +} + +QMatrix4x4 mat_from_float(const float *f) +{ + if (!f) { + return QMatrix4x4(); + } + // Check if all zeros → identity + bool all_zero = true; + for (int i = 0; i < 16; i++) { + if (f[i] != 0.0f) { + all_zero = false; + break; + } + } + if (all_zero) { + return QMatrix4x4(); + } + return QMatrix4x4(f); +} + +} // namespace + extern "C" { +/* ---- Renderer lifecycle ------------------------------------------------- */ + void *oakengine_display_renderer_create_dynamic(const char *backend_name, void *parent) { @@ -45,8 +141,6 @@ void *oakengine_display_renderer_create_dynamic(const char *backend_name, if (dyn->load()) { return dyn; } - // Backend library failed to load: drop it so the caller can fall back - // to the built-in OpenGL renderer. delete dyn; return nullptr; #else @@ -63,7 +157,7 @@ void *oakengine_display_renderer_create_opengl(void *parent) int oakengine_display_renderer_init(void *renderer, void *gl_context) { - olive::Renderer *r = static_cast(renderer); + olive::Renderer *r = ren(renderer); if (!r) { return OAKENGINE_E_INVALID; } @@ -90,7 +184,7 @@ int oakengine_display_renderer_init(void *renderer, void *gl_context) void oakengine_display_renderer_destroy(void *renderer) { - olive::Renderer *r = static_cast(renderer); + olive::Renderer *r = ren(renderer); if (!r) { return; } @@ -98,85 +192,592 @@ void oakengine_display_renderer_destroy(void *renderer) r->post_destroy(); } -void oakengine_display_renderer_create_texture(void *renderer, - const void *video_params, - const void *pixels, int linesize, - void *out_texture) +/* ---- Renderer queries --------------------------------------------------- */ + +int oakengine_display_renderer_is_open_gl(const void *renderer) { - olive::Renderer *r = static_cast(renderer); - if (!r || !video_params || !out_texture) { - return; - } - const olive::VideoParams ¶ms = - *static_cast(video_params); - *static_cast(out_texture) = - r->create_texture(params, pixels, linesize); + olive::Renderer *r = ren(const_cast(renderer)); + return r ? r->is_open_gl() : 0; } -void oakengine_display_renderer_blit_color_managed(void *renderer, - const void *color_job, - void *dst_texture, - const void *video_params) +int oakengine_display_renderer_is_vulkan(const void *renderer) { - olive::Renderer *r = static_cast(renderer); - if (!r || !color_job) { + olive::Renderer *r = ren(const_cast(renderer)); + return r ? r->is_vulkan() : 0; +} + +/* ---- Texture handle ----------------------------------------------------- */ + +void *oakengine_display_texture_create(void *renderer, + const oak_video_params *params, + const void *pixels, int linesize) +{ + olive::Renderer *r = ren(renderer); + if (!r || !params) { + return nullptr; + } + olive::VideoParams vp = pod_to_cpp(*params); + olive::TexturePtr tp = r->create_texture(vp, pixels, linesize); + if (!tp) { + return nullptr; + } + return new OakEngineDisplayTexture{tp, {1}}; +} + +void *oakengine_display_texture_retain(void *texture) +{ + if (!texture) { + return nullptr; + } + tex(texture)->refcount.fetch_add(1, std::memory_order_relaxed); + return texture; +} + +void oakengine_display_texture_free(void *texture) +{ + if (!texture) { return; } - const olive::ColorTransformJob &job = - *static_cast(color_job); - olive::Texture *dst = static_cast(dst_texture); - if (video_params) { - r->blit_color_managed( - job, dst, *static_cast(video_params)); - } else if (dst) { - r->blit_color_managed(job, dst, dst->params()); + if (tex(texture)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) { + delete tex(texture); } } -void oakengine_display_texture_upload(void *texture, void *pixels, int linesize) +int oakengine_display_texture_upload(void *texture, const void *pixels, + int linesize) { - olive::Texture *t = static_cast(texture); - if (!t) { - return; + if (!texture || !pixels) { + return OAKENGINE_E_INVALID; } - t->upload(pixels, linesize); + tex(texture)->ptr->upload(const_cast(pixels), linesize); + return OAKENGINE_OK; } -void oakengine_display_texture_download(void *texture, void *pixels, - int linesize) +int oakengine_display_texture_download(void *texture, void *pixels, + int linesize) { - olive::Texture *t = static_cast(texture); - if (!t) { - return; + if (!texture || !pixels) { + return OAKENGINE_E_INVALID; } - t->download(pixels, linesize); + tex(texture)->ptr->download(pixels, linesize); + return OAKENGINE_OK; } -void oakengine_codec_frame_create(void *out_frame) +/* ---- Texture queries ---------------------------------------------------- */ + +int oakengine_display_texture_get_params(const void *texture, + oak_video_params *out) { - if (!out_frame) { - return; + if (!texture || !out) { + return OAKENGINE_E_INVALID; } - *static_cast(out_frame) = olive::Frame::create(); + *out = cpp_to_pod(tex(const_cast(texture))->ptr->params()); + return OAKENGINE_OK; } -void oakengine_codec_frame_set_video_params(void *frame, - const void *video_params) +int oakengine_display_texture_id(const void *texture) { - olive::Frame *f = static_cast(frame); - if (!f || !video_params) { + if (!texture) { + return 0; + } + return tex(const_cast(texture))->ptr->id().toInt(); +} + +int oakengine_display_texture_is_dummy(const void *texture) +{ + if (!texture) { + return 1; + } + return tex(const_cast(texture))->ptr->is_dummy() ? 1 : 0; +} + +void *oakengine_display_texture_renderer(const void *texture) +{ + if (!texture) { + return nullptr; + } + return tex(const_cast(texture))->ptr->renderer(); +} + +int oakengine_display_texture_width(const void *texture) +{ + if (!texture) { + return 0; + } + return tex(const_cast(texture))->ptr->width(); +} + +int oakengine_display_texture_height(const void *texture) +{ + if (!texture) { + return 0; + } + return tex(const_cast(texture))->ptr->height(); +} + +int oakengine_display_texture_format(const void *texture) +{ + if (!texture) { + return 0; + } + return static_cast(tex(const_cast(texture))->ptr->format()); +} + +int oakengine_display_texture_channel_count(const void *texture) +{ + if (!texture) { + return 0; + } + return tex(const_cast(texture))->ptr->channel_count(); +} + +int oakengine_display_texture_params_equal(const void *a, const void *b) +{ + if (!a || !b) { + return 0; + } + return tex(const_cast(a))->ptr->params() == + tex(const_cast(b))->ptr->params(); +} + +/* ---- Frame handle ------------------------------------------------------- */ + +void *oakengine_codec_frame_create(void) +{ + return new OakEngineCodecFrame{olive::Frame::create(), {1}}; +} + +void *oakengine_codec_frame_retain(void *frame) +{ + if (!frame) { + return nullptr; + } + frm(frame)->refcount.fetch_add(1, std::memory_order_relaxed); + return frame; +} + +void oakengine_codec_frame_free(void *frame) +{ + if (!frame) { return; } - f->set_video_params(*static_cast(video_params)); + if (frm(frame)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) { + delete frm(frame); + } +} + +int oakengine_codec_frame_set_video_params(void *frame, + const oak_video_params *params) +{ + if (!frame || !params) { + return OAKENGINE_E_INVALID; + } + frm(frame)->ptr->set_video_params(pod_to_cpp(*params)); + return OAKENGINE_OK; +} + +int oakengine_codec_frame_get_params(const void *frame, oak_video_params *out) +{ + if (!frame || !out) { + return OAKENGINE_E_INVALID; + } + *out = cpp_to_pod(frm(const_cast(frame))->ptr->video_params()); + return OAKENGINE_OK; } int oakengine_codec_frame_allocate(void *frame) { - olive::Frame *f = static_cast(frame); - if (!f) { + if (!frame) { return 0; } - return f->allocate() ? 1 : 0; + return frm(frame)->ptr->allocate() ? 1 : 0; +} + +void *oakengine_codec_frame_data(void *frame) +{ + if (!frame) { + return nullptr; + } + return frm(frame)->ptr->data(); +} + +const void *oakengine_codec_frame_const_data(const void *frame) +{ + if (!frame) { + return nullptr; + } + return frm(const_cast(frame))->ptr->const_data(); +} + +int oakengine_codec_frame_linesize(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->linesize_pixels(); +} + +int oakengine_codec_frame_linesize_bytes(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->linesize_bytes(); +} + +/* ---- Frame queries ------------------------------------------------------ */ + +int oakengine_codec_frame_width(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->width(); +} + +int oakengine_codec_frame_height(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->height(); +} + +int oakengine_codec_frame_format(const void *frame) +{ + if (!frame) { + return 0; + } + return static_cast(frm(const_cast(frame))->ptr->format()); +} + +int oakengine_codec_frame_channel_count(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->channel_count(); +} + +int oakengine_codec_frame_is_allocated(const void *frame) +{ + if (!frame) { + return 0; + } + return frm(const_cast(frame))->ptr->is_allocated() ? 1 : 0; +} + +/* ---- Color-managed blit ------------------------------------------------- */ + +int oakengine_display_renderer_blit_color_managed( + void *renderer, const oak_color_transform_job *job, + void *dst_texture, const oak_video_params *params) +{ + olive::Renderer *r = ren(renderer); + if (!r || !job) { + return OAKENGINE_E_INVALID; + } + + olive::ColorTransformJob ctj; + if (job->processor) { + ctj.set_color_processor( + static_cast(job->processor)->ptr); + } + if (job->input_texture) { + ctj.set_input_texture(tex(job->input_texture)->ptr); + } + ctj.set_input_alpha_association( + static_cast(job->input_alpha_association)); + ctj.set_clear_destination_enabled(job->clear_destination != 0); + ctj.set_force_opaque(job->force_opaque != 0); + ctj.set_transform_matrix(mat_from_float(job->matrix)); + ctj.set_crop_matrix(mat_from_float(job->crop_matrix)); + + olive::Texture *dst = dst_texture ? tex(dst_texture)->ptr.get() : nullptr; + + if (params) { + r->blit_color_managed(ctj, dst, pod_to_cpp(*params)); + } else if (dst) { + r->blit_color_managed(ctj, dst, dst->params()); + } + + return OAKENGINE_OK; +} + +/* ---- Cross-backend texture download ------------------------------------- */ + +int oakengine_display_renderer_download_from_texture( + void *renderer, int texture_id, const oak_video_params *params, + void *dst_pixels, int linesize) +{ + olive::Renderer *r = ren(renderer); + if (!r || !params || !dst_pixels) { + return OAKENGINE_E_INVALID; + } + r->download_from_texture(texture_id, pod_to_cpp(*params), dst_pixels, + linesize); + return OAKENGINE_OK; +} + +/* ---- Shader management -------------------------------------------------- */ + +void *oakengine_display_renderer_create_shader(void *renderer, + const char *frag_src, + const char *vert_src) +{ + olive::Renderer *r = ren(renderer); + if (!r || !frag_src) { + return nullptr; + } + olive::ShaderCode code(QString::fromUtf8(frag_src), + vert_src ? QString::fromUtf8(vert_src) : QString()); + QVariant *v = new QVariant(r->create_native_shader(code)); + return v; +} + +void *oakengine_display_renderer_create_blank_shader(void *renderer) +{ + olive::Renderer *r = ren(renderer); + if (!r) { + return nullptr; + } + QVariant *v = new QVariant(r->create_native_shader(olive::ShaderCode())); + return v; +} + +void oakengine_display_renderer_destroy_shader(void *renderer, void *shader) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader) { + return; + } + QVariant *v = static_cast(shader); + r->destroy_native_shader(*v); + delete v; +} + +/* ---- Shader blit operations --------------------------------------------- */ + +int oakengine_display_renderer_blit_shader(void *renderer, void *shader, + void *texture, + const oak_video_params *viewport_params) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !texture || !viewport_params) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + job.insert(QStringLiteral("ove_maintex"), + olive::NodeValue(olive::NodeValue::k_texture, + QVariant::fromValue(tex(texture)->ptr))); + r->blit(*sv, job, pod_to_cpp(*viewport_params)); + return OAKENGINE_OK; +} + +int oakengine_display_renderer_blit_shader_to_texture( + void *renderer, void *shader, void *texture, void *dst_texture) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !texture || !dst_texture) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + job.insert(QStringLiteral("ove_maintex"), + olive::NodeValue(olive::NodeValue::k_texture, + QVariant::fromValue(tex(texture)->ptr))); + r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get()); + return OAKENGINE_OK; +} + +int oakengine_display_renderer_blit_shader_vec2_to_texture( + void *renderer, void *shader, void *texture, + const char *vec2_name, float vec2_x, float vec2_y, + void *dst_texture) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !texture || !dst_texture) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + job.insert(QStringLiteral("ove_maintex"), + olive::NodeValue(olive::NodeValue::k_texture, + QVariant::fromValue(tex(texture)->ptr))); + if (vec2_name) { + job.insert(QString::fromUtf8(vec2_name), + olive::NodeValue(olive::NodeValue::k_vec2, + QVector2D(vec2_x, vec2_y))); + } + r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get()); + return OAKENGINE_OK; +} + +int oakengine_display_renderer_blit_blank( + void *renderer, void *shader, + const float *mvp_matrix, const float *crop_matrix, + const oak_video_params *params) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !params) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + job.insert(QStringLiteral("ove_mvpmat"), + olive::NodeValue(olive::NodeValue::k_matrix, + mat_from_float(mvp_matrix))); + job.insert(QStringLiteral("ove_cropmatrix"), + olive::NodeValue(olive::NodeValue::k_matrix, + mat_from_float(crop_matrix))); + r->blit(*sv, job, pod_to_cpp(*params), false); + return OAKENGINE_OK; +} + +int oakengine_display_renderer_blit_shader_multi( + void *renderer, void *shader, + const char *const *names, void *const *textures, int count, + void *dst_texture) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !names || !textures || count <= 0 || !dst_texture) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + for (int i = 0; i < count; i++) { + if (names[i] && textures[i]) { + job.insert(QString::fromUtf8(names[i]), + olive::NodeValue(olive::NodeValue::k_texture, + QVariant::fromValue(tex(textures[i])->ptr))); + } + } + r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get()); + return OAKENGINE_OK; +} + +int oakengine_display_renderer_blit_shader_uniforms( + void *renderer, void *shader, void *texture, + const oak_shader_uniform *uniforms, int uniform_count, + void *dst_texture, const oak_video_params *params) +{ + olive::Renderer *r = ren(renderer); + if (!r || !shader || !texture) { + return OAKENGINE_E_INVALID; + } + QVariant *sv = static_cast(shader); + olive::ShaderJob job; + job.insert(QStringLiteral("ove_maintex"), + olive::NodeValue(olive::NodeValue::k_texture, + QVariant::fromValue(tex(texture)->ptr))); + for (int i = 0; i < uniform_count; i++) { + if (!uniforms[i].name) { + continue; + } + QString name = QString::fromUtf8(uniforms[i].name); + switch (uniforms[i].type) { + case 0: // float + job.insert(name, olive::NodeValue(olive::NodeValue::k_float, + uniforms[i].values[0])); + break; + case 1: // vec2 + job.insert(name, olive::NodeValue(olive::NodeValue::k_vec2, + QVector2D(uniforms[i].values[0], + uniforms[i].values[1]))); + break; + case 2: // int/bool + job.insert(name, olive::NodeValue(olive::NodeValue::k_boolean, + static_cast(uniforms[i].values[0]) != 0)); + break; + case 3: // vec3 + job.insert(name, olive::NodeValue(olive::NodeValue::k_vec3, + QVector3D(uniforms[i].values[0], + uniforms[i].values[1], + uniforms[i].values[2]))); + break; + default: + break; + } + } + if (dst_texture) { + r->blit_to_texture(*sv, job, tex(dst_texture)->ptr.get()); + } else if (params) { + r->blit(*sv, job, pod_to_cpp(*params)); + } else { + return OAKENGINE_E_INVALID; + } + return OAKENGINE_OK; +} + +/* ---- Renderer clear ----------------------------------------------------- */ + +void oakengine_display_renderer_clear(void *renderer, double r, double g, + double b) +{ + olive::Renderer *rn = ren(renderer); + if (!rn) { + return; + } + rn->clear_destination(nullptr, r, g, b); +} + +/* ---- Pixel readback ----------------------------------------------------- */ + +int oakengine_display_renderer_get_pixel(void *renderer, void *texture, + int x, int y, double *out_rgba) +{ + olive::Renderer *r = ren(renderer); + if (!r || !texture || !out_rgba) { + return OAKENGINE_E_INVALID; + } + olive::Color c = r->get_pixel_from_texture(tex(texture)->ptr.get(), + QPoint(x, y)); + out_rgba[0] = c.red(); + out_rgba[1] = c.green(); + out_rgba[2] = c.blue(); + out_rgba[3] = c.alpha(); + return OAKENGINE_OK; } } // extern "C" + +/* ---- Internal C++ helpers (not exported via C ABI) ---------------------- */ + +void *oakengine_internal_wrap_texture(const olive::TexturePtr &tp) +{ + if (!tp) { + return nullptr; + } + auto *blk = new OakEngineDisplayTexture; + blk->ptr = tp; + blk->refcount.store(1); + return blk; +} + +void *oakengine_internal_wrap_frame(const olive::FramePtr &fp) +{ + if (!fp) { + return nullptr; + } + auto *blk = new OakEngineCodecFrame; + blk->ptr = fp; + blk->refcount.store(1); + return blk; +} + +olive::TexturePtr oakengine_internal_unwrap_texture(void *handle) +{ + if (!handle) { + return nullptr; + } + return tex(handle)->ptr; +} + +olive::FramePtr oakengine_internal_unwrap_frame(void *handle) +{ + if (!handle) { + return nullptr; + } + return frm(handle)->ptr; +} diff --git a/engine/src/capi/displayinternal.h b/engine/src/capi/displayinternal.h new file mode 100644 index 000000000..92b451e1a --- /dev/null +++ b/engine/src/capi/displayinternal.h @@ -0,0 +1,54 @@ +/*** + + 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 . + +***/ + +#ifndef OAKENGINE_DISPLAYINTERNAL_H +#define OAKENGINE_DISPLAYINTERNAL_H + +// Internal (not installed) helpers for bridging engine-internal TexturePtr / +// FramePtr to the opaque C ABI handles defined in capi/display.cpp. + +#include "render/texture.h" +#include "codec/frame.h" + +/** + * @brief Wrap an existing TexturePtr into a refcounted ABI handle (refcount=1). + * Returns nullptr if tp is null. + */ +void *oakengine_internal_wrap_texture(const olive::TexturePtr &tp); + +/** + * @brief Wrap an existing FramePtr into a refcounted ABI handle (refcount=1). + * Returns nullptr if fp is null. + */ +void *oakengine_internal_wrap_frame(const olive::FramePtr &fp); + +/** + * @brief Unwrap an ABI texture handle to the underlying TexturePtr. + * Returns empty TexturePtr if handle is null. + */ +olive::TexturePtr oakengine_internal_unwrap_texture(void *handle); + +/** + * @brief Unwrap an ABI frame handle to the underlying FramePtr. + * Returns empty FramePtr if handle is null. + */ +olive::FramePtr oakengine_internal_unwrap_frame(void *handle); + +#endif // OAKENGINE_DISPLAYINTERNAL_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 616cdc6f3..31c5498cb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,8 +53,13 @@ function(olive_add_test GROUP NAME SOURCE) target_link_libraries( ${NAME} PRIVATE + oakengine-obj ${OLIVE_LIBRARIES} ) + # Export symbols so dlopen'd render backend plugins can resolve engine objects + if (UNIX AND NOT APPLE) + target_link_options(${NAME} PRIVATE "LINKER:--export-dynamic") + endif() target_compile_definitions( ${NAME} PRIVATE diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 90b13bfe1..20db1338b 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -159,6 +159,7 @@ target_include_directories( target_link_libraries( olive-gtest PRIVATE + oakengine-obj ${OLIVE_LIBRARIES} GTest::gtest Qt${QT_VERSION_MAJOR}::Test @@ -177,6 +178,11 @@ target_compile_options( ${OLIVE_COMPILE_OPTIONS} ) +# Export symbols so dlopen'd render backend plugins can resolve engine objects +if (UNIX AND NOT APPLE) + target_link_options(olive-gtest PRIVATE "LINKER:--export-dynamic") +endif() + if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND) target_compile_definitions(olive-gtest PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND) add_dependencies(olive-gtest oakgl)