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 The RenderPass type defines a custom render pass for rendering 3D content.
22 \since 6.11
23
24 A RenderPass allows you to define a custom rendering step in the rendering pipeline.
25 You can specify various properties such as clear color, material mode, and
26 override materials. Additionally, you can define a list of render commands
27 that dictate how the rendering should be performed.
28
29 \section1 Exposing data to the shaders
30
31 As with Effects and Custom Materials, the RenderPass will expose, and update,
32 user defined properties to the shader automatically.
33*/
34
35QQuick3DRenderPass::QQuick3DRenderPass(QQuick3DObject *parent)
36 : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::RenderPass, QQuick3DObjectPrivate::Flags::RequiresSecondaryUpdate)), parent)
37 , QQuick3DPropertyChangedTracker(this, QQuick3DSuperClassInfo<QQuick3DRenderPass>())
38{
39}
40
41QSSGRenderGraphObject *QQuick3DRenderPass::updateSpatialNode(QSSGRenderGraphObject *node)
42{
43 QSSGRenderUserPass *renderPassNode = static_cast<QSSGRenderUserPass *>(node);
44
45 bool newBackendNode = false;
46 if (!renderPassNode) {
47 renderPassNode = new QSSGRenderUserPass;
48 newBackendNode = true;
49 }
50
51 const bool fullUpdate = newBackendNode || (m_dirtyAttributes & Dirty::TextureDirty) || (m_dirtyAttributes & CommandsDirty);
52
53 auto &shaderAugmentation = renderPassNode->shaderAugmentation;
54 auto &uniformProps = shaderAugmentation.propertyUniforms;
55
56 if (fullUpdate) {
57 markAllDirty();
58
59 // Properties -> uniforms.
60 // NOTE: Calling extractProperties clears existing properties
61 extractProperties(uniformProps);
62
63 // Commands
64 renderPassNode->resetCommands();
65 clearDirty(Dirty::CommandsDirty);
66 for (QQuick3DShaderUtilsRenderCommand *command : std::as_const(m_commands)) {
67 if (auto *cmd = command->cloneCommand())
68 renderPassNode->commands.push_back(cmd);
69 else
70 markDirty(CommandsDirty, true); // Try again next time
71 }
72 }
73
74 // Update the property values
75 if (m_dirtyAttributes & Dirty::PropertyDirty) {
76 for (const auto &prop : std::as_const(uniformProps)) {
77 auto p = metaObject()->property(prop.pid);
78 if (Q_LIKELY(p.isValid())) {
79 QVariant v = p.read(this);
80 if (v.isValid()) {
81 if (v.metaType().id() == qMetaTypeId<QQuick3DTexture *>()) {
82 QQuick3DTexture *tex = v.value<QQuick3DTexture *>();
83 auto *po = QQuick3DObjectPrivate::get(tex);
84 QSSGRenderImage *ri = static_cast<QSSGRenderImage *>(po->spatialNode);
85 prop.value = QVariant::fromValue(ri);
86 } else {
87 prop.value = v;
88 }
89 }
90 }
91 }
92
93 clearDirty(Dirty(Dirty::PropertyDirty | Dirty::TextureDirty));
94 }
95
96 // Clear Dirty
97 if (m_dirtyAttributes & Dirty::ClearDirty) {
98 renderPassNode->renderTargetFlags = QRhiTextureRenderTarget::Flags(m_renderTargetFlags.toInt());
99 renderPassNode->clearColor = m_clearColor;
100 renderPassNode->depthStencilClearValue = { m_depthClearValue, m_stencilClearValue };
101
102 clearDirty(Dirty::ClearDirty);
103 }
104
105 if (m_dirtyAttributes & Dirty::PassTypeDirty) {
106 switch (m_passMode) {
107 case UserPass:
108 renderPassNode->passMode = QSSGRenderUserPass::UserPass;
109 break;
110 case SkyboxPass:
111 renderPassNode->passMode = QSSGRenderUserPass::SkyboxPass;
112 break;
113 case Item2DPass:
114 renderPassNode->passMode = QSSGRenderUserPass::Item2DPass;
115 break;
116 }
117
118 clearDirty(Dirty::PassTypeDirty);
119 }
120
121 // If not a user pass, we're done
122 if (m_passMode != UserPass)
123 return renderPassNode;
124
125 renderPassNode->materialMode = QSSGRenderUserPass::MaterialModes(m_materialMode);
126 clearDirty(Dirty::MaterialModeDirty);
127
128 if (renderPassNode->materialMode == QSSGRenderUserPass::OverrideMaterial) {
129 clearDirty(Dirty::OverrideMaterialDirty);
130 if (m_overrideMaterial) {
131 // Set the backend material
132 QSSGRenderGraphObject *graphObject = QQuick3DObjectPrivate::get(m_overrideMaterial)->spatialNode;
133 if (graphObject)
134 renderPassNode->overrideMaterial = graphObject;
135 else
136 markDirty(OverrideMaterialDirty, true); // Try again next time
137 } else {
138 // Set nullptr
139 renderPassNode->overrideMaterial = nullptr;
140 }
141 } else if (renderPassNode->materialMode == QSSGRenderUserPass::OriginalMaterial) {
142 // Nothing to do
143 } else if (renderPassNode->materialMode == QSSGRenderUserPass::AugmentMaterial) {
144 // Augment Shaders
145 if (!m_augmentShader.isEmpty()) {
146 const QQmlContext *context = qmlContext(this);
147 QByteArray shaderPathKey("augment material --");
148 QByteArray augment = QSSGShaderUtils::resolveShader(m_augmentShader, context, shaderPathKey);
149 QByteArray augmentSnippet;
150 QByteArray augmentPreamble;
151
152 // We have to pick apart the shader string such that the contents of the:
153 // void MAIN_FRAGMENT_AUGMENT() { }
154 // function are taken out, and will get added to the end of the shader generation
155 // and the goal is to overwrite the "output" of the shader
156
157 // We also need to scan the who shader code for certain "keywords" so that we know
158 // what features to enable in the original material.
159
160 // Everything else outsode of MAIN_FRAGMENT_AUGMENT function ends up being preamble code
161 // that will get pasted in before the real main(). So that will include helper functions and
162 // resolvable #includes etc.
163
164 static const char *mainFuncStart = "void MAIN_FRAGMENT_AUGMENT()";
165 qsizetype mainFuncIdx = augment.indexOf(mainFuncStart);
166 if (mainFuncIdx != -1) {
167 qsizetype braceOpenIdx = augment.indexOf('{', mainFuncIdx + int(strlen(mainFuncStart)));
168 if (braceOpenIdx != -1) {
169 qsizetype braceCloseIdx = braceOpenIdx;
170 qsizetype openBraces = 1;
171 while (openBraces > 0 && braceCloseIdx + 1 < augment.size()) {
172 braceCloseIdx++;
173 if (augment[braceCloseIdx] == '{')
174 openBraces++;
175 else if (augment[braceCloseIdx] == '}')
176 openBraces--;
177 }
178 if (openBraces == 0) {
179 // We found the closing brace
180 augmentSnippet = augment.mid(braceOpenIdx + 1, braceCloseIdx - braceOpenIdx - 1);
181 augmentPreamble = augment.left(mainFuncIdx);
182 augmentPreamble += augment.mid(braceCloseIdx + 1);
183 } else {
184 qWarning("QQuick3DRenderPass: Could not find the closing brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
185 }
186 } else {
187 qWarning("QQuick3DRenderPass: Could not find the opening brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
188 }
189 } else {
190 qWarning("QQuick3DRenderPass: Could not find MAIN_FRAGMENT_AUGMENT() function in shader %s", qPrintable(m_augmentShader.toString()));
191 }
192
193 renderPassNode->shaderAugmentation.body = augmentSnippet;
194 renderPassNode->shaderAugmentation.preamble = augmentPreamble;
195 renderPassNode->markDirty(QSSGRenderUserPass::DirtyFlag::ShaderDirty);
196 }
197 }
198
199 return renderPassNode;
200}
201
202void QQuick3DRenderPass::itemChange(ItemChange change, const ItemChangeData &value)
203{
204 if (change == QQuick3DObject::ItemSceneChange)
205 updateSceneManager(value.sceneManager);
206}
207
208void QQuick3DRenderPass::markTrackedPropertyDirty(QMetaProperty property, DirtyPropertyHint hint)
209{
210 Q_UNUSED(property);
211
212 // FIXME: As with the property tracking for Effects and Custom materials we
213 // should really track which property changed and only update that one.
214 if (hint == DirtyPropertyHint::Reference) {
215 // FIXME: We should verify that the property is actually a texture property.
216 markDirty(Dirty::TextureDirty);
217 } else {
218 markDirty(Dirty::PropertyDirty);
219 }
220}
221
222void QQuick3DRenderPass::onMaterialDestroyed(QObject *object)
223{
224 if (m_overrideMaterial == object) {
225 m_overrideMaterial = nullptr;
226 emit overrideMaterialChanged();
227 markDirty(OverrideMaterialDirty);
228 }
229}
230
231void QQuick3DRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, QQuick3DShaderUtilsRenderCommand *command)
232{
233 if (!command)
234 return;
235
236 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
237
238 if (!command->parentItem())
239 command->setParentItem(that);
240
241 that->m_commands.push_back(command);
242 that->markDirty(CommandsDirty);
243}
244
245QQuick3DShaderUtilsRenderCommand *QQuick3DRenderPass::qmlCommandAt(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, qsizetype index)
246{
247 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
248 return that->m_commands.at(index);
249}
250
251qsizetype QQuick3DRenderPass::qmlCommandCount(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
252{
253 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
254 return that->m_commands.size();
255}
256
257void QQuick3DRenderPass::qmlCommandClear(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
258{
259 QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
260 that->m_commands.clear();
261 that->markDirty(CommandsDirty);
262}
263
264void QQuick3DRenderPass::updateSceneManager(QQuick3DSceneManager *sceneManager)
265{
266 if (sceneManager) {
267 // Handle inline override material that may not have had a scene manager when it was set
268 if (m_overrideMaterial && !m_overrideMaterial->parentItem() && !QQuick3DObjectPrivate::get(m_overrideMaterial)->sceneManager) {
269 if (!m_overrideMaterialRefed) {
270 QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
271 m_overrideMaterialRefed = true;
272 }
273 }
274 } else {
275 // Deref the material when scene manager is removed
276 if (m_overrideMaterial && m_overrideMaterialRefed) {
277 QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
278 m_overrideMaterialRefed = false;
279 }
280 }
281}
282
283void QQuick3DRenderPass::markDirty(Dirty type, bool requestSecondaryUpdate)
284{
285 if (!(m_dirtyAttributes & quint32(type))) {
286 m_dirtyAttributes |= quint32(type);
287 update();
288 }
289
290 if (requestSecondaryUpdate)
291 QQuick3DObjectPrivate::get(this)->requestSecondaryUpdate();
292}
293
294void QQuick3DRenderPass::clearDirty(Dirty type)
295{
296 m_dirtyAttributes &= ~quint32(type);
297}
298
299/*!
300 \qmlproperty list<RenderCommand> RenderPass::commands
301 This property holds the list of render commands for the render pass.
302
303 The commands in the list are executed in the order they appear in the list.
304
305 \note The commands for RenderPass and Effects are similar but not the same, only
306 those marked as compatible can be used with this RenderPass.
307
308 \sa renderTargetBlend,
309 PipelineStateOverride,
310 RenderablesFilter,
311 RenderPassTexture,
312 ColorAttachment,
313 DepthTextureAttachment,
314 DepthStencilAttachment,
315 AddDefine
316*/
317
318QQmlListProperty<QQuick3DShaderUtilsRenderCommand> QQuick3DRenderPass::commands()
319{
320 return QQmlListProperty<QQuick3DShaderUtilsRenderCommand>(this,
321 nullptr,
322 QQuick3DRenderPass::qmlAppendCommand,
323 QQuick3DRenderPass::qmlCommandCount,
324 QQuick3DRenderPass::qmlCommandAt,
325 QQuick3DRenderPass::qmlCommandClear);
326}
327
328/*!
329 \qmlproperty color RenderPass::clearColor
330 This property holds the clear color for the render pass.
331
332 \default Qt.black
333*/
334QColor QQuick3DRenderPass::clearColor() const
335{
336 return m_clearColor;
337}
338
339void QQuick3DRenderPass::setClearColor(const QColor &newClearColor)
340{
341 if (m_clearColor == newClearColor)
342 return;
343 m_clearColor = newClearColor;
344 emit clearColorChanged();
345 markDirty(ClearDirty);
346}
347
348/*!
349 \qmlproperty RenderPass::MaterialModes RenderPass::materialMode
350 This property holds the material mode for the render pass.
351
352 \value RenderPass.OriginalMaterial Use the original material of the object.
353 \value RenderPass.AugmentMaterial Augment the original material with custom shader code.
354 \value RenderPass.OverrideMaterial Override the original material with a user specified \l{RenderPass::overrideMaterial}{material}.
355
356 \default RenderPass.OriginalMaterial
357*/
358QQuick3DRenderPass::MaterialModes QQuick3DRenderPass::materialMode() const
359{
360 return m_materialMode;
361}
362
363void QQuick3DRenderPass::setMaterialMode(MaterialModes newMaterialMode)
364{
365 if (m_materialMode == newMaterialMode)
366 return;
367 m_materialMode = newMaterialMode;
368 emit materialModeChanged();
369 markDirty(MaterialModeDirty);
370}
371
372/*!
373 \qmlproperty Material RenderPass::overrideMaterial
374 This property holds the override material for the render pass when
375 \l{RenderPass::materialMode}{materialMode} is set to \c OverrideMaterial.
376*/
377QQuick3DMaterial *QQuick3DRenderPass::overrideMaterial() const
378{
379 return m_overrideMaterial;
380}
381
382void QQuick3DRenderPass::setOverrideMaterial(QQuick3DMaterial *newOverrideMaterial)
383{
384 if (m_overrideMaterial == newOverrideMaterial)
385 return;
386
387 // Deref the old material if we had ref'd it
388 if (m_overrideMaterial && m_overrideMaterialRefed) {
389 QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
390 m_overrideMaterialRefed = false;
391 }
392
393 QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRenderPass::setOverrideMaterial, newOverrideMaterial, m_overrideMaterial);
394
395 m_overrideMaterial = newOverrideMaterial;
396
397 // Handle inline material declarations by ensuring they get registered with the scene manager
398 if (m_overrideMaterial && m_overrideMaterial->parentItem() == nullptr) {
399 // If the material has no parent, check if it has a hierarchical parent that's a QQuick3DObject
400 // and re-parent it to that, e.g., inline materials
401 QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(m_overrideMaterial->parent());
402 if (parentItem) {
403 m_overrideMaterial->setParentItem(parentItem);
404 } else {
405 // If no valid parent was found, make sure the material refs our scene manager
406 const auto &sceneManager = QQuick3DObjectPrivate::get(this)->sceneManager;
407 if (sceneManager) {
408 QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
409 m_overrideMaterialRefed = true;
410 }
411 // else: If there's no scene manager, defer until one is set, see itemChange()
412 }
413 }
414
415 emit overrideMaterialChanged();
416 markDirty(OverrideMaterialDirty);
417}
418
419/*!
420 \qmlproperty url RenderPass::augmentShader
421 This property holds the augment shader URL for the render pass when
422 \l{RenderPass::materialMode}{materialMode} is set to \c AugmentMaterial.
423
424 The shader file should contain a function with the following signature:
425 \badcode
426 void MAIN_FRAGMENT_AUGMENT() {
427 // Custom shader code here
428 }
429 \endcode
430
431 This function will be combined with the existing fragment shader of the material
432 being used by the object being rendered in this render pass. Allowing users to
433 augment the existing material shader with custom code.
434*/
435QUrl QQuick3DRenderPass::augmentShader() const
436{
437 return m_augmentShader;
438}
439
440void QQuick3DRenderPass::setAugmentShader(const QUrl &newAugmentShader)
441{
442 if (m_augmentShader == newAugmentShader)
443 return;
444 m_augmentShader = newAugmentShader;
445 emit augmentShaderChanged();
446 markDirty(AugmentShaderDirty);
447}
448
449/*!
450 \qmlproperty RenderPass::PassMode RenderPass::passMode
451 This property holds the pass mode for the render pass.
452
453 In addition to standard user render passes, Qt Quick 3D supports
454 users to manually triggering internal render passes for rendering
455 the skybox and 2D items.
456
457 \value RenderPass.UserPass A user specified render pass.
458 \value RenderPass.SkyboxPass Qt Quick 3D's built-in skybox render pass.
459 \value RenderPass.Item2DPass Qt Quick 3D's built-in 2D item render pass.
460 \default RenderPass.UserPass
461*/
462
463QQuick3DRenderPass::PassMode QQuick3DRenderPass::passMode() const
464{
465 return m_passMode;
466}
467
468void QQuick3DRenderPass::setPassMode(PassMode newPassMode)
469{
470 if (m_passMode == newPassMode)
471 return;
472 m_passMode = newPassMode;
473 emit passModeChanged();
474 markDirty(PassTypeDirty);
475}
476
477/*!
478 \qmlproperty real RenderPass::depthClearValue
479 This property holds the depth clear value for the render pass.
480
481 \default 1.0
482*/
483float QQuick3DRenderPass::depthClearValue() const
484{
485 return m_depthClearValue;
486}
487
488void QQuick3DRenderPass::setDepthClearValue(float newDepthClearValue)
489{
490 if (qFuzzyCompare(m_depthClearValue, newDepthClearValue))
491 return;
492 m_depthClearValue = newDepthClearValue;
493 emit depthClearValueChanged();
494 markDirty(ClearDirty);
495}
496
497/*!
498 \qmlproperty int RenderPass::stencilClearValue
499 This property holds the stencil clear value for the render pass.
500
501 \default 0
502*/
503quint32 QQuick3DRenderPass::stencilClearValue() const
504{
505 return m_stencilClearValue;
506}
507
508void QQuick3DRenderPass::setStencilClearValue(quint32 newStencilClearValue)
509{
510 if (m_stencilClearValue == newStencilClearValue)
511 return;
512 m_stencilClearValue = newStencilClearValue;
513 emit stencilClearValueChanged();
514 markDirty(ClearDirty);
515}
516
517/*!
518 \qmlproperty RenderPass::RenderTargetFlags RenderPass::renderTargetFlags
519 This property holds the render target flags for the render pass.
520
521 \default RenderPass.RenderTargetFlags.None
522
523 Possible values are:
524 \value RenderPass.RenderTargetFlags.None No special behavior.
525 \value RenderPass.RenderTargetFlags.PreserveColorContents Preserve the color contents of the render target between frames.
526 \value RenderPass.RenderTargetFlags.PreserveDepthStencilContents Preserve the depth and stencil contents of the render target between frames.
527 \value RenderPass.RenderTargetFlags.DoNotStoreDepthStencilContents Do not store the depth and stencil contents of the render target after rendering.
528
529 \sa QRhiTextureRenderTarget::Flags
530*/
531
532QQuick3DRenderPass::RenderTargetFlags QQuick3DRenderPass::renderTargetFlags() const
533{
534 return m_renderTargetFlags;
535}
536
537void QQuick3DRenderPass::setRenderTargetFlags(RenderTargetFlags newRenderTargetFlags)
538{
539 if (m_renderTargetFlags == newRenderTargetFlags)
540 return;
541 m_renderTargetFlags = newRenderTargetFlags;
542 emit renderTargetFlagsChanged();
543 markDirty(ClearDirty);
544}
545
546QT_END_NAMESPACE
Combined button and popup list for selecting options.