File indexing completed on 2024-05-12 08:02:12
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"