diff --git a/debug.cpp b/debug.cpp index 405cb010a..1dced8017 100644 --- a/debug.cpp +++ b/debug.cpp @@ -63,7 +63,7 @@ void debug_message_handler(QtMsgType type, const QMessageLogContext &context, co if (debug_file.isOpen()) debug_stream << QString("[FATAL] %1 (%2:%3, %4)\n").arg(localMsg.constData(), context.file, QString::number(context.line), context.function); debug_info.prepend(QString("[FATAL] %1 (%2:%3, %4)
").arg(localMsg.constData(), context.file, QString::number(context.line), context.function)); fflush(stderr); - abort(); +// abort(); } if (debug_dialog != nullptr && debug_dialog->isVisible()) { QMetaObject::invokeMethod(debug_dialog, "update_log", Qt::QueuedConnection); diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index d5c52e8fb..5ed83c0fd 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -57,7 +57,7 @@ enum ExportFormats { ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Export \"%1\"").arg(sequence->name)); + setWindowTitle(tr("Export \"%1\"").arg(sequence->name)); setup_ui(); rangeCombobox->setCurrentIndex(0); @@ -320,12 +320,12 @@ void ExportDialog::format_changed(int index) void ExportDialog::render_thread_finished() { if (progressBar->value() < 100 && !cancelled) { - QMessageBox::critical( - this, - tr("Export Failed"), - tr("Export failed - %1").arg(export_error), - QMessageBox::Ok - ); + QMessageBox::critical( + this, + tr("Export Failed"), + tr("Export failed - %1").arg(export_error), + QMessageBox::Ok + ); } prep_ui_for_render(false); panel_sequence_viewer->viewer_widget->makeCurrent(); @@ -342,12 +342,12 @@ void ExportDialog::prep_ui_for_render(bool r) { void ExportDialog::export_action() { if (widthSpinbox->value()%2 == 1 || heightSpinbox->value()%2 == 1) { - QMessageBox::critical( - this, - tr("Invalid dimensions"), - tr("Export width and height must both be even numbers/divisible by 2."), - QMessageBox::Ok - ); + QMessageBox::critical( + this, + tr("Invalid dimensions"), + tr("Export width and height must both be even numbers/divisible by 2."), + QMessageBox::Ok + ); return; } @@ -399,12 +399,12 @@ void ExportDialog::export_action() { break; default: qCritical() << "Invalid codec selection for an image sequence"; - QMessageBox::critical( - this, - tr("Invalid codec"), - tr("Couldn't determine output parameters for the selected codec. This is a bug, please contact the developers."), - QMessageBox::Ok - ); + QMessageBox::critical( + this, + tr("Invalid codec"), + tr("Couldn't determine output parameters for the selected codec. This is a bug, please contact the developers."), + QMessageBox::Ok + ); return; } break; @@ -469,20 +469,20 @@ void ExportDialog::export_action() { break; default: qCritical() << "Invalid format - this is a bug, please inform the developers"; - QMessageBox::critical( - this, - tr("Invalid format"), - tr("Couldn't determine output format. This is a bug, please contact the developers."), - QMessageBox::Ok - ); + QMessageBox::critical( + this, + tr("Invalid format"), + tr("Couldn't determine output format. This is a bug, please contact the developers."), + QMessageBox::Ok + ); return; } - QString filename = QFileDialog::getSaveFileName( - this, - tr("Export Media"), - "", - format_strings[formatCombobox->currentIndex()] + " (*." + ext + ")" - ); + QString filename = QFileDialog::getSaveFileName( + this, + tr("Export Media"), + "", + format_strings[formatCombobox->currentIndex()] + " (*." + ext + ")" + ); if (!filename.isEmpty()) { if (!filename.endsWith("." + ext, Qt::CaseInsensitive)) { filename += "." + ext; @@ -506,11 +506,9 @@ void ExportDialog::export_action() { closeActiveClips(sequence); - mainWindow->autorecover_interval(); + mainWindow->set_rendering_state(true); - rendering = true; - panel_sequence_viewer->viewer_widget->context()->doneCurrent(); - panel_sequence_viewer->viewer_widget->context()->moveToThread(et); + mainWindow->autorecover_interval(); prep_ui_for_render(true); @@ -556,20 +554,20 @@ void ExportDialog::update_progress_bar(int value, qint64 remaining_ms) { } void ExportDialog::cancel_render() { - panel_sequence_viewer->viewer_widget->force_quit = true; et->continueEncode = false; cancelled = true; + et->wake(); } void ExportDialog::vcodec_changed(int index) { compressionTypeCombobox->clear(); if ((format_vcodecs.size() > 0 && format_vcodecs.at(index) == AV_CODEC_ID_H264)) { compressionTypeCombobox->setEnabled(true); - compressionTypeCombobox->addItem(tr("Quality-based (Constant Rate Factor)"), COMPRESSION_TYPE_CFR); + compressionTypeCombobox->addItem(tr("Quality-based (Constant Rate Factor)"), COMPRESSION_TYPE_CFR); // compressionTypeCombobox->addItem("File size-based (Two-Pass)", COMPRESSION_TYPE_TARGETSIZE); // compressionTypeCombobox->addItem("Average bitrate (Two-Pass)", COMPRESSION_TYPE_TARGETBR); } else { - compressionTypeCombobox->addItem(tr("Constant Bitrate"), COMPRESSION_TYPE_CBR); + compressionTypeCombobox->addItem(tr("Constant Bitrate"), COMPRESSION_TYPE_CBR); compressionTypeCombobox->setCurrentIndex(0); compressionTypeCombobox->setEnabled(false); } @@ -582,17 +580,17 @@ void ExportDialog::comp_type_changed(int) { switch (compressionTypeCombobox->currentData().toInt()) { case COMPRESSION_TYPE_CBR: case COMPRESSION_TYPE_TARGETBR: - videoBitrateLabel->setText(tr("Bitrate (Mbps):")); + videoBitrateLabel->setText(tr("Bitrate (Mbps):")); videobitrateSpinbox->setValue(qMax(0.5, (double) qRound((0.01528 * sequence->height) - 4.5))); break; case COMPRESSION_TYPE_CFR: - videoBitrateLabel->setText(tr("Quality (CRF):")); + videoBitrateLabel->setText(tr("Quality (CRF):")); videobitrateSpinbox->setValue(36); videobitrateSpinbox->setMaximum(51); - videobitrateSpinbox->setToolTip(tr("Quality Factor:\n\n0 = lossless\n17-18 = visually lossless (compressed, but unnoticeable)\n23 = high quality\n51 = lowest quality possible")); + videobitrateSpinbox->setToolTip(tr("Quality Factor:\n\n0 = lossless\n17-18 = visually lossless (compressed, but unnoticeable)\n23 = high quality\n51 = lowest quality possible")); break; case COMPRESSION_TYPE_TARGETSIZE: - videoBitrateLabel->setText(tr("Target File Size (MB):")); + videoBitrateLabel->setText(tr("Target File Size (MB):")); videobitrateSpinbox->setValue(100); break; } @@ -601,57 +599,57 @@ void ExportDialog::comp_type_changed(int) { void ExportDialog::setup_ui() { QVBoxLayout* verticalLayout = new QVBoxLayout(this); - QHBoxLayout* format_layout = new QHBoxLayout(); + QHBoxLayout* format_layout = new QHBoxLayout(); - format_layout->addWidget(new QLabel(tr("Format:"))); + format_layout->addWidget(new QLabel(tr("Format:"))); formatCombobox = new QComboBox(this); - format_layout->addWidget(formatCombobox); + format_layout->addWidget(formatCombobox); - verticalLayout->addLayout(format_layout); + verticalLayout->addLayout(format_layout); - QHBoxLayout* range_layout = new QHBoxLayout(); + QHBoxLayout* range_layout = new QHBoxLayout(); - range_layout->addWidget(new QLabel(tr("Range:"))); + range_layout->addWidget(new QLabel(tr("Range:"))); rangeCombobox = new QComboBox(this); - rangeCombobox->addItem(tr("Entire Sequence")); - rangeCombobox->addItem(tr("In to Out")); + rangeCombobox->addItem(tr("Entire Sequence")); + rangeCombobox->addItem(tr("In to Out")); - range_layout->addWidget(rangeCombobox); + range_layout->addWidget(rangeCombobox); - verticalLayout->addLayout(range_layout); + verticalLayout->addLayout(range_layout); videoGroupbox = new QGroupBox(this); - videoGroupbox->setTitle(tr("Video")); + videoGroupbox->setTitle(tr("Video")); videoGroupbox->setFlat(false); videoGroupbox->setCheckable(true); QGridLayout* videoGridLayout = new QGridLayout(videoGroupbox); - videoGridLayout->addWidget(new QLabel(tr("Codec:")), 0, 0, 1, 1); + videoGridLayout->addWidget(new QLabel(tr("Codec:")), 0, 0, 1, 1); vcodecCombobox = new QComboBox(videoGroupbox); videoGridLayout->addWidget(vcodecCombobox, 0, 1, 1, 1); - videoGridLayout->addWidget(new QLabel(tr("Width:")), 1, 0, 1, 1); + videoGridLayout->addWidget(new QLabel(tr("Width:")), 1, 0, 1, 1); widthSpinbox = new QSpinBox(videoGroupbox); widthSpinbox->setMaximum(16777216); videoGridLayout->addWidget(widthSpinbox, 1, 1, 1, 1); - videoGridLayout->addWidget(new QLabel(tr("Height:")), 2, 0, 1, 1); + videoGridLayout->addWidget(new QLabel(tr("Height:")), 2, 0, 1, 1); heightSpinbox = new QSpinBox(videoGroupbox); heightSpinbox->setMaximum(16777216); videoGridLayout->addWidget(heightSpinbox, 2, 1, 1, 1); - videoGridLayout->addWidget(new QLabel(tr("Frame Rate:")), 3, 0, 1, 1); + videoGridLayout->addWidget(new QLabel(tr("Frame Rate:")), 3, 0, 1, 1); framerateSpinbox = new QDoubleSpinBox(videoGroupbox); framerateSpinbox->setMaximum(60); framerateSpinbox->setValue(0); videoGridLayout->addWidget(framerateSpinbox, 3, 1, 1, 1); - videoGridLayout->addWidget(new QLabel(tr("Compression Type:")), 4, 0, 1, 1); - compressionTypeCombobox = new QComboBox(videoGroupbox); + videoGridLayout->addWidget(new QLabel(tr("Compression Type:")), 4, 0, 1, 1); + compressionTypeCombobox = new QComboBox(videoGroupbox); videoGridLayout->addWidget(compressionTypeCombobox, 4, 1, 1, 1); videoBitrateLabel = new QLabel(videoGroupbox); @@ -669,17 +667,17 @@ void ExportDialog::setup_ui() { QGridLayout* audioGridLayout = new QGridLayout(audioGroupbox); - audioGridLayout->addWidget(new QLabel(tr("Codec:")), 0, 0, 1, 1); + audioGridLayout->addWidget(new QLabel(tr("Codec:")), 0, 0, 1, 1); acodecCombobox = new QComboBox(audioGroupbox); audioGridLayout->addWidget(acodecCombobox, 0, 1, 1, 1); - audioGridLayout->addWidget(new QLabel(tr("Sampling Rate:")), 1, 0, 1, 1); + audioGridLayout->addWidget(new QLabel(tr("Sampling Rate:")), 1, 0, 1, 1); samplingRateSpinbox = new QSpinBox(audioGroupbox); samplingRateSpinbox->setMaximum(96000); samplingRateSpinbox->setValue(0); audioGridLayout->addWidget(samplingRateSpinbox, 1, 1, 1, 1); - audioGridLayout->addWidget(new QLabel(tr("Bitrate (Kbps/CBR):")), 3, 0, 1, 1); + audioGridLayout->addWidget(new QLabel(tr("Bitrate (Kbps/CBR):")), 3, 0, 1, 1); audiobitrateSpinbox = new QSpinBox(audioGroupbox); audiobitrateSpinbox->setMaximum(320); audiobitrateSpinbox->setValue(256); diff --git a/effects/internal/texteffect.cpp b/effects/internal/texteffect.cpp index 05b46c33f..12368b18a 100644 --- a/effects/internal/texteffect.cpp +++ b/effects/internal/texteffect.cpp @@ -29,49 +29,49 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : enable_superimpose = true; //enable_shader = true; - text_val = add_row(tr("Text"))->add_field(EFFECT_FIELD_STRING, "text", 2); + text_val = add_row(tr("Text"))->add_field(EFFECT_FIELD_STRING, "text", 2); QTextEdit* text_widget = static_cast(text_val->ui_element); text_widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(text_widget, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(text_edit_menu())); - set_font_combobox = add_row(tr("Font"))->add_field(EFFECT_FIELD_FONT, "font", 2); + set_font_combobox = add_row(tr("Font"))->add_field(EFFECT_FIELD_FONT, "font", 2); - size_val = add_row(tr("Size"))->add_field(EFFECT_FIELD_DOUBLE, "size", 2); + size_val = add_row(tr("Size"))->add_field(EFFECT_FIELD_DOUBLE, "size", 2); size_val->set_double_minimum_value(0); - set_color_button = add_row(tr("Color"))->add_field(EFFECT_FIELD_COLOR, "color", 2); + set_color_button = add_row(tr("Color"))->add_field(EFFECT_FIELD_COLOR, "color", 2); - EffectRow* alignment_row = add_row(tr("Alignment")); + EffectRow* alignment_row = add_row(tr("Alignment")); halign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "halign"); - halign_field->add_combo_item(tr("Left"), Qt::AlignLeft); - halign_field->add_combo_item(tr("Center"), Qt::AlignHCenter); - halign_field->add_combo_item(tr("Right"), Qt::AlignRight); - halign_field->add_combo_item(tr("Justify"), Qt::AlignJustify); + halign_field->add_combo_item(tr("Left"), Qt::AlignLeft); + halign_field->add_combo_item(tr("Center"), Qt::AlignHCenter); + halign_field->add_combo_item(tr("Right"), Qt::AlignRight); + halign_field->add_combo_item(tr("Justify"), Qt::AlignJustify); valign_field = alignment_row->add_field(EFFECT_FIELD_COMBO, "valign"); - valign_field->add_combo_item(tr("Top"), Qt::AlignTop); - valign_field->add_combo_item(tr("Center"), Qt::AlignVCenter); - valign_field->add_combo_item(tr("Bottom"), Qt::AlignBottom); + valign_field->add_combo_item(tr("Top"), Qt::AlignTop); + valign_field->add_combo_item(tr("Center"), Qt::AlignVCenter); + valign_field->add_combo_item(tr("Bottom"), Qt::AlignBottom); - word_wrap_field = add_row(tr("Word Wrap"))->add_field(EFFECT_FIELD_BOOL, "wordwrap", 2); + word_wrap_field = add_row(tr("Word Wrap"))->add_field(EFFECT_FIELD_BOOL, "wordwrap", 2); - outline_bool = add_row(tr("Outline"))->add_field(EFFECT_FIELD_BOOL, "outline", 2); - outline_color = add_row(tr("Outline Color"))->add_field(EFFECT_FIELD_COLOR, "outlinecolor", 2); - outline_width = add_row(tr("Outline Width"))->add_field(EFFECT_FIELD_DOUBLE, "outlinewidth", 2); + outline_bool = add_row(tr("Outline"))->add_field(EFFECT_FIELD_BOOL, "outline", 2); + outline_color = add_row(tr("Outline Color"))->add_field(EFFECT_FIELD_COLOR, "outlinecolor", 2); + outline_width = add_row(tr("Outline Width"))->add_field(EFFECT_FIELD_DOUBLE, "outlinewidth", 2); outline_width->set_double_minimum_value(0); - shadow_bool = add_row(tr("Shadow"))->add_field(EFFECT_FIELD_BOOL, "shadow", 2); - shadow_color = add_row(tr("Shadow Color"))->add_field(EFFECT_FIELD_COLOR, "shadowcolor", 2); - shadow_distance = add_row(tr("Shadow Distance"))->add_field(EFFECT_FIELD_DOUBLE, "shadowdistance", 2); + shadow_bool = add_row(tr("Shadow"))->add_field(EFFECT_FIELD_BOOL, "shadow", 2); + shadow_color = add_row(tr("Shadow Color"))->add_field(EFFECT_FIELD_COLOR, "shadowcolor", 2); + shadow_distance = add_row(tr("Shadow Distance"))->add_field(EFFECT_FIELD_DOUBLE, "shadowdistance", 2); shadow_distance->set_double_minimum_value(0); - shadow_softness = add_row(tr("Shadow Softness"))->add_field(EFFECT_FIELD_DOUBLE, "shadowsoftness", 2); + shadow_softness = add_row(tr("Shadow Softness"))->add_field(EFFECT_FIELD_DOUBLE, "shadowsoftness", 2); shadow_softness->set_double_minimum_value(0); - shadow_opacity = add_row(tr("Shadow Opacity"))->add_field(EFFECT_FIELD_DOUBLE, "shadowopacity", 2); + shadow_opacity = add_row(tr("Shadow Opacity"))->add_field(EFFECT_FIELD_DOUBLE, "shadowopacity", 2); shadow_opacity->set_double_minimum_value(0); shadow_opacity->set_double_maximum_value(100); size_val->set_double_default_value(48); - text_val->set_string_value(tr("Sample Text")); + text_val->set_string_value(tr("Sample Text")); halign_field->set_combo_index(1); valign_field->set_combo_index(1); word_wrap_field->set_bool_value(true); @@ -94,7 +94,9 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : } void TextEffect::redraw(double timecode) { - img.fill(Qt::transparent); + QColor bkg = set_color_button->get_color_value(timecode); + bkg.setAlpha(0); + img.fill(bkg); QPainter p(&img); p.setRenderHint(QPainter::Antialiasing); @@ -217,7 +219,7 @@ void TextEffect::shadow_enable(bool e) { void TextEffect::text_edit_menu() { QMenu menu; - menu.addAction(tr("&Edit Text"), this, SLOT(open_text_edit())); + menu.addAction(tr("&Edit Text"), this, SLOT(open_text_edit())); menu.exec(QCursor::pos()); } diff --git a/io/exportthread.cpp b/io/exportthread.cpp index 0931b12f3..5e4f1a34e 100644 --- a/io/exportthread.cpp +++ b/io/exportthread.cpp @@ -6,9 +6,12 @@ #include "panels/timeline.h" #include "panels/viewer.h" #include "ui/viewerwidget.h" +#include "ui/renderthread.h" +#include "ui/renderfunctions.h" #include "playback/playback.h" #include "playback/audio.h" #include "dialogs/exportdialog.h" +#include "mainwindow.h" #include "debug.h" extern "C" { @@ -111,7 +114,7 @@ bool ExportThread::setupVideo() { vcodec_ctx->sample_aspect_ratio = {1, 1}; vcodec_ctx->pix_fmt = vcodec->pix_fmts[0]; // maybe be breakable code vcodec_ctx->framerate = av_d2q(video_frame_rate, INT_MAX); - if (video_compression_type == COMPRESSION_TYPE_CBR) vcodec_ctx->bit_rate = video_bitrate * 1000000; + if (video_compression_type == COMPRESSION_TYPE_CBR) vcodec_ctx->bit_rate = qRound(video_bitrate * 1000000); vcodec_ctx->time_base = av_inv_q(vcodec_ctx->framerate); video_stream->time_base = vcodec_ctx->time_base; @@ -312,12 +315,7 @@ bool ExportThread::setupContainer() { void ExportThread::run() { panel_sequence_viewer->pause(); - - if (!panel_sequence_viewer->viewer_widget->context()->makeCurrent(&surface)) { - qCritical() << "Make current failed"; - ed->export_error = tr("could not make OpenGL context current"); - return; - } + panel_sequence_viewer->seek(start_frame); // copy filename QByteArray ba = filename.toUtf8(); @@ -339,28 +337,35 @@ void ExportThread::run() { } } - panel_sequence_viewer->seek(start_frame); - panel_sequence_viewer->reset_all_audio(); - - QOpenGLFramebufferObject fbo(sequence->width, sequence->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE); - fbo.bind(); - - panel_sequence_viewer->viewer_widget->default_fbo = &fbo; - long file_audio_samples = 0; qint64 start_time, frame_time, avg_time, eta, total_time = 0; long remaining_frames, frame_count = 1; + RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer(); + disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + connect(renderer, SIGNAL(ready()), this, SLOT(wake())); + + mutex.lock(); + while (sequence->playhead <= end_frame && continueEncode) { start_time = QDateTime::currentMSecsSinceEpoch(); - panel_sequence_viewer->viewer_widget->paintGL(); + if (audio_enabled) { + compose_audio(nullptr, sequence, true); + } + if (video_enabled) { + do { + // TODO optimize by rendering the next frame while encoding the last + renderer->start_render(nullptr, sequence, nullptr, video_frame->data[0]); + waitCond.wait(&mutex); + if (!continueEncode) break; + } while (renderer->did_texture_fail()); + if (!continueEncode) break; + } + // encode last frame while rendering next frame double timecode_secs = (double) (sequence->playhead-start_frame) / sequence->frame_rate; if (video_enabled) { - // get image from opengl - glReadPixels(0, 0, video_frame->linesize[0]/4, sequence->height, GL_RGBA, GL_UNSIGNED_BYTE, video_frame->data[0]); - // change pixel format sws_scale(sws_ctx, video_frame->data, video_frame->linesize, 0, video_frame->height, sws_frame->data, sws_frame->linesize); sws_frame->pts = qRound(timecode_secs/av_q2d(video_stream->time_base)); @@ -371,6 +376,7 @@ void ExportThread::run() { if (audio_enabled) { // do we need to encode more audio samples? while (continueEncode && file_audio_samples <= (timecode_secs*audio_sampling_rate)) { + // copy samples from audio buffer to AVFrame int adjusted_read = audio_ibuffer_read%audio_ibuffer_size; int copylen = qMin(aframe_bytes, audio_ibuffer_size-adjusted_read); memcpy(audio_frame->data[0], audio_ibuffer+adjusted_read, copylen); @@ -405,20 +411,22 @@ void ExportThread::run() { // qInfo() << "Encoded frame" << sequence->playhead << "- took" << frame_time << "ms (avg:" << avg_time << "ms, remaining:" << remaining_frames << ", ETA:" << eta << ")"; - emit progress_changed(qRound(((double) (sequence->playhead-start_frame) / (double) (end_frame-start_frame)) * 100), eta); + emit progress_changed(qRound((double(sequence->playhead-start_frame) / double(end_frame-start_frame)) * 100.0), eta); sequence->playhead++; frame_count++; } + disconnect(renderer, SIGNAL(ready()), this, SLOT(wake())); + connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + + mutex.unlock(); + if (continueEncode) { if (video_enabled) vpkt_alloc = true; if (audio_enabled) apkt_alloc = true; } - panel_sequence_viewer->viewer_widget->default_fbo = nullptr; - rendering = false; - - fbo.release(); + mainWindow->set_rendering_state(false); if (audio_enabled && continueEncode) { // flush swresample @@ -478,9 +486,8 @@ void ExportThread::run() { } delete [] c_filename; +} - panel_sequence_viewer->viewer_widget->context()->doneCurrent(); - panel_sequence_viewer->viewer_widget->context()->moveToThread(qApp->thread()); - - rendering = false; +void ExportThread::wake() { + waitCond.wakeAll(); } diff --git a/io/exportthread.h b/io/exportthread.h index 0710343db..b06ddc2c2 100644 --- a/io/exportthread.h +++ b/io/exportthread.h @@ -3,6 +3,8 @@ #include #include +#include +#include class ExportDialog; struct AVFormatContext; @@ -52,19 +54,21 @@ public: bool continueEncode; signals: void progress_changed(int value, qint64 remaining_ms); +public slots: + void wake(); private: bool encode(AVFormatContext* ofmt_ctx, AVCodecContext* codec_ctx, AVFrame* frame, AVPacket* packet, AVStream* stream, bool rescale); bool setupVideo(); bool setupAudio(); bool setupContainer(); - AVFormatContext* fmt_ctx; + AVFormatContext* fmt_ctx; AVStream* video_stream; AVCodec* vcodec; AVCodecContext* vcodec_ctx; AVFrame* video_frame; AVFrame* sws_frame; - SwsContext* sws_ctx; + SwsContext* sws_ctx; AVStream* audio_stream; AVCodec* acodec; AVFrame* audio_frame; @@ -72,14 +76,17 @@ private: AVCodecContext* acodec_ctx; AVPacket video_pkt; AVPacket audio_pkt; - SwrContext* swr_ctx; + SwrContext* swr_ctx; - bool vpkt_alloc; - bool apkt_alloc; + bool vpkt_alloc; + bool apkt_alloc; int aframe_bytes; int ret; char* c_filename; + + QMutex mutex; + QWaitCondition waitCond; }; #endif // EXPORTTHREAD_H diff --git a/io/loadthread.cpp b/io/loadthread.cpp index 957bb84aa..64d87ff9a 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -153,12 +153,12 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { if (type == LOAD_TYPE_VERSION) { int proj_version = stream.readElementText().toInt(); if (proj_version < MIN_SAVE_VERSION && proj_version > SAVE_VERSION) { - if (QMessageBox::warning( - mainWindow, - tr("Version Mismatch"), - tr("This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?"), - QMessageBox::Yes, - QMessageBox::No) == QMessageBox::No) { + if (QMessageBox::warning( + mainWindow, + tr("Version Mismatch"), + tr("This project was saved in a different version of Olive and may not be fully compatible with this version. Would you like to attempt loading it anyway?"), + QMessageBox::Yes, + QMessageBox::No) == QMessageBox::No) { show_err = false; return false; } @@ -444,11 +444,11 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { if (!found) { correct_clip->linked.removeAt(j); j--; - if (QMessageBox::warning(mainWindow, - tr("Invalid Clip Link"), - tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"), - QMessageBox::Yes, - QMessageBox::No) == QMessageBox::No) { + if (QMessageBox::warning(mainWindow, + tr("Invalid Clip Link"), + tr("This project contains an invalid clip link. It may be corrupt. Would you like to continue loading it?"), + QMessageBox::Yes, + QMessageBox::No) == QMessageBox::No) { delete s; return false; } @@ -601,7 +601,7 @@ void LoadThread::run() { xml_error = false; if (show_err) emit error(); } else if (stream.hasError()) { - error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber())); + error_str = tr("%1 - Line: %2 Col: %3").arg(stream.errorString(), QString::number(stream.lineNumber()), QString::number(stream.columnNumber())); xml_error = true; emit error(); cont = false; @@ -641,15 +641,15 @@ void LoadThread::cancel() { void LoadThread::error_func() { if (xml_error) { qCritical() << "Error parsing XML." << error_str; - QMessageBox::critical(mainWindow, - tr("XML Parsing Error"), - tr("Couldn't load '%1'. %2").arg(project_url, error_str), - QMessageBox::Ok); + QMessageBox::critical(mainWindow, + tr("XML Parsing Error"), + tr("Couldn't load '%1'. %2").arg(project_url, error_str), + QMessageBox::Ok); } else { - QMessageBox::critical(mainWindow, - tr("Project Load Error"), - tr("Error loading project: %1").arg(error_str), - QMessageBox::Ok); + QMessageBox::critical(mainWindow, + tr("Project Load Error"), + tr("Error loading project: %1").arg(error_str), + QMessageBox::Ok); } } diff --git a/main.cpp b/main.cpp index f2806308e..12e6f051e 100644 --- a/main.cpp +++ b/main.cpp @@ -20,7 +20,7 @@ int main(int argc, char *argv[]) { bool launch_fullscreen = false; QString load_proj; - qInstallMessageHandler(debug_message_handler); +// qInstallMessageHandler(debug_message_handler); if (argc > 1) { for (int i=1;iselect_all(); - } else if (focused_panel == panel_graph_editor) { + } else if (focused_panel == panel_graph_editor) { panel_graph_editor->select_all(); } } @@ -452,7 +461,7 @@ void MainWindow::open_speed_dialog() { SpeedDialog s(this); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { s.clips.append(c); } } @@ -502,7 +511,7 @@ void MainWindow::new_project() { } void MainWindow::autorecover_interval() { - if (!rendering && isWindowModified()) { + if (isWindowModified()) { panel_project->save_project(true); qInfo() << "Auto-recovery project saved"; } @@ -972,6 +981,9 @@ void MainWindow::closeEvent(QCloseEvent *e) { set_sequence(nullptr); + panel_footage_viewer->viewer_widget->close_window(); + panel_sequence_viewer->viewer_widget->close_window(); + panel_footage_viewer->set_main_sequence(); QString data_dir = get_data_path(); @@ -1051,78 +1063,78 @@ void MainWindow::reset_layout() { } void MainWindow::go_to_in() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_in(); - } else { - panel_sequence_viewer->go_to_in(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_in(); + } else { + panel_sequence_viewer->go_to_in(); + } } void MainWindow::go_to_out() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_out(); - } else { - panel_sequence_viewer->go_to_out(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_out(); + } else { + panel_sequence_viewer->go_to_out(); + } } void MainWindow::go_to_start() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_start(); - } else { - panel_sequence_viewer->go_to_start(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_start(); + } else { + panel_sequence_viewer->go_to_start(); + } } void MainWindow::prev_frame() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->previous_frame(); - } else { - panel_sequence_viewer->previous_frame(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->previous_frame(); + } else { + panel_sequence_viewer->previous_frame(); + } } void MainWindow::next_frame() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->next_frame(); - } else { - panel_sequence_viewer->next_frame(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->next_frame(); + } else { + panel_sequence_viewer->next_frame(); + } } void MainWindow::go_to_end() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->go_to_end(); - } else { - panel_sequence_viewer->go_to_end(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->go_to_end(); + } else { + panel_sequence_viewer->go_to_end(); + } } void MainWindow::playpause() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_footage_viewer) { - panel_footage_viewer->toggle_play(); - } else { - panel_sequence_viewer->toggle_play(); - } + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_footage_viewer) { + panel_footage_viewer->toggle_play(); + } else { + panel_sequence_viewer->toggle_play(); + } } void MainWindow::prev_cut() { - QDockWidget* focused_panel = get_focused_panel(); - if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) { + QDockWidget* focused_panel = get_focused_panel(); + if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) { panel_timeline->previous_cut(); } } void MainWindow::next_cut() { - QDockWidget* focused_panel = get_focused_panel(); - if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) { + QDockWidget* focused_panel = get_focused_panel(); + if (sequence != nullptr && (panel_timeline == focused_panel || panel_sequence_viewer == focused_panel)) { panel_timeline->next_cut(); } } @@ -1404,7 +1416,7 @@ void MainWindow::toggle_enable_clips() { bool push_undo = false; for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { ca->append(new SetEnableCommand(c, !c->enabled)); push_undo = true; } @@ -1419,13 +1431,13 @@ void MainWindow::toggle_enable_clips() { } void MainWindow::edit_to_in_point() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_timeline) panel_timeline->ripple_to_in_point(true, false); + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline) panel_timeline->ripple_to_in_point(true, false); } void MainWindow::edit_to_out_point() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_timeline) panel_timeline->ripple_to_in_point(false, false); + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline) panel_timeline->ripple_to_in_point(false, false); } void MainWindow::nest() { @@ -1436,7 +1448,7 @@ void MainWindow::nest() { // get selected clips for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { selected_clips.append(i); earliest_point = qMin(c->timeline_in, earliest_point); } @@ -1488,8 +1500,8 @@ void MainWindow::nest() { } void MainWindow::paste_insert() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_timeline && sequence != nullptr) { + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline && sequence != nullptr) { panel_timeline->paste(true); } } @@ -1507,11 +1519,11 @@ void MainWindow::set_autoscroll() { } void MainWindow::menu_click_button() { - QDockWidget* focused_panel = get_focused_panel(); - if (focused_panel == panel_timeline - || focused_panel == panel_effect_controls - || focused_panel == panel_footage_viewer - || focused_panel == panel_sequence_viewer) + QDockWidget* focused_panel = get_focused_panel(); + if (focused_panel == panel_timeline + || focused_panel == panel_effect_controls + || focused_panel == panel_footage_viewer + || focused_panel == panel_sequence_viewer) reinterpret_cast(static_cast(sender())->data().value())->click(); } diff --git a/mainwindow.h b/mainwindow.h index 490c75097..42b5b8ea8 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -23,7 +23,9 @@ public: void load_shortcuts(const QString &fn, bool first = false); void save_shortcuts(const QString &fn); - void load_css_from_file(const QString& fn); + void load_css_from_file(const QString& fn); + + void set_rendering_state(bool rendering); public slots: void undo(); diff --git a/olive.pro b/olive.pro index e47ba1c0a..6cc52430a 100644 --- a/olive.pro +++ b/olive.pro @@ -124,7 +124,10 @@ SOURCES += \ effects/internal/fillleftrighteffect.cpp \ effects/internal/voideffect.cpp \ dialogs/texteditdialog.cpp \ - dialogs/debugdialog.cpp + dialogs/debugdialog.cpp \ + ui/renderthread.cpp \ + ui/renderfunctions.cpp \ + ui/viewerwindow.cpp HEADERS += \ mainwindow.h \ @@ -216,7 +219,10 @@ HEADERS += \ effects/internal/fillleftrighteffect.h \ effects/internal/voideffect.h \ dialogs/texteditdialog.h \ - dialogs/debugdialog.h + dialogs/debugdialog.h \ + ui/renderthread.h \ + ui/renderfunctions.h \ + ui/viewerwindow.h FORMS += diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index 392423e89..d9ca18dad 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -34,7 +34,7 @@ EffectControls::EffectControls(QWidget *parent) : QDockWidget(parent), multiple(false), zoom(1), - panel_name(tr("Effects: ")), + panel_name(tr("Effects: ")), mode(TA_NO_TRANSITION) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -94,7 +94,7 @@ void EffectControls::menu_select(QAction* q) { update_ui(true); } else { reload_clips(); - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); } } @@ -303,7 +303,7 @@ void EffectControls::setup_ui() { QPushButton* btnAddVideoEffect = new QPushButton(veHeader); btnAddVideoEffect->setIcon(QIcon(":/icons/add-effect.png")); - btnAddVideoEffect->setToolTip(tr("Add Video Effect")); + btnAddVideoEffect->setToolTip(tr("Add Video Effect")); veHeaderLayout->addWidget(btnAddVideoEffect); connect(btnAddVideoEffect, SIGNAL(clicked(bool)), this, SLOT(video_effect_click())); @@ -314,14 +314,14 @@ void EffectControls::setup_ui() { font.setPointSize(9); lblVideoEffects->setFont(font); lblVideoEffects->setAlignment(Qt::AlignCenter); - lblVideoEffects->setText(tr("VIDEO EFFECTS")); + lblVideoEffects->setText(tr("VIDEO EFFECTS")); veHeaderLayout->addWidget(lblVideoEffects); veHeaderLayout->addStretch(); QPushButton* btnAddVideoTransition = new QPushButton(veHeader); btnAddVideoTransition->setIcon(QIcon(":/icons/add-transition.png")); - btnAddVideoTransition->setToolTip(tr("Add Video Transition")); + btnAddVideoTransition->setToolTip(tr("Add Video Transition")); connect(btnAddVideoTransition, SIGNAL(clicked(bool)), this, SLOT(video_transition_click())); veHeaderLayout->addWidget(btnAddVideoTransition); @@ -351,7 +351,7 @@ void EffectControls::setup_ui() { QPushButton* btnAddAudioEffect = new QPushButton(aeHeader); btnAddAudioEffect->setIcon(QIcon(":/icons/add-effect.png")); - btnAddAudioEffect->setToolTip(tr("Add Audio Effect")); + btnAddAudioEffect->setToolTip(tr("Add Audio Effect")); connect(btnAddAudioEffect, SIGNAL(clicked(bool)), this, SLOT(audio_effect_click())); aeHeaderLayout->addWidget(btnAddAudioEffect); @@ -360,14 +360,14 @@ void EffectControls::setup_ui() { QLabel* lblAudioEffects = new QLabel(aeHeader); lblAudioEffects->setFont(font); lblAudioEffects->setAlignment(Qt::AlignCenter); - lblAudioEffects->setText(tr("AUDIO EFFECTS")); + lblAudioEffects->setText(tr("AUDIO EFFECTS")); aeHeaderLayout->addWidget(lblAudioEffects); aeHeaderLayout->addStretch(); QPushButton* btnAddAudioTransition = new QPushButton(aeHeader); btnAddAudioTransition->setIcon(QIcon(":/icons/add-transition.png")); - btnAddAudioTransition->setToolTip(tr("Add Audio Transition")); + btnAddAudioTransition->setToolTip(tr("Add Audio Transition")); connect(btnAddAudioTransition, SIGNAL(clicked(bool)), this, SLOT(audio_transition_click())); aeHeaderLayout->addWidget(btnAddAudioTransition); @@ -384,7 +384,7 @@ void EffectControls::setup_ui() { lblMultipleClipsSelected = new QLabel(effects_area); lblMultipleClipsSelected->setAlignment(Qt::AlignCenter); - lblMultipleClipsSelected->setText(tr("(Multiple clips selected)")); + lblMultipleClipsSelected->setText(tr("(Multiple clips selected)")); effects_area_layout->addWidget(lblMultipleClipsSelected); effects_area_layout->addStretch(); diff --git a/panels/project.cpp b/panels/project.cpp index b12ee265d..035d58a0b 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -913,7 +913,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, } // save_folder(stream, item, type, set_ids_only); } else { - int folder = (m->parentItem() != nullptr) ? m->parentItem()->temp_id : 0; + int folder = m->parentItem()->temp_id; if (type == MEDIA_TYPE_FOOTAGE) { Footage* f = m->to_footage(); f->save_id = media_id; diff --git a/panels/timeline.cpp b/panels/timeline.cpp index ba20c7236..6216ed0bb 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -39,10 +39,6 @@ #include #include -long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { - return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); -} - Timeline::Timeline(QWidget *parent) : QDockWidget(parent), cursor_frame(0), @@ -397,11 +393,11 @@ void Timeline::update_sequence() { addButton->setEnabled(!null_sequence); headers->setEnabled(!null_sequence); - QString title = tr("Timeline: "); + QString title = tr("Timeline: "); if (null_sequence) { - setWindowTitle(title + tr("")); + setWindowTitle(title + tr("")); } else { - setWindowTitle(title + sequence->name); + setWindowTitle(title + sequence->name); update_ui(false); } } @@ -623,7 +619,7 @@ void Timeline::zoom_out() { set_zoom(false); } -bool Timeline::is_clip_selected(Clip* clip, bool containing) { +bool is_clip_selected(Clip* clip, bool containing) { for (int i=0;isequence->selections.size();i++) { const Selection& s = clip->sequence->selections.at(i); if (clip->track == s.track && ((clip->timeline_in >= s.in && clip->timeline_out <= s.out && containing) || @@ -1008,15 +1004,15 @@ void Timeline::paste(bool insert) { } if (found >= 0 && ask_conflict) { QMessageBox box(this); - box.setWindowTitle(tr("Effect already exists")); - box.setText(tr("Clip '%1' already contains a '%2' effect. Would you like to replace it with the pasted one or add it as a separate effect?").arg(c->name, e->meta->name)); + box.setWindowTitle(tr("Effect already exists")); + box.setText(tr("Clip '%1' already contains a '%2' effect. Would you like to replace it with the pasted one or add it as a separate effect?").arg(c->name, e->meta->name)); box.setIcon(QMessageBox::Icon::Question); - box.addButton(tr("Add"), QMessageBox::YesRole); - QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole); - QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole); + box.addButton(tr("Add"), QMessageBox::YesRole); + QPushButton* replace_button = box.addButton(tr("Replace"), QMessageBox::NoRole); + QPushButton* skip_button = box.addButton(tr("Skip"), QMessageBox::RejectRole); - QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found")); + QCheckBox* future_box = new QCheckBox(tr("Do this for all conflicts found")); box.setCheckBox(future_box); box.exec(); @@ -1390,8 +1386,8 @@ void Timeline::set_marker() { if (!add_marker) { QInputDialog d(this); - d.setWindowTitle(tr("Set Marker")); - d.setLabelText(tr("Set marker name:")); + d.setWindowTitle(tr("Set Marker")); + d.setLabelText(tr("Set marker name:")); d.setInputMode(QInputDialog::TextInput); add_marker = (d.exec() == QDialog::Accepted); marker_name = d.textValue(); @@ -1479,29 +1475,29 @@ void Timeline::add_btn_click() { QMenu add_menu(this); QAction* titleMenuItem = new QAction(&add_menu); - titleMenuItem->setText(tr("Title...")); + titleMenuItem->setText(tr("Title...")); titleMenuItem->setData(ADD_OBJ_TITLE); add_menu.addAction(titleMenuItem); QAction* solidMenuItem = new QAction(&add_menu); - solidMenuItem->setText(tr("Solid Color...")); + solidMenuItem->setText(tr("Solid Color...")); solidMenuItem->setData(ADD_OBJ_SOLID); add_menu.addAction(solidMenuItem); QAction* barsMenuItem = new QAction(&add_menu); - barsMenuItem->setText(tr("Bars...")); + barsMenuItem->setText(tr("Bars...")); barsMenuItem->setData(ADD_OBJ_BARS); add_menu.addAction(barsMenuItem); add_menu.addSeparator(); QAction* toneMenuItem = new QAction(&add_menu); - toneMenuItem->setText(tr("Tone...")); + toneMenuItem->setText(tr("Tone...")); toneMenuItem->setData(ADD_OBJ_TONE); add_menu.addAction(toneMenuItem); QAction* noiseMenuItem = new QAction(&add_menu); - noiseMenuItem->setText(tr("Noise...")); + noiseMenuItem->setText(tr("Noise...")); noiseMenuItem->setData(ADD_OBJ_NOISE); add_menu.addAction(noiseMenuItem); @@ -1523,16 +1519,16 @@ void Timeline::setScroll(int s) { void Timeline::record_btn_click() { if (project_url.isEmpty()) { - QMessageBox::critical(this, - tr("Unsaved Project"), - tr("You must save this project before you can record audio in it."), - QMessageBox::Ok); + QMessageBox::critical(this, + tr("Unsaved Project"), + tr("You must save this project before you can record audio in it."), + QMessageBox::Ok); } else { creating = true; creating_object = ADD_OBJ_AUDIO; - mainWindow->statusBar()->showMessage( - tr("Click on the timeline where you want to start recording (drag to limit the recording to a certain timeframe)"), - 10000); + mainWindow->statusBar()->showMessage( + tr("Click on the timeline where you want to start recording (drag to limit the recording to a certain timeframe)"), + 10000); } } @@ -1611,7 +1607,7 @@ void Timeline::setup_ui() { arrow_icon.addFile(QStringLiteral(":/icons/arrow-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); toolArrowButton->setIcon(arrow_icon); toolArrowButton->setCheckable(true); - toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); + toolArrowButton->setToolTip(tr("Pointer Tool") + " (V)"); toolArrowButton->setProperty("tool", TIMELINE_TOOL_POINTER); connect(toolArrowButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolArrowButton); @@ -1622,7 +1618,7 @@ void Timeline::setup_ui() { icon1.addFile(QStringLiteral(":/icons/beam-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); toolEditButton->setIcon(icon1); toolEditButton->setCheckable(true); - toolEditButton->setToolTip(tr("Edit Tool") + " (X)"); + toolEditButton->setToolTip(tr("Edit Tool") + " (X)"); toolEditButton->setProperty("tool", TIMELINE_TOOL_EDIT); connect(toolEditButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolEditButton); @@ -1633,7 +1629,7 @@ void Timeline::setup_ui() { icon2.addFile(QStringLiteral(":/icons/ripple-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); toolRippleButton->setIcon(icon2); toolRippleButton->setCheckable(true); - toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)"); + toolRippleButton->setToolTip(tr("Ripple Tool") + " (B)"); toolRippleButton->setProperty("tool", TIMELINE_TOOL_RIPPLE); connect(toolRippleButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRippleButton); @@ -1644,7 +1640,7 @@ void Timeline::setup_ui() { icon4.addFile(QStringLiteral(":/icons/razor-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); toolRazorButton->setIcon(icon4); toolRazorButton->setCheckable(true); - toolRazorButton->setToolTip(tr("Razor Tool") + " (C)"); + toolRazorButton->setToolTip(tr("Razor Tool") + " (C)"); toolRazorButton->setProperty("tool", TIMELINE_TOOL_RAZOR); connect(toolRazorButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolRazorButton); @@ -1655,7 +1651,7 @@ void Timeline::setup_ui() { icon5.addFile(QStringLiteral(":/icons/slip-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); toolSlipButton->setIcon(icon5); toolSlipButton->setCheckable(true); - toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)"); + toolSlipButton->setToolTip(tr("Slip Tool") + " (Y)"); toolSlipButton->setProperty("tool", TIMELINE_TOOL_SLIP); connect(toolSlipButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlipButton); @@ -1666,7 +1662,7 @@ void Timeline::setup_ui() { icon6.addFile(QStringLiteral(":/icons/slide-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); toolSlideButton->setIcon(icon6); toolSlideButton->setCheckable(true); - toolSlideButton->setToolTip(tr("Slide Tool") + " (U)"); + toolSlideButton->setToolTip(tr("Slide Tool") + " (U)"); toolSlideButton->setProperty("tool", TIMELINE_TOOL_SLIDE); connect(toolSlideButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolSlideButton); @@ -1677,7 +1673,7 @@ void Timeline::setup_ui() { icon7.addFile(QStringLiteral(":/icons/hand-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); toolHandButton->setIcon(icon7); toolHandButton->setCheckable(true); - toolHandButton->setToolTip(tr("Hand Tool") + " (H)"); + toolHandButton->setToolTip(tr("Hand Tool") + " (H)"); toolHandButton->setProperty("tool", TIMELINE_TOOL_HAND); connect(toolHandButton, SIGNAL(clicked(bool)), this, SLOT(set_tool())); tool_buttons_layout->addWidget(toolHandButton); @@ -1688,7 +1684,7 @@ void Timeline::setup_ui() { icon8.addFile(QStringLiteral(":/icons/transition-tool-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); toolTransitionButton->setIcon(icon8); toolTransitionButton->setCheckable(true); - toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)"); + toolTransitionButton->setToolTip(tr("Transition Tool") + " (T)"); connect(toolTransitionButton, SIGNAL(clicked(bool)), this, SLOT(transition_tool_click())); tool_buttons_layout->addWidget(toolTransitionButton); @@ -1699,7 +1695,7 @@ void Timeline::setup_ui() { snappingButton->setIcon(icon9); snappingButton->setCheckable(true); snappingButton->setChecked(true); - snappingButton->setToolTip(tr("Snapping") + " (S)"); + snappingButton->setToolTip(tr("Snapping") + " (S)"); connect(snappingButton, SIGNAL(toggled(bool)), this, SLOT(snapping_clicked(bool))); tool_buttons_layout->addWidget(snappingButton); @@ -1708,7 +1704,7 @@ void Timeline::setup_ui() { icon10.addFile(QStringLiteral(":/icons/zoomin.png"), QSize(), QIcon::Normal, QIcon::On); icon10.addFile(QStringLiteral(":/icons/zoomin-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); zoomInButton->setIcon(icon10); - zoomInButton->setToolTip(tr("Zoom In") + " (=)"); + zoomInButton->setToolTip(tr("Zoom In") + " (=)"); connect(zoomInButton, SIGNAL(clicked(bool)), this, SLOT(zoom_in())); tool_buttons_layout->addWidget(zoomInButton); @@ -1717,7 +1713,7 @@ void Timeline::setup_ui() { icon11.addFile(QStringLiteral(":/icons/zoomout.png"), QSize(), QIcon::Normal, QIcon::On); icon11.addFile(QStringLiteral(":/icons/zoomout-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); zoomOutButton->setIcon(icon11); - zoomOutButton->setToolTip(tr("Zoom Out") + " (-)"); + zoomOutButton->setToolTip(tr("Zoom Out") + " (-)"); connect(zoomOutButton, SIGNAL(clicked(bool)), this, SLOT(zoom_out())); tool_buttons_layout->addWidget(zoomOutButton); @@ -1726,7 +1722,7 @@ void Timeline::setup_ui() { icon12.addFile(QStringLiteral(":/icons/record.png"), QSize(), QIcon::Normal, QIcon::On); icon12.addFile(QStringLiteral(":/icons/record-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); recordButton->setIcon(icon12); - recordButton->setToolTip(tr("Record audio")); + recordButton->setToolTip(tr("Record audio")); connect(recordButton, SIGNAL(clicked(bool)), this, SLOT(record_btn_click())); tool_buttons_layout->addWidget(recordButton); @@ -1736,7 +1732,7 @@ void Timeline::setup_ui() { icon13.addFile(QStringLiteral(":/icons/add-button.png"), QSize(), QIcon::Normal, QIcon::On); icon13.addFile(QStringLiteral(":/icons/add-button-disabled.png"), QSize(), QIcon::Disabled, QIcon::On); addButton->setIcon(icon13); - addButton->setToolTip(tr("Add title, solid, bars, etc.")); + addButton->setToolTip(tr("Add title, solid, bars, etc.")); connect(addButton, SIGNAL(clicked()), this, SLOT(add_btn_click())); tool_buttons_layout->addWidget(addButton); diff --git a/panels/timeline.h b/panels/timeline.h index 02ef8240c..2254bfff9 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -35,7 +35,7 @@ struct Clip; struct Footage; struct FootageStream; -long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); +bool is_clip_selected(Clip* clip, bool containing); int getScreenPointFromFrame(double zoom, long frame); long getFrameFromScreenPoint(double zoom, int x); bool selection_contains_transition(const Selection& s, Clip* c, int type); @@ -137,7 +137,6 @@ public: // selecting functions bool selecting; int selection_offset; - bool is_clip_selected(Clip* clip, bool containing); void delete_selection(QVector &selections, bool ripple); void select_all(); bool rect_select_init; @@ -204,7 +203,7 @@ public: QPushButton* snappingButton; void scroll_to_frame(long frame); - void select_from_playhead(); + void select_from_playhead(); void resizeEvent(QResizeEvent *event); public slots: diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 7969e8445..c1bdfab2c 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -44,7 +44,7 @@ Viewer::Viewer(QWidget *parent) : seq(nullptr), created_sequence(false), cue_recording_internal(false), - panel_name(tr("Viewer: ")), + panel_name(tr("Viewer: ")), minimum_zoom(1.0) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); @@ -107,7 +107,9 @@ void Viewer::reset_all_audio() { for (int i=0;iclips.size();i++) { Clip* c = seq->clips.at(i); - if (c != nullptr) c->reset_audio(); + if (c != nullptr) { + c->reset_audio(); + } } } clear_audio_ibuffer(); @@ -251,9 +253,9 @@ void Viewer::seek(long p) { update_fx = true; } } - update_parents(update_fx); reset_all_audio(); audio_scrub = true; + update_parents(update_fx); } void Viewer::go_to_start() { @@ -439,7 +441,7 @@ void Viewer::resizeEvent(QResizeEvent *) { void Viewer::update_viewer() { update_header_zoom(); - viewer_widget->update(); + viewer_widget->frame_update(); if (seq != nullptr) update_playhead_timecode(seq->playhead); update_end_timecode(); } @@ -675,7 +677,7 @@ void Viewer::set_media(Media* m) { viewer_widget->waveform = true; viewer_widget->waveform_clip = c; viewer_widget->waveform_ms = &audio_stream; - viewer_widget->update(); + viewer_widget->frame_update(); } } else { seq->audio_frequency = 48000; @@ -785,12 +787,12 @@ void Viewer::set_sequence(bool main, Sequence *s) { update_playhead_timecode(0); update_end_timecode(); - setWindowTitle(panel_name + tr("(none)")); + setWindowTitle(panel_name + tr("(none)")); } update_header_zoom(); - viewer_widget->update(); + viewer_widget->frame_update(); update(); } diff --git a/playback/audio.cpp b/playback/audio.cpp index 915c42f8e..19e2f656f 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -29,6 +29,7 @@ bool audio_scrub = false; QMutex audio_write_lock; QAudioInput* audio_input = nullptr; QFile output_recording; +bool audio_rendering = false; bool recording = false; qint8 audio_ibuffer[audio_ibuffer_size]; @@ -102,13 +103,15 @@ void stop_audio() { void clear_audio_ibuffer() { if (audio_thread != nullptr) audio_thread->lock.lock(); + audio_write_lock.lock(); memset(audio_ibuffer, 0, audio_ibuffer_size); audio_ibuffer_read = 0; + audio_write_lock.unlock(); if (audio_thread != nullptr) audio_thread->lock.unlock(); } int current_audio_freq() { - return rendering ? sequence->audio_frequency : audio_output->format().sampleRate(); + return audio_rendering ? sequence->audio_frequency : audio_output->format().sampleRate(); } int get_buffer_offset_from_frame(double framerate, long frame) { @@ -292,7 +295,7 @@ bool start_recording() { return false; } - QString audio_path = project_url + " " + QCoreApplication::translate("Audio", "Audio"); + QString audio_path = project_url + " " + QCoreApplication::translate("Audio", "Audio"); QDir audio_dir(audio_path); if (!audio_dir.exists() && !audio_dir.mkpath(".")) { qCritical() << "Failed to create audio directory"; @@ -303,7 +306,7 @@ bool start_recording() { int file_number = 0; do { file_number++; - audio_filename = audio_path + "/" + QCoreApplication::translate("Audio", "Recording") + " " + QString::number(file_number) + ".wav"; + audio_filename = audio_path + "/" + QCoreApplication::translate("Audio", "Recording") + " " + QString::number(file_number) + ".wav"; } while (QFile(audio_filename).exists()); output_recording.setFileName(audio_filename); diff --git a/playback/audio.h b/playback/audio.h index 081a92e78..c2836a6f0 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -41,6 +41,8 @@ extern int audio_ibuffer_read; extern long audio_ibuffer_frame; extern double audio_ibuffer_timecode; extern bool audio_scrub; +extern bool recording; +extern bool audio_rendering; void clear_audio_ibuffer(); int current_audio_freq(); @@ -54,6 +56,5 @@ int get_buffer_offset_from_frame(double framerate, long frame); bool start_recording(); void stop_recording(); QString get_recorded_audio_filename(); -extern bool recording; #endif // AUDIO_H diff --git a/playback/cacher.cpp b/playback/cacher.cpp index b6df93249..3066402e1 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -376,11 +376,13 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { qint16 new_sample = static_cast((frame->data[0][c->frame_sample_index+1] & 0xFF) << 8 | (frame->data[0][c->frame_sample_index] & 0xFF)); qint16 mixed_sample = mix_audio_sample(old_sample, new_sample); - audio_ibuffer[upper_byte_index] = static_cast((mixed_sample >> 8) & 0xFF); - audio_ibuffer[lower_byte_index] = static_cast(mixed_sample & 0xFF); + audio_ibuffer[upper_byte_index] = quint8((mixed_sample >> 8) & 0xFF); + audio_ibuffer[lower_byte_index] = quint8(mixed_sample & 0xFF); c->audio_buffer_write+=2; c->frame_sample_index+=2; + + if (c->audio_reset) break; } #ifdef AUDIOWARNINGS @@ -389,6 +391,8 @@ void cache_audio_worker(Clip* c, bool scrubbing, QVector& nests) { audio_write_lock.unlock(); + if (c->audio_reset) return; + if (scrubbing) { if (audio_thread != nullptr) audio_thread->notifyReceiver(); } @@ -558,7 +562,9 @@ void reset_cache(Clip* c, long target_frame) { } else { if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // clear current queue + c->queue_lock.lock(); c->queue_clear(); + c->queue_lock.unlock(); // seeks to nearest keyframe (target_frame represents internal clip frame) int64_t target_ts = seconds_to_timestamp(c, playhead_to_clip_seconds(c, target_frame)); diff --git a/playback/playback.cpp b/playback/playback.cpp index 8a30a9c6d..4e16d8d54 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -33,9 +33,12 @@ extern "C" { //#define GCF_DEBUG #endif -bool texture_failed = false; bool rendering = false; +long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { + return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); +} + bool clip_uses_cacher(Clip* clip) { return (clip->media == nullptr && clip->track >= 0) || (clip->media != nullptr && clip->media->get_type() == MEDIA_TYPE_FOOTAGE); } @@ -118,7 +121,7 @@ double get_timecode(Clip* c, long playhead) { return ((double)(playhead-c->get_timeline_in_with_transition()+c->get_clip_in_with_transition())/(double)c->sequence->frame_rate); } -void get_clip_frame(Clip* c, long playhead) { +void get_clip_frame(Clip* c, long playhead, bool& texture_failed) { if (c->finished_opening) { const FootageStream* ms = c->media->to_footage()->get_stream_from_file_index(c->track < 0, c->media_stream); @@ -154,8 +157,10 @@ void get_clip_frame(Clip* c, long playhead) { #endif closest_frame = i; break; - } else if (c->queue.at(i)->pts > c->queue.at(closest_frame)->pts && c->queue.at(i)->pts < target_pts) { - closest_frame = i; + } else { + if (c->queue.at(i)->pts > c->queue.at(closest_frame)->pts && c->queue.at(i)->pts < target_pts) { + closest_frame = i; + } } } diff --git a/playback/playback.h b/playback/playback.h index db4a80600..2c0a6cd17 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -9,9 +9,7 @@ struct ClipCache; struct Sequence; struct AVFrame; -extern bool texture_failed; -extern bool rendering; - +long refactor_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); bool clip_uses_cacher(Clip* clip); void open_clip(Clip* clip, bool multithreaded); void cache_clip(Clip* clip, long playhead, bool reset, bool scrubbing, QVector &nests); @@ -20,7 +18,7 @@ void cache_audio_worker(Clip* c, bool write_A); void cache_video_worker(Clip* c, long playhead); void handle_media(Sequence* sequence, long playhead, bool multithreaded); void reset_cache(Clip* c, long target_frame); -void get_clip_frame(Clip* c, long playhead); +void get_clip_frame(Clip* c, long playhead, bool &texture_failed); double get_timecode(Clip* c, long playhead); long playhead_to_clip_frame(Clip* c, long playhead); diff --git a/project/effect.cpp b/project/effect.cpp index 4d8109f43..e777335c1 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -537,7 +537,7 @@ int Effect::gizmo_count(){ void Effect::refresh() {} void Effect::field_changed() { - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget->frame_update(); panel_graph_editor->update_panel(); } diff --git a/project/effect.h b/project/effect.h index 413df6d9f..330426b2c 100644 --- a/project/effect.h +++ b/project/effect.h @@ -134,7 +134,7 @@ public: Effect* copy(Clip* c); void copy_field_keyframes(Effect *e); - virtual void load(QXmlStreamReader& stream); + virtual void load(QXmlStreamReader& stream); virtual void custom_load(QXmlStreamReader& stream); virtual void save(QXmlStreamWriter& stream); diff --git a/project/effectrow.cpp b/project/effectrow.cpp index 9998ad189..d40c77bcf 100644 --- a/project/effectrow.cpp +++ b/project/effectrow.cpp @@ -31,9 +31,10 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt column_count = 1; - if (parent_effect->meta != nullptr - && parent_effect->meta->type != EFFECT_TYPE_TRANSITION - && keyframable) { + keyframe_nav = nullptr; + if (parent_effect->meta != nullptr + && parent_effect->meta->type != EFFECT_TYPE_TRANSITION + && keyframable) { connect(label, SIGNAL(clicked()), this, SLOT(focus_row())); keyframe_nav = new KeyframeNavigator(); @@ -53,7 +54,9 @@ bool EffectRow::isKeyframing() { void EffectRow::setKeyframing(bool b) { if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { keyframing = b; - keyframe_nav->enable_keyframes(b); + if (keyframe_nav != nullptr) { + keyframe_nav->enable_keyframes(b); + } } } @@ -64,10 +67,10 @@ void EffectRow::set_keyframe_enabled(bool enabled) { set_keyframe_now(ca); undo_stack.push(ca); } else { - if (QMessageBox::question(panel_effect_controls, - tr("Disable Keyframes"), - tr("Disabling keyframes will delete all current keyframes. Are you sure you want to do this?"), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { + if (QMessageBox::question(panel_effect_controls, + tr("Disable Keyframes"), + tr("Disabling keyframes will delete all current keyframes. Are you sure you want to do this?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { // clear ComboAction* ca = new ComboAction(); for (int i=0;iroot = true; + make_root(); } ProjectModel::~ProjectModel() { destroy_root(); } +void ProjectModel::make_root() { + root_item = new Media(nullptr); + root_item->temp_id = 0; + root_item->root = true; +} + void ProjectModel::destroy_root() { if (panel_sequence_viewer != nullptr) panel_sequence_viewer->viewer_widget->delete_function(); if (panel_footage_viewer != nullptr) panel_footage_viewer->viewer_widget->delete_function(); @@ -27,8 +32,7 @@ void ProjectModel::destroy_root() { void ProjectModel::clear() { beginResetModel(); destroy_root(); - root_item = new Media(0); - root_item->root = true; + make_root(); endResetModel(); } diff --git a/project/projectmodel.h b/project/projectmodel.h index 03ea9eadb..349080b7a 100644 --- a/project/projectmodel.h +++ b/project/projectmodel.h @@ -9,9 +9,10 @@ class ProjectModel : public QAbstractItemModel { Q_OBJECT public: - ProjectModel(QObject* parent = 0); + ProjectModel(QObject* parent = nullptr); ~ProjectModel() override; + void make_root(); void destroy_root(); void clear(); Media* get_root(); diff --git a/ui/keyframenavigator.cpp b/ui/keyframenavigator.cpp index b3bb95c32..8115ed42f 100644 --- a/ui/keyframenavigator.cpp +++ b/ui/keyframenavigator.cpp @@ -14,50 +14,58 @@ KeyframeNavigator::KeyframeNavigator(QWidget *parent) : QWidget(parent) { QSize clock_size(clock_size_val, clock_size_val); QSize button_size(button_size_val, button_size_val); - key_controls = new QHBoxLayout(); - key_controls->setSpacing(0); - key_controls->setMargin(0); + key_controls = new QHBoxLayout(); + key_controls->setSpacing(0); + key_controls->setMargin(0); //key_controls->addStretch(); - setLayout(key_controls); + setLayout(key_controls); - left_key_nav = new QPushButton(); - left_key_nav->setIcon(QIcon(":/icons/tri-left.png")); - left_key_nav->setMaximumSize(button_size); - left_key_nav->setIconSize(icon_size); - left_key_nav->setVisible(false); - key_controls->addWidget(left_key_nav); - connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key())); + left_key_nav = new QPushButton(); + left_key_nav->setIcon(QIcon(":/icons/tri-left.png")); + left_key_nav->setMaximumSize(button_size); + left_key_nav->setIconSize(icon_size); + left_key_nav->setVisible(false); + key_controls->addWidget(left_key_nav); + connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_previous_key())); connect(left_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - key_addremove = new QPushButton(); - key_addremove->setIcon(QIcon(":/icons/diamond.png")); - key_addremove->setMaximumSize(button_size); - key_addremove->setIconSize(QSize(8, 8)); - key_addremove->setVisible(false); - key_controls->addWidget(key_addremove); - connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key())); + key_addremove = new QPushButton(); + key_addremove->setIcon(QIcon(":/icons/diamond.png")); + key_addremove->setMaximumSize(button_size); + key_addremove->setIconSize(QSize(8, 8)); + key_addremove->setVisible(false); + key_controls->addWidget(key_addremove); + connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(toggle_key())); connect(key_addremove, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - right_key_nav = new QPushButton(); - right_key_nav->setIcon(QIcon(":/icons/tri-right.png")); - right_key_nav->setMaximumSize(button_size); - right_key_nav->setIconSize(icon_size); - right_key_nav->setVisible(false); - key_controls->addWidget(right_key_nav); - connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key())); + right_key_nav = new QPushButton(); + right_key_nav->setIcon(QIcon(":/icons/tri-right.png")); + right_key_nav->setMaximumSize(button_size); + right_key_nav->setIconSize(icon_size); + right_key_nav->setVisible(false); + key_controls->addWidget(right_key_nav); + connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(goto_next_key())); connect(right_key_nav, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - keyframe_enable = new QPushButton(QIcon(":/icons/clock.png"), ""); - keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); - keyframe_enable->setMaximumSize(button_size); + keyframe_enable = new QPushButton(QIcon(":/icons/clock.png"), ""); + keyframe_enable->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + keyframe_enable->setMaximumSize(button_size); keyframe_enable->setIconSize(clock_size); - keyframe_enable->setCheckable(true); - keyframe_enable->setToolTip(tr("Enable Keyframes")); + keyframe_enable->setCheckable(true); + keyframe_enable->setToolTip(tr("Enable Keyframes")); connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(keyframe_enabled_changed(bool))); - connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); + connect(keyframe_enable, SIGNAL(toggled(bool)), this, SLOT(keyframe_ui_enabled(bool))); connect(keyframe_enable, SIGNAL(clicked(bool)), this, SIGNAL(clicked())); - key_controls->addWidget(keyframe_enable); + key_controls->addWidget(keyframe_enable); +} + +KeyframeNavigator::~KeyframeNavigator() { + delete keyframe_enable; + delete right_key_nav; + delete key_addremove; + delete left_key_nav; + delete key_controls; } void KeyframeNavigator::enable_keyframes(bool b) { @@ -69,7 +77,7 @@ void KeyframeNavigator::enable_keyframe_toggle(bool b) { } void KeyframeNavigator::keyframe_ui_enabled(bool enabled) { - left_key_nav->setVisible(enabled); - key_addremove->setVisible(enabled); - right_key_nav->setVisible(enabled); + left_key_nav->setVisible(enabled); + key_addremove->setVisible(enabled); + right_key_nav->setVisible(enabled); } diff --git a/ui/keyframenavigator.h b/ui/keyframenavigator.h index 6b6eaee85..d81f87f26 100644 --- a/ui/keyframenavigator.h +++ b/ui/keyframenavigator.h @@ -8,25 +8,26 @@ class QPushButton; class KeyframeNavigator : public QWidget { - Q_OBJECT + Q_OBJECT public: - KeyframeNavigator(QWidget* parent = 0); - void enable_keyframes(bool); + KeyframeNavigator(QWidget* parent = 0); + ~KeyframeNavigator(); + void enable_keyframes(bool); void enable_keyframe_toggle(bool); signals: - void goto_previous_key(); - void toggle_key(); - void goto_next_key(); + void goto_previous_key(); + void toggle_key(); + void goto_next_key(); void keyframe_enabled_changed(bool); void clicked(); private slots: - void keyframe_ui_enabled(bool); + void keyframe_ui_enabled(bool); private: - QHBoxLayout* key_controls; - QPushButton* left_key_nav; - QPushButton* key_addremove; - QPushButton* right_key_nav; - QPushButton* keyframe_enable; + QHBoxLayout* key_controls; + QPushButton* left_key_nav; + QPushButton* key_addremove; + QPushButton* right_key_nav; + QPushButton* keyframe_enable; }; #endif // KEYFRAMENAVIGATOR_H diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp new file mode 100644 index 000000000..c719acdda --- /dev/null +++ b/ui/renderfunctions.cpp @@ -0,0 +1,503 @@ +#include "renderfunctions.h" + +#include +#include +#include +#include + +#include "project/clip.h" +#include "project/sequence.h" +#include "project/media.h" +#include "project/effect.h" +#include "project/footage.h" +#include "project/transition.h" + +#include "ui/collapsiblewidget.h" + +#include "playback/audio.h" +#include "playback/playback.h" + +#include "io/math.h" +#include "io/config.h" +#include "io/avtogl.h" + +#include "panels/timeline.h" +#include "panels/viewer.h" + +extern "C" { + #include +} + +//#define GL_DEFAULT_BLEND glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE) +#define GL_DEFAULT_BLEND glBlendFuncSeparate(GL_ONE, GL_ONE, GL_ONE, GL_ONE) + +GLuint draw_clip(QOpenGLContext* ctx, 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); + + // get current blend mode + GLint src_rgb, src_alpha, dst_rgb, dst_alpha; + glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_alpha); + glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb); + glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_alpha); + + ctx->functions()->GL_DEFAULT_BLEND; + + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); // top left + glVertex2f(0, 0); // top left + glTexCoord2f(1, 0); // top right + glVertex2f(1, 0); // top right + glTexCoord2f(1, 1); // bottom right + glVertex2f(1, 1); // bottom right + glTexCoord2f(0, 1); // bottom left + glVertex2f(0, 1); // bottom left + glEnd(); + glBindTexture(GL_TEXTURE_2D, 0); + +// fbo->release(); + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + + // restore previous blendFunc + ctx->functions()->glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha); + + //if (default_fbo != nullptr) default_fbo->bind(); + + glPopMatrix(); + return fbo->texture(); +} + +void process_effect(QOpenGLContext* ctx, + Clip* c, + Effect* e, + double timecode, + GLTextureCoords& coords, + GLuint& composite_texture, + bool& fbo_switcher, + bool& texture_failed, + int data) { + if (e->is_enabled()) { + if (e->enable_coords) { + e->process_coords(timecode, coords, data); + } + if ((e->enable_shader && shaders_are_enabled) || e->enable_superimpose) { + e->startEffect(); + if ((e->enable_shader && shaders_are_enabled) && e->is_glsl_linked()) { + e->process_shader(timecode, coords); + composite_texture = draw_clip(ctx, c->fbo[fbo_switcher], composite_texture, true); + fbo_switcher = !fbo_switcher; + } + if (e->enable_superimpose) { + GLuint superimpose_texture = e->process_superimpose(timecode); + if (superimpose_texture == 0) { + qWarning() << "Superimpose texture was nullptr, retrying..."; + texture_failed = true; + } else { + composite_texture = draw_clip(ctx, c->fbo[!fbo_switcher], superimpose_texture, false); + } + } + e->endEffect(); + } + } +} + +GLuint compose_sequence(Viewer* viewer, + QOpenGLContext* ctx, + Sequence* seq, + QVector& nests, + bool video, + bool render_audio, + Effect** gizmos, + bool& texture_failed, + bool rendering) { + GLint current_fbo = 0; + if (video) { + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + } + + Sequence* s = seq; + long playhead = s->playhead; + + if (!nests.isEmpty()) { + for (int i=0;imedia->to_sequence(); + playhead += nests.at(i)->clip_in - nests.at(i)->get_timeline_in_with_transition(); + playhead = refactor_frame_number(playhead, nests.at(i)->sequence->frame_rate, s->frame_rate); + } + + if (video && nests.last()->fbo != nullptr) { + nests.last()->fbo[0]->bind(); + glClear(GL_COLOR_BUFFER_BIT); +// nests.last()->fbo[0]->release(); + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + } + } + + int audio_track_count = 0; + + QVector current_clips; + + 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 (!(!nests.isEmpty() && !same_sign(c->track, nests.last()->track))) { + if ((c->track < 0) == video) { + bool clip_is_active = false; + + if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { + Footage* m = c->media->to_footage(); + if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { + if (m->ready) { + const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); + 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 + if (!c->open) { + open_clip(c, !rendering); + } + clip_is_active = true; + if (c->track >= 0) audio_track_count++; + } else if (c->open) { + close_clip(c, false); + } + } else { + //qWarning() << "Media '" + m->name + "' was not ready, retrying..."; + texture_failed = true; + } + } + } else { + if (is_clip_active(c, playhead)) { + if (!c->open) open_clip(c, !rendering); + clip_is_active = true; + } else if (c->open) { + close_clip(c, false); + } + } + if (clip_is_active) { + bool added = false; + for (int j=0;jtrack < c->track) { + current_clips.insert(j, c); + added = true; + break; + } + } + if (!added) { + current_clips.append(c); + } + } + } + } + } + + int half_width = s->width/2; + int half_height = s->height/2; + + if (video) { + glPushMatrix(); + glLoadIdentity(); + glOrtho(-half_width, half_width, -half_height, half_height, -1, 10); + } + + 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"; + texture_failed = true; + } else { + if (c->track < 0) { + ctx->functions()->GL_DEFAULT_BLEND; + glColor4f(1.0, 1.0, 1.0, 1.0); + + GLuint textureID = 0; + 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, playhead, texture_failed); + textureID = c->texture->textureId(); + break; + case MEDIA_TYPE_SEQUENCE: + textureID = -1; + break; + } + } + + if (textureID == 0 && c->media != nullptr) { + qWarning() << "Texture hasn't been created yet"; + texture_failed = true; + } else 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); + } + + // clear fbos + /*c->fbo[0]->bind(); + glClear(GL_COLOR_BUFFER_BIT); + c->fbo[0]->release(); + c->fbo[1]->bind(); + glClear(GL_COLOR_BUFFER_BIT); + c->fbo[1]->release();*/ + + bool fbo_switcher = false; + + glViewport(0, 0, video_width, video_height); + + GLuint composite_texture; + + if (c->media == nullptr) { + c->fbo[fbo_switcher]->bind(); + glClear(GL_COLOR_BUFFER_BIT); +// c->fbo[fbo_switcher]->release(); + 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) { + nests.append(c); + textureID = compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering); + nests.removeLast(); + fbo_switcher = true; + } + + composite_texture = draw_clip(ctx, c->fbo[fbo_switcher], textureID, true); + } + + fbo_switcher = !fbo_switcher; + + // set up default coords + GLTextureCoords coords; + coords.grid_size = 1; + coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; + coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2; + coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2; + coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2; + coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1; + 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; + + // set up autoscale + 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); + float scale_multiplier = qMin(width_multiplier, height_multiplier); + glScalef(scale_multiplier, scale_multiplier, 1); + } + + // EFFECT CODE START + double timecode = get_timecode(c, playhead); + + Effect* first_gizmo_effect = nullptr; + Effect* selected_effect = nullptr; + + for (int j=0;jeffects.size();j++) { + Effect* e = c->effects.at(j); + process_effect(ctx, c, e, timecode, coords, composite_texture, fbo_switcher, texture_failed, TA_NO_TRANSITION); + + if (e->are_gizmos_enabled()) { + if (first_gizmo_effect == nullptr) first_gizmo_effect = e; + if (e->container->selected) selected_effect = e; + } + } + + if (selected_effect != nullptr) { + (*gizmos) = selected_effect; + } else if (is_clip_selected(c, true)) { + (*gizmos) = first_gizmo_effect; + } + + 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(ctx, c, c->get_opening_transition(), (double)transition_progress/(double)c->get_opening_transition()->get_length(), coords, composite_texture, fbo_switcher, texture_failed, TA_OPENING_TRANSITION); + } + } + + 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(ctx, c, c->get_closing_transition(), (double)transition_progress/(double)c->get_closing_transition()->get_length(), coords, composite_texture, fbo_switcher, texture_failed, TA_CLOSING_TRANSITION); + } + } + // EFFECT CODE END + + /*if (!nests.isEmpty()) { + nests.last()->fbo[0]->bind(); + glViewport(0, 0, s->width, s->height); + } else if (rendering) { + glViewport(0, 0, s->width, s->height); + } else { + int widget_width = width(); + int widget_height = height(); + + widget_width *= QApplication::desktop()->devicePixelRatio(); + widget_height *= QApplication::desktop()->devicePixelRatio(); + + glViewport(0, 0, widget_width, widget_height); + }*/ + if (!nests.isEmpty()) { + nests.last()->fbo[0]->bind(); + } + glViewport(0, 0, s->width, s->height); + + glBindTexture(GL_TEXTURE_2D, composite_texture); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + 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 + glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right + glVertex3f(coords.vertexTopRightX, coords.vertexTopRightY, z); // top right + glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right + glVertex3f(coords.vertexBottomRightX, coords.vertexBottomRightY, z); // bottom right + glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left + glVertex3f(coords.vertexBottomLeftX, coords.vertexBottomLeftY, z); // bottom left + } else { + float rows = coords.grid_size; + float cols = coords.grid_size; + + for (float k=0;kgizmo_draw(timecode, coords); // set correct gizmo coords + (*gizmos)->gizmo_world_to_screen(); // convert gizmo coords to screen coords + } + + if (!nests.isEmpty()) { + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo); + } + + glPopMatrix(); + + /*GLfloat motion_blur_frac = (GLfloat) motion_blur_prog / (GLfloat) motion_blur_lim; + if (motion_blur_prog == 0) { + glAccum(GL_LOAD, motion_blur_frac); + } else { + glAccum(GL_ACCUM, motion_blur_frac); + } + motion_blur_prog++;*/ + } + } else { + if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) { + if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { + nests.append(c); + compose_sequence(viewer, ctx, seq, nests, video, render_audio, gizmos, texture_failed, rendering); + nests.removeLast(); + } else { + if (c->lock.tryLock()) { + // clip is not caching, start caching audio + cache_clip(c, playhead, c->audio_reset, !render_audio, nests); + c->lock.unlock(); + } + } + } + + // visually update all the keyframe values + if (c->sequence == seq) { // only if you can currently see them + double ts = (playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition())/s->frame_rate; + for (int i=0;ieffects.size();i++) { + Effect* e = c->effects.at(i); + for (int j=0;jrow_count();j++) { + EffectRow* r = e->row(j); + for (int k=0;kfieldCount();k++) { + r->field(k)->validate_keyframe_data(ts); + } + } + } + } + } + } + } + + if (audio_track_count == 0 && viewer != nullptr) { + viewer->play_wake(); + } + + if (video) { + glPopMatrix(); + } + + if (!nests.isEmpty() && nests.last()->fbo != nullptr) { + // returns nested clip's texture + return nests.last()->fbo[0]->texture(); + } + + return 0; +} + +void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio) { + QVector nests; + bool texture_failed; + compose_sequence(viewer, nullptr, seq, nests, false, render_audio, nullptr, texture_failed, audio_rendering); +} diff --git a/ui/renderfunctions.h b/ui/renderfunctions.h new file mode 100644 index 000000000..de405bf8c --- /dev/null +++ b/ui/renderfunctions.h @@ -0,0 +1,26 @@ +#ifndef RENDERFUNCTIONS_H +#define RENDERFUNCTIONS_H + +#include +#include + +class Effect; +class Viewer; +struct Sequence; +struct Clip; + +GLuint compose_sequence(Viewer* viewer, + QOpenGLContext* ctx, + Sequence* seq, + QVector& nests, + bool video, + bool render_audio, + Effect **gizmos, + bool &texture_failed, + bool rendering); + +void compose_audio(Viewer* viewer, Sequence* seq, bool render_audio); + +void viewport_render(); + +#endif // RENDERFUNCTIONS_H diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp new file mode 100644 index 000000000..c1ead032d --- /dev/null +++ b/ui/renderthread.cpp @@ -0,0 +1,199 @@ +#include "renderthread.h" + +#include +#include +#include +#include + +#include "ui/renderfunctions.h" +#include "playback/playback.h" +#include "project/sequence.h" + +RenderThread::RenderThread() : + frameBuffer(0), + texColorBuffer(0), + gizmos(nullptr), + share_ctx(nullptr), + ctx(nullptr), + seq(nullptr), + tex_width(-1), + tex_height(-1), + queued(false), + texture_failed(false), + running(true) +{ + surface.create(); +} + +RenderThread::~RenderThread() { + surface.destroy(); +} + +void RenderThread::run() { + mutex.lock(); + + while (running) { + if (!queued) { + waitCond.wait(&mutex); + } + if (!running) { + break; + } + queued = false; + + + if (share_ctx != nullptr) { + if (ctx != nullptr) { + ctx->makeCurrent(&surface); + + // gen fbo + if (frameBuffer == 0) { + delete_fbo(); + ctx->functions()->glGenFramebuffers(1, &frameBuffer); + } + + // 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 + ); + 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); + } + + // draw + paint(); + + // flush changes +// glFlush(); + glFinish(); + + // release + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0); + + emit ready(); + } + } + } + + delete_ctx(); + + mutex.unlock(); +} + +void RenderThread::paint() { + glLoadIdentity(); + + texture_failed = false; + + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + glClearColor(0, 0, 0, 0); + glMatrixMode(GL_MODELVIEW); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_DEPTH); + + gizmos = nullptr; + QVector nests; + compose_sequence(nullptr, ctx, seq, nests, true, false, &gizmos, texture_failed, false); + + if (!save_fn.isEmpty()) { + if (texture_failed) { + // texture failed, try again + queued = true; + } else { + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); + 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); + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + save_fn = ""; + } + } + + if (pixel_buffer != nullptr) { + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, frameBuffer); + glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, pixel_buffer); + ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + pixel_buffer = nullptr; + } + + glDisable(GL_DEPTH); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + +void RenderThread::start_render(QOpenGLContext *share, Sequence *s, const QString& save, GLvoid* pixels, int idivider) { + seq = s; + + // stall any dependent actions + texture_failed = true; + + if (share != nullptr && (ctx == nullptr || ctx->shareContext() != share_ctx)) { + share_ctx = share; + delete_ctx(); + ctx = new QOpenGLContext(); + ctx->setFormat(share_ctx->format()); + ctx->setShareContext(share_ctx); + ctx->create(); + ctx->moveToThread(this); + } + + save_fn = save; + pixel_buffer = pixels; + + queued = true; + waitCond.wakeAll(); +} + +bool RenderThread::did_texture_fail() { + return texture_failed; +} + +void RenderThread::cancel() { + running = false; + waitCond.wakeAll(); + wait(); +} + +void RenderThread::delete_texture() { + if (texColorBuffer > 0) { + ctx->functions()->glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 + ); + glDeleteTextures(1, &texColorBuffer); + } + texColorBuffer = 0; +} + +void RenderThread::delete_fbo() { + if (frameBuffer > 0) { + ctx->functions()->glDeleteFramebuffers(1, &frameBuffer); + } + frameBuffer = 0; +} + +void RenderThread::delete_ctx() { + if (ctx != nullptr) { + delete_texture(); + delete_fbo(); + ctx->doneCurrent(); + delete ctx; + } + ctx = nullptr; +} diff --git a/ui/renderthread.h b/ui/renderthread.h new file mode 100644 index 000000000..cd4f1aec3 --- /dev/null +++ b/ui/renderthread.h @@ -0,0 +1,54 @@ +#ifndef RENDERTHREAD_H +#define RENDERTHREAD_H + +#include +#include +#include +#include +#include +#include + +struct Sequence; +class Effect; + +class RenderThread : public QThread { + Q_OBJECT +public: + RenderThread(); + ~RenderThread(); + void run(); + QMutex mutex; + GLuint frameBuffer; + GLuint texColorBuffer; + Effect* gizmos; + void paint(); + void start_render(QOpenGLContext* share, Sequence* s, const QString &save = nullptr, GLvoid *pixels = nullptr, int idivider = 0); + bool did_texture_fail(); + void cancel(); + +public slots: + // cleanup functions + void delete_ctx(); +signals: + void ready(); +private: + // cleanup functions + void delete_texture(); + void delete_fbo(); + + QWaitCondition waitCond; + QOffscreenSurface surface; + QOpenGLContext* share_ctx; + QOpenGLContext* ctx; + Sequence* seq; + int divider; + int tex_width; + int tex_height; + bool queued; + bool texture_failed; + bool running; + QString save_fn; + GLvoid *pixel_buffer; +}; + +#endif // RENDERTHREAD_H diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 118bdd6f2..8e8c1ed62 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -98,7 +98,7 @@ void TimelineWidget::show_context_menu(const QPoint& pos) { QVector selected_clips; for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { selected_clips.append(c); } } @@ -204,7 +204,7 @@ void TimelineWidget::toggle_autoscale() { SetAutoscaleAction* action = new SetAutoscaleAction(); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { action->clips.append(c); } } @@ -237,7 +237,7 @@ void TimelineWidget::rename_clip() { QVector selected_clips; for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && is_clip_selected(c, true)) { selected_clips.append(c); } } @@ -629,7 +629,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { if (clip_index >= 0) { Clip* clip = sequence->clips.at(clip_index); if (clip != nullptr) { - if (panel_timeline->is_clip_selected(clip, true)) { + if (is_clip_selected(clip, true)) { if (shift) { panel_timeline->deselect_area(clip->timeline_in, clip->timeline_out, clip->track); @@ -695,7 +695,7 @@ void TimelineWidget::mousePressEvent(QMouseEvent *event) { if (!alt && panel_timeline->transition_select == TA_NO_TRANSITION) { for (int i=0;ilinked.size();i++) { Clip* link = sequence->clips.at(clip->linked.at(i)); - if (!panel_timeline->is_clip_selected(link, true)) { + if (!is_clip_selected(link, true)) { Selection ss; ss.in = link->timeline_in; ss.out = link->timeline_out; @@ -1696,7 +1696,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { Ghost g; g.transition = nullptr; - bool add = panel_timeline->is_clip_selected(c, true); + bool add = is_clip_selected(c, true); // if a whole clip is not selected, maybe just a transition is if (panel_timeline->tool == TIMELINE_TOOL_POINTER && (c->get_opening_transition() != nullptr || c->get_closing_transition() != nullptr)) { @@ -1838,7 +1838,7 @@ void TimelineWidget::mouseMoveEvent(QMouseEvent *event) { for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); - if (c != nullptr && !panel_timeline->is_clip_selected(c, true)) { + if (c != nullptr && !is_clip_selected(c, true)) { bool clip_is_post = (c->timeline_in >= axis); // see if this a clip on this track is already in the list, and if it's closer diff --git a/ui/viewercontainer.cpp b/ui/viewercontainer.cpp index 21f709b1c..145cd49fc 100644 --- a/ui/viewercontainer.cpp +++ b/ui/viewercontainer.cpp @@ -12,85 +12,86 @@ // enforces aspect ratio ViewerContainer::ViewerContainer(QWidget *parent) : - QScrollArea(parent), - fit(true), - child(nullptr) + QScrollArea(parent), + fit(true), + child(nullptr) { - setFrameShadow(QFrame::Plain); - setFrameShape(QFrame::NoFrame); + setFrameShadow(QFrame::Plain); + setFrameShape(QFrame::NoFrame); - area = new QWidget(this); - area->move(0, 0); - setWidget(area); + area = new QWidget(this); + area->move(0, 0); + setWidget(area); - child = new ViewerWidget(area); - child->container = this; + child = new ViewerWidget(area); + child->container = this; } ViewerContainer::~ViewerContainer() { - delete area; + delete child; + delete area; } void ViewerContainer::dragScrollPress(const QPoint &p) { - drag_start_x = p.x(); - drag_start_y = p.y(); - horiz_start = horizontalScrollBar()->value(); - vert_start = verticalScrollBar()->value(); + drag_start_x = p.x(); + drag_start_y = p.y(); + horiz_start = horizontalScrollBar()->value(); + vert_start = verticalScrollBar()->value(); } void ViewerContainer::dragScrollMove(const QPoint &p) { - int true_x = p.x() + (horiz_start - horizontalScrollBar()->value()); - int true_y = p.y() + (vert_start - verticalScrollBar()->value()); + int true_x = p.x() + (horiz_start - horizontalScrollBar()->value()); + int true_y = p.y() + (vert_start - verticalScrollBar()->value()); - horizontalScrollBar()->setValue(horizontalScrollBar()->value() + (drag_start_x - true_x)); - verticalScrollBar()->setValue(verticalScrollBar()->value() + (drag_start_y - true_y)); + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + (drag_start_x - true_x)); + verticalScrollBar()->setValue(verticalScrollBar()->value() + (drag_start_y - true_y)); - drag_start_x = true_x; - drag_start_y = true_y; + drag_start_x = true_x; + drag_start_y = true_y; } void ViewerContainer::adjust() { - if (viewer->seq != nullptr) { - if (child->waveform) { - child->move(0, 0); - child->resize(size()); - } else if (fit) { - double aspect_ratio = double(viewer->seq->width)/double(viewer->seq->height); + if (viewer->seq != nullptr) { + if (child->waveform) { + child->move(0, 0); + child->resize(size()); + } else if (fit) { + double aspect_ratio = double(viewer->seq->width)/double(viewer->seq->height); - int widget_x = 0; - int widget_y = 0; - int widget_width = width(); - int widget_height = height(); - float widget_ar = (float) widget_width /(float) widget_height; + int widget_x = 0; + int widget_y = 0; + int widget_width = width(); + int widget_height = height(); + double widget_ar = double(widget_width) / double(widget_height); - bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; + bool widget_is_wider_than_sequence = widget_ar > aspect_ratio; - if (widget_is_wider_than_sequence) { - widget_width = widget_height * aspect_ratio; - widget_x = (width() / 2) - (widget_width / 2); - } else { - widget_height = widget_width / aspect_ratio; - widget_y = (height() / 2) - (widget_height / 2); - } + if (widget_is_wider_than_sequence) { + widget_width = widget_height * aspect_ratio; + widget_x = (width() / 2) - (widget_width / 2); + } else { + widget_height = widget_width / aspect_ratio; + widget_y = (height() / 2) - (widget_height / 2); + } - child->move(widget_x, widget_y); - child->resize(widget_width, widget_height); + child->move(widget_x, widget_y); + child->resize(widget_width, widget_height); - zoom = double(widget_width) / double(viewer->seq->width); - } else { - int zoomed_width = double(viewer->seq->width)*zoom; - int zoomed_height = double(viewer->seq->height)*zoom; - int zoomed_x = 0; - int zoomed_y = 0; + zoom = double(widget_width) / double(viewer->seq->width); + } else { + int zoomed_width = double(viewer->seq->width)*zoom; + int zoomed_height = double(viewer->seq->height)*zoom; + int zoomed_x = 0; + int zoomed_y = 0; - if (zoomed_width < width()) zoomed_x = (width()>>1)-(zoomed_width>>1); - if (zoomed_height < height()) zoomed_y = (height()>>1)-(zoomed_height>>1); + if (zoomed_width < width()) zoomed_x = (width()>>1)-(zoomed_width>>1); + if (zoomed_height < height()) zoomed_y = (height()>>1)-(zoomed_height>>1); - child->move(zoomed_x, zoomed_y); - child->resize(zoomed_width, zoomed_height); - } + child->move(zoomed_x, zoomed_y); + child->resize(zoomed_width, zoomed_height); + } } - area->resize(qMax(width(), child->width()), qMax(height(), child->height())); + area->resize(qMax(width(), child->width()), qMax(height(), child->height())); } void ViewerContainer::resizeEvent(QResizeEvent *event) { diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 6aca8f47b..7791d28d4 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -21,6 +21,10 @@ #include "ui/viewercontainer.h" #include "io/avtogl.h" #include "ui/timelinewidget.h" +#include "ui/renderfunctions.h" +#include "ui/renderthread.h" +#include "ui/viewerwindow.h" +#include "mainwindow.h" #include #include @@ -37,21 +41,22 @@ #include #include #include +#include +#include extern "C" { #include } -#define GL_DEFAULT_BLEND glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); - ViewerWidget::ViewerWidget(QWidget *parent) : QOpenGLWidget(parent), - default_fbo(nullptr), waveform(false), waveform_zoom(1.0), waveform_scroll(0), dragging(false), - selected_gizmo(nullptr) + gizmos(nullptr), + selected_gizmo(nullptr), + window(nullptr) { setMouseTracking(true); setFocusPolicy(Qt::ClickFocus); @@ -60,21 +65,26 @@ ViewerWidget::ViewerWidget(QWidget *parent) : format.setDepthBufferSize(24); setFormat(format); - // error handler - retries after 50ms if we couldn't get the entire image - retry_timer.setInterval(50); - connect(&retry_timer, SIGNAL(timeout()), this, SLOT(retry())); - setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); + + renderer = new RenderThread(); + renderer->start(QThread::HighPriority); + connect(renderer, SIGNAL(ready()), this, SLOT(queue_repaint())); + connect(renderer, SIGNAL(finished()), renderer, SLOT(deleteLater())); +} + +ViewerWidget::~ViewerWidget() { + if (window != nullptr) { + window->close(); + delete window; + } + renderer->cancel(); + delete renderer; } void ViewerWidget::delete_function() { - // destroy all textures as well - if (viewer->seq != nullptr) { - makeCurrent(); - closeActiveClips(viewer->seq); - doneCurrent(); - } + closeActiveClips(viewer->seq); } void ViewerWidget::set_waveform_scroll(int s) { @@ -90,8 +100,21 @@ void ViewerWidget::show_context_menu() { QAction* save_frame_as_image = menu.addAction(tr("Save Frame as Image...")); connect(save_frame_as_image, SIGNAL(triggered(bool)), this, SLOT(save_frame())); - QAction* show_fullscreen_action = menu.addAction(tr("Show Fullscreen")); - connect(show_fullscreen_action, SIGNAL(triggered()), this, SLOT(show_fullscreen())); + /*QAction* show_fullscreen_action = menu.addAction(tr("Show Fullscreen")); + connect(show_fullscreen_action, SIGNAL(triggered()), this, SLOT(show_fullscreen()));*/ + QMenu* fullscreen_menu = menu.addMenu(tr("Show Fullscreen")); + QList screens = QGuiApplication::screens(); + if (window != nullptr && window->isVisible()) { + fullscreen_menu->addAction(tr("Disable")); + } + for (int i=0;iaddAction(tr("Screen %1: %2x%3").arg( + QString::number(i), + QString::number(screens.at(i)->size().width()), + QString::number(screens.at(i)->size().height()))); + screen_action->setData(i); + } + connect(fullscreen_menu, SIGNAL(triggered(QAction*)), this, SLOT(fullscreen_menu_action(QAction*))); QMenu zoom_menu(tr("Zoom")); QAction* fit_zoom = zoom_menu.addAction(tr("Fit")); @@ -129,27 +152,30 @@ void ViewerWidget::save_frame() { if (!fn.endsWith(selected_ext, Qt::CaseInsensitive)) { fn += selected_ext; } - QOpenGLFramebufferObject fbo(viewer->seq->width, viewer->seq->height, QOpenGLFramebufferObject::CombinedDepthStencil, GL_TEXTURE_RECTANGLE); - rendering = true; - fbo.bind(); - - default_fbo = &fbo; - - paintGL(); - - QImage img(viewer->seq->width, viewer->seq->height, QImage::Format_RGBA8888); - glReadPixels(0, 0, img.width(), img.height(), GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); - img.save(fn); - - fbo.release(); - default_fbo = nullptr; - rendering = false; + renderer->start_render(context(), viewer->seq, fn); } } -void ViewerWidget::show_fullscreen() { - showFullScreen(); +void ViewerWidget::queue_repaint() { + update(); +} + +void ViewerWidget::fullscreen_menu_action(QAction *action) { + if (window == nullptr) { + QMessageBox::critical(this, "Error", "Failed to create viewer window"); + } else { + if (action->data().isNull()) { + window->hide(); + } else { + QScreen* selected_screen = QGuiApplication::screens().at(action->data().toInt()); + window->showFullScreen(); + window->setGeometry(selected_screen->geometry()); + + // HACK: window seems to show with distorted texture on first showing, so we queue an update after it's shown + QTimer::singleShot(100, window, SLOT(update())); + } + } } void ViewerWidget::set_fit_zoom() { @@ -186,30 +212,63 @@ void ViewerWidget::retry() { void ViewerWidget::initializeGL() { initializeOpenGLFunctions(); - connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(delete_function()), Qt::DirectConnection); + connect(context(), SIGNAL(aboutToBeDestroyed()), this, SLOT(context_destroy()), Qt::DirectConnection); - retry_timer.start(); + window = new ViewerWindow(context()); +} + +void ViewerWidget::frame_update() { + if (viewer->seq != nullptr) { + bool render_audio = (viewer->playing || audio_rendering); + + // send context to other thread for drawing + if (waveform) { + update(); + } else { + doneCurrent(); + renderer->start_render(context(), viewer->seq); + } + + // render the audio + compose_audio(viewer, viewer->seq, render_audio); + } +} + +RenderThread *ViewerWidget::get_renderer() { + return renderer; } //void ViewerWidget::resizeGL(int w, int h) //{ //} -void ViewerWidget::paintEvent(QPaintEvent *e) { - if (!rendering && context()->thread() == this->thread()) { - makeCurrent(); +/*void ViewerWidget::paintEvent(QPaintEvent *e) { + if (!rendering) { QOpenGLWidget::paintEvent(e); } -} +}*/ void ViewerWidget::seek_from_click(int x) { viewer->seek(getFrameFromScreenPoint(waveform_zoom, x+waveform_scroll)); } +void ViewerWidget::context_destroy() { + makeCurrent(); + if (viewer->seq != nullptr) { + closeActiveClips(viewer->seq); + } + if (window != nullptr) { + delete window; + } + //QMetaObject::invokeMethod(renderer, "delete_ctx", Qt::QueuedConnection); + renderer->delete_ctx(); + doneCurrent(); +} + EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { if (gizmos != nullptr) { double multiplier = double(viewer->seq->width) / double(width()); - QPoint mouse_pos(qRound(x*multiplier), qRound(y*multiplier)); + QPoint mouse_pos(qRound(x*multiplier), qRound((height()-y)*multiplier)); int dot_size = 2 * qRound(GIZMO_DOT_SIZE * multiplier); int target_size = 2 * qRound(GIZMO_TARGET_SIZE * multiplier); for (int i=0;igizmo_count();i++) { @@ -238,7 +297,6 @@ EffectGizmo* ViewerWidget::get_gizmo_from_mouse(int x, int y) { } break; } - } } return nullptr; @@ -248,8 +306,8 @@ void ViewerWidget::move_gizmos(QMouseEvent *event, bool done) { if (selected_gizmo != nullptr) { double multiplier = double(viewer->seq->width) / double(width()); - int x_movement = (event->pos().x() - drag_start_x)*multiplier; - int y_movement = (event->pos().y() - drag_start_y)*multiplier; + int x_movement = qRound((event->pos().x() - drag_start_x)*multiplier); + int y_movement = qRound((event->pos().y() - drag_start_y)*multiplier); gizmos->gizmo_move(selected_gizmo, x_movement, y_movement, get_timecode(gizmos->parent_clip, gizmos->parent_clip->sequence->playhead), done); @@ -315,11 +373,38 @@ void ViewerWidget::mouseMoveEvent(QMouseEvent* event) { } void ViewerWidget::mouseReleaseEvent(QMouseEvent *event) { - if (dragging) move_gizmos(event, true); + if (dragging && gizmos != nullptr && !(event->button() == Qt::MiddleButton || panel_timeline->tool == TIMELINE_TOOL_HAND)) { + move_gizmos(event, true); + } dragging = false; } -void ViewerWidget::drawTitleSafeArea() { +void ViewerWidget::close_window() { + if (window != nullptr) window->hide(); +} + +void ViewerWidget::draw_waveform_func() { + QPainter p(this); + if (viewer->seq->using_workarea) { + int in_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_in) - waveform_scroll; + int out_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_out) - waveform_scroll; + + p.fillRect(QRect(in_x, 0, out_x - in_x, height()), QColor(255, 255, 255, 64)); + p.setPen(Qt::white); + p.drawLine(in_x, 0, in_x, height()); + p.drawLine(out_x, 0, out_x, height()); + } + QRect wr = rect(); + wr.setX(wr.x() - waveform_scroll); + + p.setPen(Qt::green); + draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out, &p, wr, waveform_scroll, width()+waveform_scroll, waveform_zoom); + p.setPen(Qt::red); + int playhead_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->playhead) - waveform_scroll; + p.drawLine(playhead_x, 0, playhead_x, height()); +} + +void ViewerWidget::draw_title_safe_area() { double halfWidth = 0.5; double halfHeight = 0.5; double viewportAr = (double) width() / (double) height(); @@ -387,571 +472,129 @@ void ViewerWidget::drawTitleSafeArea() { glEnd(); } -GLuint ViewerWidget::draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { - glPushMatrix(); - glLoadIdentity(); - glOrtho(0, 1, 0, 1, -1, 1); +void ViewerWidget::draw_gizmos() { + float color[4]; + glGetFloatv(GL_CURRENT_COLOR, color); - fbo->bind(); - - if (clear) glClear(GL_COLOR_BUFFER_BIT); - - // get current blend mode - GLint src_rgb, src_alpha, dst_rgb, dst_alpha; - glGetIntegerv(GL_BLEND_SRC_RGB, &src_rgb); - glGetIntegerv(GL_BLEND_SRC_ALPHA, &src_alpha); - glGetIntegerv(GL_BLEND_DST_RGB, &dst_rgb); - glGetIntegerv(GL_BLEND_DST_ALPHA, &dst_alpha); - - GL_DEFAULT_BLEND - - glBindTexture(GL_TEXTURE_2D, texture); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); // top left - glVertex2f(0, 0); // top left - glTexCoord2f(1, 0); // top right - glVertex2f(1, 0); // top right - glTexCoord2f(1, 1); // bottom right - glVertex2f(1, 1); // bottom right - glTexCoord2f(0, 1); // bottom left - glVertex2f(0, 1); // bottom left - glEnd(); - glBindTexture(GL_TEXTURE_2D, 0); - - fbo->release(); - - // restore previous blendFunc - glBlendFuncSeparate(src_rgb, dst_rgb, src_alpha, dst_alpha); - - if (default_fbo != nullptr) default_fbo->bind(); - - glPopMatrix(); - return fbo->texture(); -} - -void ViewerWidget::process_effect(Clip* c, Effect* e, double timecode, GLTextureCoords& coords, GLuint& composite_texture, bool& fbo_switcher, int data) { - if (e->is_enabled()) { - if (e->enable_coords) { - e->process_coords(timecode, coords, data); - } - if ((e->enable_shader && shaders_are_enabled) || e->enable_superimpose) { - e->startEffect(); - if ((e->enable_shader && shaders_are_enabled) && e->is_glsl_linked()) { - e->process_shader(timecode, coords); - composite_texture = draw_clip(c->fbo[fbo_switcher], composite_texture, true); - fbo_switcher = !fbo_switcher; - } - if (e->enable_superimpose) { - GLuint superimpose_texture = e->process_superimpose(timecode); - if (superimpose_texture == 0) { - qWarning() << "Superimpose texture was nullptr, retrying..."; - texture_failed = true; - } else { - composite_texture = draw_clip(c->fbo[!fbo_switcher], superimpose_texture, false); - } - } - e->endEffect(); - } - } -} - -int motion_blur_prog = 0; -int motion_blur_lim = 4; - -GLuint ViewerWidget::compose_sequence(QVector& nests, bool render_audio) { - Sequence* s = viewer->seq; - long playhead = s->playhead; - - if (!nests.isEmpty()) { - for (int i=0;imedia->to_sequence(); - playhead += nests.at(i)->clip_in - nests.at(i)->get_timeline_in_with_transition(); - playhead = refactor_frame_number(playhead, nests.at(i)->sequence->frame_rate, s->frame_rate); - } - - if (nests.last()->fbo != nullptr) { - nests.last()->fbo[0]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - nests.last()->fbo[0]->release(); - } - } - - int audio_track_count = 0; - - QVector current_clips; - - 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 (!(!nests.isEmpty() && !same_sign(c->track, nests.last()->track))) { - bool clip_is_active = false; - - if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_FOOTAGE) { - Footage* m = c->media->to_footage(); - if (!m->invalid && !(c->track >= 0 && !is_audio_device_set())) { - if (m->ready) { - const FootageStream* ms = m->get_stream_from_file_index(c->track < 0, c->media_stream); - 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 - if (!c->open) { - open_clip(c, !rendering); - } - clip_is_active = true; - if (c->track >= 0) audio_track_count++; - } else if (c->open) { - close_clip(c, false); - } - } else { - //qWarning() << "Media '" + m->name + "' was not ready, retrying..."; - texture_failed = true; - } - } - } else { - if (is_clip_active(c, playhead)) { - if (!c->open) open_clip(c, !rendering); - clip_is_active = true; - } else if (c->open) { - close_clip(c, false); - } - } - if (clip_is_active) { - bool added = false; - for (int j=0;jtrack < c->track) { - current_clips.insert(j, c); - added = true; - break; - } - } - if (!added) { - current_clips.append(c); - } - } - } - } - } - - int half_width = s->width/2; - int half_height = s->height/2; - if (rendering || !nests.isEmpty()) half_height = -half_height; // invert vertical + float dot_size = GIZMO_DOT_SIZE / width() * viewer->seq->width; + float target_size = GIZMO_TARGET_SIZE / width() * viewer->seq->width; glPushMatrix(); glLoadIdentity(); - glOrtho(-half_width, half_width, half_height, -half_height, -1, 10); - - 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"; - texture_failed = true; - } else { - if (c->track < 0) { - GLuint textureID = 0; - 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, playhead); - textureID = c->texture->textureId(); - break; - case MEDIA_TYPE_SEQUENCE: - textureID = -1; - break; - } - } - - if (textureID == 0 && c->media != nullptr) { - qWarning() << "Texture hasn't been created yet"; - texture_failed = true; - } else 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); - } - - // clear fbos - /*c->fbo[0]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[0]->release(); - c->fbo[1]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[1]->release();*/ - - bool fbo_switcher = false; - - glViewport(0, 0, video_width, video_height); - - GLuint composite_texture; - - if (c->media == nullptr) { - c->fbo[fbo_switcher]->bind(); - glClear(GL_COLOR_BUFFER_BIT); - c->fbo[fbo_switcher]->release(); - composite_texture = c->fbo[fbo_switcher]->texture(); - } else { - // for nested sequences - if (c->media->get_type()== MEDIA_TYPE_SEQUENCE) { - nests.append(c); - textureID = compose_sequence(nests, render_audio); - nests.removeLast(); - fbo_switcher = true; - } - - composite_texture = draw_clip(c->fbo[fbo_switcher], textureID, true); - } - - fbo_switcher = !fbo_switcher; - - // set up default coords - GLTextureCoords coords; - coords.grid_size = 1; - coords.vertexTopLeftX = coords.vertexBottomLeftX = -video_width/2; - coords.vertexTopLeftY = coords.vertexTopRightY = -video_height/2; - coords.vertexTopRightX = coords.vertexBottomRightX = video_width/2; - coords.vertexBottomLeftY = coords.vertexBottomRightY = video_height/2; - coords.vertexBottomLeftZ = coords.vertexBottomRightZ = coords.vertexTopLeftZ = coords.vertexTopRightZ = 1; - 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; - - // set up autoscale - 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); - float scale_multiplier = qMin(width_multiplier, height_multiplier); - glScalef(scale_multiplier, scale_multiplier, 1); - } - - // EFFECT CODE START - double timecode = get_timecode(c, playhead); - - Effect* first_gizmo_effect = nullptr; - Effect* selected_effect = nullptr; - - for (int j=0;jeffects.size();j++) { - Effect* e = c->effects.at(j); - process_effect(c, e, timecode, coords, composite_texture, fbo_switcher, TA_NO_TRANSITION); - - if (e->are_gizmos_enabled()) { - if (first_gizmo_effect == nullptr) first_gizmo_effect = e; - if (e->container->selected) selected_effect = e; - } - } - - if (!rendering) { - if (selected_effect != nullptr) { - gizmos = selected_effect; - } else if (panel_timeline->is_clip_selected(c, true)) { - gizmos = first_gizmo_effect; - } - } - - 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(c, c->get_opening_transition(), (double)transition_progress/(double)c->get_opening_transition()->get_length(), coords, composite_texture, fbo_switcher, TA_OPENING_TRANSITION); - } - } - - 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(c, c->get_closing_transition(), (double)transition_progress/(double)c->get_closing_transition()->get_length(), coords, composite_texture, fbo_switcher, TA_CLOSING_TRANSITION); - } - } - // EFFECT CODE END - - if (!nests.isEmpty()) { - nests.last()->fbo[0]->bind(); - glViewport(0, 0, s->width, s->height); - } else if (rendering) { - glViewport(0, 0, s->width, s->height); - } else { - int widget_width = width(); - int widget_height = height(); - - widget_width *= QApplication::desktop()->devicePixelRatio(); - widget_height *= QApplication::desktop()->devicePixelRatio(); - - glViewport(0, 0, widget_width, widget_height); - } - - glBindTexture(GL_TEXTURE_2D, composite_texture); - - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - 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 - glTexCoord2f(coords.textureTopRightX, coords.textureTopRightY); // top right - glVertex3f(coords.vertexTopRightX, coords.vertexTopRightY, z); // top right - glTexCoord2f(coords.textureBottomRightX, coords.textureBottomRightY); // bottom right - glVertex3f(coords.vertexBottomRightX, coords.vertexBottomRightY, z); // bottom right - glTexCoord2f(coords.textureBottomLeftX, coords.textureBottomLeftY); // bottom left - glVertex3f(coords.vertexBottomLeftX, coords.vertexBottomLeftY, z); // bottom left - } else { - float rows = coords.grid_size; - float cols = coords.grid_size; - - for (float k=0;kgizmo_draw(timecode, coords); // set correct gizmo coords - gizmos->gizmo_world_to_screen(); - - drawn_gizmos = true; - } - - if (!nests.isEmpty()) { - nests.last()->fbo[0]->release(); - if (default_fbo != nullptr) default_fbo->bind(); - } - - glPopMatrix(); - - /*GLfloat motion_blur_frac = (GLfloat) motion_blur_prog / (GLfloat) motion_blur_lim; - if (motion_blur_prog == 0) { - glAccum(GL_LOAD, motion_blur_frac); - } else { - glAccum(GL_ACCUM, motion_blur_frac); - } - motion_blur_prog++;*/ - } - } else { - if (render_audio || (config.enable_audio_scrubbing && audio_scrub)) { - if (c->media != nullptr && c->media->get_type() == MEDIA_TYPE_SEQUENCE) { - nests.append(c); - compose_sequence(nests, render_audio); - nests.removeLast(); - } else { - if (c->lock.tryLock()) { - // clip is not caching, start caching audio - cache_clip(c, playhead, c->audio_reset, !render_audio, nests); - c->lock.unlock(); - } - } - } - - // visually update all the keyframe values - if (c->sequence == viewer->seq) { // only if you can currently see them - double ts = (playhead - c->get_timeline_in_with_transition() + c->get_clip_in_with_transition())/s->frame_rate; - for (int i=0;ieffects.size();i++) { - Effect* e = c->effects.at(i); - for (int j=0;jrow_count();j++) { - EffectRow* r = e->row(j); - for (int k=0;kfieldCount();k++) { - r->field(k)->validate_keyframe_data(ts); - } - } - } - } + glOrtho(0, viewer->seq->width, 0, viewer->seq->height, -1, 10); + float gizmo_z = 0.0f; + for (int j=0;jgizmo_count();j++) { + EffectGizmo* g = gizmos->gizmo(j); + glColor4f(g->color.redF(), g->color.greenF(), g->color.blueF(), 1.0); + switch (g->get_type()) { + case GIZMO_TYPE_DOT: // draw dot + glBegin(GL_QUADS); + glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); + glEnd(); + break; + case GIZMO_TYPE_POLY: // draw lines + glBegin(GL_LINES); + for (int k=1;kget_point_count();k++) { + glVertex3f(g->screen_pos[k-1].x(), g->screen_pos[k-1].y(), gizmo_z); + glVertex3f(g->screen_pos[k].x(), g->screen_pos[k].y(), gizmo_z); } + glVertex3f(g->screen_pos[g->get_point_count()-1].x(), g->screen_pos[g->get_point_count()-1].y(), gizmo_z); + glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); + glEnd(); + break; + case GIZMO_TYPE_TARGET: // draw target + glBegin(GL_LINES); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); + + glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y(), gizmo_z); + glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y(), gizmo_z); + + glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()-target_size, gizmo_z); + glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()+target_size, gizmo_z); + glEnd(); + break; } } - - if (audio_track_count == 0) { - viewer->play_wake(); - } - glPopMatrix(); - if (!nests.isEmpty() && nests.last()->fbo != nullptr) { - // returns nested clip's texture - return nests.last()->fbo[0]->texture(); - } - - return 0; + glColor4f(color[0], color[1], color[2], color[3]); } void ViewerWidget::paintGL() { - drawn_gizmos = false; - force_quit = false; - if (viewer->seq != nullptr) { - gizmos = nullptr; + if (waveform) { + draw_waveform_func(); + } else { + renderer->mutex.lock(); - bool render_audio = (viewer->playing || rendering); - bool loop = false; - do { - loop = false; + makeCurrent(); - glClearColor(0, 0, 0, 1); - glMatrixMode(GL_MODELVIEW); - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glEnable(GL_DEPTH); + // clear to solid black - texture_failed = false; + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); - if (!rendering) retry_timer.stop(); + // set color multipler to straight white + glColor4f(1.0, 1.0, 1.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_TEXTURE_2D); - // compose video preview - glClearColor(0, 0, 0, 0); + // set screen coords to widget size + glLoadIdentity(); + glOrtho(0, 1, 0, 1, -1, 1); - QVector nests; + // draw texture from render thread - compose_sequence(nests, render_audio); + glBindTexture(GL_TEXTURE_2D, renderer->texColorBuffer); - if (waveform) { - QPainter p(this); - if (viewer->seq->using_workarea) { - int in_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_in) - waveform_scroll; - int out_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->workarea_out) - waveform_scroll; + glBegin(GL_QUADS); - p.fillRect(QRect(in_x, 0, out_x - in_x, height()), QColor(255, 255, 255, 64)); - p.setPen(Qt::white); - p.drawLine(in_x, 0, in_x, height()); - p.drawLine(out_x, 0, out_x, height()); - } - QRect wr = rect(); - wr.setX(wr.x() - waveform_scroll); + glVertex2f(0, 0); + glTexCoord2f(0, 0); + glVertex2f(0, 1); + glTexCoord2f(1, 0); + glVertex2f(1, 1); + glTexCoord2f(1, 1); + glVertex2f(1, 0); + glTexCoord2f(0, 1); - p.setPen(Qt::green); - draw_waveform(waveform_clip, waveform_ms, waveform_clip->timeline_out, &p, wr, waveform_scroll, width()+waveform_scroll, waveform_zoom); - p.setPen(Qt::red); - int playhead_x = getScreenPointFromFrame(waveform_zoom, viewer->seq->playhead) - waveform_scroll; - p.drawLine(playhead_x, 0, playhead_x, height()); - } + glEnd(); - if (force_quit) break; - if (texture_failed) { - if (rendering) { - qInfo() << "Texture failed - looping"; - loop = true; - } else if (!viewer->playing) { - retry_timer.start(); - } - } + glBindTexture(GL_TEXTURE_2D, 0); - if (config.show_title_safe_area && !rendering) { - drawTitleSafeArea(); - } + // draw title/action safe area + if (config.show_title_safe_area) { + draw_title_safe_area(); + } - if (gizmos != nullptr && drawn_gizmos) { - float color[4]; - glGetFloatv(GL_CURRENT_COLOR, color); + gizmos = renderer->gizmos; + if (gizmos != nullptr) { + draw_gizmos(); + } - float dot_size = GIZMO_DOT_SIZE / width() * viewer->seq->width; - float target_size = GIZMO_TARGET_SIZE / width() * viewer->seq->width; + glDisable(GL_TEXTURE_2D); - glPushMatrix(); - glLoadIdentity(); - glOrtho(0, viewer->seq->width, viewer->seq->height, 0, -1, 10); - float gizmo_z = 0.0f; - for (int j=0;jgizmo_count();j++) { - EffectGizmo* g = gizmos->gizmo(j); - glColor4f(g->color.redF(), g->color.greenF(), g->color.blueF(), 1.0); - switch (g->get_type()) { - case GIZMO_TYPE_DOT: // draw dot - glBegin(GL_QUADS); - glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()-dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-dot_size, g->screen_pos[0].y()+dot_size, gizmo_z); - glEnd(); - break; - case GIZMO_TYPE_POLY: // draw lines - glBegin(GL_LINES); - for (int k=1;kget_point_count();k++) { - glVertex3f(g->screen_pos[k-1].x(), g->screen_pos[k-1].y(), gizmo_z); - glVertex3f(g->screen_pos[k].x(), g->screen_pos[k].y(), gizmo_z); - } - glVertex3f(g->screen_pos[g->get_point_count()-1].x(), g->screen_pos[g->get_point_count()-1].y(), gizmo_z); - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y(), gizmo_z); - glEnd(); - break; - case GIZMO_TYPE_TARGET: // draw target - glBegin(GL_LINES); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); + if (window != nullptr && window->isVisible()) { + window->set_texture(renderer->texColorBuffer, double(viewer->seq->width)/double(viewer->seq->height), &renderer->mutex); + } - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); + renderer->mutex.unlock(); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y()+target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); - - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()+target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y()-target_size, gizmo_z); - - glVertex3f(g->screen_pos[0].x()-target_size, g->screen_pos[0].y(), gizmo_z); - glVertex3f(g->screen_pos[0].x()+target_size, g->screen_pos[0].y(), gizmo_z); - - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()-target_size, gizmo_z); - glVertex3f(g->screen_pos[0].x(), g->screen_pos[0].y()+target_size, gizmo_z); - glEnd(); - break; - } - } - glPopMatrix(); - - glColor4f(color[0], color[1], color[2], color[3]); - - drawn_gizmos = true; - } - - glDisable(GL_DEPTH); - glDisable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - } while (loop); + if (renderer->did_texture_fail()) { + doneCurrent(); + renderer->start_render(context(), viewer->seq); + } } } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 0450efd52..a4909ac5f 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -18,62 +18,64 @@ class Effect; class EffectGizmo; class ViewerContainer; struct GLTextureCoords; +class RenderThread; +class ViewerWindow; class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions { Q_OBJECT public: - ViewerWidget(QWidget *parent = 0); + ViewerWidget(QWidget *parent = 0); + ~ViewerWidget(); - void paintGL(); - void initializeGL(); + void delete_function(); + void close_window(); + + void paintGL(); + void initializeGL(); Viewer* viewer; - ViewerContainer* container; - - QOpenGLFramebufferObject* default_fbo; + ViewerContainer* container; bool waveform; Clip* waveform_clip; - const FootageStream* waveform_ms; - double waveform_zoom; - int waveform_scroll; + const FootageStream* waveform_ms; + double waveform_zoom; + int waveform_scroll; - bool force_quit; + void frame_update(); + RenderThread* get_renderer(); public slots: - void delete_function(); - void set_waveform_scroll(int s); + void set_waveform_scroll(int s); protected: - void paintEvent(QPaintEvent *e); -// void resizeGL(int w, int h); void mousePressEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); private: - QTimer retry_timer; - void drawTitleSafeArea(); + void draw_waveform_func(); + void draw_title_safe_area(); + void draw_gizmos(); + EffectGizmo* get_gizmo_from_mouse(int x, int y); + void move_gizmos(QMouseEvent *event, bool done); bool dragging; void seek_from_click(int x); - GLuint compose_sequence(QVector &nests, bool render_audio); - GLuint draw_clip(QOpenGLFramebufferObject *clip, GLuint texture, bool clear); - void process_effect(Clip* c, Effect* e, double timecode, GLTextureCoords& coords, GLuint& composite_texture, bool& fbo_switcher, int data); - Effect* gizmos; - int drag_start_x; - int drag_start_y; - int gizmo_x_mvmt; - int gizmo_y_mvmt; - EffectGizmo* selected_gizmo; - EffectGizmo* get_gizmo_from_mouse(int x, int y); - bool drawn_gizmos; - void move_gizmos(QMouseEvent *event, bool done); + Effect* gizmos; + int drag_start_x; + int drag_start_y; + int gizmo_x_mvmt; + int gizmo_y_mvmt; + EffectGizmo* selected_gizmo; + RenderThread* renderer; + ViewerWindow* window; private slots: + void context_destroy(); void retry(); void show_context_menu(); void save_frame(); - void show_fullscreen(); - - void set_fit_zoom(); - void set_custom_zoom(); - void set_menu_zoom(QAction *action); + void queue_repaint(); + void fullscreen_menu_action(QAction* action); + void set_fit_zoom(); + void set_custom_zoom(); + void set_menu_zoom(QAction *action); }; #endif // VIEWERWIDGET_H diff --git a/ui/viewerwindow.cpp b/ui/viewerwindow.cpp new file mode 100644 index 000000000..7982c0b1f --- /dev/null +++ b/ui/viewerwindow.cpp @@ -0,0 +1,68 @@ +#include "viewerwindow.h" + +#include + +ViewerWindow::ViewerWindow(QOpenGLContext *share) : + QOpenGLWindow(share), + texture(0), + mutex(nullptr) +{} + +void ViewerWindow::set_texture(GLuint t, double iar, QMutex* imutex) { + texture = t; + ar = iar; + mutex = imutex; + update(); +} + +void ViewerWindow::paintGL() { + if (texture > 0) { + if (mutex != nullptr) mutex->lock(); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, texture); + + glLoadIdentity(); + glOrtho(0, 1, 0, 1, -1, 1); + + glBegin(GL_QUADS); + + double top = 0; + double left = 0; + double right = 1; + double bottom = 1; + + double widget_ar = double(width()) / double(height()); + + if (widget_ar > ar) { + double width = 1.0 * ar / widget_ar; + left = (1.0 - width)*0.5; + right = left + width; + } else { + double height = 1.0 / ar * widget_ar; + top = (1.0 - height)*0.5; + bottom = top + height; + } + + glVertex2d(left, top); + glTexCoord2d(0, 0); + glVertex2d(left, bottom); + glTexCoord2d(1, 0); + glVertex2d(right, bottom); + glTexCoord2d(1, 1); + glVertex2d(right, top); + glTexCoord2d(0, 1); + + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + + glDisable(GL_TEXTURE_2D); + + if (mutex != nullptr) mutex->unlock(); + } +} diff --git a/ui/viewerwindow.h b/ui/viewerwindow.h new file mode 100644 index 000000000..fd4e9e972 --- /dev/null +++ b/ui/viewerwindow.h @@ -0,0 +1,20 @@ +#ifndef VIEWERWINDOW_H +#define VIEWERWINDOW_H + +#include + +class QMutex; + +class ViewerWindow : public QOpenGLWindow { + Q_OBJECT +public: + ViewerWindow(QOpenGLContext* share); + void set_texture(GLuint t, double iar, QMutex *imutex); +private: + void paintGL(); + GLuint texture; + double ar; + QMutex* mutex; +}; + +#endif // VIEWERWINDOW_H