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
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:
290
Telegram/lib_ui/ui/gl/gl_detection.cpp
Normal file
290
Telegram/lib_ui/ui/gl/gl_detection.cpp
Normal 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
|
||||
53
Telegram/lib_ui/ui/gl/gl_detection.h
Normal file
53
Telegram/lib_ui/ui/gl/gl_detection.h
Normal 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
|
||||
165
Telegram/lib_ui/ui/gl/gl_image.cpp
Normal file
165
Telegram/lib_ui/ui/gl/gl_image.cpp
Normal 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
|
||||
145
Telegram/lib_ui/ui/gl/gl_image.h
Normal file
145
Telegram/lib_ui/ui/gl/gl_image.h
Normal 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
|
||||
36
Telegram/lib_ui/ui/gl/gl_math.cpp
Normal file
36
Telegram/lib_ui/ui/gl/gl_math.cpp
Normal 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
|
||||
83
Telegram/lib_ui/ui/gl/gl_math.h
Normal file
83
Telegram/lib_ui/ui/gl/gl_math.h
Normal 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
|
||||
125
Telegram/lib_ui/ui/gl/gl_primitives.cpp
Normal file
125
Telegram/lib_ui/ui/gl/gl_primitives.cpp
Normal 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
|
||||
39
Telegram/lib_ui/ui/gl/gl_primitives.h
Normal file
39
Telegram/lib_ui/ui/gl/gl_primitives.h
Normal 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
|
||||
240
Telegram/lib_ui/ui/gl/gl_shader.cpp
Normal file
240
Telegram/lib_ui/ui/gl/gl_shader.cpp
Normal 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
|
||||
48
Telegram/lib_ui/ui/gl/gl_shader.h
Normal file
48
Telegram/lib_ui/ui/gl/gl_shader.h
Normal 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
|
||||
187
Telegram/lib_ui/ui/gl/gl_surface.cpp
Normal file
187
Telegram/lib_ui/ui/gl/gl_surface.cpp
Normal 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
|
||||
66
Telegram/lib_ui/ui/gl/gl_surface.h
Normal file
66
Telegram/lib_ui/ui/gl/gl_surface.h
Normal 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
|
||||
55
Telegram/lib_ui/ui/gl/gl_window.cpp
Normal file
55
Telegram/lib_ui/ui/gl/gl_window.cpp
Normal 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
|
||||
38
Telegram/lib_ui/ui/gl/gl_window.h
Normal file
38
Telegram/lib_ui/ui/gl/gl_window.h
Normal 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
|
||||
Reference in New Issue
Block a user