[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-panel-devel
Subject:    Re: [Panel-devel] svg rendering
From:       Sean Harmer <sh () theharmers ! co ! uk>
Date:       2007-10-30 12:00:57
Message-ID: 200710301200.58533.sh () theharmers ! co ! uk
[Download RAW message or body]

On Tuesday 30 October 2007 11:43:43 Sean Harmer wrote:
> I've attached the current state of my patch. It still needs some
> tidying up and I need to check the visibility of certain classes etc but it
> doesn't crash now and you do actually get to see some rendered SVGs - apart
> from those with fonts in them ;-)
Ignroe my previous patch. I forgot to svn add some new files. Here is the 
correct patch.

Sean

["svg-threading.patch" (text/x-diff)]

Index: workspace/libs/plasma/svg.cpp
===================================================================
--- workspace/libs/plasma/svg.cpp	(revision 730999)
+++ workspace/libs/plasma/svg.cpp	(working copy)
@@ -24,45 +24,30 @@
 #include <QPainter>
 #include <QPixmapCache>
 #include <QSharedData>
+#include <QStyleOptionGraphicsItem>
 
 #include <KDebug>
 #include <KSharedPtr>
 #include <KSvgRenderer>
 
+#include "svgrendererthread.h"
 #include "theme.h"
 
 namespace Plasma
 {
 
-class SharedSvgRenderer : public KSvgRenderer, public QSharedData
-{
-    public:
-        typedef KSharedPtr<SharedSvgRenderer> Ptr;
-
-        SharedSvgRenderer(QObject *parent = 0)
-            : KSvgRenderer(parent)
-        {}
-
-        SharedSvgRenderer(const QString &filename, QObject *parent = 0)
-            : KSvgRenderer(filename, parent)
-        {}
-
-        SharedSvgRenderer(const QByteArray &contents, QObject *parent = 0)
-            : KSvgRenderer(contents, parent)
-        {}
-
-        ~SharedSvgRenderer()
-        {
-            //kDebug() << "leaving this world for a better one.";
-        }
-};
-
 class Svg::Private
 {
     public:
-        Private(const QString& imagePath)
-            : renderer(0),
-              contentType(Svg::SingleImage)
+        Private(Svg* parent, const QString& imagePath)
+            : renderedPixmaps(),
+              themePath(),
+              path(),
+              size(),
+              themed(true),
+              contentType(Svg::SingleImage),
+              q(parent),
+              jobIds()
         {
             if (QDir::isAbsolutePath(imagePath)) {
                 path = imagePath;
@@ -79,95 +64,95 @@
 
         ~Private()
         {
-            if (renderer.count() == 2) {
-                // this and the cache reference it; and boy is this not thread safe \
                ;)
-                renderers.erase(renderers.find(themePath));
-            }
-
-            renderer = 0;
         }
 
-        void removeFromCache()
+        void clearCache()
         {
-            if ( id.isEmpty() ) {
-                return;
-            }
-
-            QPixmapCache::remove( id );
-            id.clear();
+            qDeleteAll(renderedPixmaps);
         }
 
-        void findInCache(QPixmap& p, const QString& elementId)
+        QPixmap* findInCache(const QString& elementId)
         {
-            createRenderer();
-            id = QString::fromLatin1("%3_%2_%1")
-                                    .arg(size.width())
-                                    .arg(size.height())
-                                    .arg(path);
+            QString id = QString::fromLatin1("%3_%2_%1")
+                                             .arg(size.width())
+                                             .arg(size.height())
+                                             .arg(path);
             if (!elementId.isEmpty()) {
                 id.append(elementId);
             }
             //kDebug() << "id is " << id;
 
-            if (QPixmapCache::find(id, p)) {
-                //kDebug() << "found cached version of " << id;
-                return;
-            } else {
-                //kDebug() << "didn't find cached version of " << id << ", so \
re-rendering"; +            // See if we have the requested pixmap already rendered
+            QPixmap* pixmap = 0;
+            QHash<QString, QPixmap*>::const_iterator it = renderedPixmaps.find(id);
+
+            if (it != renderedPixmaps.end()) {
+                pixmap = it.value();
+                return pixmap;
             }
 
-            // we have to re-render this puppy
+            // No such luck, we need to enqueue a render job
             QSize s;
             if (elementId.isEmpty() || contentType == Svg::ImageSet) {
                 s = size.toSize();
             } else {
                 s = elementSize(elementId);
             }
-            //kDebug() << "size for " << elementId << " is " << s;
 
-            p = QPixmap(s);
-            p.fill(Qt::transparent);
-            QPainter renderPainter(&p);
+            int jobId =
+                SvgThreadManager::self()->enqueueRenderJob(path, elementId, s,
+                                                           q, "onSvgRendered");
 
-            if (elementId.isEmpty()) {
-                renderer->render(&renderPainter);
-            } else {
-                renderer->render(&renderPainter, elementId);
-            }
-            renderPainter.end();
-            bool inserted = QPixmapCache::insert( id, p );
-            if (!inserted)
-                kDebug() << "pixmap cache is too small for inserting" << id << "of \
size" << s; +            // Store the id and the jobID so that we know how to cache \
the rendered +            // pixmap that we receive back in Svg::onSvgRendered()
+            jobIds[jobId] = id;
+
+            return 0;
         }
 
-        void createRenderer()
+        void insertInCache(int jobId, QPixmap* pm)
         {
-            if (renderer) {
+            /** @todo Implement some kind of proper caching here */
+            // Look up the id string for this job id
+            QHash<int, QString>::const_iterator it = jobIds.find(jobId);
+            if (it == jobIds.end()) {
+                kWarning() << "Cannot find job with id =" << jobId << "in the list \
of jobs";  return;
             }
+            QString id = it.value();
 
+            // We can remove this jobId from the list of running jobs now
+            jobIds.remove(jobId);
+
+            // So now we have the originally generated id string and the rendered \
pixmap. +            // Let's cache them for later use by the painting functions
+            renderedPixmaps[id] = pm;
+        }
+
+        SharedSvgRenderer::Ptr createRenderer()
+        {
             if (themed && path.isNull()) {
                 path = Plasma::Theme::self()->image(themePath);
             }
 
-            QHash<QString, SharedSvgRenderer::Ptr>::const_iterator it = \
                renderers.find(path);
-
-            if (it != renderers.end()) {
-                //kDebug() << "gots us an existing one!";
-                renderer = it.value();
-            } else {
-                renderer = new SharedSvgRenderer(path);
-                renderers[path] = renderer;
-            }
-
+            SharedSvgRenderer::Ptr renderer = \
SvgRendererManager::findRenderer(path); +            QMutexLocker \
locker(&renderer->mutex);  size = renderer->defaultSize();
+            return renderer;
         }
 
         QSize elementSize(const QString& elementId)
         {
-            createRenderer();
+            SharedSvgRenderer::Ptr renderer = createRenderer();
+
+            // Lock the renderer's mutex in case an SvgRendererThread is doing \
something with it +            QMutexLocker locker( &renderer->mutex );
             QSizeF elementSize = renderer->boundsOnElement(elementId).size();
             QSizeF naturalSize = renderer->defaultSize();
+
+            // OK we are done with the renderer here. Be nice and unlock the mutex.
+            locker.unlock();
+
             qreal dx = size.width() / naturalSize.width();
             qreal dy = size.height() / naturalSize.height();
             elementSize.scale(elementSize.width() * dx, elementSize.height() * dy, \
Qt::IgnoreAspectRatio); @@ -177,37 +162,50 @@
 
         QRect elementRect(const QString& elementId)
         {
-            createRenderer();
+            SharedSvgRenderer::Ptr renderer = createRenderer();
+
+            // Lock the renderer's mutex in case an SvgRendererThread is doing \
something with it +            QMutexLocker locker( &renderer->mutex );
+
             QRectF elementRect = renderer->boundsOnElement(elementId);
             QSizeF naturalSize = renderer->defaultSize();
+
+            // OK we are done with the renderer here. Be nice and unlock the mutex.
+            locker.unlock();
+
             qreal dx = size.width() / naturalSize.width();
             qreal dy = size.height() / naturalSize.height();
-            
-            return QRect(elementRect.x() * dx, elementRect.y() * dy,
-                         elementRect.width() * dx, elementRect.height() * dy);
+
+            return QRect(static_cast<int>(elementRect.x() * dx),
+                         static_cast<int>(elementRect.y() * dy),
+                         static_cast<int>(elementRect.width() * dx),
+                         static_cast<int>(elementRect.height() * dy));
         }
-    
+
         QMatrix matrixForElement(const QString& elementId)
         {
-            createRenderer();
+            SharedSvgRenderer::Ptr renderer = createRenderer();
+
+            // Lock the renderer's mutex in case an SvgRendererThread is doing \
something with it +            QMutexLocker locker( &renderer->mutex );
+
             return renderer->matrixForElement(elementId);
         }
 
-        static QHash<QString, SharedSvgRenderer::Ptr> renderers;
-        SharedSvgRenderer::Ptr renderer;
+        QHash<QString, QPixmap*> renderedPixmaps;
         QString themePath;
         QString path;
-        QString id;
         QSizeF size;
         bool themed;
         Svg::ContentType contentType;
+        Svg* q;
+        QHash<int, QString> jobIds;
 };
 
-QHash<QString, SharedSvgRenderer::Ptr> Svg::Private:: renderers;
 
 Svg::Svg(const QString& imagePath, QObject* parent)
     : QObject(parent),
-      d(new Private(imagePath))
+      d(new Private(this, imagePath))
 {
     if (d->themed) {
         connect(Plasma::Theme::self(), SIGNAL(changed()), this, \
SLOT(themeChanged())); @@ -221,10 +219,13 @@
 
 void Svg::paint(QPainter* painter, const QPointF& point, const QString& elementID)
 {
-    QPixmap pix;
-    d->findInCache(pix, elementID);
-    //kDebug() << "pix size is " << pix.size();
-    painter->drawPixmap(QRectF(point, pix.size()), pix, QRectF(QPointF(0,0), \
pix.size())); +    // If we have a valid, rendered version of the SVG, then paint it.
+    QPixmap* pm = d->findInCache(elementID);
+    if (pm) {
+        painter->drawPixmap(QRectF(point, pm->size()),
+                            *pm,
+                            QRectF(QPointF(0,0), pm->size()));
+    }
 }
 
 void Svg::paint(QPainter* painter, int x, int y, const QString& elementID)
@@ -234,12 +235,25 @@
 
 void Svg::paint(QPainter* painter, const QRectF& rect, const QString& elementID)
 {
-    QPixmap pix;
-    d->findInCache(pix, elementID);
-    //kDebug() << "pix size is " << pix.size();
-    painter->drawPixmap(rect, pix, QRectF(QPointF(0,0), pix.size()));
+    // If we have a valid, rendered version of the SVG, then paint it.
+    QPixmap* pm = d->findInCache(elementID);
+    if (pm) {
+        painter->drawPixmap(rect, *pm, QRectF(QPointF(0,0), pm->size()));
+    }
 }
 
+void Svg::paint(QPainter* painter,
+                const QStyleOptionGraphicsItem *option,
+                const QString& elementID)
+{
+    // If we have a valid, rendered version of the SVG, then paint it. In this case \
we +    // can just blit the region covered by option->exposedRect.
+    QPixmap* pm = d->findInCache(elementID);
+    if (pm) {
+        painter->drawPixmap(option->exposedRect, *pm, option->exposedRect);
+    }
+}
+
 void Svg::resize( int width, int height )
 {
     resize( QSize( width, height ) );
@@ -253,8 +267,9 @@
 
 void Svg::resize()
 {
-    d->createRenderer();
-    d->size = d->renderer->defaultSize();
+    SharedSvgRenderer::Ptr renderer = d->createRenderer();
+    QMutexLocker locker(&renderer->mutex);
+    d->size = renderer->defaultSize();
 }
 
 QSize Svg::elementSize(const QString& elementId) const
@@ -269,20 +284,23 @@
 
 bool Svg::elementExists(const QString& elementId) const
 {
-    d->createRenderer();
-    return d->renderer->elementExists(elementId);
+    SharedSvgRenderer::Ptr renderer = d->createRenderer();
+    QMutexLocker locker(&renderer->mutex);
+    return renderer->elementExists(elementId);
 }
 
 QMatrix Svg::matrixForElement(const QString& elementId) const
 {
-    d->createRenderer();
-    return d->renderer->matrixForElement(elementId);
+    SharedSvgRenderer::Ptr renderer = d->createRenderer();
+    QMutexLocker locker(&renderer->mutex);
+    return renderer->matrixForElement(elementId);
 }
 
 bool Svg::isValid() const
 {
-    d->createRenderer();
-    return d->renderer->isValid();
+    SharedSvgRenderer::Ptr renderer = d->createRenderer();
+    QMutexLocker locker(&renderer->mutex);
+    return renderer->isValid();
 }
 
 QSize Svg::size() const
@@ -302,13 +320,20 @@
 
 void Svg::themeChanged()
 {
-    d->removeFromCache();
+    d->clearCache();
     d->path.clear();
     //delete d->renderer; we're a KSharedPtr
-    d->renderer = 0;
+    //d->renderer = 0;
+    /** @todo Tell the svg manager that it can delete the renderer */
     emit repaintNeeded();
 }
 
+void Svg::onSvgRendered(int jobId, QPixmap* pm)
+{
+    kDebug() << "Received rendered SVG for job id =" << jobId << "with size =" << \
pm->size(); +    d->insertInCache(jobId, pm);
+}
+
 } // Plasma namespace
 
 #include "svg.moc"
Index: workspace/libs/plasma/svgrendererthread.h
===================================================================
--- workspace/libs/plasma/svgrendererthread.h	(revision 0)
+++ workspace/libs/plasma/svgrendererthread.h	(revision 0)
@@ -0,0 +1,230 @@
+/*
+ *   Copyright 2007 Sean Harmer <sh@theharmers.co.uk>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library General Public License as
+ *   published by the Free Software Foundation; either version 2, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this program; if not, write to the
+ *   Free Software Foundation, Inc.,
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#ifndef PLASMA_SVGRENDERERTHREAD_H
+#define PLASMA_SVGRENDERERTHREAD_H
+
+#include <QImage>
+#include <QMutex>
+#include <QSharedData>
+#include <QThread>
+
+#include <ksharedptr.h>
+#include <ksvgrenderer.h>
+
+#include <plasma/plasma_export.h>
+
+namespace Plasma {
+
+struct SvgRenderParams
+{
+    int id;
+    QString path;
+    QString elementId;
+    QSize size;
+    QObject* receiver;
+    char* slot;
+};
+
+class SharedSvgRenderer : public KSvgRenderer, public QSharedData
+{
+    public:
+        typedef KSharedPtr<SharedSvgRenderer> Ptr;
+
+        SharedSvgRenderer(QObject *parent = 0)
+        : KSvgRenderer(parent)
+        {}
+
+        SharedSvgRenderer(const QString &filename, QObject *parent = 0)
+        : KSvgRenderer(filename, parent)
+        {}
+
+        SharedSvgRenderer(const QByteArray &contents, QObject *parent = 0)
+        : KSvgRenderer(contents, parent)
+        {}
+
+        ~SharedSvgRenderer()
+        {
+    //kDebug() << "leaving this world for a better one.";
+        }
+
+        QMutex mutex;
+};
+
+class SvgRendererManager
+{
+    public:
+        /** \todo Implement some sort of cache expiry algorithm, otherwise our \
renderer +         * pool will grow indefinitely.
+         */
+        SvgRendererManager();
+        ~SvgRendererManager();
+
+        /**
+         * Returns a reference counted pointer to the renderer responsible
+         * for drawing the SVG @p path. If a renderer for the specified
+         * path does not already exist, then a new one is created.
+         *
+         * @arg path the svg to find the renderer for.
+         * @return a pointer to the relevent renderer object.
+         */
+        static SharedSvgRenderer::Ptr findRenderer(const QString& path);
+
+    private:
+        static QHash<QString, SharedSvgRenderer::Ptr> ms_rendererPool;
+        static QMutex ms_mutex;
+};
+
+
+/**
+ * Objects of type SvgRendererThread are used by Plasma::Svg
+ * (by means of Plasma::SvgThreadManager) to render SVG images
+ * without blocking the main GUI thread.
+ *
+ * The SVG being processed by such a thread is rendered to a
+ * QImage which is then passed back to the GUI thread by means
+ * of a queued connection.
+ *
+ * @author Sean Harmer <sh@theharmers.co.uk>
+*/
+class PLASMA_EXPORT SvgRendererThread : public QThread
+{
+    Q_OBJECT
+
+    public:
+        SvgRendererThread(QObject* parent = 0);
+        ~SvgRendererThread();
+
+        void render(const SvgRenderParams& params);
+
+        bool isRendering() const;
+
+    protected:
+        virtual void run();
+
+    signals:
+        void renderedImage(int, const QImage&);
+
+    private:
+        class Private;
+        Private* const d;
+};
+
+
+class PLASMA_EXPORT SvgThreadManager : public QObject
+{
+    Q_OBJECT
+
+    public:
+        /**
+         * Singleton pattern accessor
+         */
+        static SvgThreadManager* self();
+
+        /**
+         * Default constructor. Usually you want to use the singleton instead.
+         */
+        explicit SvgThreadManager(QObject* parent = 0);
+
+        /**
+         * Destructor
+         */
+        virtual ~SvgThreadManager();
+
+        /**
+         * Explicitly sets the maximum number of SvgRendererThread objects
+         * that the manager can instantiate. By default the maximum number
+         * of threads is set to QThread::idealThreadCount().
+         *
+         * @arg maximumThreadCount maximum number of allowed worker threads.
+         */
+        void setMaximumThreadCount(int maximumThreadCount);
+
+        /**
+         * The maximum number of SvgRendererThreads objects that the manager
+         * can instantiate. By default the maximum number of threads is set
+         * to QThread::idealThreadCount().
+         *
+         * @return maximum number of allowed worker threads.
+         */
+        int maximumThreadCount() const;
+
+        /**
+         * Number of SvgRendererThread objects being managed.
+         *
+         * @return the number of worker threads.
+         */
+        int threadCount() const;
+
+        /**
+         * Number of SvgRendererThread objects that are currently busy.
+         *
+         * @return the number of threads rendering at this time
+         */
+        int activeThreadCount() const;
+
+        /**
+         * Enqueues a job that will (at some point) render the SVG image
+         * specified by @p path and @p elementId into a QImage of size
+         * @p size. When the job is completed the object @p receiver will be
+         * notified by having the slot @p method invoked. This slot must
+         * accept a QImage parameter.
+         *
+         * @arg path path of the SVG image to render
+         * @arg elementId element of the SVG to render, pass an empty QString
+         *      for the whole SVG.
+         * @arg size size at which to render the SVG (or element if elementId
+         *      is not empty).
+         * @arg receive the object that will handle the actual animation
+         * @arg slot the method name of slot to be invoked on job completion.
+         *           It must take a QPixmap. So if the slot is notifyMe(QImage),
+         *           pass in "notifyMe" as the method parameter.
+         *
+         * @return an id that can be used to identify this render job.
+         */
+        int enqueueRenderJob(const QString& path,
+                             const QString& elementId,
+                             const QSize& size,
+                             QObject* receiver,
+                             const char* slot);
+
+    protected:
+        /**
+         * Makes an attempt to get a rendering job underway. It checks to see
+         * if there are any available SvgRenderThread objects or if we are
+         * allowed to create a new one but so as to not have a number greater
+         * than maximumThreadCount().
+         *
+         * If a job can be started, the job is moved from the pending jobs
+         * queue and appended to the currently processing list.
+         */
+        virtual void tryNextRenderJob();
+
+    protected slots:
+        virtual void receiverDestroyed(QObject*);
+        virtual void onRenderJobFinished( int id, const QImage& image );
+
+    private:
+        class Private;
+        Private* const d;
+};
+
+} // Plasma namespace
+
+#endif // multiple inclusion guard
Index: workspace/libs/plasma/CMakeLists.txt
===================================================================
--- workspace/libs/plasma/CMakeLists.txt	(revision 730999)
+++ workspace/libs/plasma/CMakeLists.txt	(working copy)
@@ -37,6 +37,7 @@
     scriptengine.cpp
     shadowitem.cpp
     svg.cpp
+    svgrendererthread.cpp
     theme.cpp
     uiloader.cpp
     widgets/boxlayout.cpp
@@ -108,6 +109,7 @@
     scriptengine.h
     shadowitem_p.h
     svg.h
+    svgrendererthread.h
     theme.h
     uiloader.h)
 
Index: workspace/libs/plasma/svg.h
===================================================================
--- workspace/libs/plasma/svg.h	(revision 730999)
+++ workspace/libs/plasma/svg.h	(working copy)
@@ -25,12 +25,14 @@
 #include <plasma/plasma_export.h>
 
 class QPainter;
+class QPixmap;
 class QPoint;
 class QPointF;
 class QRect;
 class QRectF;
 class QSize;
 class QSizeF;
+class QStyleOptionGraphicsItem;
 class QMatrix;
 
 namespace Plasma
@@ -110,13 +112,22 @@
          * Paints the SVG represented by this object
          * @arg painter the QPainter to use
          * @arg rect the rect to draw into; if small than the current size
-         *           of the 
+         *           of the
          *      drawn starting at this point.
          */
         Q_INVOKABLE void paint(QPainter* painter, const QRectF& rect,
                                const QString& elementID = QString());
 
         /**
+         * Paints the SVG represented by this object.
+         * @arg painter the QPainter to use.
+         * @arg option options that can be used to tailor the drawing.
+         */
+        Q_INVOKABLE void paint(QPainter* painter,
+                               const QStyleOptionGraphicsItem *option,
+                               const QString& elementID = QString());
+
+        /**
          * Resizes the rendered image. Rendering will actually take place on
          * the next call to paint.
          * @arg width the new width
@@ -159,13 +170,13 @@
         Q_INVOKABLE bool elementExists( const QString& elementId ) const;
 
         /**
-         * The transformation matrix of the element. That includes the 
+         * The transformation matrix of the element. That includes the
          * transformation on the element itself.
          * @arg elementId the id of the element
          * @return the matrix for the element
          **/
         Q_INVOKABLE QMatrix matrixForElement(const QString& elementId) const;
-        
+
         /**
          * @return true if the SVG file exists and the document is valid,
          *         otherwise false. This method can be expensive as it
@@ -198,6 +209,7 @@
 
     private Q_SLOTS:
         void themeChanged();
+        void onSvgRendered(int, QPixmap*);
 
     private:
         class Private;
Index: workspace/libs/plasma/svgrendererthread.cpp
===================================================================
--- workspace/libs/plasma/svgrendererthread.cpp	(revision 0)
+++ workspace/libs/plasma/svgrendererthread.cpp	(revision 0)
@@ -0,0 +1,411 @@
+/*
+ *   Copyright 2007 Sean Harmer <sh@theharmers.co.uk>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU Library General Public License as
+ *   published by the Free Software Foundation; either version 2, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details
+ *
+ *   You should have received a copy of the GNU Library General Public
+ *   License along with this program; if not, write to the
+ *   Free Software Foundation, Inc.,
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "svgrendererthread.h"
+
+#include <QList>
+#include <QPainter>
+#include <QPixmap>
+#include <QQueue>
+#include <QWaitCondition>
+
+#include <kdebug.h>
+#include <kglobal.h>
+
+namespace Plasma {
+
+QHash<QString, SharedSvgRenderer::Ptr> SvgRendererManager::ms_rendererPool;
+QMutex SvgRendererManager::ms_mutex;
+
+SvgRendererManager::SvgRendererManager()
+{
+
+}
+
+SvgRendererManager::~SvgRendererManager()
+{
+    // Delete all of the renderers - remember they are shared pointers!
+    foreach (SharedSvgRenderer::Ptr renderer, ms_rendererPool) {
+        renderer = 0;
+    }
+}
+
+SharedSvgRenderer::Ptr SvgRendererManager::findRenderer(const QString& path)
+{
+    if (path.isEmpty())
+        return SharedSvgRenderer::Ptr();
+
+    // OK we need to do something - let's lock
+    QMutexLocker locker(&ms_mutex);
+
+    // Can we find a matching renderer?
+    SharedSvgRenderer::Ptr renderer;
+    QHash<QString, SharedSvgRenderer::Ptr>::const_iterator it = \
ms_rendererPool.find(path); +
+    if (it != ms_rendererPool.end()) {
+        // Yay, found one
+        renderer = it.value();
+    } else {
+        // No such luck, we need to make a new one
+        renderer = new SharedSvgRenderer(path);
+        ms_rendererPool[path] = renderer;
+    }
+
+    return renderer;
+}
+
+class SvgRendererThread::Private
+{
+    public:
+        Private()
+            : mutex(),
+              condition(),
+              abort(false),
+              isRendering(false),
+              params()
+        {
+
+        }
+
+        ~Private()
+        {
+
+        }
+
+        QMutex mutex;
+        QWaitCondition condition;
+        bool abort;
+        bool isRendering;
+        SvgRenderParams params;
+};
+
+
+SvgRendererThread::SvgRendererThread(QObject *parent)
+    : QThread(parent),
+      d(new Private())
+{
+}
+
+SvgRendererThread::~SvgRendererThread()
+{
+    d->mutex.lock();
+    d->abort = true;
+    d->condition.wakeOne();
+    d->mutex.unlock();
+
+    wait();
+}
+
+void SvgRendererThread::render(const SvgRenderParams& params)
+{
+    kDebug() << "Starting a render job for" << params.path
+             << "at" << params.size
+             << "with id =" << params.id;
+    QMutexLocker locker(&d->mutex);
+    d->params = params;
+
+    // Start the thread (or wake it up)
+    if ( !isRunning() ) {
+        start();
+    } else {
+        d->condition.wakeOne();
+    }
+}
+
+bool SvgRendererThread::isRendering() const
+{
+    QMutexLocker locker(&d->mutex);
+    return d->isRendering;
+}
+
+void SvgRendererThread::run()
+{
+    while (1) {
+        // Copy across data to local variables and set the rendering flag
+        d->mutex.lock();
+        d->isRendering = true;
+        int id = d->params.id;
+        QString path = d->params.path;
+        QString elementId = d->params.elementId;
+        QSize size = d->params.size;
+        d->mutex.unlock();
+
+        // Do the actual rendering
+
+        // To render we need a QSvgRenderer type object. Do we have a suitable
+        // object in our pool, or do we need to create one?
+        kDebug() << "Trying to find a renderer for job id =" << id;
+        SharedSvgRenderer::Ptr renderer = SvgRendererManager::findRenderer(path);
+
+        // Lock the renderer for our use only
+        /** \todo Be smarter in the queuing of these jobs, no point having mutliple
+         * threads if they are all contending for the same renderer!
+         */
+        QMutexLocker rendererLocker(&renderer->mutex);
+
+        // Create a QImage to paint on
+        QImage img( size, QImage::Format_ARGB32_Premultiplied );
+
+        // Create a painter
+        QPainter renderPainter(&img);
+
+        // Let's paint already :-)
+        kDebug() << "Rendering the SVG to an image for job id =" << id;
+        if (elementId.isEmpty()) {
+            renderer->render(&renderPainter);
+        } else {
+            renderer->render(&renderPainter, elementId);
+        }
+        renderPainter.end();
+
+        // We are nwo done with the renderer, so we can release it for others to \
use. +        rendererLocker.unlock();
+
+        // Signal that we are done
+        kDebug() << "Emitting renderedImage() for job id =" << id;
+        emit renderedImage(id, img);
+
+        // You are feeling sleepy...
+        d->mutex.lock();
+        d->isRendering = false;
+        kDebug() << "Thread going to sleep";
+        d->condition.wait(&d->mutex);
+        d->mutex.unlock();
+    }
+}
+
+
+class SvgThreadManager::Private
+{
+    public:
+        Private()
+            : maximumThreadCount(1),
+              threadPool(),
+              renderId(0),
+              pendingJobQueue(),
+              currentJobList()
+        {
+            maximumThreadCount = QThread::idealThreadCount();
+        }
+
+        ~Private()
+        {
+            // Delete all of the worker threads
+            qDeleteAll(threadPool);
+        }
+
+        int maximumThreadCount;
+        QList<SvgRendererThread*> threadPool;
+        int renderId;
+        QQueue<SvgRenderParams*> pendingJobQueue;
+        QList<SvgRenderParams*> currentJobList;
+};
+
+class SvgThreadManagerSingleton
+{
+    public:
+        SvgThreadManager self;
+};
+
+K_GLOBAL_STATIC( SvgThreadManagerSingleton, privateSvgThreadManagerSelf );
+
+SvgThreadManager* SvgThreadManager::self()
+{
+    return &privateSvgThreadManagerSelf->self;
+}
+
+SvgThreadManager::SvgThreadManager(QObject* parent)
+    : QObject(parent),
+      d(new Private())
+{
+
+}
+
+SvgThreadManager::~SvgThreadManager()
+{
+
+}
+
+void SvgThreadManager::setMaximumThreadCount(int maximumThreadCount)
+{
+    d->maximumThreadCount = maximumThreadCount;
+}
+
+int SvgThreadManager::maximumThreadCount() const
+{
+    return d->maximumThreadCount;
+}
+
+int SvgThreadManager::threadCount() const
+{
+    return d->threadPool.size();
+}
+
+int SvgThreadManager::activeThreadCount() const
+{
+    int activeCount = 0;
+    for (int i = 0; i < d->threadPool.size(); i++) {
+        if (d->threadPool.at(i)->isRendering()) {
+            ++activeCount;
+        }
+    }
+    return activeCount;
+}
+
+int SvgThreadManager::enqueueRenderJob(const QString& path,
+                                       const QString& elementId,
+                                       const QSize& size,
+                                       QObject* receiver,
+                                       const char* slot)
+{
+    if (size.isNull() || path.isEmpty() || !receiver || !slot) {
+        return -1;
+    }
+
+    SvgRenderParams* params = new SvgRenderParams;
+    params->id = ++d->renderId;
+    params->path = path;
+    params->elementId = elementId;
+    params->size = size;
+    params->receiver = receiver;
+    params->slot = qstrdup(slot);
+
+    // Enqueue the render job
+    d->pendingJobQueue.enqueue(params);
+
+    // Make sure that we get notified if the initiator has gone away
+    connect(receiver, SIGNAL(destroyed(QObject*)),
+            this, SLOT(receiverDestroyed(QObject*)));
+
+    // Attempt to start off the next job in the queue
+    tryNextRenderJob();
+
+    // Return the job id for reference
+    return params->id;
+}
+
+void SvgThreadManager::tryNextRenderJob()
+{
+    kDebug() << "Attempting to launch an SvgThreadManager job";
+
+    // Are there any pending jobs?
+    if (d->pendingJobQueue.size() == 0) {
+        return;
+    }
+
+    // Do we have any available threads? Or are we allowed to make another?
+    if (activeThreadCount() == d->maximumThreadCount) {
+        kDebug() << "All threads busy and no more threads allowed";
+        return; // No more resources available at this time
+    }
+
+    // Get us a thread to use
+    SvgRendererThread* worker = 0;
+    if (d->threadPool.size() == d->maximumThreadCount) {
+        // We must have a spare thread in here somewhere - find it
+        for (int i = 0; i < d->threadPool.size(); i++) {
+            if (d->threadPool.at(i)->isRendering() == false) {
+                worker = d->threadPool.at(i);
+                break;
+            }
+        }
+    } else {
+        // We have room for at least one more thread
+        kDebug() << "Creating a new SvgRendererThread";
+        worker = new SvgRendererThread(this);
+        if (worker) {
+            connect(worker, SIGNAL(renderedImage(int, const QImage&)),
+                    this, SLOT(onRenderJobFinished(int, const QImage&)));
+            d->threadPool.append(worker);
+        }
+    }
+
+    // Do we have a valid thread to work with?
+    if (worker == 0) {
+        kDebug() << "Could not find an SvgRendererThread to use";
+        return;
+    }
+
+    // We're good to go. Move a job from the pending queue to the currently
+    // processing list and fire up the thread.
+    SvgRenderParams* params = d->pendingJobQueue.dequeue();
+    d->currentJobList.append(params);
+    worker->render(*params);
+}
+
+void SvgThreadManager::receiverDestroyed(QObject* deleted)
+{
+    kDebug() << "Receiver has been destroyed before render job finished";
+
+    // "Remove" any jobs from the queue that have the deleted object as a receiver
+    // by marking the receiver as null. This is much more efficient than really
+    // removing an item form the middle of the queue. The receiver is then checked
+    // in the onRenderFinished() slot to see if there is anything to notify.
+    QList<SvgRenderParams*>::iterator i;
+    for (i = d->pendingJobQueue.begin(); i != d->pendingJobQueue.end(); ++i) {
+        if ( (*i)->receiver == deleted )
+            (*i)->receiver = 0;
+    }
+
+    // Now do the same for the currently processing list
+    for (i = d->currentJobList.begin(); i != d->currentJobList.end(); ++i) {
+        if ( (*i)->receiver == deleted )
+            (*i)->receiver = 0;
+    }
+}
+
+void SvgThreadManager::onRenderJobFinished(int id, const QImage& image)
+{
+    kDebug() << "Render Job id =" << id << "has finished";
+
+    // Test to see if there is a receiver registered that wishes to be informed
+    // when the pixmap is ready
+    for (int i = 0; i < d->currentJobList.size(); i++) {
+        SvgRenderParams* params = d->currentJobList.at(i);
+        if (params->id == id) {
+            // Found an id that matches. Does it wish to be notified and has
+            // not been deleted?
+            if (params->receiver != 0) {
+                // Found a match with a non-null receiver
+                kDebug() << "Found a receiver for job id =" << id;
+
+                // Convert the image to a pixmap
+                QPixmap* pixmap = new QPixmap(image.size());
+                *pixmap = QPixmap::fromImage(image, 0);
+
+                // Inform the registered receiver
+                QMetaObject::invokeMethod(params->receiver, params->slot,
+                                          Q_ARG(int, id),
+                                          Q_ARG(QPixmap*, pixmap));
+            }
+
+            // We have done everything that we need to with the receiver at this \
point. +            // So now we can remove its entry from the current job list
+            delete params;
+            d->currentJobList.removeAt(i);
+        }
+    }
+
+    // We're done processing that job now. Try to launch another if there are any \
pending +    tryNextRenderJob();
+}
+
+} // Plasma namespace
+
+#include "svgrendererthread.moc"



_______________________________________________
Panel-devel mailing list
Panel-devel@kde.org
https://mail.kde.org/mailman/listinfo/panel-devel


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic