From 312c36482fbc53a68e857774347099bab4f49705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Fri, 18 Oct 2024 07:59:53 +0200 Subject: [PATCH] Fixed performance issue when tinting tiles from large tilesets (#4080) Due to the tinting being applied to the entire tileset image, the 100 MB cache could easily be exhausted. This could result in the entire tileset image getting copied and tinted for each tile that is drawn. Now only the relevant sub-rect is tinted and cached, which avoids doing needless tinting and has a much better performance even when the cache is not large enough. --- NEWS.md | 1 + src/libtiled/maprenderer.cpp | 51 +++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index 68625454ef..b9471de80f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ * Fixed saving/loading of custom properties set on worlds (#4025) * Fixed issue with placing tile objects after switching maps (#3497) * Fixed crash when accessing a world through a symlink (#4042) +* Fixed performance issue when tinting tiles from large tilesets * Fixed error reporting when exporting on the command-line (by Shuhei Nagasawa, #4015) * Fixed minimum value of spinbox in Tile Animation Editor diff --git a/src/libtiled/maprenderer.cpp b/src/libtiled/maprenderer.cpp index acfa47e284..ba9763c9af 100644 --- a/src/libtiled/maprenderer.cpp +++ b/src/libtiled/maprenderer.cpp @@ -49,25 +49,33 @@ using namespace Tiled; struct TintedKey { - const qint64 key; + const qint64 pixmapKey; + const QRect rect; const QColor color; bool operator==(const TintedKey &o) const { - return key == o.key && color == o.color; + return pixmapKey == o.pixmapKey && + rect == o.rect && + color == o.color; } }; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) uint qHash(const TintedKey &key, uint seed) Q_DECL_NOTHROW -#else -size_t qHash(const TintedKey &key, size_t seed) Q_DECL_NOTHROW -#endif { - auto h = ::qHash(key.key, seed); + auto h = ::qHash(key.pixmapKey, seed); + h = ::qHash(key.rect.topLeft(), h); + h = ::qHash(key.rect.bottomRight(), h); h = ::qHash(key.color.rgba(), h); return h; } +#else +size_t qHash(const TintedKey &key, size_t seed) Q_DECL_NOTHROW +{ + return qHashMulti(seed, key.pixmapKey, key.rect, key.color.rgba()); +} +#endif // Borrowed from qpixmapcache.cpp static inline qsizetype cost(const QPixmap &pixmap) @@ -80,19 +88,25 @@ static inline qsizetype cost(const QPixmap &pixmap) return static_cast(qBound(1LL, costKb, costMax)); } -static QPixmap tinted(const QPixmap &pixmap, const QColor &color) +static bool needsTint(const QColor &color) +{ + return color.isValid() && + color != QColor(255, 255, 255, 255); +} + +static QPixmap tinted(const QPixmap &pixmap, const QRect &rect, const QColor &color) { - if (!color.isValid() || color == QColor(255, 255, 255, 255) || pixmap.isNull()) + if (pixmap.isNull() || !needsTint(color)) return pixmap; // Cache for up to 100 MB of tinted pixmaps, since tinting is expensive static QCache cache { 100 * 1024 }; - const TintedKey tintedKey { pixmap.cacheKey(), color }; + const TintedKey tintedKey { pixmap.cacheKey(), rect, color }; if (auto cached = cache.object(tintedKey)) return *cached; - QPixmap resultImage = pixmap; + QPixmap resultImage = pixmap.copy(rect); QPainter painter(&resultImage); QColor fullOpacity = color; @@ -104,7 +118,7 @@ static QPixmap tinted(const QPixmap &pixmap, const QColor &color) // apply the original alpha to the final image painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); - painter.drawPixmap(0, 0, pixmap); + painter.drawPixmap(resultImage.rect(), pixmap, rect); // apply the alpha of the tint color so that we can use it to make the image // transparent instead of just increasing or decreasing the tint effect @@ -171,7 +185,9 @@ void MapRenderer::drawImageLayer(QPainter *painter, const QRectF &exposed) const { painter->save(); - painter->setBrush(tinted(imageLayer->image(), imageLayer->effectiveTintColor())); + painter->setBrush(tinted(imageLayer->image(), + imageLayer->image().rect(), + imageLayer->effectiveTintColor())); painter->setPen(Qt::NoPen); if (exposed.isNull()) painter->drawRect(boundingRect(imageLayer)); @@ -431,10 +447,13 @@ void CellRenderer::render(const Cell &cell, const QPointF &screenPos, const QSiz flush(); const QPixmap &image = tile->image(); - const QRect imageRect = tile->imageRect(); + QRect imageRect = tile->imageRect(); if (imageRect.isEmpty()) return; + if (needsTint(mTintColor)) + imageRect.moveTopLeft(QPoint(0, 0)); + const QPoint offset = tile->offset(); const QPointF sizeHalf { size.width() / 2, size.height() / 2 }; @@ -520,7 +539,7 @@ void CellRenderer::render(const Cell &cell, const QPointF &screenPos, const QSiz fragment.width, fragment.height); mPainter->setTransform(transform); - mPainter->drawPixmap(target, tinted(image, mTintColor), source); + mPainter->drawPixmap(target, tinted(image, tile->imageRect(), mTintColor), source); mPainter->setTransform(oldTransform); // A bit of a hack to still draw tile collision shapes when requested @@ -545,7 +564,9 @@ void CellRenderer::flush() mPainter->drawPixmapFragments(mFragments.constData(), mFragments.size(), - tinted(mTile->image(), mTintColor)); + tinted(mTile->image(), + mTile->imageRect(), + mTintColor)); if (mRenderer->flags().testFlag(ShowTileCollisionShapes) && mTile->objectGroup()