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
qssgrenderuserpass.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#include <QtQuick3DRuntimeRender/private/qssgrendercommands_p.h>
8#include <QtQuick3DRuntimeRender/private/qssgrendershadertokenizer_p.h>
9#include <QtCore/qcryptographichash.h>
10
11#include <ssg/qssgrendercontextcore.h>
12
13#include <iterator>
14
16
17Q_STATIC_LOGGING_CATEGORY(lcRenderUserPass, "qt.quick3d.runtimerender.renderuserpass")
18
19namespace
20{
21
35
37{
38 switch (macro) {
39 case AugmentMacro::BASE_COLOR:
40 return QSSGUserShaderAugmentation::FeatureFlags::BaseColor;
41 case AugmentMacro::ROUGHNESS:
42 return QSSGUserShaderAugmentation::FeatureFlags::Roughness;
43 case AugmentMacro::METALNESS:
44 return QSSGUserShaderAugmentation::FeatureFlags::Metalness;
45 case AugmentMacro::DIFFUSE_LIGHT:
46 return QSSGUserShaderAugmentation::FeatureFlags::DiffuseLight;
47 case AugmentMacro::SPECULAR_LIGHT:
48 return QSSGUserShaderAugmentation::FeatureFlags::SpecularLight;
49 case AugmentMacro::EMISSIVE_LIGHT:
50 return QSSGUserShaderAugmentation::FeatureFlags::EmissiveLight;
51 case AugmentMacro::WORLD_NORMAL:
52 return QSSGUserShaderAugmentation::FeatureFlags::WorldNormal;
53 case AugmentMacro::WORLD_TANGENT:
54 return QSSGUserShaderAugmentation::FeatureFlags::WorldTangent;
55 case AugmentMacro::WORLD_BINORMAL:
56 return QSSGUserShaderAugmentation::FeatureFlags::WorldBinormal;
57 case AugmentMacro::F0:
58 return QSSGUserShaderAugmentation::FeatureFlags::F0;
59 case AugmentMacro::F90:
60 return QSSGUserShaderAugmentation::FeatureFlags::F90;
61 }
62
63 Q_UNREACHABLE_RETURN(QSSGUserShaderAugmentation::FeatureFlags::None);
64}
65
68
69struct MacroPair {
70 const char macroName[QSSGMaxMacroLength];
71 const char replacement[QSSGMaxReplacementLength];
72};
73
74static constexpr MacroPair augmentMacroPairs[] = {
75 { "BASE_COLOR", "qt_diffuseColor" },
76 { "ROUGHNESS", "qt_roughnessAmount" },
77 { "METALNESS", "qt_metalnessAmount" },
78 { "DIFFUSE_LIGHT", "global_diffuse_light.rgb" },
79 { "SPECULAR_LIGHT", "global_specular_light" },
80 { "EMISSIVE_LIGHT", "global_emission" },
81 { "WORLD_NORMAL", "qt_world_normal" },
82 { "WORLD_TANGENT", "qt_tangent" },
83 { "WORLD_BINORMAL", "qt_binormal" },
84 { "F0", "qt_f0" },
85 { "F90", "qt_f90" }
86};
87
89{
90 return QByteArrayView(augmentMacroPairs[static_cast<size_t>(macro)].macroName);
91}
92
94{
95 return QByteArrayView(augmentMacroPairs[static_cast<size_t>(macro)].replacement);
96}
97
98}
99
100// NOTE: While this class doesn't have graphics resources itself, it manages commands that do!
101QSSGRenderUserPass::QSSGRenderUserPass()
102 : QSSGRenderGraphObject(Type::RenderPass, FlagT(Flags::HasGraphicsResources))
103{
104
105}
106
107QSSGRenderUserPass::~QSSGRenderUserPass()
108{
109 resetCommands();
110}
111
112bool QSSGRenderUserPass::isDirty(DirtyFlag flag) const
113{
114 return (m_dirtyFlags & FlagT(flag)) != 0;
115}
116
117void QSSGRenderUserPass::markDirty(DirtyFlag flag)
118{
119 m_dirtyFlags |= FlagT(flag);
120}
121
122void QSSGRenderUserPass::clearDirty(DirtyFlag flag)
123{
124 m_dirtyFlags &= ~FlagT(flag);
125}
126
127void QSSGRenderUserPass::replaceAugmentMacros(QByteArray &shaderCode, QSSGUserShaderAugmentation &augmentation)
128{
129 // Tokenize the shader code and replace any identifier that matches one of the augment
130 // macros (e.g. BASE_COLOR) with the corresponding built-in variable name. Using the
131 // tokenizer means we only ever match whole identifiers - never a substring of a larger
132 // identifier - and macros inside comments are skipped, all without re-implementing that
133 // logic here.
134 QSSGShaderTokenizer tok;
135 tok.initialize(shaderCode.constData());
136
137 const char *const begin = shaderCode.constData();
138 const char *const end = begin + shaderCode.size();
139 const char *lastPos = begin; // Start of the region not yet copied to the result.
140
141 QByteArray result;
142 result.reserve(shaderCode.size());
143
144 for (QSSGShaderTokenizer::Token t = tok.next(); t != QSSGShaderTokenizer::Token_EOF; t = tok.next()) {
145 if (t != QSSGShaderTokenizer::Token_Identifier)
146 continue;
147
148 const QByteArrayView id(tok.identifier, tok.pos - tok.identifier);
149 for (size_t i = 0, n = std::size(augmentMacroPairs); i < n; ++i) {
150 const AugmentMacro macro = static_cast<AugmentMacro>(i);
151 if (id != augmentMacroName(macro))
152 continue;
153
154 // Copy everything up to the macro verbatim (preserving comments, whitespace and
155 // any non-matching code), then append the replacement in its place.
156 const QByteArrayView replacement = augmentMacroReplacement(macro);
157 result.append(lastPos, tok.identifier - lastPos);
158 result.append(replacement.data(), replacement.size());
159 lastPos = tok.pos;
160 augmentation.setFeatureFlag(featureForMacro(macro));
161 break;
162 }
163 }
164
165 // No macros were found, leave the shader code untouched.
166 if (lastPos == begin)
167 return;
168
169 // Copy the trailing remainder after the last replacement.
170 result.append(lastPos, end - lastPos);
171 shaderCode = std::move(result);
172}
173
174void QSSGRenderUserPass::finalizeShaders(const QSSGRenderContextInterface &ctx)
175{
176 Q_UNUSED(ctx);
177
178 if (materialMode != AugmentMaterial)
179 return;
180
181 if (!isDirty(DirtyFlag::ShaderDirty))
182 return;
183
184 clearDirty(DirtyFlag::ShaderDirty);
185
186 shaderAugmentation.featureFlags = 0;
187 shaderAugmentation.hash.clear();
188
189 const bool hasShaderCode = (shaderAugmentation.hasUserAugmentation());
190
191 if (hasShaderCode) {
192 qCDebug(lcRenderUserPass) << "Finalizing shaders for user pass with augment shader code.";
193
194 // Replace any instances of the AugmentMacros with the appropriate built-in variable names
195 replaceAugmentMacros(shaderAugmentation.preamble, shaderAugmentation);
196 replaceAugmentMacros(shaderAugmentation.body, shaderAugmentation);
197 // Used to build the unique shader cache key. The shader key goes in the generated
198 // shader as a one line comment so it can't be multiline. Generate hash from the
199 // augmented shader preamble and body instead, which gets combined with the material key.
200 const QByteArray fullAugmentation = shaderAugmentation.preamble + shaderAugmentation.body;
201 shaderAugmentation.hash = QCryptographicHash::hash(QByteArrayView(fullAugmentation),
202 QCryptographicHash::Algorithm::Sha1).toHex();
203
204 m_state = State::Ready;
205 } else {
206 m_state = State::Error;
207
208 qWarning() << "No augment shader code provided for user pass.";
209 }
210
211 // Finalize shaders for all commands
212 qCDebug(lcRenderUserPass) << "Finalizing shaders for user pass commands";
213}
214
215void QSSGRenderUserPass::resetCommands()
216{
217 qDeleteAll(commands);
218 commands.clear();
219}
220
221QT_END_NAMESPACE
constexpr QByteArrayView augmentMacroReplacement(AugmentMacro macro)
static constexpr MacroPair augmentMacroPairs[]
static constexpr QSSGUserShaderAugmentation::FeatureFlags featureForMacro(AugmentMacro macro)
constexpr QByteArrayView augmentMacroName(AugmentMacro macro)
constexpr size_t QSSGMaxReplacementLength
constexpr size_t QSSGMaxMacroLength
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)
const char replacement[QSSGMaxReplacementLength]
const char macroName[QSSGMaxMacroLength]