From 658a6d379e963b406a7e600dd05e4178f8b4281f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 15 Jan 2019 17:48:36 +1100 Subject: [PATCH] move gl rendering to another thread --- debug.cpp | 2 +- main.cpp | 2 +- mainwindow.cpp | 126 +++++------ olive.pro | 8 +- panels/timeline.cpp | 78 +++---- panels/timeline.h | 5 +- playback/playback.cpp | 4 + playback/playback.h | 1 + ui/renderfunctions.cpp | 496 ++++++++++++++++++++++++++++++++++++++++ ui/renderfunctions.h | 20 ++ ui/renderthread.cpp | 111 +++++++++ ui/renderthread.h | 31 +++ ui/timelinewidget.cpp | 14 +- ui/viewerwidget.cpp | 499 +++++------------------------------------ ui/viewerwidget.h | 60 ++--- 15 files changed, 872 insertions(+), 585 deletions(-) create mode 100644 ui/renderfunctions.cpp create mode 100644 ui/renderfunctions.h create mode 100644 ui/renderthread.cpp create mode 100644 ui/renderthread.h 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/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 +452,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); } } @@ -1051,78 +1051,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 +1404,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 +1419,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 +1436,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 +1488,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 +1507,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/olive.pro b/olive.pro index e47ba1c0a..698daa18d 100644 --- a/olive.pro +++ b/olive.pro @@ -124,7 +124,9 @@ SOURCES += \ effects/internal/fillleftrighteffect.cpp \ effects/internal/voideffect.cpp \ dialogs/texteditdialog.cpp \ - dialogs/debugdialog.cpp + dialogs/debugdialog.cpp \ + ui/renderthread.cpp \ + ui/renderfunctions.cpp HEADERS += \ mainwindow.h \ @@ -216,7 +218,9 @@ HEADERS += \ effects/internal/fillleftrighteffect.h \ effects/internal/voideffect.h \ dialogs/texteditdialog.h \ - dialogs/debugdialog.h + dialogs/debugdialog.h \ + ui/renderthread.h \ + ui/renderfunctions.h FORMS += 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/playback/playback.cpp b/playback/playback.cpp index 8a30a9c6d..e27353687 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -36,6 +36,10 @@ extern "C" { 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); } diff --git a/playback/playback.h b/playback/playback.h index db4a80600..914ea42fa 100644 --- a/playback/playback.h +++ b/playback/playback.h @@ -12,6 +12,7 @@ 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); diff --git a/ui/renderfunctions.cpp b/ui/renderfunctions.cpp new file mode 100644 index 000000000..f2db68027 --- /dev/null +++ b/ui/renderfunctions.cpp @@ -0,0 +1,496 @@ +#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) + +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, + 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) { + GLint current_fbo = 0; + if (video) { + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤t_fbo); + } + + bool drawn_gizmos = false; + if (video && nests.isEmpty()) texture_failed = false; + + 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 (rendering || !nests.isEmpty()) half_height = -half_height; // invert vertical + + glPushMatrix(); + glLoadIdentity(); + glOrtho(-half_width, half_width, half_height, -half_height, -1, 10); + + for (int i=0;ifunctions()->GL_DEFAULT_BLEND; + glColor4f(1.0, 1.0, 1.0, 1.0); + + Clip* c = current_clips.at(i); + + if (c->media != 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(); + 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); + 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, 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 (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, 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, 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(); + + drawn_gizmos = true; + }*/ + + if (!nests.isEmpty()) { +// nests.last()->fbo[0]->release(); +// if (default_fbo != nullptr) default_fbo->bind(); + 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); + 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(); + } + + glPopMatrix(); + + if (!nests.isEmpty() && nests.last()->fbo != nullptr) { + // returns nested clip's texture + return nests.last()->fbo[0]->texture(); + } + + return 0; +} diff --git a/ui/renderfunctions.h b/ui/renderfunctions.h new file mode 100644 index 000000000..30dcdb853 --- /dev/null +++ b/ui/renderfunctions.h @@ -0,0 +1,20 @@ +#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); + +#endif // RENDERFUNCTIONS_H diff --git a/ui/renderthread.cpp b/ui/renderthread.cpp new file mode 100644 index 000000000..e9dc57711 --- /dev/null +++ b/ui/renderthread.cpp @@ -0,0 +1,111 @@ +#include "renderthread.h" + +#include +#include +#include +#include + +#include "ui/renderfunctions.h" + +RenderThread::RenderThread() : + share_ctx(nullptr), + ctx(nullptr), + frameBuffer(0), + texColorBuffer(0) +{ + surface.create(); +} + +RenderThread::~RenderThread() { + surface.destroy(); +} + +void RenderThread::run() { + mutex.lock(); + + bool running = true; + + while (running) { + waitCond.wait(&mutex); + + if (share_ctx != nullptr) { + if (ctx == nullptr) { + ctx = new QOpenGLContext(); + ctx->setFormat(share_ctx->format()); + ctx->setShareContext(share_ctx); + ctx->create(); + ctx->makeCurrent(&surface); + } + + if (ctx != nullptr) { + // gen fbo + if (frameBuffer == 0) { + ctx->functions()->glGenFramebuffers(1, &frameBuffer); + } + + // bind + ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, frameBuffer); + + // gen texture + if (texColorBuffer == 0) { + glGenTextures(1, &texColorBuffer); + glBindTexture(GL_TEXTURE_2D, texColorBuffer); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB, 1920, 1080, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr + ); + 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(); + + // release + ctx->functions()->glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + } + } + + if (ctx != nullptr) { + if (frameBuffer != 0) ctx->functions()->glDeleteFramebuffers(1, &frameBuffer); + ctx->doneCurrent(); + delete ctx; + } + + mutex.unlock(); +} + +void RenderThread::paint() { + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + glLoadIdentity(); + + glClearColor(0, 0, 0, 0); + glMatrixMode(GL_MODELVIEW); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glEnable(GL_DEPTH); + + Effect* gizmos; // does nothing yet + QVector nests; + + compose_sequence(nullptr, ctx, seq, nests, true, false, &gizmos); + + glDisable(GL_DEPTH); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); +} + +void RenderThread::start_render(QOpenGLContext *share, Sequence *s) { + share_ctx = share; + seq = s; + waitCond.wakeAll(); +} diff --git a/ui/renderthread.h b/ui/renderthread.h new file mode 100644 index 000000000..533eaf1d4 --- /dev/null +++ b/ui/renderthread.h @@ -0,0 +1,31 @@ +#ifndef RENDERTHREAD_H +#define RENDERTHREAD_H + +#include +#include +#include +#include +#include +#include + +struct Sequence; + +class RenderThread : public QThread { +public: + RenderThread(); + ~RenderThread(); + void run(); + QMutex mutex; + GLuint frameBuffer; + GLuint texColorBuffer; + void paint(); + void start_render(QOpenGLContext* share, Sequence* s); +private: + QWaitCondition waitCond; + QOffscreenSurface surface; + QOpenGLContext* share_ctx; + QOpenGLContext* ctx; + Sequence* seq; +}; + +#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/viewerwidget.cpp b/ui/viewerwidget.cpp index 6aca8f47b..7aba22da8 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -21,6 +21,8 @@ #include "ui/viewercontainer.h" #include "io/avtogl.h" #include "ui/timelinewidget.h" +#include "ui/renderfunctions.h" +#include "ui/renderthread.h" #include #include @@ -42,8 +44,6 @@ 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), @@ -51,6 +51,7 @@ ViewerWidget::ViewerWidget(QWidget *parent) : waveform_zoom(1.0), waveform_scroll(0), dragging(false), + gizmos(nullptr), selected_gizmo(nullptr) { setMouseTracking(true); @@ -66,6 +67,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setContextMenuPolicy(Qt::CustomContextMenu); connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu())); + + renderer = new RenderThread(); + renderer->start(QThread::HighPriority); } void ViewerWidget::delete_function() { @@ -387,442 +391,61 @@ void ViewerWidget::drawTitleSafeArea() { glEnd(); } -GLuint ViewerWidget::draw_clip(QOpenGLFramebufferObject* fbo, GLuint texture, bool clear) { - glPushMatrix(); - glLoadIdentity(); - glOrtho(0, 1, 0, 1, -1, 1); - - 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 - - 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); - } - } - } - } - } - } - } - - 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; -} - void ViewerWidget::paintGL() { - drawn_gizmos = false; + if (viewer->seq != nullptr) { + gizmos = nullptr; + drawn_gizmos = false; + force_quit = false; + + bool render_audio = (viewer->playing || rendering); + + retry_timer.stop(); + + // send context to other thread for drawing + doneCurrent(); + renderer->start_render(context(), viewer->seq); + + // render the audio + QVector nests; + compose_sequence(viewer, context(), viewer->seq, nests, false, render_audio, &gizmos); + + // try to draw the texture from the other thread if we got it + if (renderer->mutex.tryLock()) { + makeCurrent(); + + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + glEnable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, renderer->texColorBuffer); + + glLoadIdentity(); + glOrtho(0, 1, 1, 0, -1, 1); + + glBegin(GL_QUADS); + + glVertex2f(0, 0); + glTexCoord2f(0, 0); + glVertex2f(0, 1); + glTexCoord2f(1, 0); + glVertex2f(1, 1); + glTexCoord2f(1, 1); + glVertex2f(1, 0); + glTexCoord2f(0, 1); + + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + + glDisable(GL_TEXTURE_2D); + + renderer->mutex.unlock(); + } + } + + + /*drawn_gizmos = false; force_quit = false; if (viewer->seq != nullptr) { gizmos = nullptr; @@ -953,5 +576,5 @@ void ViewerWidget::paintGL() { glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); } while (loop); - } + }*/ } diff --git a/ui/viewerwidget.h b/ui/viewerwidget.h index 0450efd52..52c7da1ab 100644 --- a/ui/viewerwidget.h +++ b/ui/viewerwidget.h @@ -18,62 +18,64 @@ class Effect; class EffectGizmo; class ViewerContainer; struct GLTextureCoords; +class RenderThread; class ViewerWidget : public QOpenGLWidget, QOpenGLFunctions { Q_OBJECT public: - ViewerWidget(QWidget *parent = 0); + ViewerWidget(QWidget *parent = 0); - void paintGL(); - void initializeGL(); + void paintGL(); + void initializeGL(); Viewer* viewer; - ViewerContainer* container; + ViewerContainer* container; - QOpenGLFramebufferObject* default_fbo; + QOpenGLFramebufferObject* default_fbo; 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; + bool force_quit; public slots: - void delete_function(); - void set_waveform_scroll(int s); + void delete_function(); + void set_waveform_scroll(int s); protected: - void paintEvent(QPaintEvent *e); + 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 drawTitleSafeArea(); 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); +// GLuint compose_sequence(QVector &nests, bool video, 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); + RenderThread* renderer; private slots: void retry(); void show_context_menu(); void save_frame(); - void show_fullscreen(); + void show_fullscreen(); - void set_fit_zoom(); - void set_custom_zoom(); - void set_menu_zoom(QAction *action); + void set_fit_zoom(); + void set_custom_zoom(); + void set_menu_zoom(QAction *action); }; #endif // VIEWERWIDGET_H