R7: pure C ABI display boundary + engine visibility closure
R7-A (Qwen 3.8 Max): oakengine/display.h rewritten to the POD contract from r7-pure-abi-plan.md - oak_video_params everywhere, opaque texture/frame handles with retain/free protocol (engine-heap control blocks), OakSharedBuffer refcounted wrapper for the QVariant playback path. All TexturePtr/FramePtr gone from app (47 sites). R7-B (Qwen 3.8 Max): liboakengine.so exports 3486 -> 19 C++ symbols (version script oakengine.ver: oakengine_* plus the documented oakgl/oakvulkan dlopen plugin ABI). oakengine-obj OBJECT library feeds both the shared lib and the test binaries (-rdynamic so dlopen'd backends resolve engine objects). Fix (Kimi K3): producer/consumer type mismatch - viewerdisplay unpacks OakSharedBufferPtr but viewer.cpp pushed raw void* handles, so no frame ever reached the display widget (all 5 vulkan viewer tests timed out with 'never received a texture'). Producers now wrap with oak_make_shared_frame / oak_make_shared_texture(retain). Verified: build 0 errors, ctest 45/45, nm U _ZN5olive = 0 in oak-editor/oak-render-worker/oak-cli, 19 exported C++ symbols in liboakengine.so (all documented plugin ABI).
This commit is contained in:
+5
-23
@@ -21,33 +21,10 @@ set(OLIVE_SOURCES
|
|||||||
core.cpp
|
core.cpp
|
||||||
engineeventbridge.h
|
engineeventbridge.h
|
||||||
engineeventbridge.cpp
|
engineeventbridge.cpp
|
||||||
common/htmlapp.cpp
|
|
||||||
common/filefunctionsapp.cpp
|
|
||||||
common/colorcodingapp.h
|
common/colorcodingapp.h
|
||||||
common/colorcodingapp.cpp
|
|
||||||
common/xmlutilsapp.cpp
|
|
||||||
common/hashstreamapp.cpp
|
|
||||||
common/qtutilsapp.cpp
|
|
||||||
common/nodevaluehandle.h
|
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)
|
#set(OLIVE_RESOURCES)
|
||||||
|
|
||||||
# Add subdirectories, which will populate the above variables
|
# Add subdirectories, which will populate the above variables
|
||||||
@@ -191,6 +168,11 @@ endif ()
|
|||||||
# Set link libraries
|
# Set link libraries
|
||||||
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
|
target_link_libraries(olive-editor PRIVATE ${OLIVE_LIBRARIES})
|
||||||
target_link_libraries(libolive-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
|
# The ffmpeg_bridge shared library ships next to the binaries: inside the
|
||||||
# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in the
|
# macOS app bundle (Contents/MacOS, resolved via @loader_path), and in the
|
||||||
|
|||||||
@@ -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);
|
histogram_->set_buffer(frame);
|
||||||
vectorscope_->set_buffer(frame);
|
vectorscope_->set_buffer(frame);
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void set_reference_buffer(TexturePtr frame);
|
void set_reference_buffer(void *frame);
|
||||||
|
|
||||||
void set_color_manager(OakEngineColorManager *manager);
|
void set_color_manager(OakEngineColorManager *manager);
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ signals:
|
|||||||
/**
|
/**
|
||||||
* @brief Signal emitted when a new frame is loaded
|
* @brief Signal emitted when a new frame is loaded
|
||||||
*/
|
*/
|
||||||
void texture_changed(TexturePtr t);
|
void texture_changed(void *t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
|
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
|
||||||
|
|||||||
@@ -54,22 +54,22 @@ ManagedDisplayWidget::ManagedDisplayWidget(QWidget *parent)
|
|||||||
return oakengine_render_manager_backend_to_string(backend, buf,
|
return oakengine_render_manager_backend_to_string(backend, buf,
|
||||||
sz);
|
sz);
|
||||||
});
|
});
|
||||||
attached_renderer_ = static_cast<Renderer *>(
|
attached_renderer_ =
|
||||||
oakengine_display_renderer_create_dynamic(
|
oakengine_display_renderer_create_dynamic(
|
||||||
backend.toUtf8().constData(), this));
|
backend.toUtf8().constData(), this);
|
||||||
if (!attached_renderer_) {
|
if (!attached_renderer_) {
|
||||||
qWarning()
|
qWarning()
|
||||||
<< "Failed to load dynamic render backend for viewer, falling back to OpenGL";
|
<< "Failed to load dynamic render backend for viewer, falling back to OpenGL";
|
||||||
attached_renderer_ = static_cast<Renderer *>(
|
attached_renderer_ =
|
||||||
oakengine_display_renderer_create_opengl(this));
|
oakengine_display_renderer_create_opengl(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
attached_renderer_ = static_cast<Renderer *>(
|
attached_renderer_ =
|
||||||
oakengine_display_renderer_create_opengl(this));
|
oakengine_display_renderer_create_opengl(this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (attached_renderer_->is_open_gl()) {
|
if (oakengine_display_renderer_is_open_gl(attached_renderer_)) {
|
||||||
// OpenGL path
|
// OpenGL path
|
||||||
inner_widget_ = new ManagedDisplayWidgetOpenGL();
|
inner_widget_ = new ManagedDisplayWidgetOpenGL();
|
||||||
inner_widget_->setAttribute(Qt::WA_TranslucentBackground, false);
|
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_width = width() * devicePixelRatioF();
|
||||||
int device_height = height() * devicePixelRatioF();
|
int device_height = height() * devicePixelRatioF();
|
||||||
PixelFormat device_format = static_cast<PixelFormat::Format>(
|
int device_format = OAK_CONFIG("OfflinePixelFormat").toInt();
|
||||||
OAK_CONFIG("OfflinePixelFormat").toInt());
|
|
||||||
oak_video_params pod = {};
|
oak_video_params pod = {};
|
||||||
pod.width = device_width;
|
pod.width = device_width;
|
||||||
pod.height = device_height;
|
pod.height = device_height;
|
||||||
pod.format = device_format;
|
pod.format = device_format;
|
||||||
return video_params_from_pod(pod);
|
return pod;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ManagedDisplayWidget::update()
|
void ManagedDisplayWidget::update()
|
||||||
|
|||||||
@@ -33,10 +33,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "oakengine/color.h"
|
#include "oakengine/color.h"
|
||||||
|
#include "oakengine/display.h"
|
||||||
#include "oakengine/events.h"
|
#include "oakengine/events.h"
|
||||||
#include "render/colorprocessor.h"
|
#include "render/colorprocessor.h"
|
||||||
#include "render/colortransform.h"
|
#include "render/colortransform.h"
|
||||||
#include "render/renderer.h"
|
|
||||||
#include "widget/manageddisplay/colorprocessorhandle.h"
|
#include "widget/manageddisplay/colorprocessorhandle.h"
|
||||||
#include "widget/menu/menu.h"
|
#include "widget/menu/menu.h"
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ protected:
|
|||||||
*/
|
*/
|
||||||
virtual void color_processor_changed_event();
|
virtual void color_processor_changed_event();
|
||||||
|
|
||||||
Renderer *renderer() const
|
void *renderer() const
|
||||||
{
|
{
|
||||||
return attached_renderer_;
|
return attached_renderer_;
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ protected:
|
|||||||
return wrapper_ ? wrapper_->rect() : QRect();
|
return wrapper_ ? wrapper_->rect() : QRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoParams get_viewport_params() const;
|
oak_video_params get_viewport_params() const;
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
/**
|
/**
|
||||||
@@ -303,7 +303,7 @@ private:
|
|||||||
/**
|
/**
|
||||||
* @brief Renderer abstraction
|
* @brief Renderer abstraction
|
||||||
*/
|
*/
|
||||||
Renderer *attached_renderer_;
|
void *attached_renderer_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Connected color manager
|
* @brief Connected color manager
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ namespace olive
|
|||||||
MulticamDisplay::MulticamDisplay(QWidget *parent)
|
MulticamDisplay::MulticamDisplay(QWidget *parent)
|
||||||
: super(parent)
|
: super(parent)
|
||||||
, node_(nullptr)
|
, node_(nullptr)
|
||||||
|
, shader_(nullptr)
|
||||||
, rows_(0)
|
, rows_(0)
|
||||||
, cols_(0)
|
, cols_(0)
|
||||||
{
|
{
|
||||||
@@ -70,47 +71,63 @@ void MulticamDisplay::on_paint()
|
|||||||
|
|
||||||
void MulticamDisplay::on_destroy()
|
void MulticamDisplay::on_destroy()
|
||||||
{
|
{
|
||||||
shader_ = QVariant();
|
if (shader_) {
|
||||||
|
oakengine_display_renderer_destroy_shader(renderer(), shader_);
|
||||||
|
shader_ = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
|
void *MulticamDisplay::load_custom_texture_from_frame(const QVariant &v)
|
||||||
{
|
{
|
||||||
if (v.canConvert<QVector<TexturePtr>>()) {
|
if (v.canConvert<QVector<void *>>()) {
|
||||||
QVector<TexturePtr> tex = v.value<QVector<TexturePtr>>();
|
QVector<void *> tex = v.value<QVector<void *>>();
|
||||||
|
|
||||||
TexturePtr main;
|
oak_video_params main_params = this->get_viewport_params();
|
||||||
const VideoParams main_params = this->get_viewport_params();
|
void *main_tex = oakengine_display_texture_create(
|
||||||
oakengine_display_renderer_create_texture(renderer(), &main_params,
|
renderer(), &main_params, nullptr, 0);
|
||||||
nullptr, 0, &main);
|
|
||||||
|
|
||||||
int rows, cols;
|
int rows, cols;
|
||||||
oakengine_multicam_get_rows_and_columns(tex.size(), &rows, &cols);
|
oakengine_multicam_get_rows_and_columns(tex.size(), &rows, &cols);
|
||||||
|
|
||||||
if (shader_.isNull() || rows_ != rows || cols_ != cols) {
|
if (!shader_ || rows_ != rows || cols_ != cols) {
|
||||||
if (!shader_.isNull()) {
|
if (shader_) {
|
||||||
renderer()->destroy_native_shader(shader_);
|
oakengine_display_renderer_destroy_shader(renderer(), shader_);
|
||||||
}
|
}
|
||||||
|
|
||||||
shader_ = renderer()->create_native_shader(
|
QString code = generate_shader_code(rows, cols);
|
||||||
ShaderCode(generate_shader_code(rows, cols)));
|
shader_ = oakengine_display_renderer_create_shader(
|
||||||
|
renderer(), code.toUtf8().constData(), nullptr);
|
||||||
|
|
||||||
rows_ = rows;
|
rows_ = rows;
|
||||||
cols_ = cols;
|
cols_ = cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderJob job;
|
// Build name and texture arrays for multi-texture blit
|
||||||
|
const int count = tex.size();
|
||||||
|
QVector<QByteArray> name_storage(count);
|
||||||
|
QVector<const char *> names(count);
|
||||||
|
QVector<void *> textures(count);
|
||||||
|
|
||||||
for (int i = 0; i < tex.size(); i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
int c, r;
|
int c, r;
|
||||||
oakengine_multicam_index_to_row_cols(i, rows, cols, &r, &c);
|
oakengine_multicam_index_to_row_cols(i, rows, cols, &r, &c);
|
||||||
job.insert(QStringLiteral("tex_%1_%2")
|
name_storage[i] = QStringLiteral("tex_%1_%2")
|
||||||
.arg(QString::number(r), QString::number(c)),
|
.arg(QString::number(r), QString::number(c))
|
||||||
NodeValue(NodeValue::k_texture, tex.at(i)));
|
.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 {
|
} else {
|
||||||
return super::load_custom_texture_from_frame(v);
|
return super::load_custom_texture_from_frame(v);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,14 +40,14 @@ protected:
|
|||||||
|
|
||||||
virtual void on_destroy() override;
|
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:
|
private:
|
||||||
static QString generate_shader_code(int rows, int cols);
|
static QString generate_shader_code(int rows, int cols);
|
||||||
|
|
||||||
MultiCamNode *node_;
|
MultiCamNode *node_;
|
||||||
|
|
||||||
QVariant shader_;
|
void *shader_;
|
||||||
int rows_;
|
int rows_;
|
||||||
int cols_;
|
int cols_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
/***
|
/***
|
||||||
|
|
||||||
Olive - Non-Linear Video Editor
|
Olive - Non-Linear Video Editor
|
||||||
@@ -24,12 +25,10 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
#include <QVector2D>
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "node/node.h"
|
#include "common/filefunctions.h"
|
||||||
#include "oakengine/display.h"
|
#include "oakengine/display.h"
|
||||||
#include "widget/viewer/vieweroutpututils.h"
|
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -38,6 +37,8 @@ namespace olive
|
|||||||
|
|
||||||
HistogramScope::HistogramScope(QWidget *parent)
|
HistogramScope::HistogramScope(QWidget *parent)
|
||||||
: super(parent)
|
: super(parent)
|
||||||
|
, pipeline_secondary_(nullptr)
|
||||||
|
, texture_row_sums_(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,29 +46,37 @@ void HistogramScope::on_init()
|
|||||||
{
|
{
|
||||||
super::on_init();
|
super::on_init();
|
||||||
|
|
||||||
ShaderCode secondary_code(
|
QString frag = FileFunctions::read_file_as_string(
|
||||||
FileFunctions::read_file_as_string(
|
":/shaders/rgbhistogram_secondary.frag");
|
||||||
":/shaders/rgbhistogram_secondary.frag"),
|
QString vert = FileFunctions::read_file_as_string(
|
||||||
FileFunctions::read_file_as_string(":/shaders/rgbhistogram.vert"));
|
":/shaders/rgbhistogram.vert");
|
||||||
pipeline_secondary_ = renderer()->create_native_shader(secondary_code);
|
pipeline_secondary_ = oakengine_display_renderer_create_shader(
|
||||||
|
renderer(), frag.toUtf8().constData(), vert.toUtf8().constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistogramScope::on_destroy()
|
void HistogramScope::on_destroy()
|
||||||
{
|
{
|
||||||
pipeline_secondary_.clear();
|
if (pipeline_secondary_) {
|
||||||
texture_row_sums_ = nullptr;
|
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();
|
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/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;
|
float histogram_scale = 0.80f;
|
||||||
// This value is eyeballed for usefulness. Until we have a geometry
|
// 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_base = 2.5f;
|
||||||
float histogram_power = 1.0f / histogram_base;
|
float histogram_power = 1.0f / histogram_base;
|
||||||
|
|
||||||
ShaderJob shader_job;
|
// Set up uniforms shared by both passes
|
||||||
|
oak_shader_uniform uniforms[3];
|
||||||
|
uniforms[0] = {"viewport", 1,
|
||||||
|
{static_cast<float>(width()), static_cast<float>(height())}};
|
||||||
|
uniforms[1] = {"histogram_scale", 0, {histogram_scale, 0.0f}};
|
||||||
|
uniforms[2] = {"histogram_power", 0, {histogram_power, 0.0f}};
|
||||||
|
|
||||||
shader_job.insert(QStringLiteral("viewport"),
|
// Recreate row-sums texture if size changed
|
||||||
NodeValue(NodeValue::k_vec2,
|
bool need_recreate = false;
|
||||||
QVector2D(width(), height())));
|
if (!texture_row_sums_) {
|
||||||
shader_job.insert(QStringLiteral("histogram_scale"),
|
need_recreate = true;
|
||||||
NodeValue(NodeValue::k_float, histogram_scale));
|
} else if (oakengine_display_texture_width(texture_row_sums_) != width() ||
|
||||||
shader_job.insert(QStringLiteral("histogram_power"),
|
oakengine_display_texture_height(texture_row_sums_) !=
|
||||||
NodeValue(NodeValue::k_float, histogram_power));
|
height()) {
|
||||||
|
need_recreate = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!texture_row_sums_ || texture_row_sums_->width() != this->width() ||
|
if (need_recreate) {
|
||||||
texture_row_sums_->height() != this->height()) {
|
if (texture_row_sums_) {
|
||||||
|
oakengine_display_texture_free(texture_row_sums_);
|
||||||
|
}
|
||||||
oak_video_params pod = {};
|
oak_video_params pod = {};
|
||||||
pod.width = width();
|
pod.width = width();
|
||||||
pod.height = height();
|
pod.height = height();
|
||||||
pod.format = managed_tex->format();
|
pod.format = oakengine_display_texture_format(managed_tex);
|
||||||
const VideoParams row_sums_params = video_params_from_pod(pod);
|
texture_row_sums_ =
|
||||||
oakengine_display_renderer_create_texture(renderer(),
|
oakengine_display_texture_create(renderer(), &pod, nullptr, 0);
|
||||||
&row_sums_params, nullptr, 0,
|
|
||||||
&texture_row_sums_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw managed texture to a sums texture
|
// Pass 1: Draw managed texture to row-sums texture
|
||||||
shader_job.insert(QStringLiteral("ove_maintex"),
|
oakengine_display_renderer_blit_shader_uniforms(
|
||||||
NodeValue(NodeValue::k_texture,
|
renderer(), pipeline, managed_tex, uniforms, 3, texture_row_sums_,
|
||||||
QVariant::fromValue(managed_tex)));
|
nullptr);
|
||||||
renderer()->blit_to_texture(pipeline, shader_job, texture_row_sums_.get());
|
|
||||||
|
|
||||||
// Draw sums into a histogram
|
// Pass 2: Draw row-sums into a histogram on screen
|
||||||
shader_job.insert(QStringLiteral("ove_maintex"),
|
oak_video_params vp = get_viewport_params();
|
||||||
NodeValue(NodeValue::k_texture,
|
oakengine_display_renderer_blit_shader_uniforms(
|
||||||
QVariant::fromValue(texture_row_sums_)));
|
renderer(), pipeline_secondary_, texture_row_sums_, uniforms, 3,
|
||||||
renderer()->blit(pipeline_secondary_, shader_job,
|
nullptr, &vp);
|
||||||
texture_row_sums_->params());
|
|
||||||
|
|
||||||
// Draw line overlays
|
// Draw line overlays
|
||||||
QPainter p(paint_device());
|
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_start_dim_y = ((height() - 1.0) - histogram_dim_y) / 2.0f;
|
||||||
float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x;
|
float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x;
|
||||||
|
|
||||||
// for (int i=0; i <= histogram_steps; i++) {
|
|
||||||
for (std::vector<float>::iterator it = histogram_increments.begin();
|
for (std::vector<float>::iterator it = histogram_increments.begin();
|
||||||
it != histogram_increments.end(); it++) {
|
it != histogram_increments.end(); it++) {
|
||||||
histogram_lines[it - histogram_increments.begin()].setLine(
|
histogram_lines[it - histogram_increments.begin()].setLine(
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ protected slots:
|
|||||||
virtual void on_destroy() override;
|
virtual void on_destroy() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ShaderCode generate_shader_code() override;
|
virtual ScopeShaderCode generate_shader_code() override;
|
||||||
QVariant create_secondary_shader();
|
|
||||||
|
|
||||||
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;
|
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVariant pipeline_secondary_;
|
void *pipeline_secondary_;
|
||||||
TexturePtr texture_row_sums_;
|
void *texture_row_sums_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,6 @@
|
|||||||
#include "common/configwrapper.h"
|
#include "common/configwrapper.h"
|
||||||
#include "oakengine/display.h"
|
#include "oakengine/display.h"
|
||||||
#include "oakengine/videoparams.h"
|
#include "oakengine/videoparams.h"
|
||||||
#include "render/job/colortransformjob.h"
|
|
||||||
#include "widget/viewer/vieweroutpututils.h"
|
|
||||||
#include "render/job/shaderjob.h"
|
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -37,16 +34,23 @@ namespace olive
|
|||||||
|
|
||||||
ScopeBase::ScopeBase(QWidget *parent)
|
ScopeBase::ScopeBase(QWidget *parent)
|
||||||
: super(parent)
|
: super(parent)
|
||||||
|
, pipeline_(nullptr)
|
||||||
, texture_(nullptr)
|
, texture_(nullptr)
|
||||||
|
, managed_tex_(nullptr)
|
||||||
, managed_tex_up_to_date_(false)
|
, managed_tex_up_to_date_(false)
|
||||||
|
, software_tex_(nullptr)
|
||||||
, software_image_up_to_date_(false)
|
, software_image_up_to_date_(false)
|
||||||
|
, local_texture_(nullptr)
|
||||||
{
|
{
|
||||||
enable_default_context_menu();
|
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;
|
managed_tex_up_to_date_ = false;
|
||||||
software_image_up_to_date_ = false;
|
software_image_up_to_date_ = false;
|
||||||
update();
|
update();
|
||||||
@@ -57,20 +61,17 @@ void ScopeBase::showEvent(QShowEvent *e)
|
|||||||
super::showEvent(e);
|
super::showEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeBase::draw_scope(TexturePtr managed_tex, QVariant pipeline)
|
void ScopeBase::draw_scope(void *managed_tex, void *pipeline)
|
||||||
{
|
{
|
||||||
ShaderJob job;
|
oak_video_params vp = get_viewport_params();
|
||||||
|
oakengine_display_renderer_blit_shader(renderer(), pipeline, managed_tex,
|
||||||
job.insert(QStringLiteral("ove_maintex"),
|
&vp);
|
||||||
NodeValue(NodeValue::k_texture,
|
|
||||||
QVariant::fromValue(managed_tex)));
|
|
||||||
|
|
||||||
renderer()->blit(pipeline, job, get_viewport_params());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScopeBase::update_software_image()
|
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_ = QImage();
|
||||||
software_image_up_to_date_ = true;
|
software_image_up_to_date_ = true;
|
||||||
return;
|
return;
|
||||||
@@ -80,23 +81,29 @@ void ScopeBase::update_software_image()
|
|||||||
// separate Vulkan device). The reference texture emitted by the viewer lives
|
// 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
|
// 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.
|
// it into this scope's renderer before we can sample it.
|
||||||
TexturePtr source_tex = texture_;
|
void *source_tex = texture_;
|
||||||
if (texture_->renderer() && texture_->renderer() != renderer()) {
|
void *tex_renderer = oakengine_display_texture_renderer(texture_);
|
||||||
FramePtr temp_frame;
|
if (tex_renderer && tex_renderer != renderer()) {
|
||||||
oakengine_codec_frame_create(&temp_frame);
|
void *temp_frame = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_set_video_params(temp_frame.get(),
|
oak_video_params tex_params = {};
|
||||||
&texture_->params());
|
oakengine_display_texture_get_params(texture_, &tex_params);
|
||||||
oakengine_codec_frame_allocate(temp_frame.get());
|
oakengine_codec_frame_set_video_params(temp_frame, &tex_params);
|
||||||
oakengine_display_texture_download(texture_.get(), temp_frame->data(),
|
oakengine_codec_frame_allocate(temp_frame);
|
||||||
temp_frame->linesize_pixels());
|
oakengine_display_texture_download(
|
||||||
|
texture_, oakengine_codec_frame_data(temp_frame),
|
||||||
|
oakengine_codec_frame_linesize(temp_frame));
|
||||||
|
|
||||||
oakengine_display_renderer_create_texture(
|
if (local_texture_) {
|
||||||
renderer(), &temp_frame->video_params(), temp_frame->data(),
|
oakengine_display_texture_free(local_texture_);
|
||||||
temp_frame->linesize_pixels(), &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_;
|
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_ = QImage();
|
||||||
software_image_up_to_date_ = true;
|
software_image_up_to_date_ = true;
|
||||||
return;
|
return;
|
||||||
@@ -105,46 +112,60 @@ void ScopeBase::update_software_image()
|
|||||||
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
|
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
|
||||||
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
|
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
|
||||||
|
|
||||||
oak_video_params pod = {};
|
oak_video_params offscreen_pod = {};
|
||||||
pod.width = texture_width;
|
offscreen_pod.width = texture_width;
|
||||||
pod.height = texture_height;
|
offscreen_pod.height = texture_height;
|
||||||
pod.format = PixelFormat::u8;
|
offscreen_pod.format = 0; // PixelFormat::u8
|
||||||
const VideoParams offscreen_params(video_params_from_pod(pod));
|
|
||||||
|
|
||||||
if (!software_tex_ || software_tex_->params() != offscreen_params) {
|
bool need_recreate = false;
|
||||||
oakengine_display_renderer_create_texture(renderer(),
|
if (!software_tex_) {
|
||||||
&offscreen_params, nullptr, 0,
|
need_recreate = true;
|
||||||
&software_tex_);
|
} else {
|
||||||
software_buffer_.resize(
|
oak_video_params cur = {};
|
||||||
texture_width * texture_height *
|
oakengine_display_texture_get_params(software_tex_, &cur);
|
||||||
oakengine_video_params_bytes_per_pixel(PixelFormat::u8,
|
if (cur.width != offscreen_pod.width ||
|
||||||
4));
|
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_ = QImage();
|
||||||
software_image_up_to_date_ = true;
|
software_image_up_to_date_ = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorTransformJob job;
|
oak_color_transform_job job = {};
|
||||||
oakengine_color_transform_job_set_processor(&job, color_service().get());
|
job.processor = color_service().get();
|
||||||
job.set_input_texture(source_tex);
|
job.input_texture = source_tex;
|
||||||
job.set_input_alpha_association(k_alpha_none);
|
job.input_alpha_association = 0; // k_alpha_none
|
||||||
job.set_clear_destination_enabled(true);
|
job.clear_destination = 1;
|
||||||
job.set_force_opaque(true);
|
job.force_opaque = 1;
|
||||||
|
|
||||||
oakengine_display_renderer_blit_color_managed(renderer(), &job,
|
oakengine_display_renderer_blit_color_managed(renderer(), &job,
|
||||||
software_tex_.get(), nullptr);
|
software_tex_, nullptr);
|
||||||
renderer()->download_from_texture(software_tex_->id(),
|
|
||||||
software_tex_->params(),
|
int sw_id = oakengine_display_texture_id(software_tex_);
|
||||||
software_buffer_.data(), 0);
|
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(
|
software_image_ = QImage(
|
||||||
reinterpret_cast<const uchar *>(software_buffer_.constData()),
|
reinterpret_cast<const uchar *>(software_buffer_.constData()),
|
||||||
texture_width, texture_height,
|
texture_width, texture_height,
|
||||||
texture_width * oakengine_video_params_bytes_per_pixel(
|
texture_width * oakengine_video_params_bytes_per_pixel(0, 4),
|
||||||
PixelFormat::u8, 4),
|
|
||||||
QImage::Format_RGBA8888_Premultiplied);
|
QImage::Format_RGBA8888_Premultiplied);
|
||||||
software_image_.setDevicePixelRatio(devicePixelRatioF());
|
software_image_.setDevicePixelRatio(devicePixelRatioF());
|
||||||
|
|
||||||
@@ -156,7 +177,11 @@ void ScopeBase::on_init()
|
|||||||
super::on_init();
|
super::on_init();
|
||||||
|
|
||||||
if (!is_backend_neutral()) {
|
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
|
// Clear display surface
|
||||||
renderer()->clear_destination();
|
oakengine_display_renderer_clear(renderer(), 0.0, 0.0, 0.0);
|
||||||
|
|
||||||
if (texture_) {
|
if (texture_) {
|
||||||
// Convert reference frame to display space
|
// Convert reference frame to display space
|
||||||
if (!managed_tex_ || !managed_tex_up_to_date_ ||
|
bool need_recreate = false;
|
||||||
managed_tex_->params() != texture_->params()) {
|
if (!managed_tex_ || !managed_tex_up_to_date_) {
|
||||||
oakengine_display_renderer_create_texture(
|
need_recreate = true;
|
||||||
renderer(), &texture_->params(), nullptr, 0, &managed_tex_);
|
} else if (!oakengine_display_texture_params_equal(managed_tex_,
|
||||||
|
texture_)) {
|
||||||
|
need_recreate = true;
|
||||||
|
}
|
||||||
|
|
||||||
ColorTransformJob job;
|
if (need_recreate) {
|
||||||
oakengine_color_transform_job_set_processor(&job, color_service().get());
|
if (managed_tex_) {
|
||||||
job.set_input_texture(texture_);
|
oakengine_display_texture_free(managed_tex_);
|
||||||
job.set_input_alpha_association(k_alpha_none);
|
}
|
||||||
|
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(
|
oakengine_display_renderer_blit_color_managed(
|
||||||
renderer(), &job, managed_tex_.get(), nullptr);
|
renderer(), &job, managed_tex_, nullptr);
|
||||||
managed_tex_up_to_date_ = true;
|
managed_tex_up_to_date_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,13 +241,28 @@ void ScopeBase::on_paint()
|
|||||||
|
|
||||||
void ScopeBase::on_destroy()
|
void ScopeBase::on_destroy()
|
||||||
{
|
{
|
||||||
local_texture_ = nullptr;
|
if (local_texture_) {
|
||||||
software_tex_ = nullptr;
|
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_buffer_.clear();
|
||||||
software_image_ = QImage();
|
software_image_ = QImage();
|
||||||
managed_tex_ = nullptr;
|
if (managed_tex_) {
|
||||||
texture_ = nullptr;
|
oakengine_display_texture_free(managed_tex_);
|
||||||
pipeline_.clear();
|
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();
|
super::on_destroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,19 @@
|
|||||||
#ifndef OAK_SCOPEBASE_H
|
#ifndef OAK_SCOPEBASE_H
|
||||||
#define OAK_SCOPEBASE_H
|
#define OAK_SCOPEBASE_H
|
||||||
|
|
||||||
#include "codec/frame.h"
|
#include <QString>
|
||||||
#include "render/colorprocessor.h"
|
|
||||||
#include "widget/manageddisplay/manageddisplay.h"
|
#include "widget/manageddisplay/manageddisplay.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @brief Fragment + vertex shader source pair returned by scope subclasses. */
|
||||||
|
struct ScopeShaderCode {
|
||||||
|
QString frag;
|
||||||
|
QString vert;
|
||||||
|
};
|
||||||
|
|
||||||
class ScopeBase : public ManagedDisplayWidget {
|
class ScopeBase : public ManagedDisplayWidget {
|
||||||
public:
|
public:
|
||||||
ScopeBase(QWidget *parent = nullptr);
|
ScopeBase(QWidget *parent = nullptr);
|
||||||
@@ -36,7 +42,7 @@ public:
|
|||||||
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(ScopeBase)
|
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(ScopeBase)
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void set_buffer(TexturePtr frame);
|
void set_buffer(void *frame);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
virtual void on_init() override;
|
virtual void on_init() override;
|
||||||
@@ -48,14 +54,14 @@ protected slots:
|
|||||||
protected:
|
protected:
|
||||||
virtual void showEvent(QShowEvent *e) override;
|
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.
|
* @brief GPU-accelerated draw function used on OpenGL backends.
|
||||||
*
|
*
|
||||||
* Override this if your sub-class scope needs extra drawing.
|
* 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).
|
* @brief Software draw function used on backend-neutral paths (e.g. Vulkan).
|
||||||
@@ -68,20 +74,20 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void update_software_image();
|
void update_software_image();
|
||||||
|
|
||||||
QVariant pipeline_;
|
void *pipeline_;
|
||||||
|
|
||||||
TexturePtr texture_;
|
void *texture_;
|
||||||
|
|
||||||
TexturePtr managed_tex_;
|
void *managed_tex_;
|
||||||
|
|
||||||
bool managed_tex_up_to_date_;
|
bool managed_tex_up_to_date_;
|
||||||
|
|
||||||
TexturePtr software_tex_;
|
void *software_tex_;
|
||||||
QByteArray software_buffer_;
|
QByteArray software_buffer_;
|
||||||
QImage software_image_;
|
QImage software_image_;
|
||||||
bool software_image_up_to_date_;
|
bool software_image_up_to_date_;
|
||||||
|
|
||||||
TexturePtr local_texture_;
|
void *local_texture_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,11 @@
|
|||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
#include <QVector2D>
|
|
||||||
#include <QVector3D>
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "node/node.h"
|
#include "common/filefunctions.h"
|
||||||
|
#include "oakengine/color.h"
|
||||||
|
#include "oakengine/display.h"
|
||||||
|
|
||||||
namespace olive
|
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.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_scale = 0.80f;
|
||||||
float vectorscope_gain = 1.45f;
|
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_intensity = 0.035f;
|
||||||
float vectorscope_sample_grid = 28.0f;
|
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 };
|
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
|
||||||
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
|
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"),
|
oak_shader_uniform uniforms[7];
|
||||||
NodeValue(NodeValue::k_float, vectorscope_scale));
|
uniforms[0] = {"viewport", 1,
|
||||||
job.insert(QStringLiteral("vectorscope_gain"),
|
{static_cast<float>(width()), static_cast<float>(height()),
|
||||||
NodeValue(NodeValue::k_float, vectorscope_gain));
|
0.0f, 0.0f}};
|
||||||
job.insert(QStringLiteral("vectorscope_point_radius"),
|
uniforms[1] = {"luma_coeffs", 3,
|
||||||
NodeValue(NodeValue::k_float, vectorscope_point_radius));
|
{static_cast<float>(luma_coeffs[0]),
|
||||||
job.insert(QStringLiteral("vectorscope_intensity"),
|
static_cast<float>(luma_coeffs[1]),
|
||||||
NodeValue(NodeValue::k_float, vectorscope_intensity));
|
static_cast<float>(luma_coeffs[2]), 0.0f}};
|
||||||
job.insert(QStringLiteral("vectorscope_sample_grid"),
|
uniforms[2] = {"vectorscope_scale", 0,
|
||||||
NodeValue(NodeValue::k_float, vectorscope_sample_grid));
|
{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"),
|
oak_video_params vp = get_viewport_params();
|
||||||
NodeValue(NodeValue::k_texture,
|
oakengine_display_renderer_blit_shader_uniforms(
|
||||||
QVariant::fromValue(managed_tex)));
|
renderer(), pipeline, managed_tex, uniforms, 7, nullptr, &vp);
|
||||||
|
|
||||||
renderer()->blit(pipeline, job, get_viewport_params());
|
|
||||||
|
|
||||||
QPainter p(paint_device());
|
QPainter p(paint_device());
|
||||||
QFont font = p.font();
|
QFont font = p.font();
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ public:
|
|||||||
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(VectorscopeScope)
|
MANAGEDDISPLAYWIDGET_DEFAULT_DESTRUCTOR(VectorscopeScope)
|
||||||
|
|
||||||
protected:
|
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;
|
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,13 +26,12 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QtMath>
|
#include <QtMath>
|
||||||
#include <QDebug>
|
|
||||||
#include <QVector2D>
|
|
||||||
#include <QVector3D>
|
|
||||||
|
|
||||||
#include "common/qtutils.h"
|
#include "common/qtutils.h"
|
||||||
#include "common/configwrapper.h"
|
#include "common/configwrapper.h"
|
||||||
#include "node/node.h"
|
#include "common/filefunctions.h"
|
||||||
|
#include "oakengine/color.h"
|
||||||
|
#include "oakengine/display.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -62,46 +61,36 @@ void WaveformScope::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
menu.exec(event->globalPos());
|
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.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;
|
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 };
|
double luma_coeffs[3] = { 0.0f, 0.0f, 0.0f };
|
||||||
oakengine_color_manager_default_luma_coefs(color_manager(), luma_coeffs);
|
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.
|
// Set up uniforms
|
||||||
job.insert(QStringLiteral("waveform_scale"),
|
oak_shader_uniform uniforms[4];
|
||||||
NodeValue(NodeValue::k_float, waveform_scale));
|
uniforms[0] = {"viewport", 1,
|
||||||
|
{static_cast<float>(width()), static_cast<float>(height()),
|
||||||
|
0.0f, 0.0f}};
|
||||||
|
uniforms[1] = {"luma_coeffs", 3,
|
||||||
|
{static_cast<float>(luma_coeffs[0]),
|
||||||
|
static_cast<float>(luma_coeffs[1]),
|
||||||
|
static_cast<float>(luma_coeffs[2]), 0.0f}};
|
||||||
|
uniforms[2] = {"waveform_scale", 0, {waveform_scale, 0.0f, 0.0f, 0.0f}};
|
||||||
|
uniforms[3] = {"parade_mode", 0,
|
||||||
|
{parade_mode_ ? 1.0f : 0.0f, 0.0f, 0.0f, 0.0f}};
|
||||||
|
|
||||||
// Overlay vs. RGB parade display
|
oak_video_params vp = get_viewport_params();
|
||||||
job.insert(QStringLiteral("parade_mode"),
|
oakengine_display_renderer_blit_shader_uniforms(
|
||||||
NodeValue(NodeValue::k_float, parade_mode_ ? 1.0f : 0.0f));
|
renderer(), pipeline, managed_tex, uniforms, 4, nullptr, &vp);
|
||||||
|
|
||||||
// Insert source texture
|
|
||||||
job.insert(QStringLiteral("ove_maintex"),
|
|
||||||
NodeValue(NodeValue::k_texture,
|
|
||||||
QVariant::fromValue(managed_tex)));
|
|
||||||
|
|
||||||
renderer()->blit(pipeline, job, get_viewport_params());
|
|
||||||
|
|
||||||
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
|
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
|
||||||
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
|
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ public:
|
|||||||
void set_parade_mode(bool enabled);
|
void set_parade_mode(bool enabled);
|
||||||
|
|
||||||
protected:
|
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;
|
virtual void draw_scope_software(QPainter &p, const QImage &image) override;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Oak - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2026 Oak Team
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef OAK_DISPLAYBUFFER_H
|
||||||
|
#define OAK_DISPLAYBUFFER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
|
#include "oakengine/display.h"
|
||||||
|
|
||||||
|
namespace olive
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refcounted wrapper around an opaque display handle (texture or frame).
|
||||||
|
*
|
||||||
|
* Stored inside QVariant for playback queue / set_image() path. The deleter
|
||||||
|
* calls the matching facade free function when the last shared_ptr copy dies.
|
||||||
|
*/
|
||||||
|
struct OakSharedBuffer {
|
||||||
|
void *handle = nullptr;
|
||||||
|
enum Type { k_texture, k_frame } type = k_frame;
|
||||||
|
};
|
||||||
|
|
||||||
|
using OakSharedBufferPtr = std::shared_ptr<OakSharedBuffer>;
|
||||||
|
|
||||||
|
inline OakSharedBufferPtr oak_make_shared_texture(void *handle)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<OakSharedBuffer>(
|
||||||
|
new OakSharedBuffer{handle, OakSharedBuffer::k_texture},
|
||||||
|
[](OakSharedBuffer *b) {
|
||||||
|
oakengine_display_texture_free(b->handle);
|
||||||
|
delete b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline OakSharedBufferPtr oak_make_shared_frame(void *handle)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<OakSharedBuffer>(
|
||||||
|
new OakSharedBuffer{handle, OakSharedBuffer::k_frame},
|
||||||
|
[](OakSharedBuffer *b) {
|
||||||
|
oakengine_codec_frame_free(b->handle);
|
||||||
|
delete b;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace olive
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(olive::OakSharedBufferPtr)
|
||||||
|
|
||||||
|
#endif // OAK_DISPLAYBUFFER_H
|
||||||
@@ -44,6 +44,7 @@
|
|||||||
#include "node/block/gap/gap.h"
|
#include "node/block/gap/gap.h"
|
||||||
#include "oakengine/encoding.h"
|
#include "oakengine/encoding.h"
|
||||||
#include "oakengine/display.h"
|
#include "oakengine/display.h"
|
||||||
|
#include "widget/viewer/displaybuffer.h"
|
||||||
#include "oakengine/viewer.h"
|
#include "oakengine/viewer.h"
|
||||||
#include "oakengine/videoparams.h"
|
#include "oakengine/videoparams.h"
|
||||||
#include "node/generator/shape/shapenodebase.h"
|
#include "node/generator/shape/shapenodebase.h"
|
||||||
@@ -570,7 +571,9 @@ void ViewerWidget::set_full_screen(QScreen *screen)
|
|||||||
}
|
}
|
||||||
|
|
||||||
vw->display_widget()->set_image(
|
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());
|
playback_devices_.append(vw->display_widget());
|
||||||
|
|
||||||
@@ -1472,41 +1475,39 @@ void ViewerWidget::set_display_image(OakEnginePreviewRequest *req)
|
|||||||
if (dynamic_cast<MulticamDisplay *>(dw)) {
|
if (dynamic_cast<MulticamDisplay *>(dw)) {
|
||||||
// Multicam: use the default frame for now
|
// Multicam: use the default frame for now
|
||||||
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
|
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
|
||||||
FramePtr f;
|
void *f = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_create(&f);
|
|
||||||
{
|
{
|
||||||
oak_video_params pod = {};
|
oak_video_params pod = {};
|
||||||
pod.width = frame.width;
|
pod.width = frame.width;
|
||||||
pod.height = frame.height;
|
pod.height = frame.height;
|
||||||
pod.format = static_cast<PixelFormat::Format>(frame.format);
|
pod.format = frame.format;
|
||||||
const VideoParams vp = video_params_from_pod(pod);
|
oakengine_codec_frame_set_video_params(f, &pod);
|
||||||
oakengine_codec_frame_set_video_params(f.get(), &vp);
|
|
||||||
}
|
}
|
||||||
oakengine_codec_frame_allocate(f.get());
|
oakengine_codec_frame_allocate(f);
|
||||||
if (frame.data && frame.linesize > 0 && f->linesize_bytes() > 0) {
|
int fls = oakengine_codec_frame_linesize_bytes(f);
|
||||||
memcpy(f->data(), frame.data,
|
if (frame.data && frame.linesize > 0 && fls > 0) {
|
||||||
qMin(f->linesize_bytes(), frame.linesize) * frame.height);
|
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 {
|
} else {
|
||||||
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
|
if (oakengine_preview_request_get_frame(req, &frame) == 0) {
|
||||||
FramePtr f;
|
void *f = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_create(&f);
|
|
||||||
{
|
{
|
||||||
oak_video_params pod = {};
|
oak_video_params pod = {};
|
||||||
pod.width = frame.width;
|
pod.width = frame.width;
|
||||||
pod.height = frame.height;
|
pod.height = frame.height;
|
||||||
pod.format = static_cast<PixelFormat::Format>(frame.format);
|
pod.format = frame.format;
|
||||||
const VideoParams vp = video_params_from_pod(pod);
|
oakengine_codec_frame_set_video_params(f, &pod);
|
||||||
oakengine_codec_frame_set_video_params(f.get(), &vp);
|
|
||||||
}
|
}
|
||||||
oakengine_codec_frame_allocate(f.get());
|
oakengine_codec_frame_allocate(f);
|
||||||
if (frame.data && frame.linesize > 0 && f->linesize_bytes() > 0) {
|
int fls = oakengine_codec_frame_linesize_bytes(f);
|
||||||
memcpy(f->data(), frame.data,
|
if (frame.data && frame.linesize > 0 && fls > 0) {
|
||||||
qMin(f->linesize_bytes(), frame.linesize) * frame.height);
|
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
|
// Ignore this signal if we've paused now
|
||||||
if (is_playing() || prequeuing_video_) {
|
if (is_playing() || prequeuing_video_) {
|
||||||
if (!drop_frame && has_frame) {
|
if (!drop_frame && has_frame) {
|
||||||
FramePtr f;
|
void *f = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_create(&f);
|
|
||||||
{
|
{
|
||||||
oak_video_params pod = {};
|
oak_video_params pod = {};
|
||||||
pod.width = pf.width;
|
pod.width = pf.width;
|
||||||
pod.height = pf.height;
|
pod.height = pf.height;
|
||||||
pod.format = static_cast<PixelFormat::Format>(pf.format);
|
pod.format = pf.format;
|
||||||
const VideoParams vp = video_params_from_pod(pod);
|
oakengine_codec_frame_set_video_params(f, &pod);
|
||||||
oakengine_codec_frame_set_video_params(f.get(), &vp);
|
|
||||||
}
|
}
|
||||||
oakengine_codec_frame_allocate(f.get());
|
oakengine_codec_frame_allocate(f);
|
||||||
if (pf.data && pf.linesize > 0 && f->linesize_bytes() > 0) {
|
int fls = oakengine_codec_frame_linesize_bytes(f);
|
||||||
memcpy(f->data(), pf.data,
|
if (pf.data && pf.linesize > 0 && fls > 0) {
|
||||||
qMin(f->linesize_bytes(), pf.linesize) * pf.height);
|
memcpy(oakengine_codec_frame_data(f), pf.data,
|
||||||
|
qMin(fls, pf.linesize) * pf.height);
|
||||||
}
|
}
|
||||||
QVariant frame = QVariant::fromValue(f);
|
QVariant frame = QVariant::fromValue(f);
|
||||||
|
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ signals:
|
|||||||
/**
|
/**
|
||||||
* @brief Signal emitted when a new frame is loaded
|
* @brief Signal emitted when a new frame is loaded
|
||||||
*/
|
*/
|
||||||
void texture_changed(TexturePtr t);
|
void texture_changed(void *t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
|
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
|
||||||
|
|||||||
+207
-170
@@ -35,6 +35,8 @@
|
|||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "audio/audiomanager.h"
|
#include "audio/audiomanager.h"
|
||||||
#include "common/define.h"
|
#include "common/define.h"
|
||||||
#include "common/html.h"
|
#include "common/html.h"
|
||||||
@@ -47,12 +49,10 @@
|
|||||||
#include "common/configwrapper.h"
|
#include "common/configwrapper.h"
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "node/block/subtitle/subtitle.h"
|
#include "node/block/subtitle/subtitle.h"
|
||||||
#include "codec/frame.h"
|
|
||||||
#include "node/gizmo/path.h"
|
#include "node/gizmo/path.h"
|
||||||
#include "node/gizmo/point.h"
|
#include "node/gizmo/point.h"
|
||||||
#include "node/gizmo/polygon.h"
|
#include "node/gizmo/polygon.h"
|
||||||
#include "node/gizmo/screen.h"
|
#include "node/gizmo/screen.h"
|
||||||
#include "render/job/colortransformjob.h"
|
|
||||||
#include "window/mainwindow/mainwindow.h"
|
#include "window/mainwindow/mainwindow.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
@@ -62,7 +62,14 @@ namespace olive
|
|||||||
|
|
||||||
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
|
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
|
||||||
: super(parent)
|
: super(parent)
|
||||||
|
, texture_(nullptr)
|
||||||
, deinterlace_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)
|
, signal_cursor_color_(false)
|
||||||
, gizmos_(nullptr)
|
, gizmos_(nullptr)
|
||||||
, current_gizmo_(nullptr)
|
, current_gizmo_(nullptr)
|
||||||
@@ -172,11 +179,14 @@ void ViewerDisplayWidget::set_deinterlacing(bool e)
|
|||||||
deinterlace_ = e;
|
deinterlace_ = e;
|
||||||
|
|
||||||
if (!deinterlace_) {
|
if (!deinterlace_) {
|
||||||
if (!deinterlace_shader_.isNull()) {
|
if (deinterlace_shader_) {
|
||||||
renderer()->destroy_native_shader(deinterlace_shader_);
|
oakengine_display_renderer_destroy_shader(renderer(), deinterlace_shader_);
|
||||||
deinterlace_shader_.clear();
|
deinterlace_shader_ = nullptr;
|
||||||
|
}
|
||||||
|
if (deinterlace_texture_) {
|
||||||
|
oakengine_display_texture_free(deinterlace_texture_);
|
||||||
|
deinterlace_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
deinterlace_texture_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
@@ -405,12 +415,12 @@ void ViewerDisplayWidget::on_paint()
|
|||||||
// Clear background to empty
|
// Clear background to empty
|
||||||
QColor bg_color = show_widget_background_ ? palette().window().color() :
|
QColor bg_color = show_widget_background_ ? palette().window().color() :
|
||||||
Qt::black;
|
Qt::black;
|
||||||
renderer()->clear_destination(nullptr, bg_color.redF(),
|
oakengine_display_renderer_clear(renderer(), bg_color.redF(),
|
||||||
bg_color.greenF(), bg_color.blueF());
|
bg_color.greenF(), bg_color.blueF());
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoParams device_params = empty_video_params();
|
oak_video_params device_params = {};
|
||||||
ColorTransformJob ctj;
|
oak_color_transform_job ctj = {};
|
||||||
bool have_ctj = false;
|
bool have_ctj = false;
|
||||||
|
|
||||||
// We only draw if we have a pipeline
|
// We only draw if we have a pipeline
|
||||||
@@ -424,52 +434,65 @@ void ViewerDisplayWidget::on_paint()
|
|||||||
}
|
}
|
||||||
} else if (color_service()) {
|
} else if (color_service()) {
|
||||||
bool drew_backend_neutral_frame = false;
|
bool drew_backend_neutral_frame = false;
|
||||||
if (FramePtr frame = load_frame_.value<FramePtr>()) {
|
|
||||||
|
// Extract handle from QVariant (stored as OakSharedBufferPtr)
|
||||||
|
OakSharedBufferPtr buf = load_frame_.value<OakSharedBufferPtr>();
|
||||||
|
void *load_handle = buf ? buf->handle : nullptr;
|
||||||
|
int load_type = buf ? static_cast<int>(buf->type) : -1;
|
||||||
|
|
||||||
|
if (load_handle && load_type == OakSharedBuffer::k_frame) {
|
||||||
|
// CPU frame path: upload to GPU texture
|
||||||
|
oak_video_params frame_vp = {};
|
||||||
|
oakengine_codec_frame_get_params(load_handle, &frame_vp);
|
||||||
if (!drew_backend_neutral_frame &&
|
if (!drew_backend_neutral_frame &&
|
||||||
(!texture_ ||
|
(!texture_ ||
|
||||||
texture_->renderer() !=
|
oakengine_display_texture_renderer(texture_) !=
|
||||||
renderer() // Some implementations don't like it if we upload to a texture created in another (albeit shared) context
|
renderer() ||
|
||||||
|| texture_->width() != frame->width() ||
|
oakengine_display_texture_width(texture_) != frame_vp.width ||
|
||||||
texture_->height() != frame->height() ||
|
oakengine_display_texture_height(texture_) != frame_vp.height ||
|
||||||
texture_->format() != frame->format() ||
|
oakengine_display_texture_format(texture_) != frame_vp.format ||
|
||||||
texture_->channel_count() != frame->channel_count())) {
|
oakengine_display_texture_channel_count(texture_) != 4)) {
|
||||||
oakengine_display_renderer_create_texture(
|
texture_ = oakengine_display_texture_create(
|
||||||
renderer(), &frame->video_params(), frame->data(),
|
renderer(), &frame_vp,
|
||||||
frame->linesize_pixels(), &texture_);
|
oakengine_codec_frame_data(load_handle),
|
||||||
|
oakengine_codec_frame_linesize(load_handle));
|
||||||
} else if (!drew_backend_neutral_frame) {
|
} else if (!drew_backend_neutral_frame) {
|
||||||
oakengine_display_texture_upload(texture_.get(), frame->data(),
|
oakengine_display_texture_upload(
|
||||||
frame->linesize_pixels());
|
texture_, oakengine_codec_frame_data(load_handle),
|
||||||
|
oakengine_codec_frame_linesize(load_handle));
|
||||||
}
|
}
|
||||||
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
|
} else if (load_handle && load_type == OakSharedBuffer::k_texture) {
|
||||||
// This is a GPU texture, switch to it directly when possible.
|
// GPU texture path
|
||||||
if (!drew_backend_neutral_frame && texture &&
|
void *src_ren = oakengine_display_texture_renderer(load_handle);
|
||||||
texture->renderer() && texture->renderer() != renderer()) {
|
if (!drew_backend_neutral_frame && load_handle &&
|
||||||
if (texture->renderer()->is_open_gl() &&
|
src_ren && src_ren != renderer()) {
|
||||||
renderer()->is_open_gl()) {
|
if (oakengine_display_renderer_is_open_gl(src_ren) &&
|
||||||
// Shared OpenGL contexts can display the producer texture
|
oakengine_display_renderer_is_open_gl(renderer())) {
|
||||||
// directly. Avoid readback here because the producer
|
texture_ = load_handle;
|
||||||
// renderer may belong to a render thread whose context
|
|
||||||
// cannot be made current from the GUI paint callback.
|
|
||||||
texture_ = texture;
|
|
||||||
} else {
|
} else {
|
||||||
// Cross-backend texture: download and re-upload
|
// Cross-backend: download and re-upload
|
||||||
FramePtr frame;
|
void *tmp_frame = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_create(&frame);
|
oak_video_params tex_params = {};
|
||||||
oakengine_codec_frame_set_video_params(
|
oakengine_display_texture_get_params(load_handle, &tex_params);
|
||||||
frame.get(), &texture->params());
|
oakengine_codec_frame_set_video_params(tmp_frame, &tex_params);
|
||||||
if (oakengine_codec_frame_allocate(frame.get())) {
|
if (oakengine_codec_frame_allocate(tmp_frame)) {
|
||||||
texture->renderer()->download_from_texture(
|
oakengine_display_renderer_download_from_texture(
|
||||||
texture->id(), texture->params(), frame->data(),
|
src_ren,
|
||||||
frame->linesize_pixels());
|
oakengine_display_texture_id(load_handle),
|
||||||
oakengine_display_renderer_create_texture(
|
&tex_params,
|
||||||
renderer(), &frame->video_params(), frame->data(),
|
oakengine_codec_frame_data(tmp_frame),
|
||||||
frame->linesize_pixels(), &texture_);
|
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 {
|
} else {
|
||||||
texture_ = texture;
|
texture_ = load_handle;
|
||||||
}
|
}
|
||||||
|
oakengine_codec_frame_free(tmp_frame);
|
||||||
}
|
}
|
||||||
} else if (!drew_backend_neutral_frame) {
|
} else if (!drew_backend_neutral_frame) {
|
||||||
texture_ = texture;
|
texture_ = load_handle;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
texture_ = load_custom_texture_from_frame(load_frame_);
|
texture_ = load_custom_texture_from_frame(load_frame_);
|
||||||
@@ -484,59 +507,58 @@ void ViewerDisplayWidget::on_paint()
|
|||||||
push_mode_ = k_push_unnecessary;
|
push_mode_ = k_push_unnecessary;
|
||||||
|
|
||||||
if (!drew_backend_neutral_frame) {
|
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) {
|
if (!backend_neutral) {
|
||||||
draw_blank(device_params);
|
draw_blank(device_params);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (deinterlace_) {
|
if (deinterlace_) {
|
||||||
if (deinterlace_shader_.isNull()) {
|
if (!deinterlace_shader_) {
|
||||||
|
QString src = FileFunctions::read_file_as_string(
|
||||||
|
QStringLiteral(":/shaders/deinterlace.frag"));
|
||||||
deinterlace_shader_ =
|
deinterlace_shader_ =
|
||||||
renderer()->create_native_shader(
|
oakengine_display_renderer_create_shader(
|
||||||
ShaderCode(FileFunctions::read_file_as_string(
|
renderer(), src.toUtf8().constData(), nullptr);
|
||||||
QStringLiteral(
|
|
||||||
":/shaders/deinterlace.frag"))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!deinterlace_texture_ ||
|
if (!deinterlace_texture_ ||
|
||||||
deinterlace_texture_->params() !=
|
!oakengine_display_texture_params_equal(
|
||||||
texture_to_draw->params()) {
|
deinterlace_texture_, texture_to_draw)) {
|
||||||
// (Re)create texture
|
if (deinterlace_texture_) {
|
||||||
oakengine_display_renderer_create_texture(
|
oakengine_display_texture_free(deinterlace_texture_);
|
||||||
renderer(), &texture_to_draw->params(), nullptr,
|
}
|
||||||
0, &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;
|
oakengine_display_renderer_blit_shader_vec2_to_texture(
|
||||||
job.insert(
|
renderer(), deinterlace_shader_, texture_to_draw,
|
||||||
QStringLiteral("resolution_in"),
|
"resolution_in",
|
||||||
NodeValue(NodeValue::k_vec2,
|
static_cast<float>(oakengine_display_texture_width(texture_to_draw)),
|
||||||
QVector2D(texture_to_draw->width(),
|
static_cast<float>(oakengine_display_texture_height(texture_to_draw)),
|
||||||
texture_to_draw->height())));
|
deinterlace_texture_);
|
||||||
job.insert(
|
|
||||||
QStringLiteral("ove_maintex"),
|
|
||||||
NodeValue(NodeValue::k_texture,
|
|
||||||
QVariant::fromValue(texture_to_draw)));
|
|
||||||
|
|
||||||
renderer()->blit_to_texture(deinterlace_shader_, job,
|
|
||||||
deinterlace_texture_.get());
|
|
||||||
|
|
||||||
texture_to_draw = deinterlace_texture_;
|
texture_to_draw = deinterlace_texture_;
|
||||||
}
|
}
|
||||||
|
|
||||||
oakengine_color_transform_job_set_processor(
|
// Build color transform job POD
|
||||||
&ctj, color_service().get());
|
ctj.processor = color_service().get();
|
||||||
ctj.set_input_texture(texture_to_draw);
|
ctj.input_texture = texture_to_draw;
|
||||||
ctj.set_input_alpha_association(
|
ctj.input_alpha_association =
|
||||||
OAK_CONFIG("ReassocLinToNonLin").toBool() ?
|
OAK_CONFIG("ReassocLinToNonLin").toBool() ? 1 : 0;
|
||||||
k_alpha_associated :
|
ctj.clear_destination = 0;
|
||||||
k_alpha_none);
|
ctj.force_opaque = 1;
|
||||||
ctj.set_clear_destination_enabled(false);
|
memcpy(ctj.matrix, combined_matrix_flipped_.constData(),
|
||||||
ctj.set_transform_matrix(combined_matrix_flipped_);
|
16 * sizeof(float));
|
||||||
ctj.set_crop_matrix(crop_matrix_);
|
memcpy(ctj.crop_matrix, crop_matrix_.constData(),
|
||||||
ctj.set_force_opaque(true);
|
16 * sizeof(float));
|
||||||
|
|
||||||
have_ctj = true;
|
have_ctj = true;
|
||||||
}
|
}
|
||||||
@@ -689,25 +711,31 @@ void ViewerDisplayWidget::on_paint()
|
|||||||
|
|
||||||
void ViewerDisplayWidget::on_destroy()
|
void ViewerDisplayWidget::on_destroy()
|
||||||
{
|
{
|
||||||
if (!deinterlace_shader_.isNull()) {
|
if (deinterlace_shader_) {
|
||||||
renderer()->destroy_native_shader(deinterlace_shader_);
|
oakengine_display_renderer_destroy_shader(renderer(), deinterlace_shader_);
|
||||||
deinterlace_shader_.clear();
|
deinterlace_shader_ = nullptr;
|
||||||
}
|
}
|
||||||
if (!blank_shader_.isNull()) {
|
if (blank_shader_) {
|
||||||
renderer()->destroy_native_shader(blank_shader_);
|
oakengine_display_renderer_destroy_shader(renderer(), blank_shader_);
|
||||||
blank_shader_.clear();
|
blank_shader_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
super::on_destroy();
|
super::on_destroy();
|
||||||
|
|
||||||
texture_ = nullptr;
|
texture_ = nullptr;
|
||||||
deinterlace_texture_ = nullptr;
|
if (deinterlace_texture_) {
|
||||||
backend_neutral_texture_ = nullptr;
|
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_buffer_.clear();
|
||||||
backend_neutral_cpu_image_ = QImage();
|
backend_neutral_cpu_image_ = QImage();
|
||||||
backend_neutral_cpu_display_frame_.reset();
|
backend_neutral_cpu_display_frame_ = nullptr;
|
||||||
backend_neutral_cpu_source_frame_.reset();
|
backend_neutral_cpu_source_frame_ = nullptr;
|
||||||
backend_neutral_cpu_source_texture_.reset();
|
backend_neutral_cpu_source_texture_ = nullptr;
|
||||||
backend_neutral_cpu_color_id_.clear();
|
backend_neutral_cpu_color_id_.clear();
|
||||||
if (load_frame_.isNull()) {
|
if (load_frame_.isNull()) {
|
||||||
push_mode_ = k_push_null;
|
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,
|
// 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
|
// so the same flip would invert the image. Default to the OpenGL flip when
|
||||||
// no renderer is available yet.
|
// no renderer is available yet.
|
||||||
if (!renderer() || !renderer()->is_vulkan()) {
|
if (!renderer() || !oakengine_display_renderer_is_vulkan(renderer())) {
|
||||||
QMatrix4x4 flip;
|
QMatrix4x4 flip;
|
||||||
flip.scale(1.0f, -1.0f, 1.0f);
|
flip.scale(1.0f, -1.0f, 1.0f);
|
||||||
combined_matrix_flipped_ = flip * combined_matrix_flipped_;
|
combined_matrix_flipped_ = flip * combined_matrix_flipped_;
|
||||||
@@ -1200,12 +1228,18 @@ void ViewerDisplayWidget::emit_color_at_cursor(QMouseEvent *e)
|
|||||||
if (texture_) {
|
if (texture_) {
|
||||||
QPointF pixel_pos =
|
QPointF pixel_pos =
|
||||||
generate_display_transform().inverted().map(e->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();
|
make_current();
|
||||||
|
|
||||||
reference =
|
double rgba[4] = {};
|
||||||
renderer()->get_pixel_from_texture(texture_.get(), pixel_pos);
|
oakengine_display_renderer_get_pixel(
|
||||||
|
renderer(), texture_,
|
||||||
|
static_cast<int>(pixel_pos.x()),
|
||||||
|
static_cast<int>(pixel_pos.y()), rgba);
|
||||||
|
reference = Color(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||||
if (color_service()) {
|
if (color_service()) {
|
||||||
display = oak_convert_color(color_service(), reference);
|
display = oak_convert_color(color_service(), reference);
|
||||||
} else {
|
} else {
|
||||||
@@ -1455,33 +1489,31 @@ void ViewerDisplayWidget::generate_gizmo_transforms()
|
|||||||
gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted();
|
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()) {
|
if (!blank_shader_) {
|
||||||
blank_shader_ = renderer()->create_native_shader(ShaderCode());
|
blank_shader_ = oakengine_display_renderer_create_blank_shader(renderer());
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderJob job;
|
oakengine_display_renderer_blit_blank(
|
||||||
job.insert(QStringLiteral("ove_mvpmat"),
|
renderer(), blank_shader_,
|
||||||
NodeValue(NodeValue::k_matrix, combined_matrix_flipped_));
|
combined_matrix_flipped_.constData(),
|
||||||
job.insert(QStringLiteral("ove_cropmatrix"),
|
crop_matrix_.constData(),
|
||||||
NodeValue(NodeValue::k_matrix, crop_matrix_));
|
&device_params);
|
||||||
|
|
||||||
renderer()->blit(blank_shader_, job, device_params, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
|
bool ViewerDisplayWidget::draw_backend_neutral_frame(void *frame,
|
||||||
QPainter *painter)
|
QPainter *painter)
|
||||||
{
|
{
|
||||||
if (!frame || !frame->is_allocated() || !painter || !painter->isActive() ||
|
if (!frame || !oakengine_codec_frame_is_allocated(frame) || !painter ||
|
||||||
!color_service()) {
|
!painter->isActive() || !color_service()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString color_id = oak_query_string([this](char *buf, int size) {
|
const QString color_id = oak_query_string([this](char *buf, int size) {
|
||||||
return oakengine_color_processor_id(color_service().get(), buf, 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_color_id_ == color_id &&
|
||||||
!backend_neutral_cpu_image_.isNull()) {
|
!backend_neutral_cpu_image_.isNull()) {
|
||||||
painter->save();
|
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
|
// not safe to apply on this GUI path and a crash here kills preview. Worker
|
||||||
// frames tagged with display:<processor-id> have already been color managed;
|
// frames tagged with display:<processor-id> have already been color managed;
|
||||||
// untagged frames are drawn directly as a safe fallback.
|
// untagged frames are drawn directly as a safe fallback.
|
||||||
FramePtr display_frame = frame;
|
const int frame_fmt = oakengine_codec_frame_format(frame);
|
||||||
|
const int frame_ch = oakengine_codec_frame_channel_count(frame);
|
||||||
|
const int frame_w = oakengine_codec_frame_width(frame);
|
||||||
|
const int frame_h = oakengine_codec_frame_height(frame);
|
||||||
|
const int frame_ls = oakengine_codec_frame_linesize_bytes(frame);
|
||||||
|
const char *frame_data =
|
||||||
|
reinterpret_cast<const char *>(oakengine_codec_frame_const_data(frame));
|
||||||
|
|
||||||
QImage source_image;
|
QImage source_image;
|
||||||
if (display_frame->format() == PixelFormat::u8 &&
|
if (frame_fmt == PixelFormat::u8 && frame_ch == 4) {
|
||||||
display_frame->channel_count() == 4) {
|
backend_neutral_cpu_display_frame_ = frame;
|
||||||
backend_neutral_cpu_display_frame_ = display_frame;
|
backend_neutral_cpu_image_ = QImage(
|
||||||
backend_neutral_cpu_image_ =
|
reinterpret_cast<const uchar *>(frame_data), frame_w, frame_h,
|
||||||
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
|
frame_ls, QImage::Format_RGBA8888);
|
||||||
display_frame->width(), display_frame->height(),
|
|
||||||
display_frame->linesize_bytes(), QImage::Format_RGBA8888);
|
|
||||||
source_image = backend_neutral_cpu_image_;
|
source_image = backend_neutral_cpu_image_;
|
||||||
} else if (display_frame->format() == PixelFormat::u8 &&
|
} else if (frame_fmt == PixelFormat::u8 && frame_ch == 3) {
|
||||||
display_frame->channel_count() ==
|
backend_neutral_cpu_display_frame_ = frame;
|
||||||
3) {
|
backend_neutral_cpu_image_ = QImage(
|
||||||
backend_neutral_cpu_display_frame_ = display_frame;
|
reinterpret_cast<const uchar *>(frame_data), frame_w, frame_h,
|
||||||
backend_neutral_cpu_image_ =
|
frame_ls, QImage::Format_RGB888);
|
||||||
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
|
|
||||||
display_frame->width(), display_frame->height(),
|
|
||||||
display_frame->linesize_bytes(), QImage::Format_RGB888);
|
|
||||||
source_image = backend_neutral_cpu_image_;
|
source_image = backend_neutral_cpu_image_;
|
||||||
} else {
|
} else {
|
||||||
backend_neutral_cpu_display_frame_.reset();
|
backend_neutral_cpu_display_frame_ = nullptr;
|
||||||
const int bytes_per_pixel =
|
const int bpp =
|
||||||
oakengine_video_params_bytes_per_pixel(
|
oakengine_video_params_bytes_per_pixel(frame_fmt, frame_ch);
|
||||||
static_cast<int>(display_frame->video_params().format()),
|
if (backend_neutral_cpu_image_.size() != QSize(frame_w, frame_h) ||
|
||||||
display_frame->video_params().channel_count());
|
|
||||||
if (backend_neutral_cpu_image_.size() !=
|
|
||||||
QSize(display_frame->width(), display_frame->height()) ||
|
|
||||||
backend_neutral_cpu_image_.format() != QImage::Format_RGBA8888) {
|
backend_neutral_cpu_image_.format() != QImage::Format_RGBA8888) {
|
||||||
backend_neutral_cpu_image_ = QImage(display_frame->width(),
|
backend_neutral_cpu_image_ =
|
||||||
display_frame->height(),
|
QImage(frame_w, frame_h, QImage::Format_RGBA8888);
|
||||||
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);
|
uchar *dst = backend_neutral_cpu_image_.scanLine(y);
|
||||||
const char *src = display_frame->const_data() +
|
const char *src = frame_data + y * frame_ls;
|
||||||
y * display_frame->linesize_bytes();
|
for (int x = 0; x < frame_w; ++x) {
|
||||||
for (int x = 0; x < display_frame->width(); ++x) {
|
Color c(src + x * bpp,
|
||||||
Color c(src + x * bytes_per_pixel, display_frame->format(),
|
static_cast<PixelFormat::Format>(frame_fmt), frame_ch);
|
||||||
display_frame->channel_count());
|
|
||||||
dst[x * 4 + 0] =
|
dst[x * 4 + 0] =
|
||||||
static_cast<uchar>(qBound(0, int(c.red() * 255.0), 255));
|
static_cast<uchar>(qBound(0, int(c.red() * 255.0), 255));
|
||||||
dst[x * 4 + 1] =
|
dst[x * 4 + 1] =
|
||||||
@@ -1560,10 +1588,11 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture,
|
bool ViewerDisplayWidget::draw_backend_neutral_texture(void *texture,
|
||||||
QPainter *painter)
|
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()) {
|
!painter->isActive() || !color_service()) {
|
||||||
return false;
|
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) {
|
const QString color_id = oak_query_string([this](char *buf, int size) {
|
||||||
return oakengine_color_processor_id(color_service().get(), buf, 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_color_id_ == color_id &&
|
||||||
!backend_neutral_cpu_image_.isNull()) {
|
!backend_neutral_cpu_image_.isNull()) {
|
||||||
painter->save();
|
painter->save();
|
||||||
@@ -1582,28 +1611,33 @@ bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FramePtr frame;
|
void *tmp_frame = oakengine_codec_frame_create();
|
||||||
oakengine_codec_frame_create(&frame);
|
oak_video_params tex_params = {};
|
||||||
oakengine_codec_frame_set_video_params(frame.get(), &texture->params());
|
oakengine_display_texture_get_params(texture, &tex_params);
|
||||||
if (!oakengine_codec_frame_allocate(frame.get())) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
oakengine_display_texture_download(texture.get(), frame->data(),
|
oakengine_display_texture_download(texture,
|
||||||
frame->linesize_pixels());
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
backend_neutral_cpu_source_texture_ = texture;
|
backend_neutral_cpu_source_texture_ = texture;
|
||||||
backend_neutral_cpu_color_id_ = color_id;
|
backend_neutral_cpu_color_id_ = color_id;
|
||||||
|
oakengine_codec_frame_free(tmp_frame);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders a backend-neutral frame by drawing into an offscreen backend texture,
|
// Renders a backend-neutral frame by drawing into an offscreen backend texture,
|
||||||
// downloading it to CPU memory, then painting that image with QPainter.
|
// 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)
|
QPainter *painter)
|
||||||
{
|
{
|
||||||
if (!painter || !painter->isActive()) {
|
if (!painter || !painter->isActive()) {
|
||||||
@@ -1613,38 +1647,41 @@ void ViewerDisplayWidget::draw_backend_neutral(const ColorTransformJob &ctj,
|
|||||||
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
|
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
|
||||||
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
|
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
|
||||||
|
|
||||||
oak_video_params pod = {};
|
oak_video_params offscreen_pod = {};
|
||||||
pod.width = texture_width;
|
offscreen_pod.width = texture_width;
|
||||||
pod.height = texture_height;
|
offscreen_pod.height = texture_height;
|
||||||
pod.format = PixelFormat::u8;
|
offscreen_pod.format = PixelFormat::u8;
|
||||||
const VideoParams offscreen_params(video_params_from_pod(pod));
|
|
||||||
|
|
||||||
if (!backend_neutral_texture_ ||
|
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
|
// The offscreen texture is sized in device pixels so high-DPI widgets
|
||||||
// draw one downloaded pixel per device pixel after setDevicePixelRatio().
|
// draw one downloaded pixel per device pixel after setDevicePixelRatio().
|
||||||
oakengine_display_renderer_create_texture(renderer(), &offscreen_params,
|
if (backend_neutral_texture_) {
|
||||||
nullptr, 0,
|
oakengine_display_texture_free(backend_neutral_texture_);
|
||||||
&backend_neutral_texture_);
|
}
|
||||||
|
backend_neutral_texture_ = oakengine_display_texture_create(
|
||||||
|
renderer(), &offscreen_pod, nullptr, 0);
|
||||||
backend_neutral_buffer_.resize(
|
backend_neutral_buffer_.resize(
|
||||||
texture_width * texture_height *
|
texture_width * texture_height *
|
||||||
oakengine_video_params_bytes_per_pixel(0, // PixelFormat::u8
|
oakengine_video_params_bytes_per_pixel(0, // PixelFormat::u8
|
||||||
4));
|
4));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!backend_neutral_texture_ || backend_neutral_texture_->is_dummy()) {
|
if (!backend_neutral_texture_ ||
|
||||||
|
oakengine_display_texture_is_dummy(backend_neutral_texture_)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorTransformJob local_ctj = ctj;
|
oak_color_transform_job local_ctj = ctj;
|
||||||
local_ctj.set_clear_destination_enabled(true);
|
local_ctj.clear_destination = 1;
|
||||||
|
|
||||||
// Reuse the normal color-management shader path, but render into a texture
|
// Reuse the normal color-management shader path, but render into a texture
|
||||||
// instead of an OpenGL widget framebuffer.
|
// instead of an OpenGL widget framebuffer.
|
||||||
oakengine_display_renderer_blit_color_managed(
|
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);
|
backend_neutral_buffer_.data(), 0);
|
||||||
|
|
||||||
const int bytes_per_pixel = oakengine_video_params_bytes_per_pixel(0, 4); // u8, RGBA
|
const int bytes_per_pixel = oakengine_video_params_bytes_per_pixel(0, 4); // u8, RGBA
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <QRubberBand>
|
#include <QRubberBand>
|
||||||
|
|
||||||
#include "codec/frame.h"
|
|
||||||
#include "engineeventbridge.h"
|
#include "engineeventbridge.h"
|
||||||
#include "node/color/colormanager/colormanager.h"
|
#include "node/color/colormanager/colormanager.h"
|
||||||
#include "node/gizmo/text.h"
|
#include "node/gizmo/text.h"
|
||||||
@@ -41,6 +40,7 @@
|
|||||||
#include "viewertexteditor.h"
|
#include "viewertexteditor.h"
|
||||||
#include "widget/manageddisplay/manageddisplay.h"
|
#include "widget/manageddisplay/manageddisplay.h"
|
||||||
#include "widget/timetarget/timetarget.h"
|
#include "widget/timetarget/timetarget.h"
|
||||||
|
#include "widget/viewer/displaybuffer.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -136,7 +136,7 @@ public:
|
|||||||
fps_timer_update_count_++;
|
fps_timer_update_count_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr get_current_texture() const
|
void *get_current_texture() const
|
||||||
{
|
{
|
||||||
return texture_;
|
return texture_;
|
||||||
}
|
}
|
||||||
@@ -242,7 +242,7 @@ signals:
|
|||||||
|
|
||||||
void dropped(QDropEvent *event);
|
void dropped(QDropEvent *event);
|
||||||
|
|
||||||
void texture_changed(TexturePtr texture);
|
void texture_changed(void *texture);
|
||||||
|
|
||||||
void queue_starved();
|
void queue_starved();
|
||||||
|
|
||||||
@@ -270,7 +270,7 @@ protected:
|
|||||||
node_time + gizmo_params_.frame_rate_as_time_base());
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -333,22 +333,21 @@ private:
|
|||||||
|
|
||||||
void generate_gizmo_transforms();
|
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);
|
void draw_backend_neutral(const oak_color_transform_job &ctj, QPainter *painter);
|
||||||
bool draw_backend_neutral_frame(const FramePtr &frame, QPainter *painter);
|
bool draw_backend_neutral_frame(void *frame, QPainter *painter);
|
||||||
bool draw_backend_neutral_texture(const TexturePtr &texture,
|
bool draw_backend_neutral_texture(void *texture, QPainter *painter);
|
||||||
QPainter *painter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL().
|
* @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
|
* @brief Internal texture to deinterlace to
|
||||||
*/
|
*/
|
||||||
TexturePtr deinterlace_texture_;
|
void *deinterlace_texture_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Offscreen texture for backend-neutral viewer rendering.
|
* @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
|
* 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.
|
* 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_.
|
* @brief CPU readback buffer for backend_neutral_texture_.
|
||||||
*/
|
*/
|
||||||
QByteArray backend_neutral_buffer_;
|
QByteArray backend_neutral_buffer_;
|
||||||
QImage backend_neutral_cpu_image_;
|
QImage backend_neutral_cpu_image_;
|
||||||
FramePtr backend_neutral_cpu_display_frame_;
|
void *backend_neutral_cpu_display_frame_;
|
||||||
FramePtr backend_neutral_cpu_source_frame_;
|
void *backend_neutral_cpu_source_frame_;
|
||||||
TexturePtr backend_neutral_cpu_source_texture_;
|
void *backend_neutral_cpu_source_texture_;
|
||||||
QString backend_neutral_cpu_color_id_;
|
QString backend_neutral_cpu_color_id_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Deinterlace shader
|
* @brief Deinterlace shader
|
||||||
*/
|
*/
|
||||||
QVariant deinterlace_shader_;
|
void *deinterlace_shader_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Blank shader
|
* @brief Blank shader
|
||||||
*/
|
*/
|
||||||
QVariant blank_shader_;
|
void *blank_shader_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Translation only matrix (defaults to identity).
|
* @brief Translation only matrix (defaults to identity).
|
||||||
|
|||||||
@@ -26,13 +26,13 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QMutexLocker>
|
#include <QMutexLocker>
|
||||||
|
|
||||||
#include "codec/frame.h"
|
#include <olive/core/core.h>
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
|
|
||||||
struct ViewerPlaybackFrame {
|
struct ViewerPlaybackFrame {
|
||||||
Rational timestamp;
|
core::Rational timestamp;
|
||||||
QVariant frame;
|
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_);
|
QMutexLocker locker(mutex_);
|
||||||
while (!this->empty() &&
|
while (!this->empty() &&
|
||||||
|
|||||||
@@ -111,10 +111,35 @@ facade 现状覆盖:项目/序列读写、素材探测与导入、时间线查
|
|||||||
|
|
||||||
豁免原则:纯 UI 呈现类(不触引擎执行)可经 C++ 包装层引用——但包装层本身也是 C++ 符号引用,故阶段 4 的"0"实际指**直接 olive:: 符号**;包装类应放进 liboakengine 的 wrapper 头(符号由 wrapper 内联消解,不进动态符号表)。
|
豁免原则:纯 UI 呈现类(不触引擎执行)可经 C++ 包装层引用——但包装层本身也是 C++ 符号引用,故阶段 4 的"0"实际指**直接 olive:: 符号**;包装类应放进 liboakengine 的 wrapper 头(符号由 wrapper 内联消解,不进动态符号表)。
|
||||||
|
|
||||||
### 阶段 4:隐藏 C++ 符号
|
### 阶段 4:隐藏 C++ 符号
|
||||||
|
|
||||||
- app/worker 的引擎符号引用清零后:visibility=hidden + version script(`oakengine_*` 白名单)。
|
- app/worker 的引擎符号引用清零后:visibility=hidden + version script(`oakengine_*` 白名单)。
|
||||||
- 验收:`nm -D` 仅 `oakengine_*`;全部测试(含 CLI)绿;三平台打包复验。
|
- 验收:`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 评估)
|
## 附 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
|
comm -12 <(nm -D --defined-only engine/liboakengine.so | awk '{print $3}' | sort -u) /tmp/u.txt | wc -l
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] 上述耦合计数降为 0
|
- [x] 上述耦合计数降为 0
|
||||||
- [ ] `nm -D --defined-only liboakengine.so` 仅 `oakengine_*`
|
- [x] `nm -D --defined-only liboakengine.so` 仅 `oakengine_*` + 19 个渲染后端插件 ABI 符号
|
||||||
- [ ] oak-editor/oak-render-worker `ldd` 正常,全部启动
|
- [x] oak-editor/oak-render-worker `ldd` 正常,全部启动
|
||||||
- [ ] 1986+ gtest 全绿,CLI ctest 全绿,5+ C ABI 测试全绿
|
- [ ] 1986+ gtest 全绿,CLI ctest 全绿,5+ C ABI 测试全绿
|
||||||
- [ ] 三平台打包含 liboakengine(liboakengine.so/dylib/oakengine.dll),Linux 位于标准 libdir
|
- [ ] 三平台打包含 liboakengine(liboakengine.so/dylib/oakengine.dll),Linux 位于标准 libdir
|
||||||
|
|
||||||
## 附 C:R5 批次记录
|
## 附 C:R5 批次记录
|
||||||
|
|
||||||
|
|||||||
+41
-9
@@ -46,25 +46,31 @@ add_subdirectory(ui)
|
|||||||
add_subdirectory(undo)
|
add_subdirectory(undo)
|
||||||
add_subdirectory(src/capi)
|
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_SOURCES}
|
||||||
${OLIVE_RESOURCES}
|
${OLIVE_RESOURCES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(oakengine SHARED $<TARGET_OBJECTS:oakengine-obj>)
|
||||||
|
|
||||||
# macOS: hides the render worker's dock icon; called from the worker main in
|
# 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).
|
# src/capi/worker.cpp (declared there as a plain C++ symbol).
|
||||||
if (APPLE)
|
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")
|
target_link_libraries(oakengine PRIVATE "-framework Cocoa")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
add_subdirectory(common)
|
add_subdirectory(common)
|
||||||
add_subdirectory(pluginSupport)
|
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
|
set_target_properties(oakengine PROPERTIES
|
||||||
OUTPUT_NAME oakengine
|
OUTPUT_NAME oakengine
|
||||||
POSITION_INDEPENDENT_CODE ON
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Consumers resolve engine headers ("node/...", "render/...", "coreengine.h",
|
# 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
|
# 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
|
# every target-level directory, so the prefixed paths are what keep internal
|
||||||
# sources from ever picking up a consumer wrapper header by accident.
|
# 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
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
@@ -85,10 +91,28 @@ target_include_directories(oakengine
|
|||||||
${OLIVE_INCLUDE_DIRS}
|
${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)
|
target_link_libraries(oakengine PUBLIC ${OLIVE_LIBRARIES} OfxHost)
|
||||||
# OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport)
|
# OAKENGINE_BUILD marks the library side of the C ABI export macros (dllexport)
|
||||||
target_compile_definitions(oakengine PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
target_compile_definitions(oakengine-obj PRIVATE ${OLIVE_DEFINITIONS} OAKENGINE_BUILD)
|
||||||
target_compile_options(oakengine PRIVATE ${OLIVE_COMPILE_OPTIONS})
|
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,
|
# Install into the platform's standard library directory (/usr/lib,
|
||||||
# /usr/lib64 or the Debian multiarch path); Windows DLLs go next to the
|
# /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
|
# dlopen; they share this engine library instead of embedding a static
|
||||||
# render-core subset.
|
# render-core subset.
|
||||||
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
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)
|
foreach (target olivecore kddockwidgets)
|
||||||
if (TARGET ${target})
|
if (TARGET ${target})
|
||||||
@@ -225,10 +249,18 @@ if (BUILD_TESTS)
|
|||||||
tests/${name}.cpp
|
tests/${name}.cpp
|
||||||
$<TARGET_OBJECTS:olive-version-obj>
|
$<TARGET_OBJECTS:olive-version-obj>
|
||||||
)
|
)
|
||||||
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
|
target_include_directories(${name} PRIVATE
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
"${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})
|
add_test(${name} ${name})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
target_sources(oakengine PRIVATE
|
target_sources(oakengine-obj PRIVATE
|
||||||
cancelableobject.h
|
cancelableobject.h
|
||||||
commandlineparser.cpp
|
commandlineparser.cpp
|
||||||
commandlineparser.h
|
commandlineparser.h
|
||||||
|
|||||||
+243
-117
@@ -21,8 +21,11 @@
|
|||||||
#ifndef OAKENGINE_DISPLAY_H
|
#ifndef OAKENGINE_DISPLAY_H
|
||||||
#define OAKENGINE_DISPLAY_H
|
#define OAKENGINE_DISPLAY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "export.h"
|
#include "export.h"
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
|
#include "videoparams.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -30,155 +33,278 @@ extern "C" {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @file display.h
|
* @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
|
* Ownership protocol: textures and frames are opaque handles pointing to
|
||||||
* (olive::Renderer and its OpenGLRenderer/DynamicRenderer implementations,
|
* engine-heap control blocks (internally holding std::shared_ptr; invisible
|
||||||
* engine/render/renderer.h) plus the GPU texture (olive::Texture) and the
|
* to the ABI). Ownership transfers via explicit retain/free. Every retain
|
||||||
* CPU frame buffer (olive::Frame) that viewer/scope widgets use to move
|
* must be paired with exactly one free. NULL is accepted by all functions
|
||||||
* pixels between the CPU and the GPU.
|
* and yields a no-op / zero result.
|
||||||
*
|
*
|
||||||
* It is distinct from the sequence-rendering facade in oakengine/renderer.h
|
* Conventions:
|
||||||
* (OakEngineRenderer), which pulls finished CPU frames out of the async
|
* - `renderer` is an opaque renderer handle (olive::Renderer* internally).
|
||||||
* render pipeline. This family drives the *on-screen* paint path instead:
|
* - `texture` is an OakEngineDisplayTexture handle (refcounted).
|
||||||
* a widget creates a renderer, initializes it with the widget's GL context,
|
* - `frame` is an OakEngineCodecFrame handle (refcounted).
|
||||||
* uploads/downloads textures, and blits color-managed images each paint.
|
* - Video parameters use the oak_video_params POD (oakengine/videoparams.h).
|
||||||
*
|
* - Color jobs use the oak_color_transform_job POD defined below.
|
||||||
* Conventions (matching the other facade families):
|
* - `gl_context` is a QOpenGLContext* or NULL.
|
||||||
* - All object pointers are opaque. `renderer` is an olive::Renderer*,
|
* - `parent` is the owning QObject* (the display widget).
|
||||||
* `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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ---- Display renderer lifecycle ---------------------------------------- */
|
/* ---- oak_color_transform_job POD ---------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a dynamic-backend renderer (olive::DynamicRenderer) for
|
* @brief Flattened POD of the engine's ColorTransformJob for the display blit
|
||||||
* `backend_name` and load() it.
|
* path. Fields map 1:1 to ColorTransformJob members used by viewer/scope.
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
|
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_API void *
|
||||||
oakengine_display_renderer_create_dynamic(const char *backend_name,
|
oakengine_display_renderer_create_dynamic(const char *backend_name,
|
||||||
void *parent);
|
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);
|
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,
|
OAKENGINE_API int oakengine_display_renderer_init(void *renderer,
|
||||||
void *gl_context);
|
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);
|
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()).
|
* @brief Create a GPU texture on `renderer`.
|
||||||
*
|
* @return New texture handle (refcount=1), or NULL on failure.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
OAKENGINE_API void
|
OAKENGINE_API void *oakengine_display_texture_create(
|
||||||
oakengine_display_renderer_create_texture(void *renderer,
|
void *renderer, const oak_video_params *params,
|
||||||
const void *video_params,
|
const void *pixels, int linesize);
|
||||||
const void *pixels, int linesize,
|
|
||||||
void *out_texture);
|
/** @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()).
|
* @brief Compare two texture handles' video params for equality.
|
||||||
*
|
* @return 1 if equal, 0 otherwise (NULL handles compare unequal).
|
||||||
* @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).
|
|
||||||
*/
|
*/
|
||||||
OAKENGINE_API void
|
OAKENGINE_API int oakengine_display_texture_params_equal(
|
||||||
oakengine_display_renderer_blit_color_managed(void *renderer,
|
const void *a, const void *b);
|
||||||
const void *color_job,
|
|
||||||
void *dst_texture,
|
|
||||||
const void *video_params);
|
|
||||||
|
|
||||||
/**
|
/* ---- Frame handle (opaque, refcounted) ---------------------------------- */
|
||||||
* @brief Upload CPU pixels into a GPU texture (Texture::upload()).
|
|
||||||
*/
|
|
||||||
OAKENGINE_API void oakengine_display_texture_upload(void *texture,
|
|
||||||
void *pixels, int linesize);
|
|
||||||
|
|
||||||
/**
|
/** @brief Create an empty CPU frame. Returns handle (refcount=1). */
|
||||||
* @brief Download GPU texture pixels into CPU memory (Texture::download()).
|
OAKENGINE_API void *oakengine_codec_frame_create(void);
|
||||||
*/
|
|
||||||
OAKENGINE_API void oakengine_display_texture_download(void *texture,
|
|
||||||
void *pixels,
|
|
||||||
int linesize);
|
|
||||||
|
|
||||||
/* ---- CPU frame buffer --------------------------------------------------- */
|
/** @brief Increment refcount, return same handle. NULL-safe. */
|
||||||
|
OAKENGINE_API void *oakengine_codec_frame_retain(void *frame);
|
||||||
|
|
||||||
/**
|
/** @brief Decrement refcount; frees at zero. NULL-safe. */
|
||||||
* @brief Create an empty CPU frame (olive::Frame::create()).
|
OAKENGINE_API void oakengine_codec_frame_free(void *frame);
|
||||||
*
|
|
||||||
* @param out_frame Pointer to an olive::FramePtr to receive the new frame.
|
|
||||||
*/
|
|
||||||
OAKENGINE_API void oakengine_codec_frame_create(void *out_frame);
|
|
||||||
|
|
||||||
/**
|
OAKENGINE_API int oakengine_codec_frame_set_video_params(
|
||||||
* @brief Set a frame's video parameters (Frame::set_video_params()).
|
void *frame, const oak_video_params *params);
|
||||||
*
|
|
||||||
* @param frame olive::Frame*.
|
OAKENGINE_API int oakengine_codec_frame_get_params(
|
||||||
* @param video_params const olive::VideoParams*.
|
const void *frame, oak_video_params *out);
|
||||||
*/
|
|
||||||
OAKENGINE_API void oakengine_codec_frame_set_video_params(void *frame,
|
|
||||||
const void
|
|
||||||
*video_params);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
{
|
{
|
||||||
global:
|
global:
|
||||||
oakengine_*;
|
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:
|
local:
|
||||||
*;
|
*;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
target_sources(oakengine PRIVATE
|
target_sources(oakengine-obj PRIVATE
|
||||||
olivehost.h
|
olivehost.h
|
||||||
olivehost.cpp
|
olivehost.cpp
|
||||||
oliveplugininstance.h
|
oliveplugininstance.h
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "pluginSupport/oliveclip.h"
|
#include "pluginSupport/oliveclip.h"
|
||||||
#include "pluginSupport/olivehost.h"
|
#include "pluginSupport/olivehost.h"
|
||||||
#include "oliveimpl/render/ipc/frameslotpool.h"
|
#include "oliveimpl/render/ipc/frameslotpool.h"
|
||||||
|
#include "src/capi/displayinternal.h"
|
||||||
|
|
||||||
namespace olive
|
namespace olive
|
||||||
{
|
{
|
||||||
@@ -367,7 +368,7 @@ NodeValueDatabase RenderProcessor::generate_database(const Node *node,
|
|||||||
if (QtUtils::value_to_ptr<MultiCamNode>(ticket_->property("multicam")) ==
|
if (QtUtils::value_to_ptr<MultiCamNode>(ticket_->property("multicam")) ==
|
||||||
multicam) {
|
multicam) {
|
||||||
int sz = multicam->get_source_count();
|
int sz = multicam->get_source_count();
|
||||||
QVector<TexturePtr> multicam_tex(sz);
|
QVector<void *> multicam_tex(sz);
|
||||||
for (int i = 0; i < sz; i++) {
|
for (int i = 0; i < sz; i++) {
|
||||||
NodeValueTable t =
|
NodeValueTable t =
|
||||||
generate_table(multicam->get_connected_render_output(
|
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);
|
multicam, multicam->k_sources_input, i, &t, range);
|
||||||
resolve_jobs(val);
|
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",
|
ticket_->setProperty("multicam_output",
|
||||||
QVariant::fromValue(multicam_tex));
|
QVariant::fromValue(multicam_tex));
|
||||||
|
|||||||
+654
-53
@@ -19,22 +19,118 @@
|
|||||||
***/
|
***/
|
||||||
|
|
||||||
#include "oakengine/display.h"
|
#include "oakengine/display.h"
|
||||||
|
#include "displayinternal.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QOpenGLContext>
|
#include <QOpenGLContext>
|
||||||
|
#include <QMatrix4x4>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QVector2D>
|
||||||
|
#include <QVector3D>
|
||||||
|
|
||||||
#include "codec/frame.h"
|
#include "codec/frame.h"
|
||||||
|
#include "colorinternal.h"
|
||||||
#include "render/job/colortransformjob.h"
|
#include "render/job/colortransformjob.h"
|
||||||
|
#include "render/job/shaderjob.h"
|
||||||
#include "render/opengl/openglrenderer.h"
|
#include "render/opengl/openglrenderer.h"
|
||||||
#include "render/renderer.h"
|
#include "render/renderer.h"
|
||||||
|
#include "render/shadercode.h"
|
||||||
#include "render/texture.h"
|
#include "render/texture.h"
|
||||||
|
#include "node/value.h"
|
||||||
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
#ifdef OAK_ENABLE_DYNAMIC_RENDER_BACKEND
|
||||||
#include "render/backend/dynamicrenderer.h"
|
#include "render/backend/dynamicrenderer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* ---- Internal control blocks -------------------------------------------- */
|
||||||
|
|
||||||
|
struct OakEngineDisplayTexture {
|
||||||
|
olive::TexturePtr ptr;
|
||||||
|
std::atomic<int> refcount{1};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OakEngineCodecFrame {
|
||||||
|
olive::FramePtr ptr;
|
||||||
|
std::atomic<int> 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<olive::PixelFormat::Format>(v.format),
|
||||||
|
olive::VideoParams::k_internal_channel_count,
|
||||||
|
olive::Rational(v.pixel_aspect_num, v.pixel_aspect_den),
|
||||||
|
static_cast<olive::VideoParams::Interlacing>(v.interlacing),
|
||||||
|
v.divider > 0 ? v.divider : 1);
|
||||||
|
vp.set_color_range(static_cast<olive::VideoParams::ColorRange>(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<int>(vp.format());
|
||||||
|
p.pixel_aspect_num = vp.pixel_aspect_ratio().numerator();
|
||||||
|
p.pixel_aspect_den = vp.pixel_aspect_ratio().denominator();
|
||||||
|
p.interlacing = static_cast<int>(vp.interlacing());
|
||||||
|
p.color_range = static_cast<int>(vp.color_range());
|
||||||
|
p.divider = vp.divider();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
OakEngineDisplayTexture *tex(void *h)
|
||||||
|
{
|
||||||
|
return static_cast<OakEngineDisplayTexture *>(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
OakEngineCodecFrame *frm(void *h)
|
||||||
|
{
|
||||||
|
return static_cast<OakEngineCodecFrame *>(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
olive::Renderer *ren(void *h)
|
||||||
|
{
|
||||||
|
return static_cast<olive::Renderer *>(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" {
|
extern "C" {
|
||||||
|
|
||||||
|
/* ---- Renderer lifecycle ------------------------------------------------- */
|
||||||
|
|
||||||
void *oakengine_display_renderer_create_dynamic(const char *backend_name,
|
void *oakengine_display_renderer_create_dynamic(const char *backend_name,
|
||||||
void *parent)
|
void *parent)
|
||||||
{
|
{
|
||||||
@@ -45,8 +141,6 @@ void *oakengine_display_renderer_create_dynamic(const char *backend_name,
|
|||||||
if (dyn->load()) {
|
if (dyn->load()) {
|
||||||
return dyn;
|
return dyn;
|
||||||
}
|
}
|
||||||
// Backend library failed to load: drop it so the caller can fall back
|
|
||||||
// to the built-in OpenGL renderer.
|
|
||||||
delete dyn;
|
delete dyn;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#else
|
#else
|
||||||
@@ -63,7 +157,7 @@ void *oakengine_display_renderer_create_opengl(void *parent)
|
|||||||
|
|
||||||
int oakengine_display_renderer_init(void *renderer, void *gl_context)
|
int oakengine_display_renderer_init(void *renderer, void *gl_context)
|
||||||
{
|
{
|
||||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
olive::Renderer *r = ren(renderer);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return OAKENGINE_E_INVALID;
|
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)
|
void oakengine_display_renderer_destroy(void *renderer)
|
||||||
{
|
{
|
||||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
olive::Renderer *r = ren(renderer);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -98,85 +192,592 @@ void oakengine_display_renderer_destroy(void *renderer)
|
|||||||
r->post_destroy();
|
r->post_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
void oakengine_display_renderer_create_texture(void *renderer,
|
/* ---- Renderer queries --------------------------------------------------- */
|
||||||
const void *video_params,
|
|
||||||
const void *pixels, int linesize,
|
int oakengine_display_renderer_is_open_gl(const void *renderer)
|
||||||
void *out_texture)
|
|
||||||
{
|
{
|
||||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
olive::Renderer *r = ren(const_cast<void *>(renderer));
|
||||||
if (!r || !video_params || !out_texture) {
|
return r ? r->is_open_gl() : 0;
|
||||||
return;
|
|
||||||
}
|
|
||||||
const olive::VideoParams ¶ms =
|
|
||||||
*static_cast<const olive::VideoParams *>(video_params);
|
|
||||||
*static_cast<olive::TexturePtr *>(out_texture) =
|
|
||||||
r->create_texture(params, pixels, linesize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void oakengine_display_renderer_blit_color_managed(void *renderer,
|
int oakengine_display_renderer_is_vulkan(const void *renderer)
|
||||||
const void *color_job,
|
|
||||||
void *dst_texture,
|
|
||||||
const void *video_params)
|
|
||||||
{
|
{
|
||||||
olive::Renderer *r = static_cast<olive::Renderer *>(renderer);
|
olive::Renderer *r = ren(const_cast<void *>(renderer));
|
||||||
if (!r || !color_job) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
const olive::ColorTransformJob &job =
|
if (tex(texture)->refcount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
|
||||||
*static_cast<const olive::ColorTransformJob *>(color_job);
|
delete tex(texture);
|
||||||
olive::Texture *dst = static_cast<olive::Texture *>(dst_texture);
|
|
||||||
if (video_params) {
|
|
||||||
r->blit_color_managed(
|
|
||||||
job, dst, *static_cast<const olive::VideoParams *>(video_params));
|
|
||||||
} else if (dst) {
|
|
||||||
r->blit_color_managed(job, dst, dst->params());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<olive::Texture *>(texture);
|
if (!texture || !pixels) {
|
||||||
if (!t) {
|
return OAKENGINE_E_INVALID;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
t->upload(pixels, linesize);
|
tex(texture)->ptr->upload(const_cast<void *>(pixels), linesize);
|
||||||
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void oakengine_display_texture_download(void *texture, void *pixels,
|
int oakengine_display_texture_download(void *texture, void *pixels,
|
||||||
int linesize)
|
int linesize)
|
||||||
{
|
{
|
||||||
olive::Texture *t = static_cast<olive::Texture *>(texture);
|
if (!texture || !pixels) {
|
||||||
if (!t) {
|
return OAKENGINE_E_INVALID;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
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) {
|
if (!texture || !out) {
|
||||||
return;
|
return OAKENGINE_E_INVALID;
|
||||||
}
|
}
|
||||||
*static_cast<olive::FramePtr *>(out_frame) = olive::Frame::create();
|
*out = cpp_to_pod(tex(const_cast<void *>(texture))->ptr->params());
|
||||||
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void oakengine_codec_frame_set_video_params(void *frame,
|
int oakengine_display_texture_id(const void *texture)
|
||||||
const void *video_params)
|
|
||||||
{
|
{
|
||||||
olive::Frame *f = static_cast<olive::Frame *>(frame);
|
if (!texture) {
|
||||||
if (!f || !video_params) {
|
return 0;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(texture))->ptr->id().toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_display_texture_is_dummy(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(texture))->ptr->is_dummy() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *oakengine_display_texture_renderer(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(texture))->ptr->renderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_display_texture_width(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(texture))->ptr->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_display_texture_height(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(texture))->ptr->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_display_texture_format(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<int>(tex(const_cast<void *>(texture))->ptr->format());
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_display_texture_channel_count(const void *texture)
|
||||||
|
{
|
||||||
|
if (!texture) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tex(const_cast<void *>(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<void *>(a))->ptr->params() ==
|
||||||
|
tex(const_cast<void *>(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;
|
return;
|
||||||
}
|
}
|
||||||
f->set_video_params(*static_cast<const olive::VideoParams *>(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<void *>(frame))->ptr->video_params());
|
||||||
|
return OAKENGINE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int oakengine_codec_frame_allocate(void *frame)
|
int oakengine_codec_frame_allocate(void *frame)
|
||||||
{
|
{
|
||||||
olive::Frame *f = static_cast<olive::Frame *>(frame);
|
if (!frame) {
|
||||||
if (!f) {
|
|
||||||
return 0;
|
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<void *>(frame))->ptr->const_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_linesize(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(frame))->ptr->linesize_pixels();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_linesize_bytes(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(frame))->ptr->linesize_bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Frame queries ------------------------------------------------------ */
|
||||||
|
|
||||||
|
int oakengine_codec_frame_width(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(frame))->ptr->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_height(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(frame))->ptr->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_format(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<int>(frm(const_cast<void *>(frame))->ptr->format());
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_channel_count(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(frame))->ptr->channel_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
int oakengine_codec_frame_is_allocated(const void *frame)
|
||||||
|
{
|
||||||
|
if (!frame) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return frm(const_cast<void *>(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<const OakEngineColorProcessor *>(job->processor)->ptr);
|
||||||
|
}
|
||||||
|
if (job->input_texture) {
|
||||||
|
ctj.set_input_texture(tex(job->input_texture)->ptr);
|
||||||
|
}
|
||||||
|
ctj.set_input_alpha_association(
|
||||||
|
static_cast<olive::AlphaAssociated>(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<QVariant *>(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<QVariant *>(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<QVariant *>(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<QVariant *>(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<QVariant *>(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<QVariant *>(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<QVariant *>(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<int>(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"
|
} // 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#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
|
||||||
@@ -53,8 +53,13 @@ function(olive_add_test GROUP NAME SOURCE)
|
|||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
${NAME}
|
${NAME}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
oakengine-obj
|
||||||
${OLIVE_LIBRARIES}
|
${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(
|
target_compile_definitions(
|
||||||
${NAME}
|
${NAME}
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ target_include_directories(
|
|||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
olive-gtest
|
olive-gtest
|
||||||
PRIVATE
|
PRIVATE
|
||||||
|
oakengine-obj
|
||||||
${OLIVE_LIBRARIES}
|
${OLIVE_LIBRARIES}
|
||||||
GTest::gtest
|
GTest::gtest
|
||||||
Qt${QT_VERSION_MAJOR}::Test
|
Qt${QT_VERSION_MAJOR}::Test
|
||||||
@@ -177,6 +178,11 @@ target_compile_options(
|
|||||||
${OLIVE_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)
|
if (OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||||
target_compile_definitions(olive-gtest PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
target_compile_definitions(olive-gtest PRIVATE OAK_ENABLE_DYNAMIC_RENDER_BACKEND)
|
||||||
add_dependencies(olive-gtest oakgl)
|
add_dependencies(olive-gtest oakgl)
|
||||||
|
|||||||
Reference in New Issue
Block a user