Revision control
Copy as Markdown
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qquick3drenderpass_p.h"
#include <QtQuick3D/private/qquick3dscenemanager_p.h>
#include <QtQuick3DUtils/private/qssgassert_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderuserpass_p.h>
#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
#include <QtCore/QLoggingCategory>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQuick3DRenderPass, "qt.quick3d.renderpass")
/*!
\qmltype RenderPass
\inherits Object3D
\inqmlmodule QtQuick3D
\brief Defines a custom render pass for rendering 3D content.
\since 6.11
A RenderPass defines a rendering step and the render target it writes
into. It is the combination of three concerns:
\list
\li \b {Where to render} — one or more output textures declared as
\l ColorAttachment or \l DepthTextureAttachment commands in
\l {RenderPass::commands}{commands}.
\li \b {What to render} — which scene objects the pass draws,
controlled by \l RenderablesFilter commands. Sub-divisions of
work within the same render target are described using
\l SubRenderPass commands.
\li \b {How to render} — the \l materialMode and any
\l PipelineStateOverride commands.
\endlist
A RenderPass becomes active for a scene when it is placed as a child
of a \l View3D or a \l Node, or of another RenderPass.
\section1 Render order
Nesting a RenderPass inside another RenderPass expresses ordering:
nested passes render \e before their parent, so a pass that produces a
texture consumed by another pass is declared as a child of its
consumer, and results merge towards the top of the pass hierarchy.
Only RenderPass ancestors count towards the nesting depth; other
ancestors, such as \l Node items used for grouping, do not affect the
order. The relative render order of passes at the same nesting depth
is not defined; use nesting to express an ordering requirement.
Nesting affects ordering only; a nested pass still renders into its
own render target. To render into the parent pass's render target,
reference the pass from a \l SubRenderPass command instead. A pass
referenced by a SubRenderPass command is invoked by its parent and
never renders on its own.
The following example sets up a simple off-screen pass that renders
all scene objects into a custom texture, which can then be consumed
by a material or a \l SimpleQuadRenderer:
\qml
import QtQuick3D
View3D {
// Declare the off-screen color buffer
RenderPassTexture {
id: myColorTexture
format: RenderPassTexture.RGBA8
}
// The render pass: where + what + how
RenderPass {
id: myRenderPass
commands: [
// Where: attach the texture as the color output
ColorAttachment {
name: "color0"
target: myColorTexture
},
// What: render all objects into it
RenderablesFilter {
renderableTypes: RenderablesFilter.Opaque | RenderablesFilter.Transparent
}
]
}
}
\endqml
\section1 Exposing data to the shaders
As with Effects and Custom Materials, the RenderPass will expose and
update user-defined properties to the shaders automatically. Any QML
properties declared on a RenderPass subtype will be available as
uniforms in the shader.
\sa SubRenderPass, RenderOutputProvider, RenderablesFilter
*/
QQuick3DRenderPass::QQuick3DRenderPass(QQuick3DObject *parent)
: QQuick3DObject(*(new QQuick3DObjectPrivate(QQuick3DObjectPrivate::Type::RenderPass, QQuick3DObjectPrivate::Flags::RequiresSecondaryUpdate)), 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;
}
// The role follows the SubRenderPass reference count maintained by the
// commands referencing this pass. A role change forces a full update, which
// rebuilds the command list and thereby marks the scene manager's pass set
// dirty, so synchronize() re-derives the classification-dependent state
// (nesting depth, declaration order) for the new role.
const bool isSubPass = (m_subRenderPassRef > 0);
const bool subPassChanged = (renderPassNode->role == QSSGRenderUserPass::Role::SubPass) != isSubPass;
renderPassNode->role = isSubPass ? QSSGRenderUserPass::Role::SubPass : QSSGRenderUserPass::Role::TopLevel;
const bool fullUpdate = newBackendNode || subPassChanged || (m_dirtyAttributes & Dirty::TextureDirty) || (m_dirtyAttributes & CommandsDirty);
auto &shaderAugmentation = renderPassNode->shaderAugmentation;
auto &uniformProps = shaderAugmentation.propertyUniforms;
if (fullUpdate) {
markAllDirty();
// Properties -> uniforms.
uniformProps = extractProperties();
// Commands
renderPassNode->resetCommands();
clearDirty(Dirty::CommandsDirty);
for (QQuick3DShaderUtilsRenderCommand *command : std::as_const(m_commands)) {
if (auto *cmd = command->cloneCommand())
renderPassNode->commands.push_back(cmd);
else
markDirty(CommandsDirty, true); // Try again next time
}
// The command list drives the top-level/sub-pass classification (a pass
// referenced by a SubRenderPass command is a sub-pass). Rebuilding it may
// have changed that, so ask synchronize() to re-derive the classification.
if (QQuick3DSceneManager *sceneManager = QQuick3DObjectPrivate::get(this)->sceneManager)
sceneManager->userRenderPassesDirty = true;
}
// 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;
}
}
}
}
clearDirty(Dirty(Dirty::PropertyDirty | Dirty::TextureDirty));
}
// Clear Dirty
if (m_dirtyAttributes & Dirty::ClearDirty) {
renderPassNode->renderTargetFlags = QRhiTextureRenderTarget::Flags(m_renderTargetFlags.toInt());
renderPassNode->clearColor = m_clearColor;
renderPassNode->depthStencilClearValue = { m_depthClearValue, m_stencilClearValue };
clearDirty(Dirty::ClearDirty);
}
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;
}
clearDirty(Dirty::PassTypeDirty);
}
// If not a user pass, we're done
if (m_passMode != UserPass)
return renderPassNode;
renderPassNode->materialMode = QSSGRenderUserPass::MaterialModes(m_materialMode);
clearDirty(Dirty::MaterialModeDirty);
if (renderPassNode->materialMode == QSSGRenderUserPass::OverrideMaterial) {
clearDirty(Dirty::OverrideMaterialDirty);
if (m_overrideMaterial) {
// Set the backend material
QSSGRenderGraphObject *graphObject = QQuick3DObjectPrivate::get(m_overrideMaterial)->spatialNode;
if (graphObject)
renderPassNode->overrideMaterial = graphObject;
else
markDirty(OverrideMaterialDirty, true); // 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_dirtyAttributes & AugmentShaderDirty) {
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);
}
}
clearDirty(Dirty::AugmentShaderDirty);
}
return renderPassNode;
}
void QQuick3DRenderPass::itemChange(ItemChange change, const ItemChangeData &value)
{
if (change == QQuick3DObject::ItemSceneChange) {
updateSceneManager(value.sceneManager);
} else if (change == QQuick3DObject::ItemParentHasChanged) {
// The parent determines the pass's validity and nesting depth, both
// derived in synchronize(). A same-manager reparent does not travel
// through pass registration or removal, so mark the set dirty here.
if (QQuick3DSceneManager *sceneManager = QQuick3DObjectPrivate::get(this)->sceneManager)
sceneManager->userRenderPassesDirty = true;
}
}
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::onCommandChanged()
{
markDirty(CommandsDirty);
}
void QQuick3DRenderPass::qmlAppendCommand(QQmlListProperty<QQuick3DShaderUtilsRenderCommand> *list, QQuick3DShaderUtilsRenderCommand *command)
{
if (!command)
return;
QQuick3DRenderPass *that = qobject_cast<QQuick3DRenderPass *>(list->object);
if (!command->parentItem())
command->setParentItem(that);
that->m_commands.push_back(command);
that->markDirty(CommandsDirty);
// Re-clone commands whenever a property changes so the render thread
// sees the updated state.
QObject::connect(command, &QQuick3DShaderUtilsRenderCommand::changed, that, &QQuick3DRenderPass::onCommandChanged);
}
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);
for (QQuick3DShaderUtilsRenderCommand *cmd : std::as_const(that->m_commands))
QObject::disconnect(cmd, nullptr, that, nullptr);
that->m_commands.clear();
that->markDirty(CommandsDirty);
}
void QQuick3DRenderPass::updateSceneManager(QQuick3DSceneManager *sceneManager)
{
if (sceneManager) {
// Handle inline override material that may not have had a scene manager when it was set
if (m_overrideMaterial && !m_overrideMaterial->parentItem() && !QQuick3DObjectPrivate::get(m_overrideMaterial)->sceneManager) {
if (!m_overrideMaterialRefed) {
QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
m_overrideMaterialRefed = true;
}
}
} else {
// Deref the material when scene manager is removed
if (m_overrideMaterial && m_overrideMaterialRefed) {
QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
m_overrideMaterialRefed = false;
}
}
}
void QQuick3DRenderPass::markDirty(Dirty type, bool requestSecondaryUpdate)
{
if (!(m_dirtyAttributes & quint32(type))) {
m_dirtyAttributes |= quint32(type);
update();
}
if (requestSecondaryUpdate)
QQuick3DObjectPrivate::get(this)->requestSecondaryUpdate();
}
void QQuick3DRenderPass::clearDirty(Dirty type)
{
m_dirtyAttributes &= ~quint32(type);
}
/*!
\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 SubRenderPass,
PipelineStateOverride,
RenderablesFilter,
RenderPassTexture,
ColorAttachment,
DepthTextureAttachment,
DepthStencilAttachment,
AddDefine,
renderTargetBlend
*/
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
Controls how object materials are handled when rendering into this pass.
\value RenderPass.OriginalMaterial
Objects are rendered using their own assigned materials, with full
lighting, textures, and material properties applied normally. This is
the standard mode for rendering a faithful copy of the scene into a
custom render target — for example, a secondary viewpoint for a
reflection probe, a rear-view camera, or a picture-in-picture effect.
The \c overrideMaterial, \c augmentShader, and \c shaders properties
are not used in this mode.
\value RenderPass.AugmentMaterial
Each object is rendered with its own material, but the contents of
the \c {MAIN_FRAGMENT_AUGMENT()} function defined in \l augmentShader
are injected after the original material's output definition. This
allows the augment code to read the material's computed color and write
to additional color outputs defined by \l ColorAttachment commands in
the pass. This is useful for multi-render-target (MRT) passes that need
per-material shading, such as writing the lit color to one attachment
and a world-space normal to another in a single draw call.
\value RenderPass.OverrideMaterial
All objects rendered by this pass use the single \l overrideMaterial
instead of their own. This is useful for depth-only passes, shadow maps,
silhouette or outline effects, and any other case where you want all
geometry to be shaded identically regardless of what material is
assigned to it. The \c augmentShader property is not used in this mode.
\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;
// Deref the old material if we had ref'd it
if (m_overrideMaterial && m_overrideMaterialRefed) {
QQuick3DObjectPrivate::derefSceneManager(m_overrideMaterial);
m_overrideMaterialRefed = false;
}
QQuick3DObjectPrivate::attachWatcher(this, &QQuick3DRenderPass::setOverrideMaterial, newOverrideMaterial, m_overrideMaterial);
m_overrideMaterial = newOverrideMaterial;
// Handle inline material declarations by ensuring they get registered with the scene manager
if (m_overrideMaterial && m_overrideMaterial->parentItem() == nullptr) {
// If the material has no parent, check if it has a hierarchical parent that's a QQuick3DObject
// and re-parent it to that, e.g., inline materials
QQuick3DObject *parentItem = qobject_cast<QQuick3DObject *>(m_overrideMaterial->parent());
if (parentItem) {
m_overrideMaterial->setParentItem(parentItem);
} else {
// If no valid parent was found, make sure the material refs our scene manager
const auto &sceneManager = QQuick3DObjectPrivate::get(this)->sceneManager;
if (sceneManager) {
QQuick3DObjectPrivate::refSceneManager(m_overrideMaterial, *sceneManager);
m_overrideMaterialRefed = true;
}
// else: If there's no scene manager, defer until one is set, see itemChange()
}
}
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 RenderPass::RenderTargetFlags RenderPass::renderTargetFlags
This property holds the render target flags for the render pass. These flags affect how
the render target contents are handled at the beginning and end of each frame.
\value RenderPass.None No special behavior. Color and depth/stencil contents are cleared at the start of each frame.
\value RenderPass.PreserveColorContents Preserve the color contents of the render target between frames, so the previous frame's output remains until explicitly overwritten.
\value RenderPass.PreserveDepthStencilContents Preserve the depth and stencil contents of the render target between frames.
\value RenderPass.DoNotStoreDepthStencilContents Do not store the depth and stencil contents of the render target after rendering (may improve performance on tiled GPUs).
\default RenderPass.None
\sa QRhiTextureRenderTarget::Flags
*/
QQuick3DRenderPass::RenderTargetFlags QQuick3DRenderPass::renderTargetFlags() const
{
return m_renderTargetFlags;
}
void QQuick3DRenderPass::setRenderTargetFlags(RenderTargetFlags newRenderTargetFlags)
{
if (m_renderTargetFlags == newRenderTargetFlags)
return;
m_renderTargetFlags = newRenderTargetFlags;
emit renderTargetFlagsChanged();
markDirty(ClearDirty);
}
// A reference change can flip the pass's role, so the pass is updated to
// mirror the new role to its backend node, and the scene manager's pass set is
// marked dirty so the classification-dependent state (nesting depth,
// declaration order) is re-derived. The scene manager may be null when the
// pass has not entered a scene yet; registration sets the flag then.
static void notifySubPassRefChange(QQuick3DRenderPass *pass)
{
pass->update();
if (QQuick3DSceneManager *sceneManager = QQuick3DObjectPrivate::get(pass)->sceneManager)
sceneManager->userRenderPassesDirty = true;
}
void QQuick3DRenderPassPrivateHelper::subPassRef(QQuick3DRenderPass *pass)
{
Q_ASSERT(pass);
++pass->m_subRenderPassRef;
notifySubPassRefChange(pass);
}
void QQuick3DRenderPassPrivateHelper::subPassDeref(QQuick3DRenderPass *pass)
{
Q_ASSERT(pass);
if (QSSG_GUARD(pass->m_subRenderPassRef > 0))
--pass->m_subRenderPassRef;
notifySubPassRefChange(pass);
}
QT_END_NAMESPACE