diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c7e4672371..592ac2828f 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -786,6 +786,11 @@ PRIVATE media/view/media_view_overlay_widget.h media/view/media_view_pip.cpp media/view/media_view_pip.h + media/view/media_view_pip_opengl.cpp + media/view/media_view_pip_opengl.h + media/view/media_view_pip_raster.cpp + media/view/media_view_pip_raster.h + media/view/media_view_pip_renderer.h media/view/media_view_playback_controls.cpp media/view/media_view_playback_controls.h media/view/media_view_playback_progress.cpp diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp index fefbeb819a..d2071f8336 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.cpp @@ -176,7 +176,7 @@ FrameWithInfo Instance::frameWithInfo() const { return player().frameWithInfo(this); } -bool Instance::markFrameShown() { +bool Instance::markFrameShown() const { Expects(_shared != nullptr); return _shared->player().markFrameShown(); diff --git a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h index ad5bd05599..63e2ec7432 100644 --- a/Telegram/SourceFiles/media/streaming/media_streaming_instance.h +++ b/Telegram/SourceFiles/media/streaming/media_streaming_instance.h @@ -70,7 +70,7 @@ public: [[nodiscard]] QImage frame(const FrameRequest &request) const; [[nodiscard]] FrameWithInfo frameWithInfo() const; - bool markFrameShown(); + bool markFrameShown() const; void lockPlayer(); void unlockPlayer(); diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 95053d1ed9..81591e5118 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -297,9 +297,15 @@ pipPlayIcon: icon {{ "player_pip_play", mediaviewPipControlsFg }}; pipPlayIconOver: icon {{ "player_pip_play", mediaviewPipControlsFgOver }}; pipPauseIcon: icon {{ "player_pip_pause", mediaviewPipControlsFg }}; pipPauseIconOver: icon {{ "player_pip_pause", mediaviewPipControlsFgOver }}; -pipCloseIcon: icon {{ "player_pip_close", mediaviewPlaybackIconFg }}; -pipCloseIconOver: icon {{ "player_pip_close", mediaviewPlaybackIconFgOver }}; -pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFg }}; -pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPlaybackIconFgOver }}; +pipCloseIcon: icon {{ "player_pip_close", mediaviewPipControlsFg }}; +pipCloseIconOver: icon {{ "player_pip_close", mediaviewPipControlsFgOver }}; +pipEnlargeIcon: icon {{ "player_pip_enlarge", mediaviewPipControlsFg }}; +pipEnlargeIconOver: icon {{ "player_pip_enlarge", mediaviewPipControlsFgOver }}; +pipVolumeIcon0: icon {{ "player_volume_off", mediaviewPipControlsFg }}; +pipVolumeIcon0Over: icon {{ "player_volume_off", mediaviewPipControlsFgOver }}; +pipVolumeIcon1: icon {{ "player_volume_small", mediaviewPipControlsFg }}; +pipVolumeIcon1Over: icon {{ "player_volume_small", mediaviewPipControlsFgOver }}; +pipVolumeIcon2: icon {{ "player_volume_on", mediaviewPipControlsFg }}; +pipVolumeIcon2Over: icon {{ "player_volume_on", mediaviewPipControlsFgOver }}; speedSliderDividerSize: size(2px, 8px); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp index 17286c0b2b..cec4b6ec62 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.cpp @@ -318,6 +318,7 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent( _rgbaSize = image.size(); } } + program->setUniformValue("s_texture", GLint(0)); paintTransformedContent(&*program, geometry); } @@ -325,12 +326,6 @@ void OverlayWidget::RendererGL::paintTransformedStaticContent( void OverlayWidget::RendererGL::paintTransformedContent( not_null program, ContentGeometry geometry) { - auto texCoords = std::array, 4> { { - { { 0.f, 1.f } }, - { { 1.f, 1.f } }, - { { 1.f, 0.f } }, - { { 0.f, 0.f } }, - } }; const auto rect = transformRect(geometry.rect); const auto centerx = rect.x() + rect.width() / 2; const auto centery = rect.y() + rect.height() / 2; @@ -350,22 +345,21 @@ void OverlayWidget::RendererGL::paintTransformedContent( const auto bottomleft = rotated(rect.left(), rect.bottom()); const GLfloat coords[] = { topleft[0], topleft[1], - texCoords[0][0], texCoords[0][1], + 0.f, 1.f, topright[0], topright[1], - texCoords[1][0], texCoords[1][1], + 1.f, 1.f, bottomright[0], bottomright[1], - texCoords[2][0], texCoords[2][1], + 1.f, 0.f, bottomleft[0], bottomleft[1], - texCoords[3][0], texCoords[3][1], + 0.f, 0.f, }; _contentBuffer->write(0, coords, sizeof(coords)); program->setUniformValue("viewport", _uniformViewport); - program->setUniformValue("s_texture", GLint(0)); toggleBlending(false); FillTexturedRectangle(*_f, &*program); diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h index e1d61cb184..c1fbf0e596 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_opengl.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "media/view/media_view_overlay_renderer.h" -#include "ui/gl/gl_surface.h" #include "ui/gl/gl_image.h" #include "ui/gl/gl_primitives.h" @@ -74,7 +73,7 @@ private: void paintCaption(QRect outer, float64 opacity) override; void paintGroupThumbs(QRect outer, float64 opacity) override; - void invalidate() override; + void invalidate(); void paintUsingRaster( Ui::GL::Image &image, diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp index 2e0e7e6246..57d49d68ff 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.cpp @@ -188,7 +188,4 @@ void OverlayWidget::RendererSW::paintGroupThumbs( } } -void OverlayWidget::RendererSW::invalidate() { -} - } // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_raster.h b/Telegram/SourceFiles/media/view/media_view_overlay_raster.h index 7b0462bb82..72c6e6a006 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_raster.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_raster.h @@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "media/view/media_view_overlay_renderer.h" -#include "ui/gl/gl_surface.h" namespace Media::View { @@ -51,8 +50,6 @@ private: void paintCaption(QRect outer, float64 opacity) override; void paintGroupThumbs(QRect outer, float64 opacity) override; - void invalidate() override; - [[nodiscard]] static QRect TransformRect(QRectF geometry, int rotation); const not_null _owner; diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h b/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h index d57ef8f6f3..6f302915fa 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h +++ b/Telegram/SourceFiles/media/view/media_view_overlay_renderer.h @@ -38,8 +38,6 @@ public: virtual void paintCaption(QRect outer, float64 opacity) = 0; virtual void paintGroupThumbs(QRect outer, float64 opacity) = 0; - virtual void invalidate() = 0; - }; } // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp index e7d59d4df3..2fbf4de330 100644 --- a/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp +++ b/Telegram/SourceFiles/media/view/media_view_overlay_widget.cpp @@ -3232,10 +3232,9 @@ Ui::GL::ChosenRenderer OverlayWidget::chooseRenderer( : capabilities.transparency; LOG(("OpenGL: %1 (OverlayWidget)").arg(Logs::b(use))); if (use) { - auto renderer = std::make_unique(this); _opengl = true; return { - .renderer = std::move(renderer), + .renderer = std::make_unique(this), .backend = Ui::GL::Backend::OpenGL, }; } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.cpp b/Telegram/SourceFiles/media/view/media_view_pip.cpp index 3759be411b..9059c49a90 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.cpp +++ b/Telegram/SourceFiles/media/view/media_view_pip.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/streaming/media_streaming_document.h" #include "media/streaming/media_streaming_utility.h" #include "media/view/media_view_playback_progress.h" +#include "media/view/media_view_pip_opengl.h" +#include "media/view/media_view_pip_raster.h" #include "media/audio/media_audio.h" #include "data/data_document.h" #include "data/data_document_media.h" @@ -260,54 +262,6 @@ constexpr auto kMsInSecond = 1000; return result; } -Streaming::FrameRequest UnrotateRequest( - const Streaming::FrameRequest &request, - int rotation) { - if (!rotation) { - return request; - } - const auto unrotatedCorner = [&](RectPart corner) { - if (!(request.corners & corner)) { - return RectPart(0); - } - switch (corner) { - case RectPart::TopLeft: - return (rotation == 90) - ? RectPart::BottomLeft - : (rotation == 180) - ? RectPart::BottomRight - : RectPart::TopRight; - case RectPart::TopRight: - return (rotation == 90) - ? RectPart::TopLeft - : (rotation == 180) - ? RectPart::BottomLeft - : RectPart::BottomRight; - case RectPart::BottomRight: - return (rotation == 90) - ? RectPart::TopRight - : (rotation == 180) - ? RectPart::TopLeft - : RectPart::BottomLeft; - case RectPart::BottomLeft: - return (rotation == 90) - ? RectPart::BottomRight - : (rotation == 180) - ? RectPart::TopRight - : RectPart::TopLeft; - } - Unexpected("Corner in rotateCorner."); - }; - auto result = request; - result.outer = FlipSizeByRotation(request.outer, rotation); - result.resize = FlipSizeByRotation(request.resize, rotation); - result.corners = unrotatedCorner(RectPart::TopLeft) - | unrotatedCorner(RectPart::TopRight) - | unrotatedCorner(RectPart::BottomRight) - | unrotatedCorner(RectPart::BottomLeft); - return result; -} - Qt::Edges RectPartToQtEdges(RectPart rectPart) { switch (rectPart) { case RectPart::TopLeft: @@ -373,45 +327,9 @@ QImage RotateFrameImage(QImage image, int rotation) { PipPanel::PipPanel( QWidget *parent, - Fn paint) -: _content(Ui::GL::CreateSurface( - [=](Ui::GL::Capabilities capabilities) { - return chooseRenderer(capabilities); - })) -, _parent(parent) -, _paint(std::move(paint)) { -} - -Ui::GL::ChosenRenderer PipPanel::chooseRenderer( - Ui::GL::Capabilities capabilities) { - class Renderer : public Ui::GL::Renderer { - public: - Renderer(not_null owner) : _owner(owner) { - } - - void paintFallback( - Painter &&p, - const QRegion &clip, - Ui::GL::Backend backend) override { - _owner->paint( - p, - clip, - backend == Ui::GL::Backend::OpenGL); - } - - private: - const not_null _owner; - - }; - - const auto use = Platform::IsMac() - ? true - : capabilities.transparency; - LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use))); - return { - .renderer = std::make_unique(this), - .backend = (use ? Ui::GL::Backend::OpenGL : Ui::GL::Backend::Raster), - }; + Fn renderer) +: _content(Ui::GL::CreateSurface(std::move(renderer))) +, _parent(parent) { } void PipPanel::init() { @@ -480,6 +398,10 @@ RectParts PipPanel::attached() const { return _attached; } +bool PipPanel::useTransparency() const { + return _useTransparency; +} + void PipPanel::setDragDisabled(bool disabled) { _dragDisabled = disabled; if (_dragState) { @@ -646,39 +568,6 @@ void PipPanel::setGeometry(QRect geometry) { widget()->setGeometry(geometry); } -void PipPanel::paint(QPainter &p, const QRegion &clip, bool opengl) { - if (_useTransparency && opengl) { - p.setCompositionMode(QPainter::CompositionMode_Source); - for (const auto rect : clip) { - p.fillRect(rect, Qt::transparent); - } - p.setCompositionMode(QPainter::CompositionMode_SourceOver); - } - - auto request = FrameRequest(); - const auto inner = this->inner(); - request.resize = request.outer = inner.size() * style::DevicePixelRatio(); - request.corners = RectPart(0) - | ((_attached & (RectPart::Left | RectPart::Top)) - ? RectPart(0) - : RectPart::TopLeft) - | ((_attached & (RectPart::Top | RectPart::Right)) - ? RectPart(0) - : RectPart::TopRight) - | ((_attached & (RectPart::Right | RectPart::Bottom)) - ? RectPart(0) - : RectPart::BottomRight) - | ((_attached & (RectPart::Bottom | RectPart::Left)) - ? RectPart(0) - : RectPart::BottomLeft); - request.radius = ImageRoundRadius::Large; - if (_useTransparency) { - const auto sides = RectPart::AllSides & ~_attached; - Ui::Shadow::paint(p, inner, widget()->width(), st::callShadow); - } - _paint(p, request, opengl); -} - void PipPanel::handleMousePress(QPoint position, Qt::MouseButton button) { if (button != Qt::LeftButton) { return; @@ -942,15 +831,14 @@ Pip::Pip( , _instance(std::move(shared), [=] { waitingAnimationCallback(); }) , _panel( _delegate->pipParentWidget(), - [=](QPainter &p, const FrameRequest &request, bool opengl) { - paint(p, request, opengl); + [=](Ui::GL::Capabilities capabilities) { + return chooseRenderer(capabilities); }) , _playbackProgress(std::make_unique()) , _rotation(data->owner().mediaRotation().get(data)) , _lastPositiveVolume((Core::App().settings().videoVolume() > 0.) ? Core::App().settings().videoVolume() : Core::Settings::kDefaultVolume) -, _roundRect(ImageRoundRadius::Large, st::radialBg) , _closeAndContinue(std::move(closeAndContinue)) , _destroy(std::move(destroy)) { setupPanel(); @@ -1041,19 +929,19 @@ void Pip::setOverState(OverState state) { if (_over == state) { return; } - const auto was = _over; + const auto wasShown = ResolveShownOver(_over); _over = state; - const auto nowShown = (_over != OverState::None); - if ((was != OverState::None) != nowShown) { + const auto nowAreShown = (ResolveShownOver(_over) != OverState::None); + if ((wasShown != OverState::None) != nowAreShown) { _controlsShown.start( [=] { _panel.update(); }, - nowShown ? 0. : 1., - nowShown ? 1. : 0., + nowAreShown ? 0. : 1., + nowAreShown ? 1. : 0., st::fadeWrapDuration, anim::linear); } if (!_pressed) { - updateActiveState(was); + updateActiveState(wasShown); } _panel.update(); } @@ -1062,27 +950,29 @@ void Pip::setPressedState(std::optional state) { if (_pressed == state) { return; } - const auto was = activeState(); + const auto wasShown = shownActiveState(); _pressed = state; - updateActiveState(was); + updateActiveState(wasShown); } -Pip::OverState Pip::activeState() const { - return _pressed.value_or(_over); +Pip::OverState Pip::shownActiveState() const { + return ResolveShownOver(_pressed.value_or(_over)); } float64 Pip::activeValue(const Button &button) const { - return button.active.value((activeState() == button.state) ? 1. : 0.); + const auto shownState = ResolveShownOver(button.state); + return button.active.value((shownActiveState() == shownState) ? 1. : 0.); } -void Pip::updateActiveState(OverState was) { +void Pip::updateActiveState(OverState wasShown) { const auto check = [&](Button &button) { - const auto now = (activeState() == button.state); - if ((was == button.state) != now) { + const auto shownState = ResolveShownOver(button.state); + const auto nowIsShown = (shownActiveState() == shownState); + if ((wasShown == shownState) != nowIsShown) { button.active.start( [=, &button] { _panel.widget()->update(button.icon); }, - now ? 0. : 1., - now ? 1. : 0., + nowIsShown ? 0. : 1., + nowIsShown ? 1. : 0., st::fadeWrapDuration, anim::linear); } @@ -1095,6 +985,12 @@ void Pip::updateActiveState(OverState was) { check(_volumeController); } +Pip::OverState Pip::ResolveShownOver(OverState state) { + return (state == OverState::VolumeController) + ? OverState::VolumeToggle + : state; +} + void Pip::handleMousePress(QPoint position, Qt::MouseButton button) { const auto weak = Ui::MakeWeak(_panel.widget()); const auto guard = gsl::finally([&] { @@ -1256,9 +1152,9 @@ void Pip::setupButtons() { const auto volumeSkip = st::pipPlaybackSkip; const auto volumeHeight = 2 * volumeSkip + st::pipPlaybackWide; - const auto volumeToggleWidth = st::mediaviewVolumeIcon0.width() + const auto volumeToggleWidth = st::pipVolumeIcon0.width() + 2 * skip; - const auto volumeToggleHeight = st::mediaviewVolumeIcon0.height() + const auto volumeToggleHeight = st::pipVolumeIcon0.height() + 2 * skip; const auto volumeWidth = (((st::mediaviewVolumeWidth + 2 * skip) + _close.area.width() @@ -1273,7 +1169,7 @@ void Pip::setupButtons() { volumeHeight); _volumeToggle.area = QRect( _volumeController.area.x() - - st::mediaviewVolumeIcon0.width() + - st::pipVolumeIcon0.width() - skip, rect.y(), volumeToggleWidth, @@ -1346,83 +1242,70 @@ void Pip::setupStreaming() { updatePlaybackState(); } -void Pip::paint(QPainter &p, FrameRequest request, bool opengl) { - const auto image = videoFrameForDirectPaint( - UnrotateRequest(request, _rotation)); - const auto inner = _panel.inner(); - const auto rect = QRect{ - inner.topLeft(), - request.outer / style::DevicePixelRatio() +Ui::GL::ChosenRenderer Pip::chooseRenderer( + Ui::GL::Capabilities capabilities) { + const auto use = Platform::IsMac() + ? true + : capabilities.transparency; + LOG(("OpenGL: %1 (PipPanel)").arg(Logs::b(use))); + if (use) { + _opengl = true; + return { + .renderer = std::make_unique(this), + .backend = Ui::GL::Backend::OpenGL, + }; + } + return { + .renderer = std::make_unique(this), + .backend = Ui::GL::Backend::Raster, }; - if (UsePainterRotation(_rotation, opengl)) { - if (_rotation) { - p.save(); - p.rotate(_rotation); - } - auto hq = PainterHighQualityEnabler(p); - p.drawImage(RotatedRect(rect, _rotation), image); - if (_rotation) { - p.restore(); - } - } else { - p.drawImage(rect, RotateFrameImage(image, _rotation)); - } - if (canUseVideoFrame()) { - _instance.markFrameShown(); - } - paintRadialLoading(p); - paintControls(p); } -void Pip::paintControls(QPainter &p) const { - const auto shown = _controlsShown.value( +void Pip::paint(not_null renderer) const { + const auto controlsShown = _controlsShown.value( (_over != OverState::None) ? 1. : 0.); - if (!shown) { - return; + const auto geometry = ContentGeometry{ + .inner = _panel.inner(), + .attached = (_panel.useTransparency() + ? _panel.attached() + : RectPart::AllSides), + .fade = controlsShown, + .outer = _panel.widget()->size(), + .rotation = _rotation, + .useTransparency = _panel.useTransparency(), + }; + if (canUseVideoFrame()) { + renderer->paintTransformedVideoFrame(geometry); + _instance.markFrameShown(); + } else { + renderer->paintTransformedStaticContent(staticContent(), geometry); + } + if (_instance.waitingShown()) { + renderer->paintRadialLoading(countRadialRect(), controlsShown); + } + if (controlsShown > 0) { + paintButtons(renderer, controlsShown); + paintPlayback(renderer, controlsShown); + paintVolumeController(renderer, controlsShown); } - p.setOpacity(shown); - paintFade(p); - paintButtons(p); - paintPlayback(p); - paintPlaybackTexts(p); - paintVolumeController(p); } -void Pip::paintFade(QPainter &p) const { - using Part = RectPart; - const auto sides = _panel.attached(); - const auto rounded = RectPart(0) - | ((sides & (Part::Top | Part::Left)) ? Part(0) : Part::TopLeft) - | ((sides & (Part::Top | Part::Right)) ? Part(0) : Part::TopRight) - | ((sides & (Part::Bottom | Part::Right)) - ? Part(0) - : Part::BottomRight) - | ((sides & (Part::Bottom | Part::Left)) - ? Part(0) - : Part::BottomLeft); - _roundRect.paintSomeRounded( - p, - _panel.inner(), - rounded | Part::NoTopBottom | Part::Top | Part::Bottom); -} - -void Pip::paintButtons(QPainter &p) const { - const auto opacity = p.opacity(); +void Pip::paintButtons(not_null renderer, float64 shown) const { const auto outer = _panel.widget()->width(); const auto drawOne = [&]( const Button &button, const style::icon &icon, const style::icon &iconOver) { - const auto over = activeValue(button); - if (over < 1.) { - icon.paint(p, button.icon.x(), button.icon.y(), outer); - } - if (over > 0.) { - p.setOpacity(over * opacity); - iconOver.paint(p, button.icon.x(), button.icon.y(), outer); - p.setOpacity(opacity); - } + renderer->paintButton( + button, + outer, + shown, + activeValue(button), + icon, + iconOver); }; + + renderer->paintButtonsStart(); drawOne( _play, _showPause ? st::pipPauseIcon : st::pipPlayIcon, @@ -1433,47 +1316,73 @@ void Pip::paintButtons(QPainter &p) const { if (volume <= 0.) { drawOne( _volumeToggle, - st::mediaviewVolumeIcon0, - st::mediaviewVolumeIcon0Over); + st::pipVolumeIcon0, + st::pipVolumeIcon0Over); } else if (volume < 1 / 2.) { drawOne( _volumeToggle, - st::mediaviewVolumeIcon1, - st::mediaviewVolumeIcon1Over); + st::pipVolumeIcon1, + st::pipVolumeIcon1Over); } else { drawOne( _volumeToggle, - st::mediaviewVolumeIcon2, - st::mediaviewVolumeIcon2Over); + st::pipVolumeIcon2, + st::pipVolumeIcon2Over); } } -void Pip::paintPlayback(QPainter &p) const { +void Pip::paintPlayback(not_null renderer, float64 shown) const { + const auto outer = QRect( + _playback.icon.x(), + _playback.icon.y() - st::pipPlaybackFont->height, + _playback.icon.width(), + st::pipPlaybackFont->height + _playback.icon.height()); + renderer->paintPlayback(outer, shown); +} + +void Pip::paintPlaybackContent( + QPainter &p, + QRect outer, + float64 shown) const { + p.setOpacity(shown); + paintPlaybackProgress(p, outer); + paintPlaybackTexts(p, outer); +} + +void Pip::paintPlaybackProgress(QPainter &p, QRect outer) const { const auto radius = _playback.icon.height() / 2; const auto progress = _playbackProgress->value(); + const auto active = activeValue(_playback); const auto height = anim::interpolate( st::pipPlaybackWidth, _playback.icon.height(), - activeValue(_playback)); + active); const auto rect = QRect( - _playback.icon.x(), - _playback.icon.y() + _playback.icon.height() - height, - _playback.icon.width(), + outer.x(), + (outer.y() + + st::pipPlaybackFont->height + + _playback.icon.height() + - height), + outer.width(), height); - paintProgressBar(p, rect, progress, radius); + paintProgressBar(p, rect, progress, radius, active); } void Pip::paintProgressBar( QPainter &p, const QRect &rect, float64 progress, - int radius) const { + int radius, + float64 active) const { const auto done = int(std::round(rect.width() * progress)); PainterHighQualityEnabler hq(p); p.setPen(Qt::NoPen); if (done > 0) { - p.setBrush(st::mediaviewPipPlaybackActive); + p.setBrush(anim::brush( + st::mediaviewPipControlsFg, + st::mediaviewPipPlaybackActive, + active)); p.setClipRect(rect.x(), rect.y(), done, rect.height()); p.drawRoundedRect( rect.x(), @@ -1502,14 +1411,17 @@ void Pip::paintProgressBar( p.setClipping(false); } -void Pip::paintPlaybackTexts(QPainter &p) const { - const auto left = _playback.area.x() + st::pipPlaybackTextSkip; - const auto right = _playback.area.x() +void Pip::paintPlaybackTexts(QPainter &p, QRect outer) const { + const auto left = outer.x() + - _playback.icon.x() + + _playback.area.x() + + st::pipPlaybackTextSkip; + const auto right = outer.x() + - _playback.icon.x() + + _playback.area.x() + _playback.area.width() - st::pipPlaybackTextSkip; - const auto top = _playback.icon.y() - - st::pipPlaybackFont->height - + st::pipPlaybackFont->ascent; + const auto top = outer.y() + st::pipPlaybackFont->ascent; p.setFont(st::pipPlaybackFont); p.setPen(st::mediaviewPipControlsFgOver); @@ -1517,23 +1429,35 @@ void Pip::paintPlaybackTexts(QPainter &p) const { p.drawText(right - _timeLeftWidth, top, _timeLeft); } -void Pip::paintVolumeController(QPainter &p) const { +void Pip::paintVolumeController( + not_null renderer, + float64 shown) const { if (!_volumeController.icon.width()) { return; } + renderer->paintVolumeController(_volumeController.icon, shown); +} + +void Pip::paintVolumeControllerContent( + QPainter &p, + QRect outer, + float64 shown) const { + p.setOpacity(shown); + const auto radius = _volumeController.icon.height() / 2; const auto volume = Core::App().settings().videoVolume(); + const auto active = activeValue(_volumeController); const auto height = anim::interpolate( st::pipPlaybackWidth, _volumeController.icon.height(), - activeValue(_volumeController)); + active); const auto rect = QRect( - _volumeController.icon.x(), - _volumeController.icon.y() + radius - height / 2, - _volumeController.icon.width(), + outer.x(), + outer.y() + radius - height / 2, + outer.width(), height); - paintProgressBar(p, rect, volume, radius); + paintProgressBar(p, rect, volume, radius, active); } void Pip::handleStreamingUpdate(Streaming::Update &&update) { @@ -1649,12 +1573,19 @@ bool Pip::canUseVideoFrame() const { } QImage Pip::videoFrame(const FrameRequest &request) const { - if (canUseVideoFrame()) { - _preparedCoverStorage = QImage(); - return _instance.frame(request); - } - const auto &cover = _instance.info().video.cover; + Expects(canUseVideoFrame()); + return _instance.frame(request); +} + +Streaming::FrameWithInfo Pip::videoFrameWithInfo() const { + Expects(canUseVideoFrame()); + + return _instance.frameWithInfo(); +} + +QImage Pip::staticContent() const { + const auto &cover = _instance.info().video.cover; const auto media = _data->activeMediaView(); const auto use = media ? media @@ -1677,114 +1608,32 @@ QImage Pip::videoFrame(const FrameRequest &request) const { : blurred ? ThumbState::Inline : ThumbState::Empty; - if (_preparedCoverStorage.isNull() - || _preparedCoverRequest != request - || _preparedCoverState < state) { - _preparedCoverRequest = request; - _preparedCoverState = state; - if (state == ThumbState::Cover) { - _preparedCoverStorage = Streaming::PrepareByRequest( - _instance.info().video.cover, - false, - _instance.info().video.rotation, - request, + if (!_preparedCoverStorage.isNull() && _preparedCoverState >= state) { + return _preparedCoverStorage; + } + _preparedCoverState = state; + if (state == ThumbState::Cover) { + _preparedCoverStorage = _instance.info().video.cover; + } else { + _preparedCoverStorage = (good + ? good + : thumb + ? thumb + : blurred + ? blurred + : Image::BlankMedia().get())->original(); + if (!good) { + _preparedCoverStorage = Images::prepareBlur( std::move(_preparedCoverStorage)); - } else if (!request.resize.isEmpty()) { - using Option = Images::Option; - const auto options = Option::Smooth - | (good ? Option(0) : Option::Blurred) - | Option::RoundedLarge - | ((request.corners & RectPart::TopLeft) - ? Option::RoundedTopLeft - : Option(0)) - | ((request.corners & RectPart::TopRight) - ? Option::RoundedTopRight - : Option(0)) - | ((request.corners & RectPart::BottomRight) - ? Option::RoundedBottomRight - : Option(0)) - | ((request.corners & RectPart::BottomLeft) - ? Option::RoundedBottomLeft - : Option(0)); - _preparedCoverStorage = (good - ? good - : thumb - ? thumb - : blurred - ? blurred - : Image::BlankMedia().get())->pixNoCache( - request.resize.width(), - request.resize.height(), - options, - request.outer.width(), - request.outer.height()).toImage(); } } return _preparedCoverStorage; } -QImage Pip::videoFrameForDirectPaint(const FrameRequest &request) const { - const auto result = videoFrame(request); - -#ifdef USE_OPENGL_PIP_WIDGET - const auto bytesPerLine = result.bytesPerLine(); - if (bytesPerLine == result.width() * 4) { - return result; - } - - // On macOS 10.8+ we use QOpenGLWidget as OverlayWidget base class. - // The OpenGL painter can't paint textures where byte data is with strides. - // So in that case we prepare a compact copy of the frame to render. - // - // See Qt commit ed557c037847e343caa010562952b398f806adcd - // - auto &cache = _frameForDirectPaint; - if (cache.size() != result.size()) { - cache = QImage(result.size(), result.format()); - } - const auto height = result.height(); - const auto line = cache.bytesPerLine(); - Assert(line == result.width() * 4); - Assert(line < bytesPerLine); - - auto from = result.bits(); - auto to = cache.bits(); - for (auto y = 0; y != height; ++y) { - memcpy(to, from, line); - to += line; - from += bytesPerLine; - } - return cache; -#endif // USE_OPENGL_PIP_WIDGET - - return result; -} - -void Pip::paintRadialLoading(QPainter &p) const { - const auto inner = countRadialRect(); -#ifdef USE_OPENGL_PIP_WIDGET - { - if (_radialCache.size() != inner.size() * cIntRetinaFactor()) { - _radialCache = QImage( - inner.size() * cIntRetinaFactor(), - QImage::Format_ARGB32_Premultiplied); - _radialCache.setDevicePixelRatio(cRetinaFactor()); - } - _radialCache.fill(Qt::transparent); - - Painter q(&_radialCache); - paintRadialLoadingContent(q, inner.translated(-inner.topLeft())); - } - p.drawImage(inner.topLeft(), _radialCache); -#else // USE_OPENGL_PIP_WIDGET - paintRadialLoadingContent(p, inner); -#endif // USE_OPENGL_PIP_WIDGET -} - -void Pip::paintRadialLoadingContent(QPainter &p, const QRect &inner) const { - if (!_instance.waitingShown()) { - return; - } +void Pip::paintRadialLoadingContent( + QPainter &p, + const QRect &inner, + QColor fg) const { const auto arc = inner.marginsRemoved(QMargins( st::radialLine, st::radialLine, @@ -1804,7 +1653,7 @@ void Pip::paintRadialLoadingContent(QPainter &p, const QRect &inner) const { arc.topLeft(), arc.size(), _panel.widget()->width(), - st::radialFg, + fg, st::radialLine); } diff --git a/Telegram/SourceFiles/media/view/media_view_pip.h b/Telegram/SourceFiles/media/view/media_view_pip.h index 2fbf6de679..be35e1a392 100644 --- a/Telegram/SourceFiles/media/view/media_view_pip.h +++ b/Telegram/SourceFiles/media/view/media_view_pip.h @@ -50,11 +50,10 @@ public: QRect geometry; QRect screen; }; - using FrameRequest = Streaming::FrameRequest; PipPanel( QWidget *parent, - Fn paint); + Fn renderer); void init(); [[nodiscard]] not_null widget() const; @@ -68,6 +67,8 @@ public: void setPosition(Position position); [[nodiscard]] QRect inner() const; [[nodiscard]] RectParts attached() const; + [[nodiscard]] bool useTransparency() const; + void setDragDisabled(bool disabled); [[nodiscard]] bool dragging() const; @@ -78,8 +79,6 @@ public: [[nodiscard]] rpl::producer<> saveGeometryRequests() const; private: - void paint(QPainter &p, const QRegion &clip, bool opengl); - void setPositionDefault(); void setPositionOnScreen(Position position, QRect available); @@ -92,12 +91,8 @@ private: void moveAnimated(QPoint to); void updateDecorations(); - [[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer( - Ui::GL::Capabilities capabilities); - const std::unique_ptr _content; const QPointer _parent; - Fn _paint; RectParts _attached = RectParts(); RectParts _snapped = RectParts(); QSize _ratio; @@ -161,12 +156,26 @@ private: OverState state = OverState::None; Ui::Animations::Simple active; }; + struct ContentGeometry { + QRect inner; + RectParts attached = RectParts(); + float64 fade = 0.; + QSize outer; + int rotation = 0; + bool useTransparency = false; + }; + struct StaticContent { + QImage image; + bool blurred = false; + }; using FrameRequest = Streaming::FrameRequest; + class Renderer; + class RendererGL; + class RendererSW; void setupPanel(); void setupButtons(); void setupStreaming(); - void paint(QPainter &p, FrameRequest request, bool opengl); void playbackPauseResume(); void volumeChanged(float64 volume); void volumeToggled(); @@ -182,16 +191,22 @@ private: [[nodiscard]] bool canUseVideoFrame() const; [[nodiscard]] QImage videoFrame(const FrameRequest &request) const; - [[nodiscard]] QImage videoFrameForDirectPaint( - const FrameRequest &request) const; + [[nodiscard]] Streaming::FrameWithInfo videoFrameWithInfo() const; // YUV + [[nodiscard]] QImage staticContent() const; [[nodiscard]] OverState computeState(QPoint position) const; void setOverState(OverState state); void setPressedState(std::optional state); - [[nodiscard]] OverState activeState() const; + [[nodiscard]] OverState shownActiveState() const; [[nodiscard]] float64 activeValue(const Button &button) const; void updateActiveState(OverState was); void updatePlaybackTexts(int64 position, int64 length, int64 frequency); + [[nodiscard]] static OverState ResolveShownOver(OverState state); + + [[nodiscard]] Ui::GL::ChosenRenderer chooseRenderer( + Ui::GL::Capabilities capabilities); + void paint(not_null renderer) const; + void handleMouseMove(QPoint position); void handleMousePress(QPoint position, Qt::MouseButton button); void handleMouseRelease(QPoint position, Qt::MouseButton button); @@ -199,19 +214,28 @@ private: void handleLeave(); void handleClose(); - void paintControls(QPainter &p) const; - void paintFade(QPainter &p) const; - void paintButtons(QPainter &p) const; - void paintPlayback(QPainter &p) const; + void paintRadialLoadingContent( + QPainter &p, + const QRect &inner, + QColor fg) const; + void paintButtons(not_null renderer, float64 shown) const; + void paintPlayback(not_null renderer, float64 shown) const; + void paintPlaybackContent(QPainter &p, QRect outer, float64 shown) const; + void paintPlaybackProgress(QPainter &p, QRect outer) const; void paintProgressBar( QPainter &p, const QRect &rect, float64 progress, - int radius) const; - void paintPlaybackTexts(QPainter &p) const; - void paintVolumeController(QPainter &p) const; - void paintRadialLoading(QPainter &p) const; - void paintRadialLoadingContent(QPainter &p, const QRect &inner) const; + int radius, + float64 active) const; + void paintPlaybackTexts(QPainter &p, QRect outer) const; + void paintVolumeController( + not_null renderer, + float64 shown) const; + void paintVolumeControllerContent( + QPainter &p, + QRect outer, + float64 shown) const; [[nodiscard]] QRect countRadialRect() const; void seekUpdate(QPoint position); @@ -222,6 +246,7 @@ private: not_null _data; FullMsgId _contextId; Streaming::Instance _instance; + bool _opengl = false; PipPanel _panel; QSize _size; std::unique_ptr _playbackProgress; @@ -246,19 +271,12 @@ private: Button _volumeToggle; Button _volumeController; Ui::Animations::Simple _controlsShown; - Ui::RoundRect _roundRect; FnMut _closeAndContinue; FnMut _destroy; -#if USE_OPENGL_PIP_WIDGET - mutable QImage _frameForDirectPaint; - mutable QImage _radialCache; -#endif // USE_OPENGL_PIP_WIDGET - mutable QImage _preparedCoverStorage; - mutable FrameRequest _preparedCoverRequest; - mutable ThumbState _preparedCoverState; + mutable ThumbState _preparedCoverState = ThumbState::Empty; }; diff --git a/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp b/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp new file mode 100644 index 0000000000..2a1b36b540 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_pip_opengl.cpp @@ -0,0 +1,781 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "media/view/media_view_pip_opengl.h" + +#include "ui/gl/gl_shader.h" +#include "ui/gl/gl_primitives.h" +#include "ui/widgets/shadow.h" +#include "media/streaming/media_streaming_common.h" +#include "base/platform/base_platform_info.h" +#include "styles/style_media_view.h" +#include "styles/style_calls.h" // st::callShadow. + +namespace Media::View { +namespace { + +using namespace Ui::GL; + +constexpr auto kRadialLoadingOffset = 4; +constexpr auto kPlaybackOffset = kRadialLoadingOffset + 4; +constexpr auto kVolumeControllerOffset = kPlaybackOffset + 4; +constexpr auto kControlsOffset = kVolumeControllerOffset + 4; +constexpr auto kControlValues = 4 * 4 + 2 * 4; + +[[nodiscard]] ShaderPart FragmentAddControlOver() { + return { + .header = R"( +varying vec2 o_texcoord; +uniform float o_opacity; +)", + .body = R"( + vec4 over = texture2D(s_texture, o_texcoord); + result = result * (1. - o_opacity) + + vec4(over.b, over.g, over.r, over.a) * o_opacity; +)", + }; +} + +[[nodiscard]] ShaderPart FragmentApplyFade() { + return { + .header = R"( +uniform vec4 fadeColor; // Premultiplied. +)", + .body = R"( + result = result * (1. - fadeColor.a) + fadeColor; +)", + }; +} + +ShaderPart FragmentSampleShadow() { + return { + .header = R"( +uniform sampler2D h_texture; +uniform vec2 h_size; +uniform vec4 h_extend; +uniform vec4 h_components; +)", + .body = R"( + vec4 extended = vec4( // Left-Bottom-Width-Height rectangle. + roundRect.xy - h_extend.xw, + roundRect.zw + h_extend.xw + h_extend.zy); + vec2 inside = (gl_FragCoord.xy - extended.xy); + vec2 insideOtherCorner = (inside + h_size - extended.zw); + vec4 outsideCorners = step( + vec4(h_components.xy, inside), + vec4(inside, extended.zw - h_components.xy)); + vec4 insideCorners = vec4(1.) - outsideCorners; + vec2 linear = outsideCorners.xy * outsideCorners.zw; + vec2 h_size_half = 0.5 * h_size; + + vec2 bottomleft = inside * insideCorners.x * insideCorners.y; + vec2 bottomright = vec2(insideOtherCorner.x, inside.y) + * insideCorners.z + * insideCorners.y; + vec2 topright = insideOtherCorner * insideCorners.z * insideCorners.w; + vec2 topleft = vec2(inside.x, insideOtherCorner.y) + * insideCorners.x + * insideCorners.w; + + vec2 left = vec2(inside.x, h_size_half.y) + * step(inside.x, h_components.z) + * linear.y; + vec2 bottom = vec2(h_size_half.x, inside.y) + * step(inside.y, h_components.w) + * linear.x; + vec2 right = vec2(insideOtherCorner.x, h_size_half.y) + * step(h_size.x - h_components.z, insideOtherCorner.x) + * linear.y; + vec2 top = vec2(h_size_half.x, insideOtherCorner.y) + * step(h_size.y - h_components.w, insideOtherCorner.y) + * linear.x; + + vec2 uv = bottomleft + + bottomright + + topleft + + topright + + left + + bottom + + right + + top; + result = texture2D(h_texture, uv / h_size); +)", + }; +} + +ShaderPart FragmentRoundToShadow() { + const auto shadow = FragmentSampleShadow(); + return { + .header = R"( +uniform vec4 roundRect; +uniform float roundRadius; +)" + shadow.header + R"( + +float roundedCorner() { + vec2 rectHalf = roundRect.zw / 2; + vec2 rectCenter = roundRect.xy + rectHalf; + vec2 fromRectCenter = abs(gl_FragCoord.xy - rectCenter); + vec2 vectorRadius = vec2(roundRadius + 0.5, roundRadius + 0.5); + vec2 fromCenterWithRadius = fromRectCenter + vectorRadius; + vec2 fromRoundingCenter = max(fromCenterWithRadius, rectHalf) + - rectHalf; + float rounded = length(fromRoundingCenter) - roundRadius; + + return 1. - smoothstep(0., 1., rounded); +} + +vec4 shadow() { + vec4 result; + +)" + shadow.body + R"( + + return result; +} +)", + .body = R"( + float round = roundedCorner(); + result = result * round + shadow() * (1. - round); +)", + }; +} + +} // namespace + +Pip::RendererGL::RendererGL(not_null owner) +: _owner(owner) { + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _radialImage.invalidate(); + _playbackImage.invalidate(); + _volumeControllerImage.invalidate(); + invalidateControls(); + }, _lifetime); +} + +void Pip::RendererGL::init( + not_null widget, + QOpenGLFunctions &f) { + constexpr auto kQuads = 8; + constexpr auto kQuadVertices = kQuads * 4; + constexpr auto kQuadValues = kQuadVertices * 4; + constexpr auto kControlsValues = kControlsCount * kControlValues; + constexpr auto kValues = kQuadValues + kControlsValues; + + _contentBuffer.emplace(); + _contentBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw); + _contentBuffer->create(); + _contentBuffer->bind(); + _contentBuffer->allocate(kValues * sizeof(GLfloat)); + + _textures.ensureCreated(f); + + _argb32Program.emplace(); + _texturedVertexShader = LinkProgram( + &*_argb32Program, + VertexShader({ + VertexPassTextureCoord(), + }), + FragmentShader({ + FragmentSampleARGB32Texture(), + FragmentApplyFade(), + FragmentRoundToShadow(), + })).vertex; + + _yuv420Program.emplace(); + LinkProgram( + &*_yuv420Program, + _texturedVertexShader, + FragmentShader({ + FragmentSampleYUV420Texture(), + FragmentApplyFade(), + FragmentRoundToShadow(), + })); + + _imageProgram.emplace(); + LinkProgram( + &*_imageProgram, + VertexShader({ + VertexViewportTransform(), + VertexPassTextureCoord(), + }), + FragmentShader({ + FragmentSampleARGB32Texture(), + })); + + _controlsProgram.emplace(); + LinkProgram( + &*_controlsProgram, + VertexShader({ + VertexViewportTransform(), + VertexPassTextureCoord(), + VertexPassTextureCoord('o'), + }), + FragmentShader({ + FragmentSampleARGB32Texture(), + FragmentAddControlOver(), + FragmentGlobalOpacity(), + })); + + createShadowTexture(); +} + +void Pip::RendererGL::deinit( + not_null widget, + QOpenGLFunctions &f) { + _textures.destroy(f); + _imageProgram = std::nullopt; + _texturedVertexShader = nullptr; + _argb32Program = std::nullopt; + _yuv420Program = std::nullopt; + _controlsProgram = std::nullopt; + _contentBuffer = std::nullopt; +} + +void Pip::RendererGL::resize( + not_null widget, + QOpenGLFunctions &f, + int w, + int h) { + const auto factor = widget->devicePixelRatio(); + if (_factor != factor) { + _factor = factor; + _controlsImage.invalidate(); + } + _viewport = QSize{ w, h }; + _uniformViewport = QVector2D( + _viewport.width() * _factor, + _viewport.height() * _factor); + setDefaultViewport(f); +} + +void Pip::RendererGL::setDefaultViewport(QOpenGLFunctions &f) { + f.glViewport(0, 0, _uniformViewport.x(), _uniformViewport.y()); +} + +void Pip::RendererGL::createShadowTexture() { + const auto &shadow = st::callShadow; + const auto size = 2 * st::callShadow.topLeft.size() + + QSize(st::roundRadiusLarge, st::roundRadiusLarge); + auto image = QImage( + size * cIntRetinaFactor(), + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(cRetinaFactor()); + image.fill(Qt::transparent); + { + auto p = QPainter(&image); + Ui::Shadow::paint( + p, + QRect(QPoint(), size).marginsRemoved(shadow.extend), + size.width(), + shadow); + } + image.save("C:\\Tmp\\shadow.png"); + _shadowImage.setImage(std::move(image)); +} + +void Pip::RendererGL::paint( + not_null widget, + QOpenGLFunctions &f) { + _f = &f; + _owner->paint(this); + _f = nullptr; +} + +void Pip::RendererGL::paintTransformedVideoFrame( + ContentGeometry geometry) { + const auto data = _owner->videoFrameWithInfo(); + if (data.format == Streaming::FrameFormat::None) { + return; + } + if (data.format == Streaming::FrameFormat::ARGB32) { + Assert(!data.original.isNull()); + paintTransformedStaticContent(data.original, geometry); + return; + } + Assert(data.format == Streaming::FrameFormat::YUV420); + Assert(!data.yuv420->size.isEmpty()); + const auto yuv = data.yuv420; + _yuv420Program->bind(); + + const auto upload = (_trackFrameIndex != data.index); + _trackFrameIndex = data.index; + + _f->glActiveTexture(GL_TEXTURE0); + _textures.bind(*_f, 1); + if (upload) { + _f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + uploadTexture( + GL_RED, + GL_RED, + yuv->size, + _lumaSize, + yuv->y.stride, + yuv->y.data); + _lumaSize = yuv->size; + } + _f->glActiveTexture(GL_TEXTURE1); + _textures.bind(*_f, 2); + if (upload) { + uploadTexture( + GL_RED, + GL_RED, + yuv->chromaSize, + _chromaSize, + yuv->u.stride, + yuv->u.data); + } + _f->glActiveTexture(GL_TEXTURE2); + _textures.bind(*_f, 3); + if (upload) { + uploadTexture( + GL_RED, + GL_RED, + yuv->chromaSize, + _chromaSize, + yuv->v.stride, + yuv->v.data); + _chromaSize = yuv->chromaSize; + _f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + } + _yuv420Program->setUniformValue("y_texture", GLint(0)); + _yuv420Program->setUniformValue("u_texture", GLint(1)); + _yuv420Program->setUniformValue("v_texture", GLint(2)); + + paintTransformedContent(&*_yuv420Program, geometry); +} + +void Pip::RendererGL::paintTransformedStaticContent( + const QImage &image, + ContentGeometry geometry) { + _argb32Program->bind(); + + _f->glActiveTexture(GL_TEXTURE0); + _textures.bind(*_f, 0); + const auto cacheKey = image.cacheKey(); + const auto upload = (_cacheKey != cacheKey); + if (upload) { + _cacheKey = cacheKey; + const auto stride = image.bytesPerLine() / 4; + const auto data = image.constBits(); + uploadTexture( + GL_RGBA, + GL_RGBA, + image.size(), + _rgbaSize, + stride, + data); + _rgbaSize = image.size(); + } + _argb32Program->setUniformValue("s_texture", GLint(0)); + + paintTransformedContent(&*_argb32Program, geometry); +} + +void Pip::RendererGL::paintTransformedContent( + not_null program, + ContentGeometry geometry) { + const auto rect = transformRect(geometry.inner); + const auto xscale = 1.f / geometry.inner.width(); + const auto yscale = 1.f / geometry.inner.height(); + const GLfloat coords[] = { + -1.f, 1.f, + -geometry.inner.x() * xscale, + -geometry.inner.y() * yscale, + + 1.f, 1.f, + (geometry.outer.width() - geometry.inner.x()) * xscale, + -geometry.inner.y() * yscale, + + 1.f, -1.f, + (geometry.outer.width() - geometry.inner.x()) * xscale, + (geometry.outer.height() - geometry.inner.y()) * yscale, + + -1.f, -1.f, + -geometry.inner.x() * xscale, + (geometry.outer.height() - geometry.inner.y()) * yscale, + }; + + _contentBuffer->write(0, coords, sizeof(coords)); + + const auto rgbaFrame = _chromaSize.isEmpty(); + _f->glActiveTexture(rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3); + _shadowImage.bind(*_f); + + const auto fadeAlpha = st::radialBg->c.alphaF() * geometry.fade; + const auto roundRect = transformRect(RoundingRect(geometry)); + program->setUniformValue("roundRect", Uniform(roundRect)); + program->setUniformValue("h_texture", GLint(rgbaFrame ? 1 : 3)); + program->setUniformValue("h_size", QSizeF(_shadowImage.image().size())); + program->setUniformValue("h_extend", QVector4D( + st::callShadow.extend.left(), + st::callShadow.extend.top(), + st::callShadow.extend.right(), + st::callShadow.extend.bottom())); + program->setUniformValue("h_components", QVector4D( + float(st::callShadow.topLeft.width()), + float(st::callShadow.topLeft.height()), + float(st::callShadow.left.width()), + float(st::callShadow.top.height()))); + program->setUniformValue( + "roundRadius", + GLfloat(st::roundRadiusLarge * _factor)); + program->setUniformValue("fadeColor", QVector4D( + float(st::radialBg->c.redF() * fadeAlpha), + float(st::radialBg->c.greenF() * fadeAlpha), + float(st::radialBg->c.blueF() * fadeAlpha), + float(fadeAlpha))); + + toggleBlending(true); + FillTexturedRectangle(*_f, &*program); +} + +void Pip::RendererGL::uploadTexture( + GLint internalformat, + GLint format, + QSize size, + QSize hasSize, + int stride, + const void *data) const { + _f->glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); + if (hasSize != size) { + _f->glTexImage2D( + GL_TEXTURE_2D, + 0, + internalformat, + size.width(), + size.height(), + 0, + format, + GL_UNSIGNED_BYTE, + data); + } else { + _f->glTexSubImage2D( + GL_TEXTURE_2D, + 0, + 0, + 0, + size.width(), + size.height(), + format, + GL_UNSIGNED_BYTE, + data); + } + _f->glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +} + +void Pip::RendererGL::paintRadialLoading( + QRect inner, + float64 controlsShown) { + paintUsingRaster(_radialImage, inner, [&](Painter &&p) { + // Raster renderer paints content, then radial loading, then fade. + // Here we paint fade together with the content, so we should emulate + // radial loading being under the fade. + // + // The loading background is the same color as the fade (radialBg), + // so nothing should be done with it. But the fade should be added + // to the radial loading line color (radialFg). + const auto newInner = QRect(QPoint(), inner.size()); + const auto fg = st::radialFg->c; + const auto fade = st::radialBg->c; + const auto fadeAlpha = controlsShown * fade.alphaF(); + const auto fgAlpha = 1. - fadeAlpha; + const auto color = (fadeAlpha == 0.) ? fg : QColor( + int(std::round(fg.red() * fgAlpha + fade.red() * fadeAlpha)), + int(std::round(fg.green() * fgAlpha + fade.green() * fadeAlpha)), + int(std::round(fg.blue() * fgAlpha + fade.blue() * fadeAlpha)), + fg.alphaF()); + + _owner->paintRadialLoadingContent(p, newInner, color); + }, kRadialLoadingOffset, true); +} + +void Pip::RendererGL::paintPlayback(QRect outer, float64 shown) { + paintUsingRaster(_playbackImage, outer, [&](Painter &&p) { + const auto newOuter = QRect(QPoint(), outer.size()); + _owner->paintPlaybackContent(p, newOuter, shown); + }, kPlaybackOffset, true); +} + +void Pip::RendererGL::paintVolumeController(QRect outer, float64 shown) { + paintUsingRaster(_volumeControllerImage, outer, [&](Painter &&p) { + const auto newOuter = QRect(QPoint(), outer.size()); + _owner->paintVolumeControllerContent(p, newOuter, shown); + }, kVolumeControllerOffset, true); +} + +void Pip::RendererGL::paintButtonsStart() { + validateControls(); + _f->glActiveTexture(GL_TEXTURE0); + _controlsImage.bind(*_f); + toggleBlending(true); +} + +void Pip::RendererGL::paintButton( + const Button &button, + int outerWidth, + float64 shown, + float64 over, + const style::icon &icon, + const style::icon &iconOver) { + const auto tryIndex = [&](int stateIndex) -> std::optional { + const auto result = ControlMeta(button.state, stateIndex); + return (result.icon == &icon && result.iconOver == &iconOver) + ? std::make_optional(result) + : std::nullopt; + }; + const auto meta = tryIndex(0) + ? *tryIndex(0) + : tryIndex(1) + ? *tryIndex(1) + : *tryIndex(2); + Assert(meta.icon == &icon && meta.iconOver == &iconOver); + + const auto offset = kControlsOffset + (meta.index * kControlValues) / 4; + const auto iconRect = _controlsImage.texturedRect( + button.icon, + _controlsTextures[meta.index * 2 + 0]); + const auto iconOverRect = _controlsImage.texturedRect( + button.icon, + _controlsTextures[meta.index * 2 + 1]); + const auto iconGeometry = transformRect(iconRect.geometry); + const GLfloat coords[] = { + iconGeometry.left(), iconGeometry.top(), + iconRect.texture.left(), iconRect.texture.bottom(), + + iconGeometry.right(), iconGeometry.top(), + iconRect.texture.right(), iconRect.texture.bottom(), + + iconGeometry.right(), iconGeometry.bottom(), + iconRect.texture.right(), iconRect.texture.top(), + + iconGeometry.left(), iconGeometry.bottom(), + iconRect.texture.left(), iconRect.texture.top(), + + iconOverRect.texture.left(), iconOverRect.texture.bottom(), + iconOverRect.texture.right(), iconOverRect.texture.bottom(), + iconOverRect.texture.right(), iconOverRect.texture.top(), + iconOverRect.texture.left(), iconOverRect.texture.top(), + }; + _contentBuffer->write( + offset * 4 * sizeof(GLfloat), + coords, + sizeof(coords)); + _controlsProgram->bind(); + _controlsProgram->setUniformValue("o_opacity", GLfloat(over)); + _controlsProgram->setUniformValue("g_opacity", GLfloat(shown)); + _controlsProgram->setUniformValue("viewport", _uniformViewport); + + GLint overTexcoord = _controlsProgram->attributeLocation("o_texcoordIn"); + _f->glVertexAttribPointer( + overTexcoord, + 2, + GL_FLOAT, + GL_FALSE, + 2 * sizeof(GLfloat), + reinterpret_cast((offset + 4) * 4 * sizeof(GLfloat))); + _f->glEnableVertexAttribArray(overTexcoord); + FillTexturedRectangle(*_f, &*_controlsProgram, offset); + _f->glDisableVertexAttribArray(overTexcoord); +} + +auto Pip::RendererGL::ControlMeta(OverState control, int index) +-> Control { + Expects(index >= 0); + + switch (control) { + case OverState::Close: Assert(index < 1); return { + 0, + &st::pipCloseIcon, + &st::pipCloseIconOver, + }; + case OverState::Enlarge: Assert(index < 1); return { + 1, + &st::pipEnlargeIcon, + &st::pipEnlargeIconOver, + }; + case OverState::VolumeToggle: Assert(index < 3); return { + (2 + index), + (index == 0 + ? &st::pipVolumeIcon0 + : (index == 1) + ? &st::pipVolumeIcon1 + : &st::pipVolumeIcon2), + (index == 0 + ? &st::pipVolumeIcon0Over + : (index == 1) + ? &st::pipVolumeIcon1Over + : &st::pipVolumeIcon2Over), + }; + case OverState::Other: Assert(index < 2); return { + (5 + index), + (index ? &st::pipPauseIcon : &st::pipPlayIcon), + (index ? &st::pipPauseIconOver : &st::pipPlayIconOver), + }; + } + Unexpected("Control value in Pip::RendererGL::ControlIndex."); +} + +void Pip::RendererGL::validateControls() { + if (!_controlsImage.image().isNull()) { + return; + } + const auto metas = { + ControlMeta(OverState::Close), + ControlMeta(OverState::Enlarge), + ControlMeta(OverState::VolumeToggle), + ControlMeta(OverState::VolumeToggle, 1), + ControlMeta(OverState::VolumeToggle, 2), + ControlMeta(OverState::Other), + ControlMeta(OverState::Other, 1), + }; + auto maxWidth = 0; + auto fullHeight = 0; + for (const auto meta : metas) { + Assert(meta.icon->size() == meta.iconOver->size()); + maxWidth = std::max(meta.icon->width(), maxWidth); + fullHeight += 2 * meta.icon->height(); + } + auto image = QImage( + QSize(maxWidth, fullHeight) * _factor, + QImage::Format_ARGB32_Premultiplied); + image.fill(Qt::transparent); + image.setDevicePixelRatio(_factor); + { + auto p = QPainter(&image); + auto index = 0; + auto height = 0; + const auto paint = [&](not_null icon) { + icon->paint(p, 0, height, maxWidth); + _controlsTextures[index++] = QRect( + QPoint(0, height) * _factor, + icon->size() * _factor); + height += icon->height(); + }; + for (const auto meta : metas) { + paint(meta.icon); + paint(meta.iconOver); + } + } + _controlsImage.setImage(std::move(image)); +} + +void Pip::RendererGL::invalidateControls() { + _controlsImage.invalidate(); + ranges::fill(_controlsTextures, QRect()); +} + +void Pip::RendererGL::paintUsingRaster( + Ui::GL::Image &image, + QRect rect, + Fn method, + int bufferOffset, + bool transparent) { + auto raster = image.takeImage(); + const auto size = rect.size() * _factor; + if (raster.width() < size.width() || raster.height() < size.height()) { + raster = QImage(size, QImage::Format_ARGB32_Premultiplied); + raster.setDevicePixelRatio(_factor); + if (!transparent + && (raster.width() > size.width() + || raster.height() > size.height())) { + raster.fill(Qt::transparent); + } + } else if (raster.devicePixelRatio() != _factor) { + raster.setDevicePixelRatio(_factor); + } + + if (transparent) { + raster.fill(Qt::transparent); + } + method(Painter(&raster)); + + image.setImage(std::move(raster)); + image.bind(*_f, size); + + const auto textured = image.texturedRect(rect, QRect(QPoint(), size)); + const auto geometry = transformRect(textured.geometry); + const GLfloat coords[] = { + geometry.left(), geometry.top(), + textured.texture.left(), textured.texture.bottom(), + + geometry.right(), geometry.top(), + textured.texture.right(), textured.texture.bottom(), + + geometry.right(), geometry.bottom(), + textured.texture.right(), textured.texture.top(), + + geometry.left(), geometry.bottom(), + textured.texture.left(), textured.texture.top(), + }; + _contentBuffer->write( + bufferOffset * 4 * sizeof(GLfloat), + coords, + sizeof(coords)); + + _imageProgram->bind(); + _imageProgram->setUniformValue("viewport", _uniformViewport); + _imageProgram->setUniformValue("s_texture", GLint(0)); + _imageProgram->setUniformValue("g_opacity", GLfloat(1)); + + _f->glActiveTexture(GL_TEXTURE0); + image.bind(*_f, size); + + toggleBlending(transparent); + FillTexturedRectangle(*_f, &*_imageProgram, bufferOffset); +} + +void Pip::RendererGL::toggleBlending(bool enabled) { + if (_blendingEnabled == enabled) { + return; + } else if (enabled) { + _f->glEnable(GL_BLEND); + _f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } else { + _f->glDisable(GL_BLEND); + } + _blendingEnabled = enabled; +} + +QRect Pip::RendererGL::RoundingRect(ContentGeometry geometry) { + const auto inner = geometry.inner; + const auto attached = geometry.attached; + const auto added = std::max({ + st::roundRadiusLarge, + inner.x(), + inner.y(), + geometry.outer.width() - inner.x() - inner.width(), + geometry.outer.height() - inner.y() - inner.height(), + st::callShadow.topLeft.width(), + st::callShadow.topLeft.height(), + st::callShadow.topRight.width(), + st::callShadow.topRight.height(), + st::callShadow.bottomRight.width(), + st::callShadow.bottomRight.height(), + st::callShadow.bottomLeft.width(), + st::callShadow.bottomLeft.height(), + }); + return geometry.inner.marginsAdded({ + (attached & RectPart::Left) ? added : 0, + (attached & RectPart::Top) ? added : 0, + (attached & RectPart::Right) ? added : 0, + (attached & RectPart::Bottom) ? added : 0, + }); +} + +Rect Pip::RendererGL::transformRect(const Rect &raster) const { + return TransformRect(raster, _viewport, _factor); +} + +Rect Pip::RendererGL::transformRect(const QRectF &raster) const { + return TransformRect(raster, _viewport, _factor); +} + +Rect Pip::RendererGL::transformRect(const QRect &raster) const { + return TransformRect(Rect(raster), _viewport, _factor); +} + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_pip_opengl.h b/Telegram/SourceFiles/media/view/media_view_pip_opengl.h new file mode 100644 index 0000000000..4a56bb46c0 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_pip_opengl.h @@ -0,0 +1,134 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "media/view/media_view_pip_renderer.h" +#include "ui/gl/gl_image.h" +#include "ui/gl/gl_primitives.h" + +#include + +namespace Media::View { + +class Pip::RendererGL final : public Pip::Renderer { +public: + explicit RendererGL(not_null owner); + + void init( + not_null widget, + QOpenGLFunctions &f) override; + + void deinit( + not_null widget, + QOpenGLFunctions &f) override; + + void resize( + not_null widget, + QOpenGLFunctions &f, + int w, + int h); + + void paint( + not_null widget, + QOpenGLFunctions &f) override; + +private: + struct Control { + int index = -1; + not_null icon; + not_null iconOver; + }; + void setDefaultViewport(QOpenGLFunctions &f); + void createShadowTexture(); + + void paintTransformedVideoFrame(ContentGeometry geometry) override; + void paintTransformedStaticContent( + const QImage &image, + ContentGeometry geometry) override; + void paintTransformedContent( + not_null program, + ContentGeometry geometry); + void paintRadialLoading( + QRect inner, + float64 controlsShown) override; + void paintButtonsStart() override; + void paintButton( + const Button &button, + int outerWidth, + float64 shown, + float64 over, + const style::icon &icon, + const style::icon &iconOver) override; + void paintPlayback(QRect outer, float64 shown) override; + void paintVolumeController(QRect outer, float64 shown) override; + + void paintUsingRaster( + Ui::GL::Image &image, + QRect rect, + Fn method, + int bufferOffset, + bool transparent = false); + + void validateControls(); + void invalidateControls(); + void toggleBlending(bool enabled); + + [[nodiscard]] QRect RoundingRect(ContentGeometry geometry); + + [[nodiscard]] Ui::GL::Rect transformRect(const QRect &raster) const; + [[nodiscard]] Ui::GL::Rect transformRect(const QRectF &raster) const; + [[nodiscard]] Ui::GL::Rect transformRect( + const Ui::GL::Rect &raster) const; + + void uploadTexture( + GLint internalformat, + GLint format, + QSize size, + QSize hasSize, + int stride, + const void *data) const; + + const not_null _owner; + + QOpenGLFunctions *_f = nullptr; + QSize _viewport; + float _factor = 1.; + QVector2D _uniformViewport; + + std::optional _contentBuffer; + std::optional _imageProgram; + std::optional _controlsProgram; + QOpenGLShader *_texturedVertexShader = nullptr; + std::optional _argb32Program; + std::optional _yuv420Program; + Ui::GL::Textures<4> _textures; + QSize _rgbaSize; + QSize _lumaSize; + QSize _chromaSize; + quint64 _cacheKey = 0; + int _trackFrameIndex = 0; + + Ui::GL::Image _radialImage; + Ui::GL::Image _controlsImage; + Ui::GL::Image _playbackImage; + Ui::GL::Image _volumeControllerImage; + Ui::GL::Image _shadowImage; + + static constexpr auto kControlsCount = 7; + [[nodiscard]] static Control ControlMeta( + OverState control, + int index = 0); + std::array _controlsTextures; + + bool _blendingEnabled = false; + + rpl::lifetime _lifetime; + +}; + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_pip_raster.cpp b/Telegram/SourceFiles/media/view/media_view_pip_raster.cpp new file mode 100644 index 0000000000..20150bd5c1 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_pip_raster.cpp @@ -0,0 +1,246 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "media/view/media_view_pip_raster.h" + +#include "ui/image/image_prepare.h" +#include "ui/widgets/shadow.h" +#include "styles/style_calls.h" // st::callShadow. + +namespace Media::View { +namespace { + +[[nodiscard]] Streaming::FrameRequest UnrotateRequest( + const Streaming::FrameRequest &request, + int rotation) { + if (!rotation) { + return request; + } + const auto unrotatedCorner = [&](RectPart corner) { + if (!(request.corners & corner)) { + return RectPart(0); + } + switch (corner) { + case RectPart::TopLeft: + return (rotation == 90) + ? RectPart::BottomLeft + : (rotation == 180) + ? RectPart::BottomRight + : RectPart::TopRight; + case RectPart::TopRight: + return (rotation == 90) + ? RectPart::TopLeft + : (rotation == 180) + ? RectPart::BottomLeft + : RectPart::BottomRight; + case RectPart::BottomRight: + return (rotation == 90) + ? RectPart::TopRight + : (rotation == 180) + ? RectPart::TopLeft + : RectPart::BottomLeft; + case RectPart::BottomLeft: + return (rotation == 90) + ? RectPart::BottomRight + : (rotation == 180) + ? RectPart::TopRight + : RectPart::TopLeft; + } + Unexpected("Corner in rotateCorner."); + }; + auto result = request; + result.outer = FlipSizeByRotation(request.outer, rotation); + result.resize = FlipSizeByRotation(request.resize, rotation); + result.corners = unrotatedCorner(RectPart::TopLeft) + | unrotatedCorner(RectPart::TopRight) + | unrotatedCorner(RectPart::BottomRight) + | unrotatedCorner(RectPart::BottomLeft); + return result; +} + +} // namespace + +Pip::RendererSW::RendererSW(not_null owner) +: _owner(owner) +, _roundRect(ImageRoundRadius::Large, st::radialBg) { +} + +void Pip::RendererSW::paintFallback( + Painter &&p, + const QRegion &clip, + Ui::GL::Backend backend) { + _p = &p; + _clip = &clip; + _clipOuter = clip.boundingRect(); + _owner->paint(this); + _p = nullptr; + _clip = nullptr; +} + +void Pip::RendererSW::paintTransformedVideoFrame( + ContentGeometry geometry) { + paintTransformedImage( + _owner->videoFrame(frameRequest(geometry)), + geometry); +} + +void Pip::RendererSW::paintTransformedStaticContent( + const QImage &image, + ContentGeometry geometry) { + paintTransformedImage( + staticContentByRequest(image, frameRequest(geometry)), + geometry); +} + +void Pip::RendererSW::paintFade(ContentGeometry geometry) const { + using Part = RectPart; + const auto sides = geometry.attached; + const auto rounded = RectPart(0) + | ((sides & (Part::Top | Part::Left)) ? Part(0) : Part::TopLeft) + | ((sides & (Part::Top | Part::Right)) ? Part(0) : Part::TopRight) + | ((sides & (Part::Bottom | Part::Right)) + ? Part(0) + : Part::BottomRight) + | ((sides & (Part::Bottom | Part::Left)) + ? Part(0) + : Part::BottomLeft); + _roundRect.paintSomeRounded( + *_p, + geometry.inner, + rounded | Part::NoTopBottom | Part::Top | Part::Bottom); +} + +void Pip::RendererSW::paintButtonsStart() { +} + +void Pip::RendererSW::paintButton( + const Button &button, + int outerWidth, + float64 shown, + float64 over, + const style::icon &icon, + const style::icon &iconOver) { + if (over < 1.) { + _p->setOpacity(shown); + icon.paint(*_p, button.icon.x(), button.icon.y(), outerWidth); + } + if (over > 0.) { + _p->setOpacity(over * shown); + iconOver.paint(*_p, button.icon.x(), button.icon.y(), outerWidth); + } +} + +Pip::FrameRequest Pip::RendererSW::frameRequest( + ContentGeometry geometry) const { + auto result = FrameRequest(); + result.outer = geometry.inner.size() * style::DevicePixelRatio(); + result.resize = result.outer; + result.corners = RectPart(0) + | ((geometry.attached & (RectPart::Left | RectPart::Top)) + ? RectPart(0) + : RectPart::TopLeft) + | ((geometry.attached & (RectPart::Top | RectPart::Right)) + ? RectPart(0) + : RectPart::TopRight) + | ((geometry.attached & (RectPart::Right | RectPart::Bottom)) + ? RectPart(0) + : RectPart::BottomRight) + | ((geometry.attached & (RectPart::Bottom | RectPart::Left)) + ? RectPart(0) + : RectPart::BottomLeft); + result.radius = ImageRoundRadius::Large; + return UnrotateRequest(result, geometry.rotation); +} + +QImage Pip::RendererSW::staticContentByRequest( + const QImage &image, + const FrameRequest &request) { + if (request.resize.isEmpty()) { + return QImage(); + } else if (!_preparedStaticContent.isNull() + && _preparedStaticRequest == request + && image.cacheKey() == _preparedStaticKey) { + return _preparedStaticContent; + } + _preparedStaticKey = image.cacheKey(); + _preparedStaticRequest = request; + //_preparedCoverStorage = Streaming::PrepareByRequest( + // _instance.info().video.cover, + // false, + // _instance.info().video.rotation, + // request, + // std::move(_preparedCoverStorage)); + using Option = Images::Option; + const auto options = Option::Smooth + | Option::RoundedLarge + | ((request.corners & RectPart::TopLeft) + ? Option::RoundedTopLeft + : Option(0)) + | ((request.corners & RectPart::TopRight) + ? Option::RoundedTopRight + : Option(0)) + | ((request.corners & RectPart::BottomRight) + ? Option::RoundedBottomRight + : Option(0)) + | ((request.corners & RectPart::BottomLeft) + ? Option::RoundedBottomLeft + : Option(0)); + _preparedStaticContent = Images::prepare( + image, + request.resize.width(), + request.resize.height(), + options, + request.outer.width(), + request.outer.height()); + return _preparedStaticContent; +} + +void Pip::RendererSW::paintTransformedImage( + const QImage &image, + ContentGeometry geometry) { + const auto rect = geometry.inner; + const auto rotation = geometry.rotation; + if (geometry.useTransparency) { + const auto sides = RectPart::AllSides & ~geometry.attached; + Ui::Shadow::paint(*_p, rect, geometry.outer.width(), st::callShadow); + } + + if (UsePainterRotation(rotation, false)) { + if (rotation) { + _p->save(); + _p->rotate(rotation); + } + PainterHighQualityEnabler hq(*_p); + _p->drawImage(RotatedRect(rect, rotation), image); + if (rotation) { + _p->restore(); + } + } else { + _p->drawImage(rect, RotateFrameImage(image, rotation)); + } + + if (geometry.fade > 0) { + _p->setOpacity(geometry.fade); + paintFade(geometry); + } +} + +void Pip::RendererSW::paintRadialLoading( + QRect inner, + float64 controlsShown) { + _owner->paintRadialLoadingContent(*_p, inner, st::radialFg->c); +} + +void Pip::RendererSW::paintPlayback(QRect outer, float64 shown) { + _owner->paintPlaybackContent(*_p, outer, shown); +} + +void Pip::RendererSW::paintVolumeController(QRect outer, float64 shown) { + _owner->paintVolumeControllerContent(*_p, outer, shown); +} + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_pip_raster.h b/Telegram/SourceFiles/media/view/media_view_pip_raster.h new file mode 100644 index 0000000000..d24513c438 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_pip_raster.h @@ -0,0 +1,66 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "media/view/media_view_pip_renderer.h" + +namespace Media::View { + +class Pip::RendererSW final : public Pip::Renderer { +public: + explicit RendererSW(not_null owner); + + void paintFallback( + Painter &&p, + const QRegion &clip, + Ui::GL::Backend backend) override; + +private: + void paintTransformedVideoFrame(ContentGeometry geometry) override; + void paintTransformedStaticContent( + const QImage &image, + ContentGeometry geometry) override; + void paintTransformedImage( + const QImage &image, + ContentGeometry geometry); + void paintRadialLoading( + QRect inner, + float64 controlsShown) override; + void paintButtonsStart() override; + void paintButton( + const Button &button, + int outerWidth, + float64 shown, + float64 over, + const style::icon &icon, + const style::icon &iconOver) override; + void paintPlayback(QRect outer, float64 shown) override; + void paintVolumeController(QRect outer, float64 shown) override; + + void paintFade(ContentGeometry geometry) const; + + [[nodiscard]] FrameRequest frameRequest(ContentGeometry geometry) const; + [[nodiscard]] QImage staticContentByRequest( + const QImage &image, + const FrameRequest &request); + + const not_null _owner; + + Painter *_p = nullptr; + const QRegion *_clip = nullptr; + QRect _clipOuter; + + Ui::RoundRect _roundRect; + + QImage _preparedStaticContent; + FrameRequest _preparedStaticRequest; + qint64 _preparedStaticKey = 0; + +}; + +} // namespace Media::View diff --git a/Telegram/SourceFiles/media/view/media_view_pip_renderer.h b/Telegram/SourceFiles/media/view/media_view_pip_renderer.h new file mode 100644 index 0000000000..d73ccd1a25 --- /dev/null +++ b/Telegram/SourceFiles/media/view/media_view_pip_renderer.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "media/view/media_view_pip.h" +#include "ui/gl/gl_surface.h" + +namespace Media::View { + +class Pip::Renderer : public Ui::GL::Renderer { +public: + virtual void paintTransformedVideoFrame(ContentGeometry geometry) = 0; + virtual void paintTransformedStaticContent( + const QImage &image, + ContentGeometry geometry) = 0; + virtual void paintRadialLoading( + QRect inner, + float64 controlsShown) = 0; + virtual void paintButtonsStart() = 0; + virtual void paintButton( + const Button &button, + int outerWidth, + float64 shown, + float64 over, + const style::icon &icon, + const style::icon &iconOver) = 0; + virtual void paintPlayback(QRect outer, float64 shown) = 0; + virtual void paintVolumeController(QRect outer, float64 shown) = 0; + +}; + +} // namespace Media::View