aboutsummaryrefslogtreecommitdiffstats
path: root/examples/quickcl/particles/particles.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/quickcl/particles/particles.cpp')
-rw-r--r--examples/quickcl/particles/particles.cpp448
1 files changed, 448 insertions, 0 deletions
diff --git a/examples/quickcl/particles/particles.cpp b/examples/quickcl/particles/particles.cpp
new file mode 100644
index 0000000..917baeb
--- /dev/null
+++ b/examples/quickcl/particles/particles.cpp
@@ -0,0 +1,448 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Quick CL module
+**
+** $QT_BEGIN_LICENSE:BSD$
+** You may use this file under the terms of the BSD license as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// Demonstrates generating vertex data (GL buffer) from OpenCL.
+
+#include <QGuiApplication>
+#include <QQuickView>
+#include <QQmlEngine>
+#include <QSGSimpleTextureNode>
+#include <QOpenGLFramebufferObject>
+#include <QOpenGLFunctions>
+#include <QOpenGLShaderProgram>
+#include <QQuickCLItem>
+#include <QQuickCLRunnable>
+#include <time.h>
+
+const int PARTICLE_COUNT = 1024;
+
+class CLItem;
+
+// CLNode visualizes the results of CLRunnable. This is done by rendering via a
+// FBO since we will have the data (the output from the CL kernel) on the GPU
+// already so QSGGeometryNode and friends are not suitable for us.
+//
+// If mixing with other Quick items is not needed, this can be simplified to
+// render straight to the window in a slot connected directly to the
+// beforeRendering() or afterRendering() signals of the QQuickWindow.
+//
+class CLNode : public QObject, public QSGSimpleTextureNode
+{
+ Q_OBJECT
+
+public:
+ CLNode(CLItem *item) : m_item(item), m_fbo(0), m_buf(0), m_clBuf(0), m_program(0) { }
+
+ ~CLNode() {
+ delete m_program;
+ delete m_fbo;
+ if (m_clBuf)
+ clReleaseMemObject(m_clBuf);
+ QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
+ if (m_buf)
+ f->glDeleteBuffers(1, &m_buf);
+ }
+
+public slots:
+ void render();
+
+public:
+ CLItem *m_item;
+ QOpenGLFramebufferObject *m_fbo;
+ GLuint m_buf;
+ cl_mem m_clBuf;
+ QAtomicInt m_renderPending;
+ QOpenGLShaderProgram *m_program;
+ int m_tUniformLoc;
+};
+
+// The CLRunnable lives on the render thread, like the CLNode.
+class CLRunnable : public QObject, public QQuickCLRunnable
+{
+ Q_OBJECT
+
+public:
+ CLRunnable(CLItem *item);
+ ~CLRunnable();
+ QSGNode *update(QSGNode *node) Q_DECL_OVERRIDE;
+ CLNode *node() const { return m_node; }
+ void resetComputeInProgress() { m_computeInProgress.testAndSetOrdered(1, 0); }
+
+public slots:
+ void handleScreenChange();
+
+private:
+ static void CL_CALLBACK computeDoneCallback(cl_event event, cl_int event_command_exec_status, void *user_data);
+ void computeDone();
+ QSize itemSize() const;
+ void createBuffer();
+ void createFbo();
+
+ CLItem *m_item;
+ CLNode *m_node;
+ bool m_recreateFbo;
+ cl_command_queue m_queue;
+ cl_program m_program;
+ cl_kernel m_kernel;
+ cl_event m_computeDoneEvent;
+ QSize m_itemSize;
+ bool m_needsExplicitSync;
+ QAtomicInt m_computeInProgress;
+ qreal m_lastT;
+ cl_mem m_clBufParticleInfo;
+};
+
+class CLItem : public QQuickCLItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal t READ t WRITE setT NOTIFY tChanged)
+
+public:
+ CLItem() : m_t(0) { }
+
+ QQuickCLRunnable *createCL() Q_DECL_OVERRIDE;
+ void eventCompleted(cl_event event) Q_DECL_OVERRIDE;
+
+ qreal t() const { return m_t; }
+ void setT(qreal v) {
+ if (m_t != v) {
+ m_t = v;
+ emit tChanged();
+ update();
+ }
+ }
+
+signals:
+ void tChanged();
+
+private:
+ qreal m_t;
+ CLRunnable *m_runnable;
+};
+
+static const char *vertexShaderSource =
+ "attribute vec2 vertex;\n"
+ "void main() {\n"
+ " gl_PointSize = 4.0;\n"
+ " gl_Position = vec4(vertex.xy, 0.0, 1.0);\n"
+ "}\n";
+
+static const char *fragmentShaderSource =
+ "uniform highp float t;\n"
+ "void main() {\n"
+ " gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0 - t);\n"
+ "}\n";
+
+void CLNode::render()
+{
+ if (!m_renderPending.testAndSetOrdered(1, 0))
+ return;
+
+ m_fbo->bind();
+ QOpenGLContext *ctx = QOpenGLContext::currentContext();
+ QOpenGLFunctions *f = ctx->functions();
+
+ if (!m_program) {
+ m_program = new QOpenGLShaderProgram;
+ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
+ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
+ m_program->link();
+ m_tUniformLoc = m_program->uniformLocation("t");
+
+#ifndef QT_OPENGL_ES_2
+ if (!ctx->isOpenGLES()) {
+ f->glEnable(GL_POINT_SPRITE);
+ f->glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
+ }
+#endif
+ }
+
+ f->glEnable(GL_BLEND);
+ f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ f->glViewport(0, 0, m_fbo->width(), m_fbo->height());
+ f->glClearColor(0, 0, 0.25f, 1);
+ f->glClear(GL_COLOR_BUFFER_BIT);
+
+ m_program->bind();
+ m_program->setUniformValue(m_tUniformLoc, GLfloat(m_item->t()));
+
+ f->glBindBuffer(GL_ARRAY_BUFFER, m_buf);
+ f->glEnableVertexAttribArray(0);
+ f->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
+
+ f->glDrawArrays(GL_POINTS, 0, PARTICLE_COUNT);
+}
+
+CLRunnable::CLRunnable(CLItem *item)
+ : m_item(item),
+ m_node(0),
+ m_recreateFbo(false),
+ m_program(0),
+ m_kernel(0),
+ m_computeDoneEvent(0),
+ m_lastT(-1),
+ m_clBufParticleInfo(0)
+{
+ qDebug() << "Platform" << m_item->platformName() << "Device extensions" << m_item->deviceExtensions();
+ cl_int err;
+
+ m_queue = clCreateCommandQueue(m_item->context(), m_item->device(), 0, &err);
+ if (!m_queue) {
+ qWarning("Failed to create OpenCL command queue: %d", err);
+ return;
+ }
+
+ m_program = m_item->buildProgramFromFile(QStringLiteral(":/particles.cl"));
+ if (!m_program)
+ return;
+ m_kernel = clCreateKernel(m_program, "updateParticles", &err);
+ if (!m_kernel) {
+ qWarning("Failed to create particles OpenCL kernel: %d", err);
+ return;
+ }
+
+ m_needsExplicitSync = !m_item->deviceExtensions().contains(QByteArrayLiteral("cl_khr_gl_event"));
+
+ // m_clBufParticleInfo is an ordinary OpenCL buffer.
+ size_t velBufSize = PARTICLE_COUNT * sizeof(cl_float) * 4;
+ m_clBufParticleInfo = clCreateBuffer(m_item->context(), CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR, velBufSize, 0, &err);
+ if (!m_clBufParticleInfo) {
+ qWarning("Failed to create CL buffer: %d", err);
+ return;
+ }
+ cl_float *ptr = (cl_float *) clEnqueueMapBuffer(m_queue, m_clBufParticleInfo, CL_TRUE, CL_MAP_WRITE, 0, velBufSize, 0, 0, 0, &err);
+ if (!ptr) {
+ qWarning("Failed to map CL buffer: %d", err);
+ return;
+ }
+ cl_float *p = ptr;
+ for (int i = 0; i < PARTICLE_COUNT; ++i) {
+ // Direction
+ *p++ = ((qrand() % 1000) + 1) / 1000.0f;
+ *p++ = ((qrand() % 1000) + 1) / 1000.0f;
+ // Speed
+ *p++ = ((qrand() % 1000) + 1) / 100.0f;
+ *p++ = ((qrand() % 1000) + 1) / 100.0f;
+ }
+ clEnqueueUnmapMemObject(m_queue, m_clBufParticleInfo, ptr, 0, 0, 0);
+}
+
+CLRunnable::~CLRunnable()
+{
+ if (m_clBufParticleInfo)
+ clReleaseMemObject(m_clBufParticleInfo);
+ if (m_kernel)
+ clReleaseKernel(m_kernel);
+ if (m_program)
+ clReleaseProgram(m_program);
+ if (m_queue)
+ clReleaseCommandQueue(m_queue);
+}
+
+void CLRunnable::createBuffer()
+{
+ QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
+
+ // m_buf is an OpenGL buffer that is read/written from OpenCL and then used
+ // as the input to the OpenGL vertex shader.
+ GLuint buf;
+ f->glGenBuffers(1, &buf);
+ f->glBindBuffer(GL_ARRAY_BUFFER, buf);
+ f->glBufferData(GL_ARRAY_BUFFER, PARTICLE_COUNT * sizeof(GLfloat) * 2, 0, GL_DYNAMIC_DRAW);
+ if (m_node->m_buf)
+ f->glDeleteBuffers(1, &m_node->m_buf);
+ m_node->m_buf = buf;
+
+ if (m_node->m_clBuf)
+ clReleaseMemObject(m_node->m_clBuf);
+ cl_int err;
+ m_node->m_clBuf = clCreateFromGLBuffer(m_item->context(), CL_MEM_READ_WRITE, buf, &err);
+ if (!m_node->m_clBuf)
+ qWarning("Failed to create OpenCL object for OpenGL buffer: %d", err);
+}
+
+QSize CLRunnable::itemSize() const
+{
+ QSize size(m_item->width(), m_item->height());
+ size *= m_item->window()->effectiveDevicePixelRatio();
+ return size;
+}
+
+void CLRunnable::createFbo()
+{
+ QOpenGLFramebufferObject *fbo = new QOpenGLFramebufferObject(itemSize());
+ m_node->setTexture(m_item->window()->createTextureFromId(fbo->texture(), fbo->size(),
+ QQuickWindow::TextureHasAlphaChannel));
+ delete m_node->m_fbo;
+ m_node->m_fbo = fbo;
+ m_node->m_renderPending.testAndSetOrdered(0, 1);
+}
+
+void CLRunnable::handleScreenChange()
+{
+ // We could have been moved from a high to low dpi screen or vice versa.
+ m_recreateFbo = true;
+ m_item->scheduleUpdate();
+}
+
+class StateGuard
+{
+public:
+ StateGuard(CLRunnable *r) : r(r) { }
+ ~StateGuard() { if (r) r->resetComputeInProgress(); }
+ void release() { r = 0; }
+private:
+ CLRunnable *r;
+};
+
+QSGNode *CLRunnable::update(QSGNode *node)
+{
+ if (!m_queue || !m_program || !m_kernel || !m_clBufParticleInfo)
+ return 0;
+
+ if (!node) {
+ m_node = new CLNode(m_item);
+ m_node->setFiltering(QSGTexture::Linear);
+ QObject::connect(m_item->window(), SIGNAL(beforeRendering()), m_node, SLOT(render()));
+ QObject::connect(m_item->window(), SIGNAL(screenChanged(QScreen*)), this, SLOT(handleScreenChange()));
+ node = m_node;
+ createBuffer();
+ }
+ if (m_itemSize != itemSize()) {
+ m_itemSize = itemSize();
+ m_recreateFbo = true;
+ }
+ if (m_recreateFbo) {
+ m_recreateFbo = false;
+ createFbo();
+ }
+ m_node->setRect(0, 0, m_item->width(), m_item->height());
+ if (m_node->m_renderPending)
+ m_node->markDirty(QSGNode::DirtyMaterial);
+
+ // Animation is based on the time property. No need to enqueue anything if
+ // the value has not yet changed.
+ if (m_lastT == m_item->t())
+ return node;
+
+ if (!m_computeInProgress.testAndSetOrdered(0, 1))
+ return node;
+
+ StateGuard sg(this);
+
+ cl_float dt = m_item->t() - m_lastT;
+ m_lastT = m_item->t();
+
+ if (m_needsExplicitSync)
+ QOpenGLContext::currentContext()->functions()->glFinish();
+
+ cl_int err = clEnqueueAcquireGLObjects(m_queue, 1, &m_node->m_clBuf, 0, 0, 0);
+ if (err != CL_SUCCESS) {
+ qWarning("Failed to queue acquiring the GL buffer: %d", err);
+ return node;
+ }
+
+ clSetKernelArg(m_kernel, 0, sizeof(cl_mem), &m_node->m_clBuf);
+ cl_float t = m_item->t();
+ clSetKernelArg(m_kernel, 1, sizeof(cl_float), &t);
+ clSetKernelArg(m_kernel, 2, sizeof(cl_float), &dt);
+ clSetKernelArg(m_kernel, 3, sizeof(cl_mem), &m_clBufParticleInfo);
+ size_t globalWorkSize = PARTICLE_COUNT;
+ err = clEnqueueNDRangeKernel(m_queue, m_kernel, 1, 0, &globalWorkSize, 0, 0, 0, 0);
+ if (err != CL_SUCCESS) {
+ qWarning("Failed to enqueue kernel: %d", err);
+ return node;
+ }
+
+ err = clEnqueueReleaseGLObjects(m_queue, 1, &m_node->m_clBuf, 0, 0, &m_computeDoneEvent);
+ if (err != CL_SUCCESS) {
+ qWarning("Failed to queue releasing the GL buffer: %d", err);
+ return node;
+ }
+
+ sg.release();
+ m_item->watchEvent(m_computeDoneEvent);
+
+ if (m_needsExplicitSync)
+ clFinish(m_queue);
+
+ return node;
+}
+
+void CLItem::eventCompleted(cl_event event)
+{
+ clReleaseEvent(event);
+ m_runnable->resetComputeInProgress();
+ // Tell the node that the FBO's contents is stale and needs updating.
+ m_runnable->node()->m_renderPending.testAndSetOrdered(0, 1);
+ scheduleUpdate();
+}
+
+QQuickCLRunnable *CLItem::createCL()
+{
+ m_runnable = new CLRunnable(this);
+ return m_runnable;
+}
+
+int main(int argc, char **argv)
+{
+#ifdef Q_OS_WIN
+ QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
+#endif
+ QGuiApplication app(argc, argv);
+
+ qsrand(time(0));
+
+ QQuickView view;
+ QObject::connect(view.engine(), SIGNAL(quit()), &app, SLOT(quit()));
+
+ qmlRegisterType<CLItem>("quickcl.qt.io", 1, 0, "CLItem");
+
+ view.setSource(QUrl("qrc:///qml/particles.qml"));
+ view.setResizeMode(QQuickView::SizeRootObjectToView);
+
+ view.resize(1200, 600);
+ view.show();
+
+ return app.exec();
+}
+
+#include "particles.moc"