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