Krita Source Code Documentation
Loading...
Searching...
No Matches
KisOpenGLModeProber.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Alvin Wong <alvinhochun@gmail.com>
3 * SPDX-FileCopyrightText: 2019 Dmitry Kazakov <dimula73@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10#include <config-hdr.h>
11#include <QApplication>
12#include <QOpenGLContext>
13#include <QOpenGLFunctions>
14#include <QWindow>
15#include <QColorSpace>
16#include <KoConfig.h>
17
18#ifdef HAVE_HDR
19// for fetching the legacy 80-nits PQ space
20#include <KoColorProfileQuery.h>
21#endif
22
23#ifdef HAVE_X11
24#include <qpa/qplatformnativeinterface.h>
25#endif
26
27#include <QGlobalStatic>
29
31
32
36
41
43{
44 return s_instance;
45}
46
48{
49 return isFormatHDR(QSurfaceFormat::defaultFormat());
50}
51
53{
54 // TODO: use information provided by KisOpenGL instead
55 QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext();
56 QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat();
57 return format;
58}
59
61{
63
64 const auto surfaceColorSpace =
66
67 if (surfaceColorSpace == KisSurfaceColorSpaceWrapper::sRGBColorSpace) {
68 // use the default one!
69#ifdef HAVE_HDR
70 } else if (surfaceColorSpace == KisSurfaceColorSpaceWrapper::scRGBColorSpace) {
72 } else if (surfaceColorSpace == KisSurfaceColorSpaceWrapper::bt2020PQColorSpace) {
73 // On Windows we use the legacy PQ profile, explicitly pinned to
74 // 80 nits. It is baked in the Qt's source code
76 query.hdrReferenceWhite = 80.0;
78#endif
79 }
80
81 return profile;
82}
83
84namespace {
85struct AppAttributeSetter
86{
87 AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
88 : m_attribute(attribute),
89 m_oldValue(QCoreApplication::testAttribute(attribute))
90 {
91 QCoreApplication::setAttribute(attribute, useOpenGLES);
92 }
93
94 ~AppAttributeSetter() {
95 QCoreApplication::setAttribute(m_attribute, m_oldValue);
96 }
97
98private:
99 Qt::ApplicationAttribute m_attribute;
100 bool m_oldValue = false;
101};
102
103struct SurfaceFormatSetter
104{
105 SurfaceFormatSetter(const QSurfaceFormat &format)
106 : m_oldFormat(QSurfaceFormat::defaultFormat())
107 {
108 QSurfaceFormat::setDefaultFormat(format);
109 }
110
111 ~SurfaceFormatSetter() {
112 QSurfaceFormat::setDefaultFormat(m_oldFormat);
113 }
114
115private:
116 QSurfaceFormat m_oldFormat;
117};
118
119
120struct EnvironmentSetter
121{
122 EnvironmentSetter(const QLatin1String &env, const QString &value)
123 : m_env(env)
124 {
125 if (!qEnvironmentVariableIsEmpty(m_env.latin1())) {
126 m_oldValue = qgetenv(env.latin1());
127 }
128 if (!value.isEmpty()) {
129 qputenv(env.latin1(), value.toLatin1());
130 } else {
131 qunsetenv(env.latin1());
132 }
133 }
134
135 ~EnvironmentSetter() {
136 if (m_oldValue) {
137 qputenv(m_env.latin1(), (*m_oldValue).toLatin1());
138 } else {
139 qunsetenv(m_env.latin1());
140 }
141 }
142
143private:
144 const QLatin1String m_env;
145 boost::optional<QString> m_oldValue;
146};
147
148}
149
150boost::optional<KisOpenGLModeProber::Result>
152 bool adjustGlobalState)
153{
154 const QSurfaceFormat &format = rendererConfig.format;
155
156 dbgOpenGL << "Probing format" << rendererConfig.rendererId() << rendererConfig.angleRenderer
157 << rendererConfig.format;
158
159 QScopedPointer<AppAttributeSetter> sharedContextSetter;
160 QScopedPointer<AppAttributeSetter> glSetter;
161 QScopedPointer<AppAttributeSetter> glesSetter;
162 QScopedPointer<SurfaceFormatSetter> formatSetter;
163 QScopedPointer<EnvironmentSetter> rendererSetter;
164 QScopedPointer<EnvironmentSetter> portalSetter;
165 QScopedPointer<QGuiApplication> application;
166
167 int argc = 1;
168 QByteArray probeAppName("krita");
169 char *argv = probeAppName.data();
170
171
172 if (adjustGlobalState) {
173 sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
174
175 if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
176 glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
177 glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
178 }
179
180 if (!qEnvironmentVariableIsSet("QT_ANGLE_PLATFORM")) {
181 rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer)));
182 }
183 portalSetter.reset(new EnvironmentSetter(QLatin1String("QT_NO_XDG_DESKTOP_PORTAL"), QLatin1String("1")));
184 formatSetter.reset(new SurfaceFormatSetter(format));
185
186 // Disable this workaround for plasma (BUG:408015), because it causes
187 // a crash on Windows with Qt 5.15.7
188 const bool runningInKDE = qEnvironmentVariableIsSet("KDE_FULL_SESSION");
189 const bool isInAppimage = qEnvironmentVariableIsSet("APPIMAGE");
190
191 if (runningInKDE && !isInAppimage) {
192 QGuiApplication::setDesktopSettingsAware(false);
193 }
194
195 application.reset(new QGuiApplication(argc, &argv));
196
197 if (runningInKDE && !isInAppimage) {
198 QGuiApplication::setDesktopSettingsAware(true);
199 }
200
201 }
202
203 QWindow surface;
204 surface.setFormat(format);
205 surface.setSurfaceType(QSurface::OpenGLSurface);
206 surface.create();
207 QOpenGLContext context;
208 context.setFormat(format);
209
210
211 if (!context.create()) {
212 dbgOpenGL << "OpenGL context cannot be created";
213 return boost::none;
214 }
215 if (!context.isValid()) {
216 dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status";
217 return boost::none;
218 }
219 if (!context.makeCurrent(&surface)) {
220 dbgOpenGL << "OpenGL context cannot be made current";
221 return boost::none;
222 }
223
225 KisSurfaceColorSpaceWrapper::fromQtColorSpace(context.format().colorSpace()),
226 KisSurfaceColorSpaceWrapper::fromQtColorSpace(format.colorSpace()))) {
227
228 dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
229 return boost::none;
230 }
231
232 if (format.redBufferSize() > 0 && format.greenBufferSize() > 0 && format.blueBufferSize() > 0
233 && (context.format().redBufferSize() != format.redBufferSize()
234 || context.format().greenBufferSize() != format.greenBufferSize()
235 || context.format().blueBufferSize() != format.blueBufferSize())) {
236
237 dbgOpenGL << "Failed to create an OpenGL context with requested bit depth. Requested:" << format.redBufferSize()
238 << "Actual:" << context.format().redBufferSize();
239 return boost::none;
240 }
241
242 Result result(context);
243
244 dbgOpenGL << "Probe returned" << result.rendererString() << result.driverVersionString() << result.isOpenGLES();
245
246 return result;
247}
248
257
258void KisOpenGLModeProber::initSurfaceFormatFromConfig(std::pair<KisSurfaceColorSpaceWrapper, int> rootSurfaceFormat,
259 QSurfaceFormat *format)
260{
261#ifdef HAVE_HDR
265 if (rootSurfaceFormat.first == KisSurfaceColorSpaceWrapper::bt2020PQColorSpace) {
266 KIS_SAFE_ASSERT_RECOVER_NOOP(rootSurfaceFormat.second == 10);
267 format->setRedBufferSize(10);
268 format->setGreenBufferSize(10);
269 format->setBlueBufferSize(10);
270 format->setAlphaBufferSize(2);
272 } else if (rootSurfaceFormat.first == KisSurfaceColorSpaceWrapper::scRGBColorSpace) {
273 KIS_SAFE_ASSERT_RECOVER_NOOP(rootSurfaceFormat.second == 16);
274 format->setRedBufferSize(16);
275 format->setGreenBufferSize(16);
276 format->setBlueBufferSize(16);
277 format->setAlphaBufferSize(16);
279 } else {
280 KIS_SAFE_ASSERT_RECOVER_NOOP(rootSurfaceFormat.second == 8);
281 format->setRedBufferSize(8);
282 format->setGreenBufferSize(8);
283 format->setBlueBufferSize(8);
284 format->setAlphaBufferSize(8);
285 // TODO: check if we can use real sRGB space here
286 format->setColorSpace(KisSurfaceColorSpaceWrapper());
287 }
288#else
293 if (rootSurfaceFormat.first == KisSurfaceColorSpaceWrapper::bt2020PQColorSpace) {
294 qWarning() << "WARNING: Bt.2020 PQ surface type is not supported by this build of Krita";
295 rootSurfaceFormat.first = KisSurfaceColorSpaceWrapper::DefaultColorSpace;
296 } else if (rootSurfaceFormat.first == KisSurfaceColorSpaceWrapper::scRGBColorSpace) {
297 qWarning() << "WARNING: scRGB surface type is not supported by this build of Krita";
298 rootSurfaceFormat.first = KisSurfaceColorSpaceWrapper::DefaultColorSpace;
299 } else {
301 KIS_SAFE_ASSERT_RECOVER_NOOP(rootSurfaceFormat.second == 8 || rootSurfaceFormat.second == 10);
302
303 if (rootSurfaceFormat.second == 10) {
304 format->setRedBufferSize(10);
305 format->setGreenBufferSize(10);
306 format->setBlueBufferSize(10);
307 format->setAlphaBufferSize(2);
308 // TODO: check if we can use real sRGB space here
309 format->setColorSpace(KisSurfaceColorSpaceWrapper());
310 } else {
311 format->setRedBufferSize(8);
312 format->setGreenBufferSize(8);
313 format->setBlueBufferSize(8);
314 format->setAlphaBufferSize(8);
315 // TODO: check if we can use real sRGB space here
316 format->setColorSpace(KisSurfaceColorSpaceWrapper());
317 }
318 }
319#endif
320}
321
322bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format)
323{
324#ifdef HAVE_HDR
325
326 bool isBt2020PQ =
328 format.redBufferSize() == 10 &&
329 format.greenBufferSize() == 10 &&
330 format.blueBufferSize() == 10 &&
331 format.alphaBufferSize() == 2;
332
333 bool isBt709G10 =
334 format.colorSpace() == KisSurfaceColorSpaceWrapper::makeSCRGBColorSpace() &&
335 format.redBufferSize() == 16 &&
336 format.greenBufferSize() == 16 &&
337 format.blueBufferSize() == 16 &&
338 format.alphaBufferSize() == 16;
339
340 return isBt2020PQ || isBt709G10;
341#else
342 Q_UNUSED(format);
343 return false;
344#endif
345}
346
348{
349 QString value;
350
351 switch (renderer) {
353 break;
355 value = "d3d9";
356 break;
358 value = "d3d11";
359 break;
361 value = "warp";
362 break;
363 };
364
365 return value;
366}
367
368KisOpenGLModeProber::Result::Result(QOpenGLContext &context) {
369 if (!context.isValid()) {
370 return;
371 }
372
373 QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
374
375 m_rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
376 m_driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
377 m_vendorString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR)));
378 m_shadingLanguageString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)));
379 m_glMajorVersion = context.format().majorVersion();
380 m_glMinorVersion = context.format().minorVersion();
381 m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
382 m_isOpenGLES = context.isOpenGLES();
383 m_format = context.format();
384 m_supportsFBO = context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers);
385
387 m_glMajorVersion >= 3 ||
388 context.hasExtension("GL_OES_mapbuffer") ||
389 context.hasExtension("GL_EXT_map_buffer_range") ||
390 context.hasExtension("GL_ARB_map_buffer_range");
391
393 ((m_glMajorVersion >= 4 && m_glMinorVersion >= 3) ||
394 context.hasExtension("GL_ARB_invalidate_subdata"));
395 m_supportsLod = context.format().majorVersion() >= 3 || (m_isOpenGLES && context.hasExtension("GL_EXT_shader_texture_lod"));
396
397#ifdef HAVE_X11
398 if (QApplication::platformName() == "xcb") {
399 QPlatformNativeInterface *native = qApp->platformNativeInterface();
400 if (native->nativeResourceFunctionForContext("eglcontext")) {
402 } else if (native->nativeResourceFunctionForContext("glxcontext")) {
404 } else {
405 qWarning() << "WARNING: Failed to detect QXcbGlIntegration type!";
406 }
407 }
408#endif /* HAVE_X11 */
409
410 m_extensions = context.extensions();
411 // Remove empty name extension that sometimes appears on NVIDIA output
412 m_extensions.remove("");
413}
float value(const T *src, size_t ch)
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
@ PRIMARIES_ITU_R_BT_2020_2_AND_2100_0
@ TRC_ITU_R_BT_2100_0_PQ
std::optional< KisOpenGL::XcbGLProviderProtocol > m_xcbGlProviderProtocol
Result(QOpenGLContext &context)
static KisOpenGLModeProber * instance()
boost::optional< Result > probeFormat(const KisOpenGL::RendererConfig &rendererConfig, bool adjustGlobalState=true)
const KoColorProfile * rootSurfaceColorProfile() const
QSurfaceFormat surfaceformatInUse() const
static bool isFormatHDR(const QSurfaceFormat &format)
static void initSurfaceFormatFromConfig(std::pair< KisSurfaceColorSpaceWrapper, int > rootSurfaceFormat, QSurfaceFormat *format)
static QString angleRendererToString(KisOpenGL::AngleRenderer renderer)
static bool fuzzyCompareColorSpaces(const KisSurfaceColorSpaceWrapper &lhs, const KisSurfaceColorSpaceWrapper &rhs)
@ AngleRendererD3d11Warp
Definition kis_opengl.h:53
@ AngleRendererD3d11
Definition kis_opengl.h:51
@ AngleRendererD3d9
Definition kis_opengl.h:52
@ AngleRendererDefault
Definition kis_opengl.h:50
static constexpr KisSurfaceColorSpaceWrapper makeSCRGBColorSpace()
static KisSurfaceColorSpaceWrapper fromQtColorSpace(const QColorSpace &colorSpace)
static constexpr KisSurfaceColorSpaceWrapper makeBt2020PQColorSpace()
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define dbgOpenGL
Definition kis_debug.h:63
#define GL_RENDERER
AngleRenderer angleRenderer
Definition kis_opengl.h:58
QSurfaceFormat format
Definition kis_opengl.h:57
OpenGLRenderer rendererId() const
The KoColorProfileQuery struct.
std::optional< double > hdrReferenceWhite
CICP-compatible enum value representing the transfer function.
static KoColorSpaceRegistry * instance()
const KoColorProfile * profileFor(const KoColorProfileQuery &query, const bool generate=true) const
profileFor tries to find the profile that matches these characteristics, if no such profile is found,...
const KoColorProfile * p709G10Profile() const
const KoColorProfile * p709SRGBProfile() const