Warning, file /graphics/krita/plugins/dockers/lut/ocio_display_filter_vfx2021.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "ocio_display_filter_vfx2021.h"
0009 
0010 #include <QMessageBox>
0011 #include <QOpenGLContext>
0012 #include <QOpenGLExtraFunctions>
0013 #include <QOpenGLFunctions_2_0>
0014 #include <QOpenGLFunctions_3_0>
0015 #include <QOpenGLFunctions_3_2_Core>
0016 
0017 #include <cmath>
0018 #include <cstring>
0019 
0020 #include <kis_config.h>
0021 #include <kis_debug.h>
0022 #include <opengl/kis_opengl.h>
0023 
0024 #if defined(QT_OPENGL_ES_2)
0025 #define GL_RGBA32F_ARB GL_RGBA32F_EXT
0026 #define GL_RGB32F_ARB GL_RGB32F_EXT
0027 #endif
0028 
0029 #if defined(QT_OPENGL_ES_2) && !defined(QT_OPENGL_ES_3)
0030 #define GL_R32F GL_R32F_EXT
0031 #define GL_RED GL_RED_EXT
0032 #define GL_TEXTURE_WRAP_R GL_TEXTURE_WRAP_R_OES
0033 #endif
0034 
0035 #include "kis_context_thread_locale.h"
0036 
0037 OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent)
0038     : KisDisplayFilter(parent)
0039     , inputColorSpaceName(0)
0040     , displayDevice(0)
0041     , view(0)
0042     , look(0)
0043     , swizzle(RGBA)
0044     , m_interface(interface)
0045     , m_lut3dTexIDs()
0046     , m_lut3dUniforms()
0047     , m_shaderDirty(true)
0048 {
0049 }
0050 
0051 OcioDisplayFilter::~OcioDisplayFilter()
0052 {
0053 }
0054 
0055 KisExposureGammaCorrectionInterface *OcioDisplayFilter::correctionInterface() const
0056 {
0057     return m_interface;
0058 }
0059 
0060 void OcioDisplayFilter::filter(quint8 *pixels, quint32 numPixels)
0061 {
0062     // processes that data _in_ place
0063     if (m_processor) {
0064         if (numPixels > 16) {
0065             // creation of PackedImageDesc is really slow on Windows due to malloc/free
0066             OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4);
0067             m_processorCPU->apply(img);
0068         } else {
0069             for (quint32 i = 0; i < numPixels; i++) {
0070                 m_processorCPU->applyRGBA(reinterpret_cast<float*>(pixels));
0071                 pixels+=4;
0072             }
0073         }
0074     }
0075 }
0076 
0077 void OcioDisplayFilter::approximateInverseTransformation(quint8 *pixels, quint32 numPixels)
0078 {
0079     // processes that data _in_ place
0080     if (m_reverseApproximationProcessor) {
0081         if (numPixels > 16) {
0082             // creation of PackedImageDesc is really slow on Windows due to malloc/free
0083             OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4);
0084             m_reverseApproximationProcessorCPU->apply(img);
0085         } else {
0086             for (quint32 i = 0; i < numPixels; i++) {
0087                 m_reverseApproximationProcessorCPU->applyRGBA(reinterpret_cast<float*>(pixels));
0088                 pixels+=4;
0089             }
0090         }
0091     }
0092 }
0093 
0094 void OcioDisplayFilter::approximateForwardTransformation(quint8 *pixels, quint32 numPixels)
0095 {
0096     // processes that data _in_ place
0097     if (m_forwardApproximationProcessor) {
0098         if (numPixels > 16) {
0099             // creation of PackedImageDesc is really slow on Windows due to malloc/free
0100             OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4);
0101             m_forwardApproximationProcessorCPU->apply(img);
0102         } else {
0103             for (quint32 i = 0; i < numPixels; i++) {
0104                 m_forwardApproximationProcessorCPU->applyRGBA(reinterpret_cast<float*>(pixels));
0105                 pixels+=4;
0106             }
0107         }
0108     }
0109 }
0110 
0111 bool OcioDisplayFilter::useInternalColorManagement() const
0112 {
0113     return forceInternalColorManagement;
0114 }
0115 
0116 bool OcioDisplayFilter::lockCurrentColorVisualRepresentation() const
0117 {
0118     return m_lockCurrentColorVisualRepresentation;
0119 }
0120 
0121 void OcioDisplayFilter::setLockCurrentColorVisualRepresentation(bool value)
0122 {
0123     m_lockCurrentColorVisualRepresentation = value;
0124 }
0125 
0126 QString OcioDisplayFilter::program() const
0127 {
0128     return m_program;
0129 }
0130 
0131 void OcioDisplayFilter::updateProcessor()
0132 {
0133     if (!config) {
0134         return;
0135     }
0136 
0137     if (!displayDevice) {
0138         displayDevice = config->getDefaultDisplay();
0139     }
0140 
0141     if (!view) {
0142         view = config->getDefaultView(displayDevice);
0143     }
0144 
0145     if (!inputColorSpaceName) {
0146         inputColorSpaceName = config->getColorSpaceNameByIndex(0);
0147     }
0148     if (!look) {
0149         look = config->getLookNameByIndex(0);
0150     }
0151 
0152     if (!displayDevice || !view || !inputColorSpaceName) {
0153         return;
0154     }
0155 
0156     OCIO::DisplayViewTransformRcPtr transform = OCIO::DisplayViewTransform::Create();
0157     transform->setSrc(inputColorSpaceName);
0158     transform->setDisplay(displayDevice);
0159     transform->setView(view);
0160 
0161     OCIO::LegacyViewingPipelineRcPtr vpt = OCIO::LegacyViewingPipeline::Create();
0162 
0163     vpt->setDisplayViewTransform(transform);
0164 
0165     /**
0166      * Look support:
0167      * As the OCIO docs will tell you, looks are a aesthetic transform that is
0168      * added onto the mix.
0169      * A view+display can have it's own assigned Look, or list of looks, and these
0170      * can be overridden optionally.
0171      * What the OCIO docs won't tell you is that a display transform won't use the
0172      * looks attached to it unless "skipColorSpaceConversions" is false...
0173      * I have no idea what "skipColorSpaceConversions" is beyond what it says on the
0174      * tin. It is not mentioned in the documentation anywhere. Or on the website.
0175      * Or how to set it. Or unset it. Why it is apparently set true to begin with.
0176      * Only that, apparently, this was done with non-color data in mind...
0177      *
0178      * Until there's clear documentation on how to use this feature, I am afraid the
0179      * override is all we can offer.
0180      */
0181     if (config->getLook(look)) {
0182         vpt->setLooksOverride(look);
0183         vpt->setLooksOverrideEnabled(true);
0184     }
0185 
0186     OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create();
0187 
0188     // fstop exposure control -- not sure how that translates to our exposure
0189     {
0190         const double exposureGain = pow(2.0, exposure);
0191 
0192         const double minRange = 0.001;
0193         if (qAbs(blackPoint - whitePoint) < minRange) {
0194             whitePoint = blackPoint + minRange;
0195         }
0196 
0197         const double oldMin[] = {blackPoint, blackPoint, blackPoint, 0.0};
0198         const double oldMax[] = {whitePoint, whitePoint, whitePoint, 1.0};
0199 
0200         const double newMin[] = {0.0, 0.0, 0.0, 0.0};
0201         const double newMax[] = {exposureGain, exposureGain, exposureGain, 1.0};
0202 
0203         double m44[16];
0204         double offset4[4];
0205         OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax);
0206         OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create();
0207         mtx->setMatrix(m44);
0208         mtx->setOffset(offset4);
0209         vpt->setLinearCC(mtx);
0210 
0211         // approximation (no color correction);
0212         approximateTransform->appendTransform(mtx);
0213     }
0214 
0215     // channel swizzle
0216     {
0217         int channelHot[4];
0218         switch (swizzle) {
0219         case LUMINANCE:
0220             channelHot[0] = 1;
0221             channelHot[1] = 1;
0222             channelHot[2] = 1;
0223             channelHot[3] = 0;
0224             break;
0225         case RGBA:
0226             channelHot[0] = 1;
0227             channelHot[1] = 1;
0228             channelHot[2] = 1;
0229             channelHot[3] = 1;
0230             break;
0231         case R:
0232             channelHot[0] = 1;
0233             channelHot[1] = 0;
0234             channelHot[2] = 0;
0235             channelHot[3] = 0;
0236             break;
0237         case G:
0238             channelHot[0] = 0;
0239             channelHot[1] = 1;
0240             channelHot[2] = 0;
0241             channelHot[3] = 0;
0242             break;
0243         case B:
0244             channelHot[0] = 0;
0245             channelHot[1] = 0;
0246             channelHot[2] = 1;
0247             channelHot[3] = 0;
0248             break;
0249         case A:
0250             channelHot[0] = 0;
0251             channelHot[1] = 0;
0252             channelHot[2] = 0;
0253             channelHot[3] = 1;
0254         default:;
0255         }
0256         double lumacoef[3];
0257         config->getDefaultLumaCoefs(lumacoef);
0258         double m44[16];
0259         double offset[4];
0260         OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
0261         OCIO::MatrixTransformRcPtr swizzleTransform = OCIO::MatrixTransform::Create();
0262         swizzleTransform->setMatrix(m44);
0263         swizzleTransform->setOffset(offset);
0264         vpt->setChannelView(swizzleTransform);
0265     }
0266 
0267     // Post-display transform gamma
0268     {
0269         double exponent = 1.0 / std::max(1e-6, gamma);
0270         const double exponent4f[] = {exponent, exponent, exponent, exponent};
0271         OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create();
0272         expTransform->setValue(exponent4f);
0273         vpt->setDisplayCC(expTransform);
0274 
0275         // approximation (no color correction);
0276         approximateTransform->appendTransform(expTransform);
0277     }
0278 
0279     try {
0280         AutoSetAndRestoreThreadLocale l;
0281         m_processor = vpt->getProcessor(config, config->getCurrentContext());
0282         m_processorCPU = m_processor->getDefaultCPUProcessor();
0283     } catch (OCIO::Exception &e) {
0284         // XXX: How to not break the OCIO shader now?
0285         errKrita << "OCIO exception while parsing the current context:" << e.what();
0286         m_shaderDirty = false;
0287         return;
0288     }
0289 
0290     m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD);
0291     m_forwardApproximationProcessorCPU = m_forwardApproximationProcessor->getDefaultCPUProcessor();
0292 
0293     try {
0294         m_reverseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE);
0295         m_reverseApproximationProcessorCPU = m_reverseApproximationProcessor->getDefaultCPUProcessor();
0296     } catch (...) {
0297         warnKrita << "OCIO inverted matrix does not exist!";
0298         // m_reverseApproximationProcessor;
0299     }
0300 
0301     m_shaderDirty = true;
0302 }
0303 
0304 bool OcioDisplayFilter::updateShader()
0305 {
0306     if (KisOpenGL::hasOpenGLES()) {
0307         QOpenGLContext *ctx = QOpenGLContext::currentContext();
0308 
0309         KIS_ASSERT_RECOVER_RETURN_VALUE(ctx, false);
0310 
0311         if (ctx->format().majorVersion() >= 3) {
0312             QOpenGLExtraFunctions *f = ctx->extraFunctions();
0313             if (f) {
0314                 return updateShaderImpl(f);
0315             }
0316         } else if (ctx->hasExtension("GL_OES_texture_float")
0317                    && (ctx->hasExtension("GL_EXT_texture_storage") || ctx->hasExtension("EXT_color_buffer_float"))
0318                    && ctx->hasExtension("GL_OES_texture_float_linear")) {
0319             QOpenGLExtraFunctions *f = ctx->extraFunctions();
0320             if (f) {
0321                 return updateShaderImpl(f);
0322             }
0323         } else {
0324             dbgKrita << "OcioDisplayFilter::updateShader"
0325                         << "OpenGL ES v2+ support detected but no OES_texture_float,"
0326                         "GL_EXT_color_buffer_float or GL_EXT_texture_storage, or GL_OES_texture_float_linear were found";
0327             return false;
0328         }
0329 #if defined(QT_OPENGL_3)
0330     } else if (KisOpenGL::hasOpenGL3()) {
0331         QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>();
0332         if (f) {
0333             return updateShaderImpl(f);
0334         }
0335 #endif
0336     }
0337 
0338     // XXX This option can be removed once we move to Qt 5.7+
0339     if (KisOpenGL::supportsLoD()) {
0340 #if defined(QT_OPENGL_3)
0341 #if defined(Q_OS_MAC) && defined(QT_OPENGL_3_2)
0342         QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>();
0343 #else
0344         QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_0>();
0345 #endif
0346         if (f) {
0347             return updateShaderImpl(f);
0348         }
0349 #endif
0350     }
0351 #if !defined(QT_OPENGL_ES_2)
0352     QOpenGLFunctions_2_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_0>();
0353     if (f) {
0354         return updateShaderImpl(f);
0355     }
0356 #endif
0357 
0358     return false;
0359 }
0360 
0361 template<class F>
0362 bool OcioDisplayFilter::updateShaderImpl(F *f)
0363 {
0364     // check whether we are allowed to use shaders -- though that should
0365     // work for everyone these days
0366     KisConfig cfg(true);
0367     if (!cfg.useOpenGL())
0368         return false;
0369 
0370     if (!m_shaderDirty)
0371         return false;
0372 
0373     if (!f) {
0374         qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!";
0375         return false;
0376     }
0377 
0378     f->initializeOpenGLFunctions();
0379 
0380     bool shouldRecompileShader = false;
0381 
0382     // Step 1: Create a GPU Shader Description
0383     OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc();
0384 
0385 #if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000
0386     if (KisOpenGL::supportsLoD()) {
0387         shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0);
0388     } else {
0389         shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_1_0);
0390     }
0391 #else
0392     if (KisOpenGL::supportsLoD()) {
0393         shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
0394     } else {
0395         shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_2);
0396     }
0397 #endif
0398 
0399     shaderDesc->setFunctionName("OCIODisplay");
0400     shaderDesc->setResourcePrefix("ocio_");
0401 
0402     // Step 2: Compute the 3D LUT
0403 #if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000
0404     // ensure the new GPU pipeline is used with our GLES3 patch
0405     // this way users won't run into errors when using Angle along with OCIO
0406     const auto gpu = m_processor->getOptimizedGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT);
0407 #else
0408     const int lut3DEdgeSize = cfg.ocioLutEdgeSize();
0409     const auto gpu =
0410         m_processor->getOptimizedLegacyGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT, lut3DEdgeSize);
0411 #endif
0412 
0413     gpu->extractGpuShaderInfo(shaderDesc);
0414 
0415     // OCIO v2 assumes you'll use the OglApp helpers
0416     // these are unusable from a Qt backend, because they rely on GLUT/GLFW
0417     // ociodisplay original pipeline:
0418     // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/508b3f4a0618435aeed2f45058208bdfa99e0887/src/apps/ociodisplay/main.cpp
0419     // ociodisplay new pipeline is a single call:
0420     // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/ffddc3341f5775c7866fe2c93275e1d5e0b0540f/src/apps/ociodisplay/main.cpp#L427
0421     // we need to replicate this loop:
0422     // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/oglapp.cpp#L191-L223
0423     // calls functions from here:
0424     // https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/glsl.cpp
0425 
0426     for (const auto &tex : m_lut3dTexIDs) {
0427         f->glDeleteTextures(1, &tex.m_uid);
0428     }
0429 
0430     m_lut3dTexIDs.clear();
0431 
0432     // This is the first available index for the textures.
0433     unsigned currIndex = 1;
0434 
0435     // Process the 3D LUT first.
0436 
0437     const unsigned maxTexture3D = shaderDesc->getNum3DTextures();
0438     for (unsigned idx = 0; idx < maxTexture3D; ++idx) {
0439         // 1. Get the information of the 3D LUT.
0440 
0441         const char *textureName = nullptr;
0442         const char *samplerName = nullptr;
0443         unsigned edgelen = 0;
0444         OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
0445         shaderDesc->get3DTexture(idx, textureName, samplerName, edgelen, interpolation);
0446 
0447         if (!textureName || !*textureName || !samplerName || !*samplerName || edgelen == 0) {
0448             errOpenGL << "The texture data is corrupted";
0449             return false;
0450         }
0451 
0452         const float *values = nullptr;
0453         shaderDesc->get3DTextureValues(idx, values);
0454         if (!values) {
0455             errOpenGL << "The texture values are missing";
0456             return false;
0457         }
0458 
0459         // 2. Allocate the 3D LUT.
0460 
0461         unsigned texId = 0;
0462         {
0463             if (values == nullptr) {
0464                 errOpenGL << "3D LUT" << idx << "Missing texture data";
0465                 return false;
0466             }
0467 
0468             f->glGenTextures(1, &texId);
0469 
0470             f->glActiveTexture(GL_TEXTURE0 + currIndex);
0471 
0472             f->glBindTexture(GL_TEXTURE_3D, texId);
0473 
0474             {
0475                 if (interpolation == OCIO::INTERP_NEAREST) {
0476                     f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
0477                     f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
0478                 } else {
0479                     f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0480                     f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0481                 }
0482 
0483                 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0484                 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0485                 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
0486             }
0487 
0488             f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F_ARB, edgelen, edgelen, edgelen, 0, GL_RGB, GL_FLOAT, values);
0489         }
0490 
0491         // 3. Keep the texture id & name for the later enabling.
0492 
0493         m_lut3dTexIDs.push_back({texId, textureName, samplerName, GL_TEXTURE_3D});
0494 
0495         currIndex++;
0496     }
0497 
0498     // Process the 1D LUTs.
0499 
0500     const unsigned maxTexture2D = shaderDesc->getNumTextures();
0501     for (unsigned idx = 0; idx < maxTexture2D; ++idx) {
0502         // 1. Get the information of the 1D LUT.
0503 
0504         const char *textureName = nullptr;
0505         const char *samplerName = nullptr;
0506         unsigned width = 0;
0507         unsigned height = 0;
0508         OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL;
0509         OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR;
0510 
0511 #if OCIO_VERSION_HEX >= 0x2030000
0512         OCIO::GpuShaderCreator::TextureDimensions dimensions;
0513         shaderDesc->getTexture(idx, textureName, samplerName, width, height, channel, dimensions, interpolation);
0514 #else
0515         shaderDesc->getTexture(idx, textureName, samplerName, width, height, channel, interpolation);
0516 #endif
0517 
0518         if (!textureName || !*textureName || !samplerName || !*samplerName || width == 0) {
0519             errOpenGL << "The texture data is corrupted";
0520             return false;
0521         }
0522 
0523         const float *values = nullptr;
0524         shaderDesc->getTextureValues(idx, values);
0525         if (!values) {
0526             errOpenGL << "The texture values are missing";
0527             return false;
0528         }
0529 
0530         // 2. Allocate the 1D LUT (a 2D texture is needed to hold large LUTs).
0531 
0532         unsigned texId = 0;
0533         {
0534             if (values == nullptr) {
0535                 errOpenGL << "1D LUT" << idx << "Missing texture data.";
0536                 return false;
0537             }
0538 
0539             unsigned internalformat = GL_RGB32F_ARB;
0540             unsigned format = GL_RGB;
0541 
0542             if (channel == OCIO::GpuShaderCreator::TEXTURE_RED_CHANNEL) {
0543                 internalformat = GL_R32F;
0544                 format = GL_RED;
0545             }
0546 
0547             f->glGenTextures(1, &texId);
0548 
0549             f->glActiveTexture(GL_TEXTURE0 + currIndex);
0550 
0551 #if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000
0552 #else
0553             // 1D Textures are unsupported by OpenGL ES.
0554             // https://github.com/AcademySoftwareFoundation/OpenColorIO/issues/1486
0555             if (height > 1) {
0556 #endif
0557                 f->glBindTexture(GL_TEXTURE_2D, texId);
0558 
0559                 {
0560                     if (interpolation == OCIO::INTERP_NEAREST) {
0561                         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
0562                         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
0563                     } else {
0564                         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
0565                         f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
0566                     }
0567 
0568                     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
0569                     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
0570                     f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
0571                 }
0572 
0573                 f->glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, GL_FLOAT, values);
0574 #if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000
0575 #else
0576             } else {
0577                 errOpenGL << "1D texture detected @" << idx << ", not supported by OpenGLES";
0578                 return false;
0579             }
0580 #endif
0581         }
0582 
0583         // 3. Keep the texture id & name for the later enabling.
0584 
0585         unsigned type = GL_TEXTURE_2D;
0586         m_lut3dTexIDs.push_back({texId, textureName, samplerName, type});
0587         currIndex++;
0588     }
0589 
0590     // Step 3: Generate the shader text
0591     QString shaderCacheID = QString::fromLatin1(shaderDesc->getCacheID());
0592     if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) {
0593         // dbgKrita << "Computing Shader " << m_shadercacheid;
0594 
0595         m_shadercacheid = shaderCacheID;
0596 
0597         m_program = QString::fromLatin1("%1\n").arg(shaderDesc->getShaderText());
0598         shouldRecompileShader = true;
0599     }
0600 
0601     // Step 4: mirror and bind uniforms
0602     m_lut3dUniforms.clear();
0603 
0604     const unsigned maxUniforms = shaderDesc->getNumUniforms();
0605     for (unsigned idx = 0; idx < maxUniforms; ++idx) {
0606         OCIO::GpuShaderDesc::UniformData data;
0607         const char *name = shaderDesc->getUniform(idx, data);
0608         if (data.m_type == OCIO::UNIFORM_UNKNOWN) {
0609             errOpenGL << "Uniform" << idx << "has an unknown type";
0610             return false;
0611         }
0612         // Transfer uniform.
0613         m_lut3dUniforms.push_back({name, data});
0614     }
0615 
0616     m_shaderDirty = false;
0617     return shouldRecompileShader;
0618 }
0619 
0620 void OcioDisplayFilter::setupTextures(GLFunctions *f, QOpenGLShaderProgram *program) const
0621 {
0622     for (unsigned int idx = 0; idx < m_lut3dTexIDs.size(); ++idx) {
0623         const auto &data = m_lut3dTexIDs[idx];
0624         f->glActiveTexture(GL_TEXTURE0 + 1 + idx);
0625         f->glBindTexture(data.m_type, data.m_uid);
0626         program->setUniformValue(program->uniformLocation(data.m_samplerName), GLint(1 + idx));
0627     }
0628 
0629     for (const KisTextureUniform &uniform : m_lut3dUniforms) {
0630         const int m_handle = program->uniformLocation(uniform.m_name);
0631 
0632         const OCIO::GpuShaderDesc::UniformData &m_data = uniform.m_data;
0633 
0634         // Update value.
0635         if (m_data.m_getDouble) {
0636             program->setUniformValue(m_handle, static_cast<GLfloat>(m_data.m_getDouble()));
0637         } else if (m_data.m_getBool) {
0638             program->setUniformValue(m_handle, static_cast<GLfloat>(m_data.m_getBool() ? 1.0f : 0.0f));
0639         } else if (m_data.m_getFloat3) {
0640             program->setUniformValue(m_handle,
0641                                      m_data.m_getFloat3()[0],
0642                                      m_data.m_getFloat3()[1],
0643                                      m_data.m_getFloat3()[2]);
0644         } else if (m_data.m_vectorFloat.m_getSize && m_data.m_vectorFloat.m_getVector) {
0645             program->setUniformValueArray(m_handle,
0646                                           m_data.m_vectorFloat.m_getVector(),
0647                                           m_data.m_vectorFloat.m_getSize(),
0648                                           1);
0649         } else if (m_data.m_vectorInt.m_getSize && m_data.m_vectorInt.m_getVector) {
0650             program->setUniformValueArray(m_handle, m_data.m_vectorInt.m_getVector(), m_data.m_vectorInt.m_getSize());
0651         } else {
0652             errOpenGL << "Uniform" << uniform.m_name << "is not linked to any value";
0653             continue;
0654         }
0655     }
0656 }