1// Copyright (C) 2008-2012 NVIDIA Corporation.
2// Copyright (C) 2019 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
4
5#include "qssgrendershadercodegenerator_p.h"
6
7#include <QtQuick3DUtils/private/qssgutils_p.h>
8
9#include "qssgrendercontextcore.h"
10#include <QtQuick3DRuntimeRender/private/qssgrendershaderlibrarymanager_p.h>
11#include <QtQuick3DRuntimeRender/private/qssgshaderresourcemergecontext_p.h>
12
13QT_BEGIN_NAMESPACE
14
15template<typename T>
16static inline void addStartCond(QByteArray &block, const T &var)
17{
18 // must use #if not #ifdef, as we test for the value, because featureset flags
19 // are written out even when 0, think for example #define QSSG_ENABLE_SSM 0
20 if (var.conditionType == QSSGRenderShaderMetadata::Uniform::Regular)
21 block += QString::asprintf(format: "#if %s\n", var.conditionName.constData()).toUtf8();
22 else if (var.conditionType == QSSGRenderShaderMetadata::Uniform::Negated)
23 block += QString::asprintf(format: "#if !%s\n", var.conditionName.constData()).toUtf8();
24}
25
26template<typename T>
27static inline void addEndCond(QByteArray &block, const T &var)
28{
29 if (var.conditionType != QSSGRenderShaderMetadata::Uniform::None)
30 block += QByteArrayLiteral("#endif\n");
31}
32
33struct QSSGShaderGeneratedProgramOutput
34{
35 // never null; so safe to call strlen on.
36 const char *m_vertexShader{ "" };
37 const char *m_fragmentShader{ "" };
38
39 QSSGShaderGeneratedProgramOutput() = default;
40 QSSGShaderGeneratedProgramOutput(const char *vs, const char *fs)
41 : m_vertexShader(vs), m_fragmentShader(fs)
42 {
43 }
44};
45
46QSSGStageGeneratorBase::QSSGStageGeneratorBase(QSSGShaderGeneratorStage inStage)
47
48 : m_stage(inStage)
49{
50}
51
52void QSSGStageGeneratorBase::begin(QSSGShaderGeneratorStageFlags inEnabledStages)
53{
54 m_incoming.clear();
55 m_outgoing = nullptr;
56 m_flatIncoming.clear();
57 m_flatOutgoing = nullptr;
58 m_includes.clear();
59 m_uniforms.clear();
60 m_uniformArrays.clear();
61 m_constantBuffers.clear();
62 m_constantBufferParams.clear();
63 m_codeBuilder.clear();
64 m_finalBuilder.clear();
65 m_enabledStages = inEnabledStages;
66 m_addedFunctions.clear();
67 m_addedDefinitions.clear();
68 // the shared buffers will be cleared elsewhere.
69}
70
71void QSSGStageGeneratorBase::addIncoming(const QByteArray &name, const QByteArray &type)
72{
73 m_incoming.insert(key: name, value: type);
74 m_flatIncoming.remove(key: name);
75}
76
77void QSSGStageGeneratorBase::addOutgoing(const QByteArray &name, const QByteArray &type)
78{
79 if (m_outgoing == nullptr) {
80 Q_ASSERT(false);
81 return;
82 }
83 m_outgoing->insert(key: name, value: type);
84 if (m_flatOutgoing)
85 m_flatOutgoing->remove(key: name);
86}
87
88void QSSGStageGeneratorBase::addFlatIncoming(const QByteArray &name, const QByteArray &type)
89{
90 m_flatIncoming.insert(key: name, value: type);
91 m_incoming.remove(key: name);
92}
93
94void QSSGStageGeneratorBase::addFlatOutgoing(const QByteArray &name, const QByteArray &type)
95{
96 if (m_flatOutgoing == nullptr) {
97 Q_ASSERT(false);
98 return;
99 }
100 m_flatOutgoing->insert(key: name, value: type);
101 if (m_outgoing)
102 m_outgoing->remove(key: name);
103}
104
105void QSSGStageGeneratorBase::addUniform(const QByteArray &name, const QByteArray &type)
106{
107 m_uniforms.insert(key: name, value: type);
108}
109
110void QSSGStageGeneratorBase::addUniformArray(const QByteArray &name, const QByteArray &type, quint32 size)
111{
112 m_uniformArrays.insert(key: name, value: qMakePair(value1&: size, value2: type));
113}
114
115void QSSGStageGeneratorBase::addConstantBuffer(const QByteArray &name, const QByteArray &layout)
116{
117 m_constantBuffers.insert(key: name, value: layout);
118}
119
120void QSSGStageGeneratorBase::addConstantBufferParam(const QByteArray &cbName, const QByteArray &paramName, const QByteArray &type)
121{
122 TParamPair theParamPair(paramName, type);
123 TConstantBufferParamPair theBufferParamPair(cbName, theParamPair);
124 m_constantBufferParams.push_back(t: theBufferParamPair);
125}
126
127QSSGStageGeneratorBase &QSSGStageGeneratorBase::operator<<(const QByteArray &data)
128{
129 m_codeBuilder.append(a: data);
130 return *this;
131}
132
133void QSSGStageGeneratorBase::append(const QByteArray &data)
134{
135 m_codeBuilder.append(a: data);
136 m_codeBuilder.append(s: "\n");
137}
138
139QSSGShaderGeneratorStage QSSGStageGeneratorBase::stage() const { return m_stage; }
140
141void QSSGStageGeneratorBase::addShaderPass2Marker(QSSGStageGeneratorBase::ShaderItemType itemType)
142{
143 Q_ASSERT(m_mergeContext);
144 m_finalBuilder.append(QByteArrayLiteral("//@@") + QByteArray::number(int(itemType)) + QByteArrayLiteral("\n"));
145}
146
147void QSSGStageGeneratorBase::addShaderItemMap(QSSGStageGeneratorBase::ShaderItemType itemType, const TStrTableStrMap &itemMap, ShaderItemMapFlags flags)
148{
149 m_finalBuilder.append(s: "\n");
150
151 Q_ASSERT(m_mergeContext);
152 for (TStrTableStrMap::const_iterator iter = itemMap.begin(), end = itemMap.end(); iter != end; ++iter) {
153 const QByteArray name = iter.key();
154 switch (itemType) {
155 case ShaderItemType::VertexInput:
156 m_mergeContext->registerInput(stage: QSSGShaderGeneratorStage::Vertex, type: iter.value(), name);
157 break;
158 case ShaderItemType::Input:
159 m_mergeContext->registerInput(stage: m_stage, type: iter.value(), name, flat: flags.testFlag(flag: ShaderItemMapFlag::Flat));
160 break;
161 case ShaderItemType::Output:
162 m_mergeContext->registerOutput(stage: m_stage, type: iter.value(), name, flat: flags.testFlag(flag: ShaderItemMapFlag::Flat));
163 break;
164 case ShaderItemType::Uniform:
165 if (iter.value().startsWith(QByteArrayLiteral("sampler")))
166 m_mergeContext->registerSampler(type: iter.value(), name);
167 else
168 m_mergeContext->registerUniformMember(type: iter.value(), name);
169 break;
170 default:
171 qWarning(msg: "Unknown shader item %d", int(itemType));
172 Q_UNREACHABLE();
173 }
174 }
175}
176
177void QSSGStageGeneratorBase::addShaderIncomingMap()
178{
179 addShaderItemMap(itemType: ShaderItemType::VertexInput, itemMap: m_incoming);
180 addShaderPass2Marker(itemType: ShaderItemType::VertexInput);
181}
182
183void QSSGStageGeneratorBase::addShaderUniformMap()
184{
185 addShaderItemMap(itemType: ShaderItemType::Uniform, itemMap: m_uniforms);
186 for (TStrTableSizedStrMap::const_iterator iter = m_uniformArrays.begin(), end = m_uniformArrays.end(); iter != end; ++iter) {
187 const QByteArray name = iter.key() +
188 "[" + QByteArray::number(iter.value().first) + "]";
189 if (iter.value().second.startsWith(QByteArrayLiteral("sampler")))
190 m_mergeContext->registerSampler(type: iter.value().second, name);
191 else
192 m_mergeContext->registerUniformMember(type: iter.value().second, name);
193 }
194 addShaderPass2Marker(itemType: ShaderItemType::Uniform);
195}
196
197void QSSGStageGeneratorBase::addShaderOutgoingMap()
198{
199 if (m_outgoing)
200 addShaderItemMap(itemType: ShaderItemType::Output, itemMap: *m_outgoing);
201 if (m_flatOutgoing)
202 addShaderItemMap(itemType: ShaderItemType::Output, itemMap: *m_flatOutgoing, flags: ShaderItemMapFlag::Flat);
203
204 addShaderPass2Marker(itemType: ShaderItemType::Output);
205}
206
207void QSSGStageGeneratorBase::addShaderConstantBufferItemMap(const QByteArray &itemType, const TStrTableStrMap &cbMap, TConstantBufferParamArray cbParamsArray)
208{
209 m_finalBuilder.append(s: "\n");
210
211 // iterate over all constant buffers
212 for (TStrTableStrMap::const_iterator iter = cbMap.begin(), end = cbMap.end(); iter != end; ++iter) {
213 m_finalBuilder.append(a: iter.value());
214 m_finalBuilder.append(s: " ");
215 m_finalBuilder.append(a: itemType);
216 m_finalBuilder.append(s: " ");
217 m_finalBuilder.append(a: iter.key());
218 m_finalBuilder.append(s: " {\n");
219 // iterate over all param entries and add match
220 for (TConstantBufferParamArray::const_iterator iter1 = cbParamsArray.begin(), end = cbParamsArray.end(); iter1 != end;
221 ++iter1) {
222 if (iter1->first == iter.key()) {
223 m_finalBuilder.append(a: iter1->second.second);
224 m_finalBuilder.append(s: " ");
225 m_finalBuilder.append(a: iter1->second.first);
226 m_finalBuilder.append(s: ";\n");
227 }
228 }
229
230 m_finalBuilder.append(s: "};\n");
231 }
232}
233
234void QSSGStageGeneratorBase::appendShaderCode() { m_finalBuilder.append(a: m_codeBuilder); }
235
236void QSSGStageGeneratorBase::addInclude(const QByteArray &name) { m_includes.insert(value: name); }
237
238void QSSGStageGeneratorBase::buildShaderSourcePass1(QSSGShaderResourceMergeContext *mergeContext)
239{
240 m_mergeContext = mergeContext;
241 addShaderIncomingMap();
242 addShaderUniformMap();
243 addShaderConstantBufferItemMap(itemType: "uniform", cbMap: m_constantBuffers, cbParamsArray: m_constantBufferParams);
244 addShaderOutgoingMap();
245 m_mergeContext = nullptr;
246
247 for (auto iter = m_addedDefinitions.begin(), end = m_addedDefinitions.end();
248 iter != end; ++iter) {
249 m_finalBuilder.append(s: "#ifndef ");
250 m_finalBuilder.append(a: iter.key());
251 m_finalBuilder.append(s: "\n");
252 m_finalBuilder.append(s: "#define ");
253 m_finalBuilder.append(a: iter.key());
254 if (!iter.value().isEmpty())
255 m_finalBuilder.append(QByteArrayLiteral(" ") + iter.value());
256 m_finalBuilder.append(s: "\n#endif\n");
257 }
258
259 // Sort for deterministic shader text when printing/debugging
260 QList<QByteArray> sortedIncludes(m_includes.begin(), m_includes.end());
261 std::sort(first: sortedIncludes.begin(), last: sortedIncludes.end());
262
263 for (const auto &include : sortedIncludes) {
264 m_finalBuilder.append(s: "#include \"");
265 m_finalBuilder.append(a: include);
266 m_finalBuilder.append(s: "\"\n");
267 }
268
269 appendShaderCode();
270}
271
272QByteArray QSSGStageGeneratorBase::buildShaderSourcePass2(QSSGShaderResourceMergeContext *mergeContext)
273{
274 static const char *prefix = "//@@";
275 const int prefixLen = 4;
276 const int typeLen = 1;
277 int from = 0;
278 for (; ;) {
279 int pos = m_finalBuilder.indexOf(bv: prefix, from);
280 if (pos >= 0) {
281 from = pos;
282 ShaderItemType itemType = ShaderItemType(m_finalBuilder.mid(index: pos + prefixLen, len: typeLen).toInt());
283 switch (itemType) {
284 case ShaderItemType::VertexInput:
285 if (m_stage == QSSGShaderGeneratorStage::Vertex) {
286 QByteArray block;
287 for (const QSSGShaderResourceMergeContext::InOutVar &var : mergeContext->m_inOutVars) {
288 if (var.stagesInputIn.testFlag(flag: m_stage))
289 block += QString::asprintf(format: "layout(location = %d) in %s %s;\n", var.location, var.type.constData(), var.name.constData()).toUtf8();
290 }
291 m_finalBuilder.replace(index: pos, len: prefixLen + typeLen, s: block);
292 }
293 break;
294 case ShaderItemType::Input:
295 {
296 QByteArray block;
297 for (const QSSGShaderResourceMergeContext::InOutVar &var : mergeContext->m_inOutVars) {
298 if (var.stagesInputIn.testFlag(flag: m_stage))
299 block += QString::asprintf(format: "layout(location = %d) in %s%s %s;\n", var.location, var.flat ? "flat " : "", var.type.constData(), var.name.constData()).toUtf8();
300 }
301 m_finalBuilder.replace(index: pos, len: prefixLen + typeLen, s: block);
302 }
303 break;
304 case ShaderItemType::Output:
305 {
306 QByteArray block;
307 for (const QSSGShaderResourceMergeContext::InOutVar &var : mergeContext->m_inOutVars) {
308 if (var.stageOutputFrom.testFlag(flag: m_stage))
309 block += QString::asprintf(format: "layout(location = %d) out %s%s %s;\n", var.location, var.flat ? "flat " : "", var.type.constData(), var.name.constData()).toUtf8();
310 }
311 m_finalBuilder.replace(index: pos, len: prefixLen + typeLen, s: block);
312 }
313 break;
314 case ShaderItemType::Uniform:
315 {
316 QByteArray block;
317
318 for (const auto &sampler : std::as_const(t&: mergeContext->m_samplers)) {
319 addStartCond(block, var: sampler);
320 block += QString::asprintf(format: "layout(binding = %d) uniform %s %s;\n",
321 sampler.binding,
322 sampler.type.constData(),
323 sampler.name.constData()).toUtf8();
324 addEndCond(block, var: sampler);
325 }
326
327 if (!mergeContext->m_uniformMembers.isEmpty()) {
328 // The layout (offsets of the members) of the main
329 // uniform block cannot be different in the stages.
330 // (f.ex., a given member must be assumed to be at same
331 // offset both in the vertex and the fragment shader)
332 // Therefore we output everything in all stages.
333 block += QByteArrayLiteral("layout(std140, binding = 0) uniform cbMain {\n");
334 for (auto iter = mergeContext->m_uniformMembers.cbegin(), end = mergeContext->m_uniformMembers.cend();
335 iter != end; ++iter)
336 {
337 addStartCond(block, var: iter.value());
338 block += QString::asprintf(format: " %s %s;\n", iter.value().type.constData(), iter.value().name.constData()).toUtf8();
339 addEndCond(block, var: iter.value());
340 }
341 // No instance name for this uniform block. This is
342 // essential since custom material shader code will not use
343 // any instance name prefix when accessing the members. So
344 // while the internal stuff for default/principled material
345 // could be fixed up with prefixing everything, custom
346 // materials cannot. So leave it out.
347 block += QByteArrayLiteral("};\n");
348 }
349 m_finalBuilder.replace(index: pos, len: prefixLen + typeLen, s: block);
350 }
351 break;
352 default:
353 Q_UNREACHABLE_RETURN(m_finalBuilder);
354 }
355 } else {
356 break;
357 }
358 }
359
360 return m_finalBuilder;
361}
362
363void QSSGStageGeneratorBase::addFunction(const QByteArray &functionName)
364{
365 if (!m_addedFunctions.contains(t: functionName)) {
366 m_addedFunctions.push_back(t: functionName);
367 QByteArray includeName;
368 includeName = "func" + functionName + ".glsllib";
369 addInclude(name: includeName);
370 }
371}
372
373void QSSGStageGeneratorBase::addDefinition(const QByteArray &name, const QByteArray &value)
374{
375 m_addedDefinitions.insert(key: name, value);
376}
377
378void QSSGProgramGenerator::linkStages()
379{
380 // Link stages incoming to outgoing variables.
381 QSSGStageGeneratorBase *previous = nullptr;
382 quint32 theStageId = 1;
383 for (quint32 idx = 0, end = quint32(QSSGShaderGeneratorStage::StageCount); idx < end; ++idx, theStageId = theStageId << 1) {
384 QSSGStageGeneratorBase *thisStage = nullptr;
385 QSSGShaderGeneratorStage theStageEnum = static_cast<QSSGShaderGeneratorStage>(theStageId);
386 if ((m_enabledStages & theStageEnum)) {
387 thisStage = &internalGetStage(inStage: theStageEnum);
388 if (previous) {
389 previous->m_outgoing = &thisStage->m_incoming;
390 previous->m_flatOutgoing = &thisStage->m_flatIncoming;
391 }
392 previous = thisStage;
393 }
394 }
395}
396
397void QSSGProgramGenerator::beginProgram(QSSGShaderGeneratorStageFlags inEnabledStages)
398{
399 m_vs.begin(inEnabledStages);
400 m_fs.begin(inEnabledStages);
401 m_enabledStages = inEnabledStages;
402 linkStages();
403}
404
405QSSGShaderGeneratorStageFlags QSSGProgramGenerator::getEnabledStages() const { return m_enabledStages; }
406
407QSSGStageGeneratorBase &QSSGProgramGenerator::internalGetStage(QSSGShaderGeneratorStage inStage)
408{
409 switch (inStage) {
410 case QSSGShaderGeneratorStage::Vertex:
411 return m_vs;
412 case QSSGShaderGeneratorStage::Fragment:
413 return m_fs;
414 default:
415 Q_ASSERT(false);
416 break;
417 }
418 return m_vs;
419}
420
421QSSGStageGeneratorBase *QSSGProgramGenerator::getStage(QSSGShaderGeneratorStage inStage)
422{
423 if ((m_enabledStages & inStage))
424 return &internalGetStage(inStage);
425 return nullptr;
426}
427
428void QSSGProgramGenerator::registerShaderMetaDataFromSource(QSSGShaderResourceMergeContext *mergeContext, const QByteArray &contents, QSSGShaderGeneratorStage stage)
429{
430 QSSGRenderShaderMetadata::ShaderMetaData meta = QSSGRenderShaderMetadata::getShaderMetaData(data: contents);
431
432 for (const QSSGRenderShaderMetadata::Uniform &u : std::as_const(t&: meta.uniforms)) {
433 if (u.type.startsWith(QByteArrayLiteral("sampler"))) {
434 if (u.multiview && mergeContext->viewCount >= 2) {
435 // 'sampler2D qt_screenTexture' becomes 'sampler2DArray qt_screenTextureArray'
436 mergeContext->registerSampler(type: u.type + QByteArrayLiteral("Array"), name: u.name + QByteArrayLiteral("Array"), conditionType: u.condition, conditionName: u.conditionName);
437 } else {
438 mergeContext->registerSampler(type: u.type, name: u.name, conditionType: u.condition, conditionName: u.conditionName);
439 }
440 } else {
441 if (u.multiview && mergeContext->viewCount >= 2) {
442 const QByteArray name = u.name + "[" + QByteArray::number(mergeContext->viewCount) + "]";
443 mergeContext->registerUniformMember(type: u.type, name, conditionType: u.condition, conditionName: u.conditionName);
444 } else {
445 mergeContext->registerUniformMember(type: u.type, name: u.name, conditionType: u.condition, conditionName: u.conditionName);
446 }
447 }
448 }
449
450 for (const QSSGRenderShaderMetadata::InputOutput &inputVar : std::as_const(t&: meta.inputs)) {
451 if (inputVar.stage == stage)
452 mergeContext->registerInput(stage, type: inputVar.type, name: inputVar.name, flat: inputVar.flat);
453 }
454
455 for (const QSSGRenderShaderMetadata::InputOutput &outputVar : std::as_const(t&: meta.outputs)) {
456 if (outputVar.stage == stage)
457 mergeContext->registerOutput(stage, type: outputVar.type, name: outputVar.name, flat: outputVar.flat);
458 }
459
460 for (auto it = mergeContext->m_inOutVars.cbegin(), end = mergeContext->m_inOutVars.cend(); it != end; ++it) {
461 if (it->stagesInputIn == int(QSSGShaderGeneratorStage::Fragment) && it->stageOutputFrom == 0)
462 qWarning(msg: "Fragment stage input %s is not output from vertex stage; expect errors.", it.key().constData());
463 }
464}
465
466QSSGRhiShaderPipelinePtr QSSGProgramGenerator::compileGeneratedRhiShader(const QByteArray &inMaterialInfoString,
467 const QSSGShaderFeatures &inFeatureSet,
468 QSSGShaderLibraryManager &shaderLibraryManager,
469 QSSGShaderCache &theCache,
470 QSSGRhiShaderPipeline::StageFlags stageFlags,
471 int viewCount,
472 bool perTargetCompilation)
473{
474 // No stages enabled
475 if (((quint32)m_enabledStages) == 0) {
476 Q_ASSERT(false);
477 return nullptr;
478 }
479
480 QSSGShaderResourceMergeContext mergeContext;
481 mergeContext.viewCount = viewCount;
482
483 for (quint32 stageIdx = 0; stageIdx < static_cast<quint32>(QSSGShaderGeneratorStage::StageCount); ++stageIdx) {
484 QSSGShaderGeneratorStage stageName = static_cast<QSSGShaderGeneratorStage>(1 << stageIdx);
485 if (m_enabledStages & stageName) {
486 QSSGStageGeneratorBase &theStage(internalGetStage(inStage: stageName));
487 theStage.buildShaderSourcePass1(mergeContext: &mergeContext);
488 }
489 }
490
491 for (quint32 stageIdx = 0; stageIdx < static_cast<quint32>(QSSGShaderGeneratorStage::StageCount); ++stageIdx) {
492 QSSGShaderGeneratorStage stageName = static_cast<QSSGShaderGeneratorStage>(1 << stageIdx);
493 if (m_enabledStages & stageName) {
494 QSSGStageGeneratorBase &theStage(internalGetStage(inStage: stageName));
495 shaderLibraryManager.resolveIncludeFiles(theReadBuffer&: theStage.m_finalBuilder, inMaterialInfoString);
496 registerShaderMetaDataFromSource(mergeContext: &mergeContext, contents: theStage.m_finalBuilder, stage: stageName);
497 }
498 }
499
500 for (quint32 stageIdx = 0; stageIdx < static_cast<quint32>(QSSGShaderGeneratorStage::StageCount); ++stageIdx) {
501 QSSGShaderGeneratorStage stageName = static_cast<QSSGShaderGeneratorStage>(1 << stageIdx);
502 if (m_enabledStages & stageName) {
503 QSSGStageGeneratorBase &theStage(internalGetStage(inStage: stageName));
504 theStage.buildShaderSourcePass2(mergeContext: &mergeContext);
505 }
506 }
507
508 // qDebug("VERTEX:\n%s\n\n", m_vs.m_finalBuilder.constData());
509 // qDebug("FRAGMENT:\n%s\n\n", m_fs.m_finalBuilder.constData());
510
511 return theCache.compileForRhi(inKey: inMaterialInfoString,
512 inVert: m_vs.m_finalBuilder,
513 inFrag: m_fs.m_finalBuilder,
514 inFeatures: inFeatureSet,
515 stageFlags,
516 viewCount,
517 perTargetCompilation);
518}
519
520QSSGVertexShaderGenerator::QSSGVertexShaderGenerator()
521 : QSSGStageGeneratorBase(QSSGShaderGeneratorStage::Vertex)
522{}
523
524QSSGFragmentShaderGenerator::QSSGFragmentShaderGenerator()
525 : QSSGStageGeneratorBase(QSSGShaderGeneratorStage::Fragment)
526{}
527
528void QSSGFragmentShaderGenerator::addShaderIncomingMap()
529{
530 addShaderItemMap(itemType: ShaderItemType::Input, itemMap: m_incoming);
531 addShaderItemMap(itemType: ShaderItemType::Input, itemMap: m_flatIncoming, flags: ShaderItemMapFlag::Flat);
532 addShaderPass2Marker(itemType: ShaderItemType::Input);
533}
534
535void QSSGFragmentShaderGenerator::addShaderOutgoingMap()
536{
537 addShaderPass2Marker(itemType: ShaderItemType::Output);
538}
539
540QT_END_NAMESPACE
541

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtquick3d/src/runtimerender/qssgrendershadercodegenerator.cpp