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