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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtquick3d/src/runtimerender/qssgrhicontext.cpp