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
qssgrendershadercache.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
5
6
7#include "graphobjects/qssgrendergraphobject_p.h"
10
11#include <QtQuick3DUtils/private/qssgutils_p.h>
12#include <QtQuick3DUtils/private/qquick3dprofiler_p.h>
13
14#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
15#include <qtquick3d_tracepoints_p.h>
16
17#include <QCoreApplication>
18#include <QStandardPaths>
19#include <QString>
20#include <QFile>
21#include <QDir>
22
23#include <QtGui/qsurfaceformat.h>
24#if QT_CONFIG(opengl)
25# include <QtGui/qopenglcontext.h>
26#endif
27
28#ifdef QT_QUICK3D_HAS_RUNTIME_SHADERS
29#include <rhi/qshaderbaker.h>
30#endif
31
32#include <QtCore/qmutex.h>
33
35
36Q_TRACE_POINT(qtquick3d, QSSG_loadShader_entry)
37Q_TRACE_POINT(qtquick3d, QSSG_loadShader_exit)
38
39static QtQuick3DEditorHelpers::ShaderBaker::StatusCallback s_statusCallback = nullptr;
41
42size_t qHash(QSSGShaderFeatures features) noexcept { return (features.flags & (~QSSGShaderFeatures::IndexMask)); }
43
44static QString dumpFilename(QShader::Stage stage)
45{
46 switch (stage) {
47 case QShader::VertexStage:
48 return QStringLiteral("failedvert.txt");
49 break;
50 case QShader::FragmentStage:
51 return QStringLiteral("failedfrag.txt");
52 break;
53 default:
54 return QStringLiteral("failedshader.txt");
55 }
56}
57
59{
60 const char *name = nullptr;
62};
63
64static constexpr DefineEntry DefineTable[] {
65 { "QSSG_ENABLE_LIGHT_PROBE", QSSGShaderFeatures::Feature::LightProbe },
66 { "QSSG_ENABLE_IBL_ORIENTATION", QSSGShaderFeatures::Feature::IblOrientation },
67 { "QSSG_ENABLE_SSM", QSSGShaderFeatures::Feature::Ssm },
68 { "QSSG_ENABLE_SSAO", QSSGShaderFeatures::Feature::Ssao },
69 { "QSSG_ENABLE_DEPTH_PASS", QSSGShaderFeatures::Feature::DepthPass },
70 { "QSSG_ENABLE_ORTHO_SHADOW_PASS", QSSGShaderFeatures::Feature::OrthoShadowPass },
71 { "QSSG_ENABLE_PERSPECTIVE_SHADOW_PASS", QSSGShaderFeatures::Feature::PerspectiveShadowPass },
72 { "QSSG_ENABLE_LINEAR_TONEMAPPING", QSSGShaderFeatures::Feature::LinearTonemapping },
73 { "QSSG_ENABLE_ACES_TONEMAPPING", QSSGShaderFeatures::Feature::AcesTonemapping },
74 { "QSSG_ENABLE_HEJLDAWSON_TONEMAPPING", QSSGShaderFeatures::Feature::HejlDawsonTonemapping },
75 { "QSSG_ENABLE_FILMIC_TONEMAPPING", QSSGShaderFeatures::Feature::FilmicTonemapping },
76 { "QSSG_ENABLE_RGBE_LIGHT_PROBE", QSSGShaderFeatures::Feature::RGBELightProbe },
77 { "QSSG_ENABLE_OPAQUE_DEPTH_PRE_PASS", QSSGShaderFeatures::Feature::OpaqueDepthPrePass },
78 { "QSSG_ENABLE_REFLECTION_PROBE", QSSGShaderFeatures::Feature::ReflectionProbe },
79 { "QSSG_REDUCE_MAX_NUM_LIGHTS", QSSGShaderFeatures::Feature::ReduceMaxNumLights },
80 { "QSSG_ENABLE_LIGHTMAP", QSSGShaderFeatures::Feature::Lightmap },
81 { "QSSG_DISABLE_MULTIVIEW", QSSGShaderFeatures::Feature::DisableMultiView },
82 { "QSSG_FORCE_IBL_EXPOSURE", QSSGShaderFeatures::Feature::ForceIblExposure },
83 { "QSSG_ENABLE_NORMAL_PASS", QSSGShaderFeatures::Feature::NormalPass },
84 { "QSSG_ENABLE_USER_RENDER_PASS", QSSGShaderFeatures::Feature::UserRenderPass},
85 { "QSSG_ENABLE_MOTION_VECTOR", QSSGShaderFeatures::Feature::MotionVector }
86};
87
88static_assert(std::size(DefineTable) == QSSGShaderFeatures::Count, "Missing feature define?");
89
90const char *QSSGShaderFeatures::asDefineString(QSSGShaderFeatures::Feature feature) { return DefineTable[static_cast<FlagType>(feature) & QSSGShaderFeatures::IndexMask].name; }
91QSSGShaderFeatures::Feature QSSGShaderFeatures::fromIndex(quint32 idx) { return DefineTable[idx].feature; }
92
93void QSSGShaderFeatures::set(QSSGShaderFeatures::Feature feature, bool val)
94{
95 if (val)
96 flags |= (static_cast<FlagType>(feature) & ~IndexMask);
97 else
98 flags &= ~(static_cast<FlagType>(feature) & ~IndexMask);
99}
100
101QList<QShaderVersion> QSSGShaderCache::glslTargetsForContext(const QSurfaceFormat &format, bool isGLESModule)
102{
103 QList<QShaderVersion> versions;
104
105 // QShaderVersion from QSurfaceFormat::version pair. Only valid from GLSL
106 // 330 and GLSL ES 300 up; below those the GLSL version does not follow the
107 // context version, but the callers below never get there.
108 const auto fromVersion = [](const std::pair<int, int> &version, const QShaderVersion::Flags flags = {}) -> QShaderVersion {
109 return QShaderVersion(version.first * 100 + version.second * 10, flags);
110 };
111
112 const auto version = format.version();
113
114 if (format.profile() == QSurfaceFormat::CoreProfile && version >= qMakePair(3, 3)) {
115 // Add the floor version so that silent translation errors do not leave the
116 // GLSL outputs empty
117 versions.append(QShaderVersion(330));
118 // Use actual version
119 if (version > qMakePair(3, 3))
120 versions.append(fromVersion(version));
121 } else if (format.renderableType() == QSurfaceFormat::OpenGLES || isGLESModule) {
122 if (format.majorVersion() >= 3) {
123 // Add the floor version so that silent translation errors do not leave the
124 // GLSL outputs empty
125 versions.append(QShaderVersion(300, QShaderVersion::GlslEs));
126 // Use actual version
127 if (version > qMakePair(3, 0))
128 versions.append(fromVersion(version, QShaderVersion::GlslEs));
129 } else {
130 versions.append(QShaderVersion(100, QShaderVersion::GlslEs)); // GLES 2.0
131 }
132 } else {
133 // Need to default to at least GLSL 130 (OpenGL 3.0), not 120.
134 // The difference is actually relevant when it comes to certain
135 // GLSL features (textureSize, unsigned integers, and with
136 // SPIRV-Cross even bool), and we do not have to care about
137 // pure OpenGL (non-ES) 2.x implementations in practice.
138
139 // For full feature set we need GLSL 140 (OpenGL 3.1), e.g.
140 // because of inverse() used for instancing.
141
142 // GLSL 130 should still be attempted, to support old Mesa
143 // llvmpipe that only gives us OpenGL 3.0. At the time of
144 // writing the opengl32sw.dll shipped with pre-built Qt is one
145 // of these still.
146
147 if (version >= qMakePair(3, 1))
148 versions.append(QShaderVersion(140)); // OpenGL 3.1+
149 else
150 versions.append(QShaderVersion(130)); // OpenGL 3.0+
151 }
152
153 return versions;
154}
155
156#ifdef QT_QUICK3D_HAS_RUNTIME_SHADERS
157static void initBakerForNonPersistentUse(QShaderBaker *baker, QRhi *rhi)
158{
159 QVector<QShaderBaker::GeneratedShader> outputs;
160 switch (rhi->backend()) {
161 case QRhi::D3D11:
162 outputs.append({ QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0
163 break;
164 case QRhi::D3D12:
165 outputs.append({ QShader::HlslShader, QShaderVersion(61) }); // Shader Model 6.1 (includes multiview support)
166 break;
167 case QRhi::Metal:
168#if defined(Q_OS_VISIONOS)
169 outputs.append({ QShader::MslShader, QShaderVersion(21) }); // Metal 2.1 (multiview support)
170#else
171 outputs.append({ QShader::MslShader, QShaderVersion(12) }); // Metal 1.2
172#endif // Q_OS_VISIONOS
173 break;
174 case QRhi::OpenGLES2:
175 {
176 QSurfaceFormat format = QSurfaceFormat::defaultFormat();
177 bool isGLESModule = false;
178#if QT_CONFIG(opengl)
179 auto h = static_cast<const QRhiGles2NativeHandles *>(rhi->nativeHandles());
180 if (h && h->context)
181 format = h->context->format();
182 isGLESModule = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
183#endif
184 const QList<QShaderVersion> versions = QSSGShaderCache::glslTargetsForContext(format, isGLESModule);
185 outputs.reserve(outputs.size() + versions.size());
186 for (const QShaderVersion &version : versions)
187 outputs.append({ QShader::GlslShader, version });
188 }
189 break;
190 default: // Vulkan, Null
191 outputs.append({ QShader::SpirvShader, QShaderVersion(100) });
192 break;
193 }
194
195 // Do not fail baking if one of the target fails
196 // See comment on initBakerForPersistentUse
197 baker->setBreakOnShaderTranslationError(false);
198 baker->setGeneratedShaders(outputs);
199 baker->setGeneratedShaderVariants({ QShader::StandardShader });
200}
201
202void QSSGShaderCache::initBakerForPersistentUse(QShaderBaker *baker, QRhi *)
203{
204 QVector<QShaderBaker::GeneratedShader> outputs;
205 outputs.reserve(13);
206
207#ifndef Q_OS_WASM
208 outputs.append({ QShader::SpirvShader, QShaderVersion(100) });
209 outputs.append({ QShader::HlslShader, QShaderVersion(50) }); // Shader Model 5.0
210 outputs.append({ QShader::HlslShader, QShaderVersion(61) }); // Shader Model 6.1 (for multiview on d3d12)
211#if defined(Q_OS_VISIONOS)
212 outputs.append({ QShader::MslShader, QShaderVersion(21) }); // Metal 2.1 (multiview support)
213#else
214 outputs.append({ QShader::MslShader, QShaderVersion(12) }); // Metal 1.2
215#endif // Q_OS_VISIONOS
216
217 outputs.append({ QShader::GlslShader, QShaderVersion(430) }); // OpenGL 4.3+ (compute shaders, shader storage buffers)
218 outputs.append({ QShader::GlslShader, QShaderVersion(420) }); // OpenGL 4.2+
219 outputs.append({ QShader::GlslShader, QShaderVersion(330) }); // OpenGL 3.3+
220 outputs.append({ QShader::GlslShader, QShaderVersion(140) }); // OpenGL 3.1+
221 outputs.append({ QShader::GlslShader, QShaderVersion(130) }); // OpenGL 3.0+
222 outputs.append({ QShader::GlslShader, QShaderVersion(100, QShaderVersion::GlslEs) }); // GLES 2.0
223#endif
224 outputs.append({ QShader::GlslShader, QShaderVersion(300, QShaderVersion::GlslEs) }); // GLES 3.0+
225 outputs.append({ QShader::GlslShader, QShaderVersion(310, QShaderVersion::GlslEs) }); // GLES 3.1+
226 outputs.append({ QShader::GlslShader, QShaderVersion(320, QShaderVersion::GlslEs) }); // GLES 3.2+ (image atomics)
227
228 // If one of the above cannot be generated due to failing at the
229 // SPIRV-Cross translation stage, it will be skipped, but bake() will not
230 // fail. This is essential, because with the default fail if anything fails
231 // behavior many shaders could not be baked at all due to failing for e.g.
232 // GLSL ES 100. This is a non-issue when choosing the targets dynamically
233 // based on the current API/context, but here we need to ensure what we
234 // generate will work with a different RHI backend, graphics API, and
235 // perhaps even on a different platform (if the cache file is manually
236 // moved). So have to generate what we can, without breaking the
237 // application when the shader is not compatible with a target. (if that
238 // shader is not used at runtime, it's fine anyway, it it is, it won't work
239 // just as with the other, non-caching path)
240 baker->setBreakOnShaderTranslationError(false);
241
242 baker->setGeneratedShaders(outputs);
243 baker->setGeneratedShaderVariants({ QShader::StandardShader });
244}
245
246#else
247static void initBakerForNonPersistentUse(QShaderBaker *, QRhi *)
248{
249}
250
251void QSSGShaderCache::initBakerForPersistentUse(QShaderBaker *, QRhi *)
252{
253}
254#endif // QT_QUICK3D_HAS_RUNTIME_SHADERS
255
256static bool s_autoDiskCacheEnabled = true;
257
259{
260 // these three mirror QOpenGLShaderProgram/QQuickGraphicsConfiguration/QSGRhiSupport
261 static const bool diskCacheDisabled = qEnvironmentVariableIntValue("QT_DISABLE_SHADER_DISK_CACHE")
262 || qEnvironmentVariableIntValue("QSG_RHI_DISABLE_DISK_CACHE");
263 const bool attrDiskCacheDisabled = (qApp ? qApp->testAttribute(Qt::AA_DisableShaderDiskCache) : false);
264 return (!diskCacheDisabled && !attrDiskCacheDisabled && s_autoDiskCacheEnabled);
265
266}
267
268static inline bool ensureWritableDir(const QString &name)
269{
270 QDir::root().mkpath(name);
271 return QFileInfo(name).isWritable();
272}
273
275{
276 static bool checked = false;
277 static QString currentCacheDir;
278 static bool cacheWritable = false;
279
280 if (checked)
281 return cacheWritable ? currentCacheDir : QString();
282
283 checked = true;
284 const QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
285 const QString subPath = QLatin1String("/q3dshadercache-") + QSysInfo::buildAbi() + QLatin1Char('/');
286
287 if (!cachePath.isEmpty()) {
288 currentCacheDir = cachePath + subPath;
289 cacheWritable = ensureWritableDir(currentCacheDir);
290 }
291
292 return cacheWritable ? currentCacheDir : QString();
293}
294
296{
297 const QString cacheDir = persistentQsbcDir();
298 if (!cacheDir.isEmpty())
299 return cacheDir + QLatin1String("q3dshadercache.qsbc");
300
301 return QString();
302}
303
304QSSGShaderCache::QSSGShaderCache(QSSGRhiContext &ctx,
305 const InitBakerFunc initBakeFn)
306 : m_rhiContext(ctx),
307 m_initBaker(initBakeFn),
308 m_builtInShaders(*this)
309{
310 if (isAutoDiskCacheEnabled()) {
311 const bool shaderDebug = !QSSGRhiContextPrivate::editorMode() && QSSGRhiContextPrivate::shaderDebuggingEnabled();
312 m_persistentShaderStorageFileName = persistentQsbcFileName();
313 if (!m_persistentShaderStorageFileName.isEmpty()) {
314 const bool skipCacheFile = qEnvironmentVariableIntValue("QT_QUICK3D_NO_SHADER_CACHE_LOAD");
315 if (!skipCacheFile && QFileInfo::exists(m_persistentShaderStorageFileName)) {
316 if (shaderDebug)
317 qDebug("Attempting to seed material shader cache from %s", qPrintable(m_persistentShaderStorageFileName));
318 if (m_persistentShaderBakingCache.load(m_persistentShaderStorageFileName)) {
319 if (shaderDebug) {
320 const int count = m_persistentShaderBakingCache.availableEntries().count();
321 qDebug("Loaded %d shader pipelines into the material shader cache", count);
322 }
323 }
324 }
325 }
326 }
327
328 if (!m_initBaker) {
329 // It is important to generate all possible shader variants if the qsb
330 // collection is going to be stored on disk. Otherwise switching the
331 // rhi backend could break the application. This is however an overkill
332 // if we know that what we bake will not be reused in future runs of
333 // the application, so do not do it if the disk cache was disabled or
334 // the cache directory was not available (no file system, no
335 // permissions, etc.).
336 m_initBaker = m_persistentShaderStorageFileName.isEmpty() ? initBakerForNonPersistentUse
337 : QSSGShaderCache::initBakerForPersistentUse;
338 }
339}
340
341QSSGShaderCache::~QSSGShaderCache()
342{
343 if (!m_persistentShaderStorageFileName.isEmpty())
344 m_persistentShaderBakingCache.save(m_persistentShaderStorageFileName);
345}
346
347void QSSGShaderCache::releaseCachedResources()
348{
349 m_builtInShaders.releaseCachedResources();
350
351 m_rhiShaders.clear();
352
353 // m_persistentShaderBakingCache is not cleared, that is intentional,
354 // otherwise we would permanently lose what got loaded at startup.
355}
356
357QSSGRhiShaderPipelinePtr QSSGShaderCache::tryGetRhiShaderPipeline(const QByteArray &inKey,
358 const QSSGShaderFeatures &inFeatures)
359{
360 QSSGShaderCacheKey cacheKey(inKey);
361 cacheKey.m_features = inFeatures;
362 cacheKey.updateHashCode();
363 const auto theIter = m_rhiShaders.constFind(cacheKey);
364 if (theIter != m_rhiShaders.cend())
365 return theIter.value();
366 return nullptr;
367}
368
369
370void QSSGShaderCache::addShaderPreprocessor(QByteArray &str,
371 const QByteArray &inKey,
372 ShaderType shaderType,
373 const QSSGShaderFeatures &inFeatures,
374 const QSSGUserShaderAugmentation &shaderAugmentation,
375 int viewCount)
376{
377 m_insertStr.clear();
378
379 m_insertStr += "#version 440\n";
380
381 if (!inKey.isNull()) {
382 m_insertStr += "//Shader name -";
383 m_insertStr += inKey;
384 m_insertStr += "\n";
385 }
386
387 m_insertStr += "#define texture2D texture\n";
388
389 // match Qt Quick and QSGMaterial(Shader)
390 m_insertStr += "#define QSHADER_VIEW_COUNT ";
391 m_insertStr += QByteArray::number(viewCount);
392 m_insertStr += "\n";
393
394 str.insert(0, m_insertStr);
395 QString::size_type insertPos = int(m_insertStr.size());
396
397 m_insertStr.clear();
398 const bool fragOutputEnabled = (!inFeatures.isSet(QSSGShaderFeatures::Feature::DepthPass)) && shaderType == ShaderType::Fragment;
399 for (const auto &def : DefineTable) {
400 m_insertStr.append("#define ");
401 m_insertStr.append(def.name);
402 m_insertStr.append(" ");
403 m_insertStr.append(inFeatures.isSet(def.feature) ? "1" : "0");
404 m_insertStr.append("\n");
405 }
406
407 str.insert(insertPos, m_insertStr);
408 insertPos += int(m_insertStr.size());
409
410 m_insertStr.clear();
411 if (fragOutputEnabled) {
412 if (shaderAugmentation.outputs.size() != 0) {
413 const auto &outputs = shaderAugmentation.outputs;
414 for (int i = 0; i < outputs.size(); ++i) {
415 m_insertStr += "layout(location = ";
416 m_insertStr += QByteArray::number(i);
417 m_insertStr += ") out vec4 ";
418 m_insertStr += outputs[i];
419 m_insertStr += ";\n";
420 }
421 } else {
422 m_insertStr += "layout(location = 0) out vec4 fragOutput;\n";
423 }
424 }
425
426 str.insert(insertPos, m_insertStr);
427}
428
429QByteArray QSSGShaderCache::resourceFolder()
430{
431 return QByteArrayLiteral(":/res/rhishaders/");
432}
433
434QByteArray QSSGShaderCache::shaderCollectionFile()
435{
436 return QByteArrayLiteral("qtappshaders.qsbc");
437}
438
439QByteArray QSSGShaderCache::particleShaderCollectionFile()
440{
441 return QByteArrayLiteral("qtparticleshaders.qsbc");
442}
443
444QSSGRhiShaderPipelinePtr QSSGShaderCache::compileForRhi(const QByteArray &inKey, const QByteArray &inVert, const QByteArray &inFrag,
445 const QSSGShaderFeatures &inFeatures, QSSGRhiShaderPipeline::StageFlags stageFlags,
446 const QSSGUserShaderAugmentation &shaderAugmentation,
447 int viewCount,
448 bool perTargetCompilation,
449 const QByteArray &inCacheKey)
450{
451#ifdef QT_QUICK3D_HAS_RUNTIME_SHADERS
452 // inKey is also used as the shader name in a comment in the generated
453 // source, so it may not contain everything needed to identify the
454 // shaders. When inCacheKey is set, that is what the caches are keyed on.
455 const QByteArray &cacheKeyString = inCacheKey.isEmpty() ? inKey : inCacheKey;
456
457 const QSSGRhiShaderPipelinePtr &rhiShaders = tryGetRhiShaderPipeline(cacheKeyString, inFeatures);
458 if (rhiShaders)
459 return rhiShaders;
460
461 QSSGShaderCacheKey tempKey(cacheKeyString);
462 tempKey.m_features = inFeatures;
463 tempKey.updateHashCode();
464
465 QByteArray vertexCode = inVert;
466 QByteArray fragmentCode = inFrag;
467
468 if (!vertexCode.isEmpty())
469 addShaderPreprocessor(vertexCode, inKey, ShaderType::Vertex, inFeatures, shaderAugmentation, viewCount);
470
471 if (!fragmentCode.isEmpty())
472 addShaderPreprocessor(fragmentCode, inKey, ShaderType::Fragment, inFeatures, shaderAugmentation, viewCount);
473
474 // lo and behold the final shader strings are ready
475
476 QSSGRhiShaderPipelinePtr shaders;
477 QString vertErr, fragErr;
478
479 QShaderBaker baker;
480 m_initBaker(&baker, m_rhiContext.rhi());
481
482 // If requested, per-target compilation allows doing things like #if
483 // QSHADER_HLSL in the shader code, at the expense of spending more time in
484 // bake())
485 baker.setPerTargetCompilation(perTargetCompilation);
486
487 // This is in the shader key, but cannot query that here anymore now that it's serialized.
488 // So we get it as a dedicated argument.
489 baker.setMultiViewCount(viewCount);
490
491 // For fragment shaders for GLSL ES (but only ES) we can make the generated
492 // sources contain 'precision mediump float' instead of 'precision highp float'.
493 const bool mediumPrecision = qEnvironmentVariableIntValue("QT_QUICK3D_MEDIUM_PRECISION");
494 if (mediumPrecision)
495 baker.setGlslOptions(QShaderBaker::GlslOption::GlslEsFragDefaultFloatPrecisionMedium);
496
497 const bool editorMode = QSSGRhiContextPrivate::editorMode();
498 // Shader debug is disabled in editor mode
499 const bool shaderDebug = !editorMode && QSSGRhiContextPrivate::shaderDebuggingEnabled();
500
501 static auto dumpShader = [](QShader::Stage stage, const QByteArray &code) {
502 switch (stage) {
503 case QShader::Stage::VertexStage:
504 qDebug("VERTEX SHADER:\n*****\n");
505 break;
506 case QShader::Stage::FragmentStage:
507 qDebug("FRAGMENT SHADER:\n*****\n");
508 break;
509 default:
510 qDebug("SHADER:\n*****\n");
511 break;
512 }
513 const auto lines = code.split('\n');
514 for (int i = 0; i < lines.size(); i++)
515 qDebug("%3d %s", i + 1, lines.at(i).constData());
516 qDebug("\n*****\n");
517 };
518
519 static auto dumpShaderToFile = [](QShader::Stage stage, const QByteArray &data) {
520 QFile f(dumpFilename(stage));
521 if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
522 f.write(data);
523 } else {
524 qWarning("Failed to write to file: %s (%s)\n",
525 qPrintable(f.fileName()), qPrintable(f.errorString()));
526 }
527 };
528
529 baker.setSourceString(vertexCode, QShader::VertexStage);
530 QShader vertexShader = baker.bake();
531 const auto vertShaderValid = vertexShader.isValid();
532 if (!vertShaderValid) {
533 vertErr = baker.errorMessage();
534 if (!editorMode) {
535 qWarning("Failed to compile vertex shader: %s\n", qPrintable(vertErr));
536 if (!shaderDebug)
537 qWarning() << inKey << '\n';
538 }
539 }
540
541 if (shaderDebug) {
542 dumpShader(QShader::Stage::VertexStage, vertexCode);
543 if (!vertShaderValid)
544 dumpShaderToFile(QShader::Stage::VertexStage, vertexCode);
545 }
546
547 baker.setSourceString(fragmentCode, QShader::FragmentStage);
548 QShader fragmentShader = baker.bake();
549 const bool fragShaderValid = fragmentShader.isValid();
550 if (!fragShaderValid) {
551 fragErr = baker.errorMessage();
552 if (!editorMode) {
553 qWarning("Failed to compile fragment shader: %s\n", qPrintable(fragErr));
554 if (!shaderDebug)
555 qWarning() << inKey << '\n';
556 }
557 }
558
559 if (shaderDebug) {
560 dumpShader(QShader::Stage::FragmentStage, fragmentCode);
561 if (!fragShaderValid)
562 dumpShaderToFile(QShader::Stage::FragmentStage, fragmentCode);
563 }
564
565 if (vertShaderValid && fragShaderValid) {
566 shaders = std::make_shared<QSSGRhiShaderPipeline>(m_rhiContext);
567 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Vertex, vertexShader), stageFlags);
568 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Fragment, fragmentShader), stageFlags);
569 if (shaderDebug)
570 qDebug("Compilation for vertex and fragment stages succeeded");
571 }
572
573 if (editorMode && s_statusCallback) {
574 using namespace QtQuick3DEditorHelpers::ShaderBaker;
575 const auto vertStatus = vertShaderValid ? Status::Success : Status::Error;
576 const auto fragStatus = fragShaderValid ? Status::Success : Status::Error;
577 QMutexLocker locker(&*s_statusMutex);
578 s_statusCallback(inKey, vertStatus, vertErr, QShader::VertexStage);
579 s_statusCallback(inKey, fragStatus, fragErr, QShader::FragmentStage);
580 }
581
582 auto result = m_rhiShaders.insert(tempKey, shaders).value();
583 if (result && result->vertexStage() && result->fragmentStage()) {
584 QQsbCollection::EntryDesc entryDesc = {
585 cacheKeyString,
586 QQsbCollection::toFeatureSet(inFeatures),
587 result->vertexStage()->shader(),
588 result->fragmentStage()->shader()
589 };
590 m_persistentShaderBakingCache.addEntry(entryDesc.generateSha(), entryDesc);
591 }
592 return result;
593
594#else
595 Q_UNUSED(inKey);
596 Q_UNUSED(inVert);
597 Q_UNUSED(inFrag);
598 Q_UNUSED(inFeatures);
599 Q_UNUSED(stageFlags);
600 Q_UNUSED(inCacheKey);
601 qWarning("Cannot compile and condition shaders at runtime because this build of Qt Quick 3D is not linking to Qt Shader Tools. "
602 "Only pre-processed materials are supported.");
603 return {};
604#endif
605}
606
607QSSGRhiShaderPipelinePtr QSSGShaderCache::newPipelineFromPregenerated(const QByteArray &inKey,
608 const QSSGShaderFeatures &inFeatures,
609 QQsbCollection::Entry entry,
610 const QSSGRenderGraphObject &obj,
611 QSSGRhiShaderPipeline::StageFlags stageFlags)
612{
613 // No lookup in m_rhiShaders. It is up to the caller to do that, if they
614 // want to. We will insert into it at the end, but there is intentionally
615 // no lookup. The result from this function is always a new
616 // QSSGRhiShaderPipeline (it's just much faster to create than the
617 // full-blown generator). That is important for some clients (effect
618 // system) so returning an existing QSSGRhiShaderPipeline is _wrong_.
619
620 const bool shaderDebug = !QSSGRhiContextPrivate::editorMode() && QSSGRhiContextPrivate::shaderDebuggingEnabled();
621 if (shaderDebug)
622 qDebug("Loading pregenerated rhi shader(s)");
623
624 Q_TRACE_SCOPE(QSSG_loadShader);
625 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DLoadShader);
626
627 // Note that we are required to return a non-null (but empty) shader set even if loading fails.
628 QSSGRhiShaderPipelinePtr shaders(new QSSGRhiShaderPipeline(m_rhiContext));
629
630 const QStringList collectionFiles
631 = {QString::fromLatin1(resourceFolder() + shaderCollectionFile()),
632 QString::fromLatin1(resourceFolder() + particleShaderCollectionFile())};
633
634 QQsbCollection::EntryDesc entryDesc;
635 QScopedPointer<QQsbIODeviceCollection> qsbc;
636 for (const auto &collectionFile : collectionFiles) {
637 QFileInfo info(collectionFile);
638 if (!info.exists())
639 continue;
640
641 qsbc.reset(new QQsbIODeviceCollection(collectionFile));
642 if (qsbc->map(QQsbIODeviceCollection::Read)) {
643 if (qsbc->extractEntry(entry, entryDesc))
644 break;
645 else
646 qsbc.reset();
647 }
648 }
649
650 if (qsbc.isNull())
651 qWarning("Failed to open entry %s", entry.key.constData());
652
653 if (entryDesc.vertShader.isValid() && entryDesc.fragShader.isValid()) {
654 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Vertex, entryDesc.vertShader), stageFlags);
655 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Fragment, entryDesc.fragShader), stageFlags);
656 if (shaderDebug)
657 qDebug("Loading of vertex and fragment stages succeeded");
658 }
659
660#if !QT_CONFIG(qml_debug)
661 Q_UNUSED(obj);
662#else
663 Q_QUICK3D_PROFILE_END_WITH_ID(QQuick3DProfiler::Quick3DLoadShader, 0, obj.profilingId);
664#endif
665
666 QSSGShaderCacheKey cacheKey(inKey);
667 cacheKey.m_features = inFeatures;
668 cacheKey.updateHashCode();
669
670 const auto inserted = m_rhiShaders.insert(cacheKey, shaders);
671 if (qsbc)
672 qsbc->unmap();
673 return inserted.value();
674}
675
676QSSGRhiShaderPipelinePtr QSSGShaderCache::tryNewPipelineFromPersistentCache(const QByteArray &qsbcKey,
677 const QByteArray &inKey,
678 const QSSGShaderFeatures &inFeatures,
679 QSSGRhiShaderPipeline::StageFlags stageFlags)
680{
681 // No lookup in m_rhiShaders. it is up to the caller to do that, if they
682 // want to. We will insert into it at the end, but there is intentionally
683 // no lookup. The result from this function is always a new
684 // QSSGRhiShaderPipeline (it's just much faster to create than the
685 // full-blown generator). That is important for some clients (effect
686 // system) so returning an existing QSSGRhiShaderPipeline is _wrong_.
687
688 QQsbCollection::EntryDesc entryDesc;
689
690 // Here we are allowed to return null to indicate that there is no such
691 // entry in this particular cache.
692 if (!m_persistentShaderBakingCache.extractEntry(QQsbCollection::Entry(qsbcKey), entryDesc))
693 return {};
694
695 if (entryDesc.vertShader.isValid() && entryDesc.fragShader.isValid()) {
696 const bool shaderDebug = !QSSGRhiContextPrivate::editorMode() && QSSGRhiContextPrivate::shaderDebuggingEnabled();
697 if (shaderDebug)
698 qDebug("Loading rhi shaders from disk cache for %s (%s)", qsbcKey.constData(), inKey.constData());
699
700 QSSGRhiShaderPipelinePtr shaders(new QSSGRhiShaderPipeline(m_rhiContext));
701 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Vertex, entryDesc.vertShader), stageFlags);
702 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Fragment, entryDesc.fragShader), stageFlags);
703 QSSGShaderCacheKey cacheKey(inKey);
704 cacheKey.m_features = inFeatures;
705 cacheKey.updateHashCode();
706 return m_rhiShaders.insert(cacheKey, shaders).value();
707 }
708
709 return {};
710}
711
712QSSGRhiShaderPipelinePtr QSSGShaderCache::loadBuiltinUncached(const QByteArray &inKey, int viewCount,
713 QSSGRhiShaderPipeline::StageFlags vertexStageFlags)
714{
715 const bool shaderDebug = !QSSGRhiContextPrivate::editorMode() && QSSGRhiContextPrivate::shaderDebuggingEnabled();
716 if (shaderDebug)
717 qDebug("Loading builtin rhi shader: %s (view count: %d)", inKey.constData(), viewCount);
718
719 Q_TRACE_SCOPE(QSSG_loadShader);
720 Q_QUICK3D_PROFILE_START(QQuick3DProfiler::Quick3DLoadShader);
721
722 // Note that we are required to return a non-null (but empty) shader set even if loading fails.
723 QSSGRhiShaderPipelinePtr shaders(new QSSGRhiShaderPipeline(m_rhiContext));
724
725 // inShaderName is a prefix of a .qsb file, so "abc" means we should
726 // look for abc.vert.qsb and abc.frag.qsb.
727
728 const QString prefix = QString::fromUtf8(resourceFolder() + inKey);
729 QString vertexFileName = prefix + QLatin1String(".vert.qsb");
730 QString fragmentFileName = prefix + QLatin1String(".frag.qsb");
731
732 // This must match QSGMaterial(Shader) in Qt Quick, in particular the
733 // QSGMaterialShader::setShaderFileName() overload taking a viewCount.
734 if (viewCount == 2) {
735 vertexFileName += QLatin1String(".mv2qsb");
736 fragmentFileName += QLatin1String(".mv2qsb");
737 }
738
739 QShader vertexShader;
740 QShader fragmentShader;
741
742 QFile f;
743 f.setFileName(vertexFileName);
744 if (f.open(QIODevice::ReadOnly)) {
745 const QByteArray vsData = f.readAll();
746 vertexShader = QShader::fromSerialized(vsData);
747 f.close();
748 } else {
749 qWarning("Failed to open %s", qPrintable(f.fileName()));
750 }
751 f.setFileName(fragmentFileName);
752 if (f.open(QIODevice::ReadOnly)) {
753 const QByteArray fsData = f.readAll();
754 fragmentShader = QShader::fromSerialized(fsData);
755 f.close();
756 } else {
757 qWarning("Failed to open %s", qPrintable(f.fileName()));
758 }
759
760 if (vertexShader.isValid() && fragmentShader.isValid()) {
761 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Vertex, vertexShader), vertexStageFlags);
762 shaders->addStage(QRhiShaderStage(QRhiShaderStage::Fragment, fragmentShader));
763 if (shaderDebug)
764 qDebug("Loading of vertex and fragment stages succeeded");
765 }
766
767 Q_QUICK3D_PROFILE_END_WITH_STRING(QQuick3DProfiler::Quick3DLoadShader, 0, inKey);
768
769 return shaders;
770}
771
774{
775 QMutexLocker locker(&*s_statusMutex);
776 s_statusCallback = cb;
777}
778
780{
781 s_autoDiskCacheEnabled = enable;
782}
783
788
789}
790
791QT_END_NAMESPACE
Combined button and popup list for selecting options.
Q_QUICK3DRUNTIMERENDER_EXPORT void setStatusCallback(StatusCallback cb)
void(*)(const QByteArray &descKey, Status status, const QString &err, QShader::Stage stage) StatusCallback
Q_QUICK3DRUNTIMERENDER_EXPORT void setAutomaticDiskCache(bool enable)
Q_QUICK3DRUNTIMERENDER_EXPORT bool isAutomaticDiskCacheEnabled()
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)
static QString persistentQsbcFileName()
static bool isAutoDiskCacheEnabled()
size_t qHash(QSSGShaderFeatures features) noexcept
static bool ensureWritableDir(const QString &name)
static bool s_autoDiskCacheEnabled
static QString dumpFilename(QShader::Stage stage)
static QString persistentQsbcDir()
static void initBakerForNonPersistentUse(QShaderBaker *, QRhi *)
static constexpr DefineEntry DefineTable[]