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

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