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

0001 /*
0002     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #ifndef KGAMERENDERER_P_H
0008 #define KGAMERENDERER_P_H
0009 
0010 // KF
0011 #include <KImageCache>
0012 // Qt
0013 #include <QHash>
0014 #include <QMetaType>
0015 #include <QMutex>
0016 #include <QRunnable>
0017 #include <QSvgRenderer>
0018 #include <QThreadPool>
0019 
0020 namespace KGRInternal
0021 {
0022 // Describes the state of a KGameRendererClient.
0023 struct ClientSpec {
0024     // The parentheses around QHash<QColor, QColor>() avoid compile
0025     // errors on platforms with older gcc versions, e.g. OS X 10.6.
0026     inline ClientSpec(const QString &spriteKey = QString(),
0027                       int frame = -1,
0028                       QSize size = QSize(),
0029                       const QHash<QColor, QColor> &customColors = (QHash<QColor, QColor>()));
0030     QString spriteKey;
0031     int frame;
0032     QSize size;
0033     QHash<QColor, QColor> customColors;
0034 };
0035 ClientSpec::ClientSpec(const QString &spriteKey_, int frame_, QSize size_, const QHash<QColor, QColor> &customColors_)
0036     : spriteKey(spriteKey_)
0037     , frame(frame_)
0038     , size(size_)
0039     , customColors(customColors_)
0040 {
0041 }
0042 
0043 // Instantiates QSvgRenderer instances from one SVG file for multiple threads.
0044 class RendererPool
0045 {
0046 public:
0047     // The renderer pool needs the thread pool instance of
0048     // KGameRendererPrivate to terminate workers when a new SVG is loaded.
0049     // WARNING Call this only from the main thread.
0050     inline RendererPool(QThreadPool *threadPool);
0051     inline ~RendererPool();
0052 
0053     // The second argument can be used to pass an instance which has been
0054     // used earlier to check the validity of the SVG file.
0055     inline void setPath(const QString &graphicsPath, QSvgRenderer *renderer = nullptr);
0056     // This can be used to determine whether a call to allocRenderer()
0057     // would need to create a new renderer instance.
0058     inline bool hasAvailableRenderers() const;
0059 
0060     // Returns a SVG renderer instance that can be used in the calling thread.
0061     inline QSvgRenderer *allocRenderer();
0062     // Marks this renderer as available for allocation by other threads.
0063     inline void freeRenderer(QSvgRenderer *renderer);
0064 
0065 private:
0066     QString m_path; // path to SVG file
0067     enum Validity { Checked_Invalid, Checked_Valid, Unchecked };
0068     Validity m_valid; // holds whether m_path points to a valid file
0069 
0070     mutable QMutex m_mutex;
0071     QThreadPool *m_threadPool;
0072     QHash<QSvgRenderer *, QThread *> m_hash;
0073 };
0074 
0075 // Describes a rendering job which is delegated to a worker thread.
0076 struct Job {
0077     KGRInternal::RendererPool *rendererPool;
0078     ClientSpec spec;
0079     QString cacheKey, elementKey;
0080     QImage result;
0081 };
0082 
0083 // Describes a worker thread.
0084 class Worker : public QRunnable
0085 {
0086 public:
0087     Worker(Job *job, bool isSynchronous, KGameRendererPrivate *parent);
0088 
0089     void run() override;
0090 
0091 private:
0092     Job *m_job;
0093     bool m_synchronous;
0094     KGameRendererPrivate *m_parent;
0095 };
0096 }
0097 
0098 Q_DECLARE_METATYPE(KGRInternal::Job *)
0099 
0100 class KGameRendererPrivate : public QObject
0101 {
0102     Q_OBJECT
0103 
0104 public:
0105     KGameRendererPrivate(KGameThemeProvider *provider, unsigned cacheSize, KGameRenderer *parent);
0106 
0107     void _k_setTheme(const KGameTheme *theme);
0108     bool setTheme(const KGameTheme *theme);
0109     inline QString spriteFrameKey(const QString &key, int frame, bool normalizeFrameNo = false) const;
0110     void requestPixmap(const KGRInternal::ClientSpec &spec, KGameRendererClient *client, QPixmap *synchronousResult = nullptr);
0111 
0112 private:
0113     inline void requestPixmap__propagateResult(const QPixmap &pixmap, KGameRendererClient *client, QPixmap *synchronousResult);
0114 public Q_SLOTS:
0115     void jobFinished(KGRInternal::Job *job, bool isSynchronous); // NOTE: This is invoked from KGRInternal::Worker::run.
0116 
0117 public:
0118     KGameRenderer *const m_parent;
0119 
0120     KGameThemeProvider *m_provider;
0121     const KGameTheme *m_currentTheme = nullptr; // will be loaded on first use
0122 
0123     QString m_frameSuffix, m_sizePrefix, m_frameCountPrefix, m_boundsPrefix;
0124     unsigned m_cacheSize;
0125     KGameRenderer::Strategies m_strategies = KGameRenderer::UseDiskCache | KGameRenderer::UseRenderingThreads;
0126     int m_frameBaseIndex = 0;
0127 
0128     QThreadPool m_workerPool;
0129     mutable KGRInternal::RendererPool m_rendererPool;
0130 
0131     QHash<KGameRendererClient *, QString> m_clients; // maps client -> cache key of current pixmap
0132     QStringList m_pendingRequests; // cache keys of pixmaps which are currently being rendered
0133 
0134     KImageCache *m_imageCache = nullptr;
0135 
0136     // In multi-threaded scenarios, there are two possible ways to use KIC's
0137     // pixmap cache.
0138     // 1. The worker renders a QImage and stores it in the cache. The main
0139     //    thread reads the QImage again and converts it into a QPixmap,
0140     //    storing it in the pixmap cache for later re-use.
0141     // i.e. QImage -> diskcache -> QImage -> QPixmap -> pixmapcache -> serve
0142     // 2. The worker renders a QImage and sends it directly to the main
0143     //    thread, which converts it to a QPixmap. The QPixmap is stored in
0144     //    KIC's pixmap cache, and converted to QImage to be written to the
0145     //    shared data cache.
0146     // i.e. QImage -> QPixmap -> pixmapcache -> serve
0147     //                       \-> QImage -> diskcache
0148     // We choose a third way:
0149     // 3. The worker renders a QImage which is converted to a QPixmap by the
0150     //    main thread. The main thread caches the QPixmap itself, and stores
0151     //    the QImage in the cache.
0152     // i.e. QImage -> QPixmap -> pixmapcache -> serve
0153     //            \-> diskcache
0154     // As you see, implementing an own pixmap cache saves us one conversion.
0155     // We therefore disable KIC's pixmap cache because we do not need it.
0156     QHash<QString, QPixmap> m_pixmapCache;
0157     mutable QHash<QString, int> m_frameCountCache;
0158     mutable QHash<QString, QRectF> m_boundsCache;
0159 };
0160 
0161 class KGameRendererClientPrivate : public QObject
0162 {
0163     Q_OBJECT
0164 
0165 public:
0166     KGameRendererClientPrivate(KGameRenderer *renderer, const QString &spriteKey, KGameRendererClient *parent);
0167 
0168 public Q_SLOTS:
0169     void fetchPixmap();
0170 
0171 public:
0172     KGameRendererClient *const m_parent;
0173 
0174     KGameRenderer *m_renderer;
0175 
0176     KGRInternal::ClientSpec m_spec;
0177 };
0178 
0179 #endif // KGAMERENDERER_P_H