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 }