aboutsummaryrefslogtreecommitdiffstats
path: root/src/quickcl/qquickclimagerunnable.cpp
blob: 01a7be10236246a0e1d3f4ccbad447fd4ef4b67a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/****************************************************************************
**
** 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 "qquickclimagerunnable.h"
#include "qquickclitem.h"
#include <QSGSimpleTextureNode>
#include <QSGTextureProvider>
#include <QOpenGLTexture>
#include <QOpenGLFunctions>

QT_BEGIN_NAMESPACE

/*!
    \class QQuickCLImageRunnable
    \brief A QQuickCLItem backend specialized for operating on a single texture from the scenegraph.

    Specialized QQuickCLRunnable for applications wishing to perform
    OpenCL operations on an OpenCL image object wrapping an OpenGL texture of
    an Image element - or any other texture provider in the Qt Quick scene -
    and show the result in the scene.

    The class provides an OpenCL command queue and a simple texture node for
    the scenegraph. The item providing the texture is read from the associated
    QQuickCLItem's \c source property by default. This can be overridden by
    calling setSourcePropertyName().

    By using this specialized class instead of the more generic base
    QQuickCLRunnable, applications can focus on the kernels and there is no
    need to manually manage OpenCL image objects, textures, and scenegraph nodes.

    For example, assuming a QQuickCLItem subclass named CLItem, running OpenCL
    kernels on an image, producing a new output image rendered by CLItem in an
    accelerated manner without any CPU side readbacks, becomes as simple as the
    following:

    \badcode
        Image {
            id: srcImage
            source: "image.png"
        }
        ...
        CLItem {
            source: srcImage
        }
    \endcode

    The source can be any texture provider. By enabling layering, the OpenCL
    kernels can operate on the rendering of an entire sub-tree instead of just
    a single Image item. In addition, this approach also allows hiding the
    source sub-tree:

    \badcode
        Item {
            id: srcItem
            layer.enabled: true
            visible: false
            ...
        }
        CLItem {
            source: srcItem
        }
    \endcode

    Besides image processing scenarios it is also possible to use
    QQuickCLImageRunnable for computations that produce arbitrary data from an
    image (for example histogram calculation). Passing the flag NoImageOutput to
    the constructor will avoid generating an OpenGL texture and corresponding
    OpenCL image object for the output. Instead, it is up to the runKernel()
    implementation to emit a signal on the associated QQuickCLItem and pass an
    object exposing the results of the computation to QML. The visualization is
    then done by child items since the QQuickCLItem itself does not render
    anything in the Qt Quick scenegraph in this case, although it is still
    present as an item having contents.
 */

/*!
    \fn void QQuickCLImageRunnable::runKernel(cl_mem inImage, cl_mem outImage, const QSize &size)

    Called when the OpenCL kernel(s) performing the image processing need to be
    run. \a inImage and \a outImage are ready to be used as input and output
    \c image2d_t parameters to a kernel. \a size specifies the size of the images.

    \note For QQuickCLImageRunnable instances created with the NoImageOutput
    flag \a outImage is always \c 0.

    \note QQuickCLImageRunnable is aware of \c cl_khr_gl_event and will invoke
    glFinish() and clFinish() as necessary in case the extension is not
    supported. Both will be omitted when the extension is present. However,
    clFinish() is still invoked regardless of the presence of the extension when
    either the \c ForceCLFinish or \c Profile flags are set.
 */

class QQuickCLImageRunnablePrivate
{
public:
    QQuickCLImageRunnablePrivate(QQuickCLItem *item, QQuickCLImageRunnable::Flags flags)
        : item(item),
          flags(flags),
          queue(0),
          inputTexture(0),
          outputTexture(0),
          elapsed(0)
    {
        image[0] = image[1] = 0;
        profEv[0] = profEv[1] = 0;
        sourcePropertyName = QByteArrayLiteral("source");
    }

    ~QQuickCLImageRunnablePrivate() {
        if (image[0])
            clReleaseMemObject(image[0]);
        if (image[1])
            clReleaseMemObject(image[1]);
        if (queue)
            clReleaseCommandQueue(queue);
        delete outputTexture;
    }

    QQuickCLItem *item;
    QQuickCLImageRunnable::Flags flags;
    cl_command_queue queue;
    cl_mem image[2];
    QSize textureSize;
    uint inputTexture;
    QOpenGLTexture *outputTexture;
    QByteArray sourcePropertyName;
    cl_event profEv[2];
    double elapsed;
    bool needsExplicitSync;
};

/*!
    Constructs a new QQuickCLImageRunnable instance associated with \a item.
    Special behavior, for example computations producing arbitrary non-image
    output, can be enabled via \a flags.
 */
QQuickCLImageRunnable::QQuickCLImageRunnable(QQuickCLItem *item, Flags flags)
    : d_ptr(new QQuickCLImageRunnablePrivate(item, flags))
{
    Q_D(QQuickCLImageRunnable);
    cl_int err;
    cl_command_queue_properties queueProps = flags.testFlag(Profile) ? CL_QUEUE_PROFILING_ENABLE : 0;
    d->queue = clCreateCommandQueue(item->context(), item->device(), queueProps, &err);
    if (!d->queue) {
        qWarning("Failed to create OpenCL command queue: %d", err);
        return;
    }
    d->needsExplicitSync = !item->deviceExtensions().contains(QByteArrayLiteral("cl_khr_gl_event"));
}

QQuickCLImageRunnable::~QQuickCLImageRunnable()
{
    delete d_ptr;
}

/*!
    \return the OpenCL command queue.
 */
cl_command_queue QQuickCLImageRunnable::commandQueue() const
{
    Q_D(const QQuickCLImageRunnable);
    return d->queue;
}

/*!
    Sets the name of the property that is queried from the item that was passed
    to the constructor. The default value is \c source.
 */
void QQuickCLImageRunnable::setSourcePropertyName(const QByteArray &name)
{
    Q_D(QQuickCLImageRunnable);
    d->sourcePropertyName = name;
}

QSGNode *QQuickCLImageRunnable::update(QSGNode *node)
{
    Q_D(QQuickCLImageRunnable);
    QSGTextureProvider *textureProvider;
    QSGTexture *texture;
    QQuickItem *source = d->item->property(d->sourcePropertyName.constData()).value<QQuickItem *>();
    if (!source
            || !source->isTextureProvider()
            || !(textureProvider = source->textureProvider())
            || !(texture = textureProvider->texture())) {
        delete node;
        return 0;
    }

    QSGDynamicTexture *dtex = qobject_cast<QSGDynamicTexture *>(texture);
    if (dtex)
        dtex->updateTexture();

    if (!texture->textureId()) { // the texture provider may not be ready yet, try again later
        d->item->scheduleUpdate();
        return node;
    }

    if (d->inputTexture != uint(texture->textureId())
            || d->textureSize != texture->textureSize()
            || (!d->flags.testFlag(NoOutputImage) && !d->outputTexture)) {
        if (d->image[0])
            clReleaseMemObject(d->image[0]);
        d->image[0] = 0;
        if (d->image[1])
            clReleaseMemObject(d->image[1]);
        d->image[1] = 0;
        delete d->outputTexture;
        d->outputTexture = 0;
        delete node;
        node = 0;
    }

    cl_int err = 0;
    if (!d->image[0])
        d->image[0] = clCreateFromGLTexture2D(d->item->context(), CL_MEM_READ_ONLY, GL_TEXTURE_2D, 0,
                                              texture->textureId(), &err);
    if (!d->image[0]) {
        if (err == CL_INVALID_GL_OBJECT) // the texture provider may not be ready yet, try again later
            d->item->scheduleUpdate();
        else
            qWarning("Failed to create OpenCL image object from input OpenGL texture: %d", err);
        return node;
    }

    d->inputTexture = texture->textureId();
    d->textureSize = texture->textureSize();

    const int imageCount = d->flags.testFlag(NoOutputImage) ? 1 : 2;
    if (imageCount == 2) {
        if (!d->outputTexture)
            d->outputTexture = new QOpenGLTexture(QImage(d->textureSize, QImage::Format_RGB32));

        if (!d->image[1])
            d->image[1] = clCreateFromGLTexture2D(d->item->context(), CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0,
                                                  d->outputTexture->textureId(), &err);
        if (!d->image[1]) {
            qWarning("Failed to create OpenCL image object for output OpenGL texture: %d", err);
            return node;
        }
    }

    if (d->needsExplicitSync)
        QOpenGLContext::currentContext()->functions()->glFinish();

    err = clEnqueueAcquireGLObjects(d->queue, imageCount, d->image, 0, 0, 0);
    if (err != CL_SUCCESS) {
        qWarning("Failed to queue acquiring the GL textures: %d", err);
        return node;
    }

    if (d->flags.testFlag(Profile))
        if (clEnqueueMarker(d->queue, &d->profEv[0]) != CL_SUCCESS)
            qWarning("Failed to enqueue profiling marker (start)");

    runKernel(d->image[0], d->image[1], d->textureSize);

    if (d->flags.testFlag(Profile))
        if (clEnqueueMarker(d->queue, &d->profEv[1]) != CL_SUCCESS)
            qWarning("Failed to enqueue profiling marker (end)");

    clEnqueueReleaseGLObjects(d->queue, imageCount, d->image, 0, 0, 0);

    if (d->flags.testFlag(ForceCLFinish) || d->needsExplicitSync || d->flags.testFlag(Profile))
        clFinish(d->queue);

    if (d->flags.testFlag(Profile)) {
        cl_ulong start = 0, end = 0;
        err = clGetEventProfilingInfo(d->profEv[0], CL_PROFILING_COMMAND_QUEUED, sizeof(cl_ulong), &start, 0);
        if (err != CL_SUCCESS)
            qWarning("Failed to get profiling info for start event: %d", err);
        err = clGetEventProfilingInfo(d->profEv[1], CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &end, 0);
        if (err != CL_SUCCESS)
            qWarning("Failed to get profiling info for end event: %d", err);
        d->elapsed = double(end - start) / 1000000.0;
        clReleaseEvent(d->profEv[0]);
        clReleaseEvent(d->profEv[1]);
    }

    if (imageCount == 1)
        return 0;

    QSGSimpleTextureNode *tnode = static_cast<QSGSimpleTextureNode *>(node);
    if (!tnode) {
        tnode = new QSGSimpleTextureNode;
        tnode->setFiltering(QSGTexture::Linear);
        tnode->setTexture(d->item->window()->createTextureFromId(d->outputTexture->textureId(), d->textureSize));
    }
    tnode->setRect(d->item->boundingRect());
    tnode->markDirty(QSGNode::DirtyMaterial);

    return tnode;
}

/*!
    Returns the number of milliseconds spent on OpenCL operations during the
    last finished invocation of runKernel().

    \note OpenCL command queue profiling must be enabled by passing the \c Profile
    flag to the constructor.
 */
double QQuickCLImageRunnable::elapsed() const
{
    Q_D(const QQuickCLImageRunnable);
    return d->elapsed;
}

QT_END_NAMESPACE