From 95ee92088e62dfa20eb11d6fe59b0fb1834a1207 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 19 May 2021 16:46:58 +0400 Subject: [PATCH] Allow choosing OpenGL / Raster in runtime. --- CMakeLists.txt | 4 ++ ui/gl/gl_detection.cpp | 85 +++++++++++++++++++++++++++++ ui/gl/gl_detection.h | 20 +++++++ ui/gl/gl_surface.cpp | 103 ++++++++++++++++++++++++++++++++++++ ui/gl/gl_surface.h | 62 ++++++++++++++++++++++ ui/rp_widget.cpp | 61 ++++++++++----------- ui/rp_widget.h | 53 ++++++++----------- ui/widgets/input_fields.cpp | 2 +- ui/widgets/input_fields.h | 8 +-- ui/widgets/scroll_area.cpp | 2 +- ui/widgets/scroll_area.h | 3 +- 11 files changed, 334 insertions(+), 69 deletions(-) create mode 100644 ui/gl/gl_detection.cpp create mode 100644 ui/gl/gl_detection.h create mode 100644 ui/gl/gl_surface.cpp create mode 100644 ui/gl/gl_surface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 218418f..408c323 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ui/gl/gl_detection.cpp b/ui/gl/gl_detection.cpp new file mode 100644 index 0000000..2a6d2cb --- /dev/null +++ b/ui/gl/gl_detection.cpp @@ -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 +#include +#include +#include + +#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 diff --git a/ui/gl/gl_detection.h b/ui/gl/gl_detection.h new file mode 100644 index 0000000..1001e47 --- /dev/null +++ b/ui/gl/gl_detection.h @@ -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 diff --git a/ui/gl/gl_surface.cpp b/ui/gl/gl_surface.cpp new file mode 100644 index 0000000..3323fc7 --- /dev/null +++ b/ui/gl/gl_surface.cpp @@ -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 +#include +#include +#include + +namespace Ui::GL { +namespace { + +struct SurfaceTraits : RpWidgetDefaultTraits { + static constexpr bool kSetZeroGeometry = false; +}; + +class SurfaceOpenGL final + : public RpWidgetBase { +public: + SurfaceOpenGL(QWidget *parent, std::unique_ptr renderer); + +private: + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + + const std::unique_ptr _renderer; + +}; + +class SurfaceRaster final : public RpWidgetBase { +public: + SurfaceRaster(QWidget *parent, std::unique_ptr renderer); + +private: + void paintEvent(QPaintEvent *e) override; + + const std::unique_ptr _renderer; + +}; + +SurfaceOpenGL::SurfaceOpenGL( + QWidget *parent, + std::unique_ptr renderer) +: RpWidgetBase(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) +: RpWidgetBase(parent) +, _renderer(std::move(renderer)) { +} + +void SurfaceRaster::paintEvent(QPaintEvent *e) { + _renderer->paintFallback(QPainter(this), e->region(), Backend::Raster); +} + +} // namespace + +void Renderer::paint( + not_null widget, + not_null f) { + paintFallback(QPainter(widget.get()), widget->rect(), Backend::OpenGL); +} + +std::unique_ptr CreateSurface( + QWidget *parent, + Fn chooseRenderer) { + auto chosen = chooseRenderer(CheckCapabilities(parent)); + switch (chosen.backend) { + case Backend::OpenGL: + return std::make_unique( + parent, + std::move(chosen.renderer)); + case Backend::Raster: + return std::make_unique( + parent, + std::move(chosen.renderer)); + } + Unexpected("Backend value in Ui::GL::CreateSurface."); +} + +} // namespace Ui::GL diff --git a/ui/gl/gl_surface.h b/ui/gl/gl_surface.h new file mode 100644 index 0000000..7a09ef0 --- /dev/null +++ b/ui/gl/gl_surface.h @@ -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 widget, + not_null f) { + } + + virtual void resize( + not_null widget, + not_null f, + int w, + int h) { + } + + virtual void paint( + not_null widget, + not_null f); + + virtual void paintFallback( + QPainter &&p, + const QRegion &clip, + Backend backend) { + } + + virtual ~Renderer() = default; +}; + +struct ChosenRenderer { + std::unique_ptr renderer; + Backend backend = Backend::Raster; +}; + +[[nodiscard]] std::unique_ptr CreateSurface( + QWidget *parent, + Fn chooseRenderer); + +} // namespace Ui::GL diff --git a/ui/rp_widget.cpp b/ui/rp_widget.cpp index 16f8919..318df7b 100644 --- a/ui/rp_widget.cpp +++ b/ui/rp_widget.cpp @@ -27,71 +27,71 @@ void ResizeFitChild( }, child->lifetime()); } -rpl::producer> RpWidgetMethods::events() const { +rpl::producer> RpWidgetWrap::events() const { auto &stream = eventStreams().events; return stream.events(); } -rpl::producer RpWidgetMethods::geometryValue() const { +rpl::producer RpWidgetWrap::geometryValue() const { auto &stream = eventStreams().geometry; - return stream.events_starting_with_copy(callGetGeometry()); + return stream.events_starting_with_copy(rpWidget()->geometry()); } -rpl::producer RpWidgetMethods::sizeValue() const { +rpl::producer RpWidgetWrap::sizeValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.size(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::heightValue() const { +rpl::producer RpWidgetWrap::heightValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.height(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::widthValue() const { +rpl::producer RpWidgetWrap::widthValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.width(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::positionValue() const { +rpl::producer RpWidgetWrap::positionValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.topLeft(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::leftValue() const { +rpl::producer RpWidgetWrap::leftValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.left(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::topValue() const { +rpl::producer RpWidgetWrap::topValue() const { return geometryValue() | rpl::map([](QRect &&value) { return value.top(); }) | rpl::distinct_until_changed(); } -rpl::producer RpWidgetMethods::desiredHeightValue() const { +rpl::producer RpWidgetWrap::desiredHeightValue() const { return heightValue(); } -rpl::producer RpWidgetMethods::shownValue() const { +rpl::producer RpWidgetWrap::shownValue() const { auto &stream = eventStreams().shown; - return stream.events_starting_with(!callIsHidden()); + return stream.events_starting_with(!rpWidget()->isHidden()); } -rpl::producer RpWidgetMethods::paintRequest() const { +rpl::producer 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(); - if (streams->events.has_consumers()) { - that = callCreateWeak(); + auto that = QPointer(); + 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(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(); } diff --git a/ui/rp_widget.h b/ui/rp_widget.h index f6ce3c2..5c67939 100644 --- a/ui/rp_widget.h +++ b/ui/rp_widget.h @@ -244,10 +244,13 @@ using RpWidgetParent = std::conditional_t< TWidgetHelper>; template -class RpWidgetWrap; +class RpWidgetBase; -class RpWidgetMethods { +class RpWidgetWrap { public: + virtual QWidget *rpWidget() = 0; + virtual const QWidget *rpWidget() const = 0; + rpl::producer> events() const; rpl::producer geometryValue() const; rpl::producer 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 - friend class RpWidgetWrap; + friend class RpWidgetBase; struct EventStreams { rpl::event_stream> events; @@ -296,11 +299,6 @@ private: }; virtual void callSetVisible(bool visible) = 0; - virtual QWidget *callGetWidget() = 0; - virtual const QWidget *callGetWidget() const = 0; - virtual QPointer 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 -class RpWidgetWrap +class RpWidgetBase : public RpWidgetParent - , public RpWidgetMethods { - using Self = RpWidgetWrap; + , public RpWidgetWrap { + using Self = RpWidgetBase; using Parent = RpWidgetParent; 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 callCreateWeak() override { - return QPointer((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 { +class RpWidget : public RpWidgetBase { public: - using RpWidgetWrap::RpWidgetWrap; + using RpWidgetBase::RpWidgetBase; }; diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 011bdf7..7de77f4 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -910,7 +910,7 @@ FlatInput::FlatInput( const style::FlatInput &st, rpl::producer placeholder, const QString &v) -: RpWidgetWrap(v, parent) +: Parent(v, parent) , _oldtext(v) , _placeholderFull(std::move(placeholder)) , _placeholderVisible(!v.length()) diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index 6d18aa7..e942c07 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -54,11 +54,11 @@ enum class InputSubmitSettings { None, }; -class FlatInput : public RpWidgetWrap { +class FlatInput : public RpWidgetBase { // The Q_OBJECT meta info is used for qobject_cast! Q_OBJECT - using Parent = RpWidgetWrap; + using Parent = RpWidgetBase; public: FlatInput( QWidget *parent, @@ -545,11 +545,11 @@ private: }; -class MaskedInputField : public RpWidgetWrap { +class MaskedInputField : public RpWidgetBase { // The Q_OBJECT meta info is used for qobject_cast! Q_OBJECT - using Parent = RpWidgetWrap; + using Parent = RpWidgetBase; public: MaskedInputField( QWidget *parent, diff --git a/ui/widgets/scroll_area.cpp b/ui/widgets/scroll_area.cpp index a70239d..07082bf 100644 --- a/ui/widgets/scroll_area.cpp +++ b/ui/widgets/scroll_area.cpp @@ -272,7 +272,7 @@ void ScrollBar::resizeEvent(QResizeEvent *e) { } ScrollArea::ScrollArea(QWidget *parent, const style::ScrollArea &st, bool handleTouch) -: RpWidgetWrap(parent) +: Parent(parent) , _st(st) , _horizontalBar(this, false, &_st) , _verticalBar(this, true, &_st) diff --git a/ui/widgets/scroll_area.h b/ui/widgets/scroll_area.h index e9aea1f..c393a1c 100644 --- a/ui/widgets/scroll_area.h +++ b/ui/widgets/scroll_area.h @@ -125,9 +125,10 @@ private: QRect _bar; }; -class ScrollArea : public RpWidgetWrap { +class ScrollArea : public RpWidgetBase { Q_OBJECT + using Parent = RpWidgetBase; public: ScrollArea(QWidget *parent, const style::ScrollArea &st = st::defaultScrollArea, bool handleTouch = true);