From 7d337e6de2ab416f73d8898b2477b30ccba13a2a Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Wed, 20 Mar 2019 01:42:40 +1100 Subject: [PATCH] two step process and configurable colorspaces --- dialogs/mediapropertiesdialog.cpp | 34 +++++- dialogs/mediapropertiesdialog.h | 19 +-- dialogs/preferencesdialog.cpp | 193 ++++++++++++++++++++++++++---- dialogs/preferencesdialog.h | 27 ++++- global/config.cpp | 3 +- global/config.h | 31 ++++- panels/project.cpp | 9 +- project/footage.cpp | 22 ++-- project/footage.h | 7 +- project/loadthread.cpp | 4 +- rendering/renderfunctions.cpp | 174 +++++++++++++++++++++++---- rendering/renderfunctions.h | 52 +++----- rendering/renderthread.cpp | 187 +++++++++++------------------ rendering/renderthread.h | 13 +- timeline/clip.cpp | 9 +- timeline/clip.h | 5 + ui/mainwindow.cpp | 14 +++ 17 files changed, 559 insertions(+), 244 deletions(-) diff --git a/dialogs/mediapropertiesdialog.cpp b/dialogs/mediapropertiesdialog.cpp index 3ea790c55..7e35ed5be 100644 --- a/dialogs/mediapropertiesdialog.cpp +++ b/dialogs/mediapropertiesdialog.cpp @@ -30,6 +30,10 @@ #include #include #include +#ifndef NO_OCIO +#include +namespace OCIO = OCIO_NAMESPACE; +#endif #include "project/footage.h" #include "project/media.h" @@ -103,7 +107,7 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : // premultiplied alpha mode premultiply_alpha_setting = new QCheckBox(tr("Alpha is Premultiplied"), this); - premultiply_alpha_setting->setChecked(f->alpha_is_premultiplied); + premultiply_alpha_setting->setChecked(f->alpha_is_associated); grid->addWidget(premultiply_alpha_setting, row, 0); row++; @@ -128,6 +132,28 @@ MediaPropertiesDialog::MediaPropertiesDialog(QWidget *parent, Media *i) : grid->addWidget(interlacing_box, row, 1); row++; + +#ifndef NO_OCIO + color_management = new QComboBox(this); + + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + + for (int i=0;igetNumColorSpaces();i++) { + QString colorspace = config->getColorSpaceNameByIndex(i); + + color_management->addItem(colorspace); + + if (colorspace == f->colorspace) { + color_management->setCurrentIndex(i); + } + } + + grid->addWidget(new QLabel(tr("Color Space:")), row, 0); + grid->addWidget(color_management, row, 1); + + row++; +#endif + } name_box = new QLineEdit(item->get_name(), this); @@ -192,9 +218,13 @@ void MediaPropertiesDialog::accept() { } // set premultiplied alpha - f->alpha_is_premultiplied = premultiply_alpha_setting->isChecked(); + f->alpha_is_associated = premultiply_alpha_setting->isChecked(); } +#ifndef NO_OCIO + f->colorspace = color_management->currentText(); +#endif + // set name MediaRename* mr = new MediaRename(item, name_box->text()); diff --git a/dialogs/mediapropertiesdialog.h b/dialogs/mediapropertiesdialog.h index 8f24eee72..e8ff9492a 100644 --- a/dialogs/mediapropertiesdialog.h +++ b/dialogs/mediapropertiesdialog.h @@ -32,18 +32,19 @@ #include "project/media.h" class MediaPropertiesDialog : public QDialog { - Q_OBJECT + Q_OBJECT public: - MediaPropertiesDialog(QWidget *parent, Media* i); + MediaPropertiesDialog(QWidget *parent, Media* i); private: - QComboBox* interlacing_box; - QLineEdit* name_box; - Media* item; - QListWidget* track_list; - QDoubleSpinBox* conform_fr; - QCheckBox* premultiply_alpha_setting; + QComboBox* interlacing_box; + QLineEdit* name_box; + Media* item; + QListWidget* track_list; + QDoubleSpinBox* conform_fr; + QCheckBox* premultiply_alpha_setting; + QComboBox* color_management; private slots: - void accept(); + void accept(); }; #endif // MEDIAPROPERTIESDIALOG_H diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index 94a2533f5..24ff80e64 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -20,13 +20,6 @@ #include "preferencesdialog.h" -#include "global/global.h" -#include "global/config.h" -#include "global/path.h" -#include "rendering/audio.h" -#include "panels/panels.h" -#include "ui/mainwindow.h" - #include #include #include @@ -51,6 +44,13 @@ #include #include +#include "global/global.h" +#include "global/config.h" +#include "global/path.h" +#include "rendering/audio.h" +#include "panels/panels.h" +#include "ui/mainwindow.h" + KeySequenceEditor::KeySequenceEditor(QWidget* parent, QAction* a) : QKeySequenceEdit(parent), action(a) { setKeySequence(action->shortcut()); @@ -108,12 +108,28 @@ void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* } } -void PreferencesDialog::delete_previews(char type) { - if (type != 't' && type != 'w' && type != 1) return; +void PreferencesDialog::delete_previews(PreviewDeleteTypes type) { + char delete_char = 0; + + switch (type) { + case DELETE_WAVEFORMS: + delete_char = 'w'; + break; + case DELETE_THUMBNAILS: + delete_char = 't'; + break; + case DELETE_BOTH: + delete_char = 1; + break; + case DELETE_NONE: + break; + } + + if (delete_char != 't' && delete_char != 'w' && delete_char != 1) return; QDir preview_path(get_data_path() + "/previews"); - if (type == 1) { + if (delete_char == 1) { // indiscriminately delete everything preview_path.removeRecursively(); } else { @@ -134,13 +150,85 @@ void PreferencesDialog::delete_previews(char type) { // thumbnails will have a 't' towards the end of the filenames, waveforms will have a 'w' // if they match the type of preview we're deleting, remove them - if (preview_file_str.at(identifier_char_index) == type) { + if (preview_file_str.at(identifier_char_index) == delete_char) { QFile::remove(preview_path.filePath(preview_file_str)); } } } } +void PreferencesDialog::populate_ocio_menus(OCIO::ConstConfigRcPtr config) +{ + // Get current display name (if the config is empty, get the current default display) + QString current_display = olive::CurrentConfig.ocio_display; + if (current_display.isEmpty()) { + current_display = config->getDefaultDisplay(); + } + + // Populate the display menu + ocio_display->clear(); + for (int i=0;igetNumDisplays();i++) { + ocio_display->addItem(config->getDisplay(i)); + + // Check if this index is the currently selected + if (config->getDisplay(i) == current_display) { + ocio_display->setCurrentIndex(i); + } + } + + update_ocio_view_menu(config); + + // Populate the look menu + ocio_look->clear(); + ocio_look->addItem(tr("(None)"), QString()); + for (int i=0;igetNumLooks();i++) { + const char* look = config->getLookNameByIndex(i); + + ocio_look->addItem(look, look); + + if (look == olive::CurrentConfig.ocio_look) { + ocio_look->setCurrentIndex(i); + } + } +} + +void PreferencesDialog::update_ocio_view_menu(OCIO::ConstConfigRcPtr config) +{ + + // Get views for the current display set in `ocio_display` + QString display = ocio_display->currentText(); + + // Get current view + QString current_view = olive::CurrentConfig.ocio_view; + if (current_view.isEmpty()) { + current_view = config->getDefaultView(display.toUtf8()); + } + + // Populate the view menu + int ocio_view_count = config->getNumViews(display.toUtf8()); + ocio_view->clear(); + for (int i=0;igetView(display.toUtf8(), i); + + ocio_view->addItem(view); + + if (current_view == view) { + ocio_view->setCurrentIndex(i); + } + } +} + +void PreferencesDialog::update_ocio_config(const QString &s) +{ + if (!s.isEmpty() && QFileInfo::exists(s)) { + try { + OCIO::ConstConfigRcPtr file_config = OCIO::Config::CreateFromFile(s.toUtf8()); + + populate_ocio_menus(file_config); + } catch (OCIO::Exception& e) {} + } +} + void PreferencesDialog::setup_kbd_shortcuts(QMenuBar* menubar) { QList menus = menubar->actions(); @@ -209,9 +297,9 @@ void PreferencesDialog::save() { if (olive::CurrentConfig.use_software_fallback != use_software_fallbacks_checkbox->isChecked() || olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value() || olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value() -#ifdef Q_OS_WIN32 + #ifdef Q_OS_WIN32 || olive::CurrentConfig.use_native_menu_styling != native_menus->isChecked() -#endif + #endif || olive::CurrentConfig.style != static_cast(ui_style->currentData().toInt())) { // any changes to these settings will require a restart - ask the user if we should do one now or later @@ -274,7 +362,31 @@ void PreferencesDialog::save() { olive::CurrentConfig.language_file = language_combobox->currentData().toString(); olive::CurrentConfig.enable_color_management = enable_color_management->isChecked(); - olive::CurrentConfig.ocio_config_path = ocio_config_file->text(); + +#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); + } + + } + + 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 + olive::CurrentConfig.style = static_cast(ui_style->currentData().toInt()); #ifdef Q_OS_WIN @@ -287,14 +399,14 @@ void PreferencesDialog::save() { // we're changing the size of thumbnails and waveforms, so let's delete them and regenerate them next start // delete nothing - char delete_match = 0; + PreviewDeleteTypes delete_type = DELETE_NONE; if (olive::CurrentConfig.thumbnail_resolution != thumbnail_res_spinbox->value()) { // delete existing thumbnails olive::CurrentConfig.thumbnail_resolution = thumbnail_res_spinbox->value(); // delete only thumbnails - delete_match = 't'; + delete_type = DELETE_THUMBNAILS; } if (olive::CurrentConfig.waveform_resolution != waveform_res_spinbox->value()) { @@ -302,16 +414,16 @@ void PreferencesDialog::save() { olive::CurrentConfig.waveform_resolution = waveform_res_spinbox->value(); // if we're already deleting thumbnails - if (delete_match == 't') { + if (delete_type == DELETE_THUMBNAILS) { // delete all - delete_match = 1; + delete_type = DELETE_BOTH; } else { // just delete waveforms - delete_match = 'w'; + delete_type = DELETE_WAVEFORMS; } } - delete_previews(delete_match); + delete_previews(delete_type); } // Save keyboard shortcuts @@ -476,12 +588,17 @@ void PreferencesDialog::browse_ocio_config() } } +void PreferencesDialog::update_ocio_view_menu() +{ + update_ocio_view_menu(OCIO::GetCurrentConfig()); +} + void PreferencesDialog::delete_all_previews() { if (QMessageBox::question(this, tr("Delete All Previews"), tr("Are you sure you want to delete all previews?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - delete_previews(1); + delete_previews(DELETE_BOTH); QMessageBox::information(this, tr("Previews Deleted"), tr("All previews deleted succesfully. You may have to re-open your current project for " @@ -772,32 +889,64 @@ void PreferencesDialog::setup_ui() { #ifdef NO_OCIO QLabel* no_ocio_available_lbl = new QLabel(tr("Color management is unavailable because Olive was " - "compiled without OpenColorIO support.")); + "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); row++; + // COLOR MANAGEMENT -> OpenColorIO Config File color_management_layout->addWidget(new QLabel(tr("OpenColorIO Config File:")), row, 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); 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++; + + // 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++; + + // 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++; + + // COLOR MANAGEMENT -> Look + ocio_look = new QComboBox(); + color_management_layout->addWidget(new QLabel("Look:"), row, 0); + color_management_layout->addWidget(ocio_look, row, 1); + + 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 9e01ee236..da3439f74 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -33,6 +33,10 @@ #include #include #include +#ifndef NO_OCIO +#include +namespace OCIO = OCIO_NAMESPACE; +#endif #include "timeline/sequence.h" @@ -71,10 +75,22 @@ private slots: void browse_css_file(); void browse_ocio_config(); + // OCIO function + void update_ocio_view_menu(); + void update_ocio_view_menu(OCIO::ConstConfigRcPtr config); + void update_ocio_config(const QString&); + private: void setup_ui(); void setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent); + enum PreviewDeleteTypes { + DELETE_NONE, + DELETE_THUMBNAILS, + DELETE_WAVEFORMS, + DELETE_BOTH + }; + // used to delete previews // type can be: 't' for thumbnails, 'w' for waveforms, or 1 for all /** @@ -82,9 +98,11 @@ private: * * @param type * - * The types of previews to + * The types of previews to delete. */ - void delete_previews(char type); + void delete_previews(PreviewDeleteTypes type); + + void populate_ocio_menus(OCIO::ConstConfigRcPtr config); QLineEdit* custom_css_fn; QLineEdit* imgSeqFormatEdit; @@ -103,8 +121,13 @@ private: QSpinBox* thumbnail_res_spinbox; QSpinBox* waveform_res_spinbox; QCheckBox* add_default_effects_to_clips; + QCheckBox* enable_color_management; QLineEdit* ocio_config_file; + QComboBox* ocio_display; + QComboBox* ocio_view; + QComboBox* ocio_look; + QComboBox* ui_style; Sequence sequence_settings; #ifdef Q_OS_WIN diff --git a/global/config.cpp b/global/config.cpp index 9e79abb71..ba6691cd8 100644 --- a/global/config.cpp +++ b/global/config.cpp @@ -299,5 +299,6 @@ void Config::save(QString path) { } RuntimeConfig::RuntimeConfig() : - shaders_are_enabled(true) + shaders_are_enabled(true), + ocio_config_date(QDateTime::currentMSecsSinceEpoch()) {} diff --git a/global/config.h b/global/config.h index fbcd9425e..c4ab32b79 100644 --- a/global/config.h +++ b/global/config.h @@ -519,10 +519,31 @@ struct Config { /** * @brief Path to OpenColorIO configuration file * - * Used if Config::enable_color_management is **TRUE**. + * Used if Config::enable_color_management is true. */ QString ocio_config_path; + /** + * @brief OpenColorIO Display + * + * Used if Config::enable_color_management is true + */ + QString ocio_display; + + /** + * @brief OpenColorIO View + * + * Used if Config::enable_color_management is true + */ + QString ocio_view; + + /** + * @brief OpenColorIO Look + * + * Used if Config::enable_color_management is true + */ + QString ocio_look; + /** * @brief Style to use when theming Olive. * @@ -587,6 +608,14 @@ struct RuntimeConfig { * Overrides Config::language_file and sets the path to a language file to use. */ 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/panels/project.cpp b/panels/project.cpp index dfacd8488..12d167d34 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -871,7 +871,12 @@ void Project::process_file_list(QStringList& files, bool recursive, MediaPtr rep item = std::make_shared(parent); } - m = FootagePtr(new Footage()); + m = std::make_shared(); + + // Edge case for PNGs that standardized unassociated alpha + if (file.endsWith("png", Qt::CaseInsensitive)) { + m->alpha_is_associated = false; + } m->using_inout = false; m->url = file; @@ -1107,7 +1112,7 @@ void Project::save_folder(QXmlStreamWriter& stream, int type, bool set_ids_only, stream.writeAttribute("in", QString::number(f->in)); stream.writeAttribute("out", QString::number(f->out)); stream.writeAttribute("speed", QString::number(f->speed)); - stream.writeAttribute("alphapremul", QString::number(f->alpha_is_premultiplied)); + stream.writeAttribute("alphapremul", QString::number(f->alpha_is_associated)); stream.writeAttribute("startnumber", QString::number(f->start_number)); stream.writeAttribute("proxy", QString::number(f->proxy)); diff --git a/project/footage.cpp b/project/footage.cpp index bde8c4c20..d2b36f442 100644 --- a/project/footage.cpp +++ b/project/footage.cpp @@ -27,17 +27,17 @@ #include "project/previewgenerator.h" #include "timeline/clip.h" -Footage::Footage() { - ready = (false); - preview_gen = (nullptr); - invalid = (false); - in = (0); - out = (0); - speed = (1.0); - alpha_is_premultiplied = (false); - proxy = (false); - start_number = 0; - +Footage::Footage() : + ready(false), + preview_gen(nullptr), + invalid(false), + in(0), + out(0), + speed(1.0), + alpha_is_associated(true), + proxy(false), + start_number(0) +{ ready_lock.lock(); } diff --git a/project/footage.h b/project/footage.h index 04ff51706..bc93cc4dc 100644 --- a/project/footage.h +++ b/project/footage.h @@ -81,9 +81,14 @@ struct Footage { bool ready; bool invalid; double speed; - bool alpha_is_premultiplied; + bool alpha_is_associated; int start_number; +#ifndef NO_OCIO + // color management + QString colorspace; +#endif + // proxy config bool proxy; QString proxy_path; diff --git a/project/loadthread.cpp b/project/loadthread.cpp index 2661f1d40..eeac43e03 100644 --- a/project/loadthread.cpp +++ b/project/loadthread.cpp @@ -280,7 +280,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { int folder = 0; MediaPtr item = std::make_shared(); - FootagePtr f(new Footage()); + FootagePtr f = std::make_shared(); f->using_inout = false; @@ -347,7 +347,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { } else if (attr.name() == "speed") { f->speed = attr.value().toDouble(); } else if (attr.name() == "alphapremul") { - f->alpha_is_premultiplied = (attr.value() == "1"); + f->alpha_is_associated = (attr.value() == "1"); } else if (attr.name() == "proxy") { f->proxy = (attr.value() == "1"); } else if (attr.name() == "proxypath") { diff --git a/rendering/renderfunctions.cpp b/rendering/renderfunctions.cpp index c2515e4a2..2751987c5 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -31,11 +31,6 @@ extern "C" { #include #include -#ifndef NO_OCIO -#include -namespace OCIO = OCIO_NAMESPACE; -#endif - #include "timeline/clip.h" #include "timeline/sequence.h" #include "project/media.h" @@ -80,6 +75,14 @@ GLfloat olive::rendering::flipped_blit_texcoords[] = { 1.0, 0.0 }; +#ifndef NO_OCIO +// copied from source code to OCIODisplay +const int OCIO_LUT3D_EDGE_SIZE = 32; + +// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE +const int OCIO_NUM_3D_ENTRIES = 98304; +#endif + void olive::rendering::Blit(QOpenGLShaderProgram* pipeline, bool flipped, QMatrix4x4 matrix) { QOpenGLVertexArrayObject m_vao; @@ -237,6 +240,7 @@ GLuint draw_clip(QOpenGLContext* ctx, const FramebufferObject& fbo, GLuint texture, bool clear) { + fbo.BindBuffer(); if (clear) { @@ -252,6 +256,7 @@ GLuint draw_clip(QOpenGLContext* ctx, fbo.ReleaseBuffer(); return fbo.texture(); + } void process_effect(QOpenGLContext* ctx, @@ -305,7 +310,7 @@ void process_effect(QOpenGLContext* ctx, } GLuint compose_sequence(ComposeSequenceParams ¶ms) { - GLuint final_fbo = params.main_buffer; + GLuint final_fbo = params.video ? params.main_buffer->buffer() : 0; Sequence* s = params.seq; long playhead = s->playhead; @@ -511,7 +516,7 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { } else if (c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - if (!c->media()->to_footage()->alpha_is_premultiplied) { + if (!c->media()->to_footage()->alpha_is_associated) { // alpha is not premultiplied, we'll need to multiply it for the rest of the pipeline params.ctx->functions()->glBlendFuncSeparate(GL_SRC_ALPHA, GL_ZERO, GL_ONE, GL_ZERO); @@ -525,27 +530,52 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { } #ifndef NO_OCIO - // convert to linear colorspace - if (olive::CurrentConfig.enable_color_management && params.ocio_shader != nullptr) + // Convert frame from source to linear colorspace + if (olive::CurrentConfig.enable_color_management) { - params.ctx->extraFunctions()->glActiveTexture(GL_TEXTURE2); - params.ctx->extraFunctions()->glBindTexture(GL_TEXTURE_3D, params.ocio_lut_texture); - params.ctx->extraFunctions()->glActiveTexture(GL_TEXTURE0); + if (c->ocio_shader == nullptr) { + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); - params.ocio_shader->bind(); + QString input_cs = OCIO::ROLE_SCENE_LINEAR; - params.ocio_shader->setUniformValue("tex2", 2); + if (c->media() != nullptr && c->media()->get_type() == MEDIA_TYPE_FOOTAGE) { - textureID = draw_clip(params.ctx, params.ocio_shader, c->fbo.at(fbo_switcher), textureID, true); + if (!c->media()->to_footage()->colorspace.isEmpty()) { - params.ocio_shader->release(); + input_cs = c->media()->to_footage()->colorspace; - params.ctx->extraFunctions()->glActiveTexture(GL_TEXTURE2); - params.ctx->extraFunctions()->glBindTexture(GL_TEXTURE_3D, 0); - params.ctx->extraFunctions()->glActiveTexture(GL_TEXTURE0); + } else { - fbo_switcher = !fbo_switcher; + // 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; + } + + } + + } + + try { + OCIO::ConstProcessorRcPtr processor = config->getProcessor(input_cs.toUtf8(), + OCIO::ROLE_SCENE_LINEAR); + + c->ocio_shader = olive::rendering::SetupOCIO(params.ctx, c->ocio_lut_texture, processor); + } catch (OCIO::Exception& e) { + qWarning() << e.what(); + } + } + + if (c->ocio_shader != nullptr) { + textureID = olive::rendering::OCIOBlit(c->ocio_shader.get(), + c->ocio_lut_texture, + c->fbo.at(fbo_switcher), + textureID); + + fbo_switcher = !fbo_switcher; + } } #endif @@ -640,9 +670,9 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { backend_tex_1 = params.nests.last()->fbo[1].texture(); backend_tex_2 = params.nests.last()->fbo[2].texture(); } else { - back_buffer_1 = params.backend_buffer1; - backend_tex_1 = params.backend_attachment1; - backend_tex_2 = params.backend_attachment2; + back_buffer_1 = params.backend_buffer1->buffer(); + backend_tex_1 = params.backend_buffer1->texture(); + backend_tex_2 = params.backend_buffer2->texture(); } // render a backbuffer @@ -743,7 +773,7 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { if (params.nests.size() > 0) { draw_clip(params.ctx, params.pipeline, params.nests.last()->fbo[2].buffer(), params.nests.last()->fbo[0].texture(), true); } else { - draw_clip(params.ctx, params.pipeline, params.backend_buffer2, params.main_attachment, true); + draw_clip(params.ctx, params.pipeline, params.backend_buffer2->buffer(), params.main_buffer->texture(), true); } } @@ -909,6 +939,100 @@ void close_active_clips(Sequence* s) { } } -void UpdateOCIOGLState(const ComposeSequenceParams& params) +#ifndef NO_OCIO +QOpenGLShaderProgramPtr olive::rendering::SetupOCIO(QOpenGLContext* ctx, + GLuint& lut_texture, + OCIO::ConstProcessorRcPtr processor) { + + QOpenGLExtraFunctions* xf = ctx->extraFunctions(); + + // Create LUT texture + xf->glGenTextures(1, &lut_texture); + + // Bind LUT + xf->glBindTexture(GL_TEXTURE_3D, lut_texture); + + // Set texture parameters + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + xf->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Allocate storage for texture + xf->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, + OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, + 0, GL_RGB,GL_FLOAT, nullptr); + + // + // SET UP GLSL SHADER + // + + OCIO::GpuShaderDesc shaderDesc; + shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); + shaderDesc.setFunctionName("OCIODisplay"); + shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); + + // + // COMPUTE 3D LUT + // + + GLfloat* ocio_lut_data = new GLfloat[OCIO_NUM_3D_ENTRIES]; + processor->getGpuLut3D(ocio_lut_data, shaderDesc); + + // Upload LUT data to texture + xf->glTexSubImage3D(GL_TEXTURE_3D, 0, + 0, 0, 0, + OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, + GL_RGB, GL_FLOAT, ocio_lut_data); + + delete [] ocio_lut_data; + + // Create OCIO shader code + QString shader_text(processor->getGpuShaderText(shaderDesc)); + shader_text.append("\n" + "uniform sampler3D tex2;\n" + "\n" + "vec4 process(vec4 col) {\n" + " return OCIODisplay(col, tex2);\n" + "}\n"); + + + // Get pipeline-based shader to inject OCIO shader into + QOpenGLShaderProgramPtr shader = olive::rendering::GetPipeline(shader_text); + + // Release LUT + xf->glBindTexture(GL_TEXTURE_3D, 0); + + return shader; } + +GLuint olive::rendering::OCIOBlit(QOpenGLShaderProgram *pipeline, + GLuint lut, + const FramebufferObject& fbo, + GLuint texture) +{ + QOpenGLContext* ctx = QOpenGLContext::currentContext(); + QOpenGLExtraFunctions* xf = ctx->extraFunctions(); + + xf->glActiveTexture(GL_TEXTURE2); + xf->glBindTexture(GL_TEXTURE_3D, lut); + xf->glActiveTexture(GL_TEXTURE0); + + pipeline->bind(); + + pipeline->setUniformValue("tex2", 2); + + //textureID = draw_clip(params.ctx, pipeline, c->fbo.at(fbo_switcher), textureID, true); + GLuint textureID = draw_clip(ctx, pipeline, fbo, texture, true); + + pipeline->release(); + + xf->glActiveTexture(GL_TEXTURE2); + xf->glBindTexture(GL_TEXTURE_3D, 0); + xf->glActiveTexture(GL_TEXTURE0); + + return textureID; +} +#endif diff --git a/rendering/renderfunctions.h b/rendering/renderfunctions.h index d9894eea2..6d81b5795 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -24,6 +24,10 @@ #include #include #include +#ifndef NO_OCIO +#include +namespace OCIO = OCIO_NAMESPACE; +#endif #include "timeline/sequence.h" #include "effects/effect.h" @@ -168,16 +172,7 @@ struct ComposeSequenceParams { * * When compose_sequence() is rendering the final image, this framebuffer will be bound. */ - GLuint main_buffer; - - /** - * @brief The attachment to the framebuffer in main_buffer - * - * Used only for video rendering. Never accessed with audio rendering. - * - * The OpenGL texture attached to the framebuffer referenced by main_buffer. - */ - GLuint main_attachment; + const FramebufferObject* main_buffer; /** * @brief Backend OpenGL framebuffer 1 used for further processing before rendering to main_buffer @@ -185,15 +180,7 @@ struct ComposeSequenceParams { * In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging" * between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose. */ - GLuint backend_buffer1; - - /** - * @brief Backend OpenGL framebuffer 1's texture attachment - * - * The texture that ComposeSequenceParams::backend_buffer1 renders to. Bound and drawn to - * ComposeSequenceParams::backend_buffer2 to "ping-pong" between them and various shaders. - */ - GLuint backend_attachment1; + const FramebufferObject* backend_buffer1; /** * @brief Backend OpenGL framebuffer 2 used for further processing before rendering to main_buffer @@ -201,25 +188,7 @@ struct ComposeSequenceParams { * In some situations, compose_sequence() will do some processing through shaders that requires "ping-ponging" * between framebuffers. backend_buffer1 and backend_buffer2 are used for this purpose. */ - GLuint backend_buffer2; - - /** - * @brief Backend OpenGL framebuffer 2's texture attachment - * - * The texture that ComposeSequenceParams::backend_buffer2 renders to. Bound and drawn to - * ComposeSequenceParams::backend_buffer1 to "ping-pong" between them and various shaders. - */ - GLuint backend_attachment2; - - /** - * @brief OpenGL shader containing OpenColorIO shader information - */ - QOpenGLShaderProgram* ocio_shader; - - /** - * @brief OpenGL texture containing LUT obtained form OpenColorIO - */ - GLuint ocio_lut_texture; + const FramebufferObject* backend_buffer2; }; /** @@ -417,6 +386,13 @@ namespace olive { extern GLfloat flipped_blit_texcoords[]; void Blit(QOpenGLShaderProgram* pipeline, bool flipped = false, QMatrix4x4 matrix = QMatrix4x4()); QOpenGLShaderProgramPtr GetPipeline(const QString &shader_code = QString()); +#ifndef NO_OCIO + QOpenGLShaderProgramPtr SetupOCIO(QOpenGLContext *ctx, GLuint &lut_texture, OCIO::ConstProcessorRcPtr processor); + GLuint OCIOBlit(QOpenGLShaderProgram *pipeline, + GLuint lut, + const FramebufferObject& fbo, + GLuint texture); +#endif } } diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index f0ad91e69..a4b0dc7ac 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -47,10 +47,13 @@ RenderThread::RenderThread() : tex_height(-1), queued(false), texture_failed(false), + #ifndef NO_OCIO ocio_lut_texture(0), ocio_shader(nullptr), + #endif running(true), - front_buffer_switcher(false) + front_buffer_switcher(false), + ocio_config_date(0) { surface.create(); } @@ -85,6 +88,9 @@ void RenderThread::run() { } // create any buffers that don't yet exist + if (!composite_buffer.IsCreated()) { + composite_buffer.Create(ctx, seq->width, seq->height); + } if (!front_buffer_1.IsCreated()) { front_buffer_1.Create(ctx, seq->width, seq->height); } @@ -113,9 +119,11 @@ void RenderThread::run() { } #ifndef NO_OCIO - // If there's no OpenColorIO shader, create it now + // 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_loaded_config != olive::CurrentConfig.ocio_config_path)) { + && (ocio_shader == nullptr || ocio_config_date != olive::CurrentRuntimeConfig.ocio_config_date)) { + ocio_config_date = olive::CurrentRuntimeConfig.ocio_config_date; + destroy_ocio(); set_up_ocio(); @@ -152,102 +160,43 @@ const GLuint &RenderThread::get_texture() #ifndef NO_OCIO void RenderThread::set_up_ocio() { - // Create LUT texture - ctx->extraFunctions()->glGenTextures(1, &ocio_lut_texture); - // Bind LUT - ctx->extraFunctions()->glBindTexture(GL_TEXTURE_3D, ocio_lut_texture); - - // Set texture parameters - ctx->extraFunctions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - ctx->extraFunctions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - ctx->extraFunctions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - ctx->extraFunctions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - ctx->extraFunctions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - // Allocate storage for texture - ctx->extraFunctions()->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, - OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, - 0, GL_RGB,GL_FLOAT, nullptr); - - OCIO::ConstConfigRcPtr config; - - // Set current config to the file specified in Config - if (QFileInfo::exists(olive::CurrentConfig.ocio_config_path)) { - - config = OCIO::Config::CreateFromFile(olive::CurrentConfig.ocio_config_path.toUtf8()); - OCIO::SetCurrentConfig(config); - ocio_loaded_config = olive::CurrentConfig.ocio_config_path; - - } else { - - config = OCIO::GetCurrentConfig(); + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + // Get current OCIO display from Config (or defaults if there is no setting) + QString display = olive::CurrentConfig.ocio_display; + if (display.isEmpty()) { + display = config->getDefaultDisplay(); } - const char* display = config->getDefaultDisplay(); - + QString view = olive::CurrentConfig.ocio_view; + if (view.isEmpty()) { + view = config->getDefaultView(display.toUtf8()); + } + // Get current display stats OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); transform->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR); - transform->setDisplay(display); - transform->setView(config->getDefaultView(display)); + transform->setDisplay(display.toUtf8()); + transform->setView(view.toUtf8()); - - OCIO::ConstProcessorRcPtr processor; - OCIO::GpuShaderDesc shaderDesc; - - // Get processor for this configuration + if (!olive::CurrentConfig.ocio_look.isEmpty()) { + transform->setLooksOverride(olive::CurrentConfig.ocio_look.toUtf8()); + transform->setLooksOverrideEnabled(true); + } try { - processor = config->getProcessor(transform); + // Using the current configuration, try to get an OCIO processor with a corresponding input and output colorspace + OCIO::ConstProcessorRcPtr processor = config->getProcessor(transform); + // Create a OCIO shader with this processor + ocio_shader = olive::rendering::SetupOCIO(ctx, ocio_lut_texture, processor); } catch(OCIO::Exception & e) { qCritical() << e.what(); return; } - - // - // SET UP GLSL SHADER - // - - shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); - shaderDesc.setFunctionName("OCIODisplay"); - shaderDesc.setLut3DEdgeLen(OCIO_LUT3D_EDGE_SIZE); - - // - // COMPUTE 3D LUT - // - - processor->getGpuLut3D(ocio_lut_data, shaderDesc); - - - // Upload LUT data to texture - ctx->extraFunctions()->glTexSubImage3D(GL_TEXTURE_3D, 0, - 0, 0, 0, - OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, OCIO_LUT3D_EDGE_SIZE, - GL_RGB, GL_FLOAT, ocio_lut_data); - - - // Create OCIO shader code - QString shader_text(processor->getGpuShaderText(shaderDesc)); - shader_text.append("\n" - "uniform sampler3D tex2;\n" - "\n" - "vec4 process(vec4 col) {\n" - " return OCIODisplay(col, tex2);\n" - "}\n"); - - - // Get pipeline-based shader to inject OCIO shader into - ocio_shader = olive::rendering::GetPipeline(shader_text); - - - // Release LUT - ctx->extraFunctions()->glBindTexture(GL_TEXTURE_3D, 0); - } void RenderThread::destroy_ocio() @@ -272,38 +221,43 @@ void RenderThread::paint() { params.playback_speed = 1; params.blend_mode_program = blend_mode_program.get(); params.pipeline = pipeline_program.get(); -#ifndef NO_OCIO - params.ocio_shader = ocio_shader.get(); - params.ocio_lut_texture = ocio_lut_texture; -#endif - params.backend_buffer1 = back_buffer_1.buffer(); - params.backend_buffer2 = back_buffer_2.buffer(); - params.backend_attachment1 = back_buffer_1.texture(); - params.backend_attachment2 = back_buffer_2.texture(); - params.main_buffer = front_buffer_switcher ? front_buffer_1.buffer() : front_buffer_2.buffer(); - params.main_attachment = front_buffer_switcher ? front_buffer_1.texture() : front_buffer_2.texture(); + params.backend_buffer1 = &back_buffer_1; + params.backend_buffer2 = &back_buffer_2; + params.main_buffer = &composite_buffer; // get currently selected gizmos gizmos = seq->GetSelectedGizmo(); params.gizmos = gizmos; + QOpenGLFunctions* f = ctx->functions(); + + f->glEnable(GL_BLEND); + + // bind composite framebuffer for drawing + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, composite_buffer.buffer()); + + // Clear framebuffer to nothing + f->glClearColor(0.0, 0.0, 0.0, 0.0); + f->glClear(GL_COLOR_BUFFER_BIT); + + // Compose the current frame + compose_sequence(params); + + // Copy composite buffer to front buffer + // First lock the appropriate mutex for exclusivity QMutex& active_mutex = front_buffer_switcher ? front_mutex1 : front_mutex2; active_mutex.lock(); - ctx->functions()->glEnable(GL_BLEND); - - // bind framebuffer for drawing - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, params.main_buffer); - - ctx->functions()->glClearColor(0.0, 0.0, 0.0, 0.0); - ctx->functions()->glClear(GL_COLOR_BUFFER_BIT); - - compose_sequence(params); + // Convert linear frame to display color space + olive::rendering::OCIOBlit(ocio_shader.get(), + ocio_lut_texture, + front_buffer_switcher ? front_buffer_1 : front_buffer_2, + composite_buffer.texture()); // flush changes - ctx->functions()->glFinish(); + f->glFinish(); - ctx->functions()->glDisable(GL_BLEND); + f->glDisable(GL_BLEND); texture_failed = params.texture_failed; @@ -314,11 +268,11 @@ void RenderThread::paint() { // texture failed, try again queued = true; } else { - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); QImage img(tex_width, tex_height, QImage::Format_RGBA8888); - ctx->functions()->glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); + f->glReadPixels(0, 0, tex_width, tex_height, GL_RGBA, GL_UNSIGNED_BYTE, img.bits()); img.save(save_fn); - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); save_fn = ""; } } @@ -326,25 +280,25 @@ void RenderThread::paint() { if (pixel_buffer != nullptr) { // set main framebuffer to the current read buffer - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, params.main_buffer); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, composite_buffer.buffer()); // store pixels in buffer - ctx->functions()->glReadPixels(0, - 0, - pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize, - tex_height, - GL_RGBA, - GL_UNSIGNED_BYTE, - pixel_buffer); + f->glReadPixels(0, + 0, + pixel_buffer_linesize == 0 ? tex_width : pixel_buffer_linesize, + tex_height, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixel_buffer); // release current read buffer - ctx->functions()->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); pixel_buffer = nullptr; } // release - ctx->functions()->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } void RenderThread::start_render(QOpenGLContext *share, @@ -390,6 +344,7 @@ void RenderThread::cancel() { } void RenderThread::delete_buffers() { + composite_buffer.Destroy(); front_buffer_1.Destroy(); front_buffer_2.Destroy(); back_buffer_1.Destroy(); diff --git a/rendering/renderthread.h b/rendering/renderthread.h index 29e2d5091..e959c98d4 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -34,14 +34,6 @@ #include "rendering/framebufferobject.h" #include "qopenglshaderprogramptr.h" -#ifndef NO_OCIO -// copied from source code to OCIODisplay -const int OCIO_LUT3D_EDGE_SIZE = 32; - -// copied from source code to OCIODisplay, expanded from 3*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE*LUT3D_EDGE_SIZE -const int OCIO_NUM_3D_ENTRIES = 98304; -#endif - class RenderThread : public QThread { Q_OBJECT public: @@ -80,10 +72,9 @@ private: void destroy_ocio(); // OpenColorIO variables - float ocio_lut_data[OCIO_NUM_3D_ENTRIES]; GLuint ocio_lut_texture; QOpenGLShaderProgramPtr ocio_shader; - QString ocio_loaded_config; + qint64 ocio_config_date; #endif FramebufferObject front_buffer_1; @@ -92,6 +83,8 @@ private: FramebufferObject front_buffer_2; QMutex front_mutex2; + FramebufferObject composite_buffer; + bool front_buffer_switcher; QWaitCondition wait_cond_; diff --git a/timeline/clip.cpp b/timeline/clip.cpp index 5c9ebbf07..8690cb6c5 100644 --- a/timeline/clip.cpp +++ b/timeline/clip.cpp @@ -516,6 +516,11 @@ void Clip::Close(bool wait) { // delete framebuffers fbo.clear(); +#ifndef NO_OCIO + // delete OCIO shader + ocio_shader = nullptr; +#endif + if (UsesCacher()) { cacher.Close(wait); } else { @@ -605,8 +610,8 @@ bool Clip::Retrieve() } texture->setData(QOpenGLTexture::RGBA, - QOpenGLTexture::UInt8, - const_cast(using_db_1 ? data_buffer_1 : data_buffer_2)); + QOpenGLTexture::UInt8, + const_cast(using_db_1 ? data_buffer_1 : data_buffer_2)); if (data_buffer_1 != frame->data[0]) { delete [] data_buffer_1; diff --git a/timeline/clip.h b/timeline/clip.h index ef47e623b..0d00cf09b 100644 --- a/timeline/clip.h +++ b/timeline/clip.h @@ -157,6 +157,11 @@ public: QOpenGLTexture* texture; long texture_frame; +#ifndef NO_OCIO + QOpenGLShaderProgramPtr ocio_shader; + GLuint ocio_lut_texture; +#endif + private: // timeline variables (should be copied in copy()) bool enabled_; diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 8b74ebc63..0b514fbad 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -273,6 +273,20 @@ 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 { + OCIO::SetCurrentConfig(OCIO::Config::CreateFromFile(olive::CurrentConfig.ocio_config_path.toUtf8())); + } catch (OCIO::Exception& e) { + QMessageBox::critical(this, + tr("OpenColorIO Config Error"), + tr("Failed to set OpenColorIO configuration: %1").arg(e.what()), + QMessageBox::Ok); + } + } +#endif + alloc_panels(this); // populate menu bars