1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qsgmaterial.h"
41#include "qsgrenderer_p.h"
42#include "qsgmaterialrhishader_p.h"
43#include <QtCore/QFile>
44
45QT_BEGIN_NAMESPACE
46
47/*!
48 \class QSGMaterialRhiShader
49 \brief The QSGMaterialRhiShader class represents a graphics API independent shader program.
50 \inmodule QtQuick
51 \ingroup qtquick-scenegraph-materials
52 \since 5.14
53
54 QSGMaterialRhiShader is a modern, cross-platform alternative to
55 QSGMaterialShader. The latter is tied to OpenGL and GLSL by design, whereas
56 QSGMaterialRhiShader is based on QShader, a container for multiple
57 versions of a graphics shader together with reflection information.
58
59 \note All classes with QSG prefix should be used solely on the scene graph's
60 rendering thread. See \l {Scene Graph and Rendering} for more information.
61 */
62
63/*!
64 \enum QSGMaterialRhiShader::Flag
65 Flag values to indicate special material properties.
66
67 \value UpdatesGraphicsPipelineState Setting this flag enables calling
68 updateGraphicsPipelineState().
69 */
70
71QShader QSGMaterialRhiShaderPrivate::loadShader(const QString &filename)
72{
73 QFile f(filename);
74 if (!f.open(flags: QIODevice::ReadOnly)) {
75 qWarning() << "Failed to find shader" << filename;
76 return QShader();
77 }
78 return QShader::fromSerialized(data: f.readAll());
79}
80
81void QSGMaterialRhiShaderPrivate::clearCachedRendererData()
82{
83 for (int i = 0; i < MAX_SHADER_RESOURCE_BINDINGS; ++i)
84 textureBindingTable[i] = nullptr;
85 for (int i = 0; i < MAX_SHADER_RESOURCE_BINDINGS; ++i)
86 samplerBindingTable[i] = nullptr;
87}
88
89static inline QRhiShaderResourceBinding::StageFlags toSrbStage(QShader::Stage stage)
90{
91 switch (stage) {
92 case QShader::VertexStage:
93 return QRhiShaderResourceBinding::VertexStage;
94 case QShader::FragmentStage:
95 return QRhiShaderResourceBinding::FragmentStage;
96 default:
97 Q_UNREACHABLE();
98 break;
99 }
100 return { };
101}
102
103void QSGMaterialRhiShaderPrivate::prepare(QShader::Variant vertexShaderVariant)
104{
105 ubufBinding = -1;
106 ubufSize = 0;
107 ubufStages = { };
108 memset(s: combinedImageSamplerBindings, c: 0, n: sizeof(combinedImageSamplerBindings));
109 vertexShader = fragmentShader = nullptr;
110 masterUniformData.clear();
111
112 clearCachedRendererData();
113
114 for (QShader::Stage stage : { QShader::VertexStage, QShader::FragmentStage }) {
115 auto it = shaderFileNames.find(akey: stage);
116 if (it != shaderFileNames.end()) {
117 QString fn = *it;
118 const QShader s = loadShader(filename: *it);
119 if (!s.isValid())
120 continue;
121 shaders[stage] = ShaderStageData(s);
122 // load only once, subsequent prepare() calls will have it all in shaders already
123 shaderFileNames.erase(it);
124 }
125 }
126
127 auto vsIt = shaders.find(akey: QShader::VertexStage);
128 if (vsIt != shaders.end()) {
129 vsIt->shaderVariant = vertexShaderVariant;
130 vsIt->vertexInputLocations.clear();
131 vsIt->qt_order_attrib_location = -1;
132
133 const QShaderDescription desc = vsIt->shader.description();
134 const QVector<QShaderDescription::InOutVariable> vertexInputs = desc.inputVariables();
135 for (const QShaderDescription::InOutVariable &v : vertexInputs) {
136 const QByteArray name = v.name.toUtf8();
137 if (vertexShaderVariant == QShader::BatchableVertexShader
138 && name == QByteArrayLiteral("_qt_order"))
139 {
140 vsIt->qt_order_attrib_location = v.location;
141 } else {
142 vsIt->vertexInputLocations.append(t: v.location);
143 }
144 }
145
146 if (vsIt->vertexInputLocations.contains(t: vsIt->qt_order_attrib_location)) {
147 qWarning(msg: "Vertex input clash in rewritten (batchable) vertex shader at input location %d. "
148 "Vertex shaders must avoid using this location.", vsIt->qt_order_attrib_location);
149 }
150 }
151
152 for (auto it = shaders.begin(); it != shaders.end(); ++it) {
153 const QShaderDescription desc = it->shader.description();
154
155 const QVector<QShaderDescription::UniformBlock> ubufs = desc.uniformBlocks();
156 const int ubufCount = ubufs.count();
157 if (ubufCount > 1) {
158 qWarning(msg: "Multiple uniform blocks found in shader. "
159 "This should be avoided as Qt Quick supports only one.");
160 }
161 for (int i = 0; i < ubufCount; ++i) {
162 const QShaderDescription::UniformBlock &ubuf(ubufs[i]);
163 if (ubufBinding == -1 && ubuf.binding >= 0) {
164 ubufBinding = ubuf.binding;
165 ubufSize = ubuf.size;
166 ubufStages |= toSrbStage(stage: it->shader.stage());
167 masterUniformData.fill(c: '\0', size: ubufSize);
168 } else if (ubufBinding == ubuf.binding && ubuf.binding >= 0) {
169 if (ubuf.size > ubufSize) {
170 ubufSize = ubuf.size;
171 masterUniformData.fill(c: '\0', size: ubufSize);
172 }
173 ubufStages |= toSrbStage(stage: it->shader.stage());
174 } else {
175 qWarning(msg: "Uniform block %s (binding %d) ignored", qPrintable(ubuf.blockName), ubuf.binding);
176 }
177 }
178
179 const QVector<QShaderDescription::InOutVariable> imageSamplers = desc.combinedImageSamplers();
180 const int imageSamplersCount = imageSamplers.count();
181 for (int i = 0; i < imageSamplersCount; ++i) {
182 const QShaderDescription::InOutVariable &var(imageSamplers[i]);
183 if (var.binding >= 0 && var.binding < MAX_SHADER_RESOURCE_BINDINGS)
184 combinedImageSamplerBindings[var.binding] |= toSrbStage(stage: it->shader.stage());
185 else
186 qWarning(msg: "Encountered invalid combined image sampler (%s) binding %d",
187 qPrintable(var.name), var.binding);
188 }
189
190 if (it.key() == QShader::VertexStage)
191 vertexShader = &it.value();
192 else if (it.key() == QShader::FragmentStage)
193 fragmentShader = &it.value();
194 }
195
196 if (vertexShader && vertexShaderVariant == QShader::BatchableVertexShader && vertexShader->qt_order_attrib_location == -1)
197 qWarning(msg: "No rewriter-inserted attribute found, this should not happen.");
198}
199
200/*!
201 Constructs a new QSGMaterialRhiShader.
202 */
203QSGMaterialRhiShader::QSGMaterialRhiShader()
204 : d_ptr(new QSGMaterialRhiShaderPrivate(this))
205{
206}
207
208/*!
209 \internal
210 */
211QSGMaterialRhiShader::QSGMaterialRhiShader(QSGMaterialRhiShaderPrivate &dd)
212 : d_ptr(&dd)
213{
214}
215
216/*!
217 \internal
218 */
219QSGMaterialRhiShader::~QSGMaterialRhiShader()
220{
221}
222
223// We have our own enum as QShader is not initially public. Internally
224// everything works with QShader::Stage however. So convert.
225static inline QShader::Stage toShaderStage(QSGMaterialRhiShader::Stage stage)
226{
227 switch (stage) {
228 case QSGMaterialRhiShader::VertexStage:
229 return QShader::VertexStage;
230 case QSGMaterialRhiShader::FragmentStage:
231 return QShader::FragmentStage;
232 default:
233 Q_UNREACHABLE();
234 return QShader::VertexStage;
235 }
236}
237
238/*!
239 Sets the \a shader for the specified \a stage.
240 */
241void QSGMaterialRhiShader::setShader(Stage stage, const QShader &shader)
242{
243 Q_D(QSGMaterialRhiShader);
244 d->shaders[toShaderStage(stage)] = QSGMaterialRhiShaderPrivate::ShaderStageData(shader);
245}
246
247/*!
248 Sets the \a filename for the shader for the specified \a stage.
249
250 The file is expected to contain a serialized QRhiShader.
251 */
252void QSGMaterialRhiShader::setShaderFileName(Stage stage, const QString &filename)
253{
254 Q_D(QSGMaterialRhiShader);
255 d->shaderFileNames[toShaderStage(stage)] = filename;
256}
257
258/*!
259 \return the currently set flags for this material shader.
260 */
261QSGMaterialRhiShader::Flags QSGMaterialRhiShader::flags() const
262{
263 Q_D(const QSGMaterialRhiShader);
264 return d->flags;
265}
266
267/*!
268 Sets the \a flags on this material shader if \a on is true;
269 otherwise clears the specified flags.
270*/
271void QSGMaterialRhiShader::setFlag(Flags flags, bool on)
272{
273 Q_D(QSGMaterialRhiShader);
274 if (on)
275 d->flags |= flags;
276 else
277 d->flags &= ~flags;
278}
279
280/*!
281 This function is called by the scene graph to get the contents of the
282 shader program's uniform buffer updated. The implementation is not expected
283 to perform any real graphics operations, it is merely responsible for
284 copying data to the QByteArray returned from RenderState::uniformData().
285 The scene graph takes care of making that buffer visible in the shaders.
286
287 The current rendering \a state is passed from the scene graph. If the state
288 indicates that any relevant state is dirty, the implementation must update
289 the appropriate region in the buffer data that is accessible via
290 RenderState::uniformData(). When a state, such as, matrix or opacity, is
291 not dirty, there is no need to touch the corresponding region since the
292 data is persistent.
293
294 The return value must be \c true whenever any change was made to the uniform data.
295
296 The subclass specific state, such as the color of a flat color material,
297 should be extracted from \a newMaterial to update the relevant regions in
298 the buffer accordingly.
299
300 \a oldMaterial can be used to minimize buffer changes (which are typically
301 memcpy calls) when updating material states. When \a oldMaterial is null,
302 this shader was just activated.
303 */
304bool QSGMaterialRhiShader::updateUniformData(RenderState &state,
305 QSGMaterial *newMaterial,
306 QSGMaterial *oldMaterial)
307{
308 Q_UNUSED(state);
309 Q_UNUSED(newMaterial);
310 Q_UNUSED(oldMaterial);
311 return false;
312}
313
314/*!
315 This function is called by the scene graph to prepare using a sampled image
316 in the shader, typically in form of a combined image sampler.
317
318 \a binding is the binding number of the sampler. The function is called for
319 each variable in the material's shaders'
320 \l{QShaderDescription::combinedImageSamplers()}.
321
322 When *\a{texture} is null, it must be set to a QSGTexture pointer before
323 returning. When non-null, it is up to the material to decide if a new
324 \c{QSGTexture *} is stored to it, or if it updates some parameters on the
325 already known QSGTexture. The ownership of the QSGTexture is not
326 transferred.
327
328 The current rendering \a state is passed from the scene graph. It is up to
329 the material to enqueue the texture data uploads to the
330 QRhiResourceUpdateBatch retriveable via RenderState::resourceUpdateBatch().
331
332 The subclass specific state can be extracted from \a newMaterial.
333
334 \a oldMaterial can be used to minimize changes. When \a oldMaterial is null,
335 this shader was just activated.
336 */
337void QSGMaterialRhiShader::updateSampledImage(RenderState &state,
338 int binding,
339 QSGTexture **texture,
340 QSGMaterial *newMaterial,
341 QSGMaterial *oldMaterial)
342{
343 Q_UNUSED(state);
344 Q_UNUSED(binding);
345 Q_UNUSED(texture);
346 Q_UNUSED(newMaterial);
347 Q_UNUSED(oldMaterial);
348}
349
350/*!
351 This function is called by the scene graph to enable the material to
352 provide a custom set of graphics state. The set of states that are
353 customizable by material is limited to blending and related settings.
354
355 \note This function is only called when the UpdatesGraphicsPipelineState
356 flag was enabled via setFlags(). By default it is not set, and so this
357 function is never called.
358
359 The return value must be \c true whenever a change was made to any of the
360 members in \a ps.
361
362 \note The contents of \a ps is not persistent between invocations of this
363 function.
364
365 The current rendering \a state is passed from the scene graph.
366
367 The subclass specific state can be extracted from \a newMaterial. When \a
368 oldMaterial is null, this shader was just activated.
369 */
370bool QSGMaterialRhiShader::updateGraphicsPipelineState(RenderState &state, GraphicsPipelineState *ps,
371 QSGMaterial *newMaterial, QSGMaterial *oldMaterial)
372{
373 Q_UNUSED(state);
374 Q_UNUSED(ps);
375 Q_UNUSED(newMaterial);
376 Q_UNUSED(oldMaterial);
377 return false;
378}
379
380/*!
381 \class QSGMaterialRhiShader::RenderState
382
383 \brief Encapsulates the current rendering state during a call to
384 QSGMaterialRhiShader::updateUniformData() and the other \c update type of
385 functions.
386
387 \inmodule QtQuick
388 \since 5.14
389
390 The render state contains a number of accessors that the shader needs to
391 respect in order to conform to the current state of the scene graph.
392 */
393
394/*!
395 \enum QSGMaterialRhiShader::RenderState::DirtyState
396
397 \value DirtyMatrix Used to indicate that the matrix has changed and must be
398 updated.
399
400 \value DirtyOpacity Used to indicate that the opacity has changed and must
401 be updated.
402
403 \value DirtyAll Used to indicate that everything needs to be updated.
404 */
405
406/*!
407 \fn bool QSGMaterialRhiShader::RenderState::isMatrixDirty() const
408
409 Returns \c true if the dirtyStates() contain the dirty matrix state,
410 otherwise returns \c false.
411 */
412
413/*!
414 \fn bool QSGMaterialRhiShader::RenderState::isOpacityDirty() const
415
416 Returns \c true if the dirtyStates() contains the dirty opacity state,
417 otherwise returns \c false.
418 */
419
420/*!
421 \fn QSGMaterialRhiShader::RenderState::DirtyStates QSGMaterialRhiShader::RenderState::dirtyStates() const
422
423 Returns which rendering states that have changed and needs to be updated
424 for geometry rendered with this material to conform to the current
425 rendering state.
426 */
427
428/*!
429 \class QSGMaterialRhiShader::GraphicsPipelineState
430
431 \brief Describes state changes that the material wants to apply to the
432 currently active graphics pipeline state.
433
434 \inmodule QtQuick
435 \since 5.14
436
437 Unlike QSGMaterialShader, directly issuing state change commands with the
438 underlying graphics API is not possible with QSGMaterialRhiShader. This is
439 mainly because the concept of individually changeable states is considered
440 deprecated and not supported with modern graphics APIs.
441
442 Therefore, it is up to QSGMaterialRhiShader to expose a data structure with
443 the set of supported states, which the material can change in its
444 updatePipelineState() implementation, if there is one. The scenegraph will
445 then internally apply these changes to the active graphics pipeline state,
446 then rolling them back as appropriate.
447 */
448
449/*!
450 \enum QSGMaterialRhiShader::GraphicsPipelineState::BlendFactor
451 \since 5.14
452
453 \value Zero
454 \value One
455 \value SrcColor
456 \value OneMinusSrcColor
457 \value DstColor
458 \value OneMinusDstColor
459 \value SrcAlpha
460 \value OneMinusSrcAlpha
461 \value DstAlpha
462 \value OneMinusDstAlpha
463 \value ConstantColor
464 \value OneMinusConstantColor
465 \value ConstantAlpha
466 \value OneMinusConstantAlpha
467 \value SrcAlphaSaturate
468 \value Src1Color
469 \value OneMinusSrc1Color
470 \value Src1Alpha
471 \value OneMinusSrc1Alpha
472 */
473
474/*!
475 \enum QSGMaterialRhiShader::GraphicsPipelineState::ColorMaskComponent
476 \since 5.14
477
478 \value R
479 \value G
480 \value B
481 \value A
482 */
483
484/*!
485 \enum QSGMaterialRhiShader::GraphicsPipelineState::CullMode
486 \since 5.14
487
488 \value CullNone
489 \value CullFront
490 \value CullBack
491 */
492
493/*!
494 Returns the accumulated opacity to be used for rendering.
495 */
496float QSGMaterialRhiShader::RenderState::opacity() const
497{
498 Q_ASSERT(m_data);
499 return float(static_cast<const QSGRenderer *>(m_data)->currentOpacity());
500}
501
502/*!
503 Returns the modelview determinant to be used for rendering.
504 */
505float QSGMaterialRhiShader::RenderState::determinant() const
506{
507 Q_ASSERT(m_data);
508 return float(static_cast<const QSGRenderer *>(m_data)->determinant());
509}
510
511/*!
512 Returns the matrix combined of modelview matrix and project matrix.
513 */
514QMatrix4x4 QSGMaterialRhiShader::RenderState::combinedMatrix() const
515{
516 Q_ASSERT(m_data);
517 return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix();
518}
519
520/*!
521 Returns the ratio between physical pixels and device-independent pixels
522 to be used for rendering.
523*/
524float QSGMaterialRhiShader::RenderState::devicePixelRatio() const
525{
526 Q_ASSERT(m_data);
527 return float(static_cast<const QSGRenderer *>(m_data)->devicePixelRatio());
528}
529
530/*!
531 Returns the model view matrix.
532
533 If the material has the RequiresFullMatrix flag set, this is guaranteed to
534 be the complete transform matrix calculated from the scenegraph.
535
536 However, if this flag is not set, the renderer may choose to alter this
537 matrix. For example, it may pre-transform vertices on the CPU and set this
538 matrix to identity.
539
540 In a situation such as the above, it is still possible to retrieve the
541 actual matrix determinant by setting the RequiresDeterminant flag in the
542 material and calling the determinant() accessor.
543 */
544QMatrix4x4 QSGMaterialRhiShader::RenderState::modelViewMatrix() const
545{
546 Q_ASSERT(m_data);
547 return static_cast<const QSGRenderer *>(m_data)->currentModelViewMatrix();
548}
549
550/*!
551 Returns the projection matrix.
552 */
553QMatrix4x4 QSGMaterialRhiShader::RenderState::projectionMatrix() const
554{
555 Q_ASSERT(m_data);
556 return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix();
557}
558
559/*!
560 Returns the viewport rect of the surface being rendered to.
561 */
562QRect QSGMaterialRhiShader::RenderState::viewportRect() const
563{
564 Q_ASSERT(m_data);
565 return static_cast<const QSGRenderer *>(m_data)->viewportRect();
566}
567
568/*!
569 Returns the device rect of the surface being rendered to
570 */
571QRect QSGMaterialRhiShader::RenderState::deviceRect() const
572{
573 Q_ASSERT(m_data);
574 return static_cast<const QSGRenderer *>(m_data)->deviceRect();
575}
576
577/*!
578 Returns a pointer to the data for the uniform (constant) buffer in the
579 shader. Uniform data must only be updated from
580 QSGMaterialRhiShader::updateUniformData(). The return value is null in the
581 other reimplementable functions, such as,
582 QSGMaterialRhiShader::updateSampledImage().
583
584 \note It is strongly recommended to declare the uniform block with \c
585 std140 in the shader, and to carefully study the standard uniform block
586 layout as described in section 7.6.2.2 of the OpenGL specification. It is
587 up to the QSGMaterialRhiShader implementation to ensure data gets placed
588 at the right location in this QByteArray, taking alignment requirements
589 into account. Shader code translated to other shading languages is expected
590 to use the same offsets for block members, even when the target language
591 uses different packing rules by default.
592
593 \note Avoid copying from C++ POD types, such as, structs, in order to
594 update multiple members at once, unless it has been verified that the
595 layouts of the C++ struct and the GLSL uniform block match.
596 */
597QByteArray *QSGMaterialRhiShader::RenderState::uniformData()
598{
599 Q_ASSERT(m_data);
600 return static_cast<const QSGRenderer *>(m_data)->currentUniformData();
601}
602
603/*!
604 Returns a resource update batch to which upload and copy operatoins can be
605 queued. This is typically used by
606 QSGMaterialRhiShader::updateSampledImage() to enqueue texture image
607 content updates.
608 */
609QRhiResourceUpdateBatch *QSGMaterialRhiShader::RenderState::resourceUpdateBatch()
610{
611 Q_ASSERT(m_data);
612 return static_cast<const QSGRenderer *>(m_data)->currentResourceUpdateBatch();
613}
614
615/*!
616 Returns the current QRhi.
617 */
618QRhi *QSGMaterialRhiShader::RenderState::rhi()
619{
620 Q_ASSERT(m_data);
621 return static_cast<const QSGRenderer *>(m_data)->currentRhi();
622}
623
624char const *const *QSGMaterialRhiShader::attributeNames() const
625{
626 Q_ASSERT_X(false, "QSGMaterialRhiShader::attributeNames()", "Not implemented for RHI");
627 return nullptr;
628}
629
630QT_END_NAMESPACE
631

source code of qtdeclarative/src/quick/scenegraph/coreapi/qsgmaterialrhishader.cpp