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

View file

@ -147,12 +147,20 @@ private:
const CacheBackgroundRequest &request,
Fn<void(CacheBackgroundResult&&)> done = nullptr);
void setCachedBackground(CacheBackgroundResult &&cached);
[[nodiscard]] CacheBackgroundRequest currentCacheRequest(
[[nodiscard]] CacheBackgroundRequest cacheBackgroundRequest(
QSize area,
int addRotation = 0) const;
[[nodiscard]] bool readyForBackgroundRotation() const;
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(
const QColor &accent) const;
void adjustPalette(const ChatThemeDescriptor &descriptor);
@ -167,11 +175,16 @@ private:
Animations::Simple _backgroundFade;
CacheBackgroundRequest _backgroundCachingRequest;
CacheBackgroundResult _backgroundNext;
QSize _willCacheForArea;
crl::time _lastAreaChangeTime = 0;
QSize _cacheBackgroundArea;
crl::time _lastBackgroundAreaChangeTime = 0;
std::optional<base::Timer> _cacheBackgroundTimer;
CachedBackground _bubblesBackground;
QImage _bubblesBackgroundPrepared;
CacheBackgroundRequest _bubblesCachingRequest;
QSize _cacheBubblesArea;
crl::time _lastBubblesAreaChangeTime = 0;
std::optional<base::Timer> _cacheBubblesTimer;
std::unique_ptr<BubblePattern> _bubblesBackgroundPattern;
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 fill = rect.intersected(args.patternViewport);
if (!fill.isEmpty()) {
p.setClipRect(fill);
PaintPatternBubblePart(
p,
args.patternViewport,
pattern->pixmap,
fill);
p.setClipping(false);
}
};
const auto fillSh = [&](const QRect &rect) {
@ -346,10 +344,30 @@ void PaintPatternBubblePart(
const QRect &viewport,
const QPixmap &pixmap,
const QRect &target) {
// #TODO bubbles optimizes
const auto to = viewport;
const auto from = QRect(QPoint(), pixmap.size());
p.drawPixmap(to, pixmap, from);
const auto factor = pixmap.devicePixelRatio();
if (viewport.size() * factor == pixmap.size()) {
const auto fill = target.intersected(viewport);
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(
@ -392,7 +410,9 @@ void PaintPatternBubblePart(
QImage &cache) {
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(
target.size() * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
@ -400,10 +420,15 @@ void PaintPatternBubblePart(
}
cache.fill(Qt::transparent);
auto q = Painter(&cache);
q.translate(-target.topLeft());
q.translate(-targetOrigin);
paintContent(q);
q.translate(targetOrigin);
q.setCompositionMode(QPainter::CompositionMode_SourceIn);
PaintPatternBubblePart(q, viewport, pixmap, target);
PaintPatternBubblePart(
q,
viewport.translated(-targetOrigin),
pixmap,
QRect(QPoint(), targetSize));
q.end();
p.drawImage(target, cache);