diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index 78312b4c2..5625c7bca 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -82,11 +82,6 @@ PreferencesDialog::PreferencesDialog(QWidget *parent) : { setWindowTitle(tr("Preferences")); setup_ui(); - - accurateSeekButton->setChecked(!olive::CurrentConfig.fast_seeking); - fastSeekButton->setChecked(olive::CurrentConfig.fast_seeking); - recordingComboBox->setCurrentIndex(olive::CurrentConfig.recording_mode - 1); - imgSeqFormatEdit->setText(olive::CurrentConfig.img_seq_formats); } PreferencesDialog::~PreferencesDialog() {} @@ -184,6 +179,26 @@ void PreferencesDialog::save() { return; } + // Validate whether the OCIO config path exists + if (enable_color_management->isChecked() && !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; + } + // Check if any settings will require a restart of Olive if (olive::CurrentConfig.effect_textbox_lines != effect_textbox_lines_field->value() || olive::CurrentConfig.use_software_fallback != use_software_fallbacks_checkbox->isChecked() @@ -233,7 +248,6 @@ void PreferencesDialog::save() { olive::CurrentConfig.recording_mode = recordingComboBox->currentIndex() + 1; olive::CurrentConfig.img_seq_formats = imgSeqFormatEdit->text(); - olive::CurrentConfig.fast_seeking = fastSeekButton->isChecked(); olive::CurrentConfig.upcoming_queue_size = upcoming_queue_spinbox->value(); olive::CurrentConfig.upcoming_queue_type = upcoming_queue_type->currentIndex(); olive::CurrentConfig.previous_queue_size = previous_queue_spinbox->value(); @@ -248,6 +262,9 @@ void PreferencesDialog::save() { olive::CurrentConfig.use_software_fallback = use_software_fallbacks_checkbox->isChecked(); 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(); + 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 @@ -429,6 +446,14 @@ void PreferencesDialog::browse_css_file() { } } +void PreferencesDialog::browse_ocio_config() +{ + QString fn = QFileDialog::getOpenFileName(this, tr("Browse for OpenColorIO configuration")); + if (!fn.isEmpty()) { + ocio_config_file->setText(fn); + } +} + void PreferencesDialog::delete_all_previews() { if (QMessageBox::question(this, tr("Delete All Previews"), @@ -437,7 +462,8 @@ void PreferencesDialog::delete_all_previews() { delete_previews(1); QMessageBox::information(this, tr("Previews Deleted"), - tr("All previews deleted succesfully. You may have to re-open your current project for changes to take effect."), + tr("All previews deleted succesfully. You may have to re-open your current project for " + "changes to take effect."), QMessageBox::Ok); } } @@ -577,18 +603,6 @@ void PreferencesDialog::setup_ui() { QWidget* playback_tab = new QWidget(this); QVBoxLayout* playback_tab_layout = new QVBoxLayout(playback_tab); - // Playback -> Seeking - QGroupBox* seeking_group = new QGroupBox(playback_tab); - seeking_group->setTitle(tr("Seeking")); - QVBoxLayout* seeking_group_layout = new QVBoxLayout(seeking_group); - accurateSeekButton = new QRadioButton(seeking_group); - accurateSeekButton->setText(tr("Accurate Seeking\nAlways show the correct frame (visual may pause briefly as correct frame is retrieved)")); - seeking_group_layout->addWidget(accurateSeekButton); - fastSeekButton = new QRadioButton(seeking_group); - fastSeekButton->setText(tr("Fast Seeking\nSeek quickly (may briefly show inaccurate frames when seeking - doesn't affect playback/export)")); - seeking_group_layout->addWidget(fastSeekButton); - playback_tab_layout->addWidget(seeking_group); - // Playback -> Memory Usage QGroupBox* memory_usage_group = new QGroupBox(playback_tab); memory_usage_group->setTitle(tr("Memory Usage")); @@ -673,6 +687,34 @@ void PreferencesDialog::setup_ui() { tabWidget->addTab(audio_tab, tr("Audio")); + // + // COLOR MANAGEMENT + // + + QWidget* color_management_tab = new QWidget(); + + QGridLayout* color_management_layout = new QGridLayout(color_management_tab); + + row = 0; + + 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_layout->addWidget(new QLabel(tr("OpenColorIO Config File:")), row, 0); + + ocio_config_file = new QLineEdit(); + ocio_config_file->setText(olive::CurrentConfig.ocio_config_path); + 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); + + tabWidget->addTab(color_management_tab, tr("Color Management")); + // Shortcuts QWidget* shortcut_tab = new QWidget(this); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index 03d7bd4e5..bae006d5a 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -63,9 +63,12 @@ private slots: bool refine_shortcut_list(const QString &, QTreeWidgetItem* parent = nullptr); void load_shortcut_file(); void save_shortcut_file(); - void browse_css_file(); void delete_all_previews(); + // Browse for file functionns + void browse_css_file(); + void browse_ocio_config(); + private: void setup_ui(); void setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* parent); @@ -77,8 +80,6 @@ private: QLineEdit* custom_css_fn; QLineEdit* imgSeqFormatEdit; QComboBox* recordingComboBox; - QRadioButton* accurateSeekButton; - QRadioButton* fastSeekButton; QTreeWidget* keyboard_tree; QDoubleSpinBox* upcoming_queue_spinbox; QComboBox* upcoming_queue_type; @@ -93,6 +94,8 @@ private: QSpinBox* thumbnail_res_spinbox; QSpinBox* waveform_res_spinbox; QCheckBox* add_default_effects_to_clips; + QCheckBox* enable_color_management; + QLineEdit* ocio_config_file; QVector key_shortcut_actions; QVector key_shortcut_items; diff --git a/io/clipboard.cpp b/io/clipboard.cpp index cd5de7902..f3cf1bd29 100644 --- a/io/clipboard.cpp +++ b/io/clipboard.cpp @@ -29,6 +29,6 @@ QVector clipboard; QVector clipboard_transitions; void clear_clipboard() { - clipboard.clear(); - clipboard_transitions.clear(); + clipboard.clear(); + clipboard_transitions.clear(); } diff --git a/io/config.cpp b/io/config.cpp index 1490ddcbd..b561afabf 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -54,7 +54,6 @@ Config::Config() drop_on_media_to_replace(true), autoscroll(olive::AUTOSCROLL_PAGE_SCROLL), audio_rate(48000), - fast_seeking(false), hover_focus(false), project_view_type(olive::PROJECT_VIEW_TREE), set_name_with_marker(true), @@ -71,7 +70,8 @@ Config::Config() waveform_resolution(64), thumbnail_resolution(120), add_default_effects_to_clips(true), - invert_timeline_scroll_axes(true) + invert_timeline_scroll_axes(true), + enable_color_management(false) {} void Config::load(QString path) { @@ -148,9 +148,6 @@ void Config::load(QString path) { } else if (stream.name() == "AudioRate") { stream.readNext(); audio_rate = stream.text().toInt(); - } else if (stream.name() == "FastSeeking") { - stream.readNext(); - fast_seeking = (stream.text() == "1"); } else if (stream.name() == "HoverFocus") { stream.readNext(); hover_focus = (stream.text() == "1"); @@ -211,6 +208,12 @@ void Config::load(QString path) { } else if (stream.name() == "AddDefaultEffectsToClips") { stream.readNext(); add_default_effects_to_clips = (stream.text() == "1"); + } else if (stream.name() == "EnableColorManagement") { + stream.readNext(); + enable_color_management = (stream.text() == "1"); + } else if (stream.name() == "OCIOConfigPath") { + stream.readNext(); + ocio_config_path = stream.text().toString(); } } } @@ -257,7 +260,6 @@ void Config::save(QString path) { stream.writeTextElement("DropFileOnMediaToReplace", QString::number(drop_on_media_to_replace)); stream.writeTextElement("Autoscroll", QString::number(autoscroll)); stream.writeTextElement("AudioRate", QString::number(audio_rate)); - stream.writeTextElement("FastSeeking", QString::number(fast_seeking)); stream.writeTextElement("HoverFocus", QString::number(hover_focus)); stream.writeTextElement("ProjectViewType", QString::number(project_view_type)); stream.writeTextElement("SetNameWithMarker", QString::number(set_name_with_marker)); @@ -278,6 +280,8 @@ void Config::save(QString path) { stream.writeTextElement("ThumbnailResolution", QString::number(thumbnail_resolution)); stream.writeTextElement("WaveformResolution", QString::number(waveform_resolution)); stream.writeTextElement("AddDefaultEffectsToClips", QString::number(add_default_effects_to_clips)); + stream.writeTextElement("EnableColorManagement", QString::number(enable_color_management)); + stream.writeTextElement("OCIOConfigPath", ocio_config_path); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index c9fc41d92..bd5958749 100644 --- a/io/config.h +++ b/io/config.h @@ -315,16 +315,6 @@ struct Config { */ int audio_rate; - /** - * @brief Enable fast seeking - * - * Olive supports a seek mode that shows frames faster with the risk of briefly showing a "best-effort" frame that - * may not be the accurate frame at that point of the Timeline. This does not affect exporting. - * - * Set to **TRUE** if this mode should be enabled. - */ - bool fast_seeking; - /** * @brief Enable hover focus * @@ -517,6 +507,20 @@ struct Config { */ bool invert_timeline_scroll_axes; + /** + * @brief Enable color managemennt + * + * **TRUE** if color management through OpenColorIO should be enabled + */ + bool enable_color_management; + + /** + * @brief Path to OpenColorIO configuration file + * + * Used if Config::enable_color_management is **TRUE**. + */ + QString ocio_config_path; + /** * @brief Load config from file * diff --git a/rendering/renderfunctions.cpp b/rendering/renderfunctions.cpp index bbf0b8b79..f7fcd9b51 100644 --- a/rendering/renderfunctions.cpp +++ b/rendering/renderfunctions.cpp @@ -369,19 +369,27 @@ GLuint compose_sequence(ComposeSequenceParams ¶ms) { // 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); - textureID = draw_clip(c->fbo[0], textureID, true); + textureID = draw_clip(c->fbo[fbo_switcher], textureID, true); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - fbo_switcher = true; + fbo_switcher = !fbo_switcher; } #ifdef OLIVE_OCIO // convert to linear colorspace - bool linear_convert = true; - if (linear_convert) + if (olive::CurrentConfig.enable_color_management) { + params.ocio_shader->bind(); + params.ocio_shader->setUniformValue("tex1", 0); + params.ocio_shader->setUniformValue("tex2", 2); + + textureID = draw_clip(c->fbo[fbo_switcher], textureID, true); + + params.ocio_shader->release(); + + fbo_switcher = !fbo_switcher; } #endif @@ -718,3 +726,7 @@ void close_active_clips(SequencePtr s) { } } } + +void UpdateOCIOGLState(const ComposeSequenceParams& params) +{ +} diff --git a/rendering/renderfunctions.h b/rendering/renderfunctions.h index 1d7ed9dc5..a863197bb 100644 --- a/rendering/renderfunctions.h +++ b/rendering/renderfunctions.h @@ -390,4 +390,6 @@ int64_t playhead_to_timestamp(Clip *c, long playhead); */ void close_active_clips(SequencePtr s); +void UpdateOCIOGLState(const ComposeSequenceParams ¶ms); + #endif // RENDERFUNCTIONS_H diff --git a/rendering/renderthread.cpp b/rendering/renderthread.cpp index 8babc9edc..3002d4e47 100644 --- a/rendering/renderthread.cpp +++ b/rendering/renderthread.cpp @@ -22,9 +22,10 @@ #include #include -#include #include +#include #include + #ifdef OLIVE_OCIO #include namespace OCIO = OCIO_NAMESPACE; @@ -33,6 +34,7 @@ namespace OCIO = OCIO_NAMESPACE; #include "rendering/renderfunctions.h" #include "project/sequence.h" #include "project/effectloaders.h" +#include "io/config.h" RenderThread::RenderThread() : gizmos(nullptr), @@ -93,8 +95,8 @@ void RenderThread::run() { back_buffer_2.Create(ctx, seq->width, seq->height); } + // If there's no blending mode shader, create it now if (blend_mode_program == nullptr) { - // create shader program to make blending modes work delete_shaders(); blend_mode_program = new QOpenGLShaderProgram(); @@ -105,6 +107,14 @@ void RenderThread::run() { blend_mode_program->link(); } + // If there's no OpenColorIO shader, create it now + if (olive::CurrentConfig.enable_color_management + && (ocio_shader == nullptr || ocio_loaded_config != olive::CurrentConfig.ocio_config_path)) { + destroy_ocio(); + + set_up_ocio(); + } + // draw frame paint(); @@ -132,8 +142,117 @@ const GLuint &RenderThread::get_texture() return front_buffer_switcher ? front_buffer_2.texture() : front_buffer_1.texture(); } +const char * g_fragShaderText = "" +"\n" +"uniform sampler2D tex1;\n" +"uniform sampler3D tex2;\n" +"\n" +"void main()\n" +"{\n" +" vec4 col = texture2D(tex1, gl_TexCoord[0].st);\n" +" gl_FragColor = OCIODisplay(col, tex2);\n" +"}\n"; + void RenderThread::set_up_ocio() { + functions.initializeOpenGLFunctions(); + + // + // SETUP LUT TEXTURE + // + + // Create LUT texture + ctx->functions()->glGenTextures(1, &ocio_lut_texture); + + // Bind texture to GL_TEXTURE_3D and GL_TEXTURE2 + ctx->functions()->glActiveTexture(GL_TEXTURE2); + ctx->functions()->glBindTexture(GL_TEXTURE_3D, ocio_lut_texture); + + // Set texture parameters + ctx->functions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ctx->functions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ctx->functions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + ctx->functions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + ctx->functions()->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Allocate storage for texture + functions.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, ocio_lut_data); + + // + // SET UP OCIO DISPLAY + // + + 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(); + + } + + const char* display = config->getDefaultDisplay(); + + OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); + transform->setInputColorSpaceName(OCIO::ROLE_SCENE_LINEAR); + transform->setDisplay(display); + transform->setView(config->getDefaultView(display)); + + // + // GET OCIO PROCESSOR + // + + OCIO::ConstProcessorRcPtr processor = OCIO::GetCurrentConfig()->getProcessor(transform); + + // + // 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 + // + + processor->getGpuLut3D(ocio_lut_data, shaderDesc); + + glBindTexture(GL_TEXTURE_3D, ocio_lut_texture); + functions.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); + + QString shader_text = processor->getGpuShaderText(shaderDesc); + shader_text.append("\n"); + shader_text.append(g_fragShaderText); + + ocio_shader = new QOpenGLShaderProgram(); + ocio_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, shader_text); + ocio_shader->link(); + + // Reset active texture to 0 for the rest of the pipeline + ctx->functions()->glActiveTexture(GL_TEXTURE0); +} + +void RenderThread::destroy_ocio() +{ + // Destroy LUT texture + ctx->functions()->glDeleteTextures(1, &ocio_lut_texture); + ocio_lut_texture = 0; + + delete ocio_shader; + ocio_shader = nullptr; } void RenderThread::paint() { @@ -147,6 +266,7 @@ void RenderThread::paint() { params.wait_for_mutexes = true; params.playback_speed = 1; params.blend_mode_program = blend_mode_program; + params.ocio_shader = ocio_shader; params.backend_buffer1 = back_buffer_1.buffer(); params.backend_buffer2 = back_buffer_2.buffer(); params.backend_attachment1 = back_buffer_1.texture(); @@ -279,6 +399,8 @@ void RenderThread::delete_ctx() { if (ctx != nullptr) { delete_shaders(); delete_buffers(); + + destroy_ocio(); } delete ctx; diff --git a/rendering/renderthread.h b/rendering/renderthread.h index 7f2c6cdcd..25aee6ddf 100644 --- a/rendering/renderthread.h +++ b/rendering/renderthread.h @@ -28,16 +28,17 @@ #include #include #include +#include #include "project/sequence.h" #include "project/effect.h" #include "rendering/framebufferobject.h" // copied from source code to OCIODisplay -const int LUT3D_EDGE_SIZE = 32; +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 NUM_3D_ENTRIES = 98304; +const int OCIO_NUM_3D_ENTRIES = 98304; class RenderThread : public QThread { Q_OBJECT @@ -96,9 +97,11 @@ private: FramebufferObject back_buffer_1; FramebufferObject back_buffer_2; - float ocio_lut_data[NUM_3D_ENTRIES]; + // OpenColorIO variables + float ocio_lut_data[OCIO_NUM_3D_ENTRIES]; GLuint ocio_lut_texture; QOpenGLShaderProgram* ocio_shader; + QString ocio_loaded_config; SequencePtr seq; int divider; @@ -110,6 +113,8 @@ private: QString save_fn; GLvoid *pixel_buffer; int pixel_buffer_linesize; + + QOpenGLFunctions_2_0 functions; }; #endif // RENDERTHREAD_H