Allow choosing OpenGL / Raster in runtime.

This commit is contained in:
John Preston 2021-05-19 16:46:58 +04:00
parent e9fcbfcbac
commit 95ee92088e
11 changed files with 334 additions and 69 deletions

View file

@ -57,6 +57,10 @@ PRIVATE
ui/effects/ripple_animation.h ui/effects/ripple_animation.h
ui/effects/slide_animation.cpp ui/effects/slide_animation.cpp
ui/effects/slide_animation.h ui/effects/slide_animation.h
ui/gl/gl_detection.cpp
ui/gl/gl_detection.h
ui/gl/gl_surface.cpp
ui/gl/gl_surface.h
ui/image/image_prepare.cpp ui/image/image_prepare.cpp
ui/image/image_prepare.h ui/image/image_prepare.h
ui/layers/box_content.cpp ui/layers/box_content.cpp

85
ui/gl/gl_detection.cpp Normal file
View file

@ -0,0 +1,85 @@
// 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 "base/debug_log.h"
#include <QtGui/QWindow>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLFunctions>
#include <QtWidgets/QOpenGLWidget>
#define LOG_ONCE(x) static auto logged = [&] { LOG(x); return true; };
namespace Ui::GL {
Capabilities CheckCapabilities(QWidget *widget) {
auto created = QOpenGLContext();
auto format = QSurfaceFormat();
format.setAlphaBufferSize(8);
if (widget) {
if (!widget->window()->windowHandle()) {
widget->window()->createWinId();
}
if (!widget->window()->windowHandle()) {
LOG(("OpenGL: Could not create window for widget."));
return {};
}
if (!widget->window()->windowHandle()->supportsOpenGL()) {
LOG_ONCE(("OpenGL: Not supported for window."));
return {};
}
} else {
created.setFormat(format);
if (!created.create()) {
LOG_ONCE(("OpenGL: Could not create context with alpha."));
return {};
}
}
auto tester = QOpenGLWidget(widget);
if (widget) {
tester.setFormat(format);
tester.grabFramebuffer(); // Force initialize().
}
const auto context = (widget ? tester.context() : &created);
if (!context) {
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 {};
}
const auto supported = context->format();
if (supported.profile() == QSurfaceFormat::NoProfile) {
LOG_ONCE(("OpenGL: NoProfile received in final format."));
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;
}
} // namespace Ui::GL

20
ui/gl/gl_detection.h Normal file
View file

@ -0,0 +1,20 @@
// 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"
namespace Ui::GL {
struct Capabilities {
bool supported = false;
bool transparency = false;
};
[[nodiscard]] Capabilities CheckCapabilities(QWidget *widget = nullptr);
} // namespace Ui::GL

103
ui/gl/gl_surface.cpp Normal file
View file

@ -0,0 +1,103 @@
// 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 <QtGui/QPainter>
#include <QtGui/QtEvents>
#include <QtGui/QOpenGLContext>
#include <QtWidgets/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);
private:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
const std::unique_ptr<Renderer> _renderer;
};
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)) {
}
void SurfaceOpenGL::initializeGL() {
_renderer->init(this, context()->functions());
}
void SurfaceOpenGL::resizeGL(int w, int h) {
_renderer->resize(this, context()->functions(), w, h);
}
void SurfaceOpenGL::paintGL() {
_renderer->paint(this, context()->functions());
}
SurfaceRaster::SurfaceRaster(
QWidget *parent,
std::unique_ptr<Renderer> renderer)
: RpWidgetBase<QWidget, SurfaceTraits>(parent)
, _renderer(std::move(renderer)) {
}
void SurfaceRaster::paintEvent(QPaintEvent *e) {
_renderer->paintFallback(QPainter(this), e->region(), Backend::Raster);
}
} // namespace
void Renderer::paint(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) {
paintFallback(QPainter(widget.get()), widget->rect(), Backend::OpenGL);
}
std::unique_ptr<RpWidgetWrap> CreateSurface(
QWidget *parent,
Fn<ChosenRenderer(Capabilities)> chooseRenderer) {
auto chosen = chooseRenderer(CheckCapabilities(parent));
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

62
ui/gl/gl_surface.h Normal file
View file

@ -0,0 +1,62 @@
// 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"
class QPainter;
class QOpenGLWidget;
class QOpenGLFunctions;
namespace Ui {
class RpWidgetWrap;
} // namespace Ui
namespace Ui::GL {
enum class Backend {
OpenGL,
Raster,
};
class Renderer {
public:
virtual void init(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f) {
}
virtual void resize(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f,
int w,
int h) {
}
virtual void paint(
not_null<QOpenGLWidget*> widget,
not_null<QOpenGLFunctions*> f);
virtual void paintFallback(
QPainter &&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(
QWidget *parent,
Fn<ChosenRenderer(Capabilities)> chooseRenderer);
} // namespace Ui::GL

View file

@ -27,71 +27,71 @@ void ResizeFitChild(
}, child->lifetime()); }, child->lifetime());
} }
rpl::producer<not_null<QEvent*>> RpWidgetMethods::events() const { rpl::producer<not_null<QEvent*>> RpWidgetWrap::events() const {
auto &stream = eventStreams().events; auto &stream = eventStreams().events;
return stream.events(); return stream.events();
} }
rpl::producer<QRect> RpWidgetMethods::geometryValue() const { rpl::producer<QRect> RpWidgetWrap::geometryValue() const {
auto &stream = eventStreams().geometry; auto &stream = eventStreams().geometry;
return stream.events_starting_with_copy(callGetGeometry()); return stream.events_starting_with_copy(rpWidget()->geometry());
} }
rpl::producer<QSize> RpWidgetMethods::sizeValue() const { rpl::producer<QSize> RpWidgetWrap::sizeValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.size(); }) | rpl::map([](QRect &&value) { return value.size(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<int> RpWidgetMethods::heightValue() const { rpl::producer<int> RpWidgetWrap::heightValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.height(); }) | rpl::map([](QRect &&value) { return value.height(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<int> RpWidgetMethods::widthValue() const { rpl::producer<int> RpWidgetWrap::widthValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.width(); }) | rpl::map([](QRect &&value) { return value.width(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<QPoint> RpWidgetMethods::positionValue() const { rpl::producer<QPoint> RpWidgetWrap::positionValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.topLeft(); }) | rpl::map([](QRect &&value) { return value.topLeft(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<int> RpWidgetMethods::leftValue() const { rpl::producer<int> RpWidgetWrap::leftValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.left(); }) | rpl::map([](QRect &&value) { return value.left(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<int> RpWidgetMethods::topValue() const { rpl::producer<int> RpWidgetWrap::topValue() const {
return geometryValue() return geometryValue()
| rpl::map([](QRect &&value) { return value.top(); }) | rpl::map([](QRect &&value) { return value.top(); })
| rpl::distinct_until_changed(); | rpl::distinct_until_changed();
} }
rpl::producer<int> RpWidgetMethods::desiredHeightValue() const { rpl::producer<int> RpWidgetWrap::desiredHeightValue() const {
return heightValue(); return heightValue();
} }
rpl::producer<bool> RpWidgetMethods::shownValue() const { rpl::producer<bool> RpWidgetWrap::shownValue() const {
auto &stream = eventStreams().shown; auto &stream = eventStreams().shown;
return stream.events_starting_with(!callIsHidden()); return stream.events_starting_with(!rpWidget()->isHidden());
} }
rpl::producer<QRect> RpWidgetMethods::paintRequest() const { rpl::producer<QRect> RpWidgetWrap::paintRequest() const {
return eventStreams().paint.events(); return eventStreams().paint.events();
} }
rpl::producer<> RpWidgetMethods::alive() const { rpl::producer<> RpWidgetWrap::alive() const {
return eventStreams().alive.events(); return eventStreams().alive.events();
} }
rpl::producer<> RpWidgetMethods::windowDeactivateEvents() const { rpl::producer<> RpWidgetWrap::windowDeactivateEvents() const {
const auto window = callGetWidget()->window()->windowHandle(); const auto window = rpWidget()->window()->windowHandle();
Assert(window != nullptr); Assert(window != nullptr);
return base::qt_signal_producer( return base::qt_signal_producer(
@ -102,7 +102,7 @@ rpl::producer<> RpWidgetMethods::windowDeactivateEvents() const {
}); });
} }
rpl::producer<> RpWidgetMethods::macWindowDeactivateEvents() const { rpl::producer<> RpWidgetWrap::macWindowDeactivateEvents() const {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
return windowDeactivateEvents(); return windowDeactivateEvents();
#else // Q_OS_MAC #else // Q_OS_MAC
@ -110,20 +110,21 @@ rpl::producer<> RpWidgetMethods::macWindowDeactivateEvents() const {
#endif // Q_OS_MAC #endif // Q_OS_MAC
} }
rpl::lifetime &RpWidgetMethods::lifetime() { rpl::lifetime &RpWidgetWrap::lifetime() {
return _lifetime; return _lifetime;
} }
bool RpWidgetMethods::handleEvent(QEvent *event) { bool RpWidgetWrap::handleEvent(QEvent *event) {
Expects(event != nullptr); Expects(event != nullptr);
auto streams = _eventStreams.get(); auto streams = _eventStreams.get();
if (!streams) { if (!streams) {
return eventHook(event); return eventHook(event);
} }
auto that = QPointer<QObject>(); auto that = QPointer<QWidget>();
if (streams->events.has_consumers()) { const auto allAreObserved = streams->events.has_consumers();
that = callCreateWeak(); if (allAreObserved) {
that = rpWidget();
streams->events.fire_copy(event); streams->events.fire_copy(event);
if (!that) { if (!that) {
return true; return true;
@ -133,10 +134,10 @@ bool RpWidgetMethods::handleEvent(QEvent *event) {
case QEvent::Move: case QEvent::Move:
case QEvent::Resize: case QEvent::Resize:
if (streams->geometry.has_consumers()) { if (streams->geometry.has_consumers()) {
if (!that) { if (!allAreObserved) {
that = callCreateWeak(); that = rpWidget();
} }
streams->geometry.fire_copy(callGetGeometry()); streams->geometry.fire_copy(rpWidget()->geometry());
if (!that) { if (!that) {
return true; return true;
} }
@ -145,8 +146,8 @@ bool RpWidgetMethods::handleEvent(QEvent *event) {
case QEvent::Paint: case QEvent::Paint:
if (streams->paint.has_consumers()) { if (streams->paint.has_consumers()) {
if (!that) { if (!allAreObserved) {
that = callCreateWeak(); that = rpWidget();
} }
const auto rect = static_cast<QPaintEvent*>(event)->rect(); const auto rect = static_cast<QPaintEvent*>(event)->rect();
streams->paint.fire_copy(rect); streams->paint.fire_copy(rect);
@ -160,13 +161,13 @@ bool RpWidgetMethods::handleEvent(QEvent *event) {
return eventHook(event); return eventHook(event);
} }
RpWidgetMethods::Initer::Initer(QWidget *parent, bool setZeroGeometry) { RpWidgetWrap::Initer::Initer(QWidget *parent, bool setZeroGeometry) {
if (setZeroGeometry) { if (setZeroGeometry) {
parent->setGeometry(0, 0, 0, 0); parent->setGeometry(0, 0, 0, 0);
} }
} }
void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) { void RpWidgetWrap::visibilityChangedHook(bool wasVisible, bool nowVisible) {
if (nowVisible != wasVisible) { if (nowVisible != wasVisible) {
if (auto streams = _eventStreams.get()) { if (auto streams = _eventStreams.get()) {
streams->shown.fire_copy(nowVisible); streams->shown.fire_copy(nowVisible);
@ -174,7 +175,7 @@ void RpWidgetMethods::visibilityChangedHook(bool wasVisible, bool nowVisible) {
} }
} }
auto RpWidgetMethods::eventStreams() const -> EventStreams& { auto RpWidgetWrap::eventStreams() const -> EventStreams& {
if (!_eventStreams) { if (!_eventStreams) {
_eventStreams = std::make_unique<EventStreams>(); _eventStreams = std::make_unique<EventStreams>();
} }

View file

@ -244,10 +244,13 @@ using RpWidgetParent = std::conditional_t<
TWidgetHelper<Widget>>; TWidgetHelper<Widget>>;
template <typename Widget, typename Traits> template <typename Widget, typename Traits>
class RpWidgetWrap; class RpWidgetBase;
class RpWidgetMethods { class RpWidgetWrap {
public: public:
virtual QWidget *rpWidget() = 0;
virtual const QWidget *rpWidget() const = 0;
rpl::producer<not_null<QEvent*>> events() const; rpl::producer<not_null<QEvent*>> events() const;
rpl::producer<QRect> geometryValue() const; rpl::producer<QRect> geometryValue() const;
rpl::producer<QSize> sizeValue() const; rpl::producer<QSize> sizeValue() const;
@ -274,7 +277,7 @@ public:
rpl::lifetime &lifetime(); rpl::lifetime &lifetime();
virtual ~RpWidgetMethods() = default; virtual ~RpWidgetWrap() = default;
protected: protected:
bool handleEvent(QEvent *event); bool handleEvent(QEvent *event);
@ -282,7 +285,7 @@ protected:
private: private:
template <typename Widget, typename Traits> template <typename Widget, typename Traits>
friend class RpWidgetWrap; friend class RpWidgetBase;
struct EventStreams { struct EventStreams {
rpl::event_stream<not_null<QEvent*>> events; rpl::event_stream<not_null<QEvent*>> events;
@ -296,11 +299,6 @@ private:
}; };
virtual void callSetVisible(bool visible) = 0; virtual void callSetVisible(bool visible) = 0;
virtual QWidget *callGetWidget() = 0;
virtual const QWidget *callGetWidget() const = 0;
virtual QPointer<QObject> callCreateWeak() = 0;
virtual QRect callGetGeometry() const = 0;
virtual bool callIsHidden() const = 0;
void visibilityChangedHook(bool wasVisible, bool nowVisible); void visibilityChangedHook(bool wasVisible, bool nowVisible);
EventStreams &eventStreams() const; EventStreams &eventStreams() const;
@ -315,22 +313,28 @@ struct RpWidgetDefaultTraits {
}; };
template <typename Widget, typename Traits = RpWidgetDefaultTraits> template <typename Widget, typename Traits = RpWidgetDefaultTraits>
class RpWidgetWrap class RpWidgetBase
: public RpWidgetParent<Widget> : public RpWidgetParent<Widget>
, public RpWidgetMethods { , public RpWidgetWrap {
using Self = RpWidgetWrap<Widget, Traits>; using Self = RpWidgetBase<Widget, Traits>;
using Parent = RpWidgetParent<Widget>; using Parent = RpWidgetParent<Widget>;
public: public:
using Parent::Parent; using Parent::Parent;
QWidget *rpWidget() final override {
return this;
}
const QWidget *rpWidget() const final override {
return this;
}
void setVisible(bool visible) final override { void setVisible(bool visible) final override {
auto wasVisible = !this->isHidden(); auto wasVisible = !this->isHidden();
setVisibleHook(visible); setVisibleHook(visible);
visibilityChangedHook(wasVisible, !this->isHidden()); visibilityChangedHook(wasVisible, !this->isHidden());
} }
~RpWidgetWrap() { ~RpWidgetBase() {
base::take(_lifetime); base::take(_lifetime);
base::take(_eventStreams); base::take(_eventStreams);
} }
@ -347,32 +351,17 @@ protected:
} }
private: private:
void callSetVisible(bool visible) override { void callSetVisible(bool visible) final override {
Self::setVisible(visible); Self::setVisible(visible); // Save one virtual method invocation.
}
QWidget *callGetWidget() override {
return this;
}
const QWidget *callGetWidget() const override {
return this;
}
QPointer<QObject> callCreateWeak() override {
return QPointer<QObject>((QObject*)this);
}
QRect callGetGeometry() const override {
return this->geometry();
}
bool callIsHidden() const override {
return this->isHidden();
} }
Initer _initer = { this, Traits::kSetZeroGeometry }; Initer _initer = { this, Traits::kSetZeroGeometry };
}; };
class RpWidget : public RpWidgetWrap<QWidget> { class RpWidget : public RpWidgetBase<QWidget> {
public: public:
using RpWidgetWrap<QWidget>::RpWidgetWrap; using RpWidgetBase<QWidget>::RpWidgetBase;
}; };

View file

@ -910,7 +910,7 @@ FlatInput::FlatInput(
const style::FlatInput &st, const style::FlatInput &st,
rpl::producer<QString> placeholder, rpl::producer<QString> placeholder,
const QString &v) const QString &v)
: RpWidgetWrap<QLineEdit>(v, parent) : Parent(v, parent)
, _oldtext(v) , _oldtext(v)
, _placeholderFull(std::move(placeholder)) , _placeholderFull(std::move(placeholder))
, _placeholderVisible(!v.length()) , _placeholderVisible(!v.length())

View file

@ -54,11 +54,11 @@ enum class InputSubmitSettings {
None, None,
}; };
class FlatInput : public RpWidgetWrap<QLineEdit> { class FlatInput : public RpWidgetBase<QLineEdit> {
// The Q_OBJECT meta info is used for qobject_cast! // The Q_OBJECT meta info is used for qobject_cast!
Q_OBJECT Q_OBJECT
using Parent = RpWidgetWrap<QLineEdit>; using Parent = RpWidgetBase<QLineEdit>;
public: public:
FlatInput( FlatInput(
QWidget *parent, QWidget *parent,
@ -545,11 +545,11 @@ private:
}; };
class MaskedInputField : public RpWidgetWrap<QLineEdit> { class MaskedInputField : public RpWidgetBase<QLineEdit> {
// The Q_OBJECT meta info is used for qobject_cast! // The Q_OBJECT meta info is used for qobject_cast!
Q_OBJECT Q_OBJECT
using Parent = RpWidgetWrap<QLineEdit>; using Parent = RpWidgetBase<QLineEdit>;
public: public:
MaskedInputField( MaskedInputField(
QWidget *parent, QWidget *parent,

View file

@ -272,7 +272,7 @@ void ScrollBar::resizeEvent(QResizeEvent *e) {
} }
ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch)
: RpWidgetWrap<QScrollArea>(parent) : Parent(parent)
, _st(st) , _st(st)
, _horizontalBar(this, false, &_st) , _horizontalBar(this, false, &_st)
, _verticalBar(this, true, &_st) , _verticalBar(this, true, &_st)

View file

@ -125,9 +125,10 @@ private:
QRect _bar; QRect _bar;
}; };
class ScrollArea : public RpWidgetWrap<QScrollArea> { class ScrollArea : public RpWidgetBase<QScrollArea> {
Q_OBJECT Q_OBJECT
using Parent = RpWidgetBase<QScrollArea>;
public: public:
ScrollArea(QWidget *parent, const style::ScrollArea &st = st::defaultScrollArea, bool handleTouch = true); ScrollArea(QWidget *parent, const style::ScrollArea &st = st::defaultScrollArea, bool handleTouch = true);