diff --git a/dialogs/exportdialog.cpp b/dialogs/exportdialog.cpp index ac4ba0a73..4b36513f9 100644 --- a/dialogs/exportdialog.cpp +++ b/dialogs/exportdialog.cpp @@ -351,8 +351,8 @@ void ExportDialog::export_thread_finished() { prep_ui_for_render(false); // Move OpenGL context back to the sequence viewer - panel_sequence_viewer->viewer_widget->makeCurrent(); - panel_sequence_viewer->viewer_widget->initializeGL(); + panel_sequence_viewer->viewer_widget()->makeCurrent(); + panel_sequence_viewer->viewer_widget()->initializeGL(); // Update the application UI update_ui(false); @@ -573,9 +573,9 @@ void ExportDialog::StartExport() { panel_effect_controls->Clear(); // Close all currently open clips - close_active_clips(olive::ActiveSequence.get()); + olive::ActiveSequence->Close(); - olive::Global->set_rendering_state(true); + olive::Global->set_export_state(true); olive::Global->save_autorecovery_file(); diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 150336c8b..45832cf79 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -30,10 +30,8 @@ #include #include #include -#ifndef NO_OCIO #include namespace OCIO = OCIO_NAMESPACE::v1; -#endif #include "project/footage.h" #include "project/media.h" @@ -133,26 +131,26 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : row++; -#ifndef NO_OCIO - color_management = new QComboBox(this); + input_color_space = new QComboBox(this); OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + QString footage_colorspace = f->Colorspace(); + for (int i=0;igetNumColorSpaces();i++) { QString colorspace = config->getColorSpaceNameByIndex(i); - color_management->addItem(colorspace); + input_color_space->addItem(colorspace); - if (colorspace == f->colorspace) { - color_management->setCurrentIndex(i); + if (colorspace == footage_colorspace) { + input_color_space->setCurrentIndex(i); } } grid->addWidget(new QLabel(tr("Color Space:")), row, 0); - grid->addWidget(color_management, row, 1); + grid->addWidget(input_color_space, row, 1); row++; -#endif } @@ -221,9 +219,7 @@ void MediaPropertiesDialog::accept() { f->alpha_is_associated = premultiply_alpha_setting->isChecked(); } -#ifndef NO_OCIO - f->colorspace = color_management->currentText(); -#endif + f->SetColorspace(input_color_space->currentText()); // set name MediaRename* mr = new MediaRename(item, name_box->text()); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index f18dcee9a..8f9d7ebd5 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -83,7 +83,7 @@ private: */ QCheckBox* premultiply_alpha_setting; - QComboBox* color_management; + QComboBox* input_color_space; private slots: /** * @brief Overrided accept function for saving the properties back to the Media class diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index e6ecf7405..9bf70b3b9 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -168,7 +168,6 @@ void PreferencesDialog::delete_previews(PreviewDeleteTypes type) { } } -#ifndef NO_OCIO void PreferencesDialog::populate_ocio_menus(OCIO::ConstConfigRcPtr config) { // Get current display name (if the config is empty, get the current default display) @@ -240,7 +239,6 @@ void PreferencesDialog::update_ocio_config(const QString &s) } catch (OCIO::Exception& e) {} } } -#endif void PreferencesDialog::AddBoolPair(QCheckBox *ui, bool *value, bool restart_required) { @@ -279,6 +277,8 @@ void PreferencesDialog::accept() { bool reinit_audio = false; bool reload_language = false; bool reload_effects = false; + bool reset_ocio_shaders = false; + bool reset_render_threads = false; // Validate whether the specified CSS file exists if (!custom_css_fn->text().isEmpty() && !QFileInfo::exists(custom_css_fn->text())) { @@ -290,31 +290,45 @@ void PreferencesDialog::accept() { return; } - // Validate whether the OCIO config path exists - if (enable_color_management->isChecked() && !QFileInfo::exists(ocio_config_file->text())) { + // Validate whether the chosen OCIO configuration file + if (enable_color_management->isChecked()) { - QString msg_title = tr("Invalid OpenColorIO Configuration File"); - QString msg_body; + // Check whether the file exists + if (!QFileInfo::exists(ocio_config_file->text())) { + + QString msg_title = tr("Invalid OpenColorIO Configuration File"); + QString msg_body; + + if (ocio_config_file->text().isEmpty()) { + msg_body = tr("You must specify an OpenColorIO configuration file if color management is enabled."); + } else { + msg_body = tr("OpenColorIO configuration file '%1' does not exist.").arg(ocio_config_file->text()); + } + + QMessageBox::critical( + this, + msg_title, + msg_body + ); + return; + + } else if (olive::CurrentConfig.ocio_config_path != ocio_config_file->text()) { + + // Check whether OCIO can load it + try { + OCIO::Config::CreateFromFile(ocio_config_file->text().toUtf8()); + } catch (OCIO::Exception& e) { + QMessageBox::critical(this, + tr("OpenColorIO Config Error"), + tr("Failed to set OpenColorIO configuration: %1").arg(e.what()), + QMessageBox::Ok); + return; + } - if (ocio_config_file->text().isEmpty()) { - msg_body = tr("You must specify an OpenColorIO configuration file if color management is enabled."); - } else { - msg_body = tr("OpenColorIO configuration file '%1' does not exist.").arg(ocio_config_file->text()); } - - QMessageBox::critical( - this, - msg_title, - msg_body - ); - return; - } - - // Validate whether the effects panel should refresh itself - if (olive::CurrentConfig.effect_textbox_lines != effect_textbox_lines_field->value()) { - reload_effects = true; } + // Validate whether one of the bool options requires a restart bool bool_requires_restart = false; for (int i=0;ivalue() || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value() @@ -355,20 +369,8 @@ void PreferencesDialog::accept() { } - // Audio settings may require the audio device to be re-initiated. - if (olive::CurrentConfig.preferred_audio_output != audio_output_devices->currentData().toString() - || olive::CurrentConfig.preferred_audio_input != audio_input_devices->currentData().toString() - || olive::CurrentConfig.audio_rate != audio_sample_rate->currentData().toInt()) { - reinit_audio = true; - } - // see if the language file should be reloaded (not necessary if the app is restarting anyway) - if (!restart_after_saving - && olive::CurrentConfig.language_file != language_combobox->currentData().toString()) { - reload_language = true; - } - - // save settings from UI to backend + // Everything checks out, start saving settings from the UI to the backend olive::CurrentConfig.css_path = custom_css_fn->text(); olive::CurrentConfig.recording_mode = recordingComboBox->currentIndex() + 1; olive::CurrentConfig.img_seq_formats = imgSeqFormatEdit->text(); @@ -377,53 +379,66 @@ void PreferencesDialog::accept() { olive::CurrentConfig.previous_queue_size = previous_queue_spinbox->value(); olive::CurrentConfig.previous_queue_type = previous_queue_type->currentIndex(); + // Audio settings may require the audio device to be re-initiated. + if (olive::CurrentConfig.preferred_audio_output != audio_output_devices->currentData().toString() + || olive::CurrentConfig.preferred_audio_input != audio_input_devices->currentData().toString() + || olive::CurrentConfig.audio_rate != audio_sample_rate->currentData().toInt()) { + reinit_audio = true; + } olive::CurrentConfig.preferred_audio_output = audio_output_devices->currentData().toString(); olive::CurrentConfig.preferred_audio_input = audio_input_devices->currentData().toString(); olive::CurrentConfig.audio_rate = audio_sample_rate->currentData().toInt(); olive::CurrentConfig.effect_textbox_lines = effect_textbox_lines_field->value(); + + // see if the language file should be reloaded (not necessary if the app is restarting anyway) + if (!restart_after_saving + && olive::CurrentConfig.language_file != language_combobox->currentData().toString()) { + reload_language = true; + } olive::CurrentConfig.language_file = language_combobox->currentData().toString(); - olive::CurrentConfig.enable_color_management = enable_color_management->isChecked(); - -#ifndef NO_OCIO - if (olive::CurrentConfig.ocio_config_path != ocio_config_file->text()) { - try { - OCIO::SetCurrentConfig(OCIO::Config::CreateFromFile(ocio_config_file->text().toUtf8())); - - olive::CurrentConfig.ocio_config_path = ocio_config_file->text(); - } catch (OCIO::Exception& e) { - QMessageBox::critical(this, - tr("OpenColorIO Config Error"), - tr("Failed to set OpenColorIO configuration: %1").arg(e.what()), - QMessageBox::Ok); - } - + // Check whether OCIO settings will require a reset of the render threads + if (olive::CurrentConfig.playback_bit_depth != playback_bit_depth->currentIndex() + || olive::CurrentConfig.export_bit_depth != export_bit_depth->currentIndex()) { + reset_render_threads = true; } - + if (olive::CurrentConfig.ocio_config_path != ocio_config_file->text() + || olive::CurrentConfig.ocio_display != ocio_display->currentText() + || olive::CurrentConfig.ocio_view != ocio_view->currentText() + || olive::CurrentConfig.ocio_look != ocio_look->currentData().toString()) { + reset_ocio_shaders = true; + } + if (olive::CurrentConfig.ocio_config_path != ocio_config_file->text()) { + OCIO::SetCurrentConfig(OCIO::Config::CreateFromFile(ocio_config_file->text().toUtf8())); + olive::CurrentConfig.ocio_config_path = ocio_config_file->text(); + } + olive::CurrentConfig.enable_color_management = enable_color_management->isChecked(); + olive::CurrentConfig.playback_bit_depth = playback_bit_depth->currentIndex(); + olive::CurrentConfig.export_bit_depth = export_bit_depth->currentIndex(); olive::CurrentConfig.ocio_display = ocio_display->currentText(); olive::CurrentConfig.ocio_view = ocio_view->currentText(); // We use data here instead of text because there's a "(None)" option with an empty string olive::CurrentConfig.ocio_look = ocio_look->currentData().toString(); - olive::CurrentRuntimeConfig.ocio_config_date = QDateTime::currentMSecsSinceEpoch(); -#endif - + // Set default sequence options olive::CurrentConfig.default_sequence_width = default_sequence.width; olive::CurrentConfig.default_sequence_height = default_sequence.height; olive::CurrentConfig.default_sequence_framerate = default_sequence.frame_rate; olive::CurrentConfig.default_sequence_audio_frequency = default_sequence.audio_frequency; olive::CurrentConfig.default_sequence_audio_channel_layout = default_sequence.audio_layout; + // Set all bool options for (int i=0;iisChecked(); } + // Set new style olive::CurrentConfig.style = static_cast(ui_style->currentData().toInt()); - // Check if the thumbnail or waveform icon + // Check if the thumbnail or waveform icon fields have changed, we may need to recreate the previews if so if (olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value() || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { // we're changing the size of thumbnails and waveforms, so let's delete them and regenerate them next start @@ -461,29 +476,46 @@ void PreferencesDialog::accept() { key_shortcut_fields.at(i)->set_action_shortcut(); } - // Audio settings may require the audio device to be re-initiated. - if (reinit_audio) { - init_audio(); - } - - if (reload_effects) { - panel_effect_controls->Reload(); - } - - // reload language file if it changed - if (reload_language) { - olive::Global->load_translation_from_config(); - } - QDialog::accept(); if (restart_after_saving) { + // since we already ran can_close_project(), bypass checking again by running set_modified(false) olive::Global->set_modified(false); olive::MainWindow->close(); QProcess::startDetached(QApplication::applicationFilePath(), { olive::ActiveProjectFilename }); + } else { + + // Audio settings may require the audio device to be re-initiated. + if (reinit_audio) { + init_audio(); + } + + if (reload_effects) { + panel_effect_controls->Reload(); + } + + // reload language file if it changed + if (reload_language) { + olive::Global->load_translation_from_config(); + } + + if (reset_render_threads) { + if (panel_footage_viewer->seq != nullptr) { + panel_footage_viewer->seq->Close(); + } + panel_footage_viewer->viewer_widget()->get_renderer()->delete_ctx(); + if (panel_sequence_viewer->seq != nullptr) { + panel_sequence_viewer->seq->Close(); + } + panel_sequence_viewer->viewer_widget()->get_renderer()->delete_ctx(); + } else if (reset_ocio_shaders) { + panel_footage_viewer->viewer_widget()->get_renderer()->destroy_ocio(); + panel_sequence_viewer->viewer_widget()->get_renderer()->destroy_ocio(); + } + } } @@ -618,12 +650,10 @@ void PreferencesDialog::browse_ocio_config() } } -#ifndef NO_OCIO void PreferencesDialog::update_ocio_view_menu() { update_ocio_view_menu(OCIO::GetCurrentConfig()); } -#endif void PreferencesDialog::delete_all_previews() { if (QMessageBox::question(this, @@ -988,87 +1018,77 @@ void PreferencesDialog::setup_ui() { row = 0; -#ifdef NO_OCIO - QLabel* no_ocio_available_lbl = new QLabel(tr("Color management is unavailable because Olive was " - "compiled without OpenColorIO support.")); - no_ocio_available_lbl->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - color_management_layout->addWidget(no_ocio_available_lbl, row, 0, 1, 3); - row++; -#endif - // COLOR MANAGEMENT -> Enable Color Management enable_color_management = new QCheckBox(tr("Enable Color Management")); enable_color_management->setChecked(olive::CurrentConfig.enable_color_management); - color_management_layout->addWidget(enable_color_management, row, 0, 1, 3); + color_management_layout->addWidget(enable_color_management, row, 0); row++; + QGroupBox* opencolorio_groupbox = new QGroupBox(); + QGridLayout* opencolorio_groupbox_layout = new QGridLayout(opencolorio_groupbox); + // COLOR MANAGEMENT -> OpenColorIO Config File - color_management_layout->addWidget(new QLabel(tr("OpenColorIO Config File:")), row, 0); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("OpenColorIO Config File:")), 0, 0); ocio_config_file = new QLineEdit(); ocio_config_file->setText(olive::CurrentConfig.ocio_config_path); connect(ocio_config_file, SIGNAL(textChanged(const QString &)), this, SLOT(update_ocio_config(const QString&))); - color_management_layout->addWidget(ocio_config_file, row, 1); + opencolorio_groupbox_layout->addWidget(ocio_config_file, 0, 1, 1, 4); QPushButton* ocio_config_browse_btn = new QPushButton(tr("Browse")); connect(ocio_config_browse_btn, SIGNAL(clicked(bool)), this, SLOT(browse_ocio_config())); - color_management_layout->addWidget(ocio_config_browse_btn, row, 2); - - row++; + opencolorio_groupbox_layout->addWidget(ocio_config_browse_btn, 0, 5); // COLOR MANAGEMENT -> Display ocio_display = new QComboBox(); connect(ocio_display, SIGNAL(currentIndexChanged(int)), this, SLOT(update_ocio_view_menu())); - color_management_layout->addWidget(new QLabel("Display:"), row, 0); - color_management_layout->addWidget(ocio_display, row, 1); - - row++; + opencolorio_groupbox_layout->addWidget(new QLabel(tr("Display:")), 1, 0); + opencolorio_groupbox_layout->addWidget(ocio_display, 1, 1); // COLOR MANAGEMENT -> View ocio_view = new QComboBox(); - color_management_layout->addWidget(new QLabel("View:"), row, 0); - color_management_layout->addWidget(ocio_view, row, 1); - - row++; + opencolorio_groupbox_layout->addWidget(new QLabel(tr("View:")), 1, 2); + opencolorio_groupbox_layout->addWidget(ocio_view, 1, 3); // COLOR MANAGEMENT -> Look ocio_look = new QComboBox(); - color_management_layout->addWidget(new QLabel("Look:"), row, 0); - color_management_layout->addWidget(ocio_look, row, 1); + opencolorio_groupbox_layout->addWidget(new QLabel(tr("Look:")), 1, 4); + opencolorio_groupbox_layout->addWidget(ocio_look, 1, 5); + + color_management_layout->addWidget(opencolorio_groupbox, row, 0); row++; - // COLOR MANAGEMENT -> Playback Bit Depth - QComboBox* playback_bit_depth = new QComboBox(); + // COLOR MANAGEMENT -> Bit Depth + QGroupBox* bit_depth_groupbox = new QGroupBox(tr("Bit Depth")); + QGridLayout* bit_depth_groupbox_layout = new QGridLayout(bit_depth_groupbox); + + // COLOR MANAGEMENT -> Bit Depth -> Playback + playback_bit_depth = new QComboBox(); for (int i=0;iaddItem(olive::rendering::bit_depths.at(i).name, i); } - color_management_layout->addWidget(new QLabel("Playback Bit Depth:"), row, 0); - color_management_layout->addWidget(playback_bit_depth, row, 1); + playback_bit_depth->setCurrentIndex(olive::CurrentConfig.playback_bit_depth); + bit_depth_groupbox_layout->addWidget(new QLabel(tr("Playback (Offline):")), 0, 0); + bit_depth_groupbox_layout->addWidget(playback_bit_depth, 0, 1); - row++; - - // COLOR MANAGEMENT -> Rendering Bit Depth - QComboBox* rendering_bit_depth = new QComboBox(); + // COLOR MANAGEMENT -> Bit Depth -> Export + export_bit_depth = new QComboBox(); for (int i=0;iaddItem(olive::rendering::bit_depths.at(i).name, i); + export_bit_depth->addItem(olive::rendering::bit_depths.at(i).name, i); } - color_management_layout->addWidget(new QLabel("Rendering Bit Depth:"), row, 0); - color_management_layout->addWidget(rendering_bit_depth, row, 1); + export_bit_depth->setCurrentIndex(olive::CurrentConfig.export_bit_depth); + bit_depth_groupbox_layout->addWidget(new QLabel(tr("Export (Online):")), 0, 2); + bit_depth_groupbox_layout->addWidget(export_bit_depth, 0, 3); - row++; + color_management_layout->addWidget(bit_depth_groupbox, row, 0); + + + + //row++; -#ifdef NO_OCIO - enable_color_management->setEnabled(false); - ocio_config_file->setEnabled(false); - ocio_config_browse_btn->setEnabled(false); - ocio_display->setEnabled(false); - ocio_view->setEnabled(false); - ocio_look->setEnabled(false); -#else populate_ocio_menus(OCIO::GetCurrentConfig()); -#endif tabWidget->addTab(color_management_tab, tr("Color Management")); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index 157cf01d8..a7cb00c9a 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -33,10 +33,8 @@ #include #include #include -#ifndef NO_OCIO #include namespace OCIO = OCIO_NAMESPACE::v1; -#endif #include "timeline/sequence.h" @@ -125,11 +123,9 @@ private slots: void browse_ocio_config(); // OCIO function -#ifndef NO_OCIO void update_ocio_view_menu(); void update_ocio_view_menu(OCIO::ConstConfigRcPtr config); void update_ocio_config(const QString&); -#endif /** * @brief Shows a NewSequenceDialog attached to default_sequence @@ -188,9 +184,7 @@ private: */ void delete_previews(PreviewDeleteTypes type); -#ifndef NO_OCIO void populate_ocio_menus(OCIO::ConstConfigRcPtr config); -#endif /** * @brief UI widget for editing the CSS filename @@ -273,6 +267,8 @@ private: QComboBox* ocio_display; QComboBox* ocio_view; QComboBox* ocio_look; + QComboBox* playback_bit_depth; + QComboBox* export_bit_depth; /** * @brief UI widget for selecting the current UI style diff --git a/effects/effect.cpp b/effects/effect.cpp index 5ee89d7dc..38b9f2344 100644 --- a/effects/effect.cpp +++ b/effects/effect.cpp @@ -424,7 +424,7 @@ void Effect::move_up() { command->to = command->from - 1; olive::UndoStack.push(command); panel_effect_controls->Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } void Effect::move_down() { @@ -439,7 +439,7 @@ void Effect::move_down() { command->to = command->from + 1; olive::UndoStack.push(command); panel_effect_controls->Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } void Effect::save_to_file() { diff --git a/effects/effectfield.cpp b/effects/effectfield.cpp index f06e94533..61d6a0431 100644 --- a/effects/effectfield.cpp +++ b/effects/effectfield.cpp @@ -25,6 +25,7 @@ #include "rendering/renderfunctions.h" #include "global/config.h" +#include "global/timing.h" #include "effects/effectrow.h" #include "effects/effect.h" #include "undo/undo.h" diff --git a/effects/internal/timecodeeffect.cpp b/effects/internal/timecodeeffect.cpp index 519cdefcb..84276fc34 100644 --- a/effects/internal/timecodeeffect.cpp +++ b/effects/internal/timecodeeffect.cpp @@ -37,7 +37,7 @@ #include "ui/collapsiblewidget.h" #include "timeline/clip.h" #include "timeline/sequence.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "ui/comboboxex.h" #include "ui/colorbutton.h" #include "global/config.h" diff --git a/global/config.cpp b/global/config.cpp index 295fdac05..e28ccbe1d 100644 --- a/global/config.cpp +++ b/global/config.cpp @@ -79,7 +79,9 @@ Config::Config() default_sequence_height(1080), default_sequence_framerate(29.97), default_sequence_audio_frequency(48000), - default_sequence_audio_channel_layout(3) + default_sequence_audio_channel_layout(3), + playback_bit_depth(olive::rendering::PIX_FMT_RGBA16F), + export_bit_depth(olive::rendering::PIX_FMT_RGBA32F) {} void Config::load(QString path) { @@ -246,6 +248,12 @@ void Config::load(QString path) { } else if (stream.name() == "DefaultSequenceAudioLayout") { stream.readNext(); default_sequence_audio_channel_layout = stream.text().toInt(); + } else if (stream.name() == "PlaybackBitDepth") { + stream.readNext(); + playback_bit_depth = stream.text().toInt(); + } else if (stream.name() == "ExportBitDepth") { + stream.readNext(); + export_bit_depth = stream.text().toInt(); } } } @@ -322,6 +330,8 @@ void Config::save(QString path) { stream.writeTextElement("DefaultSequenceFrameRate", QString::number(default_sequence_framerate)); stream.writeTextElement("DefaultSequenceAudioFrequency", QString::number(default_sequence_audio_frequency)); stream.writeTextElement("DefaultSequenceAudioLayout", QString::number(default_sequence_audio_channel_layout)); + stream.writeTextElement("PlaybackBitDepth", QString::number(playback_bit_depth)); + stream.writeTextElement("ExportBitDepth", QString::number(export_bit_depth)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc @@ -329,6 +339,5 @@ void Config::save(QString path) { } RuntimeConfig::RuntimeConfig() : - shaders_are_enabled(true), - ocio_config_date(QDateTime::currentMSecsSinceEpoch()) + shaders_are_enabled(true) {} diff --git a/global/config.h b/global/config.h index 3d192cdad..2f9ebd9e6 100644 --- a/global/config.h +++ b/global/config.h @@ -593,6 +593,16 @@ struct Config { */ int default_sequence_audio_channel_layout; + /** + * @brief Playback bit depth (an index of olive::rendering::bit_depths) + */ + int playback_bit_depth; + + /** + * @brief Export bit depth (an index of olive::rendering::bit_depths) + */ + int export_bit_depth; + /** * @brief Load config from file * @@ -644,13 +654,6 @@ struct RuntimeConfig { */ QString external_translation_file; - /** - * @brief OpenColorIO Configuration Time - * - * A crude but quick way of determining whether the OCIO config has changed and if the rendering threads need to - * re-create their OCIO shaders. Not intended to be saved - could be moved - */ - qint64 ocio_config_date; }; namespace olive { diff --git a/global/global.cpp b/global/global.cpp index f91c6b91b..a32015b26 100644 --- a/global/global.cpp +++ b/global/global.cpp @@ -52,7 +52,8 @@ QString olive::ActiveProjectFilename; QString olive::AppName; OliveGlobal::OliveGlobal() : - changed_since_last_autorecovery(false) + changed_since_last_autorecovery(false), + rendering_(false) { // sets current app name QString version_id; @@ -108,8 +109,13 @@ void OliveGlobal::check_for_autorecovery_file() { } } -void OliveGlobal::set_rendering_state(bool rendering) { - audio_rendering = rendering; +bool OliveGlobal::is_exporting() +{ + return rendering_; +} + +void OliveGlobal::set_export_state(bool rendering) { + rendering_ = rendering; if (rendering) { autorecovery_timer.stop(); } else { diff --git a/global/global.h b/global/global.h index ab5f342a4..050a28bc8 100644 --- a/global/global.h +++ b/global/global.h @@ -74,6 +74,16 @@ public: */ void check_for_autorecovery_file(); + /** + * @brief Get whether the project is currently being rendered or not. Useful for determining whether to treat the + * render as online or offline. + * + * @return + * + * TRUE if the project is being exported, FALSE if not. + */ + bool is_exporting(); + /** * @brief Set the application state depending on if the user is exporting a video * @@ -90,7 +100,7 @@ public: * * **TRUE** if Olive is about to export a video. **FALSE** if Olive has finished exporting. */ - void set_rendering_state(bool rendering); + void set_export_state(bool rendering); /** * @brief Set the application's "modified" state @@ -423,6 +433,11 @@ private: */ bool changed_since_last_autorecovery; + /** + * @brief Internal variable for rendering state (set by set_rendering_state() and accessed by is_rendering() ). + */ + bool rendering_; + private slots: }; diff --git a/global/timing.cpp b/global/timing.cpp new file mode 100644 index 000000000..b14315734 --- /dev/null +++ b/global/timing.cpp @@ -0,0 +1,169 @@ +#include "timing.h" + +#include "timeline/sequence.h" +#include "timeline/clip.h" +#include "global/config.h" + +double get_timecode(Clip* c, long playhead) { + return double(playhead_to_clip_frame(c, playhead))/c->sequence->frame_rate; +} + +long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { + return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); +} + +long playhead_to_clip_frame(Clip* c, long playhead) { + return (qMax(0L, playhead - c->timeline_in(true)) + c->clip_in(true)); +} + +double playhead_to_clip_seconds(Clip* c, long playhead) { + // returns time in seconds + long clip_frame = playhead_to_clip_frame(c, playhead); + + if (c->reversed()) { + clip_frame = c->media_length() - clip_frame - 1; + } + + double secs = (double(clip_frame)/c->sequence->frame_rate)*c->speed().value; + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { + secs *= c->media()->to_footage()->speed; + } + + return secs; +} + +int64_t seconds_to_timestamp(Clip *c, double seconds) { + return qRound64(seconds * av_q2d(av_inv_q(c->time_base()))); +} + +int64_t playhead_to_timestamp(Clip* c, long playhead) { + return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); +} + +bool frame_rate_is_droppable(double rate) { + return (qFuzzyCompare(rate, 23.976) + || qFuzzyCompare(rate, 29.97) + || qFuzzyCompare(rate, 59.94)); +} + +long timecode_to_frame(const QString& s, int view, double frame_rate) { + QList list = s.split(QRegExp("[:;]")); + + if (view == olive::kTimecodeFrames || (list.size() == 1 && view != olive::kTimecodeMilliseconds)) { + return s.toLong(); + } + + int frRound = qRound(frame_rate); + int hours, minutes, seconds, frames; + + if (view == olive::kTimecodeMilliseconds) { + long milliseconds = s.toLong(); + + hours = milliseconds/3600000; + milliseconds -= (hours*3600000); + minutes = milliseconds/60000; + milliseconds -= (minutes*60000); + seconds = milliseconds/1000; + milliseconds -= (seconds*1000); + frames = qRound64((milliseconds*0.001)*frame_rate); + + seconds = qRound64(seconds * frame_rate); + minutes = qRound64(minutes * frame_rate * 60); + hours = qRound64(hours * frame_rate * 3600); + } else { + hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; + minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; + seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; + frames = (list.size() > 3) ? list.at(3).toInt() : 0; + } + + int f = (frames + seconds + minutes + hours); + + if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { + // return drop + int d; + int m; + + int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + d = f / framesPer10Minutes; + f -= dropFrames*9*d; + + m = f % framesPer10Minutes; + + if (m > dropFrames) { + f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); + } + } + + // return non-drop + return f; +} + +QString frame_to_timecode(long f, int view, double frame_rate) { + if (view == olive::kTimecodeFrames) { + return QString::number(f); + } + + // return timecode + int hours = 0; + int mins = 0; + int secs = 0; + int frames = 0; + QString token = ":"; + + if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { + //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE + //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team + //Given an int called framenumber and a double called framerate + //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. + + int d; + int m; + + int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate + int framesPerHour = qRound(frame_rate*60*60); //Number of frqRound64ames in an hour + int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours + int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes + int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames + + //If framenumber is greater than 24 hrs, next operation will rollover clock + f = f % framesPer24Hours; // % is the modulus operator, which returns a remainder. a % b = the remainder of a/b + + d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) + m = f % framesPer10Minutes; + + //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. + if (m > dropFrames) { + f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); + } else { + f = f + dropFrames*9*d; + } + + int frRound = qRound(frame_rate); + frames = f % frRound; + secs = (f / frRound) % 60; + mins = ((f / frRound) / 60) % 60; + hours = (((f / frRound) / 60) / 60); + + token = ";"; + } else { + // non-drop timecode + + int int_fps = qRound(frame_rate); + hours = f / (3600 * int_fps); + mins = f / (60*int_fps) % 60; + secs = f/int_fps % 60; + frames = f%int_fps; + } + if (view == olive::kTimecodeMilliseconds) { + return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); + } + return QString(QString::number(hours).rightJustified(2, '0') + + ":" + QString::number(mins).rightJustified(2, '0') + + ":" + QString::number(secs).rightJustified(2, '0') + + token + QString::number(frames).rightJustified(2, '0') + ); +} diff --git a/global/timing.h b/global/timing.h new file mode 100644 index 000000000..1744b8c92 --- /dev/null +++ b/global/timing.h @@ -0,0 +1,137 @@ +#ifndef TIMING_H +#define TIMING_H + +#include +#include + +class Clip; + +/** + * @brief Get timecode + * + * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start + * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; + * + * @param c + * + * Clip to get the timecode of + * + * @param playhead + * + * Sequence playhead to convert to a clip/media timecode + * + * @return + * + * Timecode in seconds + */ +double get_timecode(Clip *c, long playhead); + +/** + * @brief Rescale a frame number between two frame rates + * + * Converts a frame number from one frame rate to its equivalent in another frame rate + * + * @param framenumber + * + * The frame number to convert + * + * @param source_frame_rate + * + * Frame rate that the frame number is currently in + * + * @param target_frame_rate + * + * Frame rate to convert to + * + * @return + * + * Rescaled frame number + */ +long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); + +/** + * @brief Convert playhead frame number to a clip frame number + * + * Converts a Timeline playhead to a the current clip's frame. Equivalent to + * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. + * + * @param c + * + * The clip to get the current frame number of + * + * @param playhead + * + * The current Timeline frame number + * + * @return + * + * The curren frame number of the clip at `playhead` + */ +long playhead_to_clip_frame(Clip* c, long playhead); + +/** + * @brief Converts the playhead to clip seconds + * + * Get the current timecode at the playhead in terms of clip seconds. + * + * FIXME: Possible duplicate of get_timecode()? Will need to research this more. + * + * @param c + * + * Clip to return clip seconds of. + * + * @param playhead + * + * Current Timeline playhead to convert to clip seconds + * + * @return + * + * Clip time in seconds + */ +double playhead_to_clip_seconds(Clip *c, long playhead); + +/** + * @brief Convert seconds to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param seconds + * + * Clip time in seconds + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ +int64_t seconds_to_timestamp(Clip* c, double seconds); + +/** + * @brief Convert Timeline playhead to FFmpeg timestamp + * + * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base + * units. + * + * @param c + * + * Clip to get timestamp of + * + * @param playhead + * + * Timeline playhead to convert to a timestamp + * + * @return + * + * An FFmpeg-compatible timestamp in AVStream->time_base units. + */ +int64_t playhead_to_timestamp(Clip *c, long playhead); + +bool frame_rate_is_droppable(double rate); +long timecode_to_frame(const QString& s, int view, double frame_rate); +QString frame_to_timecode(long f, int view, double frame_rate); + +#endif // TIMING_H diff --git a/olive.pro b/olive.pro index 299cf59b4..58b62e807 100644 --- a/olive.pro +++ b/olive.pro @@ -177,7 +177,8 @@ SOURCES += \ timeline/mediaimportdata.cpp \ dialogs/autocutsilencedialog.cpp \ ui/columnedgridlayout.cpp \ - rendering/shadergenerators.cpp + rendering/shadergenerators.cpp \ + global/timing.cpp HEADERS += \ ui/mainwindow.h \ @@ -308,7 +309,8 @@ HEADERS += \ timeline/mediaimportdata.h \ dialogs/autocutsilencedialog.h \ ui/columnedgridlayout.h \ - rendering/shadergenerators.h + rendering/shadergenerators.h \ + global/timing.h FORMS += @@ -327,27 +329,18 @@ TRANSLATIONS += \ win32 { RC_FILE = packaging/windows/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -luser32 - !contains(DEFINES, NO_OCIO) { - LIBS += -lOpenColorIO - } + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lOpenColorIO -lopengl32 -luser32 } mac { - LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -framework CoreFoundation + LIBS += -L/usr/local/lib -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lOpenColorIO -framework CoreFoundation ICON = packaging/macos/olive.icns INCLUDEPATH = /usr/local/include - !contains(DEFINES, NO_OCIO) { - LIBS += -lOpenColorIO - } } unix:!mac { CONFIG += link_pkgconfig - PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample - !contains(DEFINES, NO_OCIO) { - LIBS += -lOpenColorIO - } + PKGCONFIG += libavutil libavformat libavcodec libavfilter libswscale libswresample OpenColorIO } RESOURCES += \ diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index d83e6639d..454ffd884 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -127,7 +127,7 @@ void EffectControls::menu_select(QAction* q) { update_ui(true); } else { Reload(); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } } @@ -305,7 +305,7 @@ void EffectControls::deselect_all_effects(QWidget* sender) { } if (panel_sequence_viewer != nullptr) { - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget()->update(); } } diff --git a/panels/project.h b/panels/project.h index 44914600c..f8ea096f2 100644 --- a/panels/project.h +++ b/panels/project.h @@ -40,9 +40,6 @@ #include "ui/sourcetable.h" -#define LOAD_TYPE_VERSION 69 -#define LOAD_TYPE_URL 70 - extern QString autorecovery_filename; extern QStringList recent_projects; diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 30ae02da1..66469146b 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -53,6 +53,7 @@ #include "ui/mainwindow.h" #include "undo/undostack.h" #include "global/debug.h" +#include "global/timing.h" #include "ui/menu.h" int olive::timeline::kTrackDefaultHeight = 40; diff --git a/panels/viewer.cpp b/panels/viewer.cpp index 7b6bc1710..1bde2749a 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -43,6 +43,7 @@ extern "C" { #include "timeline/clip.h" #include "panels/panels.h" #include "global/config.h" +#include "global/timing.h" #include "project/footage.h" #include "project/media.h" #include "undo/undo.h" @@ -77,8 +78,8 @@ Viewer::Viewer(QWidget *parent) : headers->snapping = false; headers->show_text(false); viewer_container->viewer = this; - viewer_widget = viewer_container->child; - viewer_widget->viewer = this; + viewer_widget_ = viewer_container->child; + viewer_widget_->viewer = this; set_media(nullptr); current_timecode_slider->setEnabled(false); @@ -93,15 +94,13 @@ Viewer::Viewer(QWidget *parent) : connect(&playback_updater, SIGNAL(timeout()), this, SLOT(timer_update())); connect(&recording_flasher, SIGNAL(timeout()), this, SLOT(recording_flasher_update())); connect(horizontal_bar, SIGNAL(valueChanged(int)), headers, SLOT(set_scroll(int))); - connect(horizontal_bar, SIGNAL(valueChanged(int)), viewer_widget, SLOT(set_waveform_scroll(int))); + connect(horizontal_bar, SIGNAL(valueChanged(int)), viewer_widget_, SLOT(set_waveform_scroll(int))); connect(horizontal_bar, SIGNAL(resize_move(double)), this, SLOT(resize_move(double))); update_playhead_timecode(0); update_end_timecode(); } -Viewer::~Viewer() {} - void Viewer::Retranslate() { /// Viewer panels are retranslated through the MainWindow to differentiate Media and Sequence Viewers // update_window_title(); @@ -109,7 +108,7 @@ void Viewer::Retranslate() { bool Viewer::is_focused() { return headers->hasFocus() - || viewer_widget->hasFocus() + || viewer_widget_->hasFocus() || go_to_start_button->hasFocus() || prev_frame_button->hasFocus() || play_button->hasFocus() @@ -146,132 +145,6 @@ void Viewer::reset_all_audio() { clear_audio_ibuffer(); } -long timecode_to_frame(const QString& s, int view, double frame_rate) { - QList list = s.split(QRegExp("[:;]")); - - if (view == olive::kTimecodeFrames || (list.size() == 1 && view != olive::kTimecodeMilliseconds)) { - return s.toLong(); - } - - int frRound = qRound(frame_rate); - int hours, minutes, seconds, frames; - - if (view == olive::kTimecodeMilliseconds) { - long milliseconds = s.toLong(); - - hours = milliseconds/3600000; - milliseconds -= (hours*3600000); - minutes = milliseconds/60000; - milliseconds -= (minutes*60000); - seconds = milliseconds/1000; - milliseconds -= (seconds*1000); - frames = qRound64((milliseconds*0.001)*frame_rate); - - seconds = qRound64(seconds * frame_rate); - minutes = qRound64(minutes * frame_rate * 60); - hours = qRound64(hours * frame_rate * 3600); - } else { - hours = ((list.size() > 0) ? list.at(0).toInt() : 0) * frRound * 3600; - minutes = ((list.size() > 1) ? list.at(1).toInt() : 0) * frRound * 60; - seconds = ((list.size() > 2) ? list.at(2).toInt() : 0) * frRound; - frames = (list.size() > 3) ? list.at(3).toInt() : 0; - } - - int f = (frames + seconds + minutes + hours); - - if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { - // return drop - int d; - int m; - - int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - d = f / framesPer10Minutes; - f -= dropFrames*9*d; - - m = f % framesPer10Minutes; - - if (m > dropFrames) { - f -= (dropFrames * ((m - dropFrames) / framesPerMinute)); - } - } - - // return non-drop - return f; -} - -QString frame_to_timecode(long f, int view, double frame_rate) { - if (view == olive::kTimecodeFrames) { - return QString::number(f); - } - - // return timecode - int hours = 0; - int mins = 0; - int secs = 0; - int frames = 0; - QString token = ":"; - - if ((view == olive::kTimecodeDrop || view == olive::kTimecodeMilliseconds) && frame_rate_is_droppable(frame_rate)) { - //CONVERT A FRAME NUMBER TO DROP FRAME TIMECODE - //Code by David Heidelberger, adapted from Andrew Duncan, further adapted for Olive by Olive Team - //Given an int called framenumber and a double called framerate - //Framerate should be 29.97, 59.94, or 23.976, otherwise the calculations will be off. - - int d; - int m; - - int dropFrames = qRound(frame_rate * .066666); //Number of frames to drop on the minute marks is the nearest integer to 6% of the framerate - int framesPerHour = qRound(frame_rate*60*60); //Number of frqRound64ames in an hour - int framesPer24Hours = framesPerHour*24; //Number of frames in a day - timecode rolls over after 24 hours - int framesPer10Minutes = qRound(frame_rate * 60 * 10); //Number of frames per ten minutes - int framesPerMinute = (qRound(frame_rate)*60)- dropFrames; //Number of frames per minute is the round of the framerate * 60 minus the number of dropped frames - - //If framenumber is greater than 24 hrs, next operation will rollover clock - f = f % framesPer24Hours; // % is the modulus operator, which returns a remainder. a % b = the remainder of a/b - - d = f / framesPer10Minutes; // \ means integer division, which is a/b without a remainder. Some languages you could use floor(a/b) - m = f % framesPer10Minutes; - - //In the original post, the next line read m>1, which only worked for 29.97. Jean-Baptiste Mardelle correctly pointed out that m should be compared to dropFrames. - if (m > dropFrames) { - f = f + (dropFrames*9*d) + dropFrames * ((m - dropFrames) / framesPerMinute); - } else { - f = f + dropFrames*9*d; - } - - int frRound = qRound(frame_rate); - frames = f % frRound; - secs = (f / frRound) % 60; - mins = ((f / frRound) / 60) % 60; - hours = (((f / frRound) / 60) / 60); - - token = ";"; - } else { - // non-drop timecode - - int int_fps = qRound(frame_rate); - hours = f / (3600 * int_fps); - mins = f / (60*int_fps) % 60; - secs = f/int_fps % 60; - frames = f%int_fps; - } - if (view == olive::kTimecodeMilliseconds) { - return QString::number((hours*3600000)+(mins*60000)+(secs*1000)+qCeil(frames*1000/frame_rate)); - } - return QString(QString::number(hours).rightJustified(2, '0') + - ":" + QString::number(mins).rightJustified(2, '0') + - ":" + QString::number(secs).rightJustified(2, '0') + - token + QString::number(frames).rightJustified(2, '0') - ); -} - -bool frame_rate_is_droppable(double rate) { - return (qFuzzyCompare(rate, 23.976) || qFuzzyCompare(rate, 29.97) || qFuzzyCompare(rate, 59.94)); -} - void Viewer::seek(long p) { pause(); if (main_sequence) { @@ -499,7 +372,7 @@ void Viewer::update_header_zoom() { minimum_zoom = (sequenceEndFrame > 0) ? ((double) headers->width() / (double) sequenceEndFrame) : 1; headers->update_zoom(qMax(headers->get_zoom(), minimum_zoom)); set_sb_max(); - viewer_widget->waveform_zoom = headers->get_zoom(); + viewer_widget_->waveform_zoom = headers->get_zoom(); } else { headers->update(); } @@ -519,6 +392,11 @@ int Viewer::get_playback_speed() { return playback_speed; } +ViewerWidget *Viewer::viewer_widget() +{ + return viewer_widget_; +} + void Viewer::set_marker() { set_marker_internal(seq.get()); } @@ -527,13 +405,13 @@ void Viewer::resizeEvent(QResizeEvent *e) { QDockWidget::resizeEvent(e); if (seq != nullptr) { set_sb_max(); - viewer_widget->update(); + viewer_widget_->update(); } } void Viewer::update_viewer() { update_header_zoom(); - viewer_widget->frame_update(); + viewer_widget_->frame_update(); if (seq != nullptr) { update_playhead_timecode(seq->playhead); } @@ -616,9 +494,9 @@ void Viewer::update_window_title() { void Viewer::set_zoom_value(double d) { headers->update_zoom(d); - if (viewer_widget->waveform) { - viewer_widget->waveform_zoom = d; - viewer_widget->update(); + if (viewer_widget_->waveform) { + viewer_widget_->waveform_zoom = d; + viewer_widget_->update(); } if (seq != nullptr) { set_sb_max(); @@ -843,10 +721,10 @@ void Viewer::set_media(Media* m) { new_sequence->clips.append(c); if (footage->video_tracks.size() == 0) { - viewer_widget->waveform = true; - viewer_widget->waveform_clip = c; - viewer_widget->waveform_ms = &audio_stream; - viewer_widget->frame_update(); + viewer_widget_->waveform = true; + viewer_widget_->waveform_clip = c; + viewer_widget_->waveform_ms = &audio_stream; + viewer_widget_->frame_update(); } } else { new_sequence->audio_frequency = olive::CurrentConfig.default_sequence_audio_frequency; @@ -926,7 +804,7 @@ void Viewer::drag_audio_only() } void Viewer::clean_created_seq() { - viewer_widget->waveform = false; + viewer_widget_->waveform = false; if (created_sequence) { // TODO delete undo commands referencing this sequence to avoid crashes @@ -950,11 +828,11 @@ void Viewer::set_sequence(bool main, SequencePtr s) { reset_all_audio(); - viewer_widget->wait_until_render_is_paused(); + viewer_widget_->wait_until_render_is_paused(); // If we had a current sequence open, close it if (seq != nullptr) { - close_active_clips(seq.get()); + seq->Close(); } clean_created_seq(); @@ -969,8 +847,8 @@ void Viewer::set_sequence(bool main, SequencePtr s) { headers->setEnabled(!null_sequence); current_timecode_slider->setEnabled(!null_sequence); - viewer_widget->setEnabled(!null_sequence); - viewer_widget->setVisible(!null_sequence); + viewer_widget_->setEnabled(!null_sequence); + viewer_widget_->setVisible(!null_sequence); go_to_start_button->setEnabled(!null_sequence); prev_frame_button->setEnabled(!null_sequence); play_button->setEnabled(!null_sequence); @@ -1001,7 +879,7 @@ void Viewer::set_sequence(bool main, SequencePtr s) { update_header_zoom(); - viewer_widget->frame_update(); + viewer_widget_->frame_update(); update(); } diff --git a/panels/viewer.h b/panels/viewer.h index 061623fe0..c7343f56d 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -37,17 +37,12 @@ #include "ui/labelslider.h" #include "ui/resizablescrollbar.h" -bool frame_rate_is_droppable(double rate); -long timecode_to_frame(const QString& s, int view, double frame_rate); -QString frame_to_timecode(long f, int view, double frame_rate); - class Viewer : public Panel { Q_OBJECT public: explicit Viewer(QWidget *parent = nullptr); - ~Viewer(); bool is_focused(); bool is_main_sequence(); @@ -90,7 +85,7 @@ public: int get_playback_speed(); - ViewerWidget* viewer_widget; + ViewerWidget* viewer_widget(); Media* media; SequencePtr seq; @@ -134,7 +129,6 @@ private slots: void drag_audio_only(); private: - void update_window_title(); void clean_created_seq(); void set_sequence(bool main, SequencePtr s); @@ -155,6 +149,8 @@ private: void setup_ui(); + ViewerWidget* viewer_widget_; + ResizableScrollBar* horizontal_bar; ViewerContainer* viewer_container; LabelSlider* current_timecode_slider; diff --git a/project/footage.cpp b/project/footage.cpp index 88c600589..631c9ae49 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +namespace OCIO = OCIO_NAMESPACE::v1; #include "project/previewgenerator.h" #include "timeline/clip.h" @@ -45,6 +47,28 @@ Footage::~Footage() { reset(); } +QString Footage::Colorspace() +{ + if (!colorspace_.isEmpty()) { + return colorspace_; + } + + // If this footage has no color space set, try to guess the color space from the filename + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + QString guess_colorspace = config->parseColorSpaceFromString(url.toUtf8()); + + if (!guess_colorspace.isEmpty()) { + return guess_colorspace; + } + + return OCIO::ROLE_SCENE_LINEAR; +} + +void Footage::SetColorspace(const QString &cs) +{ + colorspace_ = cs; +} + void Footage::reset() { if (preview_gen != nullptr) { preview_gen->cancel(); diff --git a/project/footage.h b/project/footage.h index dcd84b009..2678769d3 100644 --- a/project/footage.h +++ b/project/footage.h @@ -65,7 +65,8 @@ struct FootageStream { QVector audio_preview; }; -struct Footage { +class Footage { +public: Footage(); ~Footage(); @@ -82,10 +83,9 @@ struct Footage { bool alpha_is_associated; int start_number; -#ifndef NO_OCIO // color management - QString colorspace; -#endif + QString Colorspace(); + void SetColorspace(const QString& cs); // proxy config bool proxy; @@ -107,6 +107,8 @@ struct Footage { long get_length_in_frames(double frame_rate); FootageStream *get_stream_from_file_index(bool video, int index); void reset(); +private: + QString colorspace_; }; using FootagePtr = std::shared_ptr; diff --git a/project/loadthread.cpp b/project/loadthread.cpp index 65d869bd2..ca1c49cd5 100644 --- a/project/loadthread.cpp +++ b/project/loadthread.cpp @@ -34,6 +34,9 @@ #include #include +const int LOAD_TYPE_VERSION = 100; +const int LOAD_TYPE_URL = 101; + LoadThread::LoadThread(const QString& filename, bool autorecovery) : filename_(filename), autorecovery_(autorecovery), diff --git a/project/media.cpp b/project/media.cpp index 1c9546bb2..1e77305a2 100644 --- a/project/media.cpp +++ b/project/media.cpp @@ -33,11 +33,11 @@ extern "C" { #include "undo/undo.h" #include "undo/undostack.h" #include "global/config.h" -#include "panels/viewer.h" #include "panels/project.h" #include "ui/icons.h" #include "projectmodel.h" #include "global/debug.h" +#include "global/timing.h" QString get_interlacing_name(int interlacing) { switch (interlacing) { diff --git a/project/sourcescommon.cpp b/project/sourcescommon.cpp index 27103830c..d9967ecbb 100644 --- a/project/sourcescommon.cpp +++ b/project/sourcescommon.cpp @@ -432,7 +432,7 @@ void SourcesCommon::clear_proxies_from_selected() { if (olive::ActiveSequence != nullptr) { // close all clips so we can delete any proxies requested to be deleted - close_active_clips(olive::ActiveSequence.get()); + olive::ActiveSequence->Close(); } // delete proxies requested to be deleted @@ -442,7 +442,7 @@ void SourcesCommon::clear_proxies_from_selected() { if (olive::ActiveSequence != nullptr) { // update viewer (will re-open active clips with original media) - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } olive::Global->set_modified(true); diff --git a/rendering/audio.cpp b/rendering/audio.cpp index b54677fdb..a6d189e7b 100644 --- a/rendering/audio.cpp +++ b/rendering/audio.cpp @@ -52,7 +52,6 @@ QAudioInput* audio_input = nullptr; QFile output_recording; bool recording = false; -bool audio_rendering = false; int audio_rendering_rate = 0; qint8 audio_ibuffer[audio_ibuffer_size]; @@ -154,7 +153,7 @@ void clear_audio_ibuffer() { } int current_audio_freq() { - return audio_rendering ? audio_rendering_rate : audio_output->format().sampleRate(); + return olive::Global->is_exporting() ? audio_rendering_rate : audio_output->format().sampleRate(); } qint64 get_buffer_offset_from_frame(double framerate, long frame) { diff --git a/rendering/audio.h b/rendering/audio.h index c478839c5..1c7ebb0a8 100644 --- a/rendering/audio.h +++ b/rendering/audio.h @@ -61,7 +61,6 @@ extern long audio_ibuffer_frame; extern double audio_ibuffer_timecode; extern bool audio_scrub; extern bool recording; -extern bool audio_rendering; extern int audio_rendering_rate; void clear_audio_ibuffer(); diff --git a/rendering/bitdepths.cpp b/rendering/bitdepths.cpp index b7bf217f3..bad6dc6f7 100644 --- a/rendering/bitdepths.cpp +++ b/rendering/bitdepths.cpp @@ -28,22 +28,29 @@ namespace rendering { QVector bit_depths; void InitializeBitDepths() { - BitDepthInfo bdi; - bdi.name = QCoreApplication::translate("bitdepths", "8-bit"); - bdi.pixel_type = GL_UNSIGNED_BYTE; - bdi.internal_format = GL_RGBA8; - bit_depths.append(bdi); + bit_depths.resize(PIX_FMT_COUNT); - bdi.name = QCoreApplication::translate("bitdepths", "Half-Float (16-bit)"); - bdi.pixel_type = GL_HALF_FLOAT; - bdi.internal_format = GL_RGBA16F; - bit_depths.append(bdi); + bit_depths[PIX_FMT_RGBA8].name = QCoreApplication::translate("bitdepths", "8-bit"); + bit_depths[PIX_FMT_RGBA8].internal_format = GL_RGBA8; + bit_depths[PIX_FMT_RGBA8].pixel_format = GL_RGBA; + bit_depths[PIX_FMT_RGBA8].pixel_type = GL_UNSIGNED_BYTE; + + bit_depths[PIX_FMT_RGBA16].name = QCoreApplication::translate("bitdepths", "16-bit Integer"); + bit_depths[PIX_FMT_RGBA16].internal_format = GL_RGBA16UI; + bit_depths[PIX_FMT_RGBA16].pixel_format = GL_RGBA_INTEGER; + bit_depths[PIX_FMT_RGBA16].pixel_type = GL_UNSIGNED_SHORT; + + bit_depths[PIX_FMT_RGBA16F].name = QCoreApplication::translate("bitdepths", "Half-Float (16-bit)"); + bit_depths[PIX_FMT_RGBA16F].internal_format = GL_RGBA16F; + bit_depths[PIX_FMT_RGBA16F].pixel_format = GL_RGBA; + bit_depths[PIX_FMT_RGBA16F].pixel_type = GL_HALF_FLOAT; + + bit_depths[PIX_FMT_RGBA32F].name = QCoreApplication::translate("bitdepths", "Full-Float (32-bit)"); + bit_depths[PIX_FMT_RGBA32F].internal_format = GL_RGBA32F; + bit_depths[PIX_FMT_RGBA32F].pixel_format = GL_RGBA; + bit_depths[PIX_FMT_RGBA32F].pixel_type = GL_FLOAT; - bdi.name = QCoreApplication::translate("bitdepths", "Full-Float (32-bit)"); - bdi.pixel_type = GL_FLOAT; - bdi.internal_format = GL_RGBA32F; - bit_depths.append(bdi); } } diff --git a/rendering/bitdepths.h b/rendering/bitdepths.h index ab1b07e8a..67393440b 100644 --- a/rendering/bitdepths.h +++ b/rendering/bitdepths.h @@ -26,17 +26,34 @@ #include namespace olive { - namespace rendering { - struct BitDepthInfo { - QString name; - GLuint pixel_type; - GLuint internal_format; - }; +namespace rendering { - extern QVector bit_depths; +struct BitDepthInfo { + QString name; + GLint internal_format; + GLenum pixel_format; + GLenum pixel_type; +}; - void InitializeBitDepths(); - } +/** + * @brief The OlivePixelFormat enum + * + * Olive's internal supported pixel formats. With the exception of OLIVE_PIX_FMT_COUNT, these must all + * be defined in InitializeBitDepths(). + */ +enum PixelFormat { + PIX_FMT_RGBA8, + PIX_FMT_RGBA16, + PIX_FMT_RGBA16F, + PIX_FMT_RGBA32F, + PIX_FMT_COUNT +}; + +extern QVector bit_depths; + +void InitializeBitDepths(); + +} } #endif // BITDEPTHS_H diff --git a/rendering/cacher.cpp b/rendering/cacher.cpp index 117ac8f26..2ae5dfd52 100644 --- a/rendering/cacher.cpp +++ b/rendering/cacher.cpp @@ -33,10 +33,11 @@ #include #include +#include "panels/panels.h" #include "project/projectelements.h" #include "rendering/audio.h" #include "rendering/renderfunctions.h" -#include "panels/panels.h" +#include "global/timing.h" #include "global/config.h" #include "global/debug.h" #include "ui/mainwindow.h" @@ -44,7 +45,6 @@ // Enable verbose audio messages - good for debugging reversed audio //#define AUDIOWARNINGS -const AVPixelFormat kDestPixFmt = AV_PIX_FMT_RGBA; const AVSampleFormat kDestSampleFmt = AV_SAMPLE_FMT_S16; double bytes_to_seconds(int nb_bytes, int nb_channels, int sample_rate) { @@ -858,8 +858,6 @@ Cacher::Cacher(Clip* c) : {} void Cacher::OpenWorker() { - qint64 time_start = QDateTime::currentMSecsSinceEpoch(); - // set some defaults for the audio cacher if (clip->track() >= 0) { audio_reset_ = false; @@ -988,7 +986,26 @@ void Cacher::OpenWorker() { last_filter = yadif_filter; } - const char* chosen_format = av_get_pix_fmt_name(kDestPixFmt); + AVPixelFormat possible_pix_fmts[] = { + AV_PIX_FMT_RGBA, + AV_PIX_FMT_RGBA64, + AV_PIX_FMT_NONE + }; + + AVPixelFormat pix_fmt = avcodec_find_best_pix_fmt_of_list(possible_pix_fmts, + static_cast(stream->codecpar->format), + 1, + nullptr); + + if (pix_fmt == AV_PIX_FMT_RGBA) { + qDebug() << "This is an 8-bit image."; + media_pixel_format_ = olive::rendering::PIX_FMT_RGBA8; + } else { + qDebug() << "This is an HDR image."; + media_pixel_format_ = olive::rendering::PIX_FMT_RGBA16; + } + + const char* chosen_format = av_get_pix_fmt_name(pix_fmt); snprintf(filter_args, sizeof(filter_args), "pix_fmts=%s", chosen_format); AVFilterContext* format_conv; @@ -1091,7 +1108,7 @@ void Cacher::OpenWorker() { frame_ = av_frame_alloc(); } - qInfo() << "Clip opened on track" << clip->track() << "(took" << (QDateTime::currentMSecsSinceEpoch() - time_start) << "ms)"; + qInfo() << "Clip opened on track" << clip->track(); is_valid_state_ = true; } @@ -1336,6 +1353,11 @@ ClipQueue *Cacher::queue() return &queue_; } +const olive::rendering::PixelFormat &Cacher::media_pixel_format() +{ + return media_pixel_format_; +} + int Cacher::RetrieveFrameFromDecoder(AVFrame* f) { int result = 0; int receive_ret; diff --git a/rendering/cacher.h b/rendering/cacher.h index f413ce3b2..0cda89d61 100644 --- a/rendering/cacher.h +++ b/rendering/cacher.h @@ -43,6 +43,7 @@ extern "C" { #include #include "rendering/clipqueue.h" +#include "rendering/bitdepths.h" class Clip; @@ -254,6 +255,15 @@ public: */ ClipQueue* queue(); + /** + * @brief Retrieve OpenGL information about this media's bit depth + * + * @return + * + * A olive::rendering::PixelFormat value corresponding to a member of olive::rendering::bit_depths. + */ + const olive::rendering::PixelFormat& media_pixel_format(); + private: /** * @brief Reference to the parent clip. Set in the constructor and never changed during this object's lifetime. @@ -582,6 +592,11 @@ private: * @brief Internal function using the Cacher's known information to determine whether this media is playing in reverse */ bool IsReversed(); + + /** + * @brief Internal struct holding bit depth information for the current media + */ + olive::rendering::PixelFormat media_pixel_format_; }; #endif // CACHER_H diff --git a/rendering/exportthread.cpp b/rendering/exportthread.cpp index e5f48d2df..148d26271 100644 --- a/rendering/exportthread.cpp +++ b/rendering/exportthread.cpp @@ -413,10 +413,10 @@ void ExportThread::Export() long remaining_frames, frame_count = 1; // Use Sequence Viewer's render thread - TODO separate this into a new render thread for background rendering - RenderThread* renderer = panel_sequence_viewer->viewer_widget->get_renderer(); + RenderThread* renderer = panel_sequence_viewer->viewer_widget()->get_renderer(); // Override connection from RenderThread - disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + disconnect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); connect(renderer, SIGNAL(ready()), this, SLOT(wake())); // Lock mutex (used for synchronization with RenderThread) @@ -548,7 +548,7 @@ void ExportThread::Export() // Restore original connection from RenderThread disconnect(renderer, SIGNAL(ready()), this, SLOT(wake())); - connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget, SLOT(queue_repaint())); + connect(renderer, SIGNAL(ready()), panel_sequence_viewer->viewer_widget(), SLOT(queue_repaint())); mutex.unlock(); @@ -559,7 +559,7 @@ void ExportThread::Export() if (params_.video_enabled) vpkt_alloc = true; if (params_.audio_enabled) apkt_alloc = true; - olive::Global->set_rendering_state(false); + olive::Global->set_export_state(false); // If audio is enabled, flush the rest of the audio out of swresample if (params_.audio_enabled) { diff --git a/rendering/framebufferobject.cpp b/rendering/framebufferobject.cpp index 2e51ab151..8f9eed3d7 100644 --- a/rendering/framebufferobject.cpp +++ b/rendering/framebufferobject.cpp @@ -24,8 +24,9 @@ #include #include -// TODO take this from Config rather than having a constant -const GLuint kPixelFormat = GL_RGBA16F; +#include "global/config.h" +#include "global/global.h" +#include "bitdepths.h" FramebufferObject::FramebufferObject() : buffer_(0), @@ -64,8 +65,22 @@ void FramebufferObject::Create(QOpenGLContext *ctx, int width, int height) ctx->functions()->glBindTexture(GL_TEXTURE_2D, texture_); // allocate storage for texture + const olive::rendering::BitDepthInfo& bit_depth = olive::rendering::bit_depths.at(olive::Global->is_exporting() ? + olive::CurrentConfig.export_bit_depth : + olive::CurrentConfig.playback_bit_depth); + + qDebug() << "hello" << bit_depth.name; + ctx->functions()->glTexImage2D( - GL_TEXTURE_2D, 0, kPixelFormat, width, height, 0, GL_RGBA, GL_FLOAT, nullptr + GL_TEXTURE_2D, + 0, + bit_depth.internal_format, + width, + height, + 0, + bit_depth.pixel_format, + bit_depth.pixel_type, + nullptr ); // set texture filtering to bilinear diff --git a/rendering/renderfunctions.cpp b/rendering/renderfunctions.cpp index e64097165..a11057980 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -40,9 +40,9 @@ extern "C" { #include "ui/collapsiblewidget.h" #include "rendering/audio.h" #include "global/math.h" +#include "global/timing.h" #include "global/config.h" #include "panels/timeline.h" -#include "panels/viewer.h" #include "qopenglshaderprogramptr.h" #include "shadergenerators.h" @@ -365,6 +365,20 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { int video_width = c->media_width(); int video_height = c->media_height(); + // prepare framebuffers for backend drawing operations + if (c->fbo.isEmpty()) { + // create 3 fbos for nested sequences, 2 for most clips + int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2; + + c->fbo.resize(fbo_count); + + for (int j=0;jfbo[j].Create(params.ctx, video_width, video_height); + } + } + + bool convert_frame_to_internal = false; + // if media is footage if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { @@ -378,19 +392,13 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { } if (textureID == 0) { + qWarning() << "Failed to create texture"; - } - } - // prepare framebuffers for backend drawing operations - if (c->fbo.isEmpty()) { - // create 3 fbos for nested sequences, 2 for most clips - int fbo_count = (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_SEQUENCE) ? 3 : 2; + } else { - c->fbo.resize(fbo_count); + convert_frame_to_internal = true; - for (int j=0;jfbo[j].Create(params.ctx, video_width, video_height); } } @@ -422,60 +430,43 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { } else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { -#ifndef NO_OCIO - - // Convert texture to float - if (textureID != c->fbo.at(0).texture() && textureID != c->fbo.at(1).texture()) { - textureID = draw_clip(params.ctx, params.pipeline, c->fbo.at(fbo_switcher), textureID, true); - fbo_switcher = !fbo_switcher; - } - // Convert frame from source to linear colorspace if (olive::CurrentConfig.enable_color_management) { - if (c->ocio_shader == nullptr) { - OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + // Convert texture to sequence's internal format + if (textureID != c->fbo.at(0).texture() && textureID != c->fbo.at(1).texture()) { + textureID = draw_clip(params.ctx, params.pipeline, c->fbo.at(fbo_switcher), textureID, true); + fbo_switcher = !fbo_switcher; + } + // Check if this clip has an OCIO shader set up or not + if (c->ocio_shader == nullptr) { + + + // Set default input colorspace QString input_cs = OCIO::ROLE_SCENE_LINEAR; if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - - if (!c->media()->to_footage()->colorspace.isEmpty()) { - - input_cs = c->media()->to_footage()->colorspace; - - } else { - - // If this is a footage clip, try to guess the color space from the filename - QString guess_colorspace = config->parseColorSpaceFromString(c->media()->to_footage()->url.toUtf8()); - - if (!guess_colorspace.isEmpty()) { - input_cs = guess_colorspace; - } - - } - + input_cs = c->media()->to_footage()->Colorspace(); } - qDebug() << "Input colorspace:" << input_cs; - + // Try to get a shader based on the input color space to scene linear try { + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); OCIO::ConstProcessorRcPtr processor = config->getProcessor(input_cs.toUtf8(), OCIO::ROLE_SCENE_LINEAR); - olive::shader::AlphaAssociateMode associate_mode = (c->media()->to_footage()->alpha_is_associated) - ? olive::shader::DisassociateAndReassociate : olive::shader::Associate; - c->ocio_shader = olive::shader::SetupOCIO(params.ctx, c->ocio_lut_texture, processor, - associate_mode); + c->media()->to_footage()->alpha_is_associated); } catch (OCIO::Exception& e) { qWarning() << e.what(); } } + // Ensure we got a shader, and if so, blit with it if (c->ocio_shader != nullptr) { textureID = olive::rendering::OCIOBlit(c->ocio_shader.get(), c->ocio_lut_texture, @@ -486,8 +477,6 @@ GLuint olive::rendering::compose_sequence(ComposeSequenceParams ¶ms) { } } -#endif - } } @@ -801,54 +790,6 @@ void olive::rendering::compose_audio(Viewer* viewer, Sequence* seq, int playback compose_sequence(params); } -long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate) { - return qRound((double(framenumber)/source_frame_rate)*target_frame_rate); -} - -double get_timecode(Clip* c, long playhead) { - return double(playhead_to_clip_frame(c, playhead))/c->sequence->frame_rate; -} - -long playhead_to_clip_frame(Clip* c, long playhead) { - return (qMax(0L, playhead - c->timeline_in(true)) + c->clip_in(true)); -} - -double playhead_to_clip_seconds(Clip* c, long playhead) { - // returns time in seconds - long clip_frame = playhead_to_clip_frame(c, playhead); - - if (c->reversed()) { - clip_frame = c->media_length() - clip_frame - 1; - } - - double secs = (double(clip_frame)/c->sequence->frame_rate)*c->speed().value; - if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - secs *= c->media()->to_footage()->speed; - } - - return secs; -} - -int64_t seconds_to_timestamp(Clip *c, double seconds) { - return qRound64(seconds * av_q2d(av_inv_q(c->time_base()))); -} - -int64_t playhead_to_timestamp(Clip* c, long playhead) { - return seconds_to_timestamp(c, playhead_to_clip_seconds(c, playhead)); -} - -void close_active_clips(Sequence* s) { - if (s != nullptr) { - for (int i=0;iclips.size();i++) { - Clip* c = s->clips.at(i).get(); - if (c != nullptr) { - c->Close(true); - } - } - } -} - -#ifndef NO_OCIO GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, GLuint lut, const FramebufferObject& fbo, @@ -880,4 +821,3 @@ GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, return textureID; } -#endif diff --git a/rendering/renderfunctions.h b/rendering/renderfunctions.h index a4f5c2877..228608762 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -24,10 +24,8 @@ #include #include #include -#ifndef NO_OCIO #include namespace OCIO = OCIO_NAMESPACE::v1; -#endif #include "timeline/sequence.h" #include "effects/effect.h" @@ -244,143 +242,6 @@ void compose_audio(Viewer* viewer, Sequence *seq, int playback_speed, bool wait_ } } -/** - * @brief Rescale a frame number between two frame rates - * - * Converts a frame number from one frame rate to its equivalent in another frame rate - * - * @param framenumber - * - * The frame number to convert - * - * @param source_frame_rate - * - * Frame rate that the frame number is currently in - * - * @param target_frame_rate - * - * Frame rate to convert to - * - * @return - * - * Rescaled frame number - */ -long rescale_frame_number(long framenumber, double source_frame_rate, double target_frame_rate); - -/** - * @brief Get timecode - * - * Get the current clip/media time from the Timeline playhead in seconds. For instance if the playhead was at the start - * of a clip (whose in point wasn't trimmed), this would be 0.0 as it's the start of the clip/media; - * - * @param c - * - * Clip to get the timecode of - * - * @param playhead - * - * Sequence playhead to convert to a clip/media timecode - * - * @return - * - * Timecode in seconds - */ -double get_timecode(Clip *c, long playhead); - -/** - * @brief Convert playhead frame number to a clip frame number - * - * Converts a Timeline playhead to a the current clip's frame. Equivalent to - * `PLAYHEAD - CLIP_TIMELINE_IN + CLIP_MEDIA_IN`. All keyframes are in clip frames. - * - * @param c - * - * The clip to get the current frame number of - * - * @param playhead - * - * The current Timeline frame number - * - * @return - * - * The curren frame number of the clip at `playhead` - */ -long playhead_to_clip_frame(Clip* c, long playhead); - -/** - * @brief Converts the playhead to clip seconds - * - * Get the current timecode at the playhead in terms of clip seconds. - * - * FIXME: Possible duplicate of get_timecode()? Will need to research this more. - * - * @param c - * - * Clip to return clip seconds of. - * - * @param playhead - * - * Current Timeline playhead to convert to clip seconds - * - * @return - * - * Clip time in seconds - */ -double playhead_to_clip_seconds(Clip *c, long playhead); - -/** - * @brief Convert seconds to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts seconds in a floating-point value to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param seconds - * - * Clip time in seconds - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ -int64_t seconds_to_timestamp(Clip* c, double seconds); - -/** - * @brief Convert Timeline playhead to FFmpeg timestamp - * - * Used for interaction with FFmpeg, converts the Timeline playhead to a timestamp in AVStream->time_base - * units. - * - * @param c - * - * Clip to get timestamp of - * - * @param playhead - * - * Timeline playhead to convert to a timestamp - * - * @return - * - * An FFmpeg-compatible timestamp in AVStream->time_base units. - */ -int64_t playhead_to_timestamp(Clip *c, long playhead); - -/** - * @brief Close all open clips in a Sequence - * - * Closes any currently open clips on a Sequence and waits for them to close before returning. This may be slow as a - * result on large Sequence objects. If a Clip is a nested Sequence, this function calls itself recursively on that - * Sequence too. - * - * @param s - * - * The Sequence to close all clips on. - */ -void close_active_clips(Sequence* s); - void UpdateOCIOGLState(const ComposeSequenceParams ¶ms); namespace olive { @@ -389,13 +250,10 @@ namespace olive { extern GLfloat blit_texcoords[]; extern GLfloat flipped_blit_texcoords[]; void Blit(QOpenGLShaderProgram* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); - -#ifndef NO_OCIO GLuint OCIOBlit(QOpenGLShaderProgram *pipeline, GLuint lut, const FramebufferObject& fbo, GLuint texture); -#endif } } diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index d1ab0b22f..ab839350e 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -27,10 +27,8 @@ #include #include -#ifndef NO_OCIO #include namespace OCIO = OCIO_NAMESPACE::v1; -#endif #include "timeline/sequence.h" #include "effects/effectloaders.h" @@ -48,14 +46,10 @@ RenderThread::RenderThread() : tex_height(-1), queued(false), texture_failed(false), - #ifndef NO_OCIO ocio_lut_texture(0), ocio_shader(nullptr), - #endif running(true), - #ifndef NO_OCIO ocio_config_date(0), - #endif front_buffer_switcher(false) { surface.create(); @@ -121,17 +115,12 @@ void RenderThread::run() { pipeline_program = olive::shader::GetPipeline(); } -#ifndef NO_OCIO // If there's no OpenColorIO shader or the configuration has changed, (re-)create it now - if (olive::CurrentConfig.enable_color_management - && (ocio_shader == nullptr || ocio_config_date != olive::CurrentRuntimeConfig.ocio_config_date)) { - ocio_config_date = olive::CurrentRuntimeConfig.ocio_config_date; - + if (olive::CurrentConfig.enable_color_management && ocio_shader == nullptr) { destroy_ocio(); set_up_ocio(); } -#endif // draw frame paint(); @@ -160,7 +149,6 @@ const GLuint &RenderThread::get_texture() return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.texture(); } -#ifndef NO_OCIO void RenderThread::set_up_ocio() { @@ -194,7 +182,7 @@ void RenderThread::set_up_ocio() OCIO::ConstProcessorRcPtr processor = config->getProcessor(transform); // Create a OCIO shader with this processor - ocio_shader = olive::shader::SetupOCIO(ctx, ocio_lut_texture, processor, olive::shader::NoAssociate); + ocio_shader = olive::shader::SetupOCIO(ctx, ocio_lut_texture, processor, true); } catch(OCIO::Exception & e) { qCritical() << e.what(); @@ -205,12 +193,12 @@ void RenderThread::set_up_ocio() void RenderThread::destroy_ocio() { // Destroy LUT texture - ctx->functions()->glDeleteTextures(1, &ocio_lut_texture); + if (ocio_lut_texture > 0) { + ctx->functions()->glDeleteTextures(1, &ocio_lut_texture); + } ocio_lut_texture = 0; - ocio_shader = nullptr; } -#endif void RenderThread::paint() { // set up compose_sequence() parameters @@ -254,29 +242,24 @@ void RenderThread::paint() { FramebufferObject& buffer = front_buffer_switcher ? front_buffer_1 : front_buffer_2; // Blit the composite buffer to one of the front buffers - bool standard_blit = true; -#ifndef NO_OCIO // If we're color managing, conver the linear composited frame to display color space if (olive::CurrentConfig.enable_color_management && ocio_shader != nullptr) { + olive::rendering::OCIOBlit(ocio_shader.get(), ocio_lut_texture, buffer, composite_buffer.texture()); - standard_blit = false; - } -#else + } else { -#endif - - // If we're not color managing, just blit normally - if (standard_blit) { + // If we're not color managing, just blit normally buffer.BindBuffer(); composite_buffer.BindTexture(); olive::rendering::Blit(pipeline_program.get()); composite_buffer.ReleaseTexture(); buffer.ReleaseBuffer(); + } // flush changes @@ -404,10 +387,7 @@ void RenderThread::delete_ctx() { if (ctx != nullptr) { delete_shaders(); delete_buffers(); - -#ifndef NO_OCIO destroy_ocio(); -#endif } delete ctx; diff --git a/rendering/renderthread.h b/rendering/renderthread.h index da6259b62..89e33f521 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -60,23 +60,20 @@ public: public slots: // cleanup functions void delete_ctx(); + void delete_buffers(); + void delete_shaders(); + void destroy_ocio(); signals: void ready(); private: - // cleanup functions - void delete_buffers(); - void delete_shaders(); -#ifndef NO_OCIO // OpenColorIO functions void set_up_ocio(); - void destroy_ocio(); // OpenColorIO variables GLuint ocio_lut_texture; QOpenGLShaderProgramPtr ocio_shader; qint64 ocio_config_date; -#endif FramebufferObject front_buffer_1; QMutex front_mutex1; diff --git a/rendering/shadergenerators.cpp b/rendering/shadergenerators.cpp index dde856ba6..6a6490d63 100644 --- a/rendering/shadergenerators.cpp +++ b/rendering/shadergenerators.cpp @@ -118,8 +118,6 @@ QString olive::shader::GetAlphaAssociateFunction(const QString &function_name) "}\n").arg(function_name); } -#ifndef NO_OCIO - // copied from source code to OCIODisplay const int OCIO_LUT3D_EDGE_SIZE = 32; @@ -129,7 +127,7 @@ const int OCIO_NUM_3D_ENTRIES = 98304; QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx, GLuint& lut_texture, OCIO::ConstProcessorRcPtr processor, - AlphaAssociateMode alpha_associate_mode) + bool alpha_is_associated) { QOpenGLExtraFunctions* xf = ctx->extraFunctions(); @@ -157,8 +155,9 @@ QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx, // OCIO::GpuShaderDesc shaderDesc; + const char* ocio_func_name = "OCIODisplay"; shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); - shaderDesc.setFunctionName("OCIODisplay"); + shaderDesc.setFunctionName(ocio_func_name); shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); // @@ -179,39 +178,45 @@ QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx, // Create OCIO shader code QString shader_text(processor->getGpuShaderText(shaderDesc)); - QString ocio_call_func; + QString shader_call; // Enforce alpha association - switch (alpha_associate_mode) { - case Associate: + if (alpha_is_associated) { + + // If alpha is already associated, we'll need to disassociate and reassociate + shader_text.append("\n"); + + QString disassociate_func_name = "disassoc"; + shader_text.append(GetAlphaDisassociateFunction(disassociate_func_name)); + + QString reassociate_func_name = "reassoc"; + shader_text.append(GetAlphaReassociateFunction(reassociate_func_name)); + + // Make OCIO call pass through disassociate and reassociate function + shader_call = QString("%3(%1(%2(col), tex2));").arg(ocio_func_name, + disassociate_func_name, + reassociate_func_name); + + } else { + // If alpha is not already associated, we can just associate after OCIO // Add associate function - shader_text.append(GetAlphaAssociateFunction("assoc")); + QString associate_func_name = "assoc"; + shader_text.append(GetAlphaAssociateFunction(associate_func_name)); // Make OCIO call pass through associate function - ocio_call_func = "assoc(OCIODisplay(col, tex2));"; - break; - case DisassociateAndReassociate: - // If alpha is already associated, we'll need to disassociate and reassociate - shader_text.append("\n"); - shader_text.append(GetAlphaDisassociateFunction("disassoc")); - shader_text.append(GetAlphaReassociateFunction("reassoc")); + shader_call = QString("%2(%1(col, tex2));").arg(ocio_func_name, associate_func_name); - // Make OCIO call pass through disassociate and reassociate function - ocio_call_func = "reassoc(OCIODisplay(disassoc(col), tex2));"; - break; - default: - // No association - ocio_call_func = "OCIODisplay(col, tex2);"; } + // Add process() function, which GetPipeline() will call if specified shader_text.append(QString("\n" "uniform sampler3D tex2;\n" "\n" "vec4 process(vec4 col) {\n" " return %1\n" - "}\n").arg(ocio_call_func)); + "}\n").arg(shader_call)); // Get pipeline-based shader to inject OCIO shader into @@ -222,4 +227,3 @@ QOpenGLShaderProgramPtr olive::shader::SetupOCIO(QOpenGLContext* ctx, return shader; } -#endif diff --git a/rendering/shadergenerators.h b/rendering/shadergenerators.h index eacb51e83..5fe5a7370 100644 --- a/rendering/shadergenerators.h +++ b/rendering/shadergenerators.h @@ -3,29 +3,18 @@ #include "qopenglshaderprogramptr.h" #include "framebufferobject.h" - -#ifndef NO_OCIO #include namespace OCIO = OCIO_NAMESPACE::v1; -#endif namespace olive { namespace shader { QOpenGLShaderProgramPtr GetPipeline(const QString &shader_code = QString()); -#ifndef NO_OCIO -enum AlphaAssociateMode { - NoAssociate, - Associate, - DisassociateAndReassociate -}; - QOpenGLShaderProgramPtr SetupOCIO(QOpenGLContext *ctx, GLuint &lut_texture, OCIO::ConstProcessorRcPtr processor, - AlphaAssociateMode alpha_associate_mode); -#endif + bool alpha_is_associated); QString GetAlphaDisassociateFunction(const QString& function_name); QString GetAlphaReassociateFunction(const QString& function_name); diff --git a/timeline/clip.cpp b/timeline/clip.cpp index 79b79de46..36f5bec8c 100644 --- a/timeline/clip.cpp +++ b/timeline/clip.cpp @@ -35,6 +35,7 @@ #include "project/clipboard.h" #include "undo/undo.h" #include "global/debug.h" +#include "global/timing.h" const int kRGBAComponentCount = 4; @@ -492,7 +493,7 @@ void Clip::Close(bool wait) { open_ = false; if (media() != nullptr && media()->get_type() == MEDIA_TYPE_SEQUENCE) { - close_active_clips(media()->to_sequence().get()); + media()->to_sequence()->Close(); } // destroy opengl texture in main thread @@ -511,10 +512,8 @@ void Clip::Close(bool wait) { // delete framebuffers fbo.clear(); -#ifndef NO_OCIO // delete OCIO shader ocio_shader = nullptr; -#endif if (UsesCacher()) { cacher.Close(wait); @@ -590,18 +589,20 @@ bool Clip::Retrieve() int video_width = cacher.media_width(); int video_height = cacher.media_height(); + const olive::rendering::BitDepthInfo& bit_depth_info = olive::rendering::bit_depths.at(cacher.media_pixel_format()); + if (allocate_data) { // the raw frame size may differ from the one we're using (e.g. a lower resolution proxy), so we make sure // the texture is using the correct dimensions, but then treat it as if it's the original resolution in the // composition f->glTexImage2D( - GL_TEXTURE_2D, 0, GL_RGBA8, video_width, video_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, frame->data[0] + GL_TEXTURE_2D, 0, bit_depth_info.internal_format, video_width, video_height, 0, bit_depth_info.pixel_format, bit_depth_info.pixel_type, frame->data[0] ); } else { - f->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_width, video_height, GL_RGBA, GL_UNSIGNED_BYTE, frame->data[0]); + f->glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_width, video_height, bit_depth_info.pixel_format, bit_depth_info.pixel_type, frame->data[0]); } diff --git a/timeline/clip.h b/timeline/clip.h index 993141ebf..1dae4828d 100644 --- a/timeline/clip.h +++ b/timeline/clip.h @@ -39,11 +39,6 @@ #include "marker.h" -extern "C" { -#include -#include -} - struct ClipSpeed { ClipSpeed(); double value; diff --git a/timeline/sequence.cpp b/timeline/sequence.cpp index 3df810087..8d3b06ecb 100644 --- a/timeline/sequence.cpp +++ b/timeline/sequence.cpp @@ -34,8 +34,6 @@ Sequence::Sequence() : { } -Sequence::~Sequence() {} - SequencePtr Sequence::copy() { SequencePtr s = std::make_shared(); s->name = QCoreApplication::translate("Sequence", "%1 (copy)").arg(name); @@ -75,6 +73,16 @@ long Sequence::getEndFrame() { return end; } +void Sequence::Close() +{ + for (int i=0;iClose(true); + } + } +} + void Sequence::RefreshClips(Media *m) { for (int i=0;i SelectedClips(bool containing = true); QVector SelectedClipIndexes(); diff --git a/ui/focusfilter.cpp b/ui/focusfilter.cpp index 834e4525c..18b6a1c5e 100644 --- a/ui/focusfilter.cpp +++ b/ui/focusfilter.cpp @@ -93,9 +93,9 @@ void FocusFilter::go_to_end() { void FocusFilter::set_viewer_fullscreen() { if (get_focused_panel() == panel_footage_viewer) { - panel_footage_viewer->viewer_widget->set_fullscreen(); + panel_footage_viewer->viewer_widget()->set_fullscreen(); } else { - panel_sequence_viewer->viewer_widget->set_fullscreen(); + panel_sequence_viewer->viewer_widget()->set_fullscreen(); } } diff --git a/ui/labelslider.cpp b/ui/labelslider.cpp index 9c1dcd285..77043a8b3 100644 --- a/ui/labelslider.cpp +++ b/ui/labelslider.cpp @@ -26,7 +26,7 @@ #include #include "undo/undo.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "global/config.h" #include "global/math.h" #include "global/debug.h" diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 74bf60fd7..731520496 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -271,7 +271,6 @@ MainWindow::MainWindow(QWidget *parent) : olive::icon::Initialize(); -#ifndef NO_OCIO // Load OpenColorIO configuration if set if (olive::CurrentConfig.enable_color_management && !olive::CurrentConfig.ocio_config_path.isEmpty()) { try { @@ -283,7 +282,6 @@ MainWindow::MainWindow(QWidget *parent) : QMessageBox::Ok); } } -#endif alloc_panels(this); @@ -971,8 +969,8 @@ void MainWindow::closeEvent(QCloseEvent *e) { olive::Global->set_sequence(nullptr); - panel_footage_viewer->viewer_widget->close_window(); - panel_sequence_viewer->viewer_widget->close_window(); + panel_footage_viewer->viewer_widget()->close_window(); + panel_sequence_viewer->viewer_widget()->close_window(); panel_footage_viewer->set_main_sequence(); diff --git a/ui/menuhelper.cpp b/ui/menuhelper.cpp index 9553eada1..0e0f448f0 100644 --- a/ui/menuhelper.cpp +++ b/ui/menuhelper.cpp @@ -242,7 +242,7 @@ void MenuHelper::set_titlesafe_from_menu() { } - panel_sequence_viewer->viewer_widget->update(); + panel_sequence_viewer->viewer_widget()->update(); } void MenuHelper::set_autoscroll() { diff --git a/ui/timelineheader.cpp b/ui/timelineheader.cpp index e16a3d223..81816492a 100644 --- a/ui/timelineheader.cpp +++ b/ui/timelineheader.cpp @@ -28,11 +28,10 @@ #include "mainwindow.h" #include "panels/panels.h" -#include "panels/timeline.h" #include "timeline/sequence.h" #include "undo/undo.h" #include "project/media.h" -#include "panels/viewer.h" +#include "global/timing.h" #include "global/config.h" #include "global/global.h" #include "ui/menu.h" diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 7bfa44f96..00ce56aca 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -39,6 +39,7 @@ #include "project/projectelements.h" #include "rendering/audio.h" #include "global/config.h" +#include "global/timing.h" #include "ui/sourcetable.h" #include "ui/sourceiconview.h" #include "undo/undo.h" diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index 488fc9678..3261a25cc 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -49,6 +49,7 @@ extern "C" { #include "global/config.h" #include "global/debug.h" #include "global/math.h" +#include "global/timing.h" #include "ui/collapsiblewidget.h" #include "undo/undo.h" #include "project/media.h" @@ -278,10 +279,6 @@ QMatrix4x4 ViewerWidget::get_matrix() void ViewerWidget::context_destroy() { makeCurrent(); - if (viewer->seq != nullptr) { - close_active_clips(viewer->seq.get()); - } - renderer.delete_ctx(); title_safe_area_buffer_.destroy(); diff --git a/undo/undo.cpp b/undo/undo.cpp index ba073b3fa..dcefa41ca 100644 --- a/undo/undo.cpp +++ b/undo/undo.cpp @@ -675,13 +675,13 @@ void SetClipProperty::MainLoop(bool undo) void SetClipProperty::doUndo() { MainLoop(true); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } void SetClipProperty::doRedo() { MainLoop(false); - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } AddMarkerAction::AddMarkerAction(QVector* m, long t, QString n) { @@ -896,7 +896,7 @@ void CloseAllClipsCommand::doUndo() { } void CloseAllClipsCommand::doRedo() { - close_active_clips(olive::ActiveSequence.get()); + olive::ActiveSequence->Close(); } UpdateFootageTooltip::UpdateFootageTooltip(Media *i) { @@ -1112,7 +1112,7 @@ void UpdateViewer::doUndo() { } void UpdateViewer::doRedo() { - panel_sequence_viewer->viewer_widget->frame_update(); + panel_sequence_viewer->viewer_widget()->frame_update(); } SetEffectData::SetEffectData(Effect *e, const QByteArray &s) {