Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssgrhicontext.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5
6#include <QtCore/qvariant.h>
7#include <QtGui/private/qrhi_p.h>
8
9#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
10#include <QtQuick3DUtils/private/qssgmesh_p.h>
11#include <QtQuick3DUtils/private/qssgassert_p.h>
12#include <QtQuick3DRuntimeRender/private/qssgrenderableimage_p.h>
13#include <QtQuick3DRuntimeRender/private/qssgrendermesh_p.h>
14#include <QtQuick3DUtils/private/qssgutils_p.h>
15#include <QtQuick3DUtils/private/qssgassert_p.h>
16#include <qtquick3d_tracepoints_p.h>
17
18#include <QtGui/qquaternion.h>
19
21
22Q_TRACE_POINT(qtquick3d, QSSG_renderPass_entry, const QString &renderPass);
24Q_TRACE_POINT(qtquick3d, QSSG_drawIndexed, int indexCount, int instanceCount);
25Q_TRACE_POINT(qtquick3d, QSSG_draw, int vertexCount, int instanceCount);
26
27/*!
28 \class QSSGRhiContext
29 \inmodule QtQuick3D
30 \since 6.7
31
32 \brief QSSGRhiContext.
33 */
34
35/*!
36 \class QSSGRhiGraphicsPipelineState
37 \inmodule QtQuick3D
38 \since 6.7
39
40 \brief Graphics pipeline state for the spatial scene graph.
41
42 This class is a convenience class used by QtQuick3D to wrap relevant pipeline state from the QRhi classes,
43 like \l QRhiGraphicsPipeline. Most of the types and value used in QSSGRhiGraphicsPipelineState will
44 therefore map directly to an equivalent QRhi type or class.
45 */
46
47/*!
48 \variable QSSGRhiGraphicsPipelineState::samples
49
50 The sample count.
51
52 \note A sample count of 1 means no multisample antialiasing.
53
54 \sa QRhiSwapChain::sampleCount()
55 */
56
57/*!
58 \enum QSSGRhiGraphicsPipelineState::Flag
59 \value DepthTestEnabled
60 \value DepthWriteEnabled
61 \value BlendEnabled
62 \value UsesStencilRef
63 \value UsesScissor
64 */
65
66/*!
67 \variable QSSGRhiGraphicsPipelineState::depthFunc
68
69 The depth comparison function.
70
71 \sa QRhiGraphicsPipeline::CompareOp
72 */
73
74/*!
75 \variable QSSGRhiGraphicsPipelineState::cullMode
76
77 Specifies the culling mode.
78
79 \sa QRhiGraphicsPipeline::CullMode
80 */
81
82/*!
83 \variable QSSGRhiGraphicsPipelineState::stencilOpFrontState
84
85 Describes the stencil operation state.
86
87 \sa QRhiGraphicsPipeline::StencilOpState
88 */
89
90/*!
91 \variable QSSGRhiGraphicsPipelineState::stencilWriteMask
92
93 The stencil write mask value. The default value is \c 0xFF.
94
95 \sa QRhiGraphicsPipeline::stencilWriteMask()
96 */
97
98/*!
99 \variable QSSGRhiGraphicsPipelineState::stencilRef
100
101 The active stencil reference value.
102
103 \note Only used when \l{QSSGRhiGraphicsPipelineState::Flag::}{UsesStencilRef} is set.
104
105 \sa QRhiCommandBuffer::setStencilRef()
106 */
107
108/*!
109 \variable QSSGRhiGraphicsPipelineState::depthBias
110
111 The depth bias. The default value is 0.
112
113 \sa QRhiGraphicsPipeline::depthBias()
114 */
115
116/*!
117 \variable QSSGRhiGraphicsPipelineState::slopeScaledDepthBias
118
119 The slope scaled depth bias. The default value is 0.
120
121 \sa QRhiGraphicsPipeline::slopeScaledDepthBias()
122 */
123
124/*!
125 \variable QSSGRhiGraphicsPipelineState::targetBlend
126
127 The blend state for one color attachment.
128
129 \sa QRhiGraphicsPipeline::TargetBlend
130 */
131
132/*!
133 \variable QSSGRhiGraphicsPipelineState::colorAttachmentCount
134
135 The number of color attachments. The default is 1.
136
137 \sa QRhiTextureRenderTargetDescription::setColorAttachments(),
138 QRhiTextureRenderTargetDescription::colorAttachmentCount()
139 */
140
141/*!
142 \variable QSSGRhiGraphicsPipelineState::viewport
143
144 The viewport dimensions used for rendering.
145 */
146
147/*!
148 \variable QSSGRhiGraphicsPipelineState::scissor
149
150 The scissor rect.
151
152 \note Only used if \l{QSSGRhiGraphicsPipelineState::Flag::}{UsesScissor} is set.
153
154 \sa QRhiCommandBuffer::setScissor()
155 */
156
157/*!
158 \variable QSSGRhiGraphicsPipelineState::lineWidth
159
160 The line width used. The default is 1.0
161
162 \note For values other than 1.0 it's required that feature \l QRhi::WideLines is reported
163 as supported at runtime.
164 */
165
166/*!
167 \variable QSSGRhiGraphicsPipelineState::polygonMode
168
169 The polygon mode value. The default is \l{QRhiGraphicsPipeline::Fill}{Fill}.
170
171 \sa QRhiGraphicsPipeline::polygonMode()
172 */
173
174/*!
175 \struct QSSGRhiSamplerDescription
176 \inmodule QtQuick3D
177 \since 6.7
178
179 \brief QSSGRhiSamplerDescription.
180
181 Convenience class used to request a \l QRhiSampler from QtQuick3D internal cache.
182
183 \note Samplers are owned by QtQuick3D.
184
185 \sa QSSGRhiContext::sampler()
186*/
187
188/*!
189 \variable QSSGRhiSamplerDescription::minFilter
190
191 The minification filter mode.
192
193 \sa QRhiSampler::Filter, QRhiSampler::minFilter()
194 */
195
196/*!
197 \variable QSSGRhiSamplerDescription::magFilter
198
199 The magnification filter mode.
200
201 \sa QRhiSampler::Filter, QRhiSampler::magFilter()
202 */
203
204/*!
205 \variable QSSGRhiSamplerDescription::mipmap
206
207 The mipmap filtering mode.
208
209 \sa QRhiSampler::Filter, QRhiSampler::mipmapMode()
210 */
211
212/*!
213 \variable QSSGRhiSamplerDescription::hTiling
214
215 The horizontal wrap mode.
216
217 \sa QRhiSampler::AddressMode
218 */
219
220/*!
221 \variable QSSGRhiSamplerDescription::vTiling
222
223 The vertical wrap mode.
224
225 \sa QRhiSampler::AddressMode
226 */
227
228/*!
229 \variable QSSGRhiSamplerDescription::zTiling
230
231 The depth wrap mode.
232
233 \sa QRhiSampler::AddressMode
234 */
235
236QSSGRhiBuffer::QSSGRhiBuffer(QSSGRhiContext &context,
237 QRhiBuffer::Type type,
238 QRhiBuffer::UsageFlags usageMask,
239 quint32 stride,
240 qsizetype size,
241 QRhiCommandBuffer::IndexFormat indexFormat)
242 : m_context(context),
243 m_stride(stride),
244 m_indexFormat(indexFormat)
245{
246 QSSG_ASSERT(size >= 0, size = 0);
247 m_buffer = m_context.rhi()->newBuffer(type, usageMask, quint32(size));
248 if (!m_buffer->create())
249 qWarning("Failed to build QRhiBuffer with size %d", m_buffer->size());
250}
251
252QSSGRhiBuffer::~QSSGRhiBuffer()
253{
254 delete m_buffer;
255}
256
257namespace QSSGRhiHelpers {
258QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps)
259{
260 if (compType == QSSGRenderComponentType::Float32) {
261 switch (numComps) {
262 case 1:
263 return QRhiVertexInputAttribute::Float;
264 case 2:
265 return QRhiVertexInputAttribute::Float2;
266 case 3:
267 return QRhiVertexInputAttribute::Float3;
268 case 4:
269 return QRhiVertexInputAttribute::Float4;
270 default:
271 break;
272 }
273 } else if (compType == QSSGRenderComponentType::UnsignedInt32) {
274 switch (numComps) {
275 case 1:
276 return QRhiVertexInputAttribute::UInt;
277 case 2:
278 return QRhiVertexInputAttribute::UInt2;
279 case 3:
280 return QRhiVertexInputAttribute::UInt3;
281 case 4:
282 return QRhiVertexInputAttribute::UInt4;
283 default:
284 break;
285 }
286 } else if (compType == QSSGRenderComponentType::Int32) {
287 switch (numComps) {
288 case 1:
289 return QRhiVertexInputAttribute::SInt;
290 case 2:
291 return QRhiVertexInputAttribute::SInt2;
292 case 3:
293 return QRhiVertexInputAttribute::SInt3;
294 case 4:
295 return QRhiVertexInputAttribute::SInt4;
296 default:
297 break;
298 }
299 }
300 Q_ASSERT(false);
301 return QRhiVertexInputAttribute::Float4;
302}
303
304QRhiGraphicsPipeline::Topology toTopology(QSSGRenderDrawMode drawMode)
305{
306 switch (drawMode) {
307 case QSSGRenderDrawMode::Points:
308 return QRhiGraphicsPipeline::Points;
309 case QSSGRenderDrawMode::LineStrip:
310 return QRhiGraphicsPipeline::LineStrip;
311 case QSSGRenderDrawMode::Lines:
312 return QRhiGraphicsPipeline::Lines;
313 case QSSGRenderDrawMode::TriangleStrip:
314 return QRhiGraphicsPipeline::TriangleStrip;
315 case QSSGRenderDrawMode::TriangleFan:
316 return QRhiGraphicsPipeline::TriangleFan;
317 case QSSGRenderDrawMode::Triangles:
318 return QRhiGraphicsPipeline::Triangles;
319 case QSSGRenderDrawMode::LineLoop:
320 QSSG_ASSERT_X(false, "LineLoop draw mode is not supported", return QRhiGraphicsPipeline::Triangles);
321 }
322
323 Q_UNREACHABLE_RETURN(QRhiGraphicsPipeline::Triangles);
324}
325
326void bakeVertexInputLocations(QSSGRhiInputAssemblerState *ia, const QSSGRhiShaderPipeline &shaders, int instanceBufferBinding)
327{
328 if (!shaders.vertexStage())
329 return;
330
331 const auto &vertexInputs = shaders.vertexInputs();
332
333 QVarLengthArray<QRhiVertexInputAttribute, 8> attrs;
334 int inputIndex = 0;
335 for (auto it = ia->inputLayout.cbeginAttributes(), itEnd = ia->inputLayout.cendAttributes(); it != itEnd; ++it) {
336 const QSSGRhiInputAssemblerState::InputSemantic sem = ia->inputs.at(inputIndex); // avoid detaching - submeshes share the same name list
337 auto vertexInputVar = vertexInputs.constFind(sem);
338 if (vertexInputVar != vertexInputs.constEnd()) {
339 attrs.append(*it);
340 attrs.last().setLocation(vertexInputVar->location);
341 } // else the mesh has an input attribute that is not declared and used in the vertex shader - that's fine
342
343 ++inputIndex;
344 }
345
346 // Add instance buffer input if necessary
347 if (instanceBufferBinding > 0) {
348 auto instanceBufferLocations = shaders.instanceBufferLocations();
349 // transform0
350 attrs.append(QRhiVertexInputAttribute(instanceBufferBinding,
351 instanceBufferLocations.transform0,
352 QRhiVertexInputAttribute::Float4,
353 0));
354 // transform1
355 attrs.append(QRhiVertexInputAttribute(instanceBufferBinding,
356 instanceBufferLocations.transform1,
357 QRhiVertexInputAttribute::Float4,
358 sizeof(float) * 4));
359 // transform2
360 attrs.append(QRhiVertexInputAttribute(instanceBufferBinding,
361 instanceBufferLocations.transform2,
362 QRhiVertexInputAttribute::Float4,
363 sizeof(float) * 4 * 2));
364 // color
365 attrs.append(QRhiVertexInputAttribute(instanceBufferBinding,
366 instanceBufferLocations.color,
367 QRhiVertexInputAttribute::Float4,
368 sizeof(float) * 4 * 3));
369 // data
370 attrs.append(QRhiVertexInputAttribute(instanceBufferBinding,
371 instanceBufferLocations.data,
372 QRhiVertexInputAttribute::Float4,
373 sizeof(float) * 4 * 4));
374 }
375
376 ia->inputLayout.setAttributes(attrs.cbegin(), attrs.cend());
377}
378
379} // namespace QSSGRhiHelpers
380
381void QSSGRhiShaderPipeline::addStage(const QRhiShaderStage &stage, StageFlags flags)
382{
383 m_stages.append(stage);
384
385 // Copy all member infos for the uniform block with binding 0 into m_ub0
386 // for faster lookup.
387 if (stage.type() == QRhiShaderStage::Vertex) {
388 // Optimize by doing it only for the vertex shader. This code path is
389 // only hit for pipelines with vertex+fragment stages and an in shaders
390 // from materials an identical uniform block is present in both
391 // shaders, so go through only one of them.
392 const QVector<QShaderDescription::UniformBlock> uniformBlocks = stage.shader().description().uniformBlocks();
393 for (const QShaderDescription::UniformBlock &blk : uniformBlocks) {
394 if (blk.binding == 0) {
395 m_ub0Size = blk.size;
396 m_ub0NextUBufOffset = m_context.rhi()->ubufAligned(m_ub0Size);
397 for (const QShaderDescription::BlockVariable &var : blk.members)
398 m_ub0[var.name] = var;
399 break;
400 }
401 }
402 // Now the same for vertex inputs.
403 if (!flags.testFlag(UsedWithoutIa)) {
404 const QVector<QShaderDescription::InOutVariable> inputs = stage.shader().description().inputVariables();
405 for (const QShaderDescription::InOutVariable &var : inputs) {
406 if (var.name == QSSGMesh::MeshInternal::getPositionAttrName()) {
407 m_vertexInputs[QSSGRhiInputAssemblerState::PositionSemantic] = var;
408 } else if (var.name == QSSGMesh::MeshInternal::getNormalAttrName()) {
409 m_vertexInputs[QSSGRhiInputAssemblerState::NormalSemantic] = var;
410 } else if (var.name == QSSGMesh::MeshInternal::getUV0AttrName()) {
411 m_vertexInputs[QSSGRhiInputAssemblerState::TexCoord0Semantic] = var;
412 } else if (var.name == QSSGMesh::MeshInternal::getUV1AttrName()) {
413 m_vertexInputs[QSSGRhiInputAssemblerState::TexCoord1Semantic] = var;
414 } else if (var.name == QSSGMesh::MeshInternal::getLightmapUVAttrName()) {
415 m_vertexInputs[QSSGRhiInputAssemblerState::TexCoordLightmapSemantic] = var;
416 } else if (var.name == QSSGMesh::MeshInternal::getTexTanAttrName()) {
417 m_vertexInputs[QSSGRhiInputAssemblerState::TangentSemantic] = var;
418 } else if (var.name == QSSGMesh::MeshInternal::getTexBinormalAttrName()) {
419 m_vertexInputs[QSSGRhiInputAssemblerState::BinormalSemantic] = var;
420 } else if (var.name == QSSGMesh::MeshInternal::getColorAttrName()) {
421 m_vertexInputs[QSSGRhiInputAssemblerState::ColorSemantic] = var;
422 } else if (var.name == QSSGMesh::MeshInternal::getJointAttrName()) {
423 m_vertexInputs[QSSGRhiInputAssemblerState::JointSemantic] = var;
424 } else if (var.name == QSSGMesh::MeshInternal::getWeightAttrName()) {
425 m_vertexInputs[QSSGRhiInputAssemblerState::WeightSemantic] = var;
426 } else if (var.name == "qt_instanceTransform0") {
427 instanceLocations.transform0 = var.location;
428 } else if (var.name == "qt_instanceTransform1") {
429 instanceLocations.transform1 = var.location;
430 } else if (var.name == "qt_instanceTransform2") {
431 instanceLocations.transform2 = var.location;
432 } else if (var.name == "qt_instanceColor") {
433 instanceLocations.color = var.location;
434 } else if (var.name == "qt_instanceData") {
435 instanceLocations.data = var.location;
436 } else {
437 qWarning("Ignoring vertex input %s in shader", var.name.constData());
438 }
439 }
440 }
441 }
442
443 const QVector<QShaderDescription::InOutVariable> combinedImageSamplers = stage.shader().description().combinedImageSamplers();
444 for (const QShaderDescription::InOutVariable &var : combinedImageSamplers)
445 m_combinedImageSamplers[var.name] = var;
446
447 std::fill(m_materialImageSamplerBindings,
448 m_materialImageSamplerBindings + size_t(QSSGRhiSamplerBindingHints::BindingMapSize),
449 -1);
450}
451
452int QSSGRhiShaderPipeline::ub0DirectionalLightDataOffset() const
453{
454 return m_ub0NextUBufOffset + m_context.rhi()->ubufAligned(sizeof(QSSGShaderLightsUniformData));
455}
456
457void QSSGRhiShaderPipeline::setUniformValue(char *ubufData, const char *name, const QVariant &inValue, QSSGRenderShaderValue::Type inType)
458{
459 using namespace QSSGRenderShaderValue;
460 switch (inType) {
461 case QSSGRenderShaderValue::Integer:
462 {
463 const qint32 v = inValue.toInt();
464 setUniform(ubufData, name, &v, sizeof(qint32));
465 }
466 break;
467 case QSSGRenderShaderValue::IntegerVec2:
468 {
469 const ivec2 v = inValue.value<ivec2>();
470 setUniform(ubufData, name, &v, 2 * sizeof(qint32));
471 }
472 break;
473 case QSSGRenderShaderValue::IntegerVec3:
474 {
475 const ivec3 v = inValue.value<ivec3>();
476 setUniform(ubufData, name, &v, 3 * sizeof(qint32));
477 }
478 break;
479 case QSSGRenderShaderValue::IntegerVec4:
480 {
481 const ivec4 v = inValue.value<ivec4>();
482 setUniform(ubufData, name, &v, 4 * sizeof(qint32));
483 }
484 break;
485 case QSSGRenderShaderValue::Boolean:
486 {
487 // whatever bool is does not matter, what matters is that the GLSL bool is 4 bytes
488 const qint32 v = inValue.value<bool>();
489 setUniform(ubufData, name, &v, sizeof(qint32));
490 }
491 break;
492 case QSSGRenderShaderValue::BooleanVec2:
493 {
494 const bvec2 b = inValue.value<bvec2>();
495 const ivec2 v(b.x, b.y);
496 setUniform(ubufData, name, &v, 2 * sizeof(qint32));
497 }
498 break;
499 case QSSGRenderShaderValue::BooleanVec3:
500 {
501 const bvec3 b = inValue.value<bvec3>();
502 const ivec3 v(b.x, b.y, b.z);
503 setUniform(ubufData, name, &v, 3 * sizeof(qint32));
504 }
505 break;
506 case QSSGRenderShaderValue::BooleanVec4:
507 {
508 const bvec4 b = inValue.value<bvec4>();
509 const ivec4 v(b.x, b.y, b.z, b.w);
510 setUniform(ubufData, name, &v, 4 * sizeof(qint32));
511 }
512 break;
513 case QSSGRenderShaderValue::Float:
514 {
515 const float v = inValue.value<float>();
516 setUniform(ubufData, name, &v, sizeof(float));
517 }
518 break;
519 case QSSGRenderShaderValue::Vec2:
520 {
521 const QVector2D v = inValue.value<QVector2D>();
522 setUniform(ubufData, name, &v, 2 * sizeof(float));
523 }
524 break;
525 case QSSGRenderShaderValue::Vec3:
526 {
527 const QVector3D v = inValue.value<QVector3D>();
528 setUniform(ubufData, name, &v, 3 * sizeof(float));
529 }
530 break;
531 case QSSGRenderShaderValue::Vec4:
532 {
533 const QVector4D v = inValue.value<QVector4D>();
534 setUniform(ubufData, name, &v, 4 * sizeof(float));
535 }
536 break;
537 case QSSGRenderShaderValue::Rgba:
538 {
539 const QVector4D v = QSSGUtils::color::sRGBToLinear(inValue.value<QColor>());
540 setUniform(ubufData, name, &v, 4 * sizeof(float));
541 }
542 break;
543 case QSSGRenderShaderValue::UnsignedInteger:
544 {
545 const quint32 v = inValue.value<quint32>();
546 setUniform(ubufData, name, &v, sizeof(quint32));
547 }
548 break;
549 case QSSGRenderShaderValue::UnsignedIntegerVec2:
550 {
551 const uvec2 v = inValue.value<uvec2>();
552 setUniform(ubufData, name, &v, 2 * sizeof(quint32));
553 }
554 break;
555 case QSSGRenderShaderValue::UnsignedIntegerVec3:
556 {
557 const uvec3 v = inValue.value<uvec3>();
558 setUniform(ubufData, name, &v, 3 * sizeof(quint32));
559 }
560 break;
561 case QSSGRenderShaderValue::UnsignedIntegerVec4:
562 {
563 const uvec4 v = inValue.value<uvec4>();
564 setUniform(ubufData, name, &v, 4 * sizeof(quint32));
565 }
566 break;
567 case QSSGRenderShaderValue::Matrix3x3:
568 {
569 const QMatrix3x3 m = inValue.value<QMatrix3x3>();
570 setUniform(ubufData, name, m.constData(), 12 * sizeof(float), nullptr, QSSGRhiShaderPipeline::UniformFlag::Mat3);
571 }
572 break;
573 case QSSGRenderShaderValue::Matrix4x4:
574 {
575 const QMatrix4x4 v = inValue.value<QMatrix4x4>();
576 setUniform(ubufData, name, v.constData(), 16 * sizeof(float));
577 }
578 break;
579 case QSSGRenderShaderValue::Size:
580 {
581 const QSize s = inValue.value<QSize>();
582 float v[2] = { float(s.width()), float(s.height()) };
583 setUniform(ubufData, name, v, 2 * sizeof(float));
584 }
585 break;
586 case QSSGRenderShaderValue::SizeF:
587 {
588 const QSizeF s = inValue.value<QSizeF>();
589 float v[2] = { float(s.width()), float(s.height()) };
590 setUniform(ubufData, name, v, 2 * sizeof(float));
591 }
592 break;
593 case QSSGRenderShaderValue::Point:
594 {
595 const QPoint p = inValue.value<QPoint>();
596 float v[2] = { float(p.x()), float(p.y()) };
597 setUniform(ubufData, name, v, 2 * sizeof(float));
598 }
599 break;
600 case QSSGRenderShaderValue::PointF:
601 {
602 const QPointF p = inValue.value<QPointF>();
603 float v[2] = { float(p.x()), float(p.y()) };
604 setUniform(ubufData, name, v, 2 * sizeof(float));
605 }
606 break;
607 case QSSGRenderShaderValue::Rect:
608 {
609 const QRect r = inValue.value<QRect>();
610 float v[4] = { float(r.x()), float(r.y()), float(r.width()), float(r.height()) };
611 setUniform(ubufData, name, v, 4 * sizeof(float));
612 }
613 break;
614 case QSSGRenderShaderValue::RectF:
615 {
616 const QRectF r = inValue.value<QRectF>();
617 float v[4] = { float(r.x()), float(r.y()), float(r.width()), float(r.height()) };
618 setUniform(ubufData, name, v, 4 * sizeof(float));
619 }
620 break;
621 case QSSGRenderShaderValue::Quaternion:
622 {
623 const QQuaternion q = inValue.value<QQuaternion>();
624 float v[4] = { float(q.x()), float(q.y()), float(q.z()), float(q.scalar()) };
625 setUniform(ubufData, name, v, 4 * sizeof(float));
626 }
627 break;
628 default:
629 qWarning("Attempted to set uniform %s value with unsupported data type %i",
630 name, int(inType));
631 break;
632 }
633}
634
635int QSSGRhiShaderPipeline::offsetOfUniform(const QByteArray &name)
636{
637 auto iter = m_ub0.constFind(name);
638 if (iter != m_ub0.cend())
639 return iter->offset;
640 return -1;
641}
642
643static QString getUBMemberSizeWarning(QLatin1StringView name, qsizetype correctedSize, qsizetype requestedSize)
644{
645 return QStringLiteral("Uniform block member '%1' got %2 bytes whereas the true size is %3").arg(name, QString::number(correctedSize), QString::number(requestedSize));
646}
647
648void QSSGRhiShaderPipeline::setUniform(char *ubufData, const char *name, const void *data, size_t size, int *storeIndex, UniformFlags flags)
649{
650 int index = -1;
651 if (!storeIndex || *storeIndex == -1) {
652 const QByteArray ba = QByteArray::fromRawData(name, strlen(name));
653 auto it = m_uniformIndex.constFind(ba);
654 if (it != m_uniformIndex.cend()) {
655 index = int(*it);
656 } else if (ba.size() < qsizetype(sizeof(QSSGRhiShaderUniform::name))) {
657 QSSGRhiShaderUniform u;
658 memcpy(u.name, name, ba.size() + 1);
659 u.size = size;
660
661 const int new_idx = m_uniforms.size();
662 m_uniformIndex[name] = new_idx; // key is name, not ba, this has to be a deep copy QByteArray
663 m_uniforms.push_back(u);
664 index = new_idx;
665 } else {
666 qWarning("Attempted to set uniform with too long name: %s", name);
667 return;
668 }
669 if (storeIndex)
670 *storeIndex = index;
671 } else {
672 index = *storeIndex;
673 }
674
675 Q_ASSERT(index >= 0);
676 QSSGRhiShaderUniform &u = m_uniforms[index];
677 if (size <= u.size) {
678 if (u.offset == SIZE_MAX && u.maybeExists) {
679 auto it = m_ub0.constFind(QByteArray::fromRawData(u.name, strlen(u.name)));
680 if (it != m_ub0.constEnd()) {
681 u.offset = it->offset;
682 QSSG_ASSERT_X(QSSG_DEBUG_COND(int(u.size) == it->size), qPrintable(getUBMemberSizeWarning(QLatin1StringView(it->name), u.size, it->size)), return);
683 }
684 }
685 if (u.offset == SIZE_MAX) {
686 // must silently ignore uniforms that are not in the actual shader
687 u.maybeExists = false; // but do not try again
688 return;
689 }
690
691 char *dst = ubufData + u.offset;
692 if (flags.testFlag(UniformFlag::Mat3)) {
693 // mat3 is still 4 floats per column in the uniform buffer (but there
694 // is no 4th column), so 48 bytes altogether, not 36 or 64.
695 const float *src = static_cast<const float *>(data);
696 memcpy(dst, src, 3 * sizeof(float));
697 memcpy(dst + 4 * sizeof(float), src + 3, 3 * sizeof(float));
698 memcpy(dst + 8 * sizeof(float), src + 6, 3 * sizeof(float));
699 } else {
700 memcpy(dst, data, size);
701 }
702 } else {
703 qWarning("Attempted to set %u bytes to uniform %s with size %u", uint(size), name, uint(u.size));
704 }
705}
706
707// Quick3D uniform buffer is std140 type and all array data should be stored in this rule.
708// You can check it in glspec45.core.pdf's 7.6.2.2.(4)
709// https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf
710void QSSGRhiShaderPipeline::setUniformArray(char *ubufData, const char *name, const void *data, size_t itemCount, QSSGRenderShaderValue::Type type, int *storeIndex)
711{
712 using namespace QSSGRenderShaderValue;
713
714 QSSGRhiShaderUniformArray *ua = nullptr;
715 constexpr size_t std140BaseTypeSize = 4 * sizeof(float);
716
717 static const auto checkSize = [std140BaseTypeSize](QSSGRhiShaderUniformArray *ua) -> bool {
718 Q_UNUSED(std140BaseTypeSize); // Silence clang warning about unneeded lambda capture (MSVC requires it be captrued).
719 const size_t uniformSize = std140BaseTypeSize < ua->typeSize ? ua->typeSize * ua->itemCount : std140BaseTypeSize * ua->itemCount;
720 QSSG_ASSERT_X(uniformSize == ua->size, qPrintable(getUBMemberSizeWarning(QLatin1StringView(ua->name), uniformSize, ua->size)), return false);
721 return true;
722 };
723
724 if (!storeIndex || *storeIndex == -1) {
725 int index = -1;
726 const QByteArray ba = QByteArray::fromRawData(name, strlen(name));
727 auto it = m_uniformIndex.constFind(ba);
728 if (it != m_uniformIndex.cend()) {
729 index = int(*it);
730 ua = &m_uniformArrays[index];
731 } else if (ba.size() < qsizetype(sizeof(QSSGRhiShaderUniformArray::name))) {
732 index = m_uniformArrays.size();
733 m_uniformArrays.push_back(QSSGRhiShaderUniformArray());
734 m_uniformIndex[name] = index; // key needs deep copy
735 ua = &m_uniformArrays.last();
736 memcpy(ua->name, name, ba.size() + 1);
737 } else {
738 qWarning("Attempted to set uniform array with too long name: %s", name);
739 return;
740 }
741 if (storeIndex)
742 *storeIndex = index;
743 } else {
744 ua = &m_uniformArrays[*storeIndex];
745 }
746
747 if (!ua)
748 return;
749
750 if (ua->offset == SIZE_MAX && ua->maybeExists) {
751 auto it = m_ub0.constFind(QByteArray::fromRawData(ua->name, strlen(ua->name)));
752 if (it != m_ub0.constEnd()) {
753 ua->offset = it->offset;
754 ua->size = it->size;
755 }
756 }
757 if (ua->offset == SIZE_MAX) {
758 // must silently ignore uniforms that are not in the actual shader
759 ua->maybeExists = false; // but do not try again
760 return;
761 }
762
763 char *p = ubufData + ua->offset;
764
765 switch (type) {
766 case QSSGRenderShaderValue::Integer:
767 {
768 const qint32 *v = static_cast<const qint32 *>(data);
769 if (sizeof(qint32) != ua->typeSize || itemCount != ua->itemCount) {
770 ua->typeSize = sizeof(qint32);
771 ua->itemCount = itemCount;
772 }
773
774 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
775
776 for (size_t i = 0; i < itemCount; ++i)
777 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
778 }
779 break;
780 case QSSGRenderShaderValue::IntegerVec2:
781 {
782 const ivec2 *v = static_cast<const ivec2 *>(data);
783 if (2 * sizeof(qint32) != ua->typeSize || itemCount != ua->itemCount) {
784 ua->typeSize = 2 * sizeof(qint32);
785 ua->itemCount = itemCount;
786 }
787
788 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
789
790 for (size_t i = 0; i < itemCount; ++i)
791 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
792 }
793 break;
794 case QSSGRenderShaderValue::IntegerVec3:
795 {
796 const QSSGRenderShaderValue::ivec3 *v = static_cast<const QSSGRenderShaderValue::ivec3 *>(data);
797 if (3 * sizeof(qint32) != ua->typeSize || itemCount != ua->itemCount) {
798 ua->typeSize = 3 * sizeof(qint32);
799 ua->itemCount = itemCount;
800 }
801
802 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
803
804 for (size_t i = 0; i < itemCount; ++i)
805 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
806 }
807 break;
808 case QSSGRenderShaderValue::IntegerVec4:
809 {
810 const ivec4 *v = static_cast<const ivec4 *>(data);
811 if (4 * sizeof(qint32) != ua->typeSize || itemCount != ua->itemCount) {
812 ua->typeSize = 4 * sizeof(qint32);
813 ua->itemCount = itemCount;
814 }
815
816 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
817
818 memcpy(p, v, ua->typeSize * ua->itemCount);
819 }
820 break;
821 case QSSGRenderShaderValue::Float:
822 {
823 const float *v = static_cast<const float *>(data);
824 if (sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
825 ua->typeSize = sizeof(float);
826 ua->itemCount = itemCount;
827 }
828
829 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
830
831 for (size_t i = 0; i < itemCount; ++i)
832 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
833 }
834 break;
835 case QSSGRenderShaderValue::Vec2:
836 {
837 const QVector2D *v = static_cast<const QVector2D *>(data);
838 if (2 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
839 ua->typeSize = 2 * sizeof(float);
840 ua->itemCount = itemCount;
841 }
842
843 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
844
845 for (size_t i = 0; i < itemCount; ++i)
846 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
847 }
848 break;
849 case QSSGRenderShaderValue::Vec3:
850 {
851 const QVector3D *v = static_cast<const QVector3D *>(data);
852 if (3 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
853 ua->typeSize = 3 * sizeof(float);
854 ua->itemCount = itemCount;
855 }
856
857 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
858
859 for (size_t i = 0; i < itemCount; ++i)
860 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
861 }
862 break;
863 case QSSGRenderShaderValue::Vec4:
864 {
865 const QVector4D *v = static_cast<const QVector4D *>(data);
866 if (4 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
867 ua->typeSize = 4 * sizeof(float);
868 ua->itemCount = itemCount;
869 }
870
871 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
872
873 memcpy(p, v, ua->typeSize * ua->itemCount);
874 }
875 break;
876 case QSSGRenderShaderValue::Rgba:
877 {
878 const QColor *v = static_cast<const QColor *>(data);
879 if (4 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
880 ua->typeSize = 4 * sizeof(float);
881 ua->itemCount = itemCount;
882 }
883
884 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
885
886 for (size_t i = 0; i < itemCount; ++i) {
887 const QVector4D vi = QSSGUtils::color::sRGBToLinear(v[i]);
888 memcpy(p + i * std140BaseTypeSize, &vi, ua->typeSize);
889 }
890 }
891 break;
892 case QSSGRenderShaderValue::UnsignedInteger:
893 {
894 const quint32 *v = static_cast<const quint32 *>(data);
895 if (sizeof(quint32) != ua->typeSize || itemCount != ua->itemCount) {
896 ua->typeSize = sizeof(quint32);
897 ua->itemCount = itemCount;
898 }
899
900 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
901
902 for (size_t i = 0; i < itemCount; ++i)
903 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
904 }
905 break;
906 case QSSGRenderShaderValue::UnsignedIntegerVec2:
907 {
908 const uvec2 *v = static_cast<const uvec2 *>(data);
909 if (2 * sizeof(quint32) != ua->typeSize || itemCount != ua->itemCount) {
910 ua->typeSize = 2 * sizeof(quint32);
911 ua->itemCount = itemCount;
912 }
913
914 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
915
916 for (size_t i = 0; i < itemCount; ++i)
917 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
918 }
919 break;
920 case QSSGRenderShaderValue::UnsignedIntegerVec3:
921 {
922 const uvec3 *v = static_cast<const uvec3 *>(data);
923 if (3 * sizeof(quint32) != ua->typeSize || itemCount != ua->itemCount) {
924 ua->typeSize = 3 * sizeof(quint32);
925 ua->itemCount = itemCount;
926 }
927
928 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
929
930 for (size_t i = 0; i < itemCount; ++i)
931 memcpy(p + i * std140BaseTypeSize, &v[i], ua->typeSize);
932 }
933 break;
934 case QSSGRenderShaderValue::UnsignedIntegerVec4:
935 {
936 const uvec4 *v = static_cast<const uvec4 *>(data);
937 if (4 * sizeof(quint32) != ua->typeSize || itemCount != ua->itemCount) {
938 ua->typeSize = 4 * sizeof(quint32);
939 ua->itemCount = itemCount;
940 }
941
942 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
943
944 memcpy(p, v, ua->typeSize * ua->itemCount);
945 }
946 break;
947 case QSSGRenderShaderValue::Matrix3x3:
948 {
949 const QMatrix3x3 *v = static_cast<const QMatrix3x3 *>(data);
950 if (12 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
951 ua->typeSize = 12 * sizeof(float);
952 ua->itemCount = itemCount;
953 }
954
955 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
956
957 for (uint i = 0; i < ua->itemCount; ++i) {
958 memcpy(p + i * ua->typeSize, v[i].constData(), 3 * sizeof(float));
959 memcpy(p + i * ua->typeSize + 4 * sizeof(float), v[i].constData() + 3, 3 * sizeof(float));
960 memcpy(p + i * ua->typeSize + 8 * sizeof(float), v[i].constData() + 6, 3 * sizeof(float));
961 }
962 }
963 break;
964 case QSSGRenderShaderValue::Matrix4x4:
965 {
966 const QMatrix4x4 *v = static_cast<const QMatrix4x4 *>(data);
967 if (16 * sizeof(float) != ua->typeSize || itemCount != ua->itemCount) {
968 ua->typeSize = 16 * sizeof(float);
969 ua->itemCount = itemCount;
970 }
971
972 QSSG_ASSERT(QSSG_DEBUG_COND(checkSize(ua)), return);
973
974 for (uint i = 0; i < ua->itemCount; ++i)
975 memcpy(p + i * ua->typeSize, &v[i] , ua->typeSize);
976 }
977 break;
978 case QSSGRenderShaderValue::Boolean:
979 case QSSGRenderShaderValue::BooleanVec2:
980 case QSSGRenderShaderValue::BooleanVec3:
981 case QSSGRenderShaderValue::BooleanVec4:
982 case QSSGRenderShaderValue::Size:
983 case QSSGRenderShaderValue::SizeF:
984 case QSSGRenderShaderValue::Point:
985 case QSSGRenderShaderValue::PointF:
986 case QSSGRenderShaderValue::Rect:
987 case QSSGRenderShaderValue::RectF:
988 case QSSGRenderShaderValue::Quaternion:
989 default:
990 qWarning("Attempted to set uniform %s value with type %d that is unsupported for uniform arrays",
991 name, int(type));
992 break;
993 }
994}
995
996void QSSGRhiShaderPipeline::ensureCombinedUniformBuffer(QRhiBuffer **ubuf)
997{
998 const quint32 alignedLightsSize = m_context.rhi()->ubufAligned(sizeof(QSSGShaderLightsUniformData));
999 const quint32 alignedDirectionalLightsSize = m_context.rhi()->ubufAligned(sizeof(QSSGShaderDirectionalLightsUniformData));
1000 const quint32 totalBufferSize = m_ub0NextUBufOffset + alignedLightsSize + alignedDirectionalLightsSize;
1001 if (!*ubuf) {
1002 *ubuf = m_context.rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, totalBufferSize);
1003 (*ubuf)->create();
1004 }
1005 if ((*ubuf)->size() < totalBufferSize) {
1006 (*ubuf)->setSize(totalBufferSize);
1007 (*ubuf)->create();
1008 }
1009}
1010
1011void QSSGRhiShaderPipeline::ensureUniformBuffer(QRhiBuffer **ubuf)
1012{
1013 if (!*ubuf) {
1014 *ubuf = m_context.rhi()->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, m_ub0Size);
1015 (*ubuf)->create();
1016 }
1017}
1018
1019int QSSGRhiShaderPipeline::bindingForTexture(const char *name, int hint)
1020{
1021 if (hint >= 0) {
1022 const int binding = m_materialImageSamplerBindings[hint];
1023 if (binding >= 0)
1024 return binding;
1025 }
1026
1027 auto it = m_combinedImageSamplers.constFind(QByteArray::fromRawData(name, strlen(name)));
1028 const int binding = it != m_combinedImageSamplers.cend() ? it->binding : -1;
1029 if (hint >= 0)
1030 m_materialImageSamplerBindings[hint] = binding;
1031
1032 return binding;
1033}
1034
1035/*!
1036 \internal
1037 */
1038QSSGRhiContext::QSSGRhiContext(QRhi *rhi)
1039 : d_ptr(new QSSGRhiContextPrivate(*this, rhi))
1040{
1041 Q_ASSERT(rhi);
1042 Q_STATIC_ASSERT(int(QSSGRhiSamplerBindingHints::LightProbe) > int(QSSGRenderableImage::Type::Occlusion));
1043}
1044
1045/*!
1046 \internal
1047 */
1048QSSGRhiContext::~QSSGRhiContext()
1049{
1050 Q_D(QSSGRhiContext);
1051 d->releaseCachedResources();
1052
1053 qDeleteAll(d->m_textures);
1054 qDeleteAll(d->m_meshes);
1055}
1056
1057/*!
1058 \return The QRhi object used by the Qt Quick 3D renderer.
1059 */
1060QRhi *QSSGRhiContext::rhi() const
1061{
1062 Q_D(const QSSGRhiContext);
1063 return d->m_rhi;
1064}
1065
1066/*!
1067 \return true if the renderer is initialized successfully.
1068 */
1069bool QSSGRhiContext::isValid() const
1070{
1071 Q_D(const QSSGRhiContext);
1072 return d->m_rhi != nullptr;
1073}
1074
1075void QSSGRhiContextPrivate::setMainRenderPassDescriptor(QRhiRenderPassDescriptor *rpDesc)
1076{
1077 m_mainRpDesc = rpDesc;
1078}
1079
1080/*!
1081 \return The QRhiRenderPassDescriptor used by the main render pass of the Qt
1082 Quick 3D renderer.
1083 */
1084QRhiRenderPassDescriptor *QSSGRhiContext::mainRenderPassDescriptor() const
1085{
1086 Q_D(const QSSGRhiContext);
1087 return d->m_mainRpDesc;
1088}
1089
1090void QSSGRhiContextPrivate::setCommandBuffer(QRhiCommandBuffer *cb)
1091{
1092 m_cb = cb;
1093}
1094
1095/*!
1096 \return The current frame's command buffer used by the Qt Quick 3D renderer.
1097 */
1098QRhiCommandBuffer *QSSGRhiContext::commandBuffer() const
1099{
1100 Q_D(const QSSGRhiContext);
1101 return d->m_cb;
1102}
1103
1104void QSSGRhiContextPrivate::setRenderTarget(QRhiRenderTarget *rt)
1105{
1106 m_rt = rt;
1107}
1108
1109/*!
1110 \return The render target the Qt Quick 3D renderer uses for its main render
1111 pass in the current frame.
1112
1113 This can effectively be a render target from a swapchain, if the \l View3D
1114 uses a renderMode other than Offscreen. More commonly, the render target
1115 refers to a texture (i.e., is a QRhiTextureRenderTarget), e.g. because the
1116 renderMode is the default Offscreen, or because post-processing effects are
1117 in use.
1118 */
1119QRhiRenderTarget *QSSGRhiContext::renderTarget() const
1120{
1121 Q_D(const QSSGRhiContext);
1122 return d->m_rt;
1123}
1124
1125void QSSGRhiContextPrivate::setMainPassSampleCount(int samples)
1126{
1127 m_mainSamples = samples;
1128}
1129
1130/*!
1131 Returns the sample count used in the main render pass.
1132 */
1133int QSSGRhiContext::mainPassSampleCount() const
1134{
1135 Q_D(const QSSGRhiContext);
1136 return d->m_mainSamples;
1137}
1138
1139void QSSGRhiContextPrivate::setMainPassViewCount(int viewCount)
1140{
1141 m_mainViewCount = viewCount;
1142}
1143
1144/*!
1145 Returns the multiview count used in the main render pass. This is either 2,
1146 when multiview rendering is in use, or 1 (no multiview).
1147 */
1148int QSSGRhiContext::mainPassViewCount() const
1149{
1150 Q_D(const QSSGRhiContext);
1151 return d->m_mainViewCount;
1152}
1153
1154void QSSGRhiContextPrivate::releaseCachedResources()
1155{
1156 for (QSSGRhiDrawCallData &dcd : m_drawCallData) {
1157 // We don't call releaseDrawCallData() here, since we're anyways
1158 // are going to delete the non-owned resources further down and
1159 // there's no point in removing each of those one-by-one, as is
1160 // the case with releaseDrawCallData().
1161 // This speeds up the release of cached resources greatly when there
1162 // are many entries in the map, also at application shutdown.
1163 dcd.reset();
1164 }
1165
1166 m_drawCallData.clear();
1167
1168 qDeleteAll(m_pipelines);
1169 qDeleteAll(m_computePipelines);
1170 qDeleteAll(m_srbCache);
1171 qDeleteAll(m_dummyTextures);
1172
1173 m_pipelines.clear();
1174 m_computePipelines.clear();
1175 m_srbCache.clear();
1176 m_dummyTextures.clear();
1177
1178 for (const auto &samplerInfo : std::as_const(m_samplers))
1179 delete samplerInfo.second;
1180
1181 m_samplers.clear();
1182
1183 for (const auto &particleData : std::as_const(m_particleData))
1184 delete particleData.texture;
1185
1186 m_particleData.clear();
1187
1188 for (const auto &instanceData : std::as_const(m_instanceBuffers)) {
1189 if (instanceData.owned)
1190 delete instanceData.buffer;
1191 }
1192
1193 m_instanceBuffers.clear();
1194
1195 for (const auto &instanceData : std::as_const(m_instanceBuffersLod)) {
1196 if (instanceData.owned)
1197 delete instanceData.buffer;
1198 }
1199
1200 m_instanceBuffersLod.clear();
1201}
1202
1203QRhiShaderResourceBindings *QSSGRhiContextPrivate::srb(const QSSGRhiShaderResourceBindingList &bindings)
1204{
1205 auto it = m_srbCache.constFind(bindings);
1206 if (it != m_srbCache.constEnd())
1207 return *it;
1208
1209 QRhiShaderResourceBindings *srb = m_rhi->newShaderResourceBindings();
1210 srb->setBindings(bindings.v, bindings.v + bindings.p);
1211 if (srb->create()) {
1212 m_srbCache.insert(bindings, srb);
1213 } else {
1214 qWarning("Failed to build srb");
1215 delete srb;
1216 srb = nullptr;
1217 }
1218 return srb;
1219}
1220
1221void QSSGRhiContextPrivate::releaseCachedSrb(QSSGRhiShaderResourceBindingList &bindings)
1222{
1223 auto srb = m_srbCache.take(bindings);
1224 delete srb;
1225}
1226
1227void QSSGRhiContextPrivate::releaseDrawCallData(QSSGRhiDrawCallData &dcd)
1228{
1229 delete dcd.ubuf;
1230 dcd.ubuf = nullptr;
1231 auto srb = m_srbCache.take(dcd.bindings);
1232 QSSG_CHECK(srb == dcd.srb);
1233 delete srb;
1234 dcd.srb = nullptr;
1235 dcd.pipeline = nullptr;
1236}
1237
1238QRhiGraphicsPipeline *QSSGRhiContextPrivate::pipeline(const QSSGRhiGraphicsPipelineState &ps,
1239 QRhiRenderPassDescriptor *rpDesc,
1240 QRhiShaderResourceBindings *srb)
1241{
1242 return pipeline(QSSGGraphicsPipelineStateKey::create(ps, rpDesc, srb), rpDesc, srb);
1243}
1244
1245QRhiComputePipeline *QSSGRhiContextPrivate::computePipeline(const QShader &shader,
1246 QRhiShaderResourceBindings *srb)
1247{
1248 return computePipeline(QSSGComputePipelineStateKey::create(shader, srb), srb);
1249}
1250
1251QSSGRhiDrawCallData &QSSGRhiContextPrivate::drawCallData(const QSSGRhiDrawCallDataKey &key)
1252{
1253 return m_drawCallData[key];
1254}
1255
1256using SamplerInfo = QPair<QSSGRhiSamplerDescription, QRhiSampler*>;
1257
1258/*!
1259 \return a sampler with the filter and tiling modes specified in \a samplerDescription.
1260
1261 The generated QRhiSampler objects are cached and reused. Thus this is a
1262 convenient way to gain access to a QRhiSampler with the given settings,
1263 without having to create a new, dedicated object all the time.
1264
1265 The ownership of the returned QRhiSampler stays with Qt Quick 3D.
1266 */
1267QRhiSampler *QSSGRhiContext::sampler(const QSSGRhiSamplerDescription &samplerDescription)
1268{
1269 Q_D(QSSGRhiContext);
1270 auto compareSampler = [samplerDescription](const SamplerInfo &info){ return info.first == samplerDescription; };
1271 auto &samplers = d->m_samplers;
1272 const auto found = std::find_if(samplers.cbegin(), samplers.cend(), compareSampler);
1273 if (found != samplers.cend())
1274 return found->second;
1275
1276 QRhiSampler *newSampler = d->m_rhi->newSampler(samplerDescription.magFilter,
1277 samplerDescription.minFilter,
1278 samplerDescription.mipmap,
1279 samplerDescription.hTiling,
1280 samplerDescription.vTiling,
1281 samplerDescription.zTiling);
1282 if (!newSampler->create()) {
1283 qWarning("Failed to build image sampler");
1284 delete newSampler;
1285 return nullptr;
1286 }
1287 samplers << SamplerInfo{samplerDescription, newSampler};
1288 return newSampler;
1289}
1290
1291/*!
1292 Adjusts \a samplerDescription's tiling and filtering modes based on the
1293 pixel size of \a texture.
1294
1295 In most cases, \a samplerDescription is not changed. With older, legacy 3D
1296 APIs in use, there is however a chance that tiling modes such as
1297 \l{QRhiSampler::Repeat} are not supported for textures with a
1298 non-power-of-two width or height.
1299
1300 This convenience function helps creating robust applications that can still
1301 function even when features such as \l{QRhi::NPOTTextureRepeat} are not
1302 supported by an OpenGL ES 2.0 or WebGL 1 implementation at run time.
1303 */
1304void QSSGRhiContext::checkAndAdjustForNPoT(QRhiTexture *texture, QSSGRhiSamplerDescription *samplerDescription)
1305{
1306 Q_D(const QSSGRhiContext);
1307 if (samplerDescription->mipmap != QRhiSampler::None
1308 || samplerDescription->hTiling != QRhiSampler::ClampToEdge
1309 || samplerDescription->vTiling != QRhiSampler::ClampToEdge
1310 || samplerDescription->zTiling != QRhiSampler::ClampToEdge)
1311 {
1312 if (d->m_rhi->isFeatureSupported(QRhi::NPOTTextureRepeat))
1313 return;
1314
1315 const QSize pixelSize = texture->pixelSize();
1316 const int w = qNextPowerOfTwo(pixelSize.width() - 1);
1317 const int h = qNextPowerOfTwo(pixelSize.height() - 1);
1318 if (w != pixelSize.width() || h != pixelSize.height()) {
1319 static bool warnShown = false;
1320 if (!warnShown) {
1321 warnShown = true;
1322 qWarning("Attempted to use an unsupported filtering or wrap mode, "
1323 "this is likely due to lacking proper support for non-power-of-two textures on this platform.\n"
1324 "If this is with WebGL, try updating the application to use QQuick3D::idealSurfaceFormat() in main() "
1325 "in order to ensure WebGL 2 is used.");
1326 }
1327 samplerDescription->mipmap = QRhiSampler::None;
1328 samplerDescription->hTiling = QRhiSampler::ClampToEdge;
1329 samplerDescription->vTiling = QRhiSampler::ClampToEdge;
1330 samplerDescription->zTiling = QRhiSampler::ClampToEdge;
1331 }
1332 }
1333}
1334
1335void QSSGRhiContextPrivate::registerTexture(QRhiTexture *texture)
1336{
1337 m_textures.insert(texture);
1338}
1339
1340void QSSGRhiContextPrivate::releaseTexture(QRhiTexture *texture)
1341{
1342 m_textures.remove(texture);
1343 delete texture;
1344}
1345
1346void QSSGRhiContextPrivate::registerMesh(QSSGRenderMesh *mesh)
1347{
1348 m_meshes.insert(mesh);
1349}
1350
1351void QSSGRhiContextPrivate::releaseMesh(QSSGRenderMesh *mesh)
1352{
1353 if (mesh) {
1354 for (const auto &subset : std::as_const(mesh->subsets)) {
1355 if (subset.rhi.targetsTexture) {
1356 // If there is a morph targets texture, it should be the same for
1357 // all subsets, so just release and break
1358 releaseTexture(subset.rhi.targetsTexture);
1359 break;
1360 }
1361 }
1362 }
1363 m_meshes.remove(mesh);
1364 delete mesh;
1365}
1366
1367void QSSGRhiContextPrivate::cleanupDrawCallData(const QSSGRenderModel *model)
1368{
1369 // Find all QSSGRhiUniformBufferSet that reference model
1370 // and delete them
1371 const void *modelNode = model;
1372 auto it = m_drawCallData.begin();
1373 while (it != m_drawCallData.end()) {
1374 if (it.key().model == modelNode) {
1375 releaseDrawCallData(*it);
1376 it = m_drawCallData.erase(it);
1377 } else {
1378 ++it;
1379 }
1380 }
1381}
1382
1383/*!
1384 \return a texture that has the specified \a flags and pixel \a size.
1385
1386 This is intended to efficiently gain access to a "dummy" texture filled
1387 with a given \a fillColor, and reused in various places in the rendering
1388 stack.
1389
1390 \a rub must be a valid QRhiResourceUpdateBatch since this function will
1391 create a new texture and generate content for it, if a suitable cached
1392 object is not found. The necessary upload operations are then enqueued on
1393 this given update batch.
1394
1395 When \a arraySize is 2 or more, a 2D texture array is returned.
1396
1397 The ownership of the returned texture stays with Qt Quick 3D.
1398 */
1399QRhiTexture *QSSGRhiContext::dummyTexture(QRhiTexture::Flags flags, QRhiResourceUpdateBatch *rub,
1400 const QSize &size, const QColor &fillColor, int arraySize)
1401{
1402 Q_D(QSSGRhiContext);
1403 auto it = d->m_dummyTextures.constFind({flags, size, fillColor, arraySize});
1404 if (it != d->m_dummyTextures.constEnd())
1405 return *it;
1406
1407 QRhiTexture *t = arraySize < 2
1408 ? d->m_rhi->newTexture(QRhiTexture::RGBA8, size, 1, flags)
1409 : d->m_rhi->newTextureArray(QRhiTexture::RGBA8, arraySize, size, 1, flags);
1410 if (t->create()) {
1411 QImage image(t->pixelSize(), QImage::Format_RGBA8888);
1412 image.fill(fillColor);
1413 rub->uploadTexture(t, image);
1414 for (int layer = 1; layer < arraySize; ++layer)
1415 rub->uploadTexture(t, QRhiTextureUploadDescription(QRhiTextureUploadEntry(layer, 0, QRhiTextureSubresourceUploadDescription(image))));
1416 } else {
1417 qWarning("Failed to build dummy texture");
1418 }
1419
1420 d->m_dummyTextures.insert({flags, size, fillColor, arraySize}, t);
1421 return t;
1422}
1423
1424QSSGRhiInstanceBufferData &QSSGRhiContextPrivate::instanceBufferData(QSSGRenderInstanceTable *instanceTable)
1425{
1426 return m_instanceBuffers[instanceTable];
1427}
1428
1429void QSSGRhiContextPrivate::releaseInstanceBuffer(QSSGRenderInstanceTable *instanceTable)
1430{
1431 auto it = m_instanceBuffers.constFind(instanceTable);
1432 if (it != m_instanceBuffers.constEnd()) {
1433 it->buffer->destroy();
1434 m_instanceBuffers.erase(it);
1435 }
1436}
1437
1438QSSGRhiInstanceBufferData &QSSGRhiContextPrivate::instanceBufferData(const QSSGRenderModel *model)
1439{
1440 return m_instanceBuffersLod[model];
1441}
1442
1443QSSGRhiParticleData &QSSGRhiContextPrivate::particleData(const QSSGRenderGraphObject *particlesOrModel)
1444{
1445 return m_particleData[particlesOrModel];
1446}
1447
1448void QSSGRhiContextStats::start(QSSGRenderLayer *layer)
1449{
1450 layerKey = layer;
1451 PerLayerInfo &info(perLayerInfo[layerKey]);
1452 info.renderPasses.clear();
1453 info.externalRenderPass = {};
1454 info.currentRenderPassIndex = -1;
1455}
1456
1457void QSSGRhiContextStats::stop(QSSGRenderLayer *layer)
1458{
1459 if (rendererDebugEnabled()) {
1460 PerLayerInfo &info(perLayerInfo[layer]);
1461 const int rpCount = info.renderPasses.size();
1462 qDebug("%d render passes in 3D renderer %p", rpCount, layer);
1463 for (int i = 0; i < rpCount; ++i) {
1464 const RenderPassInfo &rp(info.renderPasses[i]);
1465 qDebug("Render pass %d: rt name='%s' target size %dx%d pixels",
1466 i, rp.rtName.constData(), rp.pixelSize.width(), rp.pixelSize.height());
1467 printRenderPass(rp);
1468 }
1469 if (info.externalRenderPass.indexedDraws.callCount || info.externalRenderPass.instancedIndexedDraws.callCount
1470 || info.externalRenderPass.draws.callCount || info.externalRenderPass.instancedDraws.callCount)
1471 {
1472 qDebug("Within external render passes:");
1473 printRenderPass(info.externalRenderPass);
1474 }
1475 }
1476
1477 // a new start() may preceed stop() for the previous View3D, must handle this gracefully
1478 if (layerKey == layer)
1479 layerKey = nullptr;
1480
1481 // The data must stay valid until the next start() with the same key, the
1482 // QQuick3DRenderStats and DebugView may read it.
1483}
1484
1485void QSSGRhiContextStats::cleanupLayerInfo(QSSGRenderLayer *layer)
1486{
1487 perLayerInfo.remove(layer);
1488 dynamicDataSources.remove(layer);
1489}
1490
1491void QSSGRhiContextStats::beginRenderPass(QRhiTextureRenderTarget *rt)
1492{
1493 PerLayerInfo &info(perLayerInfo[layerKey]);
1494 Q_TRACE(QSSG_renderPass_entry, QString::fromUtf8(rt->name()));
1495 info.renderPasses.append({ rt->name(), rt->pixelSize(), {}, {}, {}, {} });
1496 info.currentRenderPassIndex = info.renderPasses.size() - 1;
1497}
1498
1499void QSSGRhiContextStats::endRenderPass()
1500{
1501 Q_TRACE(QSSG_renderPass_exit);
1502 PerLayerInfo &info(perLayerInfo[layerKey]);
1503 info.currentRenderPassIndex = -1;
1504}
1505
1506QSSGRhiContextStats &QSSGRhiContextStats::get(QSSGRhiContext &rhiCtx)
1507{
1508 return QSSGRhiContextPrivate::get(&rhiCtx)->m_stats;
1509}
1510
1511const QSSGRhiContextStats &QSSGRhiContextStats::get(const QSSGRhiContext &rhiCtx)
1512{
1513 return QSSGRhiContextPrivate::get(&rhiCtx)->m_stats;
1514}
1515
1516bool QSSGRhiContextStats::profilingEnabled()
1517{
1518 static bool enabled = Q_QUICK3D_PROFILING_ENABLED;
1519 return enabled;
1520}
1521
1522bool QSSGRhiContextStats::rendererDebugEnabled()
1523{
1524 static bool enabled = qgetenv("QSG_RENDERER_DEBUG").contains(QByteArrayLiteral("render"));
1525 return enabled;
1526}
1527
1528bool QSSGRhiContextStats::isEnabled() const
1529{
1530 return !dynamicDataSources.isEmpty() || profilingEnabled() || rendererDebugEnabled()
1531 || Q_TRACE_ENABLED(QSSG_draw);
1532}
1533
1534void QSSGRhiContextStats::drawIndexed(quint32 indexCount, quint32 instanceCount)
1535{
1536 Q_TRACE(QSSG_drawIndexed, indexCount, instanceCount);
1537 PerLayerInfo &info(perLayerInfo[layerKey]);
1538 RenderPassInfo &rp(info.currentRenderPassIndex >= 0 ? info.renderPasses[info.currentRenderPassIndex] : info.externalRenderPass);
1539 if (instanceCount > 1) {
1540 rp.instancedIndexedDraws.callCount += 1;
1541 rp.instancedIndexedDraws.vertexOrIndexCount += indexCount;
1542 rp.instancedIndexedDraws.instanceCount += instanceCount;
1543 } else {
1544 rp.indexedDraws.callCount += 1;
1545 rp.indexedDraws.vertexOrIndexCount += indexCount;
1546 }
1547}
1548
1549void QSSGRhiContextStats::draw(quint32 vertexCount, quint32 instanceCount)
1550{
1551 Q_TRACE(QSSG_draw, vertexCount, instanceCount);
1552 PerLayerInfo &info(perLayerInfo[layerKey]);
1553 RenderPassInfo &rp(info.currentRenderPassIndex >= 0 ? info.renderPasses[info.currentRenderPassIndex] : info.externalRenderPass);
1554 if (instanceCount > 1) {
1555 rp.instancedDraws.callCount += 1;
1556 rp.instancedDraws.vertexOrIndexCount += vertexCount;
1557 rp.instancedDraws.instanceCount += instanceCount;
1558 } else {
1559 rp.draws.callCount += 1;
1560 rp.draws.vertexOrIndexCount += vertexCount;
1561 }
1562}
1563
1564void QSSGRhiContextStats::printRenderPass(const QSSGRhiContextStats::RenderPassInfo &rp)
1565{
1566 qDebug("%llu indexed draw calls with %llu indices in total, "
1567 "%llu non-indexed draw calls with %llu vertices in total",
1568 rp.indexedDraws.callCount, rp.indexedDraws.vertexOrIndexCount,
1569 rp.draws.callCount, rp.draws.vertexOrIndexCount);
1570 if (rp.instancedIndexedDraws.callCount || rp.instancedDraws.callCount) {
1571 qDebug("%llu instanced indexed draw calls with %llu indices and %llu instances in total, "
1572 "%llu instanced non-indexed draw calls with %llu indices and %llu instances in total",
1573 rp.instancedIndexedDraws.callCount, rp.instancedIndexedDraws.vertexOrIndexCount, rp.instancedIndexedDraws.instanceCount,
1574 rp.instancedDraws.callCount, rp.instancedDraws.vertexOrIndexCount, rp.instancedDraws.instanceCount);
1575 }
1576}
1577
1578void QSSGRhiShaderResourceBindingList::addUniformBuffer(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiBuffer *buf, int offset, int size)
1579{
1580#ifdef QT_DEBUG
1581 if (p == MAX_SIZE) {
1582 qWarning("Out of shader resource bindings slots (max is %d)", MAX_SIZE);
1583 return;
1584 }
1585#endif
1586 QRhiShaderResourceBinding::Data *d = QRhiImplementation::shaderResourceBindingData(v[p++]);
1587 h ^= qintptr(buf);
1588 d->binding = binding;
1589 d->stage = stage;
1590 d->type = QRhiShaderResourceBinding::UniformBuffer;
1591 d->u.ubuf.buf = buf;
1592 d->u.ubuf.offset = offset;
1593 d->u.ubuf.maybeSize = size; // 0 = all
1594 d->u.ubuf.hasDynamicOffset = false;
1595}
1596
1597void QSSGRhiShaderResourceBindingList::addTexture(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler)
1598{
1599#ifdef QT_DEBUG
1600 if (p == QSSGRhiShaderResourceBindingList::MAX_SIZE) {
1601 qWarning("Out of shader resource bindings slots (max is %d)", MAX_SIZE);
1602 return;
1603 }
1604#endif
1605 QRhiShaderResourceBinding::Data *d = QRhiImplementation::shaderResourceBindingData(v[p++]);
1606 h ^= qintptr(tex) ^ qintptr(sampler);
1607 d->binding = binding;
1608 d->stage = stage;
1609 d->type = QRhiShaderResourceBinding::SampledTexture;
1610 d->u.stex.count = 1;
1611 d->u.stex.texSamplers[0].tex = tex;
1612 d->u.stex.texSamplers[0].sampler = sampler;
1613}
1614
1615QT_END_NAMESPACE
1616
1617bool QSSGRhiContextPrivate::shaderDebuggingEnabled()
1618{
1619 static const bool isSet = (qEnvironmentVariableIntValue("QT_RHI_SHADER_DEBUG") != 0);
1620 return isSet;
1621}
1622
1623bool QSSGRhiContextPrivate::editorMode()
1624{
1625 static const bool isSet = (qEnvironmentVariableIntValue("QT_QUICK3D_EDITORMODE") != 0);
1626 return isSet;
1627}
1628
1629QRhiGraphicsPipeline *QSSGRhiContextPrivate::pipeline(const QSSGGraphicsPipelineStateKey &key,
1630 QRhiRenderPassDescriptor *rpDesc,
1631 QRhiShaderResourceBindings *srb)
1632{
1633 auto it = m_pipelines.constFind(key);
1634 if (it != m_pipelines.constEnd())
1635 return it.value();
1636
1637 // Build a new one. This is potentially expensive.
1638 QRhiGraphicsPipeline *ps = m_rhi->newGraphicsPipeline();
1639 const auto &ia = QSSGRhiInputAssemblerStatePrivate::get(key.state);
1640
1641 const auto *shaderPipeline = QSSGRhiGraphicsPipelineStatePrivate::getShaderPipeline(key.state);
1642 ps->setShaderStages(shaderPipeline->cbeginStages(), shaderPipeline->cendStages());
1643 ps->setVertexInputLayout(ia.inputLayout);
1644 ps->setShaderResourceBindings(srb);
1645 ps->setRenderPassDescriptor(rpDesc);
1646
1647 QRhiGraphicsPipeline::Flags flags;
1648 if (key.state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesScissor))
1649 flags |= QRhiGraphicsPipeline::UsesScissor;
1650
1651 static const bool shaderDebugInfo = qEnvironmentVariableIntValue("QT_QUICK3D_SHADER_DEBUG_INFO");
1652 if (shaderDebugInfo)
1653 flags |= QRhiGraphicsPipeline::CompileShadersWithDebugInfo;
1654 ps->setFlags(flags);
1655
1656 ps->setTopology(ia.topology);
1657 ps->setCullMode(key.state.cullMode);
1658 if (ia.topology == QRhiGraphicsPipeline::Lines || ia.topology == QRhiGraphicsPipeline::LineStrip)
1659 ps->setLineWidth(key.state.lineWidth);
1660
1661 const bool blendEnabled = key.state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::BlendEnabled);
1662 QVarLengthArray<QRhiGraphicsPipeline::TargetBlend, 8> targetBlends(key.state.colorAttachmentCount);
1663 for (int i = 0; i < key.state.colorAttachmentCount; ++i) {
1664 targetBlends[i] = key.state.targetBlend[i];
1665 targetBlends[i].enable = blendEnabled;
1666 }
1667 ps->setTargetBlends(targetBlends.cbegin(), targetBlends.cend());
1668
1669 ps->setSampleCount(key.state.samples);
1670 ps->setMultiViewCount(key.state.viewCount);
1671
1672 ps->setDepthTest(key.state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthTestEnabled));
1673 ps->setDepthWrite(key.state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::DepthWriteEnabled));
1674 ps->setDepthOp(key.state.depthFunc);
1675
1676 ps->setDepthBias(key.state.depthBias);
1677 ps->setSlopeScaledDepthBias(key.state.slopeScaledDepthBias);
1678 ps->setPolygonMode(key.state.polygonMode);
1679
1680 const bool usesStencilRef = (key.state.flags.testFlag(QSSGRhiGraphicsPipelineState::Flag::UsesStencilRef));
1681 if (usesStencilRef)
1682 flags |= QRhiGraphicsPipeline::UsesStencilRef;
1683 ps->setFlags(flags);
1684 ps->setStencilFront(key.state.stencilOpFrontState);
1685 ps->setStencilTest(usesStencilRef);
1686 ps->setStencilWriteMask(key.state.stencilWriteMask);
1687
1688 if (!ps->create()) {
1689 qWarning("Failed to build graphics pipeline state");
1690 delete ps;
1691 return nullptr;
1692 }
1693
1694 m_pipelines.insert(key, ps);
1695 return ps;
1696}
1697
1698QRhiComputePipeline *QSSGRhiContextPrivate::computePipeline(const QSSGComputePipelineStateKey &key, QRhiShaderResourceBindings *srb)
1699{
1700 auto it = m_computePipelines.constFind(key);
1701 if (it != m_computePipelines.constEnd())
1702 return it.value();
1703
1704 QRhiComputePipeline *computePipeline = m_rhi->newComputePipeline();
1705 computePipeline->setShaderResourceBindings(srb);
1706 computePipeline->setShaderStage({ QRhiShaderStage::Compute, key.shader });
1707 if (!computePipeline->create()) {
1708 qWarning("Failed to build compute pipeline");
1709 delete computePipeline;
1710 return nullptr;
1711 }
1712 m_computePipelines.insert(key, computePipeline);
1713 return computePipeline;
1714}
1715
1716/*!
1717 \return The recommended flags when calling QRhiCommandBuffer::beginPass().
1718 */
1719QRhiCommandBuffer::BeginPassFlags QSSGRhiContext::commonPassFlags() const
1720{
1721 // We do not use GPU compute at all at the moment, this means we can
1722 // get a small performance gain with OpenGL by declaring this.
1723 return QRhiCommandBuffer::DoNotTrackResourcesForCompute;
1724}
QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps)
QRhiGraphicsPipeline::Topology toTopology(QSSGRenderDrawMode drawMode)
void bakeVertexInputLocations(QSSGRhiInputAssemblerState *ia, const QSSGRhiShaderPipeline &shaders, int instanceBufferBinding)
Q_TRACE_POINT(qtcore, QCoreApplication_postEvent_exit)
Q_TRACE_POINT(qtquick3d, QSSG_renderFrame_entry, int width, int height)
static QString getUBMemberSizeWarning(QLatin1StringView name, qsizetype correctedSize, qsizetype requestedSize)