File indexing completed on 2024-06-09 04:24:39

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Alvin Wong <alvinhochun@gmail.com>
0003  *  SPDX-FileCopyrightText: 2019 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "KisOpenGLModeProber.h"
0009 
0010 #include <config-hdr.h>
0011 #include <QApplication>
0012 #include <QOpenGLContext>
0013 #include <QOpenGLFunctions>
0014 #include <QWindow>
0015 
0016 #include <QGlobalStatic>
0017 Q_GLOBAL_STATIC(KisOpenGLModeProber, s_instance)
0018 
0019 
0020 KisOpenGLModeProber::KisOpenGLModeProber()
0021 {
0022 }
0023 
0024 KisOpenGLModeProber::~KisOpenGLModeProber()
0025 {
0026 
0027 }
0028 
0029 KisOpenGLModeProber *KisOpenGLModeProber::instance()
0030 {
0031     return s_instance;
0032 }
0033 
0034 bool KisOpenGLModeProber::useHDRMode() const
0035 {
0036     return isFormatHDR(QSurfaceFormat::defaultFormat());
0037 }
0038 
0039 QSurfaceFormat KisOpenGLModeProber::surfaceformatInUse() const
0040 {
0041     // TODO: use information provided by KisOpenGL instead
0042     QOpenGLContext *sharedContext = QOpenGLContext::globalShareContext();
0043     QSurfaceFormat format = sharedContext ? sharedContext->format() : QSurfaceFormat::defaultFormat();
0044     return format;
0045 }
0046 
0047 const KoColorProfile *KisOpenGLModeProber::rootSurfaceColorProfile() const
0048 {
0049     const KoColorProfile *profile = KoColorSpaceRegistry::instance()->p709SRGBProfile();
0050 
0051 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0052 
0053     const KisSurfaceColorSpace surfaceColorSpace = surfaceformatInUse().colorSpace();
0054     if (surfaceColorSpace == KisSurfaceColorSpace::sRGBColorSpace) {
0055         // use the default one!
0056 #ifdef HAVE_HDR
0057     } else if (surfaceColorSpace == KisSurfaceColorSpace::scRGBColorSpace) {
0058         profile = KoColorSpaceRegistry::instance()->p709G10Profile();
0059     } else if (surfaceColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace) {
0060         profile = KoColorSpaceRegistry::instance()->p2020PQProfile();
0061 #endif
0062     }
0063 
0064 #endif
0065 
0066     return profile;
0067 }
0068 
0069 namespace {
0070 struct AppAttributeSetter
0071 {
0072     AppAttributeSetter(Qt::ApplicationAttribute attribute, bool useOpenGLES)
0073         : m_attribute(attribute),
0074           m_oldValue(QCoreApplication::testAttribute(attribute))
0075     {
0076         QCoreApplication::setAttribute(attribute, useOpenGLES);
0077     }
0078 
0079     ~AppAttributeSetter() {
0080         QCoreApplication::setAttribute(m_attribute, m_oldValue);
0081     }
0082 
0083 private:
0084     Qt::ApplicationAttribute m_attribute;
0085     bool m_oldValue = false;
0086 };
0087 
0088 struct SurfaceFormatSetter
0089 {
0090     SurfaceFormatSetter(const QSurfaceFormat &format)
0091         : m_oldFormat(QSurfaceFormat::defaultFormat())
0092     {
0093         QSurfaceFormat::setDefaultFormat(format);
0094     }
0095 
0096     ~SurfaceFormatSetter() {
0097         QSurfaceFormat::setDefaultFormat(m_oldFormat);
0098     }
0099 
0100 private:
0101     QSurfaceFormat m_oldFormat;
0102 };
0103 
0104 
0105 #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
0106 QString qEnvironmentVariable(const char *varName) {
0107     return qgetenv(varName);
0108 }
0109 #endif
0110 
0111 struct EnvironmentSetter
0112 {
0113     EnvironmentSetter(const QLatin1String &env, const QString &value)
0114         : m_env(env)
0115     {
0116         if (qEnvironmentVariableIsEmpty(m_env.latin1())) {
0117             m_oldValue = qgetenv(env.latin1());
0118         }
0119         if (!value.isEmpty()) {
0120             qputenv(env.latin1(), value.toLatin1());
0121         } else {
0122             qunsetenv(env.latin1());
0123         }
0124     }
0125 
0126     ~EnvironmentSetter() {
0127         if (m_oldValue) {
0128             qputenv(m_env.latin1(), (*m_oldValue).toLatin1());
0129         } else {
0130             qunsetenv(m_env.latin1());
0131         }
0132     }
0133 
0134 private:
0135     const QLatin1String m_env;
0136     boost::optional<QString> m_oldValue;
0137 };
0138 
0139 }
0140 
0141 boost::optional<KisOpenGLModeProber::Result>
0142 KisOpenGLModeProber::probeFormat(const KisOpenGL::RendererConfig &rendererConfig,
0143                                  bool adjustGlobalState)
0144 {
0145     const QSurfaceFormat &format = rendererConfig.format;
0146 
0147     dbgOpenGL << "Probing format" << rendererConfig.rendererId() << rendererConfig.angleRenderer
0148               << rendererConfig.format;
0149 
0150     QScopedPointer<AppAttributeSetter> sharedContextSetter;
0151     QScopedPointer<AppAttributeSetter> glSetter;
0152     QScopedPointer<AppAttributeSetter> glesSetter;
0153     QScopedPointer<SurfaceFormatSetter> formatSetter;
0154     QScopedPointer<EnvironmentSetter> rendererSetter;
0155     QScopedPointer<EnvironmentSetter> portalSetter;
0156     QScopedPointer<QGuiApplication> application;
0157 
0158     int argc = 1;
0159     QByteArray probeAppName("krita");
0160     char *argv = probeAppName.data();
0161 
0162 
0163 
0164     if (adjustGlobalState) {
0165         sharedContextSetter.reset(new AppAttributeSetter(Qt::AA_ShareOpenGLContexts, false));
0166 
0167         if (format.renderableType() != QSurfaceFormat::DefaultRenderableType) {
0168             glSetter.reset(new AppAttributeSetter(Qt::AA_UseDesktopOpenGL, format.renderableType() != QSurfaceFormat::OpenGLES));
0169             glesSetter.reset(new AppAttributeSetter(Qt::AA_UseOpenGLES, format.renderableType() == QSurfaceFormat::OpenGLES));
0170         }
0171 
0172         rendererSetter.reset(new EnvironmentSetter(QLatin1String("QT_ANGLE_PLATFORM"), angleRendererToString(rendererConfig.angleRenderer)));
0173         portalSetter.reset(new EnvironmentSetter(QLatin1String("QT_NO_XDG_DESKTOP_PORTAL"), QLatin1String("1")));
0174         formatSetter.reset(new SurfaceFormatSetter(format));
0175 
0176         // Disable this workaround for plasma (BUG:408015), because it causes 
0177         // a crash on Windows with Qt 5.15.7
0178         //QGuiApplication::setDesktopSettingsAware(false);
0179         application.reset(new QGuiApplication(argc, &argv));
0180         //QGuiApplication::setDesktopSettingsAware(true);
0181     }
0182 
0183     QWindow surface;
0184     surface.setFormat(format);
0185     surface.setSurfaceType(QSurface::OpenGLSurface);
0186     surface.create();
0187     QOpenGLContext context;
0188     context.setFormat(format);
0189 
0190 
0191     if (!context.create()) {
0192         dbgOpenGL << "OpenGL context cannot be created";
0193         return boost::none;
0194     }
0195     if (!context.isValid()) {
0196         dbgOpenGL << "OpenGL context is not valid while checking Qt's OpenGL status";
0197         return boost::none;
0198     }
0199     if (!context.makeCurrent(&surface)) {
0200         dbgOpenGL << "OpenGL context cannot be made current";
0201         return boost::none;
0202     }
0203 
0204 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0205     if (!fuzzyCompareColorSpaces(context.format().colorSpace(), format.colorSpace())) {
0206         dbgOpenGL << "Failed to create an OpenGL context with requested color space. Requested:" << format.colorSpace() << "Actual:" << context.format().colorSpace();
0207         return boost::none;
0208     }
0209 #endif
0210 
0211     Result result(context);
0212 
0213     dbgOpenGL << "Probe returned" << result.rendererString() << result.driverVersionString() << result.isOpenGLES();
0214 
0215     return result;
0216 }
0217 
0218 bool KisOpenGLModeProber::fuzzyCompareColorSpaces(const KisSurfaceColorSpace &lhs, const KisSurfaceColorSpace &rhs)
0219 {
0220     return lhs == rhs ||
0221         ((lhs == KisSurfaceColorSpace::DefaultColorSpace ||
0222           lhs == KisSurfaceColorSpace::sRGBColorSpace) &&
0223          (rhs == KisSurfaceColorSpace::DefaultColorSpace ||
0224           rhs == KisSurfaceColorSpace::sRGBColorSpace));
0225 }
0226 
0227 void KisOpenGLModeProber::initSurfaceFormatFromConfig(KisConfig::RootSurfaceFormat config,
0228                                                       QSurfaceFormat *format)
0229 {
0230 #ifdef HAVE_HDR
0231     if (config == KisConfig::BT2020_PQ) {
0232 
0233         format->setRedBufferSize(10);
0234         format->setGreenBufferSize(10);
0235         format->setBlueBufferSize(10);
0236         format->setAlphaBufferSize(2);
0237         format->setColorSpace(KisSurfaceColorSpace::bt2020PQColorSpace);
0238     } else if (config == KisConfig::BT709_G10) {
0239         format->setRedBufferSize(16);
0240         format->setGreenBufferSize(16);
0241         format->setBlueBufferSize(16);
0242         format->setAlphaBufferSize(16);
0243         format->setColorSpace(KisSurfaceColorSpace::scRGBColorSpace);
0244     } else
0245 #else
0246     if (config == KisConfig::BT2020_PQ) {
0247         qWarning() << "WARNING: Bt.2020 PQ surface type is not supported by this build of Krita";
0248     } else if (config == KisConfig::BT709_G10) {
0249         qWarning() << "WARNING: scRGB surface type is not supported by this build of Krita";
0250     }
0251 #endif
0252 
0253     {
0254         format->setRedBufferSize(8);
0255         format->setGreenBufferSize(8);
0256         format->setBlueBufferSize(8);
0257 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0258         format->setAlphaBufferSize(8);
0259 #else
0260         format->setAlphaBufferSize(0);
0261 #endif
0262 
0263 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0264         // TODO: check if we can use real sRGB space here
0265         format->setColorSpace(KisSurfaceColorSpace::DefaultColorSpace);
0266 #endif
0267     }
0268 }
0269 
0270 bool KisOpenGLModeProber::isFormatHDR(const QSurfaceFormat &format)
0271 {
0272 #ifdef HAVE_HDR
0273 
0274     bool isBt2020PQ =
0275         format.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace &&
0276         format.redBufferSize() == 10 &&
0277         format.greenBufferSize() == 10 &&
0278         format.blueBufferSize() == 10 &&
0279         format.alphaBufferSize() == 2;
0280 
0281     bool isBt709G10 =
0282         format.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace &&
0283         format.redBufferSize() == 16 &&
0284         format.greenBufferSize() == 16 &&
0285         format.blueBufferSize() == 16 &&
0286         format.alphaBufferSize() == 16;
0287 
0288     return isBt2020PQ || isBt709G10;
0289 #else
0290     Q_UNUSED(format);
0291     return false;
0292 #endif
0293 }
0294 
0295 QString KisOpenGLModeProber::angleRendererToString(KisOpenGL::AngleRenderer renderer)
0296 {
0297     QString value;
0298 
0299     switch (renderer) {
0300     case KisOpenGL::AngleRendererDefault:
0301         break;
0302     case KisOpenGL::AngleRendererD3d9:
0303         value = "d3d9";
0304         break;
0305     case KisOpenGL::AngleRendererD3d11:
0306         value = "d3d11";
0307         break;
0308     case KisOpenGL::AngleRendererD3d11Warp:
0309         value = "warp";
0310         break;
0311     };
0312 
0313     return value;
0314 }
0315 
0316 KisOpenGLModeProber::Result::Result(QOpenGLContext &context) {
0317     if (!context.isValid()) {
0318         return;
0319     }
0320 
0321     QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used
0322 
0323     m_rendererString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER)));
0324     m_driverVersionString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION)));
0325     m_vendorString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR)));
0326     m_shadingLanguageString = QString(reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)));
0327     m_glMajorVersion = context.format().majorVersion();
0328     m_glMinorVersion = context.format().minorVersion();
0329     m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
0330     m_isOpenGLES = context.isOpenGLES();
0331     m_format = context.format();
0332     m_supportsFBO = context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers);
0333 
0334     m_supportsBufferMapping = !m_isOpenGLES ||
0335             m_glMajorVersion >= 3 ||
0336             context.hasExtension("GL_OES_mapbuffer") ||
0337             context.hasExtension("GL_EXT_map_buffer_range") ||
0338             context.hasExtension("GL_ARB_map_buffer_range");
0339 
0340     m_supportsBufferInvalidation = !m_isOpenGLES &&
0341             ((m_glMajorVersion >= 4 && m_glMinorVersion >= 3) ||
0342              context.hasExtension("GL_ARB_invalidate_subdata"));
0343     m_supportsLod = context.format().majorVersion() >= 3 || (m_isOpenGLES && context.hasExtension("GL_EXT_shader_texture_lod"));
0344 
0345     m_extensions = context.extensions();
0346     // Remove empty name extension that sometimes appears on NVIDIA output
0347     m_extensions.remove("");
0348 }