gl_detection.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // This file is part of Desktop App Toolkit,
  2. // a set of libraries for developing nice desktop applications.
  3. //
  4. // For license and copyright information please follow this link:
  5. // https://github.com/desktop-app/legal/blob/master/LEGAL
  6. //
  7. #include "ui/gl/gl_detection.h"
  8. #include "ui/gl/gl_shader.h"
  9. #include "ui/integration.h"
  10. #include "base/debug_log.h"
  11. #include "base/platform/base_platform_info.h"
  12. #include <QtCore/QSet>
  13. #include <QtCore/QFile>
  14. #include <QtGui/QWindow>
  15. #include <QtGui/QOpenGLContext>
  16. #include <QtGui/QOpenGLFunctions>
  17. #include <QOpenGLWidget>
  18. #ifdef DESKTOP_APP_USE_ANGLE
  19. #include <QtGui/QGuiApplication>
  20. #include <qpa/qplatformnativeinterface.h>
  21. #include <EGL/egl.h>
  22. #endif // DESKTOP_APP_USE_ANGLE
  23. #define LOG_ONCE(x) [[maybe_unused]] static auto logged = [&] { LOG(x); return true; }();
  24. namespace Ui::GL {
  25. namespace {
  26. bool ForceDisabled/* = false*/;
  27. bool LastCheckCrashed/* = false*/;
  28. #ifdef DESKTOP_APP_USE_ANGLE
  29. ANGLE ResolvedANGLE/* = ANGLE::Auto*/;
  30. QList<QByteArray> EGLExtensions(not_null<QOpenGLContext*> context) {
  31. const auto native = QGuiApplication::platformNativeInterface();
  32. Assert(native != nullptr);
  33. const auto display = static_cast<EGLDisplay>(
  34. native->nativeResourceForContext(
  35. QByteArrayLiteral("egldisplay"),
  36. context));
  37. return display
  38. ? QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ')
  39. : QList<QByteArray>();
  40. }
  41. #endif // DESKTOP_APP_USE_ANGLE
  42. void CrashCheckStart() {
  43. auto f = QFile(Integration::Instance().openglCheckFilePath());
  44. if (f.open(QIODevice::WriteOnly)) {
  45. f.write("1", 1);
  46. f.close();
  47. }
  48. }
  49. } // namespace
  50. Capabilities CheckCapabilities(QWidget *widget) {
  51. if (!Platform::IsMac()) {
  52. if (ForceDisabled) {
  53. LOG_ONCE(("OpenGL: Force-disabled."));
  54. return {};
  55. } else if (LastCheckCrashed) {
  56. LOG_ONCE(("OpenGL: Last-crashed."));
  57. return {};
  58. }
  59. }
  60. [[maybe_unused]] static const auto BugListInited = [] {
  61. if (!QFile::exists(":/misc/gpu_driver_bug_list.json")) {
  62. return false;
  63. }
  64. LOG(("OpenGL: Using custom 'gpu_driver_bug_list.json'."));
  65. qputenv("QT_OPENGL_BUGLIST", ":/misc/gpu_driver_bug_list.json");
  66. return true;
  67. }();
  68. CrashCheckStart();
  69. const auto guard = gsl::finally([=] {
  70. CrashCheckFinish();
  71. });
  72. auto tester = QOpenGLWidget(widget);
  73. auto format = tester.format();
  74. format.setAlphaBufferSize(8);
  75. tester.setFormat(format);
  76. const auto guard2 = [&]() -> std::optional<gsl::final_action<Fn<void()>>> {
  77. if (!tester.window()->windowHandle()) {
  78. tester.window()->createWinId();
  79. return gsl::finally(Fn<void()>([&] {
  80. tester.window()->windowHandle()->destroy();
  81. tester.window()->setAttribute(Qt::WA_OutsideWSRange, false);
  82. }));
  83. }
  84. return std::nullopt;
  85. }();
  86. tester.grabFramebuffer(); // Force initialize().
  87. const auto context = tester.context();
  88. if (!context
  89. || !context->isValid()
  90. || !context->makeCurrent(tester.window()->windowHandle())) {
  91. LOG_ONCE(("OpenGL: Could not create widget in a window."));
  92. return {};
  93. }
  94. const auto functions = context->functions();
  95. using Feature = QOpenGLFunctions;
  96. if (!functions->hasOpenGLFeature(Feature::NPOTTextures)) {
  97. LOG_ONCE(("OpenGL: NPOT textures not supported."));
  98. return {};
  99. } else if (!functions->hasOpenGLFeature(Feature::Framebuffers)) {
  100. LOG_ONCE(("OpenGL: Framebuffers not supported."));
  101. return {};
  102. } else if (!functions->hasOpenGLFeature(Feature::Shaders)) {
  103. LOG_ONCE(("OpenGL: Shaders not supported."));
  104. return {};
  105. }
  106. {
  107. auto program = QOpenGLShaderProgram();
  108. LinkProgram(
  109. &program,
  110. VertexShader({
  111. VertexViewportTransform(),
  112. VertexPassTextureCoord(),
  113. }),
  114. FragmentShader({
  115. FragmentSampleARGB32Texture(),
  116. }));
  117. if (!program.isLinked()) {
  118. LOG_ONCE(("OpenGL: Could not link simple shader."));
  119. return {};
  120. }
  121. }
  122. const auto supported = context->format();
  123. switch (supported.profile()) {
  124. case QSurfaceFormat::NoProfile: {
  125. if (supported.renderableType() == QSurfaceFormat::OpenGLES) {
  126. LOG_ONCE(("OpenGL Profile: OpenGLES."));
  127. } else {
  128. LOG_ONCE(("OpenGL Profile: NoProfile."));
  129. }
  130. } break;
  131. case QSurfaceFormat::CoreProfile: {
  132. LOG_ONCE(("OpenGL Profile: Core."));
  133. } break;
  134. case QSurfaceFormat::CompatibilityProfile: {
  135. LOG_ONCE(("OpenGL Profile: Compatibility."));
  136. } break;
  137. }
  138. static const auto checkVendor = [&] {
  139. const auto renderer = reinterpret_cast<const char*>(
  140. functions->glGetString(GL_RENDERER));
  141. LOG(("OpenGL Renderer: %1").arg(renderer ? renderer : "[nullptr]"));
  142. const auto vendor = reinterpret_cast<const char*>(
  143. functions->glGetString(GL_VENDOR));
  144. LOG(("OpenGL Vendor: %1").arg(vendor ? vendor : "[nullptr]"));
  145. const auto version = reinterpret_cast<const char*>(
  146. functions->glGetString(GL_VERSION));
  147. LOG(("OpenGL Version: %1").arg(version ? version : "[nullptr]"));
  148. const auto extensions = context->extensions();
  149. auto list = QStringList();
  150. for (const auto &extension : extensions) {
  151. list.append(QString::fromLatin1(extension));
  152. }
  153. LOG(("OpenGL Extensions: %1").arg(list.join(", ")));
  154. #ifdef DESKTOP_APP_USE_ANGLE
  155. auto egllist = QStringList();
  156. for (const auto &extension : EGLExtensions(context)) {
  157. egllist.append(QString::fromLatin1(extension));
  158. }
  159. LOG(("EGL Extensions: %1").arg(egllist.join(", ")));
  160. #endif // DESKTOP_APP_USE_ANGLE
  161. return true;
  162. }();
  163. if (!checkVendor) {
  164. return {};
  165. }
  166. const auto version = u"%1.%2"_q
  167. .arg(supported.majorVersion())
  168. .arg(supported.majorVersion());
  169. auto result = Capabilities{ .supported = true };
  170. if (supported.alphaBufferSize() >= 8) {
  171. result.transparency = true;
  172. LOG_ONCE(("OpenGL: QOpenGLContext created, version: %1."
  173. ).arg(version));
  174. } else {
  175. LOG_ONCE(("OpenGL: QOpenGLContext without alpha created, version: %1"
  176. ).arg(version));
  177. }
  178. return result;
  179. }
  180. Backend ChooseBackendDefault(Capabilities capabilities) {
  181. const auto use = ::Platform::IsMac()
  182. ? true
  183. : ::Platform::IsWindows()
  184. ? capabilities.supported
  185. : capabilities.transparency;
  186. return use ? Backend::OpenGL : Backend::Raster;
  187. }
  188. void DetectLastCheckCrash() {
  189. [[maybe_unused]] static const auto Once = [] {
  190. LastCheckCrashed = !Platform::IsMac()
  191. && QFile::exists(Integration::Instance().openglCheckFilePath());
  192. return false;
  193. }();
  194. }
  195. bool LastCrashCheckFailed() {
  196. DetectLastCheckCrash();
  197. return LastCheckCrashed;
  198. }
  199. void CrashCheckFinish() {
  200. QFile::remove(Integration::Instance().openglCheckFilePath());
  201. }
  202. void ForceDisable(bool disable) {
  203. if (!Platform::IsMac()) {
  204. ForceDisabled = disable;
  205. }
  206. }
  207. #ifdef DESKTOP_APP_USE_ANGLE
  208. void ConfigureANGLE() {
  209. qunsetenv("DESKTOP_APP_QT_ANGLE_PLATFORM");
  210. const auto path = Ui::Integration::Instance().angleBackendFilePath();
  211. if (path.isEmpty()) {
  212. return;
  213. }
  214. auto f = QFile(path);
  215. if (!f.open(QIODevice::ReadOnly)) {
  216. return;
  217. }
  218. auto bytes = f.read(32);
  219. const auto check = [&](const char *backend, ANGLE angle) {
  220. if (bytes.startsWith(backend)) {
  221. ResolvedANGLE = angle;
  222. qputenv("DESKTOP_APP_QT_ANGLE_PLATFORM", backend);
  223. }
  224. };
  225. //check("gl", ANGLE::OpenGL);
  226. check("d3d9", ANGLE::D3D9);
  227. check("d3d11", ANGLE::D3D11);
  228. check("d3d11on12", ANGLE::D3D11on12);
  229. if (ResolvedANGLE == ANGLE::Auto) {
  230. LOG(("ANGLE Warning: Unknown backend: %1"
  231. ).arg(QString::fromUtf8(bytes)));
  232. }
  233. }
  234. void ChangeANGLE(ANGLE backend) {
  235. const auto path = Ui::Integration::Instance().angleBackendFilePath();
  236. const auto write = [&](QByteArray backend) {
  237. auto f = QFile(path);
  238. if (!f.open(QIODevice::WriteOnly)) {
  239. LOG(("ANGLE Warning: Could not write to %1.").arg(path));
  240. return;
  241. }
  242. f.write(backend);
  243. };
  244. switch (backend) {
  245. case ANGLE::Auto: QFile(path).remove(); break;
  246. case ANGLE::D3D9: write("d3d9"); break;
  247. case ANGLE::D3D11: write("d3d11"); break;
  248. case ANGLE::D3D11on12: write("d3d11on12"); break;
  249. //case ANGLE::OpenGL: write("gl"); break;
  250. default: Unexpected("ANGLE backend value.");
  251. }
  252. }
  253. ANGLE CurrentANGLE() {
  254. return ResolvedANGLE;
  255. }
  256. #endif // DESKTOP_APP_USE_ANGLE
  257. } // namespace Ui::GL