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

View file

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

View file

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

View file

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

View file

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

View file

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