From f473a1a804359100aa10ba7853f18914517f94cf Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 20 Jul 2023 21:06:30 +0300 Subject: [PATCH] Attempted to increase performance of chart paint by caching every frame. --- .../statistics/chart_line_view_context.cpp | 19 ++++++- .../statistics/chart_line_view_context.h | 57 ++++++++++++++++++- .../SourceFiles/statistics/chart_widget.cpp | 48 ++++++++++++---- .../statistics/linear_chart_view.cpp | 48 ++++++++++++++-- .../statistics/linear_chart_view.h | 2 +- 5 files changed, 156 insertions(+), 18 deletions(-) diff --git a/Telegram/SourceFiles/statistics/chart_line_view_context.cpp b/Telegram/SourceFiles/statistics/chart_line_view_context.cpp index 1d3e7a9f8..2386c9ed5 100644 --- a/Telegram/SourceFiles/statistics/chart_line_view_context.cpp +++ b/Telegram/SourceFiles/statistics/chart_line_view_context.cpp @@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Statistic { namespace { -constexpr auto kAlphaDuration = float64(200); +constexpr auto kAlphaDuration = float64(350); } // namespace void ChartLineViewContext::setEnabled(int id, bool enabled, crl::time now) { @@ -39,6 +39,23 @@ float64 ChartLineViewContext::alpha(int id) const { return (it == end(_entries)) ? 1. : it->second.alpha; } +void ChartLineViewContext::setCacheImage(int id, QImage &&image) { + (_isFooter ? _cachesFooter : _caches)[id].image = std::move(image); +} + +void ChartLineViewContext::setCacheLastToken(int id, CacheToken token) { + (_isFooter ? _cachesFooter : _caches)[id].lastToken = token; +} + +void ChartLineViewContext::setCacheHQ(int id, bool value) { + (_isFooter ? _cachesFooter : _caches)[id].hq = value; +} + +const ChartLineViewContext::Cache &ChartLineViewContext::cache(int id) { + [[maybe_unused]] auto unused = (_isFooter ? _cachesFooter : _caches)[id]; + return (_isFooter ? _cachesFooter : _caches).find(id)->second; +} + void ChartLineViewContext::tick(crl::time now) { auto finishedCount = 0; auto idsToRemove = std::vector(); diff --git a/Telegram/SourceFiles/statistics/chart_line_view_context.h b/Telegram/SourceFiles/statistics/chart_line_view_context.h index 506763ac6..5956f2827 100644 --- a/Telegram/SourceFiles/statistics/chart_line_view_context.h +++ b/Telegram/SourceFiles/statistics/chart_line_view_context.h @@ -7,19 +7,70 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include + namespace Statistic { class ChartLineViewContext final { public: ChartLineViewContext() = default; + struct CacheToken final { + explicit CacheToken() = default; + explicit CacheToken( + Limits xIndices, + Limits xPercentageLimits, + Limits heightLimits, + QSize rectSize) + : xIndices(std::move(xIndices)) + , xPercentageLimits(std::move(xPercentageLimits)) + , heightLimits(std::move(heightLimits)) + , rectSize(std::move(rectSize)) { + } + + bool operator==(const CacheToken &other) const { + return (rectSize == other.rectSize) + && (xIndices.min == other.xIndices.min) + && (xIndices.max == other.xIndices.max) + && (xPercentageLimits.min == other.xPercentageLimits.min) + && (xPercentageLimits.max == other.xPercentageLimits.max) + && (heightLimits.min == other.heightLimits.min) + && (heightLimits.max == other.heightLimits.max); + } + + bool operator!=(const CacheToken &other) const { + return !(*this == other); + } + + Limits xIndices; + Limits xPercentageLimits; + Limits heightLimits; + QSize rectSize; + }; + + struct Cache final { + QImage image; + CacheToken lastToken; + bool hq = false; + }; + void setEnabled(int id, bool enabled, crl::time now); - [[nodiscard]] bool isFinished() const; [[nodiscard]] bool isEnabled(int id) const; + [[nodiscard]] bool isFinished() const; [[nodiscard]] float64 alpha(int id) const; + void setCacheFooter(bool value) { + _isFooter = value; + } + void setCacheImage(int id, QImage &&image); + void setCacheLastToken(int id, CacheToken token); + void setCacheHQ(int id, bool value); + [[nodiscard]] const Cache &cache(int id); + void tick(crl::time now); + float64 factor = 1.; + private: struct Entry final { bool enabled = false; @@ -28,8 +79,12 @@ private: }; base::flat_map _entries; + base::flat_map _caches; + base::flat_map _cachesFooter; bool _isFinished = true; + bool _isFooter = false; + }; } // namespace Statistic diff --git a/Telegram/SourceFiles/statistics/chart_widget.cpp b/Telegram/SourceFiles/statistics/chart_widget.cpp index bcff2b920..c8271bf48 100644 --- a/Telegram/SourceFiles/statistics/chart_widget.cpp +++ b/Telegram/SourceFiles/statistics/chart_widget.cpp @@ -945,10 +945,11 @@ void ChartWidget::setupChartArea() { : -1, }; if (_chartData) { - p.setRenderHint( - QPainter::Antialiasing, - !_animationController.isFPSSlow() - || !_animationController.animating()); + // p.setRenderHint( + // QPainter::Antialiasing, + // !_animationController.isFPSSlow() + // || !_animationController.animating()); + PainterHighQualityEnabler hp(p); Statistic::PaintLinearChartView( p, _chartData, @@ -1069,10 +1070,12 @@ void ChartWidget::setupFooter() { if (_chartData) { auto detailsPaintContext = DetailsPaintContext{ .xIndex = -1 }; p.fillRect(r, st::boxBg); - p.setRenderHint( - QPainter::Antialiasing, - !_animationController.isFPSSlow() - || !_animationController.animating()); + // p.setRenderHint( + // QPainter::Antialiasing, + // !_animationController.isFPSSlow() + // || !_animationController.animating()); + PainterHighQualityEnabler hp(p); + _animatedChartLines.setCacheFooter(true); Statistic::PaintLinearChartView( p, _chartData, @@ -1082,6 +1085,7 @@ void ChartWidget::setupFooter() { r, _animatedChartLines, detailsPaintContext); + _animatedChartLines.setCacheFooter(false); } }); @@ -1185,17 +1189,37 @@ void ChartWidget::setupDetails() { } void ChartWidget::setupFilterButtons() { - if (!_chartData) { + if (!_chartData || (_chartData.lines.size() <= 1)) { _filterButtons = nullptr; _chartArea->update(); return; } _filterButtons = base::make_unique_q(this); + const auto asd = Ui::CreateChild(_filterButtons.get()); + asd->paintRequest( + ) | rpl::start_with_next([=](QRect r) { + auto p = QPainter(asd); + p.setOpacity(0.3); + p.fillRect(r, Qt::darkRed); + p.setOpacity(1.0); + p.setFont(st::statisticsDetailsBottomCaptionStyle.font); + p.setPen(st::boxTextFg); + p.drawText(asd->rect(), QString::number(_animatedChartLines.factor * 100) + "%", style::al_center); + }, asd->lifetime()); + asd->setClickedCallback([=] { + _animatedChartLines.factor -= 0.1; + if (_animatedChartLines.factor <= 0) { + _animatedChartLines.factor = 1.0; + } + asd->update(); + }); + asd->resize(50, 50); + sizeValue( - ) | rpl::filter([=](const QSize &s) { + ) | rpl::filter([](const QSize &s) { return s.width() > 0; - }) | rpl::take(1) | rpl::start_with_next([=](const QSize &s) { + }) | rpl::take(2) | rpl::start_with_next([=](const QSize &s) { auto texts = std::vector(); auto colors = std::vector(); auto ids = std::vector(); @@ -1210,6 +1234,8 @@ void ChartWidget::setupFilterButtons() { _filterButtons->fillButtons(texts, colors, ids, s.width()); resizeHeight(); + asd->raise(); + asd->moveToRight(0, 0); }, _filterButtons->lifetime()); _filterButtons->buttonEnabledChanges( diff --git a/Telegram/SourceFiles/statistics/linear_chart_view.cpp b/Telegram/SourceFiles/statistics/linear_chart_view.cpp index 618de3ddf..704842af0 100644 --- a/Telegram/SourceFiles/statistics/linear_chart_view.cpp +++ b/Telegram/SourceFiles/statistics/linear_chart_view.cpp @@ -24,11 +24,17 @@ void PaintLinearChartView( const Limits &xPercentageLimits, const Limits &heightLimits, const QRect &rect, - const ChartLineViewContext &lineViewContext, + ChartLineViewContext &lineViewContext, DetailsPaintContext &detailsPaintContext) { const auto currentMinHeight = rect.y(); // const auto currentMaxHeight = rect.height() + rect.y(); // + const auto cacheToken = ChartLineViewContext::CacheToken( + xIndices, + xPercentageLimits, + heightLimits, + rect.size()); + for (const auto &line : chartData.lines) { p.setOpacity(lineViewContext.alpha(line.id)); if (!p.opacity()) { @@ -38,6 +44,32 @@ void PaintLinearChartView( ? 0. : (chartData.xPercentage.front() * rect.width()); + //// + const auto &cache = lineViewContext.cache(line.id); + + const auto isSameToken = (cache.lastToken == cacheToken); + if (isSameToken && cache.hq) { + p.drawImage(rect.topLeft(), cache.image); + continue; + } + const auto kRatio = lineViewContext.factor;//0.5; + lineViewContext.setCacheHQ(line.id, isSameToken); + auto image = QImage(); + image = QImage( + rect.size() * style::DevicePixelRatio() * (isSameToken ? 1. : kRatio), + QImage::Format_ARGB32_Premultiplied); + image.setDevicePixelRatio(style::DevicePixelRatio()); + image.fill(Qt::transparent); + // image.fill(Qt::darkRed); + auto imagePainter = QPainter(&image); + imagePainter.setRenderHint(QPainter::Antialiasing, true); + if (isSameToken) { + // PainterHighQualityEnabler hp(imagePainter); + } else { + imagePainter.scale(kRatio, kRatio); + } + //// + auto first = true; auto chartPath = QPainterPath(); @@ -69,9 +101,17 @@ void PaintLinearChartView( } chartPath.lineTo(xPoint, yPoint); } - p.setPen(QPen(line.color, st::statisticsChartLineWidth)); - p.setBrush(Qt::NoBrush); - p.drawPath(chartPath); + imagePainter.translate(-rect.topLeft()); + imagePainter.setPen(QPen(line.color, st::statisticsChartLineWidth)); + imagePainter.setBrush(Qt::NoBrush); + imagePainter.drawPath(chartPath); + + if (!isSameToken) { + image = image.scaled(rect.size() * style::DevicePixelRatio(), Qt::IgnoreAspectRatio, Qt::FastTransformation); + } + p.drawImage(rect.topLeft(), image); + lineViewContext.setCacheImage(line.id, std::move(image)); + lineViewContext.setCacheLastToken(line.id, cacheToken); } p.setPen(st::boxTextFg); p.setOpacity(1.); diff --git a/Telegram/SourceFiles/statistics/linear_chart_view.h b/Telegram/SourceFiles/statistics/linear_chart_view.h index 8460a8bc2..c9e11f28f 100644 --- a/Telegram/SourceFiles/statistics/linear_chart_view.h +++ b/Telegram/SourceFiles/statistics/linear_chart_view.h @@ -24,7 +24,7 @@ void PaintLinearChartView( const Limits &xPercentageLimits, const Limits &heightLimits, const QRect &rect, - const ChartLineViewContext &lineViewContext, + ChartLineViewContext &lineViewContext, DetailsPaintContext &detailsPaintContext); } // namespace Statistic