326 lines
9 KiB
C++
326 lines
9 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "ui/gl/gl_detection.h"
|
|
|
|
#include "ui/gl/gl_shader.h"
|
|
#include "ui/integration.h"
|
|
#include "base/debug_log.h"
|
|
#include "base/options.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
|
|
#include <QtCore/QSet>
|
|
#include <QtCore/QFile>
|
|
#include <QtGui/QtEvents>
|
|
#include <QtGui/QWindow>
|
|
#include <QtGui/QOpenGLContext>
|
|
#include <QtGui/QOpenGLFunctions>
|
|
#include <QOpenGLWindow>
|
|
#include <QOpenGLWidget>
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include <QtGui/QGuiApplication>
|
|
#include <qpa/qplatformnativeinterface.h>
|
|
#include <EGL/egl.h>
|
|
#endif // Q_OS_WIN
|
|
|
|
#define LOG_ONCE(x) [[maybe_unused]] static auto logged = [&] { LOG(x); return true; }();
|
|
|
|
namespace Ui::GL {
|
|
namespace {
|
|
|
|
bool ForceDisabled/* = false*/;
|
|
|
|
#ifdef Q_OS_WIN
|
|
ANGLE ResolvedANGLE/* = ANGLE::Auto*/;
|
|
#endif // Q_OS_WIN
|
|
|
|
base::options::toggle AllowLinuxNvidiaOpenGL({
|
|
.id = kOptionAllowLinuxNvidiaOpenGL,
|
|
.name = "Allow OpenGL on the NVIDIA drivers (Linux)",
|
|
.description = "Qt+OpenGL have problems on Linux with NVIDIA drivers.",
|
|
.scope = base::options::linux,
|
|
.restartRequired = true,
|
|
});
|
|
|
|
void CrashCheckStart() {
|
|
auto f = QFile(Integration::Instance().openglCheckFilePath());
|
|
if (f.open(QIODevice::WriteOnly)) {
|
|
f.write("1", 1);
|
|
f.close();
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char kOptionAllowLinuxNvidiaOpenGL[] = "allow-linux-nvidia-opengl";
|
|
|
|
Capabilities CheckCapabilities(QWidget *widget, bool avoidWidgetCreation) {
|
|
if (ForceDisabled) {
|
|
LOG_ONCE(("OpenGL: Force-disabled."));
|
|
return {};
|
|
}
|
|
|
|
[[maybe_unused]] static const auto BugListInited = [] {
|
|
if (!QFile::exists(":/misc/gpu_driver_bug_list.json")) {
|
|
return false;
|
|
}
|
|
LOG(("OpenGL: Using custom 'gpu_driver_bug_list.json'."));
|
|
qputenv("QT_OPENGL_BUGLIST", ":/misc/gpu_driver_bug_list.json");
|
|
return true;
|
|
}();
|
|
|
|
auto format = QSurfaceFormat();
|
|
if (widget) {
|
|
if (!widget->window()->windowHandle()) {
|
|
widget->window()->createWinId();
|
|
}
|
|
if (!widget->window()->windowHandle()) {
|
|
LOG(("OpenGL: Could not create window for widget."));
|
|
return {};
|
|
}
|
|
format = widget->window()->windowHandle()->format();
|
|
format.setAlphaBufferSize(8);
|
|
widget->window()->windowHandle()->setFormat(format);
|
|
} else {
|
|
format.setAlphaBufferSize(8);
|
|
}
|
|
|
|
CrashCheckStart();
|
|
const auto tester = [&] {
|
|
std::unique_ptr<QObject> result;
|
|
if (avoidWidgetCreation) {
|
|
const auto w = new QOpenGLWindow();
|
|
auto e = QResizeEvent(QSize(), QSize());
|
|
w->setFormat(format);
|
|
w->create();
|
|
static_cast<QObject*>(w)->event(&e); // Force initialize().
|
|
w->grabFramebuffer(); // Force makeCurrent().
|
|
result.reset(w);
|
|
} else {
|
|
const auto w = new QOpenGLWidget(widget);
|
|
w->setFormat(format);
|
|
w->grabFramebuffer(); // Force initialize().
|
|
if (!w->window()->windowHandle()) {
|
|
w->window()->createWinId();
|
|
}
|
|
result.reset(w);
|
|
}
|
|
return result;
|
|
}();
|
|
const auto testerWidget = avoidWidgetCreation
|
|
? nullptr
|
|
: static_cast<QOpenGLWidget*>(tester.get());
|
|
const auto testerWindow = avoidWidgetCreation
|
|
? static_cast<QOpenGLWindow*>(tester.get())
|
|
: nullptr;
|
|
/*const auto testerQWindow = avoidWidgetCreation
|
|
? static_cast<QWindow*>(tester.get())
|
|
: testerWidget->window()->windowHandle();*/
|
|
CrashCheckFinish();
|
|
|
|
const auto context = avoidWidgetCreation ? testerWindow->context() : testerWidget->context();
|
|
if (!context
|
|
|| !context->isValid()/*
|
|
// This check doesn't work for a widget with WA_NativeWindow.
|
|
|| !context->makeCurrent(testerQWindow)*/) {
|
|
LOG_ONCE(("OpenGL: Could not create widget in a window."));
|
|
return {};
|
|
}
|
|
const auto functions = context->functions();
|
|
using Feature = QOpenGLFunctions;
|
|
if (!functions->hasOpenGLFeature(Feature::NPOTTextures)) {
|
|
LOG_ONCE(("OpenGL: NPOT textures not supported."));
|
|
return {};
|
|
} else if (!functions->hasOpenGLFeature(Feature::Framebuffers)) {
|
|
LOG_ONCE(("OpenGL: Framebuffers not supported."));
|
|
return {};
|
|
} else if (!functions->hasOpenGLFeature(Feature::Shaders)) {
|
|
LOG_ONCE(("OpenGL: Shaders not supported."));
|
|
return {};
|
|
}
|
|
{
|
|
auto program = QOpenGLShaderProgram();
|
|
LinkProgram(
|
|
&program,
|
|
VertexShader({
|
|
VertexViewportTransform(),
|
|
VertexPassTextureCoord(),
|
|
}),
|
|
FragmentShader({
|
|
FragmentSampleARGB32Texture(),
|
|
}));
|
|
if (!program.isLinked()) {
|
|
LOG_ONCE(("OpenGL: Could not link simple shader."));
|
|
return {};
|
|
}
|
|
}
|
|
|
|
const auto supported = context->format();
|
|
switch (supported.profile()) {
|
|
case QSurfaceFormat::NoProfile: {
|
|
if (supported.renderableType() == QSurfaceFormat::OpenGLES) {
|
|
LOG_ONCE(("OpenGL Profile: OpenGLES."));
|
|
} else {
|
|
LOG_ONCE(("OpenGL Profile: NoProfile."));
|
|
}
|
|
} break;
|
|
case QSurfaceFormat::CoreProfile: {
|
|
LOG_ONCE(("OpenGL Profile: Core."));
|
|
} break;
|
|
case QSurfaceFormat::CompatibilityProfile: {
|
|
LOG_ONCE(("OpenGL Profile: Compatibility."));
|
|
} break;
|
|
}
|
|
|
|
static const auto checkVendor = [&] {
|
|
const auto renderer = reinterpret_cast<const char*>(
|
|
functions->glGetString(GL_RENDERER));
|
|
LOG(("OpenGL Renderer: %1").arg(renderer ? renderer : "[nullptr]"));
|
|
const auto vendor = reinterpret_cast<const char*>(
|
|
functions->glGetString(GL_VENDOR));
|
|
LOG(("OpenGL Vendor: %1").arg(vendor ? vendor : "[nullptr]"));
|
|
const auto version = reinterpret_cast<const char*>(
|
|
functions->glGetString(GL_VERSION));
|
|
LOG(("OpenGL Version: %1").arg(version ? version : "[nullptr]"));
|
|
const auto extensions = context->extensions();
|
|
auto list = QStringList();
|
|
for (const auto &extension : extensions) {
|
|
list.append(QString::fromLatin1(extension));
|
|
}
|
|
LOG(("OpenGL Extensions: %1").arg(list.join(", ")));
|
|
|
|
#ifdef Q_OS_WIN
|
|
auto egllist = QStringList();
|
|
for (const auto &extension : EGLExtensions(context)) {
|
|
egllist.append(QString::fromLatin1(extension));
|
|
}
|
|
LOG(("EGL Extensions: %1").arg(egllist.join(", ")));
|
|
#endif // Q_OS_WIN
|
|
|
|
if (::Platform::IsLinux()
|
|
&& version
|
|
&& QByteArray(version).contains("NVIDIA")) {
|
|
// https://github.com/telegramdesktop/tdesktop/issues/16830
|
|
if (AllowLinuxNvidiaOpenGL.value()) {
|
|
LOG_ONCE(("OpenGL: Allow on NVIDIA driver (experimental)."));
|
|
} else {
|
|
LOG_ONCE(("OpenGL: Disable on NVIDIA driver on Linux."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}();
|
|
if (!checkVendor) {
|
|
return {};
|
|
}
|
|
|
|
const auto version = u"%1.%2"_q
|
|
.arg(supported.majorVersion())
|
|
.arg(supported.majorVersion());
|
|
auto result = Capabilities{ .supported = true };
|
|
if (supported.alphaBufferSize() >= 8) {
|
|
result.transparency = true;
|
|
LOG_ONCE(("OpenGL: QOpenGLContext created, version: %1."
|
|
).arg(version));
|
|
} else {
|
|
LOG_ONCE(("OpenGL: QOpenGLContext without alpha created, version: %1"
|
|
).arg(version));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Backend ChooseBackendDefault(Capabilities capabilities) {
|
|
const auto use = ::Platform::IsMac()
|
|
? true
|
|
: ::Platform::IsWindows()
|
|
? capabilities.supported
|
|
: capabilities.transparency;
|
|
return use ? Backend::OpenGL : Backend::Raster;
|
|
}
|
|
|
|
bool LastCrashCheckFailed() {
|
|
return QFile::exists(Integration::Instance().openglCheckFilePath());
|
|
}
|
|
|
|
void CrashCheckFinish() {
|
|
QFile::remove(Integration::Instance().openglCheckFilePath());
|
|
}
|
|
|
|
void ForceDisable(bool disable) {
|
|
ForceDisabled = disable;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
void ConfigureANGLE() {
|
|
qunsetenv("DESKTOP_APP_QT_ANGLE_PLATFORM");
|
|
const auto path = Ui::Integration::Instance().angleBackendFilePath();
|
|
if (path.isEmpty()) {
|
|
return;
|
|
}
|
|
auto f = QFile(path);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
return;
|
|
}
|
|
auto bytes = f.read(32);
|
|
const auto check = [&](const char *backend, ANGLE angle) {
|
|
if (bytes.startsWith(backend)) {
|
|
ResolvedANGLE = angle;
|
|
qputenv("DESKTOP_APP_QT_ANGLE_PLATFORM", backend);
|
|
}
|
|
};
|
|
//check("gl", ANGLE::OpenGL);
|
|
check("d3d9", ANGLE::D3D9);
|
|
check("d3d11", ANGLE::D3D11);
|
|
check("d3d11on12", ANGLE::D3D11on12);
|
|
if (ResolvedANGLE == ANGLE::Auto) {
|
|
LOG(("ANGLE Warning: Unknown backend: %1"
|
|
).arg(QString::fromUtf8(bytes)));
|
|
}
|
|
}
|
|
|
|
void ChangeANGLE(ANGLE backend) {
|
|
const auto path = Ui::Integration::Instance().angleBackendFilePath();
|
|
const auto write = [&](QByteArray backend) {
|
|
auto f = QFile(path);
|
|
if (!f.open(QIODevice::WriteOnly)) {
|
|
LOG(("ANGLE Warning: Could not write to %1.").arg(path));
|
|
return;
|
|
}
|
|
f.write(backend);
|
|
};
|
|
switch (backend) {
|
|
case ANGLE::Auto: QFile(path).remove(); break;
|
|
case ANGLE::D3D9: write("d3d9"); break;
|
|
case ANGLE::D3D11: write("d3d11"); break;
|
|
case ANGLE::D3D11on12: write("d3d11on12"); break;
|
|
//case ANGLE::OpenGL: write("gl"); break;
|
|
default: Unexpected("ANGLE backend value.");
|
|
}
|
|
}
|
|
|
|
ANGLE CurrentANGLE() {
|
|
return ResolvedANGLE;
|
|
}
|
|
|
|
QList<QByteArray> EGLExtensions(not_null<QOpenGLContext*> context) {
|
|
const auto native = QGuiApplication::platformNativeInterface();
|
|
Assert(native != nullptr);
|
|
|
|
const auto display = static_cast<EGLDisplay>(
|
|
native->nativeResourceForContext(
|
|
QByteArrayLiteral("egldisplay"),
|
|
context));
|
|
return display
|
|
? QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ')
|
|
: QList<QByteArray>();
|
|
}
|
|
|
|
#endif // Q_OS_WIN
|
|
|
|
} // namespace Ui::GL
|