mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
[VDS] Cache mana symbol renders and skip redundant resizes (#7167)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Fedora 43 (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 26 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [VDS] Cache mana symbol renders and skip redundant resizes - Render each mana symbol once at a bounded master size and derive every requested size from the cached master, avoiding repeated full-size SVG rasterization on the GUI thread - Share scaled results through a process-wide cache keyed by symbol and size, so repeated widget creation and rescales don't redo the work - Skip redundant resize work in ColorIdentityWidget and ManaSymbolWidget when sizes did not change Took 8 minutes Took 50 seconds * Move to pixmap generator Took 8 minutes Took 4 seconds --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
b13c682a7a
commit
24d8d8be3b
9 changed files with 127 additions and 56 deletions
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QImageReader>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QSvgRenderer>
|
#include <QSvgRenderer>
|
||||||
|
|
@ -418,6 +419,57 @@ QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||||
|
|
||||||
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// Longest side mana symbols are rendered at before being scaled to their final size.
|
||||||
|
constexpr int MASTER_ICON_SIZE = 128;
|
||||||
|
|
||||||
|
QString manaSymbolCacheKey(const QString &symbol, const QSize &size)
|
||||||
|
{
|
||||||
|
return symbol + QLatin1Char('|') + QString::number(size.width()) + QLatin1Char('x') +
|
||||||
|
QString::number(size.height());
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const QPixmap &ManaSymbolPixmapGenerator::masterIcon(const QString &symbol)
|
||||||
|
{
|
||||||
|
auto it = masterCache.constFind(symbol);
|
||||||
|
if (it != masterCache.constEnd()) {
|
||||||
|
return it.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageReader reader("theme:icons/mana/" + symbol);
|
||||||
|
QSize sourceSize = reader.size();
|
||||||
|
if (!sourceSize.isEmpty()) {
|
||||||
|
sourceSize.scale(QSize(MASTER_ICON_SIZE, MASTER_ICON_SIZE), Qt::KeepAspectRatio);
|
||||||
|
reader.setScaledSize(sourceSize);
|
||||||
|
}
|
||||||
|
const QPixmap rendered = QPixmap::fromImageReader(&reader);
|
||||||
|
|
||||||
|
return masterCache.insert(symbol, rendered).value();
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap ManaSymbolPixmapGenerator::generatePixmap(const QString &symbol, const QSize &size)
|
||||||
|
{
|
||||||
|
const QString key = manaSymbolCacheKey(symbol, size);
|
||||||
|
auto it = scaledCache.constFind(key);
|
||||||
|
if (it != scaledCache.constEnd()) {
|
||||||
|
return it.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QPixmap &icon = masterIcon(symbol);
|
||||||
|
if (icon.isNull()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap scaled = icon.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
scaledCache.insert(key, scaled);
|
||||||
|
return scaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<QString, QPixmap> ManaSymbolPixmapGenerator::masterCache;
|
||||||
|
QHash<QString, QPixmap> ManaSymbolPixmapGenerator::scaledCache;
|
||||||
|
|
||||||
QPixmap loadColorAdjustedPixmap(const QString &name)
|
QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||||
{
|
{
|
||||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#ifndef PIXMAPGENERATOR_H
|
#ifndef PIXMAPGENERATOR_H
|
||||||
#define PIXMAPGENERATOR_H
|
#define PIXMAPGENERATOR_H
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
@ -125,6 +126,34 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ManaSymbolPixmapGenerator
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static QHash<QString, QPixmap> masterCache;
|
||||||
|
static QHash<QString, QPixmap> scaledCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Renders \a symbol once at a fixed moderate size, so repeated scalings never
|
||||||
|
* re-rasterize the source file (SVG sources can be very expensive to rasterize).
|
||||||
|
*/
|
||||||
|
static const QPixmap &masterIcon(const QString &symbol);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Returns a smooth-scaled rendering of the given mana symbol icon.
|
||||||
|
*
|
||||||
|
* Results are shared between all callers via a process-wide cache keyed by symbol
|
||||||
|
* and size, so scaling work is done once per distinct combination instead of once
|
||||||
|
* per widget creation or resize.
|
||||||
|
*/
|
||||||
|
static QPixmap generatePixmap(const QString &symbol, const QSize &size);
|
||||||
|
static void clear()
|
||||||
|
{
|
||||||
|
masterCache.clear();
|
||||||
|
scaledCache.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
QPixmap loadColorAdjustedPixmap(const QString &name);
|
QPixmap loadColorAdjustedPixmap(const QString &name);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ void ColorIdentityWidget::populateManaSymbolWidgets()
|
||||||
// clear old layout
|
// clear old layout
|
||||||
QtUtils::clearLayoutRec(layout);
|
QtUtils::clearLayoutRec(layout);
|
||||||
|
|
||||||
|
// The freshly created symbols haven't been sized yet, so force the next resize pass
|
||||||
|
// to apply the symbol size again.
|
||||||
|
lastIconSize = -1;
|
||||||
|
lastWidth = -1;
|
||||||
|
|
||||||
// populate mana symbols
|
// populate mana symbols
|
||||||
if (SettingsCache::instance().visualDeckStorage().getVisualDeckStorageDrawUnusedColorIdentities()) {
|
if (SettingsCache::instance().visualDeckStorage().getVisualDeckStorageDrawUnusedColorIdentities()) {
|
||||||
for (const QString symbol : fullColorIdentity) {
|
for (const QString symbol : fullColorIdentity) {
|
||||||
|
|
@ -73,21 +78,35 @@ void ColorIdentityWidget::toggleUnusedVisibility()
|
||||||
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
|
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QWidget::resizeEvent(event);
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
// Layout passes resize this widget repeatedly with identical sizes, so bail out before
|
||||||
|
// touching the children when neither the width nor the resulting symbol size changed.
|
||||||
|
const int totalWidth = event->size().width();
|
||||||
|
if (totalWidth == lastWidth && lastIconSize != -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastWidth = totalWidth;
|
||||||
|
|
||||||
|
const int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
|
||||||
|
setFixedHeight(totalHeight);
|
||||||
|
|
||||||
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
||||||
|
if (manaSymbols.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!manaSymbols.isEmpty()) {
|
const int spacing = layout->spacing();
|
||||||
int totalWidth = event->size().width();
|
const int count = manaSymbols.size();
|
||||||
int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
|
const int availableWidth = totalWidth - (spacing * (count - 1));
|
||||||
setFixedHeight(totalHeight);
|
const int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
|
||||||
|
|
||||||
int spacing = layout->spacing();
|
if (iconSize == lastIconSize) {
|
||||||
int count = manaSymbols.size();
|
return;
|
||||||
int availableWidth = totalWidth - (spacing * (count - 1));
|
}
|
||||||
int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
|
lastIconSize = iconSize;
|
||||||
|
|
||||||
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
||||||
manaSymbol->setFixedSize(iconSize, iconSize);
|
manaSymbol->setFixedSize(iconSize, iconSize);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ public slots:
|
||||||
private:
|
private:
|
||||||
QString colorIdentity;
|
QString colorIdentity;
|
||||||
QHBoxLayout *layout;
|
QHBoxLayout *layout;
|
||||||
|
int lastIconSize = -1; ///< The symbol size last applied, to skip redundant resize passes.
|
||||||
|
int lastWidth = -1; ///< The width last processed, to skip redundant resize passes.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COLOR_IDENTITY_WIDGET_H
|
#endif // COLOR_IDENTITY_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
#include "mana_symbol_widget.h"
|
#include "mana_symbol_widget.h"
|
||||||
|
|
||||||
#include "../../../../client/settings/cache_settings.h"
|
#include "../../../../client/settings/cache_settings.h"
|
||||||
|
#include "../../../pixel_map_generator.h"
|
||||||
|
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
#include <libcockatrice/settings/visual_deck_storage_settings.h>
|
||||||
|
|
||||||
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
|
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
|
||||||
: QLabel(parent), symbol(_symbol), isActive(_isActive), mayBeToggled(_mayBeToggled)
|
: QLabel(parent), symbol(std::move(_symbol)), isActive(_isActive), mayBeToggled(_mayBeToggled)
|
||||||
{
|
{
|
||||||
loadManaIcon();
|
setPixmap(ManaSymbolPixmapGenerator::generatePixmap(symbol, QSize(50, 50)));
|
||||||
setPixmap(manaIcon.scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
||||||
setMaximumWidth(50);
|
setMaximumWidth(50);
|
||||||
|
|
||||||
// Initialize opacity effect
|
// Initialize opacity effect
|
||||||
|
|
@ -64,16 +64,13 @@ void ManaSymbolWidget::mousePressEvent(QMouseEvent *event)
|
||||||
void ManaSymbolWidget::resizeEvent(QResizeEvent *event)
|
void ManaSymbolWidget::resizeEvent(QResizeEvent *event)
|
||||||
{
|
{
|
||||||
QLabel::resizeEvent(event);
|
QLabel::resizeEvent(event);
|
||||||
setPixmap(manaIcon.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
const QSize newSize = event->size();
|
||||||
}
|
|
||||||
|
|
||||||
void ManaSymbolWidget::loadManaIcon()
|
// Skip the rescale when the size didn't actually change: layout passes resize these
|
||||||
{
|
// widgets repeatedly with identical sizes.
|
||||||
QString filename = "theme:icons/mana/";
|
if (newSize.isEmpty() || pixmap().size() == newSize) {
|
||||||
|
return;
|
||||||
if (symbol == "W" || symbol == "U" || symbol == "B" || symbol == "R" || symbol == "G") {
|
|
||||||
filename += symbol;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
manaIcon = QPixmap(filename);
|
setPixmap(ManaSymbolPixmapGenerator::generatePixmap(symbol, newSize));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,6 @@ public:
|
||||||
return symbol[0];
|
return symbol[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadManaIcon();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
|
@ -44,7 +42,6 @@ signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString symbol;
|
QString symbol;
|
||||||
QPixmap manaIcon;
|
|
||||||
bool isActive;
|
bool isActive;
|
||||||
bool mayBeToggled;
|
bool mayBeToggled;
|
||||||
QGraphicsOpacityEffect *opacityEffect;
|
QGraphicsOpacityEffect *opacityEffect;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "card_completer_delegate.h"
|
#include "card_completer_delegate.h"
|
||||||
|
|
||||||
|
#include "../../pixel_map_generator.h"
|
||||||
#include "../cards/additional_info/mana_cost_widget.h"
|
#include "../cards/additional_info/mana_cost_widget.h"
|
||||||
|
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
|
@ -84,7 +85,6 @@ QColor CardCompleterDelegate::accentForColors(const QString &colors)
|
||||||
|
|
||||||
CardCompleterDelegate::CardCompleterDelegate(QObject *parent) : QStyledItemDelegate(parent)
|
CardCompleterDelegate::CardCompleterDelegate(QObject *parent) : QStyledItemDelegate(parent)
|
||||||
{
|
{
|
||||||
symbolCache.setMaxCost(64);
|
|
||||||
setCodeCache.setMaxCost(64);
|
setCodeCache.setMaxCost(64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,36 +108,16 @@ QSize CardCompleterDelegate::sizeHint(const QStyleOptionViewItem &option, const
|
||||||
// Mana symbol painting
|
// Mana symbol painting
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const QPixmap *CardCompleterDelegate::cachedSymbolPixmap(const QString &symbol, int size) const
|
|
||||||
{
|
|
||||||
const QString key = symbol + QString::number(size);
|
|
||||||
|
|
||||||
if (symbolCache.contains(key)) {
|
|
||||||
return symbolCache[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
QPixmap src(QString("theme:icons/mana/%1").arg(symbol));
|
|
||||||
|
|
||||||
if (!src.isNull()) {
|
|
||||||
auto *pm = new QPixmap(src.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
||||||
|
|
||||||
symbolCache.insert(key, pm);
|
|
||||||
return pm;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QString &symbol, int radius) const
|
void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QString &symbol, int radius) const
|
||||||
{
|
{
|
||||||
const QRect pip(centre.x() - radius, centre.y() - radius, radius * 2, radius * 2);
|
const QRect pip(centre.x() - radius, centre.y() - radius, radius * 2, radius * 2);
|
||||||
|
|
||||||
const QPixmap *px = cachedSymbolPixmap(symbol, radius * 2);
|
const QPixmap px = ManaSymbolPixmapGenerator::generatePixmap(symbol, QSize(radius * 2, radius * 2));
|
||||||
|
|
||||||
if (px && !px->isNull()) {
|
if (!px.isNull()) {
|
||||||
p->drawPixmap(pip, *px);
|
p->drawPixmap(pip, px);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,6 @@ public:
|
||||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Mana symbol pixmaps, loaded once and cached
|
|
||||||
mutable QCache<QString, QPixmap> symbolCache;
|
|
||||||
|
|
||||||
// Set short codes, resolved once per card name and cached
|
// Set short codes, resolved once per card name and cached
|
||||||
mutable QCache<QString, QString> setCodeCache;
|
mutable QCache<QString, QString> setCodeCache;
|
||||||
|
|
||||||
|
|
@ -47,9 +44,6 @@ private:
|
||||||
// adventure costs ("1W // W") are drawn as separate groups. Returns the left-most x used
|
// adventure costs ("1W // W") are drawn as separate groups. Returns the left-most x used
|
||||||
int drawManaCost(QPainter *p, const QRect &row, const QString &manaCost, int radius) const;
|
int drawManaCost(QPainter *p, const QRect &row, const QString &manaCost, int radius) const;
|
||||||
|
|
||||||
// Load (or return cached) a mana icon pixmap; falls back to painted circle
|
|
||||||
const QPixmap *cachedSymbolPixmap(const QString &symbol, int size) const;
|
|
||||||
|
|
||||||
// Resolve the preferred printing's set short code for a card
|
// Resolve the preferred printing's set short code for a card
|
||||||
QString setCodeForCard(const QSharedPointer<CardInfo> &card) const;
|
QString setCodeForCard(const QSharedPointer<CardInfo> &card) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -390,6 +390,7 @@ int main(int argc, char *argv[])
|
||||||
PingPixmapGenerator::clear();
|
PingPixmapGenerator::clear();
|
||||||
CountryPixmapGenerator::clear();
|
CountryPixmapGenerator::clear();
|
||||||
UserLevelPixmapGenerator::clear();
|
UserLevelPixmapGenerator::clear();
|
||||||
|
ManaSymbolPixmapGenerator::clear();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue