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

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