diff options
Diffstat (limited to 'src/quickcl/qquickclitem.cpp')
-rw-r--r-- | src/quickcl/qquickclitem.cpp | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/quickcl/qquickclitem.cpp b/src/quickcl/qquickclitem.cpp new file mode 100644 index 0000000..fa5933c --- /dev/null +++ b/src/quickcl/qquickclitem.cpp @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick CL module +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickclitem.h" +#include "qquickclcontext_p.h" +#include <QtCore/QAtomicInt> +#include <QtCore/QHash> +#include <QtCore/QFile> +#include <QtCore/QLoggingCategory> +#include <QtQuick/private/qquickitem_p.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(logCL) + +/*! + \class QQuickCLItem + + \brief QQuickCLItem is a QQuickItem that automatically gets an OpenCL + context with the proper platform and device chosen for CL-GL interop. + + Each instance of QQuickCLItem is backed by a corresponding QQuickCLRunnable. + + \note When animating properties that are used in OpenCL kernels, call the + \l{QQuickItem::update()}{update()} function (from the gui thread) to + trigger updates. + */ + +/*! + \class QQuickCLRunnable + + \brief QQuickCLRunnable instances live on the scenegraph's render thread + encapsulating OpenCL and related OpenGL resources. + + The constructor, destructor and update() are always invoked on the render + thread with the OpenGL context bound and the OpenCL context ready. This + allows easy usage of OpenCL and especially OpenGL resources without + worrying about lifetime and threading. They can simply be created in the + constructor and released in the destructor. + */ + +/*! + \fn QSGNode *QQuickCLRunnable::update(QSGNode *node) + + Called on the render thread every time the QQuickCLItem is updated. + Semantically equivalent to + \l{QQuickItem::updatePaintNode()}{updatePaintNode()}. + + It is up to the implementation to decide what to do here: it can launch + OpenCL operations, wait for them to finish, and return a scenegraph node, + for example a QSGSimpleTextureNode wrapping the resulting texture. + + Alternatively, it can also launch a longer running CL operation and return + \c null or a node providing some temporary content. Then, when the CL + operation is finished, \l{QQuickCLItem::scheduleUpdate()}{scheduleUpdate()} + is called from an OpenCL event callback to schedule an update for the Quick + item, which means eventually invoking this function again. This function + can then provide a new node with the results of the computation. + + \note When necessary, future updates for the QQuickCLItem can also be + scheduled from this function. However, this requires calling + QQuickCLItem::scheduleUpdate() instead of QQuickItem::update(). + */ + +/*! + \fn QQuickCLRunnable *QQuickCLItem::createCL() + + Factory function invoked on the render thread after initializing OpenCL. + */ + +class QQuickCLItemPrivate : public QQuickItemPrivate +{ + Q_DECLARE_PUBLIC(QQuickCLItem) + +public: + QQuickCLItemPrivate() : clctx(0), clnode(0) { } + + static void CL_CALLBACK eventCallback(cl_event event, cl_int status, void *user_data); + + QQuickCLContext *clctx; + QQuickCLRunnable *clnode; +}; + +QQuickCLItem::QQuickCLItem(QQuickItem *parent) + : QQuickItem(*new QQuickCLItemPrivate, parent) +{ + setFlag(ItemHasContents); +} + +/*! + \return the selected OpenCL platform. Matches the OpenGL context in use. + + \note The value is only available after the item is first rendered. It is + always safe to call this function from QQuickCLRunnable's constructor, + destructor and \l{QQuickCLRunnable::update()}{update()} function. + */ +cl_platform_id QQuickCLItem::platform() const +{ + Q_D(const QQuickCLItem); + return d->clctx ? d->clctx->platform() : 0; +} + +/*! + \return the selected OpenCL device. Matches the OpenGL context in use. + + \note The value is only available after the item is first rendered. It is + always safe to call this function from QQuickCLRunnable's constructor, + destructor and \l{QQuickCLRunnable::update()}{update()} function. + */ +cl_device_id QQuickCLItem::device() const +{ + Q_D(const QQuickCLItem); + return d->clctx ? d->clctx->device() : 0; +} + +/*! + \return the OpenCL context. + + \note The value is only available after the item is first rendered. It is + always safe to call this function from QQuickCLRunnable's constructor, + destructor and \l{QQuickCLRunnable::update()}{update()} function. + */ +cl_context QQuickCLItem::context() const +{ + Q_D(const QQuickCLItem); + return d->clctx ? d->clctx->context() : 0; +} + +QSGNode *QQuickCLItem::updatePaintNode(QSGNode *node, UpdatePaintNodeData *) +{ + Q_D(QQuickCLItem); + + if (width() <= 0 || height() <= 0) { + delete node; + return 0; + } + + // render thread, initialize CL if not yet done + if (!d->clctx) { + d->clctx = new QQuickCLContext; + if (!d->clctx->create()) { + qWarning("Failed to create OpenCL context"); + delete d->clctx; + d->clctx = 0; + } + } + + if (!d->clctx) + return 0; + + if (!d->clnode) + d->clnode = createCL(); + + return d->clnode ? d->clnode->update(node) : 0; +} + +class ReleaseRunnable : public QRunnable +{ +public: + ReleaseRunnable(QQuickCLContext *clctx, QQuickCLRunnable *clnode) : clctx(clctx), clnode(clnode) { } + void run() Q_DECL_OVERRIDE { + delete clnode; + delete clctx; + } +private: + QQuickCLContext *clctx; + QQuickCLRunnable *clnode; +}; + +void QQuickCLItem::releaseResources() +{ + // gui thread, just schedule. NB this and d may be dead by the time the runnable is run + Q_D(QQuickCLItem); + window()->scheduleRenderJob(new ReleaseRunnable(d->clctx, d->clnode), QQuickWindow::BeforeSynchronizingStage); + d->clnode = 0; + d->clctx = 0; +} + +void QQuickCLItem::invalidateSceneGraph() +{ + // render thread + Q_D(QQuickCLItem); + delete d->clnode; + d->clnode = 0; + delete d->clctx; + d->clctx = 0; +} + +static const int EV_UPDATE = QEvent::User + 128; +static const int EV_EVENT = QEvent::User + 129; + +class EventCompleteEvent : public QEvent +{ +public: + EventCompleteEvent(cl_event event) : QEvent(QEvent::Type(EV_EVENT)), event(event) { } + cl_event event; +}; + +bool QQuickCLItem::event(QEvent *e) +{ + if (e->type() == EV_UPDATE) { + update(); + return true; + } else if (e->type() == EV_EVENT) { + EventCompleteEvent *ev = static_cast<EventCompleteEvent *>(e); + eventCompleted(ev->event); + return true; + } + return QQuickItem::event(e); +} + +/*! + Schedules an update for the item. Unlike \l{QQuickItem::update()}{the base + class' update()}, this is safe to be called on any thread, hence it is safe + for use from CL event callbacks. + */ +void QQuickCLItem::scheduleUpdate() +{ + QCoreApplication::postEvent(this, new QEvent(QEvent::Type(EV_UPDATE))); +} + +/*! + \return the name of the current platform in use. + + \note This function can only be called from a QQuickCLRunnable's + constructor, destructor and \l{QQuickCLRunnable::update()}{update()} + function, or after the item has been rendered at least once. + */ +QByteArray QQuickCLItem::platformName() const +{ + QByteArray name(1024, '\0'); + clGetPlatformInfo(platform(), CL_PLATFORM_NAME, name.size(), name.data(), 0); + name.resize(int(strlen(name.constData()))); + return name; +} + +/*! + \return the list of device extensions. + + \note This function can only be called from a QQuickCLRunnable's + constructor, destructor and \l{QQuickCLRunnable::update()}{update()} + function, or after the item has been rendered at least once. + */ +QByteArray QQuickCLItem::deviceExtensions() const +{ + QByteArray ext(8192, '\0'); + clGetDeviceInfo(device(), CL_DEVICE_EXTENSIONS, ext.size(), ext.data(), 0); + ext.resize(int(strlen(ext.constData()))); + return ext; +} + +/*! + Creates and builds an OpenCL program from the source code in \a src. + + \return the cl_program or \c 0 when failed. Errors and build logs are + printed to the warning output. + + \note This function can only be called from a QQuickCLRunnable's + constructor, destructor and \l{QQuickCLRunnable::update()}{update()} + function, or after the item has been rendered at least once. + */ +cl_program QQuickCLItem::buildProgram(const QByteArray &src) +{ + cl_int err; + const char *str = src.constData(); + cl_program prog = clCreateProgramWithSource(context(), 1, &str, 0, &err); + if (!prog) { + qWarning("Failed to create OpenCL program: %d", err); + qWarning("Source was:\n%s", str); + return 0; + } + cl_device_id dev = device(); + err = clBuildProgram(prog, 1, &dev, 0, 0, 0); + if (err != CL_SUCCESS) { + qWarning("Failed to build OpenCL program: %d", err); + qWarning("Source was:\n%s", str); + QByteArray log; + log.resize(8192); + clGetProgramBuildInfo(prog, dev, CL_PROGRAM_BUILD_LOG, log.size(), log.data(), 0); + qWarning("Build log:\n%s", log.constData()); + return 0; + } + return prog; +} + +/*! + Creates and builds an OpenCL program from the source file \a filename. + + \sa buildProgram() + */ +cl_program QQuickCLItem::buildProgramFromFile(const QString &filename) +{ + QFile f(filename); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning("Failed to open OpenCL program source file %s", qPrintable(filename)); + return 0; + } + return buildProgram(f.readAll()); +} + +struct EventCallbackParam +{ + EventCallbackParam(QQuickCLItem *item) : item(item) { } + QPointer<QQuickCLItem> item; +}; + +/*! + Registers an event callback for \a event. The virtual function + eventCompleted() will get invoked on the gui/main thread when the event + completes. + + This allows easy and safe asynchronous computations because the results can + directly be exposed to QML from eventCompleted() due to it running on the + gui/main thread. In addition it is guaranteed that eventCompleted() is + never called directly from the OpenCL callback function. Special cases like + destroying the QQuickCLItem before completing the event are also handled + gracefully. + + \note \a event is not released. + */ +void QQuickCLItem::watchEvent(cl_event event) +{ + EventCallbackParam *param = new EventCallbackParam(this); + cl_int err = clSetEventCallback(event, CL_COMPLETE, QQuickCLItemPrivate::eventCallback, param); + if (err != CL_SUCCESS) + qWarning("Failed to set event callback: %d", err); +} + +/*! + Called on the gui/main thread when the \a event watched via watchEvent() + completes. The default implementation does nothing. + */ +void QQuickCLItem::eventCompleted(cl_event event) +{ + Q_UNUSED(event); +} + +void CL_CALLBACK QQuickCLItemPrivate::eventCallback(cl_event event, cl_int status, void *user_data) +{ + if (status != CL_COMPLETE) + return; + EventCallbackParam *param = static_cast<EventCallbackParam *>(user_data); + if (!param->item.isNull()) + QCoreApplication::postEvent(param->item, new EventCompleteEvent(event)); + delete param; +} + +QQuickCLRunnable::~QQuickCLRunnable() +{ +} + +QT_END_NAMESPACE |