File indexing completed on 2024-04-28 04:05:03

0001 /*
0002     SPDX-FileCopyrightText: 2010-2012 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "kgamerenderer.h"
0008 #include "kgamerenderer_p.h"
0009 
0010 // own
0011 #include "colorproxy_p.h"
0012 #include "kgamerendererclient.h"
0013 #include "kgametheme.h"
0014 #include "kgamethemeprovider.h"
0015 #include <kdegames_logging.h>
0016 // Qt
0017 #include <QDateTime>
0018 #include <QFileInfo>
0019 #include <QGuiApplication>
0020 #include <QPainter>
0021 #include <QVariant>
0022 
0023 // TODO: automatically schedule pre-rendering of animation frames
0024 // TODO: multithreaded SVG loading?
0025 
0026 static const QString cacheName(QByteArray theme)
0027 {
0028     const QString appName = QCoreApplication::instance()->applicationName();
0029     // e.g. "themes/foobar.desktop" -> "themes/foobar"
0030     if (theme.endsWith(QByteArrayLiteral(".desktop")))
0031         theme.chop(8); // 8 = strlen(".desktop")
0032     return QStringLiteral("kgamerenderer-%1-%2").arg(appName, QString::fromUtf8(theme));
0033 }
0034 
0035 KGameRendererPrivate::KGameRendererPrivate(KGameThemeProvider *provider, unsigned cacheSize, KGameRenderer *parent)
0036     : m_parent(parent)
0037     , m_provider(provider)
0038     , m_frameSuffix(QStringLiteral("_%1"))
0039     , m_sizePrefix(QStringLiteral("%1-%2-"))
0040     , m_frameCountPrefix(QStringLiteral("fc-"))
0041     , m_boundsPrefix(QStringLiteral("br-"))
0042     // default cache size: 3 MiB = 3 << 20 bytes
0043     , m_cacheSize((cacheSize == 0 ? 3 : cacheSize) << 20)
0044     , m_rendererPool(&m_workerPool)
0045 {
0046     qRegisterMetaType<KGRInternal::Job *>();
0047 }
0048 
0049 KGameRenderer::KGameRenderer(KGameThemeProvider *provider, unsigned cacheSize)
0050     : d_ptr(new KGameRendererPrivate(provider, cacheSize, this))
0051 {
0052     if (!provider->parent()) {
0053         provider->setParent(this);
0054     }
0055     connect(provider, &KGameThemeProvider::currentThemeChanged, this, [this](const KGameTheme *theme) {
0056         Q_D(KGameRenderer);
0057         d->_k_setTheme(theme);
0058     });
0059 }
0060 
0061 static KGameThemeProvider *providerForSingleTheme(KGameTheme *theme, QObject *parent)
0062 {
0063     KGameThemeProvider *prov = new KGameThemeProvider(QByteArray(), parent);
0064     prov->addTheme(theme);
0065     return prov;
0066 }
0067 
0068 KGameRenderer::KGameRenderer(KGameTheme *theme, unsigned cacheSize)
0069     : d_ptr(new KGameRendererPrivate(providerForSingleTheme(theme, this), cacheSize, this))
0070 {
0071 }
0072 
0073 KGameRenderer::~KGameRenderer()
0074 {
0075     Q_D(KGameRenderer);
0076 
0077     // cleanup clients
0078     while (!d->m_clients.isEmpty()) {
0079         delete d->m_clients.constBegin().key();
0080     }
0081     // cleanup own stuff
0082     d->m_workerPool.waitForDone();
0083     delete d->m_imageCache;
0084 }
0085 
0086 int KGameRenderer::frameBaseIndex() const
0087 {
0088     Q_D(const KGameRenderer);
0089 
0090     return d->m_frameBaseIndex;
0091 }
0092 
0093 void KGameRenderer::setFrameBaseIndex(int frameBaseIndex)
0094 {
0095     Q_D(KGameRenderer);
0096 
0097     d->m_frameBaseIndex = frameBaseIndex;
0098 }
0099 
0100 QString KGameRenderer::frameSuffix() const
0101 {
0102     Q_D(const KGameRenderer);
0103 
0104     return d->m_frameSuffix;
0105 }
0106 
0107 void KGameRenderer::setFrameSuffix(const QString &suffix)
0108 {
0109     Q_D(KGameRenderer);
0110 
0111     d->m_frameSuffix = suffix.contains(QLatin1String("%1")) ? suffix : QStringLiteral("_%1");
0112 }
0113 
0114 KGameRenderer::Strategies KGameRenderer::strategies() const
0115 {
0116     Q_D(const KGameRenderer);
0117 
0118     return d->m_strategies;
0119 }
0120 
0121 void KGameRenderer::setStrategyEnabled(KGameRenderer::Strategy strategy, bool enabled)
0122 {
0123     Q_D(KGameRenderer);
0124 
0125     const bool oldEnabled = d->m_strategies & strategy;
0126     if (enabled) {
0127         d->m_strategies |= strategy;
0128     } else {
0129         d->m_strategies &= ~strategy;
0130     }
0131     if (strategy == KGameRenderer::UseDiskCache && oldEnabled != enabled) {
0132         // reload theme
0133         const KGameTheme *theme = d->m_currentTheme;
0134         if (theme) {
0135             d->m_currentTheme = nullptr; // or setTheme() will return immediately
0136             d->_k_setTheme(theme);
0137         }
0138     }
0139 }
0140 
0141 void KGameRendererPrivate::_k_setTheme(const KGameTheme *theme)
0142 {
0143     const KGameTheme *oldTheme = m_currentTheme;
0144     if (oldTheme == theme) {
0145         return;
0146     }
0147     qCDebug(KDEGAMES_LOG) << "Setting theme:" << theme->name();
0148     if (!setTheme(theme)) {
0149         const KGameTheme *defaultTheme = m_provider->defaultTheme();
0150         if (theme != defaultTheme) {
0151             qCDebug(KDEGAMES_LOG) << "Falling back to default theme:" << defaultTheme->name();
0152             setTheme(defaultTheme);
0153             m_provider->setCurrentTheme(defaultTheme);
0154         }
0155     }
0156     // announce change to KGameRendererClients
0157     QHash<KGameRendererClient *, QString>::iterator it1 = m_clients.begin(), it2 = m_clients.end();
0158     for (; it1 != it2; ++it1) {
0159         it1.value().clear(); // because the pixmap is outdated
0160         it1.key()->d_ptr->fetchPixmap();
0161     }
0162     Q_EMIT m_parent->themeChanged(m_currentTheme);
0163 }
0164 
0165 bool KGameRendererPrivate::setTheme(const KGameTheme *theme)
0166 {
0167     if (!theme) {
0168         return false;
0169     }
0170     // open cache (and SVG file, if necessary)
0171     if (m_strategies & KGameRenderer::UseDiskCache) {
0172         std::unique_ptr<KImageCache> oldCache(m_imageCache);
0173         const QString imageCacheName = cacheName(theme->identifier());
0174         m_imageCache = new KImageCache(imageCacheName, m_cacheSize);
0175         m_imageCache->setPixmapCaching(false); // see big comment in KGRPrivate class declaration
0176         // check timestamp of cache vs. last write access to theme/SVG
0177         const uint svgTimestamp =
0178             qMax(QFileInfo(theme->graphicsPath()).lastModified().toSecsSinceEpoch(), theme->property("_k_themeDescTimestamp").value<qint64>());
0179         QByteArray buffer;
0180         if (!m_imageCache->find(QStringLiteral("kgr_timestamp"), &buffer))
0181             buffer = "0";
0182         const uint cacheTimestamp = buffer.toInt();
0183         // try to instantiate renderer immediately if the cache does not exist or is outdated
0184         // FIXME: This logic breaks if the cache evicts the "kgr_timestamp" key. We need additional API in KSharedDataCache to make sure that this key does not
0185         // get evicted.
0186         if (cacheTimestamp < svgTimestamp) {
0187             qCDebug(KDEGAMES_LOG) << "Theme newer than cache, checking SVG";
0188             std::unique_ptr<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
0189             if (renderer->isValid()) {
0190                 m_rendererPool.setPath(theme->graphicsPath(), renderer.release());
0191                 m_imageCache->clear();
0192                 m_imageCache->insert(QStringLiteral("kgr_timestamp"), QByteArray::number(svgTimestamp));
0193             } else {
0194                 // The SVG file is broken, so we deny to change the theme without
0195                 // breaking the previous theme.
0196                 delete m_imageCache;
0197                 KSharedDataCache::deleteCache(imageCacheName);
0198                 m_imageCache = oldCache.release();
0199                 qCDebug(KDEGAMES_LOG) << "Theme change failed: SVG file broken";
0200                 return false;
0201             }
0202         }
0203         // theme is cached - just delete the old renderer after making sure that no worker threads are using it anymore
0204         else if (m_currentTheme != theme)
0205             m_rendererPool.setPath(theme->graphicsPath());
0206     } else // !(m_strategies & KGameRenderer::UseDiskCache) -> no cache is used
0207     {
0208         // load SVG file
0209         std::unique_ptr<QSvgRenderer> renderer(new QSvgRenderer(theme->graphicsPath()));
0210         if (renderer->isValid()) {
0211             m_rendererPool.setPath(theme->graphicsPath(), renderer.release());
0212         } else {
0213             qCDebug(KDEGAMES_LOG) << "Theme change failed: SVG file broken";
0214             return false;
0215         }
0216         // disconnect from disk cache (only needed if changing strategy)
0217         delete m_imageCache;
0218         m_imageCache = nullptr;
0219     }
0220     // clear in-process caches
0221     m_pixmapCache.clear();
0222     m_frameCountCache.clear();
0223     m_boundsCache.clear();
0224     // done
0225     m_currentTheme = theme;
0226     return true;
0227 }
0228 
0229 const KGameTheme *KGameRenderer::theme() const
0230 {
0231     Q_D(const KGameRenderer);
0232 
0233     // ensure that some theme is loaded
0234     if (!d->m_currentTheme) {
0235         const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
0236     }
0237     return d->m_currentTheme;
0238 }
0239 
0240 KGameThemeProvider *KGameRenderer::themeProvider() const
0241 {
0242     Q_D(const KGameRenderer);
0243 
0244     return d->m_provider;
0245 }
0246 
0247 QString KGameRendererPrivate::spriteFrameKey(const QString &key, int frame, bool normalizeFrameNo) const
0248 {
0249     // fast path for non-animated sprites
0250     if (frame < 0) {
0251         return key;
0252     }
0253     // normalize frame number
0254     if (normalizeFrameNo) {
0255         const int frameCount = m_parent->frameCount(key);
0256         if (frameCount <= 0) {
0257             // non-animated sprite
0258             return key;
0259         } else {
0260             frame = (frame - m_frameBaseIndex) % frameCount + m_frameBaseIndex;
0261         }
0262     }
0263     return key + m_frameSuffix.arg(frame);
0264 }
0265 
0266 int KGameRenderer::frameCount(const QString &key) const
0267 {
0268     Q_D(const KGameRenderer);
0269 
0270     // ensure that some theme is loaded
0271     if (!d->m_currentTheme) {
0272         const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
0273     }
0274     // look up in in-process cache
0275     QHash<QString, int>::const_iterator it = d->m_frameCountCache.constFind(key);
0276     if (it != d->m_frameCountCache.constEnd()) {
0277         return it.value();
0278     }
0279     // look up in shared cache (if SVG is not yet loaded)
0280     int count = -1;
0281     bool countFound = false;
0282     const QString cacheKey = d->m_frameCountPrefix + key;
0283     if (d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) {
0284         QByteArray buffer;
0285         if (d->m_imageCache->find(cacheKey, &buffer)) {
0286             count = buffer.toInt();
0287             countFound = true;
0288         }
0289     }
0290     // determine from SVG
0291     if (!countFound) {
0292         QSvgRenderer *renderer = d->m_rendererPool.allocRenderer();
0293         // look for animated sprite first
0294         count = d->m_frameBaseIndex;
0295         while (renderer->elementExists(d->spriteFrameKey(key, count, false))) {
0296             ++count;
0297         }
0298         count -= d->m_frameBaseIndex;
0299         // look for non-animated sprite instead
0300         if (count == 0) {
0301             if (!renderer->elementExists(key)) {
0302                 count = -1;
0303             }
0304         }
0305         d->m_rendererPool.freeRenderer(renderer);
0306         // save in shared cache for following requests
0307         if (d->m_strategies & KGameRenderer::UseDiskCache) {
0308             d->m_imageCache->insert(cacheKey, QByteArray::number(count));
0309         }
0310     }
0311     d->m_frameCountCache.insert(key, count);
0312     return count;
0313 }
0314 
0315 QRectF KGameRenderer::boundsOnSprite(const QString &key, int frame) const
0316 {
0317     Q_D(const KGameRenderer);
0318 
0319     const QString elementKey = d->spriteFrameKey(key, frame);
0320     // ensure that some theme is loaded
0321     if (!d->m_currentTheme) {
0322         const_cast<KGameRendererPrivate *>(d)->_k_setTheme(d->m_provider->currentTheme());
0323     }
0324     // look up in in-process cache
0325     QHash<QString, QRectF>::const_iterator it = d->m_boundsCache.constFind(elementKey);
0326     if (it != d->m_boundsCache.constEnd()) {
0327         return it.value();
0328     }
0329     // look up in shared cache (if SVG is not yet loaded)
0330     QRectF bounds;
0331     bool boundsFound = false;
0332     const QString cacheKey = d->m_boundsPrefix + elementKey;
0333     if (!d->m_rendererPool.hasAvailableRenderers() && (d->m_strategies & KGameRenderer::UseDiskCache)) {
0334         QByteArray buffer;
0335         if (d->m_imageCache->find(cacheKey, &buffer)) {
0336             QDataStream stream(buffer);
0337             stream >> bounds;
0338             boundsFound = true;
0339         }
0340     }
0341     // determine from SVG
0342     if (!boundsFound) {
0343         QSvgRenderer *renderer = d->m_rendererPool.allocRenderer();
0344         bounds = renderer->boundsOnElement(elementKey);
0345         d->m_rendererPool.freeRenderer(renderer);
0346         // save in shared cache for following requests
0347         if (d->m_strategies & KGameRenderer::UseDiskCache) {
0348             QByteArray buffer;
0349             {
0350                 QDataStream stream(&buffer, QIODevice::WriteOnly);
0351                 stream << bounds;
0352             }
0353             d->m_imageCache->insert(cacheKey, buffer);
0354         }
0355     }
0356     d->m_boundsCache.insert(elementKey, bounds);
0357     return bounds;
0358 }
0359 
0360 bool KGameRenderer::spriteExists(const QString &key) const
0361 {
0362     Q_D(const KGameRenderer);
0363 
0364     return this->frameCount(key) >= 0;
0365 }
0366 
0367 QPixmap KGameRenderer::spritePixmap(const QString &key, QSize size, int frame, const QHash<QColor, QColor> &customColors) const
0368 {
0369     Q_D(const KGameRenderer);
0370 
0371     QPixmap result;
0372     const_cast<KGameRendererPrivate *>(d)->requestPixmap(KGRInternal::ClientSpec(key, frame, size, customColors), nullptr, &result);
0373     return result;
0374 }
0375 
0376 // Helper function for KGameRendererPrivate::requestPixmap.
0377 void KGameRendererPrivate::requestPixmap__propagateResult(const QPixmap &pixmap, KGameRendererClient *client, QPixmap *synchronousResult)
0378 {
0379     if (client) {
0380         client->receivePixmap(pixmap);
0381     }
0382     if (synchronousResult) {
0383         *synchronousResult = pixmap;
0384     }
0385 }
0386 
0387 void KGameRendererPrivate::requestPixmap(const KGRInternal::ClientSpec &spec, KGameRendererClient *client, QPixmap *synchronousResult)
0388 {
0389     // NOTE: If client == 0, the request is synchronous and must be finished when this method returns. This behavior is used by KGR::spritePixmap(). Instead of
0390     // KGameRendererClient::receivePixmap, the QPixmap* argument is then used to return the result. parse request
0391     if (spec.size.isEmpty()) {
0392         requestPixmap__propagateResult(QPixmap(), client, synchronousResult);
0393         return;
0394     }
0395     const QString elementKey = spriteFrameKey(spec.spriteKey, spec.frame);
0396     const qreal dpr = qApp->devicePixelRatio();
0397     const QSize size = spec.size * dpr;
0398     QString cacheKey = m_sizePrefix.arg(size.width()).arg(size.height()) + elementKey;
0399     QHash<QColor, QColor>::const_iterator it1 = spec.customColors.constBegin(), it2 = spec.customColors.constEnd();
0400     static const QString colorSuffix(QStringLiteral("-%1-%2"));
0401     for (; it1 != it2; ++it1) {
0402         cacheKey += colorSuffix.arg(it1.key().rgba()).arg(it1.value().rgba());
0403     }
0404     // check if update is needed
0405     if (client) {
0406         if (m_clients.value(client) == cacheKey) {
0407             return;
0408         }
0409         m_clients[client] = cacheKey;
0410     }
0411     // ensure that some theme is loaded
0412     if (!m_currentTheme) {
0413         _k_setTheme(m_provider->currentTheme());
0414     }
0415     // try to serve from high-speed cache
0416     QHash<QString, QPixmap>::const_iterator it = m_pixmapCache.constFind(cacheKey);
0417     if (it != m_pixmapCache.constEnd()) {
0418         QPixmap p = it.value();
0419         p.setDevicePixelRatio(dpr);
0420         requestPixmap__propagateResult(p, client, synchronousResult);
0421         return;
0422     }
0423     // try to serve from low-speed cache
0424     if (m_strategies & KGameRenderer::UseDiskCache) {
0425         QPixmap pix;
0426         if (m_imageCache->findPixmap(cacheKey, &pix)) {
0427             pix.setDevicePixelRatio(dpr);
0428             m_pixmapCache.insert(cacheKey, pix);
0429             requestPixmap__propagateResult(pix, client, synchronousResult);
0430             return;
0431         }
0432     }
0433     // if asynchronous request, is such a rendering job already running?
0434     if (client && m_pendingRequests.contains(cacheKey)) {
0435         return;
0436     }
0437     // create job
0438     KGRInternal::Job *job = new KGRInternal::Job;
0439     job->rendererPool = &m_rendererPool;
0440     job->cacheKey = cacheKey;
0441     job->elementKey = elementKey;
0442     job->spec = spec;
0443     job->spec.size = size;
0444     const bool synchronous = !client;
0445     if (synchronous || !(m_strategies & KGameRenderer::UseRenderingThreads)) {
0446         KGRInternal::Worker worker(job, true, this);
0447         worker.run();
0448         // if everything worked fine, result is in high-speed cache now
0449         const QPixmap result = m_pixmapCache.value(cacheKey);
0450         requestPixmap__propagateResult(result, client, synchronousResult);
0451     } else {
0452         m_workerPool.start(new KGRInternal::Worker(job, !client, this));
0453         m_pendingRequests << cacheKey;
0454     }
0455 }
0456 
0457 void KGameRendererPrivate::jobFinished(KGRInternal::Job *job, bool isSynchronous)
0458 {
0459     // read job
0460     const qreal dpr = qApp->devicePixelRatio();
0461     const QString cacheKey = job->cacheKey;
0462     QImage result = job->result;
0463     result.setDevicePixelRatio(dpr);
0464     delete job;
0465     // check who wanted this pixmap
0466     m_pendingRequests.removeAll(cacheKey);
0467     const QList<KGameRendererClient *> requesters = m_clients.keys(cacheKey);
0468     // put result into image cache
0469     if (m_strategies & KGameRenderer::UseDiskCache) {
0470         m_imageCache->insertImage(cacheKey, result);
0471         // convert result to pixmap (and put into pixmap cache) only if it is needed now
0472         // This optimization saves the image-pixmap conversion for intermediate sizes which occur during smooth resize events or window initializations.
0473         if (!isSynchronous && requesters.isEmpty()) {
0474             return;
0475         }
0476     }
0477     const QPixmap pixmap = QPixmap::fromImage(result);
0478     m_pixmapCache.insert(cacheKey, pixmap);
0479     for (KGameRendererClient *requester : requesters) {
0480         requester->receivePixmap(pixmap);
0481     }
0482 }
0483 
0484 // BEGIN KGRInternal::Job/Worker
0485 
0486 KGRInternal::Worker::Worker(KGRInternal::Job *job, bool isSynchronous, KGameRendererPrivate *parent)
0487     : m_job(job)
0488     , m_synchronous(isSynchronous)
0489     , m_parent(parent)
0490 {
0491 }
0492 
0493 static const uint transparentRgba = QColor(Qt::transparent).rgba();
0494 
0495 void KGRInternal::Worker::run()
0496 {
0497     QImage image(m_job->spec.size, QImage::Format_ARGB32_Premultiplied);
0498     // image might not be created if size is too large for needed memory, or invalid
0499     if (image.isNull()) {
0500         qCWarning(KDEGAMES_LOG) << "Could not create QImage. Key:" << m_job->spec.spriteKey << "Frame:" << m_job->spec.frame << "Size:" << m_job->spec.size;
0501     } else {
0502         image.fill(transparentRgba);
0503         QPainter *painter = nullptr;
0504         QPaintDeviceColorProxy *proxy = nullptr;
0505         // if no custom colors requested, paint directly onto image
0506         if (m_job->spec.customColors.isEmpty()) {
0507             painter = new QPainter(&image);
0508         } else {
0509             proxy = new QPaintDeviceColorProxy(&image, m_job->spec.customColors);
0510             painter = new QPainter(proxy);
0511         }
0512 
0513         // do renderering
0514         QSvgRenderer *renderer = m_job->rendererPool->allocRenderer();
0515         renderer->render(painter, m_job->elementKey);
0516         m_job->rendererPool->freeRenderer(renderer);
0517         delete painter;
0518         delete proxy;
0519     }
0520     // talk back to the main thread
0521     m_job->result = image;
0522     QMetaObject::invokeMethod(m_parent, "jobFinished", Qt::AutoConnection, Q_ARG(KGRInternal::Job *, m_job), Q_ARG(bool, m_synchronous));
0523     // NOTE: KGR::spritePixmap relies on Qt::DirectConnection when this method is run in the main thread.
0524 }
0525 
0526 // END KGRInternal::Job/Worker
0527 
0528 // BEGIN KGRInternal::RendererPool
0529 
0530 KGRInternal::RendererPool::RendererPool(QThreadPool *threadPool)
0531     : m_valid(Checked_Invalid) // don't try to allocate renderers until given a valid SVG file
0532     , m_threadPool(threadPool)
0533 {
0534 }
0535 
0536 KGRInternal::RendererPool::~RendererPool()
0537 {
0538     // This deletes all renderers.
0539     setPath(QString());
0540 }
0541 
0542 void KGRInternal::RendererPool::setPath(const QString &graphicsPath, QSvgRenderer *renderer)
0543 {
0544     QMutexLocker locker(&m_mutex);
0545     // delete all renderers
0546     m_threadPool->waitForDone();
0547     QHash<QSvgRenderer *, QThread *>::const_iterator it1 = m_hash.constBegin(), it2 = m_hash.constEnd();
0548     for (; it1 != it2; ++it1) {
0549         Q_ASSERT(it1.value() == nullptr); // nobody may be using our renderers anymore now
0550         delete it1.key();
0551     }
0552     m_hash.clear();
0553     // set path
0554     m_path = graphicsPath;
0555     // existence of a renderer instance is evidence for the validity of the SVG file
0556     if (renderer) {
0557         m_valid = Checked_Valid;
0558         m_hash.insert(renderer, nullptr);
0559     } else {
0560         m_valid = Unchecked;
0561     }
0562 }
0563 
0564 bool KGRInternal::RendererPool::hasAvailableRenderers() const
0565 {
0566     // look for a renderer which is not associated with a thread
0567     QMutexLocker locker(&m_mutex);
0568     return m_hash.key(nullptr) != nullptr;
0569 }
0570 
0571 QSvgRenderer *KGRInternal::RendererPool::allocRenderer()
0572 {
0573     QThread *thread = QThread::currentThread();
0574     // look for an available renderer
0575     QMutexLocker locker(&m_mutex);
0576     QSvgRenderer *renderer = m_hash.key(nullptr);
0577     if (!renderer) {
0578         // instantiate a new renderer (only if the SVG file has not been found to be invalid yet)
0579         if (m_valid == Checked_Invalid) {
0580             return nullptr;
0581         }
0582         renderer = new QSvgRenderer(m_path);
0583         m_valid = renderer->isValid() ? Checked_Valid : Checked_Invalid;
0584     }
0585     // mark renderer as used
0586     m_hash.insert(renderer, thread);
0587     return renderer;
0588 }
0589 
0590 void KGRInternal::RendererPool::freeRenderer(QSvgRenderer *renderer)
0591 {
0592     // mark renderer as available
0593     QMutexLocker locker(&m_mutex);
0594     m_hash.insert(renderer, nullptr);
0595 }
0596 
0597 // END KGRInternal::RendererPool
0598 
0599 #include "moc_kgamerenderer.cpp"
0600 #include "moc_kgamerenderer_p.cpp"