From 97a28ab54a63b7e8918fc52410bcee1a411ea763 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 26 Jan 2019 21:49:18 +1100 Subject: [PATCH] support for premultipled alpha --- dialogs/mediapropertiesdialog.cpp | 76 +++++--- dialogs/mediapropertiesdialog.h | 2 + effects/internal/transformeffect.cpp | 35 +--- io/loadthread.cpp | 2 + panels/project.cpp | 67 +++---- panels/viewer.h | 2 +- playback/cacher.cpp | 21 +- project/effect.cpp | 4 +- project/footage.cpp | 10 +- project/footage.h | 1 + project/undo.cpp | 12 +- project/undo.h | 6 + ui/renderfunctions.cpp | 280 ++++++++++++++++----------- ui/renderfunctions.h | 1 + ui/renderthread.cpp | 111 +++++++---- ui/renderthread.h | 12 +- ui/viewerwidget.cpp | 4 +- 17 files changed, 392 insertions(+), 254 deletions(-) diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 188187f3f..bacc74cea 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "project/footage.h" @@ -19,7 +20,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : QDialog(parent), item(i) { - setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); + setWindowTitle(tr("\"%1\" Properties").arg(i->get_name())); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QGridLayout* grid = new QGridLayout(); @@ -29,21 +30,21 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : Footage* f = item->to_footage(); - grid->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); + grid->addWidget(new QLabel(tr("Tracks:")), row, 0, 1, 2); row++; track_list = new QListWidget(); for (int i=0;ivideo_tracks.size();i++) { const FootageStream& fs = f->video_tracks.at(i); - QListWidgetItem* item = new QListWidgetItem( - tr("Video %1: %2x%3 %4FPS").arg( - QString::number(fs.file_index), - QString::number(fs.video_width), - QString::number(fs.video_height), - QString::number(fs.video_frame_rate) - ) - ); + QListWidgetItem* item = new QListWidgetItem( + tr("Video %1: %2x%3 %4FPS").arg( + QString::number(fs.file_index), + QString::number(fs.video_width), + QString::number(fs.video_height), + QString::number(fs.video_frame_rate) + ) + ); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole+1, fs.file_index); @@ -51,13 +52,13 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : } for (int i=0;iaudio_tracks.size();i++) { const FootageStream& fs = f->audio_tracks.at(i); - QListWidgetItem* item = new QListWidgetItem( - tr("Audio %1: %2Hz %3 channels").arg( - QString::number(fs.file_index), - QString::number(fs.audio_frequency), - QString::number(fs.audio_channels) - ) - ); + QListWidgetItem* item = new QListWidgetItem( + tr("Audio %1: %2Hz %3 channels").arg( + QString::number(fs.file_index), + QString::number(fs.audio_frequency), + QString::number(fs.audio_channels) + ) + ); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(fs.enabled ? Qt::Checked : Qt::Unchecked); item->setData(Qt::UserRole+1, fs.file_index); @@ -69,7 +70,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : if (f->video_tracks.size() > 0) { // frame conforming if (!f->video_tracks.at(0).infinite_length) { - grid->addWidget(new QLabel(tr("Conform to Frame Rate:")), row, 0); + grid->addWidget(new QLabel(tr("Conform to Frame Rate:")), row, 0); conform_fr = new QDoubleSpinBox(); conform_fr->setMinimum(0.01); conform_fr->setValue(f->video_tracks.at(0).video_frame_rate * f->speed); @@ -78,30 +79,37 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : row++; + // premultiplied alpha mode + premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied")); + premultiply_alpha_setting->setChecked(f->alpha_is_premultiplied); + grid->addWidget(premultiply_alpha_setting, row, 0); + + row++; + // deinterlacing mode - interlacing_box = new QComboBox(); - interlacing_box->addItem( - tr("Auto (%1)").arg( - get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) - ) - ); + interlacing_box = new QComboBox(); + interlacing_box->addItem( + tr("Auto (%1)").arg( + get_interlacing_name(f->video_tracks.at(0).video_auto_interlacing) + ) + ); interlacing_box->addItem(get_interlacing_name(VIDEO_PROGRESSIVE)); interlacing_box->addItem(get_interlacing_name(VIDEO_TOP_FIELD_FIRST)); interlacing_box->addItem(get_interlacing_name(VIDEO_BOTTOM_FIELD_FIRST)); - interlacing_box->setCurrentIndex( - (f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) - ? 0 - : f->video_tracks.at(0).video_interlacing + 1); + interlacing_box->setCurrentIndex( + (f->video_tracks.at(0).video_auto_interlacing == f->video_tracks.at(0).video_interlacing) + ? 0 + : f->video_tracks.at(0).video_interlacing + 1); - grid->addWidget(new QLabel(tr("Interlacing:")), row, 0); + grid->addWidget(new QLabel(tr("Interlacing:")), row, 0); grid->addWidget(interlacing_box, row, 1); row++; } name_box = new QLineEdit(item->get_name()); - grid->addWidget(new QLabel(tr("Name:")), row, 0); + grid->addWidget(new QLabel(tr("Name:")), row, 0); grid->addWidget(name_box, row, 1); row++; @@ -160,6 +168,9 @@ void MediaPropertiesDialog::accept() { refresh_clips = true; } } + + // set premultiplied alpha + f->alpha_is_premultiplied = premultiply_alpha_setting->isChecked(); } // set name @@ -168,7 +179,10 @@ void MediaPropertiesDialog::accept() { ca->append(mr); ca->appendPost(new CloseAllClipsCommand()); ca->appendPost(new UpdateFootageTooltip(item)); - if (refresh_clips) ca->appendPost(new RefreshClips(item)); + if (refresh_clips) { + ca->appendPost(new RefreshClips(item)); + } + ca->appendPost(new UpdateViewer()); undo_stack.push(ca); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index b5699dff7..fe58fa9e2 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -9,6 +9,7 @@ class QLineEdit; class Media; class QListWidget; class QDoubleSpinBox; +class QCheckBox; class MediaPropertiesDialog : public QDialog { Q_OBJECT @@ -20,6 +21,7 @@ private: Media* item; QListWidget* track_list; QDoubleSpinBox* conform_fr; + QCheckBox* premultiply_alpha_setting; private slots: void accept(); }; diff --git a/effects/internal/transformeffect.cpp b/effects/internal/transformeffect.cpp index 8f22d0970..6bbcf1cfe 100644 --- a/effects/internal/transformeffect.cpp +++ b/effects/internal/transformeffect.cpp @@ -205,11 +205,11 @@ void TransformEffect::toggle_uniform_scale(bool enabled) { void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, int) { // position - glTranslatef(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); + glTranslated(position_x->get_double_value(timecode)-(parent_clip->sequence->width/2), position_y->get_double_value(timecode)-(parent_clip->sequence->height/2), 0); // anchor point - int anchor_x_offset = (anchor_x_box->get_double_value(timecode)); - int anchor_y_offset = (anchor_y_box->get_double_value(timecode)); + int anchor_x_offset = qRound(anchor_x_box->get_double_value(timecode)); + int anchor_y_offset = qRound(anchor_y_box->get_double_value(timecode)); coords.vertexTopLeftX -= anchor_x_offset; coords.vertexTopRightX -= anchor_x_offset; coords.vertexBottomLeftX -= anchor_x_offset; @@ -220,37 +220,18 @@ void TransformEffect::process_coords(double timecode, GLTextureCoords& coords, i coords.vertexBottomRightY -= anchor_y_offset; // rotation - glRotatef(rotation->get_double_value(timecode), 0, 0, 1); + glRotated(rotation->get_double_value(timecode), 0, 0, 1); // scale - float sx = scale_x->get_double_value(timecode)*0.01; - float sy = (uniform_scale_field->get_bool_value(timecode)) ? sx : scale_y->get_double_value(timecode)*0.01; - glScalef(sx, sy, 1); + double sx = scale_x->get_double_value(timecode)*0.01; + double sy = (uniform_scale_field->get_bool_value(timecode)) ? sx : scale_y->get_double_value(timecode)*0.01; + glScaled(sx, sy, 1); // blend mode coords.blendmode = blend_mode_box->get_combo_data(timecode).toInt(); - /*switch (blend_mode_box->get_combo_data(timecode).toInt()) { - case BLEND_MODE_NORMAL: - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case BLEND_MODE_OVERLAY: - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case BLEND_MODE_SCREEN: - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR); - break; - case BLEND_MODE_MULTIPLY: - glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); - break; - default: - qCritical() << "Invalid blend mode. This is a bug - please contact developers"; - }*/ // opacity - coords.opacity *= opacity->get_double_value(timecode)*0.01; - /*float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); - glColor4f(1.0, 1.0, 1.0, color[3]*(opacity->get_double_value(timecode)*0.01));*/ + coords.opacity *= float(opacity->get_double_value(timecode)*0.01); } void TransformEffect::gizmo_draw(double, GLTextureCoords& coords) { diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 2da9aaf27..adfc3a510 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -248,6 +248,8 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { m->out = attr.value().toLong(); } else if (attr.name() == "speed") { m->speed = attr.value().toDouble(); + } else if (attr.name() == "alphapremul") { + m->alpha_is_premultiplied = (attr.value() == "1"); } } diff --git a/panels/project.cpp b/panels/project.cpp index fb6deffb8..fac48f0c1 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -83,67 +83,67 @@ Project::Project(QWidget *parent) : toolbar->setSpacing(0); toolbar_widget->setLayout(toolbar); - QPushButton* toolbar_new = new QPushButton(toolbar_widget); - QIcon icon1; - icon1.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On); - icon1.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_new->setIcon(icon1); + QPushButton* toolbar_new = new QPushButton(toolbar_widget); + QIcon icon1; + icon1.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On); + icon1.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_new->setIcon(icon1); toolbar_new->setToolTip("New"); connect(toolbar_new, SIGNAL(clicked(bool)), this, SLOT(make_new_menu())); toolbar->addWidget(toolbar_new); QPushButton* toolbar_open = new QPushButton(toolbar_widget); - QIcon icon2; - icon2.addFile(QStringLiteral(":/icons/open.png"), QSize(), QIcon::Normal, QIcon::On); - icon2.addFile(QStringLiteral(":/icons/open-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_open->setIcon(icon2); + QIcon icon2; + icon2.addFile(QStringLiteral(":/icons/open.png"), QSize(), QIcon::Normal, QIcon::On); + icon2.addFile(QStringLiteral(":/icons/open-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_open->setIcon(icon2); toolbar_open->setToolTip("Open Project"); connect(toolbar_open, SIGNAL(clicked(bool)), mainWindow, SLOT(open_project())); toolbar->addWidget(toolbar_open); QPushButton* toolbar_save = new QPushButton(toolbar_widget); - QIcon icon3; - icon3.addFile(QStringLiteral(":/icons/save.png"), QSize(), QIcon::Normal, QIcon::On); - icon3.addFile(QStringLiteral(":/icons/save-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_save->setIcon(icon3); + QIcon icon3; + icon3.addFile(QStringLiteral(":/icons/save.png"), QSize(), QIcon::Normal, QIcon::On); + icon3.addFile(QStringLiteral(":/icons/save-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_save->setIcon(icon3); toolbar_save->setToolTip("Save Project"); connect(toolbar_save, SIGNAL(clicked(bool)), mainWindow, SLOT(save_project())); toolbar->addWidget(toolbar_save); QPushButton* toolbar_undo = new QPushButton(toolbar_widget); - QIcon icon4; - icon4.addFile(QStringLiteral(":/icons/undo.png"), QSize(), QIcon::Normal, QIcon::On); - icon4.addFile(QStringLiteral(":/icons/undo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_undo->setIcon(icon4); + QIcon icon4; + icon4.addFile(QStringLiteral(":/icons/undo.png"), QSize(), QIcon::Normal, QIcon::On); + icon4.addFile(QStringLiteral(":/icons/undo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_undo->setIcon(icon4); toolbar_undo->setToolTip("Undo"); connect(toolbar_undo, SIGNAL(clicked(bool)), mainWindow, SLOT(undo())); toolbar->addWidget(toolbar_undo); QPushButton* toolbar_redo = new QPushButton(toolbar_widget); - QIcon icon5; - icon5.addFile(QStringLiteral(":/icons/redo.png"), QSize(), QIcon::Normal, QIcon::On); - icon5.addFile(QStringLiteral(":/icons/redo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_redo->setIcon(icon5); + QIcon icon5; + icon5.addFile(QStringLiteral(":/icons/redo.png"), QSize(), QIcon::Normal, QIcon::On); + icon5.addFile(QStringLiteral(":/icons/redo-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_redo->setIcon(icon5); toolbar_redo->setToolTip("Redo"); connect(toolbar_redo, SIGNAL(clicked(bool)), mainWindow, SLOT(redo())); toolbar->addWidget(toolbar_redo); toolbar->addStretch(); - QPushButton* toolbar_tree_view = new QPushButton(toolbar_widget); - QIcon icon6; - icon6.addFile(QStringLiteral(":/icons/treeview.png"), QSize(), QIcon::Normal, QIcon::On); - icon6.addFile(QStringLiteral(":/icons/treeview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_tree_view->setIcon(icon6); - toolbar_tree_view->setToolTip("Tree View"); - connect(toolbar_tree_view, SIGNAL(clicked(bool)), this, SLOT(set_tree_view())); - toolbar->addWidget(toolbar_tree_view); + QPushButton* toolbar_tree_view = new QPushButton(toolbar_widget); + QIcon icon6; + icon6.addFile(QStringLiteral(":/icons/treeview.png"), QSize(), QIcon::Normal, QIcon::On); + icon6.addFile(QStringLiteral(":/icons/treeview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_tree_view->setIcon(icon6); + toolbar_tree_view->setToolTip("Tree View"); + connect(toolbar_tree_view, SIGNAL(clicked(bool)), this, SLOT(set_tree_view())); + toolbar->addWidget(toolbar_tree_view); QPushButton* toolbar_icon_view = new QPushButton(toolbar_widget); - QIcon icon7; - icon7.addFile(QStringLiteral(":/icons/iconview.png"), QSize(), QIcon::Normal, QIcon::On); - icon7.addFile(QStringLiteral(":/icons/iconview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); - toolbar_icon_view->setIcon(icon7); + QIcon icon7; + icon7.addFile(QStringLiteral(":/icons/iconview.png"), QSize(), QIcon::Normal, QIcon::On); + icon7.addFile(QStringLiteral(":/icons/iconview-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); + toolbar_icon_view->setIcon(icon7); toolbar_icon_view->setToolTip("Icon View"); connect(toolbar_icon_view, SIGNAL(clicked(bool)), this, SLOT(set_icon_view())); toolbar->addWidget(toolbar_icon_view); @@ -953,6 +953,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("in", QString::number(f->in)); stream.writeAttribute("out", QString::number(f->out)); stream.writeAttribute("speed", QString::number(f->speed)); + stream.writeAttribute("alphapremul", QString::number(f->alpha_is_premultiplied)); for (int j=0;jvideo_tracks.size();j++) { const FootageStream& ms = f->video_tracks.at(j); stream.writeStartElement("video"); diff --git a/panels/viewer.h b/panels/viewer.h index 7181d426d..db95d90ac 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -37,7 +37,6 @@ public: void update_playhead_timecode(long p); void update_end_timecode(); void update_header_zoom(); - void update_viewer(); void clear_in(); void clear_out(); void clear_inout_point(); @@ -88,6 +87,7 @@ public slots: void go_to_out(); void go_to_end(); void close_media(); + void update_viewer(); private slots: void update_playhead(); diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 7d7f294ca..7e096fe92 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -766,16 +766,26 @@ void open_clip_worker(Clip* clip) { AVFilterContext* last_filter = clip->buffersrc_ctx; + char filter_args[100]; + if (ms->video_interlacing != VIDEO_PROGRESSIVE) { AVFilterContext* yadif_filter; - char yadif_args[100]; - snprintf(yadif_args, sizeof(yadif_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc - avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", yadif_args, nullptr, clip->filter_graph); + snprintf(filter_args, sizeof(filter_args), "mode=3:parity=%d", ((ms->video_interlacing == VIDEO_TOP_FIELD_FIRST) ? 0 : 1)); // there's a CUDA version if we start using nvdec/nvenc + avfilter_graph_create_filter(&yadif_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, clip->filter_graph); avfilter_link(last_filter, 0, yadif_filter, 0); last_filter = yadif_filter; } + if (!clip->media->to_footage()->alpha_is_premultiplied) { + AVFilterContext* premultiply_filter; + snprintf(filter_args, sizeof(filter_args), "inplace=1"); + avfilter_graph_create_filter(&premultiply_filter, avfilter_get_by_name("premultiply"), "premultiply", filter_args, nullptr, clip->filter_graph); + + avfilter_link(last_filter, 0, premultiply_filter, 0); + last_filter = premultiply_filter; + } + /* stabilization code */ /*bool stabilize = false; if (stabilize) { @@ -799,11 +809,10 @@ void open_clip_worker(Clip* clip) { clip->pix_fmt = avcodec_find_best_pix_fmt_of_list(valid_pix_fmts, static_cast(clip->stream->codecpar->format), 1, nullptr); const char* chosen_format = av_get_pix_fmt_name(static_cast(clip->pix_fmt)); - char format_args[100]; - snprintf(format_args, sizeof(format_args), "pix_fmts=%s", chosen_format); + snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format); AVFilterContext* format_conv; - avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", format_args, nullptr, clip->filter_graph); + avfilter_graph_create_filter(&format_conv, avfilter_get_by_name("format"), "fmt", filter_args, nullptr, clip->filter_graph); avfilter_link(last_filter, 0, format_conv, 0); avfilter_link(format_conv, 0, clip->buffersink_ctx, 0); diff --git a/project/effect.cpp b/project/effect.cpp index 650935174..f535b4ff4 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -29,9 +29,7 @@ #include "effects/internal/paneffect.h" #include "effects/internal/shakeeffect.h" #include "effects/internal/cornerpineffect.h" -#ifndef NOVST #include "effects/internal/vsthost.h" -#endif #include "effects/internal/fillleftrighteffect.h" #include "effects/internal/frei0reffect.h" @@ -754,7 +752,7 @@ GLuint Effect::process_superimpose(double timecode) { int height = parent_clip->getHeight(); if (width != img.width() || height != img.height()) { - img = QImage(width, height, QImage::Format_RGBA8888); + img = QImage(width, height, QImage::Format_RGBA8888_Premultiplied); recreate_texture = true; } diff --git a/project/footage.cpp b/project/footage.cpp index 2f3651017..c9dbd83f6 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -11,7 +11,15 @@ extern "C" { #include "project/clip.h" -Footage::Footage() : ready(false), preview_gen(nullptr), invalid(false), in(0), out(0), speed(1.0) { +Footage::Footage() : + ready(false), + preview_gen(nullptr), + invalid(false), + in(0), + out(0), + speed(1.0), + alpha_is_premultiplied(false) +{ ready_lock.lock(); } diff --git a/project/footage.h b/project/footage.h index d6408857b..4c12e7edc 100644 --- a/project/footage.h +++ b/project/footage.h @@ -52,6 +52,7 @@ struct Footage { bool ready; bool invalid; double speed; + bool alpha_is_premultiplied; PreviewGenerator* preview_gen; QMutex ready_lock; diff --git a/project/undo.cpp b/project/undo.cpp index d16b481c4..2fbf5218b 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -787,7 +787,7 @@ void SetAutoscaleAction::undo() { for (int i=0;iautoscale = !clips.at(i)->autoscale; } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); mainWindow->setWindowModified(old_project_changed); } @@ -795,7 +795,7 @@ void SetAutoscaleAction::redo() { for (int i=0;iautoscale = !clips.at(i)->autoscale; } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); mainWindow->setWindowModified(true); } @@ -1272,3 +1272,11 @@ void RefreshClips::redo() { } } } + +void UpdateViewer::undo() { + redo(); +} + +void UpdateViewer::redo() { + panel_sequence_viewer->viewer_widget->frame_update(); +} diff --git a/project/undo.h b/project/undo.h index c4fbf7619..9f94ee3a4 100644 --- a/project/undo.h +++ b/project/undo.h @@ -644,4 +644,10 @@ private: Media* media; }; +class UpdateViewer : public QUndoCommand { +public: + void undo(); + void redo(); +}; + #endif // UNDO_H diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp index c480a6f69..bf1266948 100644 --- a/ui/renderfunctions.cpp +++ b/ui/renderfunctions.cpp @@ -28,17 +28,16 @@ extern "C" { #include } -GLuint draw_clip(QOpenGLContext* ctx, QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { +GLuint draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { glPushMatrix(); glLoadIdentity(); glOrtho(0, 1, 0, 1, -1, 1); - GLint current_fbo = 0; - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - fbo->bind(); - if (clear) glClear(GL_COLOR_BUFFER_BIT); + if (clear) { + glClear(GL_COLOR_BUFFER_BIT); + } glBindTexture(GL_TEXTURE_2D, texture); glBegin(GL_QUADS); @@ -53,14 +52,11 @@ GLuint draw_clip(QOpenGLContext* ctx, QOpenGLFramebufferObject* fbo, GLuint text glEnd(); glBindTexture(GL_TEXTURE_2D, 0); - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - glPopMatrix(); return fbo->texture(); } -void process_effect(QOpenGLContext* ctx, - Clip* c, +void process_effect(Clip* c, Effect* e, double timecode, GLTextureCoords& coords, @@ -72,11 +68,12 @@ void process_effect(QOpenGLContext* ctx, if (e->enable_coords) { e->process_coords(timecode, coords, data); } - if ((e->enable_shader && shaders_are_enabled) || e->enable_superimpose) { + bool can_process_shaders = (e->enable_shader && shaders_are_enabled); + if (can_process_shaders || e->enable_superimpose) { e->startEffect(); - if ((e->enable_shader && shaders_are_enabled) && e->is_glsl_linked()) { + if (can_process_shaders && e->is_glsl_linked()) { e->process_shader(timecode, coords); - composite_texture = draw_clip(ctx, c->fbo[fbo_switcher], composite_texture, true); + composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true); fbo_switcher = !fbo_switcher; } if (e->enable_superimpose) { @@ -85,7 +82,18 @@ void process_effect(QOpenGLContext* ctx, qWarning() << "Superimpose texture was nullptr, retrying..."; texture_failed = true; } else { - composite_texture = draw_clip(ctx, c->fbo[!fbo_switcher], superimpose_texture, false); + if (composite_texture == 0) { + // if there is no previous texture, just return the superimposes texture + composite_texture = superimpose_texture; + } else { + // if the source texture is not already a framebuffer texture, + // we'll need to make it one before drawing a superimpose effect on it + if (composite_texture != c->fbo[0]->texture() && composite_texture != c->fbo[1]->texture()) { + draw_clip(c->fbo[!fbo_switcher], composite_texture, true); + } + + composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false); + } } } e->endEffect(); @@ -94,10 +102,7 @@ void process_effect(QOpenGLContext* ctx, } GLuint compose_sequence(ComposeSequenceParams ¶ms) { - GLint current_fbo = 0; - if (params.video) { - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); - } + GLuint final_fbo = params.main_buffer; Sequence* s = params.seq; long playhead = s->playhead; @@ -112,7 +117,7 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { if (params.video && params.nests.last()->fbo != nullptr) { params.nests.last()->fbo[0]->bind(); glClear(GL_COLOR_BUFFER_BIT); - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + final_fbo = params.nests.last()->fbo[0]->handle(); } } @@ -120,36 +125,58 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { QVector current_clips; + // loop through clips, find currently active, and sort by track for (int i=0;iclips.size();i++) { + Clip* c = s->clips.at(i); - // if clip starts within one second and/or hasn't finished yet if (c != nullptr) { + + // if clip is video and we're processing video if ((c->track < 0) == params.video) { + bool clip_is_active = false; + // is the clip a "footage" clip? if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { Footage* m = c->media->to_footage(); + + // does the clip have a valid media source? if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { + + // is the media process and ready? if (m->ready) { const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + + // does the media have a valid media stream source and is it active? if (ms != nullptr && is_clip_active(c, playhead)) { - // if thread is already working, we don't want to touch this, - // but we also don't want to hang the UI thread + + // open if not open if (!c->open) { open_clip(c, !params.rendering); } + clip_is_active = true; + + // increment audio track count if (c->track >= 0) audio_track_count++; + } else if (c->finished_opening) { + + // close the clip if it isn't active anymore close_clip(c, false); + } } else { - //qWarning() << "Media '" + m->name + "' was not ready, retrying..."; + + // media wasn't ready, schedule a redraw params.texture_failed = true; + } } } else { + // if the clip is a nested sequence or null clip, just open it + if (is_clip_active(c, playhead)) { if (!c->open) open_clip(c, !params.rendering); clip_is_active = true; @@ -157,15 +184,26 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { close_clip(c, false); } } + + // if the clip is active, added it to "current_clips", sorted by track if (clip_is_active) { bool added = false; - for (int j=0;jtrack < c->track) { - current_clips.insert(j, c); - added = true; - break; + + // track sorting is only necessary for video clips + // audio clips are mixed equally, so we skip sorting for those + if (params.video) { + + // insertion sort by track + for (int j=0;jtrack < c->track) { + current_clips.insert(j, c); + added = true; + break; + } } + } + if (!added) { current_clips.append(c); } @@ -174,90 +212,98 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { } } - int half_width = s->width/2; - int half_height = s->height/2; - if (params.video) { + // set default coordinates based on the sequence, with 0 in the direct center glPushMatrix(); glLoadIdentity(); + + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + int half_width = s->width/2; + int half_height = s->height/2; glOrtho(-half_width, half_width, -half_height, half_height, -1, 10); } + // loop through current clips for (int i=0;imedia != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE && !c->finished_opening) { qWarning() << "Tried to display clip" << i << "but it's closed"; params.texture_failed = true; } else { + // if clip is a video clip if (c->track < 0) { + // reset OpenGL to full color glColor4f(1.0, 1.0, 1.0, 1.0); + // textureID variable contains texture to be drawn on screen at the end GLuint textureID = 0; + + // store video source dimensions int video_width = c->getWidth(); int video_height = c->getHeight(); - if (c->media != nullptr) { - switch (c->media->get_type()) { - case MEDIA_TYPE_FOOTAGE: - // set up opengl texture - if (c->texture == nullptr) { - c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); - c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height); - c->texture->setFormat(get_gl_tex_fmt_from_av(c->pix_fmt)); - c->texture->setMipLevels(c->texture->maximumMipLevels()); - c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); - c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8); - } - get_clip_frame(c, qMax(playhead, c->timeline_in), params.texture_failed); - textureID = c->texture->textureId(); - break; - case MEDIA_TYPE_SEQUENCE: - textureID = -1; - break; + // if media is footage + if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + + if (c->texture == nullptr) { + // opengl texture doesn't exist yet, create it + + c->texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + c->texture->setSize(c->stream->codecpar->width, c->stream->codecpar->height); + c->texture->setFormat(get_gl_tex_fmt_from_av(c->pix_fmt)); + c->texture->setMipLevels(c->texture->maximumMipLevels()); + c->texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear); + c->texture->allocateStorage(get_gl_pix_fmt_from_av(c->pix_fmt), QOpenGLTexture::UInt8); + } + + // retrieve video frame from cache and store it in c->texture + get_clip_frame(c, qMax(playhead, c->timeline_in), params.texture_failed); + + // retrieve ID from c->texture + textureID = c->texture->textureId(); + + if (textureID == 0) { + qWarning() << "Failed to create texture"; + return 0; } } - if (textureID == 0 && c->media != nullptr) { - qWarning() << "Texture hasn't been created yet"; - params.texture_failed = true; - } else if (playhead >= c->get_timeline_in_with_transition()) { + // prepare framebuffers for backend drawing operations + if (c->fbo == nullptr) { + c->fbo = new QOpenGLFramebufferObject* [2]; + c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height); + c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height); + } + + // if clip should actually be shown on screen in this frame + if (playhead >= c->get_timeline_in_with_transition()) { glPushMatrix(); - // start preparing cache - if (c->fbo == nullptr) { - c->fbo = new QOpenGLFramebufferObject* [2]; - c->fbo[0] = new QOpenGLFramebufferObject(video_width, video_height); - c->fbo[1] = new QOpenGLFramebufferObject(video_width, video_height); - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - } - + // simple bool for switching between the two framebuffers bool fbo_switcher = false; glViewport(0, 0, video_width, video_height); - GLuint composite_texture; + if (c->media != nullptr && c->media->get_type()== MEDIA_TYPE_SEQUENCE) { + // for a nested sequence, run this function again on that sequence and retrieve the texture - if (c->media == nullptr) { - c->fbo[fbo_switcher]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - composite_texture = c->fbo[fbo_switcher]->texture(); - } else { - // for nested sequences - if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) { - params.nests.append(c); - textureID = compose_sequence(params); - params.nests.removeLast(); - fbo_switcher = true; - } + // add nested sequence to nest list + params.nests.append(c); - composite_texture = draw_clip(params.ctx, c->fbo[fbo_switcher], textureID, true); + // compose sequence + textureID = compose_sequence(params); + + // remove sequence from nest list + params.nests.removeLast(); + + // compose_sequence() would have written to this clip's fbo[0], so we switch to fbo[1] + fbo_switcher = true; } - fbo_switcher = !fbo_switcher; - - // set up default coords + // set up default coordinates for drawing the clip GLTextureCoords coords; coords.grid_size = 1; coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; @@ -268,8 +314,10 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { coords.textureTopLeftY = coords.textureTopRightY = coords.textureTopLeftX = coords.textureBottomLeftX = 0.0; coords.textureBottomLeftY = coords.textureBottomRightY = coords.textureTopRightX = coords.textureBottomRightX = 1.0; coords.textureTopLeftQ = coords.textureTopRightQ = coords.textureTopLeftQ = coords.textureBottomLeftQ = 1; + coords.blendmode = BLEND_MODE_NORMAL; + coords.opacity = 1.0; - // set up autoscale + // if auto-scale is enabled, auto-scale the clip if (c->autoscale && (video_width != s->width && video_height != s->height)) { float width_multiplier = float(s->width) / float(video_width); float height_multiplier = float(s->height) / float(video_height); @@ -277,85 +325,94 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { glScalef(scale_multiplier, scale_multiplier, 1); } - // EFFECT CODE START + // == EFFECT CODE START == + + // get current sequence time in seconds (used for effects) double timecode = get_timecode(c, playhead); + // set up variables for gizmos later Effect* first_gizmo_effect = nullptr; Effect* selected_effect = nullptr; + // run through all of the clip's effects for (int j=0;jeffects.size();j++) { Effect* e = c->effects.at(j); - process_effect(params.ctx, c, e, timecode, coords, composite_texture, fbo_switcher, params.texture_failed, TA_NO_TRANSITION); + process_effect(c, e, timecode, coords, textureID, fbo_switcher, params.texture_failed, TA_NO_TRANSITION); + // retrieve gizmo data from effect if (e->are_gizmos_enabled()) { if (first_gizmo_effect == nullptr) first_gizmo_effect = e; if (e->container->selected) selected_effect = e; } } + // using gizmo data, set definitive gizmo if (selected_effect != nullptr) { (*params.gizmos) = selected_effect; } else if (is_clip_selected(c, true)) { (*params.gizmos) = first_gizmo_effect; } + // if the clip has an opening transition, process that now if (c->get_opening_transition() != nullptr) { int transition_progress = playhead - c->get_timeline_in_with_transition(); if (transition_progress < c->get_opening_transition()->get_length()) { - process_effect(params.ctx, c, c->get_opening_transition(), (double)transition_progress/(double)c->get_opening_transition()->get_length(), coords, composite_texture, fbo_switcher, params.texture_failed, TA_OPENING_TRANSITION); + process_effect(c, c->get_opening_transition(), double(transition_progress)/double(c->get_opening_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, TA_OPENING_TRANSITION); } } + // if the clip has a closing transition, process that now if (c->get_closing_transition() != nullptr) { int transition_progress = playhead - (c->get_timeline_out_with_transition() - c->get_closing_transition()->get_length()); if (transition_progress >= 0 && transition_progress < c->get_closing_transition()->get_length()) { - process_effect(params.ctx, c, c->get_closing_transition(), (double)transition_progress/(double)c->get_closing_transition()->get_length(), coords, composite_texture, fbo_switcher, params.texture_failed, TA_CLOSING_TRANSITION); + process_effect(c, c->get_closing_transition(), double(transition_progress)/double(c->get_closing_transition()->get_length()), coords, textureID, fbo_switcher, params.texture_failed, TA_CLOSING_TRANSITION); } } - // EFFECT CODE END - if (!params.nests.isEmpty()) { - params.nests.last()->fbo[0]->bind(); - } + // == EFFECT CODE END == + + // == START FINAL DRAW ON SEQUENCE BUFFER == + + // bind framebuffer + params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, final_fbo); + + // set viewport to sequence size glViewport(0, 0, s->width, s->height); - glBindTexture(GL_TEXTURE_2D, composite_texture); + // bind final texture + glBindTexture(GL_TEXTURE_2D, textureID); + // set texture filter to bilinear glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // get current color attachment from framebuffer - GLint texture_id; - params.ctx->functions()->glGetFramebufferAttachmentParameteriv(GL_TEXTURE_2D, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &texture_id); - + // bind and configure blending mode shader params.blend_mode_program->bind(); params.blend_mode_program->setUniformValue("blend_mode", coords.blendmode); params.blend_mode_program->setUniformValue("opacity", coords.opacity); -// blend_mode_program->setUniformValue("background", texture_id); + // draw clip on screen glBegin(GL_QUADS); if (coords.grid_size <= 1) { - float z = 0.0f; - glTexCoord2f(coords.textureTopLeftX, coords.textureTopLeftY); // top left - glVertex3f(coords.vertexTopLeftX, coords.vertexTopLeftY, z); // top left + glVertex2f(coords.vertexTopLeftX, coords.vertexTopLeftY); // top left glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right - glVertex3f(coords.vertexTopRightX, coords.vertexTopRightY, z); // top right + glVertex2f(coords.vertexTopRightX, coords.vertexTopRightY); // top right glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right - glVertex3f(coords.vertexBottomRightX, coords.vertexBottomRightY, z); // bottom right + glVertex2f(coords.vertexBottomRightX, coords.vertexBottomRightY); // bottom right glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left - glVertex3f(coords.vertexBottomLeftX, coords.vertexBottomLeftY, z); // bottom left + glVertex2f(coords.vertexBottomLeftX, coords.vertexBottomLeftY); // bottom left } else { float rows = coords.grid_size; float cols = coords.grid_size; - for (float k=0;krelease(); - glBindTexture(GL_TEXTURE_2D, 0); // unbind texture + // unbind texture + glBindTexture(GL_TEXTURE_2D, 0); + + // unbind framebuffer + params.ctx->functions()->glBindFramebuffer(GL_TEXTURE_2D, 0); + + // == END FINAL DRAW ON SEQUENCE BUFFER == // prepare gizmos if ((*params.gizmos) != nullptr @@ -394,10 +458,6 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { (*params.gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords } - if (!params.nests.isEmpty()) { - params.ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); - } - glPopMatrix(); } } else { diff --git a/ui/renderfunctions.h b/ui/renderfunctions.h index 52698d3f8..3d452f100 100644 --- a/ui/renderfunctions.h +++ b/ui/renderfunctions.h @@ -22,6 +22,7 @@ struct ComposeSequenceParams { bool rendering; int playback_speed; QOpenGLShaderProgram* blend_mode_program; + GLuint main_buffer; GLuint backend_buffer1; GLuint backend_attachment1; GLuint backend_buffer2; diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp index 601f40c69..d7f2b4f72 100644 --- a/ui/renderthread.cpp +++ b/ui/renderthread.cpp @@ -10,8 +10,8 @@ #include "project/sequence.h" RenderThread::RenderThread() : - frameBuffer(0), - texColorBuffer(0), + front_buffer(0), + front_texture(0), gizmos(nullptr), share_ctx(nullptr), ctx(nullptr), @@ -42,39 +42,70 @@ void RenderThread::run() { } queued = false; - if (share_ctx != nullptr) { if (ctx != nullptr) { ctx->makeCurrent(&surface); // gen fbo - if (frameBuffer == 0) { + if (front_buffer == 0) { + // delete any existing framebuffers delete_fbo(); - ctx->functions()->glGenFramebuffers(1, &frameBuffer); + + // create framebuffers + ctx->functions()->glGenFramebuffers(1, &front_buffer); + ctx->functions()->glGenFramebuffers(1, &back_buffer_1); + ctx->functions()->glGenFramebuffers(1, &back_buffer_2); } - // bind - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer); // gen texture - if (texColorBuffer == 0 || tex_width != seq->width || tex_height != seq->height) { - delete_texture(); - glGenTextures(1, &texColorBuffer); - glBindTexture(GL_TEXTURE_2D, texColorBuffer); - glTexImage2D( - GL_TEXTURE_2D, 0, GL_RGB, seq->width, seq->height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr - ); + if (front_texture == 0 || tex_width != seq->width || tex_height != seq->height) { + // cache texture size tex_width = seq->width; tex_height = seq->height; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - ctx->functions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texColorBuffer, 0 - ); - glBindTexture(GL_TEXTURE_2D, 0); + + // delete any existing textures + delete_texture(); + + // create texture + glGenTextures(1, &front_texture); + glGenTextures(1, &back_buffer_1); + glGenTextures(1, &back_buffer_2); + + GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2}; + GLuint textures[3] = {front_buffer, back_buffer_1, back_buffer_2}; + + for (int i=0;i<3;i++) { + // bind framebuffer for attaching + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[i]); + + // bind texture + glBindTexture(GL_TEXTURE_2D, textures[i]); + + // allocate storage for texture + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB, seq->width, seq->height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr + ); + + // set texture filtering to bilinear + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // attach texture to framebuffer + ctx->functions()->glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0 + ); + + // release texture + glBindTexture(GL_TEXTURE_2D, 0); + + // release framebuffer + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } } if (blend_mode_program == nullptr) { + // create shader program to make blending modes work delete_shader_program(); blend_mode_program = new QOpenGLShaderProgram(); blend_mode_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "C:/msys64/home/Matt/olive/effects/common.vert"); @@ -82,15 +113,17 @@ void RenderThread::run() { blend_mode_program->link(); } - // draw + // bind framebuffer for drawing + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, front_buffer); + + // draw frame paint(); // flush changes -// glFlush(); - glFinish(); + ctx->functions()->glFinish(); // release - ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0); + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); emit ready(); } @@ -128,6 +161,11 @@ void RenderThread::paint() { params.rendering = false; params.playback_speed = 1; params.blend_mode_program = blend_mode_program; + params.backend_buffer1 = back_buffer_1; + params.backend_buffer2 = back_buffer_2; + params.backend_attachment1 = back_texture_1; + params.backend_attachment2 = back_texture_2; + params.main_buffer = front_buffer; compose_sequence(params); texture_failed = params.texture_failed; @@ -137,7 +175,7 @@ void RenderThread::paint() { // texture failed, try again queued = true; } else { - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer); QImage img(tex_width, tex_height, QImage::Format_RGBA8888); glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); img.save(save_fn); @@ -147,7 +185,7 @@ void RenderThread::paint() { } if (pixel_buffer != nullptr) { - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, front_buffer); glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer); ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); pixel_buffer = nullptr; @@ -193,20 +231,23 @@ void RenderThread::cancel() { } void RenderThread::delete_texture() { - if (texColorBuffer > 0) { - ctx->functions()->glFramebufferTexture2D( - GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 - ); - glDeleteTextures(1, &texColorBuffer); + if (front_texture > 0) { + GLuint tex[3] = {front_texture, back_texture_1, back_texture_2}; + glDeleteTextures(3, tex); } - texColorBuffer = 0; + front_texture = 0; + back_texture_1 = 0; + back_texture_2 = 0; } void RenderThread::delete_fbo() { - if (frameBuffer > 0) { - ctx->functions()->glDeleteFramebuffers(1, &frameBuffer); + if (front_buffer > 0) { + GLuint fbos[3] = {front_buffer, back_buffer_1, back_buffer_2}; + ctx->functions()->glDeleteFramebuffers(3, fbos); } - frameBuffer = 0; + front_buffer = 0; + back_buffer_1 = 0; + back_buffer_2 = 0; } void RenderThread::delete_shader_program() { diff --git a/ui/renderthread.h b/ui/renderthread.h index a73813888..6ec304d31 100644 --- a/ui/renderthread.h +++ b/ui/renderthread.h @@ -1,4 +1,4 @@ -#ifndef RENDERTHREAD_H +#ifndef RENDERTHREAD_H #define RENDERTHREAD_H #include @@ -19,8 +19,8 @@ public: ~RenderThread(); void run(); QMutex mutex; - GLuint frameBuffer; - GLuint texColorBuffer; + GLuint front_buffer; + GLuint front_texture; Effect* gizmos; void paint(); void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0); @@ -43,6 +43,12 @@ private: QOpenGLContext* share_ctx; QOpenGLContext* ctx; QOpenGLShaderProgram* blend_mode_program; + + GLuint back_buffer_1; + GLuint back_buffer_2; + GLuint back_texture_1; + GLuint back_texture_2; + Sequence* seq; int divider; int tex_width; diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 196968d58..648199df2 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -562,7 +562,7 @@ void ViewerWidget::paintGL() { // draw texture from render thread - glBindTexture(GL_TEXTURE_2D, renderer->texColorBuffer); + glBindTexture(GL_TEXTURE_2D, renderer->front_texture); glBegin(GL_QUADS); @@ -592,7 +592,7 @@ void ViewerWidget::paintGL() { glDisable(GL_TEXTURE_2D); if (window != nullptr && window->isVisible()) { - window->set_texture(renderer->texColorBuffer, double(viewer->seq->width)/double(viewer->seq->height), &renderer->mutex); + window->set_texture(renderer->front_texture, double(viewer->seq->width)/double(viewer->seq->height), &renderer->mutex); } renderer->mutex.unlock();