aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick3d/qquick3drenderpass.cpp
blob: feb29d99c22fb1d9b84ca70bbd3d7377ff949105 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include "qquick3drenderpass_p.h"

#include <QtQuick3DRuntimeRender/private/qssgrenderuserpass_p.h>
#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>

QT_BEGIN_NAMESPACE

/*!
    \qmltype RenderPass
    \inherits Object3D
    \inqmlmodule QtQuick3D
    \brief The RenderPass type defines a custom render pass for rendering 3D content.
    \since 6.11

    A RenderPass allows you to define a custom rendering step in the rendering pipeline.
    You can specify various properties such as clear color, material mode, and
    override materials. Additionally, you can define a list of render commands
    that dictate how the rendering should be performed.

    \section1 Exposing data to the shaders

    As with Effects and Custom Materials, the RenderPass will expose, and update,
    user defined properties to the shader automatically.
*/

QQuick3DRenderPass::QQuick3DRenderPass(QQuick3DObject *parent)
    : QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::RenderPass)), parent)
    , QQuick3DPropertyChangedTracker(this, QQuick3DSuperClassInfo<QQuick3DRenderPass>())
{
}

QSSGRenderGraphObject *QQuick3DRenderPass::updateSpatialNode(QSSGRenderGraphObject *node)
{
    QSSGRenderUserPass *renderPassNode = static_cast<QSSGRenderUserPass *>(node);

    bool newBackendNode = false;
    if (!renderPassNode) {
        renderPassNode = new QSSGRenderUserPass;
        newBackendNode = true;
    }

    const bool fullUpdate = newBackendNode  || (m_dirtyAttributes & Dirty::TextureDirty);

    auto &shaderAugmentation = renderPassNode->shaderAugmentation;
    auto &uniformProps = shaderAugmentation.propertyUniforms;

    if (fullUpdate) {
        markAllDirty();

        // Properties -> uniforms.
        // NOTE: Calling extractProperties clears existing properties
        extractProperties(uniformProps);

        // Commands
        renderPassNode->resetCommands();
        for (QQuick3DShaderUtilsRenderCommand *command : std::as_const(m_commands)) {
            if (auto *cmd = command->cloneCommand())
                renderPassNode->commands.push_back(cmd);
        }
    }

    // Update the property values
    if (m_dirtyAttributes & Dirty::PropertyDirty) {
        for (const auto &prop : std::as_const(uniformProps)) {
            auto p = metaObject()->property(prop.pid);
            if (Q_LIKELY(p.isValid())) {
                QVariant v = p.read(this);
                if (v.isValid()) {
                    if (v.metaType().id() == qMetaTypeId<QQuick3DTexture *>()) {
                        QQuick3DTexture *tex = v.value<QQuick3DTexture *>();
                        auto *po = QQuick3DObjectPrivate::get(tex);
                        QSSGRenderImage *ri = static_cast<QSSGRenderImage *>(po->spatialNode);
                        prop.value = QVariant::fromValue(ri);
                    } else {
                        prop.value = v;
                    }
                }
            }
        }
    }

    // Clear Dirty
    if (m_dirtyAttributes & Dirty::ClearDirty) {
        renderPassNode->clearBuffers = m_enableClearBuffers;
        renderPassNode->clearColor = m_clearColor;
        renderPassNode->depthStencilClearValue = { m_depthClearValue, m_stencilClearValue };
    }

    if (m_dirtyAttributes & Dirty::PassTypeDirty) {
        switch (m_passMode) {
        case UserPass:
            renderPassNode->passMode = QSSGRenderUserPass::UserPass;
            break;
        case SkyboxPass:
            renderPassNode->passMode = QSSGRenderUserPass::SkyboxPass;
            break;
        case Item2DPass:
            renderPassNode->passMode = QSSGRenderUserPass::Item2DPass;
            break;
        }
    }

    m_dirtyAttributes = 0;

    // If not a user pass, we're done
    if (m_passMode != UserPass)
        return renderPassNode;

    renderPassNode->materialMode = QSSGRenderUserPass::MaterialModes(m_materialMode);
    if (renderPassNode->materialMode == QSSGRenderUserPass::OverrideMaterial) {
        if (m_overrideMaterial) {
            // Set the backend material
            QSSGRenderGraphObject *graphObject = QQuick3DObjectPrivate::get(m_overrideMaterial)->spatialNode;
            if (graphObject)
                renderPassNode->overrideMaterial = graphObject;
            else
                markDirty(OverrideMaterialDirty); // Try again next time
        } else {
            // Set nullptr
            renderPassNode->overrideMaterial = nullptr;
        }
    } else if (renderPassNode->materialMode == QSSGRenderUserPass::OriginalMaterial) {
        // Nothing to do
    } else if (renderPassNode->materialMode == QSSGRenderUserPass::AugmentMaterial) {
        // Augment Shaders
        if (!m_augmentShader.isEmpty()) {
            const QQmlContext *context = qmlContext(this);
            QByteArray shaderPathKey("augment material --");
            QByteArray augment = QSSGShaderUtils::resolveShader(m_augmentShader, context, shaderPathKey);
            QByteArray augmentSnippet;
            QByteArray augmentPreamble;

            // We have to pick apart the shader string such that the contents of the:
            // void MAIN_FRAGMENT_AUGMENT() { }
            // function are taken out, and will get added to the end of the shader generation
            // and the goal is to overwrite the "output" of the shader

            // We also need to scan the who shader code for certain "keywords" so that we know
            // what features to enable in the original material.

            // Everything else outsode of MAIN_FRAGMENT_AUGMENT function ends up being preamble code
            // that will get pasted in before the real main().  So that will include helper functions and
            // resolvable #includes etc.

            static const char *mainFuncStart = "void MAIN_FRAGMENT_AUGMENT()";
            qsizetype mainFuncIdx = augment.indexOf(mainFuncStart);
            if (mainFuncIdx != -1) {
                qsizetype braceOpenIdx = augment.indexOf('{', mainFuncIdx + int(strlen(mainFuncStart)));
                if (braceOpenIdx != -1) {
                    qsizetype braceCloseIdx = braceOpenIdx;
                    qsizetype openBraces = 1;
                    while (openBraces > 0 && braceCloseIdx + 1 < augment.size()) {
                        braceCloseIdx++;
                        if (augment[braceCloseIdx] == '{')
                            openBraces++;
                        else if (augment[braceCloseIdx] == '}')
                            openBraces--;
                    }
                    if (openBraces == 0) {
                        // We found the closing brace
                        augmentSnippet = augment.mid(braceOpenIdx + 1, braceCloseIdx - braceOpenIdx - 1);
                        augmentPreamble = augment.left(mainFuncIdx);
                        augmentPreamble += augment.mid(braceCloseIdx + 1);
                    } else {
                        qWarning("QQuick3DRenderPass: Could not find the closing brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
                    }
                } else {
                    qWarning("QQuick3DRenderPass: Could not find the opening brace of MAIN_FRAGMENT_AUGMENT() in shader %s", qPrintable(m_augmentShader.toString()));
                }
            } else {
                qWarning("QQuick3DRenderPass: Could not find MAIN_FRAGMENT_AUGMENT() function in shader %s", qPrintable(m_augmentShader.toString()));
            }

            renderPassNode->shaderAugmentation.body = augmentSnippet;
            renderPassNode->shaderAugmentation.preamble = augmentPreamble;
            renderPassNode->markDirty(QSSGRenderUserPass::DirtyFlag::ShaderDirty);
        }
    }

    return renderPassNode;
}

void QQuick3DRenderPass::itemChange(ItemChange change, const ItemChangeData &value)
{
    if (change == QQuick3DObject::ItemSceneChange)
        updateSceneManager(value.sceneManager);
}

void QQuick3DRenderPass::markTrackedPropertyDirty(QMetaProperty property, DirtyPropertyHint hint)
{
    Q_UNUSED(property);

    // FIXME: As with the property tracking for Effects and Custom materials we
    // should really track which property changed and only update that one.
    if (hint == DirtyPropertyHint::Reference) {
        // FIXME: We should verify that the property is actually a texture property.
        markDirty(Dirty::TextureDirty);
    } else {
        markDirty(Dirty::PropertyDirty);
    }
}

void QQuick3DRenderPass::onMaterialDestroyed(QObject *object)
{
    if (m_overrideMaterial == object) {
        m_overrideMaterial = nullptr;
        emit overrideMaterialChanged();
        markDirty(OverrideMaterialDirty);
    }
}

void QQuick3DRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, QQuick3DShaderUtilsRenderCommand *command)
{
    if (!command)
        return;

    QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
    that->m_commands.push_back(command);
    that->markDirty(CommandsDirty);
}

QQuick3DShaderUtilsRenderCommand *QQuick3DRenderPass::qmlCommandAt(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, qsizetype index)
{
    QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
    return that->m_commands.at(index);
}

qsizetype QQuick3DRenderPass::qmlCommandCount(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
{
    QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
    return that->m_commands.size();
}

void QQuick3DRenderPass::qmlCommandClear(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list)
{
    QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
    that->m_commands.clear();
    that->markDirty(CommandsDirty);
}

void QQuick3DRenderPass::updateSceneManager(QQuick3DSceneManager *sceneManager)
{
    Q_UNUSED(sceneManager)
}

void QQuick3DRenderPass::markDirty(Dirty type)
{
    if (!(m_dirtyAttributes & quint32(type))) {
        m_dirtyAttributes |= quint32(type);
        update();
    }
}

/*!
    \qmlproperty list<RenderCommand> RenderPass::commands
    This property holds the list of render commands for the render pass.

    The commands in the list are executed in the order they appear in the list.

    \note The commands for RenderPass and Effects are similar but not the same, only
    those marked as compatible can be used with this RenderPass.

    \sa renderTargetBlend,
        PipelineStateOverride,
        RenderablesFilter,
        RenderPassTexture,
        ColorAttachment,
        DepthTextureAttachment,
        DepthStencilAttachment,
        AddDefine
*/

QQmlListProperty<QQuick3DShaderUtilsRenderCommand> QQuick3DRenderPass::commands()
{
    return QQmlListProperty<QQuick3DShaderUtilsRenderCommand>(this,
                                                              nullptr,
                                                              QQuick3DRenderPass::qmlAppendCommand,
                                                              QQuick3DRenderPass::qmlCommandCount,
                                                              QQuick3DRenderPass::qmlCommandAt,
                                                              QQuick3DRenderPass::qmlCommandClear);
}

/*!
    \qmlproperty color RenderPass::clearColor
    This property holds the clear color for the render pass.

    \default Qt.black
*/
QColor QQuick3DRenderPass::clearColor() const
{
    return m_clearColor;
}

void QQuick3DRenderPass::setClearColor(const QColor &newClearColor)
{
    if (m_clearColor == newClearColor)
        return;
    m_clearColor = newClearColor;
    emit clearColorChanged();
    markDirty(ClearDirty);
}

/*!
    \qmlproperty RenderPass::MaterialModes RenderPass::materialMode
    This property holds the material mode for the render pass.

    \value RenderPass.OriginalMaterial Use the original material of the object.
    \value RenderPass.AugmentMaterial Augment the original material with custom shader code.
    \value RenderPass.OverrideMaterial Override the original material with a user specified \l{RenderPass::overrideMaterial}{material}.

    \default RenderPass.OriginalMaterial
*/
QQuick3DRenderPass::MaterialModes QQuick3DRenderPass::materialMode() const
{
    return m_materialMode;
}

void QQuick3DRenderPass::setMaterialMode(MaterialModes newMaterialMode)
{
    if (m_materialMode == newMaterialMode)
        return;
    m_materialMode = newMaterialMode;
    emit materialModeChanged();
    markDirty(MaterialModeDirty);
}

/*!
    \qmlproperty Material RenderPass::overrideMaterial
    This property holds the override material for the render pass when
    \l{RenderPass::materialMode}{materialMode} is set to \c OverrideMaterial.
*/
QQuick3DMaterial *QQuick3DRenderPass::overrideMaterial() const
{
    return m_overrideMaterial;
}

void QQuick3DRenderPass::setOverrideMaterial(QQuick3DMaterial *newOverrideMaterial)
{
    if (m_overrideMaterial == newOverrideMaterial)
        return;

    QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRenderPass::setOverrideMaterial, newOverrideMaterial, m_overrideMaterial);

    m_overrideMaterial = newOverrideMaterial;
    emit overrideMaterialChanged();
    markDirty(OverrideMaterialDirty);
}

/*!
    \qmlproperty url RenderPass::augmentShader
    This property holds the augment shader URL for the render pass when
    \l{RenderPass::materialMode}{materialMode} is set to \c AugmentMaterial.

    The shader file should contain a function with the following signature:
    \badcode
    void MAIN_FRAGMENT_AUGMENT() {
        // Custom shader code here
    }
    \endcode

    This function will be combined with the existing fragment shader of the material
    being used by the object being rendered in this render pass. Allowing users to
    augment the existing material shader with custom code.
*/
QUrl QQuick3DRenderPass::augmentShader() const
{
    return m_augmentShader;
}

void QQuick3DRenderPass::setAugmentShader(const QUrl &newAugmentShader)
{
    if (m_augmentShader == newAugmentShader)
        return;
    m_augmentShader = newAugmentShader;
    emit augmentShaderChanged();
    markDirty(AugmentShaderDirty);
}

/*!
    \qmlproperty RenderPass::PassMode RenderPass::passMode
    This property holds the pass mode for the render pass.

    In addition to standard user render passes, Qt Quick 3D supports
    users to manually triggering internal render passes for rendering
    the skybox and 2D items.

    \value RenderPass.UserPass A user specified render pass.
    \value RenderPass.SkyboxPass Qt Quick 3D's built-in skybox render pass.
    \value RenderPass.Item2DPass Qt Quick 3D's built-in 2D item render pass.
    \default RenderPass.UserPass
*/

QQuick3DRenderPass::PassMode QQuick3DRenderPass::passMode() const
{
    return m_passMode;
}

void QQuick3DRenderPass::setPassMode(PassMode newPassMode)
{
    if (m_passMode == newPassMode)
        return;
    m_passMode = newPassMode;
    emit passModeChanged();
    markDirty(PassTypeDirty);
}

/*!
    \qmlproperty real RenderPass::depthClearValue
    This property holds the depth clear value for the render pass.

    \default 1.0
*/
float QQuick3DRenderPass::depthClearValue() const
{
    return m_depthClearValue;
}

void QQuick3DRenderPass::setDepthClearValue(float newDepthClearValue)
{
    if (qFuzzyCompare(m_depthClearValue, newDepthClearValue))
        return;
    m_depthClearValue = newDepthClearValue;
    emit depthClearValueChanged();
    markDirty(ClearDirty);
}

/*!
    \qmlproperty int RenderPass::stencilClearValue
    This property holds the stencil clear value for the render pass.

    \default 0
*/
quint32 QQuick3DRenderPass::stencilClearValue() const
{
    return m_stencilClearValue;
}

void QQuick3DRenderPass::setStencilClearValue(quint32 newStencilClearValue)
{
    if (m_stencilClearValue == newStencilClearValue)
        return;
    m_stencilClearValue = newStencilClearValue;
    emit stencilClearValueChanged();
    markDirty(ClearDirty);
}

/*!
    \qmlproperty bool RenderPass::enableClearBuffers
    This property holds whether the render pass should clear its buffers
    before rendering.

    \default true.
*/
bool QQuick3DRenderPass::enableClearBuffers() const
{
    return m_enableClearBuffers;
}

void QQuick3DRenderPass::setEnableClearBuffers(bool newEnableClearBuffers)
{
    if (m_enableClearBuffers == newEnableClearBuffers)
        return;
    m_enableClearBuffers = newEnableClearBuffers;
    emit enableClearBuffersChanged();
    markDirty(ClearDirty);
}

QT_END_NAMESPACE