Krita Source Code Documentation
Loading...
Searching...
No Matches
ocio_display_filter_vfx2020.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
8
9#include <cstdlib>
10#include <cmath>
11#include <cstdio>
12#include <cstring>
13#include <iostream>
14#include <fstream>
15#include <sstream>
16
17#include <kis_config.h>
18
19#include <opengl/kis_opengl.h>
20#include <QOpenGLContext>
21#include <QOpenGLFunctions_3_2_Core>
22#include <QOpenGLFunctions_3_0>
23#include <QOpenGLFunctions_2_0>
24#include <QOpenGLExtraFunctions>
25
26#if defined(QT_OPENGL_ES_2)
27#define GL_RGBA16F_ARB GL_RGBA16F_EXT
28#define GL_RGB16F_ARB GL_RGB16F_EXT
29#endif
30
31#if defined(QT_OPENGL_ES_2) && !defined(QT_OPENGL_ES_3)
32#define GL_R32F GL_R32F_EXT
33#define GL_RED GL_RED_EXT
34#define GL_TEXTURE_WRAP_R GL_TEXTURE_WRAP_R_OES
35#endif
36
38
40 : KisDisplayFilter(parent)
41 , m_interface(interface)
42{
43}
44
48
53
54void OcioDisplayFilter::filter(quint8 *pixels, quint32 numPixels)
55{
56 // processes that data _in_ place
57 if (m_processor) {
58 OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4);
59 m_processor->apply(img);
60 }
61}
62
63void OcioDisplayFilter::approximateInverseTransformation(quint8 *pixels, quint32 numPixels)
64{
65 // processes that data _in_ place
67 OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4);
69 }
70}
71
72void OcioDisplayFilter::approximateForwardTransformation(quint8 *pixels, quint32 numPixels)
73{
74 // processes that data _in_ place
76 OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4);
78 }
79}
80
85
90
95
97{
98 return m_program;
99}
100
102{
103 if (!config) {
104 return;
105 }
106
107 if (!displayDevice) {
108 displayDevice = config->getDefaultDisplay();
109 }
110
111 if (!view) {
112 view = config->getDefaultView(displayDevice);
113 }
114
115 if (!inputColorSpaceName) {
116 inputColorSpaceName = config->getColorSpaceNameByIndex(0);
117 }
118 if (!look) {
119 look = config->getLookNameByIndex(0);
120 }
121
123 return;
124 }
125
126 OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create();
127 transform->setInputColorSpaceName(inputColorSpaceName);
128 transform->setDisplay(displayDevice);
129 transform->setView(view);
130
147 if (config->getLook(look)) {
148 transform->setLooksOverride(look);
149 transform->setLooksOverrideEnabled(true);
150 }
151
152 OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create();
153
154 // fstop exposure control -- not sure how that translates to our exposure
155 {
156 float exposureGain = powf(2.0f, exposure);
157
158 const qreal minRange = 0.001;
159 if (qAbs(blackPoint - whitePoint) < minRange) {
160 whitePoint = blackPoint + minRange;
161 }
162
163 const float oldMin[] = { blackPoint, blackPoint, blackPoint, 0.0f };
164 const float oldMax[] = { whitePoint, whitePoint, whitePoint, 1.0f };
165
166 const float newMin[] = { 0.0f, 0.0f, 0.0f, 0.0f };
167 const float newMax[] = { exposureGain, exposureGain, exposureGain, 1.0f };
168
169 float m44[16];
170 float offset4[4];
171 OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax);
172 OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create();
173 mtx->setValue(m44, offset4);
174 transform->setLinearCC(mtx);
175
176 // approximation (no color correction);
177 approximateTransform->push_back(mtx);
178 }
179
180 // channel swizzle
181 {
182 int channelHot[4];
183 switch (swizzle) {
184 case LUMINANCE:
185 channelHot[0] = 1;
186 channelHot[1] = 1;
187 channelHot[2] = 1;
188 channelHot[3] = 0;
189 break;
190 case RGBA:
191 channelHot[0] = 1;
192 channelHot[1] = 1;
193 channelHot[2] = 1;
194 channelHot[3] = 1;
195 break;
196 case R:
197 channelHot[0] = 1;
198 channelHot[1] = 0;
199 channelHot[2] = 0;
200 channelHot[3] = 0;
201 break;
202 case G:
203 channelHot[0] = 0;
204 channelHot[1] = 1;
205 channelHot[2] = 0;
206 channelHot[3] = 0;
207 break;
208 case B:
209 channelHot[0] = 0;
210 channelHot[1] = 0;
211 channelHot[2] = 1;
212 channelHot[3] = 0;
213 break;
214 case A:
215 channelHot[0] = 0;
216 channelHot[1] = 0;
217 channelHot[2] = 0;
218 channelHot[3] = 1;
219 default:
220 ;
221 }
222 float lumacoef[3];
223 config->getDefaultLumaCoefs(lumacoef);
224 float m44[16];
225 float offset[4];
226 OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef);
227 OCIO::MatrixTransformRcPtr swizzleTransform = OCIO::MatrixTransform::Create();
228 swizzleTransform->setValue(m44, offset);
229 transform->setChannelView(swizzleTransform);
230 }
231
232 // Post-display transform gamma
233 {
234 float exponent = 1.0f/std::max(1e-6f, static_cast<float>(gamma));
235 const float exponent4f[] = { exponent, exponent, exponent, exponent };
236 OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create();
237 expTransform->setValue(exponent4f);
238 transform->setDisplayCC(expTransform);
239
240 // approximation (no color correction);
241 approximateTransform->push_back(expTransform);
242 }
243
244 try {
246 m_processor = config->getProcessor(transform);
247 } catch (OCIO::Exception &e) {
248 // XXX: How to not break the OCIO shader now?
249 errKrita << "OCIO exception while parsing the current context:" << e.what();
250 m_shaderDirty = false;
251 return;
252 }
253
254 m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD);
255
256 try {
257 m_reverseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE);
258 } catch (...) {
259 warnKrita << "OCIO inverted matrix does not exist!";
260 //m_reverseApproximationProcessor;
261 }
262
263 m_shaderDirty = true;
264}
265
267{
269 QOpenGLContext *ctx = QOpenGLContext::currentContext();
270
272 if (ctx->format().majorVersion() >= 3) {
273 QOpenGLExtraFunctions *f = ctx->extraFunctions();
274 if (f) {
275 return updateShaderImpl(f);
276 }
277 } else if (ctx->hasExtension("GL_OES_texture_half_float") && ctx->hasExtension("GL_EXT_color_buffer_half_float")
278 && ctx->hasExtension("GL_OES_texture_half_float_linear")) {
279 QOpenGLExtraFunctions *f = ctx->extraFunctions();
280 if (f) {
281 return updateShaderImpl(f);
282 }
283 } else {
284 dbgKrita << "OcioDisplayFilter::updateShader (2020)"
285 << "OpenGL ES v2+ support detected but no OES_texture_half_float,"
286 "GL_EXT_color_buffer_half_float or GL_OES_texture_half_float_linear were found";
287 return false;
288 }
289#if defined(QT_OPENGL_3)
290 } else if (KisOpenGL::hasOpenGL3()) {
291 QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>();
292 if (f) {
293 return updateShaderImpl(f);
294 }
295#endif
296 }
297
298 // XXX This option can be removed once we move to Qt 5.7+
300#if defined(QT_OPENGL_3)
301#if defined(Q_OS_MAC) && defined(QT_OPENGL_3_2)
302 QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>();
303#else
304 QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_0>();
305#endif
306 if (f) {
307 return updateShaderImpl(f);
308 }
309#endif
310 }
311#if !defined(QT_OPENGL_ES_2)
312 QOpenGLFunctions_2_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_0>();
313 if (f) {
314 return updateShaderImpl(f);
315 }
316#endif
317
318 return false;
319}
320
321template <class F>
323 // check whether we are allowed to use shaders -- though that should
324 // work for everyone these days
325 KisConfig cfg(true);
326 if (!cfg.useOpenGL()) return false;
327
328 if (!m_shaderDirty) return false;
329
330 if (!f) {
331 qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!";
332 return false;
333 }
334
335 f->initializeOpenGLFunctions();
336
337 bool shouldRecompileShader = false;
338
339 const int lut3DEdgeSize = cfg.ocioLutEdgeSize();
340
341 if (m_lut3d.size() == 0) {
342 //dbgKrita << "generating lut";
343 f->glGenTextures(1, &m_lut3dTexID);
344
345 int num3Dentries = 3 * lut3DEdgeSize * lut3DEdgeSize * lut3DEdgeSize;
346 m_lut3d.fill(0.0, num3Dentries);
347
348 f->glActiveTexture(GL_TEXTURE1);
349 f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID);
350
351 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
352 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
353 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
354 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
355 f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
356 f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB,
357 lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize,
358 0, GL_RGB, GL_FLOAT, &m_lut3d.constData()[0]);
359 }
360
361 // Step 1: Create a GPU Shader Description
362 OCIO::GpuShaderDesc shaderDesc;
363
365 shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3);
366 }
367 else {
368 shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0);
369 }
370
371
372 shaderDesc.setFunctionName("OCIODisplay");
373 shaderDesc.setLut3DEdgeLen(lut3DEdgeSize);
374
375
376 // Step 2: Compute the 3D LUT
377 QString lut3dCacheID = QString::fromLatin1(m_processor->getGpuLut3DCacheID(shaderDesc));
378 if (lut3dCacheID != m_lut3dcacheid) {
379 //dbgKrita << "Computing 3DLut " << m_lut3dcacheid;
380 m_lut3dcacheid = lut3dCacheID;
381 m_processor->getGpuLut3D(&m_lut3d[0], shaderDesc);
382
383 f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID);
384 f->glTexSubImage3D(GL_TEXTURE_3D, 0,
385 0, 0, 0,
386 lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize,
387 GL_RGB, GL_FLOAT, &m_lut3d[0]);
388 }
389
390 // Step 3: Generate the shader text
391 QString shaderCacheID = QString::fromLatin1(m_processor->getGpuShaderTextCacheID(shaderDesc));
392 if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) {
393 //dbgKrita << "Computing Shader " << m_shadercacheid;
394
395 m_shadercacheid = shaderCacheID;
396
397 std::ostringstream os;
398 os << m_processor->getGpuShaderText(shaderDesc) << "\n";
399
400 m_program = QString::fromLatin1(os.str().c_str());
401 shouldRecompileShader = true;
402 }
403
404 m_shaderDirty = false;
405 return shouldRecompileShader;
406}
407
408void OcioDisplayFilter::setupTextures(GLFunctions *f, QOpenGLShaderProgram *program) const
409{
410 f->glActiveTexture(GL_TEXTURE0 + 1);
411 f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID);
412 program->setUniformValue(program->uniformLocation("texture1"), 1);
413}
float value(const T *src, size_t ch)
int ocioLutEdgeSize(bool defaultValue=false) const
bool useOpenGL(bool defaultValue=false) const
The KisDisplayFilter class is the base class for filters that are applied by the canvas to the projec...
static bool hasOpenGLES()
static bool supportsLoD()
static bool hasOpenGL3()
void setupTextures(GLFunctions *f, QOpenGLShaderProgram *program) const override
void approximateInverseTransformation(quint8 *pixels, quint32 numPixels) override
OCIO::ConstConfigRcPtr config
OCIO::ConstProcessorRcPtr m_processor
KisExposureGammaCorrectionInterface * m_interface
void approximateForwardTransformation(quint8 *pixels, quint32 numPixels) override
virtual QString program() const override
OCIO_CHANNEL_SWIZZLE swizzle
OCIO::ConstProcessorRcPtr m_reverseApproximationProcessor
bool lockCurrentColorVisualRepresentation() const override
OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent=0)
OCIO::ConstProcessorRcPtr m_forwardApproximationProcessor
void filter(quint8 *pixels, quint32 numPixels) override
bool useInternalColorManagement() const override
KisExposureGammaCorrectionInterface * correctionInterface() const override
void setLockCurrentColorVisualRepresentation(bool value)
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define dbgKrita
Definition kis_debug.h:45
#define errKrita
Definition kis_debug.h:107
#define warnKrita
Definition kis_debug.h:87
#define GL_CLAMP_TO_EDGE