1/****************************************************************************
2**
3** Copyright (C) 2014 Gunnar Sletta <gunnar@sletta.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "xorblender.h"
52
53#include <QtCore/QPointer>
54
55#include <QtGui/QOpenGLContext>
56#include <QtGui/QOpenGLFunctions>
57
58#include <QtQuick/QSGMaterial>
59#include <QtQuick/QSGTexture>
60#include <QtQuick/QSGGeometryNode>
61#include <QtQuick/QSGTextureProvider>
62
63/* This example could just as well have been implemented all in QML using
64 * a ShaderEffect, and for 90% of all usecases, using a ShaderEffect will
65 * be sufficient. This example exists to illustrate how to consume
66 * texture providers from C++ and how to use multiple texture sources in
67 * a custom material.
68 */
69
70class XorBlendMaterial : public QSGMaterial
71{
72public:
73 XorBlendMaterial();
74 QSGMaterialType *type() const override;
75 QSGMaterialShader *createShader() const override;
76 int compare(const QSGMaterial *other) const override;
77
78 struct {
79 QSGTexture *texture1 = nullptr;
80 QSGTexture *texture2 = nullptr;
81 } state;
82};
83
84class XorBlendShader : public QSGMaterialShader // for when the scenegraph is using OpenGL directly
85{
86public:
87 XorBlendShader();
88 void initialize() override;
89 char const *const *attributeNames() const override;
90 void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *oldEffect) override;
91
92private:
93 int m_matrix_id;
94 int m_opacity_id;
95};
96
97class XorBlendRhiShader : public QSGMaterialRhiShader // for when the scenegraph is using QRhi
98{
99public:
100 XorBlendRhiShader();
101 bool updateUniformData(RenderState &state,
102 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
103 void updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
104 QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override;
105};
106
107XorBlendMaterial::XorBlendMaterial()
108{
109 setFlag(flags: SupportsRhiShader);
110 setFlag(flags: Blending);
111}
112
113QSGMaterialShader *XorBlendMaterial::createShader() const
114{
115 if (flags().testFlag(flag: RhiShaderWanted))
116 return new XorBlendRhiShader;
117 else
118 return new XorBlendShader;
119}
120
121QSGMaterialType *XorBlendMaterial::type() const
122{
123 static QSGMaterialType type;
124 return &type;
125}
126
127int XorBlendMaterial::compare(const QSGMaterial *o) const
128{
129 Q_ASSERT(o && type() == o->type());
130 const XorBlendMaterial *other = static_cast<const XorBlendMaterial *>(o);
131
132 if (!state.texture1 || !other->state.texture1)
133 return state.texture1 ? 1 : -1;
134
135 if (!state.texture2 || !other->state.texture2)
136 return state.texture2 ? -1 : 1;
137
138 if (int diff = state.texture1->comparisonKey() - other->state.texture1->comparisonKey())
139 return diff;
140
141 if (int diff = state.texture2->comparisonKey() - other->state.texture2->comparisonKey())
142 return diff;
143
144 return 0;
145}
146
147XorBlendShader::XorBlendShader()
148{
149 setShaderSourceFile(type: QOpenGLShader::Vertex, sourceFile: QLatin1String(":/scenegraph/twotextureproviders/shaders/xorblender.vert"));
150 setShaderSourceFile(type: QOpenGLShader::Fragment, sourceFile: QLatin1String(":/scenegraph/twotextureproviders/shaders/xorblender.frag"));
151}
152
153void XorBlendShader::initialize()
154{
155 m_matrix_id = program()->uniformLocation(name: "qt_Matrix");
156 m_opacity_id = program()->uniformLocation(name: "qt_Opacity");
157 // The texture units never change, only the textures we bind to them so
158 // we set these once and for all here.
159 program()->setUniformValue(name: "uSource1", value: 0); // GL_TEXTURE0
160 program()->setUniformValue(name: "uSource2", value: 1); // GL_TEXTURE1
161}
162
163char const *const *XorBlendShader::attributeNames() const
164{
165 static char const *const attr[] = { "aVertex", "aTexCoord", nullptr };
166 return attr;
167}
168
169void XorBlendShader::updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *)
170{
171 XorBlendMaterial *material = static_cast<XorBlendMaterial *>(newEffect);
172
173 if (state.isMatrixDirty())
174 program()->setUniformValue(location: m_matrix_id, value: state.combinedMatrix());
175
176 if (state.isOpacityDirty())
177 program()->setUniformValue(location: m_opacity_id, value: state.opacity());
178
179 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
180 // We bind the textures in inverse order so that we leave the updateState
181 // function with GL_TEXTURE0 as the active texture unit. This is maintain
182 // the "contract" that updateState should not mess up the GL state beyond
183 // what is needed for this material.
184 f->glActiveTexture(GL_TEXTURE1);
185 material->state.texture2->bind();
186 f->glActiveTexture(GL_TEXTURE0);
187 material->state.texture1->bind();
188}
189
190XorBlendRhiShader::XorBlendRhiShader()
191{
192 setShaderFileName(stage: VertexStage, filename: QLatin1String(":/scenegraph/twotextureproviders/shaders/+qsb/xorblender.vert"));
193 setShaderFileName(stage: FragmentStage, filename: QLatin1String(":/scenegraph/twotextureproviders/shaders/+qsb/xorblender.frag"));
194}
195
196bool XorBlendRhiShader::updateUniformData(RenderState &state, QSGMaterial *, QSGMaterial *)
197{
198 bool changed = false;
199 QByteArray *buf = state.uniformData();
200 Q_ASSERT(buf->size() >= 68);
201
202 if (state.isMatrixDirty()) {
203 const QMatrix4x4 m = state.combinedMatrix();
204 memcpy(dest: buf->data(), src: m.constData(), n: 64);
205 changed = true;
206 }
207
208 if (state.isOpacityDirty()) {
209 const float opacity = state.opacity();
210 memcpy(dest: buf->data() + 64, src: &opacity, n: 4);
211 changed = true;
212 }
213
214 return changed;
215}
216
217void XorBlendRhiShader::updateSampledImage(RenderState &state, int binding, QSGTexture **texture,
218 QSGMaterial *newMaterial, QSGMaterial *)
219{
220 Q_UNUSED(state);
221
222 XorBlendMaterial *mat = static_cast<XorBlendMaterial *>(newMaterial);
223 switch (binding) { // the binding for the sampler2Ds in the fragment shader
224 case 1:
225 *texture = mat->state.texture1;
226 break;
227 case 2:
228 *texture = mat->state.texture2;
229 break;
230 default:
231 return;
232 }
233}
234
235/* The rendering is split into two nodes. The top-most node is not actually
236 * rendering anything, but is responsible for managing the texture providers.
237 * The XorNode also has a geometry node internally which it uses to render
238 * the texture providers using the XorBlendShader when all providers and
239 * textures are all present.
240 *
241 * The texture providers are updated solely on the render thread (when rendering
242 * is happening on a separate thread). This is why we are using preprocess
243 * and direct signals between the the texture providers and the node rather
244 * than updating state in updatePaintNode.
245 */
246class XorNode : public QObject, public QSGNode
247{
248 Q_OBJECT
249public:
250 XorNode(QSGTextureProvider *p1, QSGTextureProvider *p2)
251 : m_provider1(p1)
252 , m_provider2(p2)
253 {
254 setFlag(QSGNode::UsePreprocess, true);
255
256 // Set up material so it is all set for later..
257 m_material = new XorBlendMaterial;
258 m_node.setMaterial(m_material);
259 m_node.setFlag(QSGNode::OwnsMaterial);
260
261 // Set up geometry, actual vertices will be initialized in updatePaintNode
262 m_node.setGeometry(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4));
263 m_node.setFlag(QSGNode::OwnsGeometry);
264
265 // If this node is used as in a shader effect source, we need to propegate
266 // changes that will occur in this node outwards.
267 connect(sender: m_provider1.data(), signal: &QSGTextureProvider::textureChanged, receiver: this, slot: &XorNode::textureChange, type: Qt::DirectConnection);
268 connect(sender: m_provider2.data(), signal: &QSGTextureProvider::textureChanged, receiver: this, slot: &XorNode::textureChange, type: Qt::DirectConnection);
269 }
270
271 void preprocess() override {
272 // Update the textures from the providers, calling into QSGDynamicTexture if required
273 if (m_provider1) {
274 m_material->state.texture1 = m_provider1->texture();
275 if (QSGDynamicTexture *dt1 = qobject_cast<QSGDynamicTexture *>(object: m_material->state.texture1))
276 dt1->updateTexture();
277 }
278 if (m_provider2) {
279 m_material->state.texture2 = m_provider2->texture();
280 if (QSGDynamicTexture *dt2 = qobject_cast<QSGDynamicTexture *>(object: m_material->state.texture2))
281 dt2->updateTexture();
282 }
283
284 // Remove node from the scene graph if it is there and either texture is missing...
285 if (m_node.parent() && (!m_material->state.texture1 || !m_material->state.texture2))
286 removeChildNode(node: &m_node);
287
288 // Add it if it is not already there and both textures are present..
289 else if (!m_node.parent() && m_material->state.texture1 && m_material->state.texture2)
290 appendChildNode(node: &m_node);
291 }
292
293 void setRect(const QRectF &rect) {
294 // Update geometry if it has changed and mark the change in the scene graph.
295 if (m_rect != rect) {
296 m_rect = rect;
297 QSGGeometry::updateTexturedRectGeometry(g: m_node.geometry(), rect: m_rect, sourceRect: QRectF(0, 0, 1, 1));
298 m_node.markDirty(bits: QSGNode::DirtyGeometry);
299 }
300 }
301
302public slots:
303 void textureChange() {
304 // When our sources change, we will look different, so signal the change to the
305 // scene graph.
306 markDirty(bits: QSGNode::DirtyMaterial);
307 }
308
309private:
310 QRectF m_rect;
311 XorBlendMaterial *m_material;
312 QSGGeometryNode m_node;
313 QPointer<QSGTextureProvider> m_provider1;
314 QPointer<QSGTextureProvider> m_provider2;
315};
316
317XorBlender::XorBlender(QQuickItem *parent)
318 : QQuickItem(parent)
319 , m_source1(nullptr)
320 , m_source2(nullptr)
321 , m_source1Changed(false)
322 , m_source2Changed(false)
323{
324 setFlag(flag: ItemHasContents, enabled: true);
325}
326
327void XorBlender::setSource1(QQuickItem *i)
328{
329 if (i == m_source1)
330 return;
331 m_source1 = i;
332 emit source1Changed(item: m_source1);
333 m_source1Changed = true;
334 update();
335}
336
337void XorBlender::setSource2(QQuickItem *i)
338{
339 if (i == m_source2)
340 return;
341 m_source2 = i;
342 emit source2Changed(item: m_source2);
343 m_source2Changed = true;
344 update();
345}
346
347QSGNode *XorBlender::updatePaintNode(QSGNode *old, UpdatePaintNodeData *)
348{
349 // Check if our input is valid and abort if not, deleting the old node.
350 bool abort = false;
351 if (!m_source1 || !m_source1->isTextureProvider()) {
352 qDebug() << "source1 is missing or not a texture provider";
353 abort = true;
354 }
355 if (!m_source2 || !m_source2->isTextureProvider()) {
356 qDebug() << "source2 is missing or not a texture provider";
357 abort = true;
358 }
359 if (abort) {
360 delete old;
361 return nullptr;
362 }
363
364 XorNode *node = static_cast<XorNode *>(old);
365
366 // If the sources have changed, recreate the nodes
367 if (m_source1Changed || m_source2Changed) {
368 delete node;
369 node = nullptr;
370 m_source1Changed = false;
371 m_source2Changed = false;
372 }
373
374 // Create a new XorNode for us to render with.
375 if (!node)
376 node = new XorNode(m_source1->textureProvider(), m_source2->textureProvider());
377
378 // Update the geometry of the node to match the new bounding rect
379 node->setRect(boundingRect());
380
381 return node;
382}
383
384#include "xorblender.moc"
385

source code of qtdeclarative/examples/quick/scenegraph/twotextureproviders/xorblender.cpp