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(QIODevice::ReadOnly)) {
75 qWarning() << "Failed to find shader" << filename;
76 return QShader();
77 }
78 return QShader::fromSerialized(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 0;
101}
102
103void QSGMaterialRhiShaderPrivate::prepare(QShader::Variant vertexShaderVariant)
104{
105 ubufBinding = -1;
106 ubufSize = 0;
107 ubufStages = 0;
108 memset(combinedImageSamplerBindings, 0, 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(stage);
116 if (it != shaderFileNames.end()) {
117 QString fn = *it;
118 const QShader s = loadShader(*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(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(v.location);
143 }
144 }
145
146 if (vsIt->vertexInputLocations.contains(vsIt->qt_order_attrib_location)) {
147 qWarning("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("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(it->shader.stage());
167 masterUniformData.fill('\0', ubufSize);
168 } else if (ubufBinding == ubuf.binding && ubuf.binding >= 0) {
169 if (ubuf.size > ubufSize) {
170 ubufSize = ubuf.size;
171 masterUniformData.fill('\0', ubufSize);
172 }
173 ubufStages |= toSrbStage(it->shader.stage());
174 } else {
175 qWarning("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(it->shader.stage());
185 else
186 qWarning("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("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(const 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 \c{*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(const 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(const 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 Returns the accumulated opacity to be used for rendering.
430 */
431float QSGMaterialRhiShader::RenderState::opacity() const
432{
433 Q_ASSERT(m_data);
434 return static_cast<const QSGRenderer *>(m_data)->currentOpacity();
435}
436
437/*!
438 Returns the modelview determinant to be used for rendering.
439 */
440float QSGMaterialRhiShader::RenderState::determinant() const
441{
442 Q_ASSERT(m_data);
443 return static_cast<const QSGRenderer *>(m_data)->determinant();
444}
445
446/*!
447 Returns the matrix combined of modelview matrix and project matrix.
448 */
449QMatrix4x4 QSGMaterialRhiShader::RenderState::combinedMatrix() const
450{
451 Q_ASSERT(m_data);
452 return static_cast<const QSGRenderer *>(m_data)->currentCombinedMatrix();
453}
454
455/*!
456 Returns the ratio between physical pixels and device-independent pixels
457 to be used for rendering.
458*/
459float QSGMaterialRhiShader::RenderState::devicePixelRatio() const
460{
461 Q_ASSERT(m_data);
462 return static_cast<const QSGRenderer *>(m_data)->devicePixelRatio();
463}
464
465/*!
466 Returns the model view matrix.
467
468 If the material has the RequiresFullMatrix flag set, this is guaranteed to
469 be the complete transform matrix calculated from the scenegraph.
470
471 However, if this flag is not set, the renderer may choose to alter this
472 matrix. For example, it may pre-transform vertices on the CPU and set this
473 matrix to identity.
474
475 In a situation such as the above, it is still possible to retrieve the
476 actual matrix determinant by setting the RequiresDeterminant flag in the
477 material and calling the determinant() accessor.
478 */
479QMatrix4x4 QSGMaterialRhiShader::RenderState::modelViewMatrix() const
480{
481 Q_ASSERT(m_data);
482 return static_cast<const QSGRenderer *>(m_data)->currentModelViewMatrix();
483}
484
485/*!
486 Returns the projection matrix.
487 */
488QMatrix4x4 QSGMaterialRhiShader::RenderState::projectionMatrix() const
489{
490 Q_ASSERT(m_data);
491 return static_cast<const QSGRenderer *>(m_data)->currentProjectionMatrix();
492}
493
494/*!
495 Returns the viewport rect of the surface being rendered to.
496 */
497QRect QSGMaterialRhiShader::RenderState::viewportRect() const
498{
499 Q_ASSERT(m_data);
500 return static_cast<const QSGRenderer *>(m_data)->viewportRect();
501}
502
503/*!
504 Returns the device rect of the surface being rendered to
505 */
506QRect QSGMaterialRhiShader::RenderState::deviceRect() const
507{
508 Q_ASSERT(m_data);
509 return static_cast<const QSGRenderer *>(m_data)->deviceRect();
510}
511
512/*!
513 Returns a pointer to the data for the uniform (constant) buffer in the
514 shader.
515
516 \note It is strongly recommended to declare the uniform block with \c
517 std140 in the shader, and to carefully study the standard uniform block
518 layout as described in section 7.6.2.2 of the OpenGL specification. It is
519 up to the QSGMaterialRhiShader implementation to ensure data gets placed
520 at the right location in this QByteArray, taking alignment requirements
521 into account. Shader code translated to other shading languages is expected
522 to use the same offsets for block members, even when the target language
523 uses different packing rules by default.
524
525 \note Avoid copying from C++ POD types, such as, structs, in order to
526 update multiple members at once, unless it has been verified that the
527 layouts of the C++ struct and the GLSL uniform block match.
528
529 \note Uniform data must only be updated from
530 QSGMaterialRhiShader::updateUniformData().
531 */
532QByteArray *QSGMaterialRhiShader::RenderState::uniformData() const
533{
534 Q_ASSERT(m_data);
535 return static_cast<const QSGRenderer *>(m_data)->currentUniformData();
536}
537
538/*!
539 Returns a resource update batch to which upload and copy operatoins can be
540 queued. This is typically used by
541 QSGMaterialRhiShader::updateSampledImage() to enqueue texture image
542 content updates.
543 */
544QRhiResourceUpdateBatch *QSGMaterialRhiShader::RenderState::resourceUpdateBatch() const
545{
546 Q_ASSERT(m_data);
547 return static_cast<const QSGRenderer *>(m_data)->currentResourceUpdateBatch();
548}
549
550/*!
551 Returns the current QRhi.
552 */
553QRhi *QSGMaterialRhiShader::RenderState::rhi() const
554{
555 Q_ASSERT(m_data);
556 return static_cast<const QSGRenderer *>(m_data)->currentRhi();
557}
558
559char const *const *QSGMaterialRhiShader::attributeNames() const
560{
561 Q_ASSERT_X(false, "QSGMaterialRhiShader::attributeNames()", "Not implemented for RHI");
562 return nullptr;
563}
564
565QT_END_NAMESPACE
566