File indexing completed on 2024-05-26 04:30:20

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Adrian Page <adrian@pagenet.plus.com>
0003  *  SPDX-FileCopyrightText: 2023 L. E. Segovia <amy@amyspark.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <tuple>
0009 
0010 #include <boost/optional.hpp>
0011 
0012 #include <QOpenGLContext>
0013 #include <QOpenGLDebugLogger>
0014 #include <QOpenGLFunctions>
0015 
0016 #include <QApplication>
0017 #include <QDesktopWidget>
0018 #include <QPixmapCache>
0019 
0020 #include <QDir>
0021 #include <QFile>
0022 #include <QStandardPaths>
0023 #include <QVector>
0024 #include <QWindow>
0025 #include <QRegularExpression>
0026 #include <QSettings>
0027 #include <QScreen>
0028 
0029 #include <klocalizedstring.h>
0030 
0031 #include <KisRepaintDebugger.h>
0032 #include <KisUsageLogger.h>
0033 #include <kis_assert.h>
0034 #include <kis_config.h>
0035 #include <kis_debug.h>
0036 
0037 #include "KisOpenGLModeProber.h"
0038 #include "opengl/kis_opengl.h"
0039 
0040 #include <config-hdr.h>
0041 
0042 #ifndef GL_RENDERER
0043 #  define GL_RENDERER 0x1F01
0044 #endif
0045 
0046 /// openGL ES headers use a bit different names in prototypes,
0047 /// add a workaround for it
0048 #if !defined APIENTRYP && defined GL_APIENTRYP
0049 #define APIENTRYP GL_APIENTRYP
0050 #endif
0051 
0052 typedef void (APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC) (GLuint buffer);
0053 
0054 namespace
0055 {
0056     // config option, set manually by main()
0057     bool g_isDebugSynchronous = false;
0058 
0059     bool g_sanityDefaultFormatIsSet = false;
0060 
0061     boost::optional<KisOpenGLModeProber::Result> openGLCheckResult;
0062 
0063     bool g_needsFenceWorkaround = false;
0064 
0065     QString g_surfaceFormatDetectionLog;
0066     QString g_debugText("OpenGL Info\n  **OpenGL not initialized**");
0067 
0068     QVector<KLocalizedString> g_openglWarningStrings;
0069 
0070     using DetectedRenderer = std::tuple<QString, QString, bool>;
0071     QVector<DetectedRenderer> g_detectedRenderers;
0072     KisOpenGL::OpenGLRenderers g_supportedRenderers;
0073     KisOpenGL::OpenGLRenderer g_rendererPreferredByQt;
0074 
0075     bool g_useBufferInvalidation = false;
0076     PFNGLINVALIDATEBUFFERDATAPROC g_glInvalidateBufferData = nullptr;
0077 
0078     bool g_forceDisableTextureBuffers = false;
0079 
0080     void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) {
0081         g_supportedRenderers = supportedRenderers;
0082         g_rendererPreferredByQt = preferredByQt;
0083     }
0084 
0085     void appendOpenGLWarningString(KLocalizedString warning)
0086     {
0087         g_openglWarningStrings << warning;
0088     }
0089 
0090     void overrideOpenGLWarningString(QVector<KLocalizedString> warnings)
0091     {
0092         g_openglWarningStrings = warnings;
0093     }
0094 
0095     void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) {
0096         qDebug() << "OpenGL:" << debugMessage;
0097     }
0098 
0099     KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) {
0100 
0101         KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL;
0102 
0103         if (info.isOpenGLES()) {
0104             const QString rendererString = info.rendererString().toLower();
0105 
0106             if (rendererString.contains("basic render driver") ||
0107                 rendererString.contains("software")) {
0108 
0109                 result = KisOpenGL::RendererSoftware;
0110             } else {
0111                 result = KisOpenGL::RendererOpenGLES;
0112             }
0113         }
0114 
0115         return result;
0116     }
0117 }
0118 
0119 void KisOpenGL::initialize()
0120 {
0121     if (openGLCheckResult) return;
0122 
0123     KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet);
0124 
0125     KisOpenGL::RendererConfig config;
0126     config.format = QSurfaceFormat::defaultFormat();
0127 
0128     openGLCheckResult =
0129         KisOpenGLModeProber::instance()->probeFormat(config, false);
0130 
0131 #ifdef Q_OS_WIN
0132 
0133     if (!qEnvironmentVariableIsSet("KRITA_UNLOCK_TEXTURE_BUFFERS") &&
0134         openGLCheckResult->rendererString().toUpper().contains("ANGLE")) {
0135 
0136         // Angle should always be openGLES...
0137         KIS_SAFE_ASSERT_RECOVER_NOOP(KisOpenGL::hasOpenGLES());
0138 
0139         /**
0140          * Angle works badly with texture buffers on DirectX. It does no distinction
0141          * between stream and dynamic buffers, therefore it does too many copies of the
0142          * data:
0143          *
0144          * source user data -> ram buffer -> staging buffer -> native buffer -> texture,
0145          *
0146          * which is extremely slow when painting with big brushes. On Angle we should
0147          * just use normal unbuffered texture uploads
0148          */
0149 
0150         g_forceDisableTextureBuffers = true;
0151         appendOpenGLWarningString(
0152             ki18n("Texture buffers are explicitly disabled on ANGLE renderer due "
0153                   "to performance issues."));
0154     }
0155 #endif
0156 
0157 
0158     g_debugText.clear();
0159     QDebug debugOut(&g_debugText);
0160     debugOut << "OpenGL Info\n";
0161 
0162     if (openGLCheckResult) {
0163         debugOut << "\n  Vendor: " << openGLCheckResult->vendorString();
0164         debugOut << "\n  Renderer: " << openGLCheckResult->rendererString();
0165         debugOut << "\n  Driver version: " << openGLCheckResult->driverVersionString();
0166         debugOut << "\n  Shading language: " << openGLCheckResult->shadingLanguageString();
0167         debugOut << "\n  Requested format: " << QSurfaceFormat::defaultFormat();
0168         debugOut << "\n  Current format: " << openGLCheckResult->format();
0169         {
0170             QDebugStateSaver saver(debugOut);
0171             debugOut.nospace() << "\n  GL version: " << openGLCheckResult->glMajorVersion() << "."
0172                                << openGLCheckResult->glMinorVersion();
0173         }
0174         debugOut << "\n  Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions();
0175         debugOut << "\n  Is OpenGL ES:" << openGLCheckResult->isOpenGLES();
0176         debugOut << "\n  supportsBufferMapping:" << openGLCheckResult->supportsBufferMapping();
0177         debugOut << "\n  supportsBufferInvalidation:" << openGLCheckResult->supportsBufferInvalidation();
0178         debugOut << "\n  forceDisableTextureBuffers:" << g_forceDisableTextureBuffers;
0179         debugOut << "\n  Extensions:";
0180         {
0181             QDebugStateSaver saver(debugOut);
0182             Q_FOREACH (const QByteArray &i, openGLCheckResult->extensions()) {
0183                 debugOut.noquote() << "\n    " << QString::fromLatin1(i);
0184             }
0185         }
0186     }
0187 
0188     debugOut << "\n\nQPA OpenGL Detection Info";
0189     debugOut << "\n  supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL);
0190 #ifdef Q_OS_WIN
0191     debugOut << "\n  supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES);
0192     debugOut << "\n  isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES);
0193 #else
0194     debugOut << "\n  supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES);
0195     debugOut << "\n  isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES);
0196 #endif
0197     debugOut << "\n  Detected renderers:";
0198     {
0199         QDebugStateSaver saver(debugOut);
0200         Q_FOREACH (const DetectedRenderer &x, g_detectedRenderers) {
0201             debugOut.noquote().nospace() << "\n    " << (std::get<2>(x) ? "(Supported)" : "(Unsupported)") << " "
0202                                          << std::get<0>(x) << " (" << std::get<1>(x) << ") ";
0203         }
0204     }
0205 
0206 //    debugOut << "\n== log ==\n";
0207 //    debugOut.noquote();
0208 //    debugOut << g_surfaceFormatDetectionLog;
0209 //    debugOut.resetFormat();
0210 //    debugOut << "\n== end log ==";
0211 
0212     dbgOpenGL.noquote().nospace() << g_debugText;
0213     KisUsageLogger::writeSysInfo(g_debugText);
0214 
0215     if (!openGLCheckResult) {
0216         return;
0217     }
0218 
0219 
0220     // Check if we have a bugged driver that needs fence workaround
0221     bool isOnX11 = false;
0222 #ifdef HAVE_X11
0223     isOnX11 = true;
0224 #endif
0225 
0226     KisConfig cfg(true);
0227 
0228     g_useBufferInvalidation = cfg.readEntry("useBufferInvalidation", false);
0229     KisUsageLogger::writeSysInfo(QString("\nuseBufferInvalidation (config option): %1\n").arg(g_useBufferInvalidation ? "true" : "false"));
0230 
0231     if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) {
0232         g_needsFenceWorkaround = true;
0233     }
0234 
0235     /**
0236      * The large pixmap cache workaround was originally added to fix
0237      * the bug 361709 and later extended to all GPU/OS configurations.
0238      * This setting is still left here in case anyone finds the cached
0239      * method performing better that the direct drawing of assistants
0240      * onto the canvas.
0241      *
0242      * See bugs:
0243      *   https://bugs.kde.org/show_bug.cgi?id=361709
0244      *   https://bugs.kde.org/show_bug.cgi?id=401940
0245      */
0246 
0247     if (cfg.assistantsDrawMode() == KisConfig::ASSISTANTS_DRAW_MODE_LARGE_PIXMAP_CACHE) {
0248         const qreal devicePixelRatio = QGuiApplication::primaryScreen()->devicePixelRatio();
0249         const QSize screenSize = QGuiApplication::primaryScreen()->size() * devicePixelRatio;
0250         const int minCacheSize = 20 * 1024;
0251 
0252         // reserve space for at least 4 textures
0253         const int cacheSize = 2048 + 5 * 4 * screenSize.width() * screenSize.height() / 1024; // KiB
0254 
0255         QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize));
0256     }
0257 }
0258 
0259 void KisOpenGL::initializeContext(QOpenGLContext *ctx)
0260 {
0261     KisConfig cfg(true);
0262     initialize();
0263 
0264     const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext);
0265 
0266     dbgUI << "OpenGL: Opening new context";
0267     if (isDebugEnabled) {
0268         // Passing ctx for ownership management only, not specifying context.
0269         // QOpenGLDebugLogger only function on the current active context.
0270         // FIXME: Do we need to make sure ctx is the active context?
0271         QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx);
0272         if (openglLogger->initialize()) {
0273             qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below.";
0274             QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged);
0275             openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging);
0276             openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging.")));
0277         } else {
0278             qDebug() << "QOpenGLDebugLogger cannot be initialized.";
0279             delete openglLogger;
0280         }
0281     }
0282 
0283     // Double check we were given the version we requested
0284     QSurfaceFormat format = ctx->format();
0285     QOpenGLFunctions *f = ctx->functions();
0286     f->initializeOpenGLFunctions();
0287 
0288     if (openGLCheckResult->supportsBufferInvalidation()) {
0289         QOpenGLContext *ctx = QOpenGLContext::currentContext();
0290         g_glInvalidateBufferData = (PFNGLINVALIDATEBUFFERDATAPROC)ctx->getProcAddress("glInvalidateBufferData");
0291     }
0292 
0293     QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt");
0294     log.open(QFile::WriteOnly);
0295     QString vendor((const char*)f->glGetString(GL_VENDOR));
0296     log.write(vendor.toLatin1());
0297     log.write(", ");
0298     log.write(openGLCheckResult->rendererString().toLatin1());
0299     log.write(", ");
0300     QString version((const char*)f->glGetString(GL_VERSION));
0301     log.write(version.toLatin1());
0302     log.close();
0303 }
0304 
0305 const QString &KisOpenGL::getDebugText()
0306 {
0307     initialize();
0308     return g_debugText;
0309 }
0310 
0311 QStringList KisOpenGL::getOpenGLWarnings() {
0312     QStringList strings;
0313     Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) {
0314         strings << item.toString();
0315     }
0316     return strings;
0317 }
0318 
0319 QString KisOpenGL::currentDriver()
0320 {
0321     initialize();
0322     if (openGLCheckResult) {
0323         return openGLCheckResult->driverVersionString();
0324     }
0325     return QString();
0326 }
0327 
0328 // XXX Temporary function to allow LoD on OpenGL3 without triggering
0329 // all of the other 3.2 functionality, can be removed once we move to Qt5.7
0330 bool KisOpenGL::supportsLoD()
0331 {
0332     initialize();
0333     return openGLCheckResult && openGLCheckResult->supportsLoD();
0334 }
0335 
0336 bool KisOpenGL::hasOpenGL3()
0337 {
0338     initialize();
0339     return openGLCheckResult && openGLCheckResult->hasOpenGL3();
0340 }
0341 
0342 bool KisOpenGL::supportsVAO()
0343 {
0344     initialize();
0345     return openGLCheckResult && openGLCheckResult->supportsVAO();
0346 }
0347 
0348 bool KisOpenGL::hasOpenGLES()
0349 {
0350     initialize();
0351     return openGLCheckResult && openGLCheckResult->isOpenGLES();
0352 }
0353 
0354 bool KisOpenGL::supportsFenceSync()
0355 {
0356     initialize();
0357     return openGLCheckResult && openGLCheckResult->supportsFenceSync();
0358 }
0359 
0360 bool KisOpenGL::supportsBufferMapping()
0361 {
0362     initialize();
0363     return openGLCheckResult && openGLCheckResult->supportsBufferMapping();
0364 }
0365 
0366 bool KisOpenGL::forceDisableTextureBuffers()
0367 {
0368     initialize();
0369     return g_forceDisableTextureBuffers;
0370 }
0371 
0372 bool KisOpenGL::shouldUseTextureBuffers(bool userPreference)
0373 {
0374     initialize();
0375     return !g_forceDisableTextureBuffers && userPreference;
0376 }
0377 
0378 bool KisOpenGL::useTextureBufferInvalidation()
0379 {
0380     initialize();
0381     return g_useBufferInvalidation &&
0382         openGLCheckResult && openGLCheckResult->supportsBufferInvalidation();
0383 }
0384 
0385 bool KisOpenGL::useFBOForToolOutlineRendering()
0386 {
0387     initialize();
0388     return openGLCheckResult && openGLCheckResult->supportsFBO();
0389 }
0390 
0391 bool KisOpenGL::needsFenceWorkaround()
0392 {
0393     initialize();
0394     return g_needsFenceWorkaround;
0395 }
0396 
0397 void KisOpenGL::testingInitializeDefaultSurfaceFormat()
0398 {
0399     setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false));
0400 }
0401 
0402 void KisOpenGL::setDebugSynchronous(bool value)
0403 {
0404     g_isDebugSynchronous = value;
0405 }
0406 
0407 void KisOpenGL::glInvalidateBufferData(uint buffer)
0408 {
0409     g_glInvalidateBufferData(buffer);
0410 }
0411 
0412 KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer()
0413 {
0414     if (!openGLCheckResult) return RendererAuto;
0415     return getRendererFromProbeResult(*openGLCheckResult);
0416 }
0417 
0418 KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer()
0419 {
0420     return g_rendererPreferredByQt;
0421 }
0422 
0423 KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers()
0424 {
0425     return g_supportedRenderers;
0426 }
0427 
0428 KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig()
0429 {
0430     const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0431     QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
0432     return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString());
0433 }
0434 
0435 void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer)
0436 {
0437     const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0438     QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
0439     kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer));
0440 }
0441 
0442 QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer)
0443 {
0444     switch (renderer) {
0445     case RendererNone:
0446         return QStringLiteral("none");
0447     case RendererSoftware:
0448         return QStringLiteral("software");
0449     case RendererDesktopGL:
0450         return QStringLiteral("desktop");
0451     case RendererOpenGLES:
0452         return QStringLiteral("angle");
0453     default:
0454         return QStringLiteral("auto");
0455     }
0456 }
0457 
0458 KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer)
0459 {
0460     if (renderer == "desktop") {
0461         return RendererDesktopGL;
0462     } else if (renderer == "angle") {
0463         return RendererOpenGLES;
0464     } else if (renderer == "software") {
0465         return RendererSoftware;
0466     } else if (renderer == "none") {
0467         return RendererNone;
0468     } else {
0469         return RendererAuto;
0470     }
0471 }
0472 
0473 KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const
0474 {
0475     KisOpenGL::OpenGLRenderer result = RendererAuto;
0476 
0477     if (format.renderableType() == QSurfaceFormat::OpenGLES &&
0478         angleRenderer == AngleRendererD3d11Warp) {
0479 
0480         result = RendererSoftware;
0481 
0482     } else if (format.renderableType() == QSurfaceFormat::OpenGLES) {
0483         // If D3D11, D3D9?, Default (which is after probing, if selected)
0484         // or the system specifies QT_OPENGL_ES_2
0485         result = RendererOpenGLES;
0486     } else if (format.renderableType() == QSurfaceFormat::OpenGL) {
0487         result = RendererDesktopGL;
0488     } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType &&
0489                angleRenderer == AngleRendererD3d11) {
0490         // noop
0491     } else {
0492         qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer);
0493     }
0494 
0495     return result;
0496 }
0497 
0498 namespace {
0499 
0500 typedef std::pair<QSurfaceFormat::RenderableType, KisOpenGL::AngleRenderer> RendererInfo;
0501 
0502 RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer)
0503 {
0504     RendererInfo info = {QSurfaceFormat::DefaultRenderableType,
0505                          KisOpenGL::AngleRendererD3d11};
0506 
0507     switch (renderer) {
0508     case KisOpenGL::RendererNone:
0509         info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault};
0510         break;
0511     case KisOpenGL::RendererAuto:
0512         break;
0513     case KisOpenGL::RendererDesktopGL:
0514         info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererDefault};
0515         break;
0516     case KisOpenGL::RendererOpenGLES:
0517         info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11};
0518         break;
0519     case KisOpenGL::RendererSoftware:
0520         info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp};
0521         break;
0522     }
0523 
0524     return info;
0525 }
0526 
0527 QOpenGLContext::OpenGLModuleType determineOpenGLImplementation(const RendererInfo &info)
0528 {
0529     switch (info.first) {
0530     case QSurfaceFormat::OpenGLES:
0531 #if defined(Q_OS_WINDOWS)
0532         // https://invent.kde.org/szaman/qtbase/-/blob/krita/5.15/src/plugins/platforms/windows/qwindowsintegration.cpp#L425
0533         switch (info.second) {
0534         case KisOpenGL::AngleRendererD3d11:
0535         case KisOpenGL::AngleRendererD3d9:
0536         case KisOpenGL::AngleRendererD3d11Warp:
0537             return QOpenGLContext::LibGLES;
0538         // Assume system OpenGL -- QOpenGLStaticContext
0539         default:
0540             break;
0541         }
0542         return QOpenGLContext::LibGL;
0543 #else
0544         // At least Manjaro Qt can perfectly call up a ES context,
0545         // while Qt says via macros that it doesn't support that...
0546         return QOpenGLContext::LibGLES;
0547 #endif
0548     case QSurfaceFormat::DefaultRenderableType:
0549 #ifdef Q_OS_WIN
0550     // https://invent.kde.org/szaman/qtbase/-/blob/krita/5.15/src/plugins/platforms/windows/qwindowsglcontext.cpp#L1117
0551         return QOpenGLContext::LibGL;
0552 #else
0553     // https://invent.kde.org/szaman/qtbase/-/blob/krita/5.15/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp#L246
0554 #if defined(QT_OPENGL_ES_2)
0555     return QOpenGLContext::LibGLES;
0556 #else
0557     return QOpenGLContext::LibGL;
0558 #endif
0559 #endif
0560     case QSurfaceFormat::OpenGL:
0561     default:
0562         // https://invent.kde.org/szaman/qtbase/-/blob/krita/5.15/src/plugins/platforms/windows/qwindowsglcontext.cpp#L1117
0563         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(info.first != QSurfaceFormat::OpenVG, QOpenGLContext::LibGL);
0564         // https://invent.kde.org/szaman/qtbase/-/blob/krita/5.15/src/gui/kernel/qplatformintegration.cpp#L547
0565         return QOpenGLContext::LibGL;
0566     };
0567 }
0568 
0569 KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer,
0570                                                 KisConfig::RootSurfaceFormat rootSurfaceFormat,
0571                                                 bool debugContext)
0572 {
0573     RendererInfo info = getRendererInfo(renderer);
0574 
0575     KisOpenGL::RendererConfig config;
0576     config.angleRenderer = info.second;
0577 
0578     dbgOpenGL << "Requesting configuration for" << info.first << info.second;
0579 
0580     QSurfaceFormat &format = config.format;
0581     const auto openGLModuleType = determineOpenGLImplementation(info);
0582     switch (openGLModuleType) {
0583     case QOpenGLContext::LibGL:
0584 #if defined Q_OS_MACOS
0585         format.setVersion(4, 1);
0586         format.setProfile(QSurfaceFormat::CoreProfile);
0587 #else
0588         // If asked for 3.0 "Core", Qt will instead request
0589         // an OpenGL ES context.
0590         // NVIDIA's GLX implementation will not allow that and results
0591         // in a forced process exit through X11 (NVIDIA bug #3959482).
0592         format.setVersion(3, 3);
0593         // Make sure to request a Compatibility profile to have NVIDIA
0594         // return the maximum supported GL version.
0595         format.setProfile(QSurfaceFormat::CompatibilityProfile);
0596 #ifdef Q_OS_WIN
0597         // Some parts of Qt seems to require deprecated functions. On Windows
0598         // with the Intel Graphics driver, things like canvas decorations and
0599         // the Touch Docker does not render without this option.
0600         format.setOptions(QSurfaceFormat::DeprecatedFunctions);
0601 #endif
0602 #endif
0603         break;
0604     case QOpenGLContext::LibGLES:
0605         format.setVersion(3, 0);
0606         format.setProfile(QSurfaceFormat::NoProfile);
0607         break;
0608     }
0609 
0610     dbgOpenGL << "Version selected:" << openGLModuleType << format.version();
0611 
0612     format.setDepthBufferSize(24);
0613     format.setStencilBufferSize(8);
0614 
0615     KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format);
0616 
0617     format.setRenderableType(info.first);
0618     format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
0619     format.setSwapInterval(0); // Disable vertical refresh syncing
0620     if (KisRepaintDebugger::enabled()) {
0621         // With repaint debugging, vsync is preferred so that all update regions
0622         // can be visible.
0623         format.setSwapInterval(1);
0624     }
0625     if (debugContext) {
0626         format.setOption(QSurfaceFormat::DebugContext, true);
0627     }
0628 
0629     return config;
0630 }
0631 
0632 bool isOpenGLRendererBlacklisted(const QString &rendererString,
0633                                  const QString &driverVersionString,
0634                                  QVector<KLocalizedString> *warningMessage)
0635 {
0636     bool isBlacklisted = false;
0637 #ifndef Q_OS_WIN
0638     Q_UNUSED(rendererString);
0639     Q_UNUSED(driverVersionString);
0640     Q_UNUSED(warningMessage);
0641 #else
0642     // Special blacklisting of OpenGL/ANGLE is tracked on:
0643     // https://phabricator.kde.org/T7411
0644 
0645     // HACK: Specifically detect for Intel driver build number
0646     //       See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html
0647     if (rendererString.startsWith("Intel")) {
0648         KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL.");
0649         KLocalizedString grossIntelWarning = ki18n(
0650             "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. "
0651             "You may manually switch to OpenGL but it is not guaranteed to work properly."
0652         );
0653         QRegularExpression regex("\\b\\d{1,2}\\.\\d{1,2}\\.(\\d{1,3})\\.(\\d{4})\\b");
0654         QRegularExpressionMatch match = regex.match(driverVersionString);
0655         if (match.hasMatch()) {
0656             const int thirdPart = match.captured(1).toInt();
0657             const int fourthPart = match.captured(2).toInt();
0658             int driverBuild;
0659             if (thirdPart >= 100) {
0660                 driverBuild = thirdPart * 10000 + fourthPart;
0661             } else {
0662                 driverBuild = fourthPart;
0663             }
0664             qDebug() << "Detected Intel driver build number as" << driverBuild;
0665             if (driverBuild > 4636 && driverBuild < 4729) {
0666                 // Make ANGLE the preferred renderer for Intel driver versions
0667                 // between build 4636 and 4729 (exclusive) due to an UI offset bug.
0668                 // See https://communities.intel.com/thread/116003
0669                 // (Build 4636 is known to work from some test results)
0670                 qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer";
0671                 isBlacklisted = true;
0672                 *warningMessage << knownBadIntelWarning;
0673             } else if (driverBuild == 4358) {
0674                 // There are several reports on a bug where the canvas is not being
0675                 // updated properly which has debug info pointing to this build.
0676                 qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer";
0677                 isBlacklisted = true;
0678                 *warningMessage << knownBadIntelWarning;
0679             } else {
0680                 // Intel tends to randomly break OpenGL in some of their new driver
0681                 // builds, therefore we just shouldn't use OpenGL by default to
0682                 // reduce bug report noises.
0683                 qDebug() << "Detected Intel driver, making ANGLE the preferred renderer";
0684                 isBlacklisted = true;
0685                 *warningMessage << grossIntelWarning;
0686             }
0687         } else {
0688             // In case Intel changed the driver version format to something that
0689             // we don't understand, we still select ANGLE.
0690             qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer";
0691             isBlacklisted = true;
0692             *warningMessage << grossIntelWarning;
0693         }
0694     }
0695 #endif
0696     return isBlacklisted;
0697 }
0698 
0699 boost::optional<bool> orderPreference(bool lhs, bool rhs)
0700 {
0701     if (lhs == rhs) return boost::none;
0702     if (lhs && !rhs) return true;
0703     if (!lhs && rhs) return false;
0704     return false;
0705 }
0706 
0707 #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; }
0708 
0709 class FormatPositionLess
0710 {
0711 public:
0712 
0713     FormatPositionLess()
0714     {
0715     }
0716 
0717     bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const {
0718         KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace);
0719 
0720         if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) {
0721             ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId()));
0722         }
0723 
0724 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
0725         ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()),
0726                  isPreferredColorSpace(rhs.format.colorSpace()));
0727 #endif
0728 
0729         if (doPreferHDR()) {
0730             ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format));
0731         } else {
0732             ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format));
0733         }
0734 
0735         if (m_preferredRendererByUser != KisOpenGL::RendererAuto) {
0736             ORDER_BY(lhs.rendererId() == m_preferredRendererByUser,
0737                      rhs.rendererId() == m_preferredRendererByUser);
0738         }
0739 
0740         ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId()));
0741 
0742         if (doPreferHDR() &&
0743             m_preferredRendererByHDR != KisOpenGL::RendererAuto) {
0744 
0745             ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR,
0746                      rhs.rendererId() == m_preferredRendererByHDR);
0747 
0748         }
0749 
0750         KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto);
0751 
0752         ORDER_BY(lhs.rendererId() == m_preferredRendererByQt,
0753                  rhs.rendererId() == m_preferredRendererByQt);
0754 
0755         return false;
0756     }
0757 
0758 
0759 public:
0760     void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) {
0761         m_preferredColorSpace = preferredColorSpace;
0762     }
0763 
0764     void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) {
0765         m_preferredRendererByQt = preferredRendererByQt;
0766     }
0767 
0768     void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) {
0769         m_preferredRendererByUser = preferredRendererByUser;
0770     }
0771 
0772     void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) {
0773         m_preferredRendererByHDR = preferredRendererByHDR;
0774     }
0775 
0776     void setOpenGLBlacklisted(bool openGLBlacklisted) {
0777         m_openGLBlacklisted = openGLBlacklisted;
0778     }
0779 
0780     void setOpenGLESBlacklisted(bool openGLESBlacklisted) {
0781         m_openGLESBlacklisted = openGLESBlacklisted;
0782     }
0783 
0784     bool isOpenGLBlacklisted() const {
0785         return m_openGLBlacklisted;
0786     }
0787 
0788     bool isOpenGLESBlacklisted() const {
0789         return m_openGLESBlacklisted;
0790     }
0791 
0792     KisSurfaceColorSpace preferredColorSpace() const {
0793         return m_preferredColorSpace;
0794     }
0795 
0796     KisOpenGL::OpenGLRenderer preferredRendererByUser() const {
0797         return m_preferredRendererByUser;
0798     }
0799 
0800 private:
0801     bool isHDRFormat(const QSurfaceFormat &f) const {
0802 #ifdef HAVE_HDR
0803         return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace ||
0804             f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace;
0805 #else
0806         Q_UNUSED(f);
0807         return false;
0808 #endif
0809     }
0810 
0811     bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const {
0812         return r == KisOpenGL::RendererSoftware;
0813     }
0814 
0815     bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const {
0816         KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto ||
0817                                      r == KisOpenGL::RendererDesktopGL ||
0818                                      r == KisOpenGL::RendererOpenGLES ||
0819                                      r == KisOpenGL::RendererSoftware ||
0820                                      r == KisOpenGL::RendererNone);
0821 
0822         return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) ||
0823             (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) ||
0824             (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted);
0825     }
0826 
0827     bool doPreferHDR() const {
0828 #ifdef HAVE_HDR
0829         return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace ||
0830             m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace;
0831 #else
0832         return false;
0833 #endif
0834     }
0835 
0836     bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const {
0837         return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs);
0838         return false;
0839     }
0840 
0841 private:
0842     KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace;
0843     KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL;
0844     KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto;
0845     KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto;
0846     bool m_openGLBlacklisted = false;
0847     bool m_openGLESBlacklisted = false;
0848 };
0849 
0850 struct DetectionDebug : public QDebug
0851 {
0852     DetectionDebug(QString *string)
0853         : QDebug(string),
0854           m_string(string),
0855           m_originalSize(string->size())
0856     {}
0857     ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; }
0858 
0859     QString *m_string;
0860     int m_originalSize;
0861 };
0862 }
0863 
0864 #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog)
0865 
0866 KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer,
0867                                                          KisConfig::RootSurfaceFormat preferredRootSurfaceFormat,
0868                                                          bool enableDebug)
0869 {
0870     QVector<KLocalizedString> warningMessages;
0871 
0872     using Info = boost::optional<KisOpenGLModeProber::Result>;
0873 
0874     QHash<OpenGLRenderer, Info> renderersToTest;
0875 #ifndef Q_OS_ANDROID
0876     renderersToTest.insert(RendererDesktopGL, Info());
0877 #endif
0878     renderersToTest.insert(RendererOpenGLES, Info());
0879 
0880 #ifdef Q_OS_WIN
0881     renderersToTest.insert(RendererSoftware, Info());
0882 #endif
0883 
0884 
0885 #ifdef HAVE_HDR
0886     QVector<KisConfig::RootSurfaceFormat> formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ});
0887 #else
0888     QVector<KisConfig::RootSurfaceFormat> formatSymbols({KisConfig::BT709_G22});
0889 #endif
0890 
0891     KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto,
0892                                                                     KisConfig::BT709_G22, false);
0893     Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig);
0894 
0895 #ifdef Q_OS_WIN
0896     if (!info) {
0897         // try software rasterizer (WARP)
0898         defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware,
0899                                               KisConfig::BT709_G22, false);
0900         info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig);
0901 
0902         if (!info) {
0903             renderersToTest.remove(RendererSoftware);
0904         }
0905     }
0906 #endif
0907 
0908     if (!info) return KisOpenGL::RendererConfig();
0909 
0910     const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info);
0911 
0912     /**
0913      * On Windows we always prefer Angle, not what Qt suggests us
0914      */
0915 #ifdef Q_OS_WIN
0916     const OpenGLRenderer preferredAutoRenderer = RendererOpenGLES;
0917 #else
0918     const OpenGLRenderer preferredAutoRenderer = defaultRenderer;
0919 #endif
0920 
0921     OpenGLRenderers supportedRenderers = RendererNone;
0922 
0923     FormatPositionLess compareOp;
0924     compareOp.setPreferredRendererByQt(preferredAutoRenderer);
0925 
0926 #ifdef HAVE_HDR
0927     compareOp.setPreferredColorSpace(
0928         preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace :
0929         preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace :
0930         KisSurfaceColorSpace::bt2020PQColorSpace);
0931 #else
0932     Q_UNUSED(preferredRootSurfaceFormat);
0933     compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace);
0934 #endif
0935 
0936 #ifdef Q_OS_WIN
0937     compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES);
0938 #endif
0939     compareOp.setPreferredRendererByUser(preferredRenderer);
0940     compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm
0941 
0942 
0943     renderersToTest[defaultRenderer] = info;
0944 
0945     for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) {
0946         Info info = it.value();
0947 
0948         if (!info) {
0949             const RendererConfig config = generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false);
0950             dbgOpenGL << "Probing" << it.key() << "from default:" << config.format << config.angleRenderer
0951                       << config.rendererId();
0952             info = KisOpenGLModeProber::instance()->probeFormat(config);
0953             *it = info;
0954         } else {
0955             dbgOpenGL << "Already probed:" << it.key();
0956         }
0957 
0958         compareOp.setOpenGLBlacklisted(
0959             !info ||
0960             isOpenGLRendererBlacklisted(info->rendererString(),
0961                                         info->driverVersionString(),
0962                                         &warningMessages));
0963 
0964         if (info) {
0965             dbgOpenGL << "Result:" << info->rendererString() << info->driverVersionString()
0966                       << info->isSupportedVersion();
0967         }
0968 
0969         if (info) {
0970             g_detectedRenderers << std::make_tuple(info->rendererString(),
0971                                                    info->driverVersionString(),
0972                                                    info->isSupportedVersion());
0973         }
0974 
0975         if (info && info->isSupportedVersion()) {
0976             supportedRenderers |= it.key();
0977         }
0978     }
0979 
0980     OpenGLRenderer preferredByQt = preferredAutoRenderer;
0981 
0982     if (preferredByQt == RendererDesktopGL &&
0983         supportedRenderers & RendererDesktopGL &&
0984         compareOp.isOpenGLBlacklisted()) {
0985 
0986         preferredByQt = RendererOpenGLES;
0987 
0988     } else if (preferredByQt == RendererOpenGLES &&
0989                supportedRenderers & RendererOpenGLES &&
0990                compareOp.isOpenGLESBlacklisted()) {
0991 
0992         preferredByQt = RendererDesktopGL;
0993     }
0994 
0995     QVector<RendererConfig> preferredConfigs;
0996     for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) {
0997         // if default mode of the renderer doesn't work, then custom won't either
0998         if (!it.value()) continue;
0999 
1000         Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) {
1001             preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug);
1002         }
1003     }
1004 
1005     std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp);
1006 
1007     dbgDetection() << "Supported renderers:" << supportedRenderers;
1008 
1009     dbgDetection() << "Surface format preference list:";
1010     Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) {
1011         dbgDetection() << "*" << config.format;
1012         dbgDetection() << "   " << config.rendererId();
1013     }
1014 
1015     KisOpenGL::RendererConfig resultConfig = defaultConfig;
1016 
1017     if (preferredRenderer != RendererNone) {
1018         Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) {
1019 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
1020             dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId();
1021 #else
1022             dbgDetection() <<"Probing format..." << config.rendererId();
1023 #endif
1024             Info info = KisOpenGLModeProber::instance()->probeFormat(config);
1025 
1026             if (info && info->isSupportedVersion()) {
1027 
1028 #ifdef Q_OS_WIN
1029                 // HACK: Block ANGLE with Direct3D9
1030                 //       Direct3D9 does not give OpenGL ES 3.0
1031                 //       Some versions of ANGLE returns OpenGL version 3.0 incorrectly
1032 
1033                 if (info->isUsingAngle() &&
1034                         info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) {
1035 
1036                     dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened.";
1037 
1038                     continue;
1039                 }
1040 #endif
1041 
1042                 dbgDetection() << "Found format:" << config.format;
1043                 dbgDetection() << "   " << config.rendererId();
1044 
1045                 resultConfig = config;
1046                 break;
1047             }
1048         }
1049 
1050         {
1051             const bool colorSpaceIsCorrect =
1052         #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
1053                     KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(),
1054                                                                  resultConfig.format.colorSpace());
1055 #else
1056                     true;
1057 #endif
1058 
1059             const bool rendererIsCorrect =
1060                     compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto ||
1061                     compareOp.preferredRendererByUser() == resultConfig.rendererId();
1062 
1063             if (!rendererIsCorrect && colorSpaceIsCorrect) {
1064                 warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected.");
1065             } else if (!colorSpaceIsCorrect) {
1066                 warningMessages << ki18n("Preferred output format is not supported by available renderers");
1067             }
1068 
1069         }
1070     } else {
1071         resultConfig.format = QSurfaceFormat();
1072         resultConfig.angleRenderer = AngleRendererDefault;
1073     }
1074 
1075     overrideSupportedRenderers(supportedRenderers, preferredByQt);
1076     overrideOpenGLWarningString(warningMessages);
1077 
1078     return resultConfig;
1079 }
1080 
1081 void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config)
1082 {
1083     KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet);
1084 
1085     g_sanityDefaultFormatIsSet = true;
1086     QSurfaceFormat::setDefaultFormat(config.format);
1087 
1088     if (config.format.renderableType() == QSurfaceFormat::OpenGLES) {
1089         QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true);
1090 #ifdef Q_OS_WIN
1091         // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP
1092         //  might get weird crashes atm.
1093         qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1());
1094 #endif
1095     } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) {
1096         QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true);
1097     }
1098 }
1099 
1100 bool KisOpenGL::hasOpenGL()
1101 {
1102     return openGLCheckResult->isSupportedVersion();
1103 }