[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