init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,290 @@
// 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/platform/base_platform_info.h"
#include <QtCore/QSet>
#include <QtCore/QFile>
#include <QtGui/QWindow>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLFunctions>
#include <QOpenGLWidget>
#ifdef DESKTOP_APP_USE_ANGLE
#include <QtGui/QGuiApplication>
#include <qpa/qplatformnativeinterface.h>
#include <EGL/egl.h>
#endif // DESKTOP_APP_USE_ANGLE
#define LOG_ONCE(x) [[maybe_unused]] static auto logged = [&] { LOG(x); return true; }();
namespace Ui::GL {
namespace {
bool ForceDisabled/* = false*/;
bool LastCheckCrashed/* = false*/;
#ifdef DESKTOP_APP_USE_ANGLE
ANGLE ResolvedANGLE/* = ANGLE::Auto*/;
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 // DESKTOP_APP_USE_ANGLE
void CrashCheckStart() {
auto f = QFile(Integration::Instance().openglCheckFilePath());
if (f.open(QIODevice::WriteOnly)) {
f.write("1", 1);
f.close();
}
}
} // namespace
Capabilities CheckCapabilities(QWidget *widget) {
if (!Platform::IsMac()) {
if (ForceDisabled) {
LOG_ONCE(("OpenGL: Force-disabled."));
return {};
} else if (LastCheckCrashed) {
LOG_ONCE(("OpenGL: Last-crashed."));
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;
}();
CrashCheckStart();
const auto guard = gsl::finally([=] {
CrashCheckFinish();
});
auto tester = QOpenGLWidget(widget);
tester.setAttribute(Qt::WA_TranslucentBackground);
if (tester.window()->testAttribute(Qt::WA_TranslucentBackground)) {
auto format = tester.format();
format.setAlphaBufferSize(8);
tester.setFormat(format);
}
const auto guard2 = [&]() -> std::optional<gsl::final_action<Fn<void()>>> {
if (!tester.window()->windowHandle()) {
tester.window()->createWinId();
return gsl::finally(Fn<void()>([&] {
tester.window()->windowHandle()->destroy();
tester.window()->setAttribute(Qt::WA_OutsideWSRange, false);
}));
}
return std::nullopt;
}();
tester.grabFramebuffer(); // Force initialize().
const auto context = tester.context();
if (!context
|| !context->isValid()
|| !context->makeCurrent(tester.window()->windowHandle())) {
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 DESKTOP_APP_USE_ANGLE
auto egllist = QStringList();
for (const auto &extension : EGLExtensions(context)) {
egllist.append(QString::fromLatin1(extension));
}
LOG(("EGL Extensions: %1").arg(egllist.join(", ")));
#endif // DESKTOP_APP_USE_ANGLE
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;
}
void DetectLastCheckCrash() {
[[maybe_unused]] static const auto Once = [] {
LastCheckCrashed = !Platform::IsMac()
&& QFile::exists(Integration::Instance().openglCheckFilePath());
return false;
}();
}
bool LastCrashCheckFailed() {
DetectLastCheckCrash();
return LastCheckCrashed;
}
void CrashCheckFinish() {
QFile::remove(Integration::Instance().openglCheckFilePath());
}
void ForceDisable(bool disable) {
if (!Platform::IsMac()) {
ForceDisabled = disable;
}
}
#ifdef DESKTOP_APP_USE_ANGLE
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;
}
#endif // DESKTOP_APP_USE_ANGLE
} // namespace Ui::GL

View File

@@ -0,0 +1,53 @@
// 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
//
#pragma once
#include "base/flags.h"
// ANGLE is used only on Windows with Qt < 6.
#if defined Q_OS_WIN && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#define DESKTOP_APP_USE_ANGLE
#endif // Q_OS_WIN && Qt < 6
class QOpenGLContext;
namespace Ui::GL {
enum class Backend {
Raster,
OpenGL,
};
struct Capabilities {
bool supported = false;
bool transparency = false;
};
[[nodiscard]] Capabilities CheckCapabilities(QWidget *widget = nullptr);
[[nodiscard]] Backend ChooseBackendDefault(Capabilities capabilities);
void ForceDisable(bool disable);
void DetectLastCheckCrash();
[[nodiscard]] bool LastCrashCheckFailed();
void CrashCheckFinish();
#ifdef DESKTOP_APP_USE_ANGLE
enum class ANGLE {
Auto,
D3D9,
D3D11,
D3D11on12,
//OpenGL,
};
void ConfigureANGLE(); // Requires Ui::Integration being set.
void ChangeANGLE(ANGLE backend);
[[nodiscard]] ANGLE CurrentANGLE();
#endif // DESKTOP_APP_USE_ANGLE
} // namespace Ui::GL

View File

@@ -0,0 +1,165 @@
// 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_image.h"
#include <QtGui/QOpenGLFunctions>
namespace Ui::GL {
namespace details {
void GenerateTextures(
QOpenGLFunctions &f,
gsl::span<GLuint> values,
GLint filter,
GLint clamp) {
Expects(!values.empty());
f.glGenTextures(values.size(), values.data());
for (const auto texture : values) {
f.glBindTexture(GL_TEXTURE_2D, texture);
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp);
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp);
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
f.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}
}
void DestroyTextures(QOpenGLFunctions *f, gsl::span<GLuint> values) {
Expects(!values.empty());
if (f) {
f->glDeleteTextures(values.size(), values.data());
}
ranges::fill(values, 0);
}
void GenerateFramebuffers(QOpenGLFunctions &f, gsl::span<GLuint> values) {
Expects(!values.empty());
f.glGenFramebuffers(values.size(), values.data());
}
void DestroyFramebuffers(QOpenGLFunctions *f, gsl::span<GLuint> values) {
Expects(!values.empty());
if (f) {
f->glDeleteFramebuffers(values.size(), values.data());
}
ranges::fill(values, 0);
}
} // namespace details
void Image::setImage(QImage image, QSize subimage) {
Expects(subimage.width() <= image.width()
&& subimage.height() <= image.height());
_image = std::move(image);
_subimage = subimage.isValid() ? subimage : _image.size();
}
const QImage &Image::image() const {
return _image;
}
QImage Image::takeImage() {
return _image.isNull() ? base::take(_storage) : base::take(_image);
}
void Image::invalidate() {
_storage = base::take(_image);
_subimage = QSize();
}
void Image::bind(QOpenGLFunctions &f) {
_textures.ensureCreated(f, GL_NEAREST);
if (_subimage.isEmpty()) {
_textureSize = _subimage;
return;
}
const auto cacheKey = _image.cacheKey();
const auto upload = (_cacheKey != cacheKey);
if (upload) {
_cacheKey = cacheKey;
}
_textures.bind(f, 0);
if (upload) {
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, _image.bytesPerLine() / 4);
if (_textureSize.width() < _subimage.width()
|| _textureSize.height() < _subimage.height()) {
_textureSize = _subimage;
f.glTexImage2D(
GL_TEXTURE_2D,
0,
kFormatRGBA,
_subimage.width(),
_subimage.height(),
0,
kFormatRGBA,
GL_UNSIGNED_BYTE,
_image.constBits());
} else {
f.glTexSubImage2D(
GL_TEXTURE_2D,
0,
0,
0,
_subimage.width(),
_subimage.height(),
kFormatRGBA,
GL_UNSIGNED_BYTE,
_image.constBits());
}
f.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
}
void Image::destroy(QOpenGLFunctions *f) {
invalidate();
_textures.destroy(f);
_cacheKey = 0;
_textureSize = QSize();
}
TexturedRect Image::texturedRect(
const QRect &geometry,
const QRect &texture,
const QRect &clip) {
Expects(!_image.isNull());
const auto visible = clip.isNull()
? geometry
: clip.intersected(geometry);
if (visible.isEmpty()) {
return TexturedRect{
.geometry = Rect(visible),
.texture = Rect(0., 0., 0., 0.),
};
}
const auto xFactor = texture.width() / geometry.width();
const auto yFactor = texture.height() / geometry.height();
const auto usedTexture = QRect(
texture.x() + (visible.x() - geometry.x()) * xFactor,
texture.y() + (visible.y() - geometry.y()) * yFactor,
visible.width() * xFactor,
visible.height() * yFactor);
const auto dimensions = QSizeF((_textureSize.width() < _subimage.width()
|| _textureSize.height() < _subimage.height())
? _subimage
: _textureSize);
return {
.geometry = Rect(visible),
.texture = Rect(QRectF(
usedTexture.x() / dimensions.width(),
usedTexture.y() / dimensions.height(),
usedTexture.width() / dimensions.width(),
usedTexture.height() / dimensions.height())),
};
}
} // namespace Ui::GL

View File

@@ -0,0 +1,145 @@
// 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
//
#pragma once
#include "ui/gl/gl_math.h"
#include <QtGui/QOpenGLFunctions>
namespace Ui::GL {
namespace details {
void GenerateTextures(
QOpenGLFunctions &f,
gsl::span<GLuint> values,
GLint filter,
GLint clamp);
void DestroyTextures(QOpenGLFunctions *f, gsl::span<GLuint> values);
void GenerateFramebuffers(QOpenGLFunctions &f, gsl::span<GLuint> values);
void DestroyFramebuffers(QOpenGLFunctions *f, gsl::span<GLuint> values);
} // namespace details
template <size_t Count>
class Textures final {
public:
static_assert(Count > 0);
void ensureCreated(
QOpenGLFunctions &f,
GLint filter = GL_LINEAR,
GLint clamp = GL_CLAMP_TO_EDGE) {
if (!created()) {
details::GenerateTextures(
f,
gsl::make_span(_values),
filter,
clamp);
}
}
void destroy(QOpenGLFunctions *f) {
if (created()) {
details::DestroyTextures(f, gsl::make_span(_values));
}
}
void bind(QOpenGLFunctions &f, int index) const {
Expects(index >= 0 && index < Count);
f.glBindTexture(GL_TEXTURE_2D, _values[index]);
}
[[nodiscard]] GLuint id(int index) const {
Expects(index >= 0 && index < Count);
return _values[index];
}
[[nodiscard]] bool created() const {
return (_values[0] != 0);
}
private:
std::array<GLuint, Count> _values = { { 0 } };
};
template <size_t Count>
class Framebuffers final {
public:
static_assert(Count > 0);
void ensureCreated(QOpenGLFunctions &f) {
if (!created()) {
details::GenerateFramebuffers(f, gsl::make_span(_values));
}
}
void destroy(QOpenGLFunctions *f) {
if (created()) {
details::DestroyFramebuffers(f, gsl::make_span(_values));
}
}
void bind(QOpenGLFunctions &f, int index) const {
Expects(index >= 0 && index < Count);
f.glBindFramebuffer(GL_FRAMEBUFFER, _values[index]);
}
[[nodiscard]] bool created() const {
return (_values[0] != 0);
}
private:
std::array<GLuint, Count> _values = { { 0 } };
};
struct TexturedRect {
Rect geometry;
Rect texture;
};
class Image final {
public:
void setImage(QImage image, QSize subimage = QSize());
[[nodiscard]] const QImage &image() const;
[[nodiscard]] QImage takeImage();
void invalidate();
void bind(QOpenGLFunctions &f);
void destroy(QOpenGLFunctions *f);
[[nodiscard]] TexturedRect texturedRect(
const QRect &geometry,
const QRect &texture,
const QRect &clip = QRect());
explicit operator bool() const {
return !_image.isNull();
}
private:
QImage _image;
QImage _storage;
Textures<1> _textures;
qint64 _cacheKey = 0;
QSize _subimage;
QSize _textureSize;
};
#if defined Q_OS_WIN && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
inline constexpr auto kFormatRGBA = GL_BGRA_EXT;
inline constexpr auto kSwizzleRedBlue = false;
#else // Q_OS_WIN
inline constexpr auto kFormatRGBA = GL_RGBA;
inline constexpr auto kSwizzleRedBlue = true;
#endif // Q_OS_WIN
} // namespace Ui::GL

View File

@@ -0,0 +1,36 @@
// 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_math.h"
namespace Ui::GL {
QVector4D Uniform(const QRect &rect, float factor) {
return QVector4D(
rect.x() * factor,
rect.y() * factor,
rect.width() * factor,
rect.height() * factor);
}
QVector4D Uniform(const Rect &rect) {
return QVector4D(rect.x(), rect.y(), rect.width(), rect.height());
}
QSizeF Uniform(QSize size) {
return size;
}
Rect TransformRect(const Rect &raster, QSize viewport, float factor) {
return {
raster.left() * factor,
float(viewport.height() - raster.bottom()) * factor,
raster.width() * factor,
raster.height() * factor,
};
}
} // namespace Ui::GL

View File

@@ -0,0 +1,83 @@
// 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
//
#pragma once
#include <QtGui/QVector4D>
#include <QtCore/QSizeF>
namespace Ui::GL {
class Rect final {
public:
Rect(QRect rect)
: _x(rect.x())
, _y(rect.y())
, _width(rect.width())
, _height(rect.height()) {
}
Rect(QRectF rect)
: _x(rect.x())
, _y(rect.y())
, _width(rect.width())
, _height(rect.height()) {
}
Rect(float x, float y, float width, float height)
: _x(x)
, _y(y)
, _width(width)
, _height(height) {
}
[[nodiscard]] float x() const {
return _x;
}
[[nodiscard]] float y() const {
return _y;
}
[[nodiscard]] float width() const {
return _width;
}
[[nodiscard]] float height() const {
return _height;
}
[[nodiscard]] float left() const {
return _x;
}
[[nodiscard]] float top() const {
return _y;
}
[[nodiscard]] float right() const {
return _x + _width;
}
[[nodiscard]] float bottom() const {
return _y + _height;
}
[[nodiscard]] bool empty() const {
return (_width <= 0) || (_height <= 0);
}
private:
float _x = 0;
float _y = 0;
float _width = 0;
float _height = 0;
};
[[nodiscard]] QVector4D Uniform(const QRect &rect, float factor);
[[nodiscard]] QVector4D Uniform(const Rect &rect);
[[nodiscard]] QSizeF Uniform(QSize size);
[[nodiscard]] Rect TransformRect(
const Rect &raster,
QSize viewport,
float factor);
} // namespace Ui::GL

View File

@@ -0,0 +1,125 @@
// 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_primitives.h"
#include "ui/gl/gl_shader.h"
#include "ui/style/style_core.h"
#include <QtGui/QOpenGLFunctions>
namespace Ui::GL {
static_assert(std::is_same_v<float, GLfloat>);
void FillRectTriangleVertices(float *coords, Rect rect) {
coords[0] = coords[10] = rect.left();
coords[1] = coords[11] = rect.top();
coords[2] = rect.right();
coords[3] = rect.top();
coords[4] = coords[6] = rect.right();
coords[5] = coords[7] = rect.bottom();
coords[8] = rect.left();
coords[9] = rect.bottom();
}
void FillTriangles(
QOpenGLFunctions &f,
gsl::span<const float> coords,
not_null<QOpenGLBuffer*> buffer,
not_null<QOpenGLShaderProgram*> program,
const QColor &color,
Fn<void()> additional) {
Expects(coords.size() % 6 == 0);
if (coords.empty()) {
return;
}
buffer->bind();
buffer->allocate(coords.data(), coords.size() * sizeof(GLfloat));
program->setUniformValue("s_color", color);
GLint position = program->attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
nullptr);
f.glEnableVertexAttribArray(position);
if (additional) {
additional();
}
f.glDrawArrays(GL_TRIANGLES, 0, coords.size() / 2);
f.glDisableVertexAttribArray(position);
}
void FillRectangle(
QOpenGLFunctions &f,
not_null<QOpenGLShaderProgram*> program,
int skipVertices,
const QColor &color) {
const auto shift = [&](int elements) {
return reinterpret_cast<const void*>(
(skipVertices * 4 + elements) * sizeof(GLfloat));
};
program->setUniformValue("s_color", color);
GLint position = program->attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
2 * sizeof(GLfloat),
shift(0));
f.glEnableVertexAttribArray(position);
f.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
f.glDisableVertexAttribArray(position);
}
void FillTexturedRectangle(
QOpenGLFunctions &f,
not_null<QOpenGLShaderProgram*> program,
int skipVertices) {
const auto shift = [&](int elements) {
return reinterpret_cast<const void*>(
(skipVertices * 4 + elements) * sizeof(GLfloat));
};
GLint position = program->attributeLocation("position");
f.glVertexAttribPointer(
position,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(GLfloat),
shift(0));
f.glEnableVertexAttribArray(position);
GLint texcoord = program->attributeLocation("v_texcoordIn");
f.glVertexAttribPointer(
texcoord,
2,
GL_FLOAT,
GL_FALSE,
4 * sizeof(GLfloat),
shift(2));
f.glEnableVertexAttribArray(texcoord);
f.glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
f.glDisableVertexAttribArray(position);
f.glDisableVertexAttribArray(texcoord);
}
} // namespace Ui::GL

View File

@@ -0,0 +1,39 @@
// 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
//
#pragma once
#include "ui/gl/gl_math.h"
#include "ui/style/style_core.h"
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
class QOpenGLFunctions;
namespace Ui::GL {
void FillRectTriangleVertices(float *coords, Rect rect);
void FillTriangles(
QOpenGLFunctions &f,
gsl::span<const float> coords,
not_null<QOpenGLBuffer*> buffer,
not_null<QOpenGLShaderProgram*> program,
const QColor &color,
Fn<void()> additional = nullptr);
void FillRectangle(
QOpenGLFunctions &f,
not_null<QOpenGLShaderProgram*> program,
int skipVertices,
const QColor &color);
void FillTexturedRectangle(
QOpenGLFunctions &f,
not_null<QOpenGLShaderProgram*> program,
int skipVertices = 0);
} // namespace Ui::GL

View File

@@ -0,0 +1,240 @@
// 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_shader.h"
#include "ui/gl/gl_image.h"
#include "base/debug_log.h"
#include <QtGui/QOpenGLContext>
namespace Ui::GL {
[[nodiscard]] bool IsOpenGLES() {
const auto current = QOpenGLContext::currentContext();
Assert(current != nullptr);
return (current->format().renderableType() == QSurfaceFormat::OpenGLES);
}
QString VertexShader(const std::vector<ShaderPart> &parts) {
const auto version = IsOpenGLES()
? QString("#version 100\nprecision highp float;\n")
: QString("#version 120\n");
const auto accumulate = [&](auto proj) {
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
};
return version + R"(
attribute vec2 position;
)" + accumulate(&ShaderPart::header) + R"(
void main() {
vec4 result = vec4(position, 0., 1.);
)" + accumulate(&ShaderPart::body) + R"(
gl_Position = result;
}
)";
}
QString FragmentShader(const std::vector<ShaderPart> &parts) {
const auto version = IsOpenGLES()
? QString("#version 100\nprecision highp float;\n")
: QString("#version 120\n");
const auto accumulate = [&](auto proj) {
return ranges::accumulate(parts, QString(), std::plus<>(), proj);
};
return version + accumulate(&ShaderPart::header) + R"(
void main() {
vec4 result = vec4(0., 0., 0., 0.);
)" + accumulate(&ShaderPart::body) + R"(
gl_FragColor = result;
}
)";
}
ShaderPart VertexPassTextureCoord(char prefix) {
const auto name = prefix + QString("_texcoord");
return {
.header = R"(
attribute vec2 )" + name + R"(In;
varying vec2 )" + name + ";\n",
.body = R"(
)" + name + " = " + name + "In;\n",
};
}
ShaderPart FragmentSampleARGB32Texture() {
return {
.header = R"(
varying vec2 v_texcoord;
uniform sampler2D s_texture;
)",
.body = R"(
result = texture2D(s_texture, v_texcoord);
)" + (kSwizzleRedBlue
? R"(
result = vec4(result.b, result.g, result.r, result.a);
)" : QString()),
};
}
QString FragmentYUV2RGB() {
return R"(
result = vec4(
1.164 * y + 1.596 * v,
1.164 * y - 0.392 * u - 0.813 * v,
1.164 * y + 2.017 * u,
1.);
)";
}
ShaderPart FragmentSampleYUV420Texture() {
return {
.header = R"(
varying vec2 v_texcoord;
uniform sampler2D y_texture;
uniform sampler2D u_texture;
uniform sampler2D v_texture;
)",
.body = R"(
float y = texture2D(y_texture, v_texcoord).a - 0.0625;
float u = texture2D(u_texture, v_texcoord).a - 0.5;
float v = texture2D(v_texture, v_texcoord).a - 0.5;
)" + FragmentYUV2RGB(),
};
}
ShaderPart FragmentSampleNV12Texture() {
return {
.header = R"(
varying vec2 v_texcoord;
uniform sampler2D y_texture;
uniform sampler2D uv_texture;
)",
.body = R"(
float y = texture2D(y_texture, v_texcoord).a - 0.0625;
vec2 uv = texture2D(uv_texture, v_texcoord).rg - vec2(0.5, 0.5);
float u = uv.x;
float v = uv.y;
)" + FragmentYUV2RGB(),
};
}
ShaderPart FragmentGlobalOpacity() {
return {
.header = R"(
uniform float g_opacity;
)",
.body = R"(
result *= g_opacity;
)",
};
}
ShaderPart VertexViewportTransform() {
return {
.header = R"(
uniform vec2 viewport;
vec4 transform(vec4 position) {
return vec4(
vec2(-1, -1) + 2. * position.xy / viewport,
position.z,
position.w);
}
)",
.body = R"(
result = transform(result);
)",
};
}
ShaderPart FragmentRoundCorners() {
return {
.header = R"(
uniform vec4 roundRect;
uniform vec2 radiusOutline;
uniform vec4 roundBg;
uniform vec4 outlineFg;
vec2 roundedCorner() {
vec2 rectHalf = roundRect.zw / 2.;
vec2 rectCenter = roundRect.xy + rectHalf;
vec2 fromRectCenter = abs(gl_FragCoord.xy - rectCenter);
vec2 vectorRadius = radiusOutline.xx + vec2(0.5, 0.5);
vec2 fromCenterWithRadius = fromRectCenter + vectorRadius;
vec2 fromRoundingCenter = max(fromCenterWithRadius, rectHalf)
- rectHalf;
float rounded = length(fromRoundingCenter) - radiusOutline.x;
float outline = rounded + radiusOutline.y;
return vec2(
1. - smoothstep(0., 1., rounded),
1. - (smoothstep(0., 1., outline) * outlineFg.a));
}
)",
.body = R"(
vec2 roundOutline = roundedCorner();
result = result * roundOutline.y
+ vec4(outlineFg.rgb, 1) * (1. - roundOutline.y);
result = result * roundOutline.x + roundBg * (1. - roundOutline.x);
)",
};
}
ShaderPart FragmentStaticColor() {
return {
.header = R"(
uniform vec4 s_color;
)",
.body = R"(
result = s_color;
)",
};
}
not_null<QOpenGLShader*> MakeShader(
not_null<QOpenGLShaderProgram*> program,
QOpenGLShader::ShaderType type,
const QString &source) {
const auto result = new QOpenGLShader(type, program);
if (!result->compileSourceCode(source)) {
LOG(("Shader Compilation Failed: %1, error %2.").arg(
source,
result->log()));
}
program->addShader(result);
return result;
}
Program LinkProgram(
not_null<QOpenGLShaderProgram*> program,
std::variant<QString, not_null<QOpenGLShader*>> vertex,
std::variant<QString, not_null<QOpenGLShader*>> fragment) {
const auto vertexAsSource = v::is<QString>(vertex);
const auto v = vertexAsSource
? MakeShader(
program,
QOpenGLShader::Vertex,
v::get<QString>(vertex))
: v::get<not_null<QOpenGLShader*>>(vertex);
if (!vertexAsSource) {
program->addShader(v);
}
const auto fragmentAsSource = v::is<QString>(fragment);
const auto f = fragmentAsSource
? MakeShader(
program,
QOpenGLShader::Fragment,
v::get<QString>(fragment))
: v::get<not_null<QOpenGLShader*>>(fragment);
if (!fragmentAsSource) {
program->addShader(f);
}
if (!program->link()) {
LOG(("Shader Link Failed: %1.").arg(program->log()));
}
return { v, f };
}
} // namespace Ui::GL

View File

@@ -0,0 +1,48 @@
// 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
//
#pragma once
#include <QtCore/QString>
#include <QOpenGLShader>
class OpenGLShaderProgram;
namespace Ui::GL {
struct ShaderPart {
QString header;
QString body;
};
[[nodiscard]] QString VertexShader(const std::vector<ShaderPart> &parts);
[[nodiscard]] QString FragmentShader(const std::vector<ShaderPart> &parts);
[[nodiscard]] ShaderPart VertexPassTextureCoord(char prefix = 'v');
[[nodiscard]] ShaderPart FragmentSampleARGB32Texture();
[[nodiscard]] ShaderPart FragmentSampleYUV420Texture();
[[nodiscard]] ShaderPart FragmentSampleNV12Texture();
[[nodiscard]] ShaderPart FragmentGlobalOpacity();
[[nodiscard]] ShaderPart VertexViewportTransform();
[[nodiscard]] ShaderPart FragmentRoundCorners();
[[nodiscard]] ShaderPart FragmentStaticColor();
not_null<QOpenGLShader*> MakeShader(
not_null<QOpenGLShaderProgram*> program,
QOpenGLShader::ShaderType type,
const QString &source);
struct Program {
not_null<QOpenGLShader*> vertex;
not_null<QOpenGLShader*> fragment;
};
Program LinkProgram(
not_null<QOpenGLShaderProgram*> program,
std::variant<QString, not_null<QOpenGLShader*>> vertex,
std::variant<QString, not_null<QOpenGLShader*>> fragment);
} // namespace Ui::GL

View File

@@ -0,0 +1,187 @@
// 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_surface.h"
#include "ui/rp_widget.h"
#include "ui/painter.h"
#include <QtCore/QCoreApplication>
#include <QtGui/QtEvents>
#include <QtGui/QOpenGLContext>
#include <QtGui/QWindow>
#include <QOpenGLWidget>
namespace Ui::GL {
namespace {
struct SurfaceTraits : RpWidgetDefaultTraits {
static constexpr bool kSetZeroGeometry = false;
};
class SurfaceOpenGL final
: public RpWidgetBase<QOpenGLWidget, SurfaceTraits> {
public:
SurfaceOpenGL(QWidget *parent, std::unique_ptr<Renderer> renderer);
~SurfaceOpenGL();
private:
void initializeGL() override;
void resizeEvent(QResizeEvent *e) override;
void resizeGL(int w, int h) override;
void paintEvent(QPaintEvent *e) override;
void paintGL() override;
bool eventHook(QEvent *e) override;
void callDeInit();
const std::unique_ptr<Renderer> _renderer;
QMetaObject::Connection _connection;
QSize _deviceSize;
};
class SurfaceRaster final : public RpWidgetBase<QWidget, SurfaceTraits> {
public:
SurfaceRaster(QWidget *parent, std::unique_ptr<Renderer> renderer);
private:
void paintEvent(QPaintEvent *e) override;
const std::unique_ptr<Renderer> _renderer;
};
SurfaceOpenGL::SurfaceOpenGL(
QWidget *parent,
std::unique_ptr<Renderer> renderer)
: RpWidgetBase<QOpenGLWidget, SurfaceTraits>(parent)
, _renderer(std::move(renderer)) {
setUpdateBehavior(QOpenGLWidget::PartialUpdate);
}
SurfaceOpenGL::~SurfaceOpenGL() {
callDeInit();
}
void SurfaceOpenGL::initializeGL() {
if (_connection) {
QObject::disconnect(base::take(_connection));
}
const auto context = this->context();
_connection = QObject::connect(
context,
&QOpenGLContext::aboutToBeDestroyed,
[=] { callDeInit(); });
_renderer->init(*context->functions());
}
void SurfaceOpenGL::resizeEvent(QResizeEvent *e) {
if (!window()->windowHandle()) {
return;
}
QOpenGLWidget::resizeEvent(e);
}
void SurfaceOpenGL::resizeGL(int w, int h) {
_deviceSize = QSize(w, h) * devicePixelRatio();
_renderer->resize(this, *context()->functions(), w, h);
}
void SurfaceOpenGL::paintEvent(QPaintEvent *e) {
if (_deviceSize != size() * devicePixelRatio()) {
QCoreApplication::postEvent(this, new QResizeEvent(size(), size()));
}
QOpenGLWidget::paintEvent(e);
}
void SurfaceOpenGL::paintGL() {
if (!updatesEnabled() || size().isEmpty() || !isValid()) {
return;
}
const auto f = context()->functions();
if (const auto bg = _renderer->clearColor()) {
f->glClearColor(bg->redF(), bg->greenF(), bg->blueF(), bg->alphaF());
f->glClear(GL_COLOR_BUFFER_BIT);
}
f->glDisable(GL_BLEND);
_renderer->paint(this, *f);
}
bool SurfaceOpenGL::eventHook(QEvent *e) {
const auto result = RpWidgetBase::eventHook(e);
if (e->type() == QEvent::ScreenChangeInternal) {
_deviceSize = size() * devicePixelRatio();
}
return result;
}
void SurfaceOpenGL::callDeInit() {
if (!_connection) {
return;
}
QObject::disconnect(base::take(_connection));
makeCurrent();
const auto context = this->context();
_renderer->deinit(
(isValid() && context && QOpenGLContext::currentContext() == context)
? context->functions()
: nullptr);
}
SurfaceRaster::SurfaceRaster(
QWidget *parent,
std::unique_ptr<Renderer> renderer)
: RpWidgetBase<QWidget, SurfaceTraits>(parent)
, _renderer(std::move(renderer)) {
}
void SurfaceRaster::paintEvent(QPaintEvent *e) {
auto p = Painter(this);
_renderer->paintFallback(p, e->region(), Backend::Raster);
}
} // namespace
void Renderer::paint(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
auto p = Painter(widget.get());
paintFallback(p, widget->rect(), Backend::OpenGL);
}
std::unique_ptr<RpWidgetWrap> CreateSurface(
Fn<ChosenRenderer(Capabilities)> chooseRenderer) {
auto chosen = chooseRenderer(CheckCapabilities(nullptr));
switch (chosen.backend) {
case Backend::OpenGL:
return std::make_unique<SurfaceOpenGL>(
nullptr,
std::move(chosen.renderer));
case Backend::Raster:
return std::make_unique<SurfaceRaster>(
nullptr,
std::move(chosen.renderer));
}
Unexpected("Backend value in Ui::GL::CreateSurface.");
}
std::unique_ptr<RpWidgetWrap> CreateSurface(
QWidget *parent,
ChosenRenderer chosen) {
switch (chosen.backend) {
case Backend::OpenGL:
return std::make_unique<SurfaceOpenGL>(
parent,
std::move(chosen.renderer));
case Backend::Raster:
return std::make_unique<SurfaceRaster>(
parent,
std::move(chosen.renderer));
}
Unexpected("Backend value in Ui::GL::CreateSurface.");
}
} // namespace Ui::GL

View File

@@ -0,0 +1,66 @@
// 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
//
#pragma once
#include "ui/gl/gl_detection.h"
#include <QtGui/QOpenGLFunctions>
class Painter;
class QOpenGLWidget;
namespace Ui {
class RpWidgetWrap;
} // namespace Ui
namespace Ui::GL {
class Renderer {
public:
virtual void init(QOpenGLFunctions &f) {
}
virtual void deinit(QOpenGLFunctions *f) {
}
virtual void resize(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f,
int w,
int h) {
}
virtual void paint(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f);
[[nodiscard]] virtual std::optional<QColor> clearColor() {
return std::nullopt;
}
virtual void paintFallback(
Painter &p,
const QRegion &clip,
Backend backend) {
}
virtual ~Renderer() = default;
};
struct ChosenRenderer {
std::unique_ptr<Renderer> renderer;
Backend backend = Backend::Raster;
};
[[nodiscard]] std::unique_ptr<RpWidgetWrap> CreateSurface(
Fn<ChosenRenderer(Capabilities)> chooseRenderer);
[[nodiscard]] std::unique_ptr<RpWidgetWrap> CreateSurface(
QWidget *parent,
ChosenRenderer chosen);
} // namespace Ui::GL

View File

@@ -0,0 +1,55 @@
// 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_window.h"
#include "ui/gl/gl_detection.h"
#include "ui/widgets/rp_window.h"
#include "base/debug_log.h"
namespace Ui::GL {
namespace {
[[nodiscard]] Fn<Backend(Capabilities)> ChooseBackendWrap(
Fn<Backend(Capabilities)> chooseBackend) {
return [=](Capabilities capabilities) {
const auto backend = chooseBackend(capabilities);
const auto use = backend == Backend::OpenGL;
LOG(("OpenGL: %1 (Window)").arg(use ? "[TRUE]" : "[FALSE]"));
return backend;
};
}
} // namespace
Window::Window() : Window(ChooseBackendDefault) {
}
Window::Window(Fn<Backend(Capabilities)> chooseBackend)
: _window(createWindow(ChooseBackendWrap(chooseBackend))) {
}
Window::~Window() = default;
Backend Window::backend() const {
return _backend;
}
not_null<RpWindow*> Window::window() const {
return _window.get();
}
not_null<RpWidget*> Window::widget() const {
return _window->body().get();
}
std::unique_ptr<RpWindow> Window::createWindow(
const Fn<Backend(Capabilities)> &chooseBackend) {
_backend = chooseBackend(CheckCapabilities());
return std::make_unique<RpWindow>();
}
} // namespace Ui::GL

View File

@@ -0,0 +1,38 @@
// 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
//
#pragma once
namespace Ui {
class RpWindow;
class RpWidget;
} // namespace Ui
namespace Ui::GL {
enum class Backend;
struct Capabilities;
class Window final {
public:
Window();
explicit Window(Fn<Backend(Capabilities)> chooseBackend);
~Window();
[[nodiscard]] Backend backend() const;
[[nodiscard]] not_null<RpWindow*> window() const;
[[nodiscard]] not_null<RpWidget*> widget() const;
private:
[[nodiscard]] std::unique_ptr<RpWindow> createWindow(
const Fn<Backend(Capabilities)> &chooseBackend);
Backend _backend = Backend();
const std::unique_ptr<RpWindow> _window;
};
} // namespace Ui::GL