diff --git a/CMakeLists.txt b/CMakeLists.txt index e06a3bd0ab..e02b1ad196 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -705,13 +705,13 @@ if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() -find_package(OCIO) -set_package_properties(OCIO PROPERTIES +find_package(OpenColorIO) +set_package_properties(OpenColorIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "https://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") -macro_bool_to_01(OCIO_FOUND HAVE_OCIO) +macro_bool_to_01(OpenColorIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" diff --git a/krita/data/shaders/highq_downscale.frag b/krita/data/shaders/highq_downscale.frag index bb2a47c2a3..73c95ebfa8 100644 --- a/krita/data/shaders/highq_downscale.frag +++ b/krita/data/shaders/highq_downscale.frag @@ -3,10 +3,6 @@ */ uniform sampler2D texture0; -#ifdef USE_OCIO -uniform sampler3D texture1; -#endif - in vec4 v_textureCoordinate; out vec4 fragColor; @@ -119,7 +115,7 @@ void main() { } #ifdef USE_OCIO - fragColor = OCIODisplay(col, texture1); + fragColor = OCIODisplay(col); #else /* USE_OCIO */ fragColor = col; #endif /* USE_OCIO */ diff --git a/libs/ui/canvas/kis_display_filter.h b/libs/ui/canvas/kis_display_filter.h index a2ae5ab584..e96506d892 100644 --- a/libs/ui/canvas/kis_display_filter.h +++ b/libs/ui/canvas/kis_display_filter.h @@ -26,6 +26,9 @@ struct KisExposureGammaCorrectionInterface; +class QOpenGLFunctions; +class QOpenGLShaderProgram; + /** * @brief The KisDisplayFilter class is the base class for filters that * are applied by the canvas to the projection before displaying. @@ -37,7 +40,7 @@ public: explicit KisDisplayFilter(QObject *parent = 0); virtual QString program() const = 0; - virtual GLuint lutTexture() const = 0; + virtual void setupTextures(QOpenGLFunctions *f, QOpenGLShaderProgram *program) const = 0; virtual void filter(quint8 *pixels, quint32 numPixels) = 0; virtual void approximateInverseTransformation(quint8 *pixels, quint32 numPixels) = 0; virtual void approximateForwardTransformation(quint8 *pixels, quint32 numPixels) = 0; diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index 69bd84000f..50acab76bf 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -947,9 +947,7 @@ void KisOpenGLCanvas2::drawImage(const QRect &updateRect) } if (d->displayFilter) { - glActiveTexture(GL_TEXTURE0 + 1); - glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); - d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); + d->displayFilter->setupTextures(this, d->displayShader); } glActiveTexture(GL_TEXTURE0); diff --git a/libs/ui/opengl/kis_opengl_shader_loader.cpp b/libs/ui/opengl/kis_opengl_shader_loader.cpp index 8513617815..75a4f08ba3 100644 --- a/libs/ui/opengl/kis_opengl_shader_loader.cpp +++ b/libs/ui/opengl/kis_opengl_shader_loader.cpp @@ -150,6 +150,7 @@ KisShaderProgram *KisOpenGLShaderLoader::loadDisplayShader(QSharedPointerprogram().isEmpty(); if (haveDisplayFilter) { fragHeader.append("#define USE_OCIO\n"); + fragHeader.append("#define USE_OCIO_V2\n"); fragHeader.append(displayFilter->program().toLatin1()); } diff --git a/plugins/dockers/lut/CMakeLists.txt b/plugins/dockers/lut/CMakeLists.txt index 01844be146..795c99162d 100644 --- a/plugins/dockers/lut/CMakeLists.txt +++ b/plugins/dockers/lut/CMakeLists.txt @@ -17,5 +17,5 @@ ki18n_wrap_ui(KRITA_LUTDOCKER_SOURCES add_library(kritalutdocker MODULE ${KRITA_LUTDOCKER_SOURCES}) -target_link_libraries(kritalutdocker kritaui ${Boost_SYSTEM_LIBRARY} ${OCIO_LIBRARIES}) +target_link_libraries(kritalutdocker kritaui ${Boost_SYSTEM_LIBRARY} OpenColorIO::OpenColorIO) install(TARGETS kritalutdocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/dockers/lut/lutdocker_dock.cpp b/plugins/dockers/lut/lutdocker_dock.cpp index c7e68dce06..3c57144c9d 100644 --- a/plugins/dockers/lut/lutdocker_dock.cpp +++ b/plugins/dockers/lut/lutdocker_dock.cpp @@ -56,12 +56,12 @@ #include #include "kis_signals_blocker.h" #include "krita_utils.h" +#include -#include "ocio_display_filter.h" -#include "black_white_point_chooser.h" -#include "KisOcioConfiguration.h" #include +#include "black_white_point_chooser.h" + OCIO::ConstConfigRcPtr defaultRawProfile() { @@ -231,6 +231,8 @@ void LutDockerDock::slotUpdateIcons() { m_btnConvertCurrentColor->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_btmShowBWConfiguration->setIcon(KisIconUtils::loadIcon("properties")); + m_lblOcioVersion->setText(OCIO_VERSION_FULL_STR); + m_lblOcioVersion->setEnabled(false); } void LutDockerDock::slotShowBWConfiguration() diff --git a/plugins/dockers/lut/ocio_display_filter.cpp b/plugins/dockers/lut/ocio_display_filter.cpp index 33afb01f1e..e429e3c2f4 100644 --- a/plugins/dockers/lut/ocio_display_filter.cpp +++ b/plugins/dockers/lut/ocio_display_filter.cpp @@ -1,38 +1,29 @@ /* - * Copyright (c) 2012 Boudewijn Rempt + * SPDX-FileCopyrightText: 2012 Boudewijn Rempt + * SPDX-FileCopyrightText: 2021 L. E. Segovia * - * 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 2 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * SPDX-License-Identifier: GPL-2.0-or-later */ + #include "ocio_display_filter.h" -#include -#include + +#include +#include +#include +#include +#include + #include -#include #include -#include -#include -#include #include - +#include #include -#include -#include -#include -#include -#include + +#if defined(QT_OPENGL_ES_2) +#define GL_RGBA32F_ARB GL_RGBA32F_EXT +#define GL_RGB32F_ARB GL_RGB32F_EXT +#endif OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent) : KisDisplayFilter(parent) @@ -42,7 +33,8 @@ OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interf , look(0) , swizzle(RGBA) , m_interface(interface) - , m_lut3dTexID(0) + , m_lut3dTexIDs() + , m_lut3dUniforms() , m_shaderDirty(true) { } @@ -51,7 +43,7 @@ OcioDisplayFilter::~OcioDisplayFilter() { } -KisExposureGammaCorrectionInterface* OcioDisplayFilter::correctionInterface() const +KisExposureGammaCorrectionInterface *OcioDisplayFilter::correctionInterface() const { return m_interface; } @@ -60,8 +52,8 @@ void OcioDisplayFilter::filter(quint8 *pixels, quint32 numPixels) { // processes that data _in_ place if (m_processor) { - OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); - m_processor->apply(img); + OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); + m_processor->getDefaultCPUProcessor()->apply(img); } } @@ -69,8 +61,8 @@ void OcioDisplayFilter::approximateInverseTransformation(quint8 *pixels, quint32 { // processes that data _in_ place if (m_revereseApproximationProcessor) { - OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); - m_revereseApproximationProcessor->apply(img); + OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); + m_revereseApproximationProcessor->getDefaultCPUProcessor()->apply(img); } } @@ -78,8 +70,8 @@ void OcioDisplayFilter::approximateForwardTransformation(quint8 *pixels, quint32 { // processes that data _in_ place if (m_forwardApproximationProcessor) { - OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); - m_forwardApproximationProcessor->apply(img); + OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); + m_forwardApproximationProcessor->getDefaultCPUProcessor()->apply(img); } } @@ -103,11 +95,6 @@ QString OcioDisplayFilter::program() const return m_program; } -GLuint OcioDisplayFilter::lutTexture() const -{ - return m_lut3dTexID; -} - void OcioDisplayFilter::updateProcessor() { if (!config) { @@ -126,18 +113,22 @@ void OcioDisplayFilter::updateProcessor() inputColorSpaceName = config->getColorSpaceNameByIndex(0); } if (!look) { - look = config->getLookNameByIndex(0); + look = config->getLookNameByIndex(0); } if (!displayDevice || !view || !inputColorSpaceName) { return; } - OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); - transform->setInputColorSpaceName(inputColorSpaceName); + OCIO::DisplayViewTransformRcPtr transform = OCIO::DisplayViewTransform::Create(); + transform->setSrc(inputColorSpaceName); transform->setDisplay(displayDevice); transform->setView(view); + OCIO::LegacyViewingPipelineRcPtr vpt = OCIO::LegacyViewingPipeline::Create(); + + vpt->setDisplayViewTransform(transform); + /** * Look support: * As the OCIO docs will tell you, looks are a aesthetic transform that is @@ -155,36 +146,37 @@ void OcioDisplayFilter::updateProcessor() * override is all we can offer. */ if (config->getLook(look)) { - transform->setLooksOverride(look); - transform->setLooksOverrideEnabled(true); + vpt->setLooksOverride(look); + vpt->setLooksOverrideEnabled(true); } OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create(); // fstop exposure control -- not sure how that translates to our exposure { - float exposureGain = powf(2.0f, exposure); + const double exposureGain = pow(2.0, exposure); - const qreal minRange = 0.001; + const double minRange = 0.001; if (qAbs(blackPoint - whitePoint) < minRange) { whitePoint = blackPoint + minRange; } - const float oldMin[] = { blackPoint, blackPoint, blackPoint, 0.0f }; - const float oldMax[] = { whitePoint, whitePoint, whitePoint, 1.0f }; + const double oldMin[] = {blackPoint, blackPoint, blackPoint, 0.0}; + const double oldMax[] = {whitePoint, whitePoint, whitePoint, 1.0}; - const float newMin[] = { 0.0f, 0.0f, 0.0f, 0.0f }; - const float newMax[] = { exposureGain, exposureGain, exposureGain, 1.0f }; + const double newMin[] = {0.0, 0.0, 0.0, 0.0}; + const double newMax[] = {exposureGain, exposureGain, exposureGain, 1.0}; - float m44[16]; - float offset4[4]; + double m44[16]; + double offset4[4]; OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax); - OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); - mtx->setValue(m44, offset4); - transform->setLinearCC(mtx); + OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); + mtx->setMatrix(m44); + mtx->setOffset(offset4); + vpt->setLinearCC(mtx); // approximation (no color correction); - approximateTransform->push_back(mtx); + approximateTransform->appendTransform(mtx); } // channel swizzle @@ -226,32 +218,39 @@ void OcioDisplayFilter::updateProcessor() channelHot[1] = 0; channelHot[2] = 0; channelHot[3] = 1; - default: - ; + default:; } - float lumacoef[3]; + double lumacoef[3]; config->getDefaultLumaCoefs(lumacoef); - float m44[16]; - float offset[4]; + double m44[16]; + double offset[4]; OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef); OCIO::MatrixTransformRcPtr swizzleTransform = OCIO::MatrixTransform::Create(); - swizzleTransform->setValue(m44, offset); - transform->setChannelView(swizzleTransform); + swizzleTransform->setMatrix(m44); + swizzleTransform->setOffset(offset); + vpt->setChannelView(swizzleTransform); } // Post-display transform gamma { - float exponent = 1.0f/std::max(1e-6f, static_cast(gamma)); - const float exponent4f[] = { exponent, exponent, exponent, exponent }; - OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); + double exponent = 1.0 / std::max(1e-6, gamma); + const double exponent4f[] = {exponent, exponent, exponent, exponent}; + OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); expTransform->setValue(exponent4f); - transform->setDisplayCC(expTransform); + vpt->setDisplayCC(expTransform); // approximation (no color correction); - approximateTransform->push_back(expTransform); + approximateTransform->appendTransform(expTransform); } - m_processor = config->getProcessor(transform); + try { + m_processor = vpt->getProcessor(config, config->getCurrentContext()); + } catch (OCIO::Exception &e) { + // XXX: How to not break the OCIO shader now? + errKrita << "OCIO exception while parsing the current context:" << e.what(); + m_shaderDirty = false; + return; + } m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD); @@ -259,7 +258,7 @@ void OcioDisplayFilter::updateProcessor() m_revereseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE); } catch (...) { warnKrita << "OCIO inverted matrix does not exist!"; - //m_revereseApproximationProcessor; + // m_revereseApproximationProcessor; } m_shaderDirty = true; @@ -272,16 +271,19 @@ bool OcioDisplayFilter::updateShader() if (f) { return updateShaderImpl(f); } +#if defined(QT_OPENGL_3) } else if (KisOpenGL::hasOpenGL3()) { QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions(); if (f) { return updateShaderImpl(f); } +#endif } // XXX This option can be removed once we move to Qt 5.7+ if (KisOpenGL::supportsLoD()) { -#ifdef Q_OS_MAC +#if defined(QT_OPENGL_3) +#if defined(Q_OS_MAC) && defined(QT_OPENGL_3_2) QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions(); #else QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions(); @@ -289,23 +291,29 @@ bool OcioDisplayFilter::updateShader() if (f) { return updateShaderImpl(f); } +#endif } - QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions(); +#if !defined(QT_OPENGL_ES_2) + QOpenGLFunctions_2_0 *f = QOpenGLContext::currentContext()->versionFunctions(); if (f) { return updateShaderImpl(f); } +#endif return false; } -template -bool OcioDisplayFilter::updateShaderImpl(F *f) { +template +bool OcioDisplayFilter::updateShaderImpl(F *f) +{ // check whether we are allowed to use shaders -- though that should // work for everyone these days KisConfig cfg(true); - if (!cfg.useOpenGL()) return false; + if (!cfg.useOpenGL()) + return false; - if (!m_shaderDirty) return false; + if (!m_shaderDirty) + return false; if (!f) { qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!"; @@ -316,71 +324,272 @@ bool OcioDisplayFilter::updateShaderImpl(F *f) { bool shouldRecompileShader = false; - const int lut3DEdgeSize = cfg.ocioLutEdgeSize(); + // Step 1: Create a GPU Shader Description + OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc(); - if (m_lut3d.size() == 0) { - //dbgKrita << "generating lut"; - f->glGenTextures(1, &m_lut3dTexID); +#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 + if (KisOpenGL::supportsLoD()) { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0); + } else { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_1_0); + } +#else + if (KisOpenGL::supportsLoD()) { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); + } else { + shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_2); + } +#endif - int num3Dentries = 3 * lut3DEdgeSize * lut3DEdgeSize * lut3DEdgeSize; - m_lut3d.fill(0.0, num3Dentries); + shaderDesc->setFunctionName("OCIODisplay"); + shaderDesc->setResourcePrefix("ocio_"); - f->glActiveTexture(GL_TEXTURE1); - f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); + // Step 2: Compute the 3D LUT +#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 + // ensure the new GPU pipeline is used with our GLES3 patch + // this way users won't run into errors when using Angle along with OCIO + const auto gpu = m_processor->getOptimizedGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT); +#else + const int lut3DEdgeSize = cfg.ocioLutEdgeSize(); + const auto gpu = + m_processor->getOptimizedLegacyGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT, lut3DEdgeSize); +#endif - f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, - lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, - 0, GL_RGB, GL_FLOAT, &m_lut3d.constData()[0]); + gpu->extractGpuShaderInfo(shaderDesc); + + // OCIO v2 assumes you'll use the OglApp helpers + // these are unusable from a Qt backend, because they rely on GLUT/GLFW + // ociodisplay original pipeline: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/508b3f4a0618435aeed2f45058208bdfa99e0887/src/apps/ociodisplay/main.cpp + // ociodisplay new pipeline is a single call: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/ffddc3341f5775c7866fe2c93275e1d5e0b0540f/src/apps/ociodisplay/main.cpp#L427 + // we need to replicate this loop: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/oglapp.cpp#L191-L223 + // calls functions from here: + // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/glsl.cpp + + for (const auto &tex : m_lut3dTexIDs) { + f->glDeleteTextures(1, &tex.m_uid); } - // Step 1: Create a GPU Shader Description - OCIO::GpuShaderDesc shaderDesc; + m_lut3dTexIDs.clear(); - if (KisOpenGL::supportsLoD()) { - shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); - } - else { - shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); + // This is the first available index for the textures. + unsigned currIndex = 1; + + // Process the 3D LUT first. + + const unsigned maxTexture3D = shaderDesc->getNum3DTextures(); + for (unsigned idx = 0; idx < maxTexture3D; ++idx) { + // 1. Get the information of the 3D LUT. + + const char *textureName = nullptr; + const char *samplerName = nullptr; + unsigned edgelen = 0; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + shaderDesc->get3DTexture(idx, textureName, samplerName, edgelen, interpolation); + + if (!textureName || !*textureName || !samplerName || !*samplerName || edgelen == 0) { + errOpenGL << "The texture data is corrupted"; + return false; + } + + const float *values = nullptr; + shaderDesc->get3DTextureValues(idx, values); + if (!values) { + errOpenGL << "The texture values are missing"; + return false; + } + + // 2. Allocate the 3D LUT. + + unsigned texId = 0; + { + if (values == nullptr) { + errOpenGL << "3D LUT" << idx << "Missing texture data"; + return false; + } + + f->glGenTextures(1, &texId); + + f->glActiveTexture(GL_TEXTURE0 + currIndex); + + f->glBindTexture(GL_TEXTURE_3D, texId); + + { + if (interpolation == OCIO::INTERP_NEAREST) { + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F_ARB, edgelen, edgelen, edgelen, 0, GL_RGB, GL_FLOAT, values); + } + + // 3. Keep the texture id & name for the later enabling. + + m_lut3dTexIDs.push_back({texId, textureName, samplerName, GL_TEXTURE_3D}); + + currIndex++; } + // Process the 1D LUTs. - shaderDesc.setFunctionName("OCIODisplay"); - shaderDesc.setLut3DEdgeLen(lut3DEdgeSize); + const unsigned maxTexture2D = shaderDesc->getNumTextures(); + for (unsigned idx = 0; idx < maxTexture2D; ++idx) { + // 1. Get the information of the 1D LUT. + const char *textureName = nullptr; + const char *samplerName = nullptr; + unsigned width = 0; + unsigned height = 0; + OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL; + OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; + shaderDesc->getTexture(idx, textureName, samplerName, width, height, channel, interpolation); - // Step 2: Compute the 3D LUT - QString lut3dCacheID = QString::fromLatin1(m_processor->getGpuLut3DCacheID(shaderDesc)); - if (lut3dCacheID != m_lut3dcacheid) { - //dbgKrita << "Computing 3DLut " << m_lut3dcacheid; - m_lut3dcacheid = lut3dCacheID; - m_processor->getGpuLut3D(&m_lut3d[0], shaderDesc); - - f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); - f->glTexSubImage3D(GL_TEXTURE_3D, 0, - 0, 0, 0, - lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, - GL_RGB, GL_FLOAT, &m_lut3d[0]); + if (!textureName || !*textureName || !samplerName || !*samplerName || width == 0) { + errOpenGL << "The texture data is corrupted"; + return false; + } + + const float *values = nullptr; + shaderDesc->getTextureValues(idx, values); + if (!values) { + errOpenGL << "The texture values are missing"; + return false; + } + + // 2. Allocate the 1D LUT (a 2D texture is needed to hold large LUTs). + + unsigned texId = 0; + { + if (values == nullptr) { + errOpenGL << "1D LUT" << idx << "Missing texture data."; + return false; + } + + unsigned internalformat = GL_RGB32F_ARB; + unsigned format = GL_RGB; + + if (channel == OCIO::GpuShaderCreator::TEXTURE_RED_CHANNEL) { + internalformat = GL_R32F; + format = GL_RED; + } + + f->glGenTextures(1, &texId); + + f->glActiveTexture(GL_TEXTURE0 + currIndex); + +#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 +#else + // 1D Textures are unsupported by OpenGL ES. + // https://github.com/AcademySoftwareFoundation/OpenColorIO/issues/1486 + if (height > 1) { +#endif + f->glBindTexture(GL_TEXTURE_2D, texId); + + { + if (interpolation == OCIO::INTERP_NEAREST) { + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } else { + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + } + + f->glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, GL_FLOAT, values); +#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 +#else + } else { + errOpenGL << "1D texture detected @" << idx << ", not supported by OpenGLES"; + return false; + } +#endif + } + + // 3. Keep the texture id & name for the later enabling. + + unsigned type = GL_TEXTURE_2D; + m_lut3dTexIDs.push_back({texId, textureName, samplerName, type}); + currIndex++; } // Step 3: Generate the shader text - QString shaderCacheID = QString::fromLatin1(m_processor->getGpuShaderTextCacheID(shaderDesc)); + QString shaderCacheID = QString::fromLatin1(shaderDesc->getCacheID()); if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) { - //dbgKrita << "Computing Shader " << m_shadercacheid; + // dbgKrita << "Computing Shader " << m_shadercacheid; m_shadercacheid = shaderCacheID; - std::ostringstream os; - os << m_processor->getGpuShaderText(shaderDesc) << "\n"; - - m_program = QString::fromLatin1(os.str().c_str()); + m_program = QString::fromLatin1("%1\n").arg(shaderDesc->getShaderText()); shouldRecompileShader = true; } + // Step 4: mirror and bind uniforms + m_lut3dUniforms.clear(); + + const unsigned maxUniforms = shaderDesc->getNumUniforms(); + for (unsigned idx = 0; idx < maxUniforms; ++idx) { + OCIO::GpuShaderDesc::UniformData data; + const char *name = shaderDesc->getUniform(idx, data); + if (data.m_type == OCIO::UNIFORM_UNKNOWN) { + errOpenGL << "Uniform" << idx << "has an unknown type"; + return false; + } + // Transfer uniform. + m_lut3dUniforms.push_back({name, data}); + } + m_shaderDirty = false; return shouldRecompileShader; } + +void OcioDisplayFilter::setupTextures(QOpenGLFunctions *f, QOpenGLShaderProgram *program) const +{ + for (unsigned int idx = 0; idx < m_lut3dTexIDs.size(); ++idx) { + const auto &data = m_lut3dTexIDs[idx]; + f->glActiveTexture(GL_TEXTURE0 + 1 + idx); + f->glBindTexture(data.m_type, data.m_uid); + program->setUniformValue(program->uniformLocation(data.m_samplerName), GLint(1 + idx)); + } + + for (const KisTextureUniform &uniform : m_lut3dUniforms) { + const int m_handle = program->uniformLocation(uniform.m_name); + + const OCIO::GpuShaderDesc::UniformData &m_data = uniform.m_data; + + // Update value. + if (m_data.m_getDouble) { + program->setUniformValue(m_handle, static_cast(m_data.m_getDouble())); + } else if (m_data.m_getBool) { + program->setUniformValue(m_handle, static_cast(m_data.m_getBool() ? 1.0f : 0.0f)); + } else if (m_data.m_getFloat3) { + program->setUniformValue(m_handle, + m_data.m_getFloat3()[0], + m_data.m_getFloat3()[1], + m_data.m_getFloat3()[2]); + } else if (m_data.m_vectorFloat.m_getSize && m_data.m_vectorFloat.m_getVector) { + program->setUniformValueArray(m_handle, + m_data.m_vectorFloat.m_getVector(), + m_data.m_vectorFloat.m_getSize(), + 1); + } else if (m_data.m_vectorInt.m_getSize && m_data.m_vectorInt.m_getVector) { + program->setUniformValueArray(m_handle, m_data.m_vectorInt.m_getVector(), m_data.m_vectorInt.m_getSize()); + } else { + errOpenGL << "Uniform" << uniform.m_name << "is not linked to any value"; + continue; + } + } +} diff --git a/plugins/dockers/lut/ocio_display_filter.h b/plugins/dockers/lut/ocio_display_filter.h index 48d896a4c0..963fcf5f93 100644 --- a/plugins/dockers/lut/ocio_display_filter.h +++ b/plugins/dockers/lut/ocio_display_filter.h @@ -1,38 +1,56 @@ /* - * Copyright (c) 2012 Boudewijn Rempt + * SPDX-FileCopyrightText: 2012 Boudewijn Rempt + * SPDX-FileCopyrightText: 2021 L. E. Segovia * - * 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 2 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * SPDX-License-Identifier: GPL-2.0-or-later */ + #ifndef OCIO_DISPLAY_FILTER_H #define OCIO_DISPLAY_FILTER_H +#include + +#include + +#include +#include +#include + #include -#include -#include -#include -#include "kis_exposure_gamma_correction_interface.h" +#include namespace OCIO = OCIO_NAMESPACE; -enum OCIO_CHANNEL_SWIZZLE { - LUMINANCE, - RGBA, - R, - G, - B, - A +enum OCIO_CHANNEL_SWIZZLE { LUMINANCE, RGBA, R, G, B, A }; + +struct KisTextureUniform { +public: + KisTextureUniform(const QString &name, const OCIO::GpuShaderDesc::UniformData &data) + : m_name(name) + , m_data(data) + { + } + + QString m_name; + OCIO::GpuShaderDesc::UniformData m_data; + +private: + KisTextureUniform() = delete; +}; + +struct KisTextureEntry { + unsigned m_uid = -1; + QString m_textureName; + QString m_samplerName; + unsigned m_type = -1; + + KisTextureEntry(unsigned uid, const QString &textureName, const QString &samplerName, unsigned type) + : m_uid(uid) + , m_textureName(textureName) + , m_samplerName(samplerName) + , m_type(type) + { + } }; class OcioDisplayFilter : public KisDisplayFilter @@ -42,21 +60,23 @@ public: explicit OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent = 0); ~OcioDisplayFilter(); - void filter(quint8 *pixels, quint32 numPixels); - void approximateInverseTransformation(quint8 *pixels, quint32 numPixels); - void approximateForwardTransformation(quint8 *pixels, quint32 numPixels); - bool useInternalColorManagement() const; - bool lockCurrentColorVisualRepresentation() const; + void filter(quint8 *pixels, quint32 numPixels) override; + void approximateInverseTransformation(quint8 *pixels, quint32 numPixels) override; + void approximateForwardTransformation(quint8 *pixels, quint32 numPixels) override; + bool useInternalColorManagement() const override; + bool lockCurrentColorVisualRepresentation() const override; void setLockCurrentColorVisualRepresentation(bool value); - bool updateShader(); - template + bool updateShader() override; + + template bool updateShaderImpl(F *f); - KisExposureGammaCorrectionInterface *correctionInterface() const; + void setupTextures(QOpenGLFunctions *f, QOpenGLShaderProgram *program) const override; - virtual QString program() const; - GLuint lutTexture() const; + KisExposureGammaCorrectionInterface *correctionInterface() const override; + + QString program() const override; void updateProcessor(); @@ -67,14 +87,13 @@ public: const char *view; const char *look; OCIO_CHANNEL_SWIZZLE swizzle; - float exposure; - float gamma; - float blackPoint; - float whitePoint; + double exposure; + double gamma; + double blackPoint; + double whitePoint; bool forceInternalColorManagement; private: - OCIO::ConstProcessorRcPtr m_processor; OCIO::ConstProcessorRcPtr m_revereseApproximationProcessor; OCIO::ConstProcessorRcPtr m_forwardApproximationProcessor; @@ -84,10 +103,10 @@ private: bool m_lockCurrentColorVisualRepresentation; QString m_program; - GLuint m_lut3dTexID; - QVector m_lut3d; + std::vector m_lut3dTexIDs; QString m_lut3dcacheid; QString m_shadercacheid; + std::vector m_lut3dUniforms; bool m_shaderDirty; }; diff --git a/plugins/dockers/lut/tests/CMakeLists.txt b/plugins/dockers/lut/tests/CMakeLists.txt index 85cd5ecbc6..8891b8b683 100644 --- a/plugins/dockers/lut/tests/CMakeLists.txt +++ b/plugins/dockers/lut/tests/CMakeLists.txt @@ -11,5 +11,5 @@ krita_add_broken_unit_test(kis_ocio_display_filter_test.cpp ../ocio_display_filter.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp TEST_NAME KisOcioDisplayFilterTest - LINK_LIBRARIES kritaui ${OCIO_LIBRARIES} KF5::I18n Qt5::Test + LINK_LIBRARIES kritaui OpenColorIO::OpenColorIO KF5::I18n Qt5::Test NAME_PREFIX "plugins-dockers-lut-") diff --git a/plugins/dockers/lut/wdglut.ui b/plugins/dockers/lut/wdglut.ui index 2ba3c2b073..403e5c26a3 100644 --- a/plugins/dockers/lut/wdglut.ui +++ b/plugins/dockers/lut/wdglut.ui @@ -7,7 +7,7 @@ 0 0 357 - 286 + 328 @@ -338,7 +338,7 @@ - + 2 @@ -348,6 +348,13 @@ 2 + + + + TextLabel + + + diff --git a/cmake/modules/FindOpenColorIO.cmake b/cmake/modules/FindOpenColorIO.cmake new file mode 100644 index 0000000000..2a9e9310b1 --- /dev/null +++ b/cmake/modules/FindOpenColorIO.cmake @@ -0,0 +1,104 @@ +# Module to find OpenColorIO +# +# This module will first look into the directories hinted by the variables: +# - OpenColorIO_ROOT +# +# This module defines the following variables: +# +# OPENCOLORIO_FOUND - True if OpenColorIO was found. +# OPENCOLORIO_INCLUDES - where to find OpenColorIO.h +# OPENCOLORIO_LIBRARIES - list of libraries to link against when using OpenColorIO +# OPENCOLORIO_DEFINITIONS - Definitions needed when using OpenColorIO +# +# SPDX-FileCopyrightText: 2008 Contributors to the OpenImageIO project +# SPDX-FileCopyrightText: 2021 L. E. Segovia +# SPDX-License-Identifier: BSD-3-Clause + +include (FindPackageHandleStandardArgs) +include (FindPackageMessage) + +find_path (OPENCOLORIO_INCLUDE_DIR + OpenColorIO.h + HINTS + ${OPENCOLORIO_INCLUDE_PATH} + ENV OPENCOLORIO_INCLUDE_PATH + PATHS + /sw/include + /opt/local/include + PATH_SUFFIXES OpenColorIO OpenColorIO1 + DOC "The directory where OpenColorIO.h resides") + +if (EXISTS "${OPENCOLORIO_INCLUDE_DIR}/OpenColorABI.h") + # Search twice, because this symbol changed between OCIO 1.x and 2.x + file(STRINGS "${OPENCOLORIO_INCLUDE_DIR}/OpenColorABI.h" TMP + REGEX "^#define OCIO_VERSION_STR[ \t].*$") + if (NOT TMP) + file(STRINGS "${OPENCOLORIO_INCLUDE_DIR}/OpenColorABI.h" TMP + REGEX "^#define OCIO_VERSION[ \t].*$") + endif () + string (REGEX MATCHALL "([0-9]+)\\.([0-9]+)\\.[0-9]+" OPENCOLORIO_VERSION ${TMP}) + set (OPENCOLORIO_VERSION_MAJOR ${CMAKE_MATCH_1}) + set (OPENCOLORIO_VERSION_MINOR ${CMAKE_MATCH_2}) +endif () + +find_library (OPENCOLORIO_LIBRARY + NAMES + OpenColorIO + OpenColorIO_${OPENCOLORIO_VERSION_MAJOR}_${OPENCOLORIO_VERSION_MINOR} + OpenColorIO${OPENCOLORIO_VERSION_MAJOR} + HINTS + ${OPENCOLORIO_LIBRARY_PATH} + ENV OPENCOLORIO_LIBRARY_PATH + PATHS + /usr/lib64 + /usr/local/lib64 + /sw/lib + /opt/local/lib + DOC "The OCIO library") + +find_package_handle_standard_args (OpenColorIO + REQUIRED_VARS OPENCOLORIO_INCLUDE_DIR OPENCOLORIO_LIBRARY + FOUND_VAR OPENCOLORIO_FOUND + VERSION_VAR OPENCOLORIO_VERSION + ) + +if (OpenColorIO_FOUND) + set (OPENCOLORIO_INCLUDES ${OPENCOLORIO_INCLUDE_DIR}) + set (OPENCOLORIO_LIBRARIES ${OPENCOLORIO_LIBRARY}) + set (OPENCOLORIO_DEFINITIONS "") + if (NOT TARGET OpenColorIO::OpenColorIO) + add_library(OpenColorIO::OpenColorIO UNKNOWN IMPORTED) + set_target_properties(OpenColorIO::OpenColorIO PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPENCOLORIO_INCLUDES}") + + set_property(TARGET OpenColorIO::OpenColorIO APPEND PROPERTY + IMPORTED_LOCATION "${OPENCOLORIO_LIBRARIES}") + if (LINKSTATIC) + target_compile_definitions(OpenColorIO::OpenColorIO + INTERFACE "-DOpenColorIO_STATIC") + endif() + endif () + if (NOT TARGET OpenColorIO::OpenColorIOHeaders) + add_library(OpenColorIO::OpenColorIOHeaders INTERFACE IMPORTED) + set_target_properties(OpenColorIO::OpenColorIOHeaders PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${OPENCOLORIO_INCLUDES}") + endif () +endif () + +if (OpenColorIO_FOUND AND LINKSTATIC) + # Is this necessary? + set (OPENCOLORIO_DEFINITIONS "-DOpenColorIO_STATIC") + find_library (TINYXML_LIBRARY NAMES tinyxml) + if (TINYXML_LIBRARY) + set (OPENCOLORIO_LIBRARIES "${OPENCOLORIO_LIBRARIES};${TINYXML_LIBRARY}" CACHE STRING "" FORCE) + endif () + find_library (YAML_LIBRARY NAMES yaml-cpp) + if (YAML_LIBRARY) + set (OPENCOLORIO_LIBRARIES "${OPENCOLORIO_LIBRARIES};${YAML_LIBRARY}" CACHE STRING "" FORCE) + endif () + find_library (LCMS2_LIBRARY NAMES lcms2) + if (LCMS2_LIBRARY) + set (OPENCOLORIO_LIBRARIES "${OPENCOLORIO_LIBRARIES};${LCMS2_LIBRARY}" CACHE STRING "" FORCE) + endif () +endif () +