Lines 1-38
Link Here
|
1 |
/* |
1 |
/* |
2 |
* Copyright (c) 2012 Boudewijn Rempt <boud@valdyas.org> |
2 |
* SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org> |
|
|
3 |
* SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> |
3 |
* |
4 |
* |
4 |
* This program is free software; you can redistribute it and/or modify |
5 |
* SPDX-License-Identifier: GPL-2.0-or-later |
5 |
* it under the terms of the GNU General Public License as published by |
|
|
6 |
* the Free Software Foundation; either version 2 of the License, or |
7 |
* (at your option) any later version. |
8 |
* |
9 |
* This program is distributed in the hope that it will be useful, |
10 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 |
* GNU General Public License for more details. |
13 |
* |
14 |
* You should have received a copy of the GNU General Public License |
15 |
* along with this program; if not, write to the Free Software |
16 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 |
*/ |
6 |
*/ |
|
|
7 |
|
18 |
#include "ocio_display_filter.h" |
8 |
#include "ocio_display_filter.h" |
19 |
#include <math.h> |
9 |
|
20 |
#include <cstdlib> |
10 |
#include <QOpenGLContext> |
|
|
11 |
#include <QOpenGLExtraFunctions> |
12 |
#include <QOpenGLFunctions_2_0> |
13 |
#include <QOpenGLFunctions_3_0> |
14 |
#include <QOpenGLFunctions_3_2_Core> |
15 |
|
21 |
#include <cmath> |
16 |
#include <cmath> |
22 |
#include <cstdio> |
|
|
23 |
#include <cstring> |
17 |
#include <cstring> |
24 |
#include <iostream> |
|
|
25 |
#include <fstream> |
26 |
#include <sstream> |
27 |
|
18 |
|
28 |
#include <kis_config.h> |
19 |
#include <kis_config.h> |
29 |
|
20 |
#include <kis_debug.h> |
30 |
#include <opengl/kis_opengl.h> |
21 |
#include <opengl/kis_opengl.h> |
31 |
#include <QOpenGLContext> |
22 |
|
32 |
#include <QOpenGLFunctions_3_2_Core> |
23 |
#if defined(QT_OPENGL_ES_2) |
33 |
#include <QOpenGLFunctions_3_0> |
24 |
#define GL_RGBA32F_ARB GL_RGBA32F_EXT |
34 |
#include <QOpenGLFunctions_2_0> |
25 |
#define GL_RGB32F_ARB GL_RGB32F_EXT |
35 |
#include <QOpenGLExtraFunctions> |
26 |
#endif |
36 |
|
27 |
|
37 |
OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent) |
28 |
OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent) |
38 |
: KisDisplayFilter(parent) |
29 |
: KisDisplayFilter(parent) |
Lines 42-48
OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interf
Link Here
|
42 |
, look(0) |
33 |
, look(0) |
43 |
, swizzle(RGBA) |
34 |
, swizzle(RGBA) |
44 |
, m_interface(interface) |
35 |
, m_interface(interface) |
45 |
, m_lut3dTexID(0) |
36 |
, m_lut3dTexIDs() |
|
|
37 |
, m_lut3dUniforms() |
46 |
, m_shaderDirty(true) |
38 |
, m_shaderDirty(true) |
47 |
{ |
39 |
{ |
48 |
} |
40 |
} |
Lines 51-57
OcioDisplayFilter::~OcioDisplayFilter()
Link Here
|
51 |
{ |
43 |
{ |
52 |
} |
44 |
} |
53 |
|
45 |
|
54 |
KisExposureGammaCorrectionInterface* OcioDisplayFilter::correctionInterface() const |
46 |
KisExposureGammaCorrectionInterface *OcioDisplayFilter::correctionInterface() const |
55 |
{ |
47 |
{ |
56 |
return m_interface; |
48 |
return m_interface; |
57 |
} |
49 |
} |
Lines 60-67
void OcioDisplayFilter::filter(quint8 *pixels, quint32 numPixels)
Link Here
|
60 |
{ |
52 |
{ |
61 |
// processes that data _in_ place |
53 |
// processes that data _in_ place |
62 |
if (m_processor) { |
54 |
if (m_processor) { |
63 |
OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4); |
55 |
OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4); |
64 |
m_processor->apply(img); |
56 |
m_processor->getDefaultCPUProcessor()->apply(img); |
65 |
} |
57 |
} |
66 |
} |
58 |
} |
67 |
|
59 |
|
Lines 69-76
void OcioDisplayFilter::approximateInverseTransformation(quint8 *pixels, quint32
Link Here
|
69 |
{ |
61 |
{ |
70 |
// processes that data _in_ place |
62 |
// processes that data _in_ place |
71 |
if (m_revereseApproximationProcessor) { |
63 |
if (m_revereseApproximationProcessor) { |
72 |
OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4); |
64 |
OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4); |
73 |
m_revereseApproximationProcessor->apply(img); |
65 |
m_revereseApproximationProcessor->getDefaultCPUProcessor()->apply(img); |
74 |
} |
66 |
} |
75 |
} |
67 |
} |
76 |
|
68 |
|
Lines 78-85
void OcioDisplayFilter::approximateForwardTransformation(quint8 *pixels, quint32
Link Here
|
78 |
{ |
70 |
{ |
79 |
// processes that data _in_ place |
71 |
// processes that data _in_ place |
80 |
if (m_forwardApproximationProcessor) { |
72 |
if (m_forwardApproximationProcessor) { |
81 |
OCIO::PackedImageDesc img(reinterpret_cast<float*>(pixels), numPixels, 1, 4); |
73 |
OCIO::PackedImageDesc img(reinterpret_cast<float *>(pixels), numPixels, 1, 4); |
82 |
m_forwardApproximationProcessor->apply(img); |
74 |
m_forwardApproximationProcessor->getDefaultCPUProcessor()->apply(img); |
83 |
} |
75 |
} |
84 |
} |
76 |
} |
85 |
|
77 |
|
Lines 103-113
QString OcioDisplayFilter::program() const
Link Here
|
103 |
return m_program; |
95 |
return m_program; |
104 |
} |
96 |
} |
105 |
|
97 |
|
106 |
GLuint OcioDisplayFilter::lutTexture() const |
|
|
107 |
{ |
108 |
return m_lut3dTexID; |
109 |
} |
110 |
|
111 |
void OcioDisplayFilter::updateProcessor() |
98 |
void OcioDisplayFilter::updateProcessor() |
112 |
{ |
99 |
{ |
113 |
if (!config) { |
100 |
if (!config) { |
Lines 126-143
void OcioDisplayFilter::updateProcessor()
Link Here
|
126 |
inputColorSpaceName = config->getColorSpaceNameByIndex(0); |
113 |
inputColorSpaceName = config->getColorSpaceNameByIndex(0); |
127 |
} |
114 |
} |
128 |
if (!look) { |
115 |
if (!look) { |
129 |
look = config->getLookNameByIndex(0); |
116 |
look = config->getLookNameByIndex(0); |
130 |
} |
117 |
} |
131 |
|
118 |
|
132 |
if (!displayDevice || !view || !inputColorSpaceName) { |
119 |
if (!displayDevice || !view || !inputColorSpaceName) { |
133 |
return; |
120 |
return; |
134 |
} |
121 |
} |
135 |
|
122 |
|
136 |
OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); |
123 |
OCIO::DisplayViewTransformRcPtr transform = OCIO::DisplayViewTransform::Create(); |
137 |
transform->setInputColorSpaceName(inputColorSpaceName); |
124 |
transform->setSrc(inputColorSpaceName); |
138 |
transform->setDisplay(displayDevice); |
125 |
transform->setDisplay(displayDevice); |
139 |
transform->setView(view); |
126 |
transform->setView(view); |
140 |
|
127 |
|
|
|
128 |
OCIO::LegacyViewingPipelineRcPtr vpt = OCIO::LegacyViewingPipeline::Create(); |
129 |
|
130 |
vpt->setDisplayViewTransform(transform); |
131 |
|
141 |
/** |
132 |
/** |
142 |
* Look support: |
133 |
* Look support: |
143 |
* As the OCIO docs will tell you, looks are a aesthetic transform that is |
134 |
* As the OCIO docs will tell you, looks are a aesthetic transform that is |
Lines 155-190
void OcioDisplayFilter::updateProcessor()
Link Here
|
155 |
* override is all we can offer. |
146 |
* override is all we can offer. |
156 |
*/ |
147 |
*/ |
157 |
if (config->getLook(look)) { |
148 |
if (config->getLook(look)) { |
158 |
transform->setLooksOverride(look); |
149 |
vpt->setLooksOverride(look); |
159 |
transform->setLooksOverrideEnabled(true); |
150 |
vpt->setLooksOverrideEnabled(true); |
160 |
} |
151 |
} |
161 |
|
152 |
|
162 |
OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create(); |
153 |
OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create(); |
163 |
|
154 |
|
164 |
// fstop exposure control -- not sure how that translates to our exposure |
155 |
// fstop exposure control -- not sure how that translates to our exposure |
165 |
{ |
156 |
{ |
166 |
float exposureGain = powf(2.0f, exposure); |
157 |
const double exposureGain = pow(2.0, exposure); |
167 |
|
158 |
|
168 |
const qreal minRange = 0.001; |
159 |
const double minRange = 0.001; |
169 |
if (qAbs(blackPoint - whitePoint) < minRange) { |
160 |
if (qAbs(blackPoint - whitePoint) < minRange) { |
170 |
whitePoint = blackPoint + minRange; |
161 |
whitePoint = blackPoint + minRange; |
171 |
} |
162 |
} |
172 |
|
163 |
|
173 |
const float oldMin[] = { blackPoint, blackPoint, blackPoint, 0.0f }; |
164 |
const double oldMin[] = {blackPoint, blackPoint, blackPoint, 0.0}; |
174 |
const float oldMax[] = { whitePoint, whitePoint, whitePoint, 1.0f }; |
165 |
const double oldMax[] = {whitePoint, whitePoint, whitePoint, 1.0}; |
175 |
|
166 |
|
176 |
const float newMin[] = { 0.0f, 0.0f, 0.0f, 0.0f }; |
167 |
const double newMin[] = {0.0, 0.0, 0.0, 0.0}; |
177 |
const float newMax[] = { exposureGain, exposureGain, exposureGain, 1.0f }; |
168 |
const double newMax[] = {exposureGain, exposureGain, exposureGain, 1.0}; |
178 |
|
169 |
|
179 |
float m44[16]; |
170 |
double m44[16]; |
180 |
float offset4[4]; |
171 |
double offset4[4]; |
181 |
OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax); |
172 |
OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax); |
182 |
OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); |
173 |
OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); |
183 |
mtx->setValue(m44, offset4); |
174 |
mtx->setMatrix(m44); |
184 |
transform->setLinearCC(mtx); |
175 |
mtx->setOffset(offset4); |
|
|
176 |
vpt->setLinearCC(mtx); |
185 |
|
177 |
|
186 |
// approximation (no color correction); |
178 |
// approximation (no color correction); |
187 |
approximateTransform->push_back(mtx); |
179 |
approximateTransform->appendTransform(mtx); |
188 |
} |
180 |
} |
189 |
|
181 |
|
190 |
// channel swizzle |
182 |
// channel swizzle |
Lines 226-257
void OcioDisplayFilter::updateProcessor()
Link Here
|
226 |
channelHot[1] = 0; |
218 |
channelHot[1] = 0; |
227 |
channelHot[2] = 0; |
219 |
channelHot[2] = 0; |
228 |
channelHot[3] = 1; |
220 |
channelHot[3] = 1; |
229 |
default: |
221 |
default:; |
230 |
; |
|
|
231 |
} |
222 |
} |
232 |
float lumacoef[3]; |
223 |
double lumacoef[3]; |
233 |
config->getDefaultLumaCoefs(lumacoef); |
224 |
config->getDefaultLumaCoefs(lumacoef); |
234 |
float m44[16]; |
225 |
double m44[16]; |
235 |
float offset[4]; |
226 |
double offset[4]; |
236 |
OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef); |
227 |
OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef); |
237 |
OCIO::MatrixTransformRcPtr swizzleTransform = OCIO::MatrixTransform::Create(); |
228 |
OCIO::MatrixTransformRcPtr swizzleTransform = OCIO::MatrixTransform::Create(); |
238 |
swizzleTransform->setValue(m44, offset); |
229 |
swizzleTransform->setMatrix(m44); |
239 |
transform->setChannelView(swizzleTransform); |
230 |
swizzleTransform->setOffset(offset); |
|
|
231 |
vpt->setChannelView(swizzleTransform); |
240 |
} |
232 |
} |
241 |
|
233 |
|
242 |
// Post-display transform gamma |
234 |
// Post-display transform gamma |
243 |
{ |
235 |
{ |
244 |
float exponent = 1.0f/std::max(1e-6f, static_cast<float>(gamma)); |
236 |
double exponent = 1.0 / std::max(1e-6, gamma); |
245 |
const float exponent4f[] = { exponent, exponent, exponent, exponent }; |
237 |
const double exponent4f[] = {exponent, exponent, exponent, exponent}; |
246 |
OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); |
238 |
OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); |
247 |
expTransform->setValue(exponent4f); |
239 |
expTransform->setValue(exponent4f); |
248 |
transform->setDisplayCC(expTransform); |
240 |
vpt->setDisplayCC(expTransform); |
249 |
|
241 |
|
250 |
// approximation (no color correction); |
242 |
// approximation (no color correction); |
251 |
approximateTransform->push_back(expTransform); |
243 |
approximateTransform->appendTransform(expTransform); |
252 |
} |
244 |
} |
253 |
|
245 |
|
254 |
m_processor = config->getProcessor(transform); |
246 |
try { |
|
|
247 |
m_processor = vpt->getProcessor(config, config->getCurrentContext()); |
248 |
} catch (OCIO::Exception &e) { |
249 |
// XXX: How to not break the OCIO shader now? |
250 |
errKrita << "OCIO exception while parsing the current context:" << e.what(); |
251 |
m_shaderDirty = false; |
252 |
return; |
253 |
} |
255 |
|
254 |
|
256 |
m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD); |
255 |
m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD); |
257 |
|
256 |
|
Lines 259-265
void OcioDisplayFilter::updateProcessor()
Link Here
|
259 |
m_revereseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE); |
258 |
m_revereseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE); |
260 |
} catch (...) { |
259 |
} catch (...) { |
261 |
warnKrita << "OCIO inverted matrix does not exist!"; |
260 |
warnKrita << "OCIO inverted matrix does not exist!"; |
262 |
//m_revereseApproximationProcessor; |
261 |
// m_revereseApproximationProcessor; |
263 |
} |
262 |
} |
264 |
|
263 |
|
265 |
m_shaderDirty = true; |
264 |
m_shaderDirty = true; |
Lines 272-287
bool OcioDisplayFilter::updateShader()
Link Here
|
272 |
if (f) { |
271 |
if (f) { |
273 |
return updateShaderImpl(f); |
272 |
return updateShaderImpl(f); |
274 |
} |
273 |
} |
|
|
274 |
#if defined(QT_OPENGL_3) |
275 |
} else if (KisOpenGL::hasOpenGL3()) { |
275 |
} else if (KisOpenGL::hasOpenGL3()) { |
276 |
QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); |
276 |
QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); |
277 |
if (f) { |
277 |
if (f) { |
278 |
return updateShaderImpl(f); |
278 |
return updateShaderImpl(f); |
279 |
} |
279 |
} |
|
|
280 |
#endif |
280 |
} |
281 |
} |
281 |
|
282 |
|
282 |
// XXX This option can be removed once we move to Qt 5.7+ |
283 |
// XXX This option can be removed once we move to Qt 5.7+ |
283 |
if (KisOpenGL::supportsLoD()) { |
284 |
if (KisOpenGL::supportsLoD()) { |
284 |
#ifdef Q_OS_MAC |
285 |
#if defined(QT_OPENGL_3) |
|
|
286 |
#if defined(Q_OS_MAC) && defined(QT_OPENGL_3_2) |
285 |
QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); |
287 |
QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); |
286 |
#else |
288 |
#else |
287 |
QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_0>(); |
289 |
QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_0>(); |
Lines 289-311
bool OcioDisplayFilter::updateShader()
Link Here
|
289 |
if (f) { |
291 |
if (f) { |
290 |
return updateShaderImpl(f); |
292 |
return updateShaderImpl(f); |
291 |
} |
293 |
} |
|
|
294 |
#endif |
292 |
} |
295 |
} |
293 |
QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions(); |
296 |
#if !defined(QT_OPENGL_ES_2) |
|
|
297 |
QOpenGLFunctions_2_0 *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_2_0>(); |
294 |
if (f) { |
298 |
if (f) { |
295 |
return updateShaderImpl(f); |
299 |
return updateShaderImpl(f); |
296 |
} |
300 |
} |
|
|
301 |
#endif |
297 |
|
302 |
|
298 |
return false; |
303 |
return false; |
299 |
} |
304 |
} |
300 |
|
305 |
|
301 |
template <class F> |
306 |
template<class F> |
302 |
bool OcioDisplayFilter::updateShaderImpl(F *f) { |
307 |
bool OcioDisplayFilter::updateShaderImpl(F *f) |
|
|
308 |
{ |
303 |
// check whether we are allowed to use shaders -- though that should |
309 |
// check whether we are allowed to use shaders -- though that should |
304 |
// work for everyone these days |
310 |
// work for everyone these days |
305 |
KisConfig cfg(true); |
311 |
KisConfig cfg(true); |
306 |
if (!cfg.useOpenGL()) return false; |
312 |
if (!cfg.useOpenGL()) |
|
|
313 |
return false; |
307 |
|
314 |
|
308 |
if (!m_shaderDirty) return false; |
315 |
if (!m_shaderDirty) |
|
|
316 |
return false; |
309 |
|
317 |
|
310 |
if (!f) { |
318 |
if (!f) { |
311 |
qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!"; |
319 |
qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!"; |
Lines 316-386
bool OcioDisplayFilter::updateShaderImpl(F *f) {
Link Here
|
316 |
|
324 |
|
317 |
bool shouldRecompileShader = false; |
325 |
bool shouldRecompileShader = false; |
318 |
|
326 |
|
319 |
const int lut3DEdgeSize = cfg.ocioLutEdgeSize(); |
327 |
// Step 1: Create a GPU Shader Description |
|
|
328 |
OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc(); |
320 |
|
329 |
|
321 |
if (m_lut3d.size() == 0) { |
330 |
#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 |
322 |
//dbgKrita << "generating lut"; |
331 |
if (KisOpenGL::supportsLoD()) { |
323 |
f->glGenTextures(1, &m_lut3dTexID); |
332 |
shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_3_0); |
|
|
333 |
} else { |
334 |
shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_ES_1_0); |
335 |
} |
336 |
#else |
337 |
if (KisOpenGL::supportsLoD()) { |
338 |
shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); |
339 |
} else { |
340 |
shaderDesc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_2); |
341 |
} |
342 |
#endif |
324 |
|
343 |
|
325 |
int num3Dentries = 3 * lut3DEdgeSize * lut3DEdgeSize * lut3DEdgeSize; |
344 |
shaderDesc->setFunctionName("OCIODisplay"); |
326 |
m_lut3d.fill(0.0, num3Dentries); |
345 |
shaderDesc->setResourcePrefix("ocio_"); |
327 |
|
346 |
|
328 |
f->glActiveTexture(GL_TEXTURE1); |
347 |
// Step 2: Compute the 3D LUT |
329 |
f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); |
348 |
#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 |
|
|
349 |
// ensure the new GPU pipeline is used with our GLES3 patch |
350 |
// this way users won't run into errors when using Angle along with OCIO |
351 |
const auto gpu = m_processor->getOptimizedGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT); |
352 |
#else |
353 |
const int lut3DEdgeSize = cfg.ocioLutEdgeSize(); |
354 |
const auto gpu = |
355 |
m_processor->getOptimizedLegacyGPUProcessor(OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT, lut3DEdgeSize); |
356 |
#endif |
330 |
|
357 |
|
331 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
358 |
gpu->extractGpuShaderInfo(shaderDesc); |
332 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
359 |
|
333 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
360 |
// OCIO v2 assumes you'll use the OglApp helpers |
334 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
361 |
// these are unusable from a Qt backend, because they rely on GLUT/GLFW |
335 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); |
362 |
// ociodisplay original pipeline: |
336 |
f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, |
363 |
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/508b3f4a0618435aeed2f45058208bdfa99e0887/src/apps/ociodisplay/main.cpp |
337 |
lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, |
364 |
// ociodisplay new pipeline is a single call: |
338 |
0, GL_RGB, GL_FLOAT, &m_lut3d.constData()[0]); |
365 |
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/ffddc3341f5775c7866fe2c93275e1d5e0b0540f/src/apps/ociodisplay/main.cpp#L427 |
|
|
366 |
// we need to replicate this loop: |
367 |
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/oglapp.cpp#L191-L223 |
368 |
// calls functions from here: |
369 |
// https://github.com/AcademySoftwareFoundation/OpenColorIO/blob/dd59baf555656e09f52c3838e85ccf154497ec1d/src/libutils/oglapphelpers/glsl.cpp |
370 |
|
371 |
for (const auto &tex : m_lut3dTexIDs) { |
372 |
f->glDeleteTextures(1, &tex.m_uid); |
339 |
} |
373 |
} |
340 |
|
374 |
|
341 |
// Step 1: Create a GPU Shader Description |
375 |
m_lut3dTexIDs.clear(); |
342 |
OCIO::GpuShaderDesc shaderDesc; |
|
|
343 |
|
376 |
|
344 |
if (KisOpenGL::supportsLoD()) { |
377 |
// This is the first available index for the textures. |
345 |
shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); |
378 |
unsigned currIndex = 1; |
346 |
} |
379 |
|
347 |
else { |
380 |
// Process the 3D LUT first. |
348 |
shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); |
381 |
|
|
|
382 |
const unsigned maxTexture3D = shaderDesc->getNum3DTextures(); |
383 |
for (unsigned idx = 0; idx < maxTexture3D; ++idx) { |
384 |
// 1. Get the information of the 3D LUT. |
385 |
|
386 |
const char *textureName = nullptr; |
387 |
const char *samplerName = nullptr; |
388 |
unsigned edgelen = 0; |
389 |
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; |
390 |
shaderDesc->get3DTexture(idx, textureName, samplerName, edgelen, interpolation); |
391 |
|
392 |
if (!textureName || !*textureName || !samplerName || !*samplerName || edgelen == 0) { |
393 |
errOpenGL << "The texture data is corrupted"; |
394 |
return false; |
395 |
} |
396 |
|
397 |
const float *values = nullptr; |
398 |
shaderDesc->get3DTextureValues(idx, values); |
399 |
if (!values) { |
400 |
errOpenGL << "The texture values are missing"; |
401 |
return false; |
402 |
} |
403 |
|
404 |
// 2. Allocate the 3D LUT. |
405 |
|
406 |
unsigned texId = 0; |
407 |
{ |
408 |
if (values == nullptr) { |
409 |
errOpenGL << "3D LUT" << idx << "Missing texture data"; |
410 |
return false; |
411 |
} |
412 |
|
413 |
f->glGenTextures(1, &texId); |
414 |
|
415 |
f->glActiveTexture(GL_TEXTURE0 + currIndex); |
416 |
|
417 |
f->glBindTexture(GL_TEXTURE_3D, texId); |
418 |
|
419 |
{ |
420 |
if (interpolation == OCIO::INTERP_NEAREST) { |
421 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
422 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
423 |
} else { |
424 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
425 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
426 |
} |
427 |
|
428 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
429 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
430 |
f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); |
431 |
} |
432 |
|
433 |
f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB32F_ARB, edgelen, edgelen, edgelen, 0, GL_RGB, GL_FLOAT, values); |
434 |
} |
435 |
|
436 |
// 3. Keep the texture id & name for the later enabling. |
437 |
|
438 |
m_lut3dTexIDs.push_back({texId, textureName, samplerName, GL_TEXTURE_3D}); |
439 |
|
440 |
currIndex++; |
349 |
} |
441 |
} |
350 |
|
442 |
|
|
|
443 |
// Process the 1D LUTs. |
351 |
|
444 |
|
352 |
shaderDesc.setFunctionName("OCIODisplay"); |
445 |
const unsigned maxTexture2D = shaderDesc->getNumTextures(); |
353 |
shaderDesc.setLut3DEdgeLen(lut3DEdgeSize); |
446 |
for (unsigned idx = 0; idx < maxTexture2D; ++idx) { |
|
|
447 |
// 1. Get the information of the 1D LUT. |
354 |
|
448 |
|
|
|
449 |
const char *textureName = nullptr; |
450 |
const char *samplerName = nullptr; |
451 |
unsigned width = 0; |
452 |
unsigned height = 0; |
453 |
OCIO::GpuShaderDesc::TextureType channel = OCIO::GpuShaderDesc::TEXTURE_RGB_CHANNEL; |
454 |
OCIO::Interpolation interpolation = OCIO::INTERP_LINEAR; |
455 |
shaderDesc->getTexture(idx, textureName, samplerName, width, height, channel, interpolation); |
355 |
|
456 |
|
356 |
// Step 2: Compute the 3D LUT |
457 |
if (!textureName || !*textureName || !samplerName || !*samplerName || width == 0) { |
357 |
QString lut3dCacheID = QString::fromLatin1(m_processor->getGpuLut3DCacheID(shaderDesc)); |
458 |
errOpenGL << "The texture data is corrupted"; |
358 |
if (lut3dCacheID != m_lut3dcacheid) { |
459 |
return false; |
359 |
//dbgKrita << "Computing 3DLut " << m_lut3dcacheid; |
460 |
} |
360 |
m_lut3dcacheid = lut3dCacheID; |
461 |
|
361 |
m_processor->getGpuLut3D(&m_lut3d[0], shaderDesc); |
462 |
const float *values = nullptr; |
362 |
|
463 |
shaderDesc->getTextureValues(idx, values); |
363 |
f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); |
464 |
if (!values) { |
364 |
f->glTexSubImage3D(GL_TEXTURE_3D, 0, |
465 |
errOpenGL << "The texture values are missing"; |
365 |
0, 0, 0, |
466 |
return false; |
366 |
lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, |
467 |
} |
367 |
GL_RGB, GL_FLOAT, &m_lut3d[0]); |
468 |
|
|
|
469 |
// 2. Allocate the 1D LUT (a 2D texture is needed to hold large LUTs). |
470 |
|
471 |
unsigned texId = 0; |
472 |
{ |
473 |
if (values == nullptr) { |
474 |
errOpenGL << "1D LUT" << idx << "Missing texture data."; |
475 |
return false; |
476 |
} |
477 |
|
478 |
unsigned internalformat = GL_RGB32F_ARB; |
479 |
unsigned format = GL_RGB; |
480 |
|
481 |
if (channel == OCIO::GpuShaderCreator::TEXTURE_RED_CHANNEL) { |
482 |
internalformat = GL_R32F; |
483 |
format = GL_RED; |
484 |
} |
485 |
|
486 |
f->glGenTextures(1, &texId); |
487 |
|
488 |
f->glActiveTexture(GL_TEXTURE0 + currIndex); |
489 |
|
490 |
#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 |
491 |
#else |
492 |
// 1D Textures are unsupported by OpenGL ES. |
493 |
// https://github.com/AcademySoftwareFoundation/OpenColorIO/issues/1486 |
494 |
if (height > 1) { |
495 |
#endif |
496 |
f->glBindTexture(GL_TEXTURE_2D, texId); |
497 |
|
498 |
{ |
499 |
if (interpolation == OCIO::INTERP_NEAREST) { |
500 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
501 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
502 |
} else { |
503 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
504 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
505 |
} |
506 |
|
507 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
508 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
509 |
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); |
510 |
} |
511 |
|
512 |
f->glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, GL_FLOAT, values); |
513 |
#if OCIO_VERSION_HEX >= 0x2010100 || OCIO_VERSION_HEX >= 0x2020000 |
514 |
#else |
515 |
} else { |
516 |
errOpenGL << "1D texture detected @" << idx << ", not supported by OpenGLES"; |
517 |
return false; |
518 |
} |
519 |
#endif |
520 |
} |
521 |
|
522 |
// 3. Keep the texture id & name for the later enabling. |
523 |
|
524 |
unsigned type = GL_TEXTURE_2D; |
525 |
m_lut3dTexIDs.push_back({texId, textureName, samplerName, type}); |
526 |
currIndex++; |
368 |
} |
527 |
} |
369 |
|
528 |
|
370 |
// Step 3: Generate the shader text |
529 |
// Step 3: Generate the shader text |
371 |
QString shaderCacheID = QString::fromLatin1(m_processor->getGpuShaderTextCacheID(shaderDesc)); |
530 |
QString shaderCacheID = QString::fromLatin1(shaderDesc->getCacheID()); |
372 |
if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) { |
531 |
if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) { |
373 |
//dbgKrita << "Computing Shader " << m_shadercacheid; |
532 |
// dbgKrita << "Computing Shader " << m_shadercacheid; |
374 |
|
533 |
|
375 |
m_shadercacheid = shaderCacheID; |
534 |
m_shadercacheid = shaderCacheID; |
376 |
|
535 |
|
377 |
std::ostringstream os; |
536 |
m_program = QString::fromLatin1("%1\n").arg(shaderDesc->getShaderText()); |
378 |
os << m_processor->getGpuShaderText(shaderDesc) << "\n"; |
|
|
379 |
|
380 |
m_program = QString::fromLatin1(os.str().c_str()); |
381 |
shouldRecompileShader = true; |
537 |
shouldRecompileShader = true; |
382 |
} |
538 |
} |
383 |
|
539 |
|
|
|
540 |
// Step 4: mirror and bind uniforms |
541 |
m_lut3dUniforms.clear(); |
542 |
|
543 |
const unsigned maxUniforms = shaderDesc->getNumUniforms(); |
544 |
for (unsigned idx = 0; idx < maxUniforms; ++idx) { |
545 |
OCIO::GpuShaderDesc::UniformData data; |
546 |
const char *name = shaderDesc->getUniform(idx, data); |
547 |
if (data.m_type == OCIO::UNIFORM_UNKNOWN) { |
548 |
errOpenGL << "Uniform" << idx << "has an unknown type"; |
549 |
return false; |
550 |
} |
551 |
// Transfer uniform. |
552 |
m_lut3dUniforms.push_back({name, data}); |
553 |
} |
554 |
|
384 |
m_shaderDirty = false; |
555 |
m_shaderDirty = false; |
385 |
return shouldRecompileShader; |
556 |
return shouldRecompileShader; |
386 |
} |
557 |
} |
|
|
558 |
|
559 |
void OcioDisplayFilter::setupTextures(QOpenGLFunctions *f, QOpenGLShaderProgram *program) const |
560 |
{ |
561 |
for (unsigned int idx = 0; idx < m_lut3dTexIDs.size(); ++idx) { |
562 |
const auto &data = m_lut3dTexIDs[idx]; |
563 |
f->glActiveTexture(GL_TEXTURE0 + 1 + idx); |
564 |
f->glBindTexture(data.m_type, data.m_uid); |
565 |
program->setUniformValue(program->uniformLocation(data.m_samplerName), GLint(1 + idx)); |
566 |
} |
567 |
|
568 |
for (const KisTextureUniform &uniform : m_lut3dUniforms) { |
569 |
const int m_handle = program->uniformLocation(uniform.m_name); |
570 |
|
571 |
const OCIO::GpuShaderDesc::UniformData &m_data = uniform.m_data; |
572 |
|
573 |
// Update value. |
574 |
if (m_data.m_getDouble) { |
575 |
program->setUniformValue(m_handle, static_cast<const GLfloat>(m_data.m_getDouble())); |
576 |
} else if (m_data.m_getBool) { |
577 |
program->setUniformValue(m_handle, static_cast<const GLfloat>(m_data.m_getBool() ? 1.0f : 0.0f)); |
578 |
} else if (m_data.m_getFloat3) { |
579 |
program->setUniformValue(m_handle, |
580 |
m_data.m_getFloat3()[0], |
581 |
m_data.m_getFloat3()[1], |
582 |
m_data.m_getFloat3()[2]); |
583 |
} else if (m_data.m_vectorFloat.m_getSize && m_data.m_vectorFloat.m_getVector) { |
584 |
program->setUniformValueArray(m_handle, |
585 |
m_data.m_vectorFloat.m_getVector(), |
586 |
m_data.m_vectorFloat.m_getSize(), |
587 |
1); |
588 |
} else if (m_data.m_vectorInt.m_getSize && m_data.m_vectorInt.m_getVector) { |
589 |
program->setUniformValueArray(m_handle, m_data.m_vectorInt.m_getVector(), m_data.m_vectorInt.m_getSize()); |
590 |
} else { |
591 |
errOpenGL << "Uniform" << uniform.m_name << "is not linked to any value"; |
592 |
continue; |
593 |
} |
594 |
} |
595 |
} |