diff --git a/app/node/color/CMakeLists.txt b/app/node/color/CMakeLists.txt index afaa401ff..ca29ad8db 100644 --- a/app/node/color/CMakeLists.txt +++ b/app/node/color/CMakeLists.txt @@ -15,6 +15,8 @@ # along with this program. If not, see . add_subdirectory(colormanager) +add_subdirectory(displaytransform) +add_subdirectory(ociobase) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/color/displaytransform/CMakeLists.txt b/app/node/color/displaytransform/CMakeLists.txt new file mode 100644 index 000000000..4bede1600 --- /dev/null +++ b/app/node/color/displaytransform/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/color/displaytransform/displaytransform.cpp + node/color/displaytransform/displaytransform.h + PARENT_SCOPE +) diff --git a/app/node/color/displaytransform/displaytransform.cpp b/app/node/color/displaytransform/displaytransform.cpp new file mode 100644 index 000000000..dcaed76d0 --- /dev/null +++ b/app/node/color/displaytransform/displaytransform.cpp @@ -0,0 +1,108 @@ +/*** + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +***/ + +#include "displaytransform.h" + +#include "node/project/project.h" +#include "render/colorprocessor.h" + +namespace olive { + +const QString DisplayTransformNode::kTextureInput = QStringLiteral("tex_in"); +const QString DisplayTransformNode::kDirectionInput = QStringLiteral("dir_in"); + +DisplayTransformNode::DisplayTransformNode() +{ + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + AddInput(kDirectionInput, NodeValue::kCombo, 0); + + GenerateProcessor(true); +} + +void DisplayTransformNode::GenerateProcessor(bool direction) +{ + if (!project()) { + return; + } + reference_to_display_ = ColorProcessor::Create(project()->color_manager(), project()->color_manager()->GetReferenceColorSpace(), + project()->color_manager()->GetDefaultDisplay()); + + // Create shader description + shader_desc_ = OCIO::GpuShaderDesc::CreateShaderDesc(); + shader_desc_->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); + shader_desc_->setFunctionName("DisplayTransform"); + shader_desc_->setResourcePrefix("ocio_"); + + // Generate shader + reference_to_display_->GetProcessor()->getDefaultGPUProcessor()->extractGpuShaderInfo(shader_desc_); +} + +Node *DisplayTransformNode::copy() const +{ + return new DisplayTransformNode(); +} + +QString DisplayTransformNode::Name() const +{ + return tr("Display Transform"); +} + +QString DisplayTransformNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.disaplaytransform"); +} + +QVector DisplayTransformNode::Category() const +{ + return {kCategoryColor}; +} + +QString DisplayTransformNode::Description() const +{ + return tr("Converts an image to/from display space"); +} + +void DisplayTransformNode::Retranslate() +{ + SetInputName(kTextureInput, tr("Input")); + SetInputName(kDirectionInput, tr("Direction")); + SetComboBoxStrings(kDirectionInput, {tr("Forward"), tr("Inverse")}); + GenerateProcessor(true); +} + +ShaderCode DisplayTransformNode::GetShaderCode(const QString &shader_id) const +{ + Q_UNUSED(shader_id) + // Generate shader code using OCIO stub and our auto-generated name + QString shader_frag = FileFunctions::ReadFileAsString(QStringLiteral(":shaders/displaytransform.frag")) + .arg(shader_desc_->getShaderText()); + return ShaderCode(shader_frag); +} + +void DisplayTransformNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + job.InsertValue(value); + + renderer()->ShaderJobInsertTextures(reference_to_display_, &job, shader_desc_); + + // If there's no texture, no need to run an operation + if (!job.GetValue(kTextureInput).data().isNull()) { + table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + } +} + +} diff --git a/app/node/color/displaytransform/displaytransform.h b/app/node/color/displaytransform/displaytransform.h new file mode 100644 index 000000000..4975d49b7 --- /dev/null +++ b/app/node/color/displaytransform/displaytransform.h @@ -0,0 +1,54 @@ +/*** + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +***/ + +#ifndef DISPLAYTRANSFORMNODE_H +#define DISPLAYTRANSFORMNODE_H + +#include "node/color/ociobase/ociobase.h" + +namespace olive { +class DisplayTransformNode : public OCIONodeBase { + Q_OBJECT + public: + DisplayTransformNode(); + + NODE_DEFAULT_DESTRUCTOR(DisplayTransformNode) + + void GenerateProcessor(bool direction); + + virtual Node *copy() const override; + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual void Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTextureInput; + static const QString kDirectionInput; + + const QString shader_text_; + + ColorProcessorPtr reference_to_display_; + OCIO::GpuShaderDescRcPtr shader_desc_; +}; + +} // olive + +#endif diff --git a/app/node/color/ociobase/CMakeLists.txt b/app/node/color/ociobase/CMakeLists.txt new file mode 100644 index 000000000..fa00411a4 --- /dev/null +++ b/app/node/color/ociobase/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/color/ociobase/ociobase.cpp + node/color/ociobase/ociobase.h + PARENT_SCOPE +) diff --git a/app/node/color/ociobase/ociobase.cpp b/app/node/color/ociobase/ociobase.cpp new file mode 100644 index 000000000..2c4e2fdcc --- /dev/null +++ b/app/node/color/ociobase/ociobase.cpp @@ -0,0 +1,35 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "ociobase.h" + +#include "render/opengl/openglrenderer.h" + +namespace olive { + +OCIONodeBase::OCIONodeBase() +{ + if (true){//RenderManager::instance()->backend() == RenderManager::kOpenGL) { + // Create OpenGL renderer + attached_renderer_ = new OpenGLRenderer(this); + } +} + +} diff --git a/app/node/color/ociobase/ociobase.h b/app/node/color/ociobase/ociobase.h new file mode 100644 index 000000000..e74a97cb0 --- /dev/null +++ b/app/node/color/ociobase/ociobase.h @@ -0,0 +1,51 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef OCIOBASE_H +#define OCIOBASE_H + +#include "node/node.h" +#include "render/rendermanager.h" + +namespace olive { + +class OCIONodeBase : public Node +{ +public: + OCIONodeBase(); + + NODE_DEFAULT_DESTRUCTOR(OCIONodeBase); + +protected: + Renderer* renderer() const + { + return attached_renderer_; + } + + /** + * @brief Renderer abstraction + */ + Renderer* attached_renderer_; +}; + + +} // olive + +#endif // OCIOBASE_H diff --git a/app/node/factory.cpp b/app/node/factory.cpp index ed4645a60..ef6d262f0 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -29,6 +29,7 @@ #include "block/subtitle/subtitle.h" #include "block/transition/crossdissolve/crossdissolvetransition.h" #include "block/transition/diptocolor/diptocolortransition.h" +#include "color/displaytransform/displaytransform.h" #include "distort/crop/cropdistortnode.h" #include "distort/flip/flipdistortnode.h" #include "distort/transform/transformdistortnode.h" @@ -261,6 +262,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new FlipDistortNode(); case kNoiseGenerator: return new NoiseGeneratorNode(); + case kDisplayTransform: + return new DisplayTransformNode(); case kInternalNodeCount: break; diff --git a/app/node/factory.h b/app/node/factory.h index c91895ff3..e7cca6a1e 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -65,6 +65,7 @@ public: kOpacityEffect, kFlipDistort, kNoiseGenerator, + kDisplayTransform, // Count value kInternalNodeCount diff --git a/app/render/renderer.cpp b/app/render/renderer.cpp index a3701c2ff..ef04d964f 100644 --- a/app/render/renderer.cpp +++ b/app/render/renderer.cpp @@ -62,6 +62,23 @@ void Renderer::BlitColorManaged(ColorProcessorPtr color_processor, TexturePtr so BlitColorManagedInternal(color_processor, source, source_alpha_association, nullptr, params, clear_destination, matrix, crop_matrix); } +void Renderer::ShaderJobInsertTextures(ColorProcessorPtr color_processor, ShaderJob* job, + OCIO::GpuShaderDescRcPtr shader_desc) { + ColorContext color_ctx; + if (!GetCustomColorContext(color_processor, &color_ctx, shader_desc)) { + return; + } + + foreach (const ColorContext::LUT& l, color_ctx.lut3d_textures) { + job->InsertValue(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job->SetInterpolation(l.name, l.interpolation); + } + foreach (const ColorContext::LUT& l, color_ctx.lut1d_textures) { + job->InsertValue(l.name, NodeValue(NodeValue::kTexture, QVariant::fromValue(l.texture))); + job->SetInterpolation(l.name, l.interpolation); + } +} + TexturePtr Renderer::InterlaceTexture(TexturePtr top, TexturePtr bottom, const VideoParams ¶ms) { color_cache_mutex_.lock(); @@ -103,6 +120,95 @@ TexturePtr Renderer::CreateTextureFromNativeHandle(const QVariant &v, const Vide return std::make_shared(this, v, params, type); } +bool Renderer::GetCustomColorContext(ColorProcessorPtr color_processor, Renderer::ColorContext* ctx, + OCIO::GpuShaderDescRcPtr shader_desc) { + QMutexLocker locker(&color_cache_mutex_); + + ColorContext& color_ctx = *ctx; + + if (color_cache_.contains(color_processor->id())) { + color_ctx = color_cache_.value(color_processor->id()); + return true; + } else { + if (SetupColorContextTextures(color_ctx, shader_desc, color_processor)) { + return true; + } else { + qCritical() << "Failed to allocate OCIO shader textures"; + return false; + } + } +} + +bool Renderer::SetupColorContextTextures(ColorContext& color_ctx, OCIO::ConstGpuShaderDescRcPtr shader_desc, + ColorProcessorPtr color_processor) { + color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures()); + for (unsigned int i = 0; i < shader_desc->getNum3DTextures(); i++) { + const char* tex_name = nullptr; + const char* sampler_name = nullptr; + unsigned int edge_len = 0; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + + shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len, interpolation); + + if (!tex_name || !*tex_name || !sampler_name || !*sampler_name || !edge_len) { + qCritical() << "3D LUT texture data is corrupted"; + return false; + } + + const float* values = nullptr; + shader_desc->get3DTextureValues(i, values); + if (!values) { + qCritical() << "3D LUT texture values are missing"; + return false; + } + + // Allocate 3D LUT + color_ctx.lut3d_textures[i].texture = CreateTexture( + VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), + Texture::k3D, values); + color_ctx.lut3d_textures[i].name = sampler_name; + color_ctx.lut3d_textures[i].interpolation = + (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; + } + + color_ctx.lut1d_textures.resize(shader_desc->getNumTextures()); + for (unsigned int i = 0; i < shader_desc->getNumTextures(); i++) { + const char* tex_name = nullptr; + const char* sampler_name = nullptr; + unsigned int width = 0, height = 0; + OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + + shader_desc->getTexture(i, tex_name, sampler_name, width, height, channel, interpolation); + + if (!tex_name || !*tex_name || !sampler_name || !*sampler_name || !width) { + qCritical() << "1D LUT texture data is corrupted"; + return false; + } + + const float* values = nullptr; + shader_desc->getTextureValues(i, values); + if (!values) { + qCritical() << "1D LUT texture values are missing"; + return false; + } + + // Allocate 1D LUT + color_ctx.lut1d_textures[i].texture = CreateTexture( + VideoParams(width, height, VideoParams::kFormatFloat32, + (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), + Texture::k2D, values); + color_ctx.lut1d_textures[i].name = sampler_name; + color_ctx.lut1d_textures[i].interpolation = + (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; + } + + color_cache_.insert(color_processor->id(), color_ctx); + + return true; + +} + bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::ColorContext *ctx) { QMutexLocker locker(&color_cache_mutex_); @@ -137,71 +243,12 @@ bool Renderer::GetColorContext(ColorProcessorPtr color_processor, Renderer::Colo return false; } - color_ctx.lut3d_textures.resize(shader_desc->getNum3DTextures()); - for (unsigned int i=0; igetNum3DTextures(); i++) { - const char* tex_name = nullptr; - const char* sampler_name = nullptr; - unsigned int edge_len = 0; - OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; - - shader_desc->get3DTexture(i, tex_name, sampler_name, edge_len, interpolation); - - if (!tex_name || !*tex_name - || !sampler_name || !*sampler_name - || !edge_len) { - qCritical() << "3D LUT texture data is corrupted"; - return false; - } - - const float* values = nullptr; - shader_desc->get3DTextureValues(i, values); - if (!values) { - qCritical() << "3D LUT texture values are missing"; - return false; - } - - // Allocate 3D LUT - color_ctx.lut3d_textures[i].texture = CreateTexture(VideoParams(edge_len, edge_len, edge_len, VideoParams::kFormatFloat32, VideoParams::kRGBChannelCount), - Texture::k3D, values); - color_ctx.lut3d_textures[i].name = sampler_name; - color_ctx.lut3d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; + if (SetupColorContextTextures(color_ctx, shader_desc, color_processor)) { + return true; + } else { + qCritical() << "Failed to allocate OCIO shader textures"; + return false; } - - color_ctx.lut1d_textures.resize(shader_desc->getNumTextures()); - for (unsigned int i=0; igetNumTextures(); i++) { - const char* tex_name = nullptr; - const char* sampler_name = nullptr; - unsigned int width = 0, height = 0; - OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL; - OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; - - shader_desc->getTexture(i, tex_name, sampler_name, width, height, channel, interpolation); - - if (!tex_name || !*tex_name - || !sampler_name || !*sampler_name - || !width) { - qCritical() << "1D LUT texture data is corrupted"; - return false; - } - - const float* values = nullptr; - shader_desc->getTextureValues(i, values); - if (!values) { - qCritical() << "1D LUT texture values are missing"; - return false; - } - - // Allocate 1D LUT - color_ctx.lut1d_textures[i].texture = CreateTexture(VideoParams(width, height, VideoParams::kFormatFloat32, (channel == OCIO::GpuShaderDesc::TEXTURE_RED_CHANNEL) ? 1 : VideoParams::kRGBChannelCount), - Texture::k2D, - values); - color_ctx.lut1d_textures[i].name = sampler_name; - color_ctx.lut1d_textures[i].interpolation = (interpolation == OCIO::INTERP_NEAREST) ? Texture::kNearest : Texture::kLinear; - } - - color_cache_.insert(color_processor->id(), color_ctx); - - return true; } } diff --git a/app/render/renderer.h b/app/render/renderer.h index 977fc98ce..ef41dfa93 100644 --- a/app/render/renderer.h +++ b/app/render/renderer.h @@ -63,6 +63,8 @@ public: Blit(shader, job, nullptr, params, clear_destination); } + void ShaderJobInsertTextures(ColorProcessorPtr color_processor, ShaderJob* job, OCIO::GpuShaderDescRcPtr shader_desc); + enum AlphaAssociated { kAlphaNone, kAlphaUnassociated, @@ -128,6 +130,12 @@ private: bool GetColorContext(ColorProcessorPtr color_processor, ColorContext* ctx); + bool GetCustomColorContext(ColorProcessorPtr color_processor, ColorContext* ctx, + OCIO::GpuShaderDescRcPtr shader_desc); + + bool SetupColorContextTextures(ColorContext& color_ctx, OCIO::ConstGpuShaderDescRcPtr shader_desc, + ColorProcessorPtr color_processor); + void BlitColorManagedInternal(ColorProcessorPtr color_processor, TexturePtr source, AlphaAssociated source_alpha_association, Texture* destination, VideoParams params, bool clear_destination, diff --git a/app/shaders/displaytransform.frag b/app/shaders/displaytransform.frag new file mode 100644 index 000000000..e42554cf7 --- /dev/null +++ b/app/shaders/displaytransform.frag @@ -0,0 +1,12 @@ +uniform sampler2D tex_in; + +// Program will replace this with OCIO's auto-generated shader code +%1 + +void main() { + col = texture2D(tex_in, ove_texcoord); + + col = DIsplayTransform(col) + + gl_FragColor - col; +}