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
qquick3drenderpass.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5
7
8#include <QtQuick3DRuntimeRender/private/qssgrenderuserpass_p.h>
9#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
10
11#include <QtCore/QLoggingCategory>
12
14
15Q_LOGGING_CATEGORY(lcQuick3DRenderPass, "qt.quick3d.renderpass")
16
17/*!
18 \qmltype RenderPass
19 \inherits Object3D
20 \inqmlmodule QtQuick3D
21 \brief Defines a custom render pass for rendering 3D content.
22 \since 6.11
23
24 A RenderPass defines a rendering step and the render target it writes
25 into. It is the combination of three concerns:
26
27 \list
28 \li \b {Where to render} — one or more output textures declared as
29 \l ColorAttachment or \l DepthTextureAttachment commands in
30 \l {RenderPass::commands}{commands}.
31 \li \b {What to render} — which scene objects the pass draws,
32 controlled by \l RenderablesFilter commands. Sub-divisions of
33 work within the same render target are described using
34 \l SubRenderPass commands.
35 \li \b {How to render} — the \l materialMode and any
36 \l PipelineStateOverride commands.
37 \endlist
38
39 A RenderPass becomes active for a scene when it is placed as a child
40 of a \l View3D or a \l Node.
41
42 The following example sets up a simple off-screen pass that renders
43 all scene objects into a custom texture, which can then be consumed
44 by a material or a \l SimpleQuadRenderer:
45
46 \qml
47 import QtQuick3D
48
49 View3D {
50 // Declare the off-screen color buffer
51 RenderPassTexture {
52 id: myColorTexture
53 format: RenderPassTexture.RGBA8
54 }
55
56 // The render pass: where + what + how
57 RenderPass {
58 id: myRenderPass
59 commands: [
60 // Where: attach the texture as the color output
61 ColorAttachment {
62 name: "color0"
63 target: myColorTexture
64 },
65 // What: render all objects into it
66 RenderablesFilter {
67 renderableTypes: RenderablesFilter.Opaque | RenderablesFilter.Transparent
68 }
69 ]
70 }
71 }
72 \endqml
73
74 \section1 Exposing data to the shaders
75
76 As with Effects and Custom Materials, the RenderPass will expose and
77 update user-defined properties to the shaders automatically. Any QML
78 properties declared on a RenderPass subtype will be available as
79 uniforms in the shader.
80
81 \sa SubRenderPass, RenderOutputProvider, RenderablesFilter
82*/
83
84QQuick3DRenderPass::QQuick3DRenderPass(QQuick3DObject *parent)
85 : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::RenderPass, QQuick3DObjectPrivate::Flags::RequiresSecondaryUpdate)), parent)
86 , QQuick3DPropertyChangedTracker(this, QQuick3DSuperClassInfo<QQuick3DRenderPass>())
87{
88}
89
90QSSGRenderGraphObject *QQuick3DRenderPass::updateSpatialNode(QSSGRenderGraphObject *node)
91{
92 QSSGRenderUserPass *renderPassNode = static_cast<QSSGRenderUserPass *>(node);
93
94 bool newBackendNode = false;
95 if (!renderPassNode) {
96 renderPassNode = new QSSGRenderUserPass;
97 newBackendNode = true;
98 }
99
100 const bool fullUpdate = newBackendNode || (m_dirtyAttributes & Dirty::TextureDirty) || (m_dirtyAttributes & CommandsDirty);
101
102 auto &shaderAugmentation = renderPassNode->shaderAugmentation;
103 auto &uniformProps = shaderAugmentation.propertyUniforms;
104
105 if (fullUpdate) {
106 markAllDirty();
107
108 // Properties -> uniforms.
109 uniformProps = extractProperties();
110
111 // Commands
112 renderPassNode->resetCommands();
113 clearDirty(Dirty::CommandsDirty);
114 for (QQuick3DShaderUtilsRenderCommand *command : std::as_const(m_commands)) {
115 if (auto *cmd = command->cloneCommand())
116 renderPassNode->commands.push_back(cmd);
117 else
118 markDirty(CommandsDirty, true); // Try again next time
119 }
120 }
121
122 // Update the property values
123 if (m_dirtyAttributes & Dirty::PropertyDirty) {
124 for (const auto &prop : std::as_const(uniformProps)) {
125 auto p = metaObject()->property(prop.pid);
126 if (Q_LIKELY(p.isValid())) {
127 QVariant v = p.read(this);
128 if (v.isValid()) {
129 if (v.metaType().id() == qMetaTypeId<QQuick3DTexture *>()) {
130 QQuick3DTexture *tex = v.value<QQuick3DTexture *>();
131 auto *po = QQuick3DObjectPrivate::get(tex);
132 QSSGRenderImage *ri = static_cast<QSSGRenderImage *>(po->spatialNode);
133 prop.value = QVariant::fromValue(ri);
134 } else {
135 prop.value = v;
136 }
137 }
138 }
139 }
140
141 clearDirty(Dirty(Dirty::PropertyDirty | Dirty::TextureDirty));
142 }
143
144 // Clear Dirty
145 if (m_dirtyAttributes & Dirty::ClearDirty) {
146 renderPassNode->renderTargetFlags = QRhiTextureRenderTarget::Flags(m_renderTargetFlags.toInt());
147 renderPassNode->clearColor = m_clearColor;
148 renderPassNode->depthStencilClearValue = { m_depthClearValue, m_stencilClearValue };
149
150 clearDirty(Dirty::ClearDirty);
151 }
152
153 if (m_dirtyAttributes & Dirty::PassTypeDirty) {
154 switch (m_passMode) {
155 case UserPass:
156 renderPassNode->passMode = QSSGRenderUserPass::UserPass;
157 break;
158 case SkyboxPass:
159 renderPassNode->passMode = QSSGRenderUserPass::SkyboxPass;
160 break;
161 case Item2DPass:
162 renderPassNode->passMode = QSSGRenderUserPass::Item2DPass;
163 break;
164 }
165
166 clearDirty(Dirty::PassTypeDirty);
167 }
168
169 // If not a user pass, we're done
170 if (m_passMode != UserPass)
171 return renderPassNode;
172
173 renderPassNode->materialMode = QSSGRenderUserPass::MaterialModes(m_materialMode);
174 clearDirty(Dirty::MaterialModeDirty);
175
176 if (renderPassNode->materialMode == QSSGRenderUserPass::OverrideMaterial) {
177 clearDirty(Dirty::OverrideMaterialDirty);
178 if (m_overrideMaterial) {
179 // Set the backend material
180 QSSGRenderGraphObject *graphObject = QQuick3DObjectPrivate::get(m_overrideMaterial)->spatialNode;
181 if (graphObject)
182 renderPassNode->overrideMaterial = graphObject;
183 else
184 markDirty(OverrideMaterialDirty, true); // Try again next time
185 } else {
186 // Set nullptr
187 renderPassNode->overrideMaterial = nullptr;
188 }
189 } else if (renderPassNode->materialMode == QSSGRenderUserPass::OriginalMaterial) {
190 // Nothing to do
191 } else if (renderPassNode->materialMode == QSSGRenderUserPass::AugmentMaterial) {
192 // Augment Shaders
193 if (!m_augmentShader.isEmpty()) {
194 const QQmlContext *context = qmlContext(this);
195 QByteArray shaderPathKey("augment material --");
196 QByteArray augment = QSSGShaderUtils::resolveShader(m_augmentShader, context, shaderPathKey);
197 QByteArray augmentSnippet;
198 QByteArray augmentPreamble;
199
200 // We have to pick apart the shader string such that the contents of the:
201 // void MAIN_FRAGMENT_AUGMENT() { }
202 // function are taken out, and will get added to the end of the shader generation
203 // and the goal is to overwrite the "output" of the shader
204
205 // We also need to scan the who shader code for certain "keywords" so that we know
206 // what features to enable in the original material.
207
208 // Everything else outsode of MAIN_FRAGMENT_AUGMENT function ends up being preamble code
209 // that will get pasted in before the real main(). So that will include helper functions and
210 // resolvable #includes etc.
211
212 static const char *mainFuncStart = "void MAIN_FRAGMENT_AUGMENT()";
213 qsizetype mainFuncIdx = augment.indexOf(mainFuncStart);
214 if (mainFuncIdx != -1) {
215 qsizetype braceOpenIdx = augment.indexOf('{', mainFuncIdx + int(strlen(mainFuncStart)));
216 if (braceOpenIdx != -1) {
217 qsizetype braceCloseIdx = braceOpenIdx;
218 qsizetype openBraces = 1;
219 while (openBraces > 0 && braceCloseIdx + 1 < augment.size()) {
220 braceCloseIdx++;
221 if (augment[braceCloseIdx] == '{')
222 openBraces++;
223 else if (augment[braceCloseIdx] == '}')
224 openBraces--;
225 }
226 if (openBraces == 0) {
227 // We found the closing brace
228 augmentSnippet = augment.mid(braceOpenIdx + 1, braceCloseIdx - braceOpenIdx - 1);
229 augmentPreamble = augment.left(mainFuncIdx);
230 augmentPreamble += augment.mid(braceCloseIdx + 1);
231 } else {
232 qWarning("QQuick3DRenderPass: Could not find the closing brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
233 }
234 } else {
235 qWarning("QQuick3DRenderPass: Could not find the opening brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
236 }
237 } else {
238 qWarning("QQuick3DRenderPass: Could not find MAIN_FRAGMENT_AUGMENT() function in shader %s", qPrintable(m_augmentShader.toString()));
239 }
240
241 renderPassNode->shaderAugmentation.body = augmentSnippet;
242 renderPassNode->shaderAugmentation.preamble = augmentPreamble;
243 renderPassNode->markDirty(QSSGRenderUserPass::DirtyFlag::ShaderDirty);
244 }
245 }
246
247 return renderPassNode;
248}
249
250void QQuick3DRenderPass::itemChange(ItemChange change, const ItemChangeData &value)
251{
252 if (change == QQuick3DObject::ItemSceneChange)
253 updateSceneManager(value.sceneManager);
254}
255
256void QQuick3DRenderPass::markTrackedPropertyDirty(QMetaProperty property, DirtyPropertyHint hint)
257{
258 Q_UNUSED(property);
259
260 // FIXME: As with the property tracking for Effects and Custom materials we
261 // should really track which property changed and only update that one.
262 if (hint == DirtyPropertyHint::Reference) {
263 // FIXME: We should verify that the property is actually a texture property.
264 markDirty(Dirty::TextureDirty);
265 } else {
266 markDirty(Dirty::PropertyDirty);
267 }
268}
269
270void QQuick3DRenderPass::onMaterialDestroyed(QObject *object)
271{
272 if (m_overrideMaterial == object) {
273 m_overrideMaterial = nullptr;
274 emit overrideMaterialChanged();
275 markDirty(OverrideMaterialDirty);
276 }
277}
278
279void QQuick3DRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, QQuick3DShaderUtilsRenderCommand *command)
280{
281 if (!command)
282 return;
283
284 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
285
286 if (!command->parentItem())
287 command->setParentItem(that);
288
289 that->m_commands.push_back(command);
290 that->markDirty(CommandsDirty);
291}
292
293QQuick3DShaderUtilsRenderCommand *QQuick3DRenderPass::qmlCommandAt(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, qsizetype index)
294{
295 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
296 return that->m_commands.at(index);
297}
298
299qsizetype QQuick3DRenderPass::qmlCommandCount(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
300{
301 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
302 return that->m_commands.size();
303}
304
305void QQuick3DRenderPass::qmlCommandClear(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
306{
307 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
308 that->m_commands.clear();
309 that->markDirty(CommandsDirty);
310}
311
312void QQuick3DRenderPass::updateSceneManager(QQuick3DSceneManager *sceneManager)
313{
314 if (sceneManager) {
315 // Handle inline override material that may not have had a scene manager when it was set
316 if (m_overrideMaterial && !m_overrideMaterial->parentItem() && !QQuick3DObjectPrivate::get(m_overrideMaterial)->sceneManager) {
317 if (!m_overrideMaterialRefed) {
318 QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
319 m_overrideMaterialRefed = true;
320 }
321 }
322 } else {
323 // Deref the material when scene manager is removed
324 if (m_overrideMaterial && m_overrideMaterialRefed) {
325 QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
326 m_overrideMaterialRefed = false;
327 }
328 }
329}
330
331void QQuick3DRenderPass::markDirty(Dirty type, bool requestSecondaryUpdate)
332{
333 if (!(m_dirtyAttributes & quint32(type))) {
334 m_dirtyAttributes |= quint32(type);
335 update();
336 }
337
338 if (requestSecondaryUpdate)
339 QQuick3DObjectPrivate::get(this)->requestSecondaryUpdate();
340}
341
342void QQuick3DRenderPass::clearDirty(Dirty type)
343{
344 m_dirtyAttributes &= ~quint32(type);
345}
346
347/*!
348 \qmlproperty list<RenderCommand> RenderPass::commands
349 This property holds the list of render commands for the render pass.
350
351 The commands in the list are executed in the order they appear in the list.
352
353 \note The commands for RenderPass and Effects are similar but not the same, only
354 those marked as compatible can be used with this RenderPass.
355
356 \sa SubRenderPass,
357 PipelineStateOverride,
358 RenderablesFilter,
359 RenderPassTexture,
360 ColorAttachment,
361 DepthTextureAttachment,
362 DepthStencilAttachment,
363 AddDefine,
364 renderTargetBlend
365*/
366
367QQmlListProperty<QQuick3DShaderUtilsRenderCommand> QQuick3DRenderPass::commands()
368{
369 return QQmlListProperty<QQuick3DShaderUtilsRenderCommand>(this,
370 nullptr,
371 QQuick3DRenderPass::qmlAppendCommand,
372 QQuick3DRenderPass::qmlCommandCount,
373 QQuick3DRenderPass::qmlCommandAt,
374 QQuick3DRenderPass::qmlCommandClear);
375}
376
377/*!
378 \qmlproperty color RenderPass::clearColor
379 This property holds the clear color for the render pass.
380
381 \default Qt.black
382*/
383QColor QQuick3DRenderPass::clearColor() const
384{
385 return m_clearColor;
386}
387
388void QQuick3DRenderPass::setClearColor(const QColor &newClearColor)
389{
390 if (m_clearColor == newClearColor)
391 return;
392 m_clearColor = newClearColor;
393 emit clearColorChanged();
394 markDirty(ClearDirty);
395}
396
397/*!
398 \qmlproperty RenderPass::MaterialModes RenderPass::materialMode
399 Controls how object materials are handled when rendering into this pass.
400
401 \value RenderPass.OriginalMaterial
402 Objects are rendered using their own assigned materials, with full
403 lighting, textures, and material properties applied normally. This is
404 the standard mode for rendering a faithful copy of the scene into a
405 custom render target — for example, a secondary viewpoint for a
406 reflection probe, a rear-view camera, or a picture-in-picture effect.
407 The \c overrideMaterial, \c augmentShader, and \c shaders properties
408 are not used in this mode.
409
410 \value RenderPass.AugmentMaterial
411 Each object is rendered with its own material, but the contents of
412 the \c {MAIN_FRAGMENT_AUGMENT()} function defined in \l augmentShader
413 are injected after the original material's output definition. This
414 allows the augment code to read the material's computed color and write
415 to additional color outputs defined by \l ColorAttachment commands in
416 the pass. This is useful for multi-render-target (MRT) passes that need
417 per-material shading, such as writing the lit color to one attachment
418 and a world-space normal to another in a single draw call.
419
420 \value RenderPass.OverrideMaterial
421 All objects rendered by this pass use the single \l overrideMaterial
422 instead of their own. This is useful for depth-only passes, shadow maps,
423 silhouette or outline effects, and any other case where you want all
424 geometry to be shaded identically regardless of what material is
425 assigned to it. The \c augmentShader property is not used in this mode.
426
427 \default RenderPass.OriginalMaterial
428*/
429QQuick3DRenderPass::MaterialModes QQuick3DRenderPass::materialMode() const
430{
431 return m_materialMode;
432}
433
434void QQuick3DRenderPass::setMaterialMode(MaterialModes newMaterialMode)
435{
436 if (m_materialMode == newMaterialMode)
437 return;
438 m_materialMode = newMaterialMode;
439 emit materialModeChanged();
440 markDirty(MaterialModeDirty);
441}
442
443/*!
444 \qmlproperty Material RenderPass::overrideMaterial
445 This property holds the override material for the render pass when
446 \l{RenderPass::materialMode}{materialMode} is set to \c OverrideMaterial.
447*/
448QQuick3DMaterial *QQuick3DRenderPass::overrideMaterial() const
449{
450 return m_overrideMaterial;
451}
452
453void QQuick3DRenderPass::setOverrideMaterial(QQuick3DMaterial *newOverrideMaterial)
454{
455 if (m_overrideMaterial == newOverrideMaterial)
456 return;
457
458 // Deref the old material if we had ref'd it
459 if (m_overrideMaterial && m_overrideMaterialRefed) {
460 QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
461 m_overrideMaterialRefed = false;
462 }
463
464 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRenderPass::setOverrideMaterial, newOverrideMaterial, m_overrideMaterial);
465
466 m_overrideMaterial = newOverrideMaterial;
467
468 // Handle inline material declarations by ensuring they get registered with the scene manager
469 if (m_overrideMaterial && m_overrideMaterial->parentItem() == nullptr) {
470 // If the material has no parent, check if it has a hierarchical parent that's a QQuick3DObject
471 // and re-parent it to that, e.g., inline materials
472 QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(m_overrideMaterial->parent());
473 if (parentItem) {
474 m_overrideMaterial->setParentItem(parentItem);
475 } else {
476 // If no valid parent was found, make sure the material refs our scene manager
477 const auto &sceneManager = QQuick3DObjectPrivate::get(this)->sceneManager;
478 if (sceneManager) {
479 QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
480 m_overrideMaterialRefed = true;
481 }
482 // else: If there's no scene manager, defer until one is set, see itemChange()
483 }
484 }
485
486 emit overrideMaterialChanged();
487 markDirty(OverrideMaterialDirty);
488}
489
490/*!
491 \qmlproperty url RenderPass::augmentShader
492 This property holds the augment shader URL for the render pass when
493 \l{RenderPass::materialMode}{materialMode} is set to \c AugmentMaterial.
494
495 The shader file should contain a function with the following signature:
496 \badcode
497 void MAIN_FRAGMENT_AUGMENT() {
498 // Custom shader code here
499 }
500 \endcode
501
502 This function will be combined with the existing fragment shader of the material
503 being used by the object being rendered in this render pass. Allowing users to
504 augment the existing material shader with custom code.
505*/
506QUrl QQuick3DRenderPass::augmentShader() const
507{
508 return m_augmentShader;
509}
510
511void QQuick3DRenderPass::setAugmentShader(const QUrl &newAugmentShader)
512{
513 if (m_augmentShader == newAugmentShader)
514 return;
515 m_augmentShader = newAugmentShader;
516 emit augmentShaderChanged();
517 markDirty(AugmentShaderDirty);
518}
519
520/*!
521 \qmlproperty RenderPass::PassMode RenderPass::passMode
522 This property holds the pass mode for the render pass.
523
524 In addition to standard user render passes, Qt Quick 3D supports
525 users to manually triggering internal render passes for rendering
526 the skybox and 2D items.
527
528 \value RenderPass.UserPass A user specified render pass.
529 \value RenderPass.SkyboxPass Qt Quick 3D's built-in skybox render pass.
530 \value RenderPass.Item2DPass Qt Quick 3D's built-in 2D item render pass.
531 \default RenderPass.UserPass
532*/
533
534QQuick3DRenderPass::PassMode QQuick3DRenderPass::passMode() const
535{
536 return m_passMode;
537}
538
539void QQuick3DRenderPass::setPassMode(PassMode newPassMode)
540{
541 if (m_passMode == newPassMode)
542 return;
543 m_passMode = newPassMode;
544 emit passModeChanged();
545 markDirty(PassTypeDirty);
546}
547
548/*!
549 \qmlproperty real RenderPass::depthClearValue
550 This property holds the depth clear value for the render pass.
551
552 \default 1.0
553*/
554float QQuick3DRenderPass::depthClearValue() const
555{
556 return m_depthClearValue;
557}
558
559void QQuick3DRenderPass::setDepthClearValue(float newDepthClearValue)
560{
561 if (qFuzzyCompare(m_depthClearValue, newDepthClearValue))
562 return;
563 m_depthClearValue = newDepthClearValue;
564 emit depthClearValueChanged();
565 markDirty(ClearDirty);
566}
567
568/*!
569 \qmlproperty int RenderPass::stencilClearValue
570 This property holds the stencil clear value for the render pass.
571
572 \default 0
573*/
574quint32 QQuick3DRenderPass::stencilClearValue() const
575{
576 return m_stencilClearValue;
577}
578
579void QQuick3DRenderPass::setStencilClearValue(quint32 newStencilClearValue)
580{
581 if (m_stencilClearValue == newStencilClearValue)
582 return;
583 m_stencilClearValue = newStencilClearValue;
584 emit stencilClearValueChanged();
585 markDirty(ClearDirty);
586}
587
588/*!
589 \qmlproperty RenderPass::RenderTargetFlags RenderPass::renderTargetFlags
590 This property holds the render target flags for the render pass. These flags affect how
591 the render target contents are handled at the beginning and end of each frame.
592
593 \value RenderPass.None No special behavior. Color and depth/stencil contents are cleared at the start of each frame.
594 \value RenderPass.PreserveColorContents Preserve the color contents of the render target between frames, so the previous frame's output remains until explicitly overwritten.
595 \value RenderPass.PreserveDepthStencilContents Preserve the depth and stencil contents of the render target between frames.
596 \value RenderPass.DoNotStoreDepthStencilContents Do not store the depth and stencil contents of the render target after rendering (may improve performance on tiled GPUs).
597
598 \default RenderPass.None
599
600 \sa QRhiTextureRenderTarget::Flags
601*/
602
603QQuick3DRenderPass::RenderTargetFlags QQuick3DRenderPass::renderTargetFlags() const
604{
605 return m_renderTargetFlags;
606}
607
608void QQuick3DRenderPass::setRenderTargetFlags(RenderTargetFlags newRenderTargetFlags)
609{
610 if (m_renderTargetFlags == newRenderTargetFlags)
611 return;
612 m_renderTargetFlags = newRenderTargetFlags;
613 emit renderTargetFlagsChanged();
614 markDirty(ClearDirty);
615}
616
617QT_END_NAMESPACE
Combined button and popup list for selecting options.