Optimize bubble gradient background painting.

This commit is contained in:
John Preston 2021-09-17 13:21:06 +03:00
parent 559d4cf4da
commit ab6f5ae2ac
3 changed files with 135 additions and 36 deletions

View file

@ -431,17 +431,20 @@ ChatPaintContext ChatTheme::preparePaintContext(
not_null<const ChatStyle*> st, not_null<const ChatStyle*> st,
QRect viewport, QRect viewport,
QRect clip) { QRect clip) {
_bubblesBackground.area = viewport.size(); const auto area = viewport.size();
//if (!_bubblesBackgroundPrepared.isNull() if (!_bubblesBackgroundPrepared.isNull()
// && _bubblesBackground.area != viewport.size() && _bubblesBackground.area != area) {
// && !viewport.isEmpty()) { if (!_cacheBubblesTimer) {
// // #TODO bubbles delayed caching _cacheBubblesTimer.emplace([=] { cacheBubbles(); });
// _bubblesBackground = CacheBackground({ }
// .prepared = _bubblesBackgroundPrepared, if (_cacheBubblesArea != area
// .area = viewport.size(), || (!_cacheBubblesTimer->isActive()
// }); && !_bubblesCachingRequest)) {
// _bubblesBackgroundPattern->pixmap = _bubblesBackground.pixmap; _cacheBubblesArea = area;
//} _lastBubblesAreaChangeTime = crl::now();
_cacheBubblesTimer->callOnce(kCacheBackgroundFastTimeout);
}
}
return { return {
.st = st, .st = st,
.bubblesPattern = _bubblesBackgroundPattern.get(), .bubblesPattern = _bubblesBackgroundPattern.get(),
@ -460,15 +463,15 @@ const BackgroundState &ChatTheme::backgroundState(QSize area) {
&& !background().gradientForFill.isNull()) { && !background().gradientForFill.isNull()) {
// We don't support direct painting of patterned gradients. // We don't support direct painting of patterned gradients.
// So we need to sync-generate cache image here. // So we need to sync-generate cache image here.
_willCacheForArea = area; _cacheBackgroundArea = area;
setCachedBackground(CacheBackground(currentCacheRequest(area))); setCachedBackground(CacheBackground(cacheBackgroundRequest(area)));
_cacheBackgroundTimer->cancel(); _cacheBackgroundTimer->cancel();
} else if (_backgroundState.now.area != area) { } else if (_backgroundState.now.area != area) {
if (_willCacheForArea != area if (_cacheBackgroundArea != area
|| (!_cacheBackgroundTimer->isActive() || (!_cacheBackgroundTimer->isActive()
&& !_backgroundCachingRequest)) { && !_backgroundCachingRequest)) {
_willCacheForArea = area; _cacheBackgroundArea = area;
_lastAreaChangeTime = crl::now(); _lastBackgroundAreaChangeTime = crl::now();
_cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout); _cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout);
} }
} }
@ -495,7 +498,7 @@ void ChatTheme::generateNextBackgroundRotation() {
return; return;
} }
constexpr auto kAddRotationDoubled = (720 - 45); constexpr auto kAddRotationDoubled = (720 - 45);
const auto request = currentCacheRequest( const auto request = cacheBackgroundRequest(
_backgroundState.now.area, _backgroundState.now.area,
kAddRotationDoubled); kAddRotationDoubled);
if (!request) { if (!request) {
@ -506,7 +509,7 @@ void ChatTheme::generateNextBackgroundRotation() {
if (!readyForBackgroundRotation()) { if (!readyForBackgroundRotation()) {
return; return;
} }
const auto request = currentCacheRequest( const auto request = cacheBackgroundRequest(
_backgroundState.now.area, _backgroundState.now.area,
kAddRotationDoubled); kAddRotationDoubled);
if (forRequest == request) { if (forRequest == request) {
@ -518,7 +521,7 @@ void ChatTheme::generateNextBackgroundRotation() {
}); });
} }
auto ChatTheme::currentCacheRequest(QSize area, int addRotation) const auto ChatTheme::cacheBackgroundRequest(QSize area, int addRotation) const
-> CacheBackgroundRequest { -> CacheBackgroundRequest {
if (background().colorForFill) { if (background().colorForFill) {
return {}; return {};
@ -527,7 +530,6 @@ auto ChatTheme::currentCacheRequest(QSize area, int addRotation) const
.background = background(), .background = background(),
.area = area, .area = area,
.gradientRotationAdd = addRotation, .gradientRotationAdd = addRotation,
// .recreateGradient = (addRotation != 0),
}; };
} }
@ -535,7 +537,7 @@ void ChatTheme::cacheBackground() {
Expects(_cacheBackgroundTimer.has_value()); Expects(_cacheBackgroundTimer.has_value());
const auto now = crl::now(); const auto now = crl::now();
if (now - _lastAreaChangeTime < kCacheBackgroundTimeout if (now - _lastBackgroundAreaChangeTime < kCacheBackgroundTimeout
&& QGuiApplication::mouseButtons() != 0) { && QGuiApplication::mouseButtons() != 0) {
_cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout); _cacheBackgroundTimer->callOnce(kCacheBackgroundFastTimeout);
return; return;
@ -545,7 +547,8 @@ void ChatTheme::cacheBackground() {
void ChatTheme::cacheBackgroundNow() { void ChatTheme::cacheBackgroundNow() {
if (!_backgroundCachingRequest) { if (!_backgroundCachingRequest) {
if (const auto request = currentCacheRequest(_willCacheForArea)) { if (const auto request = cacheBackgroundRequest(
_cacheBackgroundArea)) {
cacheBackgroundAsync(request); cacheBackgroundAsync(request);
} }
} }
@ -563,8 +566,8 @@ void ChatTheme::cacheBackgroundAsync(
crl::on_main(weak, [=, result = CacheBackground(request)]() mutable { crl::on_main(weak, [=, result = CacheBackground(request)]() mutable {
if (done) { if (done) {
done(std::move(result)); done(std::move(result));
} else if (const auto request = currentCacheRequest( } else if (const auto request = cacheBackgroundRequest(
_willCacheForArea)) { _cacheBackgroundArea)) {
if (_backgroundCachingRequest != request) { if (_backgroundCachingRequest != request) {
cacheBackgroundAsync(request); cacheBackgroundAsync(request);
} else { } else {
@ -605,6 +608,64 @@ void ChatTheme::setCachedBackground(CacheBackgroundResult &&cached) {
kBackgroundFadeDuration); kBackgroundFadeDuration);
} }
auto ChatTheme::cacheBubblesRequest(QSize area) const
-> CacheBackgroundRequest {
if (_bubblesBackgroundPrepared.isNull()) {
return {};
}
return {
.background = {
.gradientForFill = _bubblesBackgroundPrepared,
},
.area = area,
};
}
void ChatTheme::cacheBubbles() {
Expects(_cacheBubblesTimer.has_value());
const auto now = crl::now();
if (now - _lastBubblesAreaChangeTime < kCacheBackgroundTimeout
&& QGuiApplication::mouseButtons() != 0) {
_cacheBubblesTimer->callOnce(kCacheBackgroundFastTimeout);
return;
}
cacheBubblesNow();
}
void ChatTheme::cacheBubblesNow() {
if (!_bubblesCachingRequest) {
if (const auto request = cacheBackgroundRequest(
_cacheBubblesArea)) {
cacheBubblesAsync(request);
}
}
}
void ChatTheme::cacheBubblesAsync(
const CacheBackgroundRequest &request) {
_bubblesCachingRequest = request;
const auto weak = base::make_weak(this);
crl::async([=] {
if (!weak) {
return;
}
crl::on_main(weak, [=, result = CacheBackground(request)]() mutable {
if (const auto request = cacheBubblesRequest(
_cacheBubblesArea)) {
if (_bubblesCachingRequest != request) {
cacheBubblesAsync(request);
} else {
_bubblesCachingRequest = {};
_bubblesBackground = std::move(result);
_bubblesBackgroundPattern->pixmap
= _bubblesBackground.pixmap;
}
}
});
});
}
rpl::producer<> ChatTheme::repaintBackgroundRequests() const { rpl::producer<> ChatTheme::repaintBackgroundRequests() const {
return _repaintBackgroundRequests.events(); return _repaintBackgroundRequests.events();
} }

View file

@ -147,12 +147,20 @@ private:
const CacheBackgroundRequest &request, const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done = nullptr); Fn<void(CacheBackgroundResult&&)> done = nullptr);
void setCachedBackground(CacheBackgroundResult &&cached); void setCachedBackground(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest currentCacheRequest( [[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest(
QSize area, QSize area,
int addRotation = 0) const; int addRotation = 0) const;
[[nodiscard]] bool readyForBackgroundRotation() const; [[nodiscard]] bool readyForBackgroundRotation() const;
void generateNextBackgroundRotation(); void generateNextBackgroundRotation();
void cacheBubbles();
void cacheBubblesNow();
void cacheBubblesAsync(
const CacheBackgroundRequest &request);
void setCachedBubbles(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest cacheBubblesRequest(
QSize area) const;
[[nodiscard]] style::colorizer bubblesAccentColorizer( [[nodiscard]] style::colorizer bubblesAccentColorizer(
const QColor &accent) const; const QColor &accent) const;
void adjustPalette(const ChatThemeDescriptor &descriptor); void adjustPalette(const ChatThemeDescriptor &descriptor);
@ -167,11 +175,16 @@ private:
Animations::Simple _backgroundFade; Animations::Simple _backgroundFade;
CacheBackgroundRequest _backgroundCachingRequest; CacheBackgroundRequest _backgroundCachingRequest;
CacheBackgroundResult _backgroundNext; CacheBackgroundResult _backgroundNext;
QSize _willCacheForArea; QSize _cacheBackgroundArea;
crl::time _lastAreaChangeTime = 0; crl::time _lastBackgroundAreaChangeTime = 0;
std::optional<base::Timer> _cacheBackgroundTimer; std::optional<base::Timer> _cacheBackgroundTimer;
CachedBackground _bubblesBackground; CachedBackground _bubblesBackground;
QImage _bubblesBackgroundPrepared; QImage _bubblesBackgroundPrepared;
CacheBackgroundRequest _bubblesCachingRequest;
QSize _cacheBubblesArea;
crl::time _lastBubblesAreaChangeTime = 0;
std::optional<base::Timer> _cacheBubblesTimer;
std::unique_ptr<BubblePattern> _bubblesBackgroundPattern; std::unique_ptr<BubblePattern> _bubblesBackgroundPattern;
rpl::event_stream<> _repaintBackgroundRequests; rpl::event_stream<> _repaintBackgroundRequests;

View file

@ -93,13 +93,11 @@ void PaintPatternBubble(Painter &p, const SimpleBubble &args) {
const auto fillBg = [&](const QRect &rect) { const auto fillBg = [&](const QRect &rect) {
const auto fill = rect.intersected(args.patternViewport); const auto fill = rect.intersected(args.patternViewport);
if (!fill.isEmpty()) { if (!fill.isEmpty()) {
p.setClipRect(fill);
PaintPatternBubblePart( PaintPatternBubblePart(
p, p,
args.patternViewport, args.patternViewport,
pattern->pixmap, pattern->pixmap,
fill); fill);
p.setClipping(false);
} }
}; };
const auto fillSh = [&](const QRect &rect) { const auto fillSh = [&](const QRect &rect) {
@ -346,10 +344,30 @@ void PaintPatternBubblePart(
const QRect &viewport, const QRect &viewport,
const QPixmap &pixmap, const QPixmap &pixmap,
const QRect &target) { const QRect &target) {
// #TODO bubbles optimizes const auto factor = pixmap.devicePixelRatio();
const auto to = viewport; if (viewport.size() * factor == pixmap.size()) {
const auto from = QRect(QPoint(), pixmap.size()); const auto fill = target.intersected(viewport);
p.drawPixmap(to, pixmap, from); if (fill.isEmpty()) {
return;
}
p.drawPixmap(fill, pixmap, QRect(
(fill.topLeft() - viewport.topLeft()) * factor,
fill.size() * factor));
} else {
const auto to = viewport;
const auto from = QRect(QPoint(), pixmap.size());
const auto deviceRect = QRect(
QPoint(),
QSize(p.device()->width(), p.device()->height()));
const auto clip = (target != deviceRect);
if (clip) {
p.setClipRect(target);
}
p.drawPixmap(to, pixmap, from);
if (clip) {
p.setClipping(false);
}
}
} }
void PaintPatternBubblePart( void PaintPatternBubblePart(
@ -392,7 +410,9 @@ void PaintPatternBubblePart(
QImage &cache) { QImage &cache) {
Expects(paintContent != nullptr); Expects(paintContent != nullptr);
if (cache.size() != target.size() * style::DevicePixelRatio()) { const auto targetOrigin = target.topLeft();
const auto targetSize = target.size();
if (cache.size() != targetSize * style::DevicePixelRatio()) {
cache = QImage( cache = QImage(
target.size() * style::DevicePixelRatio(), target.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
@ -400,10 +420,15 @@ void PaintPatternBubblePart(
} }
cache.fill(Qt::transparent); cache.fill(Qt::transparent);
auto q = Painter(&cache); auto q = Painter(&cache);
q.translate(-target.topLeft()); q.translate(-targetOrigin);
paintContent(q); paintContent(q);
q.translate(targetOrigin);
q.setCompositionMode(QPainter::CompositionMode_SourceIn); q.setCompositionMode(QPainter::CompositionMode_SourceIn);
PaintPatternBubblePart(q, viewport, pixmap, target); PaintPatternBubblePart(
q,
viewport.translated(-targetOrigin),
pixmap,
QRect(QPoint(), targetSize));
q.end(); q.end();
p.drawImage(target, cache); p.drawImage(target, cache);