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