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