app: migrate all engine access to the C ABI facade (nm U _ZN5olive = 0)

Every app module now reaches liboakengine exclusively through
oakengine_* C calls, EngineEventBridge subscriptions and app-side
handle headers (cliphandle/keyframehandle/nodevaluehandle/oakvaluehelper).
Direct C++ command construction, engine signal connect()s, and engine
type usage in MOC-visible signatures are gone: 557 -> 0 undefined
olive:: symbols in oak-editor.
This commit is contained in:
2026-07-26 22:43:21 +08:00
parent f95590e924
commit 0aa5879f35
256 changed files with 12357 additions and 4044 deletions
+4
View File
@@ -26,6 +26,8 @@ set(OLIVE_SOURCES
widget/viewer/viewerdisplay.h
widget/viewer/viewerpreventsleep.cpp
widget/viewer/viewerpreventsleep.h
widget/viewer/viewerplaybacktimer.cpp
widget/viewer/viewerplaybacktimer.h
widget/viewer/viewerqueue.h
widget/viewer/viewersafemargininfo.h
widget/viewer/viewersizer.cpp
@@ -34,5 +36,7 @@ set(OLIVE_SOURCES
widget/viewer/viewertexteditor.h
widget/viewer/viewerwindow.cpp
widget/viewer/viewerwindow.h
widget/viewer/vieweroutpututils.h
widget/viewer/vieweroutpututils.cpp
PARENT_SCOPE
)
+19 -7
View File
@@ -25,8 +25,10 @@
#include <QPainter>
#include <QtMath>
#include "config/config.h"
#include "common/configwrapper.h"
#include "engineeventbridge.h"
#include "timeline/timelinecommon.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -36,6 +38,8 @@ namespace olive
AudioWaveformView::AudioWaveformView(QWidget *parent)
: super(parent)
, playback_(nullptr)
, waveform_bridge_(nullptr)
, waveform_subscription_(0)
{
setAutoFillBackground(true);
setBackgroundRole(QPalette::Base);
@@ -46,6 +50,13 @@ AudioWaveformView::AudioWaveformView(QWidget *parent)
// originates from the center. But we're leaving it top/left for now since it was just
// ported from a QWidget's paintEvent.
setAlignment(Qt::AlignLeft | Qt::AlignTop);
// The connected-waveform notification arrives through the liboakengine
// event C ABI (replaces the old ViewerOutput::connected_waveform_changed
// connect); set_viewer() only subscribes/unsubscribes on the handle.
waveform_bridge_ = new EngineEventBridge(this);
connect(waveform_bridge_, &EngineEventBridge::viewer_connected_waveform_changed,
this, [this](OakEngineNode *) { viewport()->update(); });
}
void AudioWaveformView::set_viewer(ViewerOutput *playback)
@@ -54,9 +65,8 @@ void AudioWaveformView::set_viewer(ViewerOutput *playback)
pool_.clear();
pool_.waitForDone();
disconnect(playback_, &ViewerOutput::connected_waveform_changed,
viewport(),
static_cast<void (QWidget::*)()>(&QWidget::update));
waveform_bridge_->unsubscribe(waveform_subscription_);
waveform_subscription_ = 0;
set_timebase(0);
}
@@ -64,10 +74,12 @@ void AudioWaveformView::set_viewer(ViewerOutput *playback)
playback_ = playback;
if (playback_) {
connect(playback_, &ViewerOutput::connected_waveform_changed, viewport(),
static_cast<void (QWidget::*)()>(&QWidget::update));
waveform_subscription_ = waveform_bridge_->subscribe(
reinterpret_cast<OakEngineNode *>(playback_),
OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED);
Rational tb = playback_->get_video_params().frame_rate_as_time_base();
Rational tb =
viewer_output_video_params(playback_).frame_rate_as_time_base();
if (tb.isNull()) {
tb = OAK_CONFIG("DefaultSequenceFrameRate")
.value<Rational>()
+8
View File
@@ -31,6 +31,8 @@
namespace olive
{
class EngineEventBridge;
class AudioWaveformView : public SeekableWidget {
Q_OBJECT
public:
@@ -45,6 +47,12 @@ private:
QThreadPool pool_;
ViewerOutput *playback_;
// Engine event bridge for the viewer's connected-waveform notification
// (facade C ABI); subscription is per-viewer in set_viewer().
EngineEventBridge *waveform_bridge_;
int64_t waveform_subscription_;
};
}
+20 -5
View File
@@ -24,8 +24,10 @@
#include <QDrag>
#include <QMimeData>
#include "config/config.h"
#include "common/configwrapper.h"
#include "node/project.h"
#include "oakengine/project.h"
#include "oakengine/timeline.h"
namespace olive
{
@@ -48,13 +50,26 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent)
connect(controls_, &PlaybackControls::audio_dragged, this,
&FootageViewerWidget::start_audio_drag);
override_workarea_ = new TimelineWorkArea(this);
override_workarea_ = reinterpret_cast<TimelineWorkArea *>(
oakengine_workarea_create());
}
FootageViewerWidget::~FootageViewerWidget()
{
if (override_workarea_) {
oakengine_workarea_free(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_));
}
}
void FootageViewerWidget::override_work_area(const TimeRange &r)
{
override_workarea_->set_enabled(true);
override_workarea_->set_range(r);
oakengine_workarea_set_enabled(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_), 1);
oakengine_workarea_set_range(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_),
r.in().numerator(), r.in().denominator(),
r.out().numerator(), r.out().denominator());
this->connect_work_area(override_workarea_);
}
@@ -99,7 +114,7 @@ void FootageViewerWidget::start_footage_drag_internal(bool enable_video,
data_stream << streams
<< reinterpret_cast<quintptr>(get_connected_node());
mimedata->setData(Project::k_item_mime_type, encoded_data);
mimedata->setData(QString::fromUtf8(oakengine_project_item_mime_type()), encoded_data);
drag->setMimeData(mimedata);
drag->exec();
+2
View File
@@ -33,6 +33,8 @@ class FootageViewerWidget : public ViewerWidget {
public:
FootageViewerWidget(QWidget *parent = nullptr);
~FootageViewerWidget() override;
void override_work_area(const TimeRange &r);
void reset_work_area();
File diff suppressed because it is too large Load Diff
+70 -37
View File
@@ -29,11 +29,11 @@
#include <QTimer>
#include <QWidget>
#include "audio/audioprocessor.h"
#include "oakengine/audio.h"
#include "audiowaveformview.h"
#include "node/output/viewer/viewer.h"
#include "oakengine/playback.h"
#include "render/previewautocacher.h"
#include "render/previewaudiodevice.h"
#include "oakengine/preview.h"
#include "viewerdisplay.h"
#include "viewersizer.h"
#include "viewerwindow.h"
@@ -44,6 +44,7 @@
namespace olive
{
class EngineEventBridge;
class MulticamWidget;
/**
@@ -91,7 +92,7 @@ public:
*/
void set_full_screen(QScreen *screen = nullptr);
ColorManager *color_manager() const
OakEngineColorManager *color_manager() const
{
return display_widget_->color_manager();
}
@@ -122,9 +123,13 @@ public:
}
}
void set_node_view_selections(const QVector<Node *> &n)
void set_node_view_selections(const QVector<OakEngineNode *> &n)
{
node_view_selected_ = n;
node_view_selected_.clear();
node_view_selected_.reserve(n.size());
foreach (OakEngineNode *handle, n) {
node_view_selected_.append(reinterpret_cast<Node *>(handle));
}
if (!is_playing()) {
// If is playing, this will happen by the next frame automatically
@@ -184,12 +189,12 @@ signals:
/**
* @brief Wrapper for ViewerGLWidget::ColorProcessorChanged()
*/
void color_processor_changed(ColorProcessorPtr processor);
void color_processor_changed(ColorProcessorHandlePtr processor);
/**
* @brief Wrapper for ViewerGLWidget::ColorManagerChanged()
*/
void color_manager_changed(ColorManager *color_manager);
void color_manager_changed(OakEngineColorManager *color_manager);
protected:
ViewerWidget(ViewerDisplayWidget *display, QWidget *parent = nullptr);
@@ -219,7 +224,7 @@ protected:
ignore_scrub_++;
}
RenderTicketPtr get_single_frame(const Rational &t, bool dry = false);
OakEnginePreviewRequest *get_single_frame(const Rational &t, bool dry = false);
void set_waveform_mode(WaveformMode wf);
@@ -250,16 +255,15 @@ private:
bool viewer_might_be_a_still();
void set_display_image(RenderTicketPtr ticket);
void set_display_image(OakEnginePreviewRequest *req);
RenderTicketPtr get_frame(const Rational &t);
OakEnginePreviewRequest *request_next_frame_for_queue(bool increment = true);
static FramePtr decode_cached_image(const QString &cache_path,
const QUuid &cache_id,
const int64_t &time);
OakEnginePreviewRequest *get_frame(const Rational &t);
static void decode_cached_image(RenderTicketPtr ticket,
const QString &cache_path,
const QUuid &cache_id, const int64_t &time);
void finish_play_preprocess();
int determine_playback_queue_size();
bool should_force_waveform() const;
@@ -267,6 +271,8 @@ private:
void update_auto_cacher();
void decrement_prequeued_audio();
void arm_for_recording();
void disarm_recording();
@@ -277,15 +283,6 @@ private:
bool is_video_visible() const;
void deliver_facade_frame(const FramePtr &frame, const Rational &ts);
// Trampolines for the facade playback engine's pull-thread
// callbacks; both marshal onto the main thread.
static void facade_frame_callback(const oak_playback_frame *frame,
void *userdata);
static void facade_audio_callback(const oak_playback_audio *audio,
void *userdata);
ViewerSizer *sizer_;
int playback_speed_;
@@ -306,24 +303,33 @@ private:
ViewerDisplayWidget *context_menu_widget_;
// Facade playback session (created on first play; the engine drives
// frames/audio from its pull thread and we poll its position).
OakEnginePlayback *playback_;
QTimer playback_poll_timer_;
// Sample-rate/channel converter for the audio SCRUB path (continuous
// playback audio is handled by the facade engine itself).
AudioProcessor audio_processor_;
QTimer playback_backup_timer_;
int64_t playback_queue_next_frame_;
int64_t dry_run_next_frame_;
QVector<ViewerDisplayWidget *> playback_devices_;
QList<RenderTicketWatcher *> nonqueue_watchers_;
bool prequeuing_video_;
int prequeuing_audio_;
QList<OakEnginePreviewRequest *> nonqueue_watchers_;
Rational last_length_;
int prequeue_length_;
int prequeue_count_;
QVector<OakEnginePreviewRequest *> queue_watchers_;
std::list<OakEnginePreviewRequest *> audio_playback_queue_;
Rational audio_playback_queue_time_;
OakEngineAudioProcessor *audio_processor_;
QByteArray prequeued_audio_;
static const Rational k_audio_playback_interval;
static QVector<ViewerWidget *> instances;
std::list<RenderTicketWatcher *> audio_scrub_watchers_;
std::list<OakEnginePreviewRequest *> audio_scrub_watchers_;
bool record_armed_;
bool recording_;
@@ -332,10 +338,15 @@ private:
Track::Reference recording_track_;
QString recording_filename_;
qint64 queue_starved_start_;
OakEnginePreviewRequest *first_requeue_watcher_;
bool enable_audio_scrubbing_;
WaveformMode waveform_mode_;
QVector<OakEnginePreviewRequest *> dry_run_watchers_;
int ignore_scrub_;
QVector<Block *> timeline_selected_blocks_;
@@ -343,12 +354,16 @@ private:
MulticamWidget *multicam_panel_;
EngineEventBridge *bridge_;
int64_t audio_notify_sub_ = 0;
private slots:
void playback_poll_update();
void playback_timer_update();
void length_changed_slot(const Rational &length);
void interlacing_changed_slot(VideoParams::Interlacing interlacing);
void interlacing_changed_slot(int interlacing);
void update_renderer_video_parameters();
@@ -374,6 +389,8 @@ private slots:
void renderer_generated_frame();
void renderer_generated_frame_for_queue();
void viewer_invalidated_video_range(const olive::TimeRange &range);
void update_waveform_mode_from_menu(QAction *a);
@@ -382,14 +399,30 @@ private slots:
void dropped(QDropEvent *event);
void queue_next_audio_buffer();
void received_audio_buffer_for_playback();
void received_audio_buffer_for_scrubbing();
void queue_starved();
void queue_no_longer_starved();
void force_requeue_from_current_time();
void force_requeue_from_current_time_internal();
void update_audio_processor();
void create_addable_at(const QRectF &f);
void handle_first_requeue_destroy();
void show_subtitle_properties();
void dry_run_finished();
void request_next_dry_run();
void save_frame_as_image();
void detect_multicam_node_now();
+147 -100
View File
@@ -35,10 +35,16 @@
#include <QScreen>
#include <QTextEdit>
#include "audio/audiomanager.h"
#include "common/define.h"
#include "common/html.h"
#include "oakengine/gizmo.h"
#include "oakengine/videoparams.h"
#include "oakengine/display.h"
#include "oakengine/undo.h"
#include "widget/viewer/vieweroutpututils.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/subtitle/subtitle.h"
#include "codec/frame.h"
@@ -46,6 +52,7 @@
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
#include "render/job/colortransformjob.h"
#include "window/mainwindow/mainwindow.h"
namespace olive
@@ -72,6 +79,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
, add_band_(false)
, queue_starved_(false)
, text_edit_(nullptr)
, gizmo_params_(empty_video_params())
{
connect(Core::instance(), &Core::tool_changed, this,
&ViewerDisplayWidget::tool_changed);
@@ -83,6 +91,10 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent)
frame_rate_averages_.resize(k_frame_rate_average_count);
inner_widget()->setAcceptDrops(true);
bridge_ = new EngineEventBridge(this);
connect(bridge_, &EngineEventBridge::sequence_subtitles_changed, this,
[this](OakEngineSequence *, qint64, qint64) { update(); });
}
ViewerDisplayWidget::~ViewerDisplayWidget()
@@ -223,15 +235,16 @@ void ViewerDisplayWidget::set_time(const Rational &time)
void ViewerDisplayWidget::set_subtitle_tracks(Sequence *list)
{
if (subtitle_tracks_) {
disconnect(subtitle_tracks_, &Sequence::subtitles_changed, this,
&ViewerDisplayWidget::subtitles_changed);
bridge_->unsubscribe(subtitle_sub_);
subtitle_sub_ = 0;
}
subtitle_tracks_ = list;
if (subtitle_tracks_) {
connect(subtitle_tracks_, &Sequence::subtitles_changed, this,
&ViewerDisplayWidget::subtitles_changed);
subtitle_sub_ = bridge_->subscribe(
reinterpret_cast<void *>(subtitle_tracks_),
OAKENGINE_EVENT_SEQUENCE_SUBTITLES_CHANGED);
}
update();
@@ -396,7 +409,7 @@ void ViewerDisplayWidget::on_paint()
bg_color.greenF(), bg_color.blueF());
}
VideoParams device_params;
VideoParams device_params = empty_video_params();
ColorTransformJob ctj;
bool have_ctj = false;
@@ -420,11 +433,12 @@ void ViewerDisplayWidget::on_paint()
texture_->height() != frame->height() ||
texture_->format() != frame->format() ||
texture_->channel_count() != frame->channel_count())) {
texture_ = renderer()->create_texture(
frame->video_params(), frame->data(),
frame->linesize_pixels());
oakengine_display_renderer_create_texture(
renderer(), &frame->video_params(), frame->data(),
frame->linesize_pixels(), &texture_);
} else if (!drew_backend_neutral_frame) {
texture_->upload(frame->data(), frame->linesize_pixels());
oakengine_display_texture_upload(texture_.get(), frame->data(),
frame->linesize_pixels());
}
} else if (TexturePtr texture = load_frame_.value<TexturePtr>()) {
// This is a GPU texture, switch to it directly when possible.
@@ -439,15 +453,17 @@ void ViewerDisplayWidget::on_paint()
texture_ = texture;
} else {
// Cross-backend texture: download and re-upload
FramePtr frame = Frame::create();
frame->set_video_params(texture->params());
if (frame->allocate()) {
FramePtr frame;
oakengine_codec_frame_create(&frame);
oakengine_codec_frame_set_video_params(
frame.get(), &texture->params());
if (oakengine_codec_frame_allocate(frame.get())) {
texture->renderer()->download_from_texture(
texture->id(), texture->params(), frame->data(),
frame->linesize_pixels());
texture_ = renderer()->create_texture(
frame->video_params(), frame->data(),
frame->linesize_pixels());
oakengine_display_renderer_create_texture(
renderer(), &frame->video_params(), frame->data(),
frame->linesize_pixels(), &texture_);
} else {
texture_ = texture;
}
@@ -488,8 +504,9 @@ void ViewerDisplayWidget::on_paint()
deinterlace_texture_->params() !=
texture_to_draw->params()) {
// (Re)create texture
deinterlace_texture_ = renderer()->create_texture(
texture_to_draw->params());
oakengine_display_renderer_create_texture(
renderer(), &texture_to_draw->params(), nullptr,
0, &deinterlace_texture_);
}
ShaderJob job;
@@ -509,7 +526,8 @@ void ViewerDisplayWidget::on_paint()
texture_to_draw = deinterlace_texture_;
}
ctj.set_color_processor(color_service());
oakengine_color_transform_job_set_processor(
&ctj, color_service().get());
ctj.set_input_texture(texture_to_draw);
ctj.set_input_alpha_association(
OAK_CONFIG("ReassocLinToNonLin").toBool() ?
@@ -531,7 +549,8 @@ void ViewerDisplayWidget::on_paint()
if (backend_neutral) {
draw_backend_neutral(ctj, &bg_painter);
} else {
renderer()->blit_color_managed(ctj, device_params);
oakengine_display_renderer_blit_color_managed(
renderer(), &ctj, nullptr, &device_params);
}
}
@@ -789,20 +808,25 @@ QTransform ViewerDisplayWidget::generate_display_transform()
return gizmo_transform;
}
QTransform ViewerDisplayWidget::generate_gizmo_transform(NodeTraverser &gt,
QTransform ViewerDisplayWidget::generate_gizmo_transform(Node *gizmos,
Node *target,
const TimeRange &range)
{
QTransform t = generate_display_transform();
if (get_time_target()) {
Node *target = get_time_target();
if (target) {
if (ViewerOutput *v = dynamic_cast<ViewerOutput *>(target)) {
if (Node *n = v->get_connected_texture_output()) {
target = n;
}
}
QTransform nt;
gt.transform(&nt, gizmos_, target, range);
double m[6];
oakengine_traverse_transform(
reinterpret_cast<OakEngineNode *>(gizmos),
reinterpret_cast<OakEngineNode *>(target),
range.in().numerator(), range.in().denominator(),
range.out().numerator(), range.out().denominator(), nullptr, m);
QTransform nt(m[0], m[1], m[2], m[3], m[4], m[5]);
t.translate(gizmo_params_.width() * 0.5, gizmo_params_.height() * 0.5);
t.scale(gizmo_params_.width(), gizmo_params_.height());
@@ -861,8 +885,6 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
gizmo_draw_time_, LoopMode::k_loop_mode_off));
active_text_gizmo_ = text;
connect(active_text_gizmo_, &TextGizmo::rect_changed, this,
&ViewerDisplayWidget::update_active_text_gizmo_size);
text_transform_ = generate_gizmo_transform();
text_transform_inverted_ = text_transform_.inverted();
@@ -899,16 +921,30 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
QRectF text_rect = update_active_text_gizmo_size();
// Emit text gizmo activation signal
emit text->activated();
oakengine_text_gizmo_activated(
reinterpret_cast<OakEngineNode *>(gizmos_));
// Create toolbar
text_toolbar_ = new ViewerTextEditorToolBar(text_edit_);
text_toolbar_->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint);
connect(text_toolbar_, &ViewerTextEditorToolBar::vertical_alignment_changed,
text, &TextGizmo::set_vertical_alignment);
connect(text, &TextGizmo::vertical_alignment_changed, text_toolbar_,
&ViewerTextEditorToolBar::set_vertical_alignment);
text_toolbar_->set_vertical_alignment(text->get_vertical_alignment());
this, [this](Qt::Alignment align) {
oakengine_text_gizmo_set_vertical_alignment(
reinterpret_cast<OakEngineNode *>(gizmos_),
static_cast<int>(align));
});
{
oakengine_text_gizmo _tg;
if (oakengine_text_gizmo_get(
reinterpret_cast<OakEngineNode *>(gizmos_),
get_gizmo_time().numerator(), get_gizmo_time().denominator(),
&_tg) == OAKENGINE_OK) {
text_toolbar_->set_vertical_alignment(
static_cast<Qt::AlignmentFlag>(_tg.vertical_alignment));
} else {
text_toolbar_->set_vertical_alignment(Qt::AlignTop);
}
}
text_edit_->connect_tool_bar(text_toolbar_);
QPoint toolbar_pos =
@@ -1033,40 +1069,41 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event)
} else if (current_gizmo_) {
// Signal movement
if (DraggableGizmo *draggable =
dynamic_cast<DraggableGizmo *>(current_gizmo_)) {
int drag_behavior = oakengine_gizmo_get_drag_value_behavior(current_gizmo_);
if (drag_behavior >= 0) {
if (!gizmo_drag_started_) {
QPointF start = screen_to_scene_point(gizmo_start_drag_);
Rational gizmo_time = get_gizmo_time();
NodeTraverser t;
t.set_cache_video_params(gizmo_params_);
t.set_cache_audio_params(gizmo_audio_params_);
NodeValueRow row = t.generate_row(
gizmos_,
TimeRange(gizmo_time,
gizmo_time +
gizmo_params_.frame_rate_as_time_base()));
NodeValueRow row;
oakengine_traverse_generate_row(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmo_time.numerator(), gizmo_time.denominator(),
(gizmo_time + gizmo_params_.frame_rate_as_time_base())
.numerator(),
(gizmo_time + gizmo_params_.frame_rate_as_time_base())
.denominator(),
nullptr, 0, 0, &row);
draggable->drag_start(row, start.x(), start.y(), gizmo_time);
oakengine_gizmo_drag_start(current_gizmo_, &row,
start.x(), start.y(), gizmo_time.numerator(),
gizmo_time.denominator());
gizmo_drag_started_ = true;
}
QPointF v = screen_to_scene_point(event->pos());
switch (draggable->get_drag_value_behavior()) {
case DraggableGizmo::k_absolute:
// Above value is correct
break;
case DraggableGizmo::k_delta_from_previous:
switch (drag_behavior) {
case 1:
v -= screen_to_scene_point(gizmo_last_drag_);
gizmo_last_drag_ = event->pos();
break;
case DraggableGizmo::k_delta_from_start:
case 2:
v -= screen_to_scene_point(gizmo_start_drag_);
break;
}
draggable->drag_move(v.x(), v.y(), event->modifiers());
oakengine_gizmo_drag_move(current_gizmo_, v.x(), v.y(),
static_cast<int>(event->modifiers()));
return true;
}
@@ -1101,12 +1138,9 @@ bool ViewerDisplayWidget::on_mouse_release(QMouseEvent *e)
} else if (current_gizmo_) {
// Handle gizmo
if (gizmo_drag_started_) {
MultiUndoCommand *command = new MultiUndoCommand();
if (DraggableGizmo *draggable =
dynamic_cast<DraggableGizmo *>(current_gizmo_)) {
draggable->drag_end(command);
}
Core::instance()->undo_stack()->push(command, tr("Dragged Gizmo"));
void *command = oakengine_undo_command_create_multi();
oakengine_gizmo_drag_end(current_gizmo_, command);
oakengine_undo_push(command, tr("Dragged Gizmo").toUtf8().constData());
gizmo_drag_started_ = false;
}
current_gizmo_ = nullptr;
@@ -1173,7 +1207,7 @@ void ViewerDisplayWidget::emit_color_at_cursor(QMouseEvent *e)
reference =
renderer()->get_pixel_from_texture(texture_.get(), pixel_pos);
if (color_service()) {
display = color_service()->convert_color(reference);
display = oak_convert_color(color_service(), reference);
} else {
display = reference;
}
@@ -1231,8 +1265,13 @@ void ViewerDisplayWidget::draw_subtitle_tracks()
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock *>(
sub_track->visible_block_at_time(time_))) {
// Split into lines
char text_buf[4096];
const int len = oakengine_subtitle_get_text(
reinterpret_cast<OakEngineNode *>(sub), text_buf,
sizeof(text_buf));
QStringList list = QtUtils::word_wrap_string(
sub->get_text(), fm, bounding_box.width());
QString::fromUtf8(text_buf, len), fm,
bounding_box.width());
for (int i = list.size() - 1; i >= 0; i--) {
int w = QtUtils::q_font_metrics_width(fm, list.at(i));
@@ -1393,24 +1432,26 @@ void ViewerDisplayWidget::close_text_editor()
text_edit_->deleteLater();
text_edit_ = nullptr;
disconnect(active_text_gizmo_, &TextGizmo::rect_changed, this,
&ViewerDisplayWidget::update_active_text_gizmo_size);
active_text_gizmo_ = nullptr;
}
void ViewerDisplayWidget::generate_gizmo_transforms()
{
NodeTraverser gt;
gt.set_cache_video_params(gizmo_params_);
gt.set_cache_audio_params(gizmo_audio_params_);
gizmo_draw_time_ = generate_gizmo_time();
if (gizmos_) {
gizmo_db_ = gt.generate_row(gizmos_, gizmo_draw_time_);
NodeValueRow row;
oakengine_traverse_generate_row(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmo_draw_time_.in().numerator(),
gizmo_draw_time_.in().denominator(),
gizmo_draw_time_.out().numerator(),
gizmo_draw_time_.out().denominator(), nullptr, 0, 0, &row);
gizmo_db_ = row;
}
gizmo_last_draw_transform_ = generate_gizmo_transform(gt, gizmo_draw_time_);
gizmo_last_draw_transform_ = generate_gizmo_transform(
gizmos_, get_time_target(), gizmo_draw_time_);
gizmo_last_draw_transform_inverted_ = gizmo_last_draw_transform_.inverted();
}
@@ -1437,7 +1478,9 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
return false;
}
const QString color_id = QString::fromUtf8(color_service()->id());
const QString color_id = oak_query_string([this](char *buf, int size) {
return oakengine_color_processor_id(color_service().get(), buf, size);
});
if (backend_neutral_cpu_source_frame_.get() == frame.get() &&
backend_neutral_cpu_color_id_ == color_id &&
!backend_neutral_cpu_image_.isNull()) {
@@ -1457,7 +1500,7 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
QImage source_image;
if (display_frame->format() == PixelFormat::u8 &&
display_frame->channel_count() == VideoParams::k_rgba_channel_count) {
display_frame->channel_count() == 4) {
backend_neutral_cpu_display_frame_ = display_frame;
backend_neutral_cpu_image_ =
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
@@ -1466,7 +1509,7 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
source_image = backend_neutral_cpu_image_;
} else if (display_frame->format() == PixelFormat::u8 &&
display_frame->channel_count() ==
VideoParams::k_rgb_channel_count) {
3) {
backend_neutral_cpu_display_frame_ = display_frame;
backend_neutral_cpu_image_ =
QImage(reinterpret_cast<const uchar *>(display_frame->const_data()),
@@ -1476,7 +1519,9 @@ bool ViewerDisplayWidget::draw_backend_neutral_frame(const FramePtr &frame,
} else {
backend_neutral_cpu_display_frame_.reset();
const int bytes_per_pixel =
display_frame->video_params().get_bytes_per_pixel();
oakengine_video_params_bytes_per_pixel(
static_cast<int>(display_frame->video_params().format()),
display_frame->video_params().channel_count());
if (backend_neutral_cpu_image_.size() !=
QSize(display_frame->width(), display_frame->height()) ||
backend_neutral_cpu_image_.format() != QImage::Format_RGBA8888) {
@@ -1523,7 +1568,9 @@ bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture
return false;
}
const QString color_id = QString::fromUtf8(color_service()->id());
const QString color_id = oak_query_string([this](char *buf, int size) {
return oakengine_color_processor_id(color_service().get(), buf, size);
});
if (backend_neutral_cpu_source_texture_.get() == texture.get() &&
backend_neutral_cpu_color_id_ == color_id &&
!backend_neutral_cpu_image_.isNull()) {
@@ -1535,13 +1582,15 @@ bool ViewerDisplayWidget::draw_backend_neutral_texture(const TexturePtr &texture
return true;
}
FramePtr frame = Frame::create();
frame->set_video_params(texture->params());
if (!frame->allocate()) {
FramePtr frame;
oakengine_codec_frame_create(&frame);
oakengine_codec_frame_set_video_params(frame.get(), &texture->params());
if (!oakengine_codec_frame_allocate(frame.get())) {
return false;
}
texture->download(frame->data(), frame->linesize_pixels());
oakengine_display_texture_download(texture.get(), frame->data(),
frame->linesize_pixels());
if (!draw_backend_neutral_frame(frame, painter)) {
return false;
@@ -1564,19 +1613,23 @@ void ViewerDisplayWidget::draw_backend_neutral(const ColorTransformJob &ctj,
const int texture_width = static_cast<int>(width() * devicePixelRatioF());
const int texture_height = static_cast<int>(height() * devicePixelRatioF());
const VideoParams offscreen_params(texture_width, texture_height,
PixelFormat::u8,
VideoParams::k_rgba_channel_count);
oak_video_params pod = {};
pod.width = texture_width;
pod.height = texture_height;
pod.format = PixelFormat::u8;
const VideoParams offscreen_params(video_params_from_pod(pod));
if (!backend_neutral_texture_ ||
backend_neutral_texture_->params() != offscreen_params) {
// The offscreen texture is sized in device pixels so high-DPI widgets
// draw one downloaded pixel per device pixel after setDevicePixelRatio().
backend_neutral_texture_ = renderer()->create_texture(offscreen_params);
oakengine_display_renderer_create_texture(renderer(), &offscreen_params,
nullptr, 0,
&backend_neutral_texture_);
backend_neutral_buffer_.resize(
texture_width * texture_height *
VideoParams::get_bytes_per_pixel(PixelFormat::u8,
VideoParams::k_rgba_channel_count));
oakengine_video_params_bytes_per_pixel(0, // PixelFormat::u8
4));
}
if (!backend_neutral_texture_ || backend_neutral_texture_->is_dummy()) {
@@ -1588,12 +1641,13 @@ void ViewerDisplayWidget::draw_backend_neutral(const ColorTransformJob &ctj,
// Reuse the normal color-management shader path, but render into a texture
// instead of an OpenGL widget framebuffer.
renderer()->blit_color_managed(local_ctj, backend_neutral_texture_.get());
oakengine_display_renderer_blit_color_managed(
renderer(), &local_ctj, backend_neutral_texture_.get(), nullptr);
backend_neutral_texture_->download(backend_neutral_buffer_.data(), 0);
oakengine_display_texture_download(backend_neutral_texture_.get(),
backend_neutral_buffer_.data(), 0);
const int bytes_per_pixel = VideoParams::get_bytes_per_pixel(
PixelFormat::u8, VideoParams::k_rgba_channel_count);
const int bytes_per_pixel = oakengine_video_params_bytes_per_pixel(0, 4); // u8, RGBA
QImage img(
reinterpret_cast<const uchar *>(backend_neutral_buffer_.constData()),
@@ -1632,10 +1686,7 @@ void ViewerDisplayWidget::play(const int64_t &start_timestamp,
playback_timebase_ = timebase;
playback_speed_ = playback_speed;
// The facade playback engine owns the master clock; seed the display
// clock with the start timestamp (the ViewerWidget keeps feeding it
// from oakengine_playback_get_position afterwards).
external_ts_.store(start_timestamp);
timer_.start(start_timestamp, playback_speed, timebase.to_double());
if (start_updating) {
connect(this, &ViewerDisplayWidget::frame_swapped, this,
@@ -1650,8 +1701,6 @@ void ViewerDisplayWidget::pause()
disconnect(this, &ViewerDisplayWidget::frame_swapped, this,
&ViewerDisplayWidget::update_from_queue);
external_ts_.store(-1);
queue_.clear();
queue_starved_ = false;
}
@@ -1667,11 +1716,7 @@ QPointF ViewerDisplayWidget::screen_to_scene_point(const QPoint &p)
void ViewerDisplayWidget::update_from_queue()
{
const int64_t t = external_ts_.load();
if (t < 0) {
// No facade playback position fed yet.
return;
}
int64_t t = timer_.get_timestamp_now();
Rational time = Timecode::timestamp_to_time(t, playback_timebase_);
@@ -1731,14 +1776,16 @@ void ViewerDisplayWidget::text_edit_changed()
editor->property("gizmo").value<quintptr>());
QString html = Html::doc_to_html(editor->document());
gizmo->update_input_html(html, get_gizmo_time());
oakengine_text_gizmo_update_html(
reinterpret_cast<OakEngineNode *>(gizmos_),
html.toUtf8().constData(),
get_gizmo_time().numerator(), get_gizmo_time().denominator());
}
void ViewerDisplayWidget::text_edit_destroyed()
{
TextGizmo *gizmo = reinterpret_cast<TextGizmo *>(
sender()->property("gizmo").value<quintptr>());
emit gizmo->deactivated();
oakengine_text_gizmo_deactivated(
reinterpret_cast<OakEngineNode *>(gizmos_));
text_edit_ = nullptr;
text_toolbar_ = nullptr;
inner_widget()->setMouseTracking(false);
+15 -19
View File
@@ -22,19 +22,20 @@
#ifndef OAK_VIEWERGLWIDGET_H
#define OAK_VIEWERGLWIDGET_H
#include <atomic>
#include <QImage>
#include <QMatrix4x4>
#include <QRubberBand>
#include "codec/frame.h"
#include "engineeventbridge.h"
#include "node/color/colormanager/colormanager.h"
#include "node/gizmo/text.h"
#include "node/node.h"
#include "node/output/track/tracklist.h"
#include "node/traverser.h"
#include "node/value.h"
#include "oakengine/traverse.h"
#include "tool/tool.h"
#include "viewerplaybacktimer.h"
#include "viewerqueue.h"
#include "viewersafemargininfo.h"
#include "viewertexteditor.h"
@@ -140,7 +141,7 @@ public:
return texture_;
}
ColorProcessorPtr get_current_color_processor()
ColorProcessorHandlePtr get_current_color_processor()
{
return color_service();
}
@@ -155,16 +156,9 @@ public:
return &queue_;
}
/**
* @brief Feed the facade playback position as this display's clock
*
* The facade playback engine owns the master clock now; the
* ViewerWidget polls it (oakengine_playback_get_position) and pushes
* the timestamp here for update_from_queue() to pop by.
*/
void set_playback_timestamp(int64_t ts)
ViewerPlaybackTimer *timer()
{
external_ts_.store(ts);
return &timer_;
}
QPointF screen_to_scene_point(const QPoint &p);
@@ -261,13 +255,12 @@ protected:
QTransform generate_display_transform();
QTransform generate_gizmo_transform(NodeTraverser &gt,
const TimeRange &range);
QTransform generate_gizmo_transform(Node *gizmos, Node *target,
const TimeRange &range);
QTransform generate_gizmo_transform()
{
NodeTraverser t;
t.set_cache_video_params(gizmo_params_);
return generate_gizmo_transform(t, generate_gizmo_time());
TimeRange range = generate_gizmo_time();
return generate_gizmo_transform(gizmos_, get_time_target(), range);
}
TimeRange generate_gizmo_time()
@@ -425,6 +418,9 @@ private:
bool show_subtitles_;
Sequence *subtitle_tracks_;
EngineEventBridge *bridge_;
int64_t subtitle_sub_ = 0;
Rational time_;
/**
@@ -469,7 +465,7 @@ private:
// Playback
ViewerQueue queue_;
std::atomic<int64_t> external_ts_{ -1 };
ViewerPlaybackTimer timer_;
Rational playback_timebase_;
+76
View File
@@ -0,0 +1,76 @@
/***
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/>.
***/
#include "vieweroutpututils.h"
namespace olive {
VideoParams video_params_from_pod(const oak_video_params &pod)
{
void *vp = oakengine_video_params_create(&pod);
VideoParams result = *static_cast<VideoParams *>(vp);
oakengine_video_params_free(vp);
return result;
}
VideoParams empty_video_params()
{
oak_video_params pod = {};
return video_params_from_pod(pod);
}
Rational sequence_timebase(const void *sequence)
{
oak_video_params vp = {};
if (oakengine_viewer_get_video_params(
reinterpret_cast<const OakEngineNode *>(sequence), 0, &vp) < 0 ||
vp.time_base_den <= 0) {
return Rational();
}
return Rational(vp.time_base_num, vp.time_base_den);
}
VideoParams viewer_output_video_params(const void *viewer, int index)
{
oak_video_params vp;
if (oakengine_viewer_get_video_params(
reinterpret_cast<const OakEngineNode *>(viewer), index, &vp) < 0 ||
vp.width <= 0 || vp.height <= 0) {
return empty_video_params();
}
return video_params_from_pod(vp);
}
AudioParams viewer_output_audio_params(const void *viewer, int index)
{
int sample_rate = 0;
int format = 0;
uint64_t channel_layout = 0;
if (oakengine_viewer_get_audio_params(
reinterpret_cast<const OakEngineNode *>(viewer), index, &sample_rate,
&channel_layout, &format) < 0 ||
sample_rate <= 0) {
return AudioParams();
}
return AudioParams(sample_rate, channel_layout,
static_cast<SampleFormat::Format>(format));
}
} // namespace olive
+103
View File
@@ -0,0 +1,103 @@
/***
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 VIEWEROUTPUTUTILS_H
#define VIEWEROUTPUTUTILS_H
#include <olive/core/render/audioparams.h>
#include <cstring>
#include "oakengine/viewer.h"
#include "render/subtitleparams.h"
#include "render/videoparams.h"
/**
* @file vieweroutpututils.h
* @brief Facade-backed replacements for ViewerOutput's header-inline
* parameter getters
*
* ViewerOutput::get_video_params()/get_audio_params()/... are defined inline
* in the engine header and reference the ViewerOutput::k_*_params_input
* statics, so calling them from the app would leave undefined ViewerOutput
* symbols in oak-editor. These helpers fetch the same values through the
* oakengine C ABI instead. `viewer` may be any viewer handle (ViewerOutput,
* Sequence, Footage).
*/
namespace olive {
VideoParams viewer_output_video_params(const void *viewer, int index = 0);
AudioParams viewer_output_audio_params(const void *viewer, int index = 0);
/**
* @brief Construct a VideoParams from an oak_video_params POD using the engine
* facade. This is the single app-side construction point allowed during the R6
* C ABI migration; all other app code must not call VideoParams constructors
* directly.
*/
VideoParams video_params_from_pod(const oak_video_params &pod);
/** @brief Equivalent to the default-constructed VideoParams(). */
VideoParams empty_video_params();
/**
* @brief Frame-duration timebase of `sequence` as a Rational (frame rate flipped).
*
* Facade-backed replacement for ViewerOutput::get_video_params().frame_rate().
* flipped(), used for Rational -> timestamp conversions without pulling in
* ViewerOutput inline symbols.
*/
Rational sequence_timebase(const void *sequence);
/**
* @brief 1 if `node`'s engine type id (Node::id()) equals `type_id`.
* Facade-backed replacement for dynamic_cast-based type probes (a
* dynamic_cast to/from ViewerOutput drags an undefined ViewerOutput
* typeinfo reference into the app binary).
*/
inline bool viewer_output_node_type_is(const void *node, const char *type_id)
{
if (!node) {
return false;
}
char buf[128];
const int len = oakengine_node_get_type_id(
reinterpret_cast<const OakEngineNode *>(node), buf, sizeof(buf));
return len >= 0 && len < int(sizeof(buf)) && strcmp(buf, type_id) == 0;
}
/** @brief 1 if `node` is a Sequence (Sequence::id()). */
inline bool viewer_output_is_sequence(const void *node)
{
return viewer_output_node_type_is(node,
"org.olivevideoeditor.Olive.sequence");
}
/** @brief 1 if `node` is a Footage (Footage::id()). */
inline bool viewer_output_is_footage(const void *node)
{
return viewer_output_node_type_is(node,
"org.olivevideoeditor.Olive.footage");
}
} // namespace olive
#endif // VIEWEROUTPUTUTILS_H
+45
View File
@@ -0,0 +1,45 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive 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/>.
***/
#include "viewerplaybacktimer.h"
#include <QDateTime>
namespace olive
{
void ViewerPlaybackTimer::start(const int64_t &start_timestamp, const int &playback_speed, const double &timebase)
{
start_msec_ = QDateTime::currentMSecsSinceEpoch();
start_timestamp_ = start_timestamp;
playback_speed_ = playback_speed;
timebase_ = timebase;
}
int64_t ViewerPlaybackTimer::get_timestamp_now() const
{
int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_;
int64_t frames_since_start = qRound(static_cast<double>(real_time) / (timebase_ * 1000));
return start_timestamp_ + frames_since_start * playback_speed_;
}
}
+49
View File
@@ -0,0 +1,49 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive 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 VIEWERPLAYBACKTIMER_H
#define VIEWERPLAYBACKTIMER_H
#include <QtGlobal>
#include "common/define.h"
namespace olive
{
class ViewerPlaybackTimer {
public:
void start(const int64_t& start_timestamp, const int& playback_speed, const double& timebase);
int64_t get_timestamp_now() const;
private:
qint64 start_msec_;
int64_t start_timestamp_;
int playback_speed_;
double timebase_;
};
}
#endif // VIEWERPLAYBACKTIMER_H