1 /****************************************************************************
2 **
3 ** Copyright (C) 2008-2012 NVIDIA Corporation.
4 ** Copyright (C) 2019 The Qt Company Ltd.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of Qt Quick 3D.
8 **
9 ** $QT_BEGIN_LICENSE:GPL$
10 ** Commercial License Usage
11 ** Licensees holding valid commercial Qt licenses may use this file in
12 ** accordance with the commercial license agreement provided with the
13 ** Software or, alternatively, in accordance with the terms contained in
14 ** a written agreement between you and The Qt Company. For licensing terms
15 ** and conditions see https://www.qt.io/terms-conditions. For further
16 ** information use the contact form at https://www.qt.io/contact-us.
17 **
18 ** GNU General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 or (at your option) any later version
21 ** approved by the KDE Free Qt Foundation. The licenses are as published by
22 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
23 ** included in the packaging of this file. Please review the following
24 ** information to ensure the GNU General Public License requirements will
25 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
26 **
27 ** $QT_END_LICENSE$
28 **
29 ****************************************************************************/
30
31 #include "qssgrendershadercache_p.h"
32
33 #include <QtQuick3DUtils/private/qssgutils_p.h>
34
35 #include <QtQuick3DRender/private/qssgrendercontext_p.h>
36 #include <QtQuick3DRender/private/qssgrendershaderprogram_p.h>
37
38 #include <QtQuick3DRuntimeRender/private/qssgrenderinputstreamfactory_p.h>
39 #include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
40 #include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
41
42 #include <QtCore/QRegularExpression>
43 #include <QtCore/QString>
44
45 QT_BEGIN_NAMESPACE
46
47 namespace {
48 // using QSSGRenderContextScopedProperty;
49 // const char *TessellationEnabledStr = "TessellationStageEnabled";
50 // const char *GeometryEnabledStr = "GeometryStageEnabled";
51 // inline void AppendFlagValue(QString &inStr, const char *flag)
52 //{
53 // if (inStr.length())
54 // inStr.append(QStringLiteral(","));
55 // inStr.append(QString::fromLocal8Bit(flag));
56 //}
57 // inline void CacheFlagsToStr(const QSSGShaderCacheProgramFlags &inFlags, QString &inString)
58 //{
59 // inString.clear();
60 // if (inFlags.isTessellationEnabled())
61 // AppendFlagValue(inString, TessellationEnabledStr);
62 // if (inFlags.isGeometryShaderEnabled())
63 // AppendFlagValue(inString, GeometryEnabledStr);
64 //}
65
66 // inline ShaderType StringToShaderType(QString &inShaderType)
67 //{
68 // ShaderType retval = ShaderType::Vertex;
69
70 // if (inShaderType.size() == 0)
71 // return retval;
72
73 // if (!inShaderType.compare("VertexCode"))
74 // retval = ShaderType::Vertex;
75 // else if (!inShaderType.compare("FragmentCode"))
76 // retval = ShaderType::Fragment;
77 // else if (!inShaderType.compare("TessControlCode"))
78 // retval = ShaderType::TessControl;
79 // else if (!inShaderType.compare("TessEvalCode"))
80 // retval = ShaderType::TessEval;
81 // else if (!inShaderType.compare("GeometryCode"))
82 // retval = ShaderType::Geometry;
83 // else
84 // Q_ASSERT(false);
85
86 // return retval;
87 //}
88
89 // inline QSSGShaderCacheProgramFlags CacheFlagsToStr(const QString &inString)
90 //{
91 // QSSGShaderCacheProgramFlags retval;
92 // if (inString.contains(QString::fromLocal8Bit(TessellationEnabledStr)))
93 // retval.setTessellationEnabled(true);
94 // if (inString.contains(QString::fromLocal8Bit(GeometryEnabledStr)))
95 // retval.setGeometryShaderEnabled(true);
96 // return retval;
97 //}
98
99 // typedef QPair<const char *, QSSGRenderContextValues> TStringToContextValuePair;
100
101 /*GLES2 = 1 << 0,
102 GL2 = 1 << 1,
103 GLES3 = 1 << 2,
104 GL3 = 1 << 3,
105 GL4 = 1 << 4,
106 NullContext = 1 << 5,*/
107 // TStringToContextValuePair g_StringToContextTypeValue[] = {
108 // TStringToContextValuePair("GLES2", QSSGRenderContextValues::GLES2),
109 // TStringToContextValuePair("GL2", QSSGRenderContextValues::GL2),
110 // TStringToContextValuePair("GLES3", QSSGRenderContextValues::GLES3),
111 // TStringToContextValuePair("GLES3PLUS", QSSGRenderContextValues::GLES3PLUS),
112 // TStringToContextValuePair("GL3", QSSGRenderContextValues::GL3),
113 // TStringToContextValuePair("GL4", QSSGRenderContextValues::GL4),
114 // TStringToContextValuePair("NullContext", QSSGRenderContextValues::NullContext),
115 //};
116
117 // size_t g_NumStringToContextValueEntries =
118 // sizeof(g_StringToContextTypeValue) / sizeof(*g_StringToContextTypeValue);
119
120 // inline void ContextTypeToString(QSSGRenderContextType inType,
121 // QString &outContextType)
122 //{
123 // outContextType.clear();
124 // for (size_t idx = 0, end = g_NumStringToContextValueEntries; idx < end; ++idx) {
125 // if (inType & g_StringToContextTypeValue[idx].second) {
126 // if (outContextType.size())
127 // outContextType.append("|");
128 // outContextType.append(QString::fromLocal8Bit(g_StringToContextTypeValue[idx].first));
129 // }
130 // }
131 //}
132
133 // inline QSSGRenderContextType StringToContextType(const QString &inContextType)
134 //{
135 // QSSGRenderContextType retval;
136 // char tempBuffer[128];
137 // memZero(tempBuffer, 128);
138 // const QString::size_type lastTempBufIdx = 127;
139 // QString::size_type pos = 0, lastpos = 0;
140 // if (inContextType.size() == 0)
141 // return retval;
142
143 // do {
144 // pos = int(inContextType.indexOf('|', lastpos));
145 // if (pos == -1)
146 // pos = int(inContextType.size());
147 // {
148 // size_t sectionLen = size_t(qMin(pos - lastpos, lastTempBufIdx));
149 // ::memcpy(tempBuffer, inContextType.data() + lastpos, sectionLen);
150 // tempBuffer[lastTempBufIdx] = 0;
151 // for (size_t idx = 0, end = g_NumStringToContextValueEntries; idx < end; ++idx) {
152 // if (strcmp(g_StringToContextTypeValue[idx].first, tempBuffer) == 0)
153 // retval = retval | g_StringToContextTypeValue[idx].second;
154 // }
155 // }
156 // // iterate past the bar
157 // ++pos;
158 // lastpos = pos;
159 // } while (pos < inContextType.size() && pos != -1);
160
161 // return retval;
162 //}
163 }
164
defaultShaderPrecision(const QByteArray & defPrecision)165 static QByteArray defaultShaderPrecision(const QByteArray &defPrecision)
166 {
167 static const QByteArray precision = qEnvironmentVariable("QT_QUICK3D_SHADER_PRECISION").toLatin1();
168 if (precision.isEmpty() || (precision != QByteArrayLiteral("mediump")
169 && precision != QByteArrayLiteral("lowp")
170 && precision != QByteArrayLiteral("highp"))) {
171 return defPrecision;
172 }
173 return precision;
174 }
175
defaultSamplerPrecision(const QByteArray & defPrecision)176 static QByteArray defaultSamplerPrecision(const QByteArray &defPrecision)
177 {
178 static const QByteArray samplerPrecision = qEnvironmentVariable("QT_QUICK3D_SAMPLER_PRECISION").toLatin1();
179 if (samplerPrecision.isEmpty() || (samplerPrecision != QByteArrayLiteral("mediump")
180 && samplerPrecision != QByteArrayLiteral("lowp")
181 && samplerPrecision != QByteArrayLiteral("highp"))) {
182 return defPrecision;
183 }
184 return samplerPrecision;
185 }
186
187 static const char *defineTable[QSSGShaderDefines::Count] {
188 "QSSG_ENABLE_LIGHT_PROBE",
189 "QSSG_ENABLE_LIGHT_PROBE_2",
190 "QSSG_ENABLE_IBL_FOV",
191 "QSSG_ENABLE_SSM",
192 "QSSG_ENABLE_SSAO",
193 "QSSG_ENABLE_SSDO",
194 "QSSG_ENABLE_CG_LIGHTING"
195 };
196
asString(QSSGShaderDefines::Define def)197 const char *QSSGShaderDefines::asString(QSSGShaderDefines::Define def) { return defineTable[def]; }
198
qHash(const QSSGShaderCacheKey & key)199 uint qHash(const QSSGShaderCacheKey &key)
200 {
201 return key.m_hashCode;
202 }
203
hashShaderFeatureSet(const ShaderFeatureSetList & inFeatureSet)204 uint hashShaderFeatureSet(const ShaderFeatureSetList &inFeatureSet)
205 {
206 uint retval(0);
207 for (int idx = 0, end = inFeatureSet.size(); idx < end; ++idx) {
208 // From previous implementation, it seems we need to ignore the order of the features.
209 // But we need to bind the feature flag together with its name, so that the flags will
210 // influence
211 // the final hash not only by the true-value count.
212 retval ^= (inFeatureSet.at(idx).key ^ uint(inFeatureSet.at(idx).enabled));
213 }
214 return retval;
215 }
216
~QSSGShaderCache()217 QSSGShaderCache::~QSSGShaderCache() {}
218
createShaderCache(const QSSGRef<QSSGRenderContext> & inContext,const QSSGRef<QSSGInputStreamFactory> & inInputStreamFactory,QSSGPerfTimer * inPerfTimer)219 QSSGRef<QSSGShaderCache> QSSGShaderCache::createShaderCache(const QSSGRef<QSSGRenderContext> &inContext,
220 const QSSGRef<QSSGInputStreamFactory> &inInputStreamFactory,
221 QSSGPerfTimer *inPerfTimer)
222 {
223 return QSSGRef<QSSGShaderCache>(new QSSGShaderCache(inContext, inInputStreamFactory, inPerfTimer));
224 }
225
QSSGShaderCache(const QSSGRef<QSSGRenderContext> & ctx,const QSSGRef<QSSGInputStreamFactory> & inInputStreamFactory,QSSGPerfTimer *)226 QSSGShaderCache::QSSGShaderCache(const QSSGRef<QSSGRenderContext> &ctx, const QSSGRef<QSSGInputStreamFactory> &inInputStreamFactory, QSSGPerfTimer *)
227 : m_renderContext(ctx), /*m_perfTimer(inPerfTimer),*/ m_inputStreamFactory(inInputStreamFactory), m_shaderCompilationEnabled(true)
228 {
229 }
230
getProgram(const QByteArray & inKey,const ShaderFeatureSetList & inFeatures)231 QSSGRef<QSSGRenderShaderProgram> QSSGShaderCache::getProgram(const QByteArray &inKey, const ShaderFeatureSetList &inFeatures)
232 {
233 m_tempKey.m_key = inKey;
234 m_tempKey.m_features = inFeatures;
235 m_tempKey.generateHashCode();
236 const auto theIter = m_shaders.constFind(m_tempKey);
237 if (theIter != m_shaders.cend())
238 return theIter.value();
239 return nullptr;
240 }
241
addBackwardCompatibilityDefines(ShaderType shaderType)242 void QSSGShaderCache::addBackwardCompatibilityDefines(ShaderType shaderType)
243 {
244 if (shaderType == ShaderType::Vertex || shaderType == ShaderType::TessControl
245 || shaderType == ShaderType::TessEval || shaderType == ShaderType::Geometry) {
246 m_insertStr += "#define attribute in\n";
247 m_insertStr += "#define varying out\n";
248 } else if (shaderType == ShaderType::Fragment) {
249 m_insertStr += "#define varying in\n";
250 m_insertStr += "#define texture2D texture\n";
251 m_insertStr += "#define gl_FragColor fragOutput\n";
252
253 if (m_renderContext->supportsAdvancedBlendHwKHR())
254 m_insertStr += "layout(blend_support_all_equations) out;\n ";
255
256 m_insertStr += "#ifndef NO_FRAG_OUTPUT\n";
257 m_insertStr += "out vec4 fragOutput;\n";
258 m_insertStr += "#endif\n";
259 }
260 }
261
addShaderExtensionStrings(ShaderType shaderType,bool isGLES)262 void QSSGShaderCache::addShaderExtensionStrings(ShaderType shaderType, bool isGLES)
263 {
264 if (isGLES) {
265 if (m_renderContext->supportsStandardDerivatives())
266 m_insertStr += "#extension GL_OES_standard_derivatives : enable\n";
267 else
268 m_insertStr += "#extension GL_OES_standard_derivatives : disable\n";
269 }
270
271 if (QSSGRendererInterface::isGlEs3Context(m_renderContext->renderContextType())) {
272 if (shaderType == ShaderType::TessControl || shaderType == ShaderType::TessEval) {
273 m_insertStr += "#extension GL_EXT_tessellation_shader : enable\n";
274 } else if (shaderType == ShaderType::Geometry) {
275 m_insertStr += "#extension GL_EXT_geometry_shader : enable\n";
276 } else if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment) {
277 if (m_renderContext->renderBackendCap(QSSGRenderBackend::QSSGRenderBackendCaps::gpuShader5))
278 m_insertStr += "#extension GL_EXT_gpu_shader5 : enable\n";
279 if (m_renderContext->supportsAdvancedBlendHwKHR())
280 m_insertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
281 }
282 } else {
283 if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment || shaderType == ShaderType::Geometry) {
284 if (m_renderContext->renderContextType() != QSSGRenderContextType::GLES2) {
285 m_insertStr += "#extension GL_ARB_gpu_shader5 : enable\n";
286 // m_insertStr += "#extension GL_ARB_shading_language_420pack : enable\n";
287 }
288 if (isGLES && m_renderContext->supportsTextureLod())
289 m_insertStr += "#extension GL_EXT_shader_texture_lod : enable\n";
290 if (m_renderContext->supportsShaderImageLoadStore())
291 m_insertStr += "#extension GL_ARB_shader_image_load_store : enable\n";
292 if (m_renderContext->supportsStorageBuffer())
293 m_insertStr += "#extension GL_ARB_shader_storage_buffer_object : enable\n";
294 if (m_renderContext->supportsAdvancedBlendHwKHR())
295 m_insertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
296 }
297 }
298 }
299
addShaderPreprocessor(QByteArray & str,const QByteArray & inKey,ShaderType shaderType,const ShaderFeatureSetList & inFeatures)300 void QSSGShaderCache::addShaderPreprocessor(QByteArray &str, const QByteArray &inKey, ShaderType shaderType, const ShaderFeatureSetList &inFeatures)
301 {
302 // Don't use shading language version returned by the driver as it might
303 // differ from the context version. Instead use the context type to specify
304 // the version string.
305 const auto contextType = m_renderContext->renderContextType();
306 const bool isGlES = QSSGRendererInterface::isGlEsContext(contextType);
307 m_insertStr.clear();
308
309 m_insertStr.append(m_renderContext->shadingLanguageVersion());
310
311 if (inFeatures.size()) {
312 for (int idx = 0, end = inFeatures.size(); idx < end; ++idx) {
313 QSSGShaderPreprocessorFeature feature(inFeatures[idx]);
314 m_insertStr.append("#define ");
315 m_insertStr.append(inFeatures[idx].name);
316 m_insertStr.append(" ");
317 m_insertStr.append(feature.enabled ? "1" : "0");
318 m_insertStr.append("\n");
319 }
320 }
321
322 if (isGlES) {
323 if (!QSSGRendererInterface::isGlEs3Context(contextType)) {
324 if (shaderType == ShaderType::Fragment) {
325 m_insertStr += "#define fragOutput gl_FragData[0]\n";
326 }
327 } else {
328 m_insertStr += "#define texture2D texture\n";
329 }
330
331 // add extenions strings before any other non-processor token
332 addShaderExtensionStrings(shaderType, isGlES);
333
334 // add precision qualifier depending on backend
335 if (QSSGRendererInterface::isGlEs3Context(contextType)) {
336 const QByteArray precision = defaultShaderPrecision(QByteArrayLiteral("highp"));
337 const QByteArray samplerPrecision = defaultSamplerPrecision(QByteArrayLiteral("mediump"));
338
339 QByteArray precisionQualifiers = "precision " + precision + " float;\n";
340 precisionQualifiers += "precision " + precision + " int;\n";
341 m_insertStr.append(precisionQualifiers);
342
343 if (m_renderContext->renderBackendCap(QSSGRenderBackend::QSSGRenderBackendCaps::gpuShader5)) {
344 precisionQualifiers = "precision " + samplerPrecision + " sampler2D;\n";
345 precisionQualifiers += "precision " + samplerPrecision + " sampler2DArray;\n";
346 precisionQualifiers += "precision " + samplerPrecision + " sampler2DShadow;\n";
347 m_insertStr.append(precisionQualifiers);
348
349 if (m_renderContext->supportsShaderImageLoadStore()) {
350 precisionQualifiers = "precision " + samplerPrecision + " image2D;\n";
351 m_insertStr.append(precisionQualifiers);
352 }
353 }
354
355 addBackwardCompatibilityDefines(shaderType);
356 } else {
357 // GLES2
358 const QByteArray precision = defaultShaderPrecision(QByteArrayLiteral("mediump"));
359
360 QByteArray precisionQualifiers = "precision " + precision + " float;\n";
361 precisionQualifiers += "precision " + precision + " int;\n";
362 m_insertStr.append(precisionQualifiers);
363
364 m_insertStr.append("#define texture texture2D\n");
365 if (m_renderContext->supportsTextureLod())
366 m_insertStr.append("#define textureLod texture2DLodEXT\n");
367 else
368 m_insertStr.append("#define textureLod(s, co, lod) texture2D(s, co)\n");
369 }
370 } else {
371 if (!QSSGRendererInterface::isGl2Context(contextType)) {
372 m_insertStr += "#define texture2D texture\n";
373
374 addShaderExtensionStrings(shaderType, isGlES);
375
376 m_insertStr += "#if __VERSION__ >= 330\n";
377
378 addBackwardCompatibilityDefines(shaderType);
379
380 m_insertStr += "#else\n";
381 if (shaderType == ShaderType::Fragment) {
382 m_insertStr += "#define fragOutput gl_FragData[0]\n";
383 }
384 m_insertStr += "#endif\n";
385 }
386 }
387
388 if (!inKey.isNull()) {
389 m_insertStr += "//Shader name -";
390 m_insertStr += inKey;
391 m_insertStr += "\n";
392 }
393
394 if (shaderType == ShaderType::TessControl) {
395 m_insertStr += "#define TESSELLATION_CONTROL_SHADER 1\n";
396 m_insertStr += "#define TESSELLATION_EVALUATION_SHADER 0\n";
397 } else if (shaderType == ShaderType::TessEval) {
398 m_insertStr += "#define TESSELLATION_CONTROL_SHADER 0\n";
399 m_insertStr += "#define TESSELLATION_EVALUATION_SHADER 1\n";
400 }
401
402 str.insert(0, m_insertStr);
403 }
404
forceCompileProgram(const QByteArray & inKey,const QByteArray & inVert,const QByteArray & inFrag,const QByteArray & inTessCtrl,const QByteArray & inTessEval,const QByteArray & inGeom,const QSSGShaderCacheProgramFlags & inFlags,const ShaderFeatureSetList & inFeatures,bool separableProgram,bool fromDisk)405 QSSGRef<QSSGRenderShaderProgram> QSSGShaderCache::forceCompileProgram(const QByteArray &inKey, const QByteArray &inVert, const QByteArray &inFrag, const QByteArray &inTessCtrl, const QByteArray &inTessEval, const QByteArray &inGeom, const QSSGShaderCacheProgramFlags &inFlags, const ShaderFeatureSetList &inFeatures, bool separableProgram, bool fromDisk)
406 {
407 if (!m_shaderCompilationEnabled)
408 return nullptr;
409 QSSGShaderCacheKey tempKey(inKey);
410 tempKey.m_features = inFeatures;
411 tempKey.generateHashCode();
412
413 if (fromDisk) {
414 qCInfo(TRACE_INFO) << "Loading from persistent shader cache: '<" << tempKey.m_key << ">'";
415 } else {
416 qCInfo(TRACE_INFO) << "Compiling into shader cache: '" << tempKey.m_key << ">'";
417 }
418
419 // SStackPerfTimer __perfTimer(m_PerfTimer, "Shader Compilation");
420 m_vertexCode = inVert;
421 m_tessCtrlCode = inTessCtrl;
422 m_tessEvalCode = inTessEval;
423 m_geometryCode = inGeom;
424 m_fragmentCode = inFrag;
425 // Add defines and such so we can write unified shaders that work across platforms.
426 // vertex and fragment shaders are optional for separable shaders
427 if (!separableProgram || !m_vertexCode.isEmpty())
428 addShaderPreprocessor(m_vertexCode, inKey, ShaderType::Vertex, inFeatures);
429 if (!separableProgram || !m_fragmentCode.isEmpty())
430 addShaderPreprocessor(m_fragmentCode, inKey, ShaderType::Fragment, inFeatures);
431 // optional shaders
432 if (inFlags & ShaderCacheProgramFlagValues::TessellationEnabled) {
433 Q_ASSERT(m_tessCtrlCode.size() && m_tessEvalCode.size());
434 addShaderPreprocessor(m_tessCtrlCode, inKey, ShaderType::TessControl, inFeatures);
435 addShaderPreprocessor(m_tessEvalCode, inKey, ShaderType::TessEval, inFeatures);
436 }
437 if (inFlags & ShaderCacheProgramFlagValues::GeometryShaderEnabled)
438 addShaderPreprocessor(m_geometryCode, inKey, ShaderType::Geometry, inFeatures);
439
440 auto shaderProgram = m_renderContext->compileSource(inKey.constData(),
441 toByteView(m_vertexCode),
442 toByteView(m_fragmentCode),
443 toByteView(m_tessCtrlCode),
444 toByteView(m_tessEvalCode),
445 toByteView(m_geometryCode),
446 separableProgram).m_shader;
447 const auto inserted = m_shaders.insert(tempKey, shaderProgram);
448 if (shaderProgram) {
449 // This is unnecessary memory waste in final deployed product, so we don't store this
450 // information when shaders were initialized from a cache.
451 // Unfortunately it is not practical to just regenerate shader source from scratch, when we
452 // want to export it, as the triggers and original sources are spread all over the place.
453 if (!m_shadersInitializedFromCache && inserted != m_shaders.end()) {
454 // Store sources for possible cache generation later
455 QSSGShaderSource ss;
456 for (int i = 0, end = inFeatures.size(); i < end; ++i)
457 ss.features.append(inFeatures[i]);
458 ss.key = inKey;
459 ss.flags = inFlags;
460 ss.vertexCode = inVert;
461 ss.fragmentCode = inFrag;
462 ss.tessCtrlCode = inTessCtrl;
463 ss.tessEvalCode = inTessEval;
464 ss.geometryCode = inGeom;
465 m_shaderSourceCache.append(ss);
466 }
467 // ### Shader Chache Writing Code is disabled
468 // if (m_ShaderCache) {
469 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "Program");
470 // m_ShaderCache->Att("key", inKey.toLocal8Bit().constData());
471 // CacheFlagsToStr(inFlags, m_FlagString);
472 // if (m_FlagString.size())
473 // m_ShaderCache->Att("glflags", m_FlagString.toLocal8Bit().constData());
474 // // write out the GL version.
475 // {
476 // QSSGRenderContextType theContextType =
477 // m_RenderContext.GetRenderContextType();
478 // ContextTypeToString(theContextType, m_ContextTypeString);
479 // m_ShaderCache->Att("gl-context-type", m_ContextTypeString.toLocal8Bit().constData());
480 // }
481 // if (inFeatures.size()) {
482 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "Features");
483 // for (int idx = 0, end = inFeatures.size(); idx < end; ++idx) {
484 // m_ShaderCache->Att(inFeatures[idx].m_Name, inFeatures[idx].m_Enabled);
485 // }
486 // }
487
488 // {
489 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "VertexCode");
490 // m_ShaderCache->Value(inVert);
491 // }
492 // {
493 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "FragmentCode");
494 // m_ShaderCache->Value(inFrag);
495 // }
496 // if (m_TessCtrlCode.size()) {
497 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "TessControlCode");
498 // m_ShaderCache->Value(inTessCtrl);
499 // }
500 // if (m_TessEvalCode.size()) {
501 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "TessEvalCode");
502 // m_ShaderCache->Value(inTessEval);
503 // }
504 // if (m_GeometryCode.size()) {
505 // IDOMWriter::Scope __writeScope(*m_ShaderCache, "GeometryCode");
506 // m_ShaderCache->Value(inGeom);
507 // }
508 // }
509 }
510 return inserted.value();
511 }
512
compileProgram(const QByteArray & inKey,const QByteArray & inVert,const QByteArray & inFrag,const QByteArray & inTessCtrl,const QByteArray & inTessEval,const QByteArray & inGeom,const QSSGShaderCacheProgramFlags & inFlags,const ShaderFeatureSetList & inFeatures,bool separableProgram)513 QSSGRef<QSSGRenderShaderProgram> QSSGShaderCache::compileProgram(const QByteArray &inKey, const QByteArray &inVert, const QByteArray &inFrag, const QByteArray &inTessCtrl, const QByteArray &inTessEval, const QByteArray &inGeom, const QSSGShaderCacheProgramFlags &inFlags, const ShaderFeatureSetList &inFeatures, bool separableProgram)
514 {
515 const QSSGRef<QSSGRenderShaderProgram> &theProgram = getProgram(inKey, inFeatures);
516 if (theProgram)
517 return theProgram;
518
519 const QSSGRef<QSSGRenderShaderProgram> &retval = forceCompileProgram(inKey, inVert, inFrag, inTessCtrl, inTessEval, inGeom, inFlags, inFeatures, separableProgram);
520 // ### Shader Chache Writing Code is disabled
521 // if (m_CacheFilePath.toLocal8Bit().constData() && m_ShaderCache && m_ShaderCompilationEnabled) {
522 // CFileSeekableIOStream theStream(m_CacheFilePath.toLocal8Bit().constData(), FileWriteFlags());
523 // if (theStream.IsOpen()) {
524 // CDOMSerializer::WriteXMLHeader(theStream);
525 // CDOMSerializer::Write(*m_ShaderCache->GetTopElement(), theStream);
526 // }
527 // }
528 return retval;
529 }
530
setShaderCachePersistenceEnabled(const QString & inDirectory)531 void QSSGShaderCache::setShaderCachePersistenceEnabled(const QString &inDirectory)
532 {
533 // ### Shader Chache Writing Code is disabled
534 Q_UNUSED(inDirectory)
535
536 // if (inDirectory == nullptr) {
537 // m_ShaderCache = nullptr;
538 // return;
539 // }
540 // BootupDOMWriter();
541 // m_CacheFilePath = QDir(inDirectory).filePath(GetShaderCacheFileName()).toStdString();
542
543 // QSSGRef<IRefCountedInputStream> theInStream =
544 // m_InputStreamFactory.GetStreamForFile(m_CacheFilePath.c_str());
545 // if (theInStream) {
546 // SStackPerfTimer __perfTimer(m_PerfTimer, "ShaderCache - Load");
547 // QSSGRef<IDOMFactory> theFactory(
548 // IDOMFactory::CreateDOMFactory(m_RenderContext.GetAllocator(), theStringTable));
549 // QVector<SShaderPreprocessorFeature> theFeatures;
550
551 // SDOMElement *theElem = CDOMSerializer::Read(*theFactory, *theInStream).second;
552 // if (theElem) {
553 // QSSGRef<IDOMReader> theReader = IDOMReader::CreateDOMReader(
554 // m_RenderContext.GetAllocator(), *theElem, theStringTable, theFactory);
555 // quint32 theAttValue = 0;
556 // theReader->Att("cache_version", theAttValue);
557 // if (theAttValue == IShaderCache::GetShaderVersion()) {
558 // QString loadVertexData;
559 // QString loadFragmentData;
560 // QString loadTessControlData;
561 // QString loadTessEvalData;
562 // QString loadGeometryData;
563 // QString shaderTypeString;
564 // for (bool success = theReader->MoveToFirstChild(); success;
565 // success = theReader->MoveToNextSibling()) {
566 // const char *theKeyStr = nullptr;
567 // theReader->UnregisteredAtt("key", theKeyStr);
568
569 // QString theKey = QString::fromLocal8Bit(theKeyStr);
570 // if (theKey.IsValid()) {
571 // m_FlagString.clear();
572 // const char *theFlagStr = "";
573 // SShaderCacheProgramFlags theFlags;
574 // if (theReader->UnregisteredAtt("glflags", theFlagStr)) {
575 // m_FlagString.assign(theFlagStr);
576 // theFlags = CacheFlagsToStr(m_FlagString);
577 // }
578
579 // m_ContextTypeString.clear();
580 // if (theReader->UnregisteredAtt("gl-context-type", theFlagStr))
581 // m_ContextTypeString.assign(theFlagStr);
582
583 // theFeatures.clear();
584 // {
585 // IDOMReader::Scope __featureScope(*theReader);
586 // if (theReader->MoveToFirstChild("Features")) {
587 // for (SDOMAttribute *theAttribute =
588 // theReader->GetFirstAttribute();
589 // theAttribute;
590 // theAttribute = theAttribute->m_NextAttribute) {
591 // bool featureValue = false;
592 // StringConversion<bool>().StrTo(theAttribute->m_Value,
593 // featureValue);
594 // theFeatures.push_back(SShaderPreprocessorFeature(
595 // QString::fromLocal8Bit(
596 // theAttribute->m_Name.c_str()),
597 // featureValue));
598 // }
599 // }
600 // }
601
602 // QSSGRenderContextType theContextType =
603 // StringToContextType(m_ContextTypeString);
604 // if (((quint32)theContextType != 0)
605 // && (theContextType & m_RenderContext.GetRenderContextType())
606 // == theContextType) {
607 // IDOMReader::Scope __readerScope(*theReader);
608 // loadVertexData.clear();
609 // loadFragmentData.clear();
610 // loadTessControlData.clear();
611 // loadTessEvalData.clear();
612 // loadGeometryData.clear();
613
614 // // Vertex *MUST* be the first
615 // // Todo deal with pure compute shader programs
616 // if (theReader->MoveToFirstChild("VertexCode")) {
617 // const char *theValue = nullptr;
618 // theReader->Value(theValue);
619 // loadVertexData.assign(theValue);
620 // while (theReader->MoveToNextSibling()) {
621 // theReader->Value(theValue);
622
623 // shaderTypeString.assign(
624 // theReader->GetElementName().c_str());
625 // ShaderType shaderType =
626 // StringToShaderType(shaderTypeString);
627
628 // if (shaderType == ShaderType::Fragment)
629 // loadFragmentData.assign(theValue);
630 // else if (shaderType == ShaderType::TessControl)
631 // loadTessControlData.assign(theValue);
632 // else if (shaderType == ShaderType::TessEval)
633 // loadTessEvalData.assign(theValue);
634 // else if (shaderType == ShaderType::Geometry)
635 // loadGeometryData.assign(theValue);
636 // }
637 // }
638
639 // if (loadVertexData.size()
640 // && (loadFragmentData.size() || loadGeometryData.size())) {
641
642 // QSSGRef<QSSGRenderShaderProgram> theShader = ForceCompileProgram(
643 // theKey, loadVertexData.toLocal8Bit().constData(),
644 // loadFragmentData.toLocal8Bit().constData(), loadTessControlData.toLocal8Bit().constData(),
645 // loadTessEvalData.toLocal8Bit().constData(), loadGeometryData.toLocal8Bit().constData(),
646 // theFlags, theFeatures, false, true /*fromDisk*/);
647 // // If something doesn't save or load correctly, get the runtime
648 // // to re-generate.
649 // if (!theShader)
650 // m_Shaders.remove(theKey);
651 // }
652 // }
653 // }
654 // }
655 // }
656 // }
657 // }
658 }
659
isShaderCachePersistenceEnabled() const660 bool QSSGShaderCache::isShaderCachePersistenceEnabled() const
661 {
662 // ### Shader Chache Writing Code is disabled
663 // return m_ShaderCache != nullptr;
664 return false;
665 }
666
setShaderCompilationEnabled(bool inEnableShaderCompilation)667 void QSSGShaderCache::setShaderCompilationEnabled(bool inEnableShaderCompilation)
668 {
669 m_shaderCompilationEnabled = inEnableShaderCompilation;
670 }
671
shaderCacheVersion() const672 quint32 QSSGShaderCache::shaderCacheVersion() const
673 {
674 return 1;
675 }
676
shaderCacheFileId() const677 quint32 QSSGShaderCache::shaderCacheFileId() const
678 {
679 return 0x26a9b358;
680 }
681
importShaderCache(const QByteArray & shaderCache,QByteArray & errors)682 void QSSGShaderCache::importShaderCache(const QByteArray &shaderCache, QByteArray &errors)
683 {
684 #define BAILOUT(details) { \
685 QByteArray errorMsg = QByteArrayLiteral("importShaderCache failed to import shader cache: " details); \
686 qWarning() << errorMsg; \
687 errors.append(errorMsg); \
688 return; \
689 }
690
691 if (shaderCache.isEmpty())
692 BAILOUT("Shader cache Empty")
693
694 QDataStream data(shaderCache);
695 quint32 type;
696 quint32 version;
697 bool isBinary;
698 data >> type;
699
700 auto binaryShadersSupported = [this]() -> bool {
701 return !(m_renderContext->format().renderableType() == QSurfaceFormat::OpenGLES
702 && m_renderContext->format().majorVersion() == 2);
703 };
704
705 if (type != shaderCacheFileId())
706 BAILOUT("Not a shader cache")
707 data >> isBinary;
708 if (isBinary && !binaryShadersSupported())
709 BAILOUT("Binary shaders are not supported")
710 data >> version;
711 if (version != shaderCacheVersion())
712 BAILOUT("Version mismatch")
713
714 #undef BAILOUT
715
716 int progCount;
717 data >> progCount;
718 m_shadersInitializedFromCache = progCount > 0;
719 for (int i = 0; i < progCount; ++i) {
720 QByteArray key;
721 int featCount;
722
723 data >> key;
724 data >> featCount;
725
726 ShaderFeatureSetList features;
727 for (int j = 0; j < featCount; ++j) {
728 QByteArray featName;
729 bool featVal;
730 data >> featName;
731 data >> featVal;
732 features.push_back(QSSGShaderPreprocessorFeature(featName, featVal));
733 }
734 QSSGRef<QSSGRenderShaderProgram> theShader;
735 QSSGShaderCacheKey tempKey(key);
736 tempKey.m_features = features;
737 tempKey.generateHashCode();
738 if (isBinary) {
739 quint32 format;
740 QByteArray binary;
741 data >> format;
742 data >> binary;
743
744 qCInfo(TRACE_INFO) << "Loading binary program from shader cache: '<" << key << ">'";
745
746 QSSGRenderVertFragCompilationResult result = m_renderContext->compileBinary(key, format, binary);
747 theShader = result.m_shader;
748 if (theShader.isNull())
749 errors += theShader->errorMessage();
750 else
751 m_shaders.insert(tempKey, theShader);
752 } else {
753 QByteArray loadVertexData;
754 QByteArray loadFragmentData;
755 QByteArray loadTessControlData;
756 QByteArray loadTessEvalData;
757 QByteArray loadGeometryData;
758
759 data >> loadVertexData;
760 data >> loadFragmentData;
761 data >> loadTessControlData;
762 data >> loadTessEvalData;
763 data >> loadGeometryData;
764
765 if (!loadVertexData.isEmpty() && (!loadFragmentData.isEmpty()
766 || !loadGeometryData.isEmpty())) {
767 QByteArray error;
768 QSSGRenderVertFragCompilationResult result
769 = m_renderContext->compileSource(key, QSSGByteView(loadVertexData), QSSGByteView(loadFragmentData),
770 QSSGByteView(loadTessControlData), QSSGByteView(loadTessControlData),
771 QSSGByteView(loadGeometryData));
772 theShader = result.m_shader;
773 if (theShader.isNull())
774 errors += theShader->errorMessage();
775 else
776 m_shaders.insert(tempKey, theShader);
777 }
778 }
779 // If something doesn't save or load correctly, get the runtime to re-generate.
780 if (theShader.isNull()) {
781 qWarning() << __FUNCTION__ << "Failed to load a cached a shader:" << key;
782 m_shadersInitializedFromCache = false;
783 }
784 }
785 }
786
exportShaderCache(bool binaryShaders)787 QByteArray QSSGShaderCache::exportShaderCache(bool binaryShaders)
788 {
789 if (m_shadersInitializedFromCache) {
790 qWarning() << __FUNCTION__ << "Warning: Shader cache export is not supported when"
791 " shaders were originally imported from a cache file.";
792 return {};
793 }
794
795 auto binaryShadersSupported = [this]() -> bool {
796 return !(m_renderContext->format().renderableType() == QSurfaceFormat::OpenGLES
797 && m_renderContext->format().majorVersion() == 2);
798 };
799
800 // The assumption is that cache was generated on the same environment it will be read.
801 // Attempting to load a cache generated on another environment will likely lead to crash.
802
803 QByteArray retval;
804 QDataStream data(&retval, QIODevice::WriteOnly);
805 bool saveBinary = binaryShaders && binaryShadersSupported();
806 data << shaderCacheFileId();
807 data << saveBinary;
808 data << shaderCacheVersion();
809 data << m_shaderSourceCache.size();
810
811 for (const auto &ss : qAsConst(m_shaderSourceCache))
812 {
813 data << ss.key;
814 data << ss.features.size();
815 for (int i = 0, end = ss.features.size(); i < end; ++i) {
816 data << ss.features[i].name;
817 data << ss.features[i].enabled;
818 }
819
820 if (saveBinary) {
821 auto program = getProgram(ss.key, ss.features);
822 quint32 format = 0;
823 QByteArray binaryData;
824 program->getProgramBinary(format, binaryData);
825 data << format;
826 data << binaryData;
827 } else {
828 m_vertexCode = ss.vertexCode;
829 m_tessCtrlCode = ss.tessCtrlCode;
830 m_tessEvalCode = ss.tessEvalCode;
831 m_geometryCode = ss.geometryCode;
832 m_fragmentCode = ss.fragmentCode;
833 // Add defines and such so we can write unified shaders that work across platforms.
834 // vertex and fragment shaders are optional for separable shaders
835 if (m_vertexCode.size())
836 addShaderPreprocessor(m_vertexCode, ss.key, ShaderType::Vertex, ss.features);
837 if (m_fragmentCode.size())
838 addShaderPreprocessor(m_fragmentCode, ss.key, ShaderType::Fragment, ss.features);
839 // optional shaders
840 if (m_tessCtrlCode.size() && m_tessEvalCode.size()) {
841 addShaderPreprocessor(m_tessCtrlCode, ss.key, ShaderType::TessControl, ss.features);
842 addShaderPreprocessor(m_tessEvalCode, ss.key, ShaderType::TessEval, ss.features);
843 }
844 if (m_geometryCode.size())
845 addShaderPreprocessor(m_geometryCode, ss.key, ShaderType::Geometry, ss.features);
846
847 auto writeShaderElement = [&data](const QByteArray &shaderSource) {
848 QByteArray stripped = shaderSource;
849 int start = stripped.indexOf(QByteArrayLiteral("/*"));
850 while (start != -1) {
851 int end = stripped.indexOf(QByteArrayLiteral("*/"));
852 if (end == -1)
853 break; // Mismatched comment
854 stripped.replace(start, end - start + 2, QByteArray());
855 start = stripped.indexOf(QByteArrayLiteral("/*"));
856 }
857 data << stripped;
858 };
859
860 writeShaderElement(m_vertexCode);
861 writeShaderElement(m_fragmentCode);
862 writeShaderElement(m_tessCtrlCode);
863 writeShaderElement(m_tessEvalCode);
864 writeShaderElement(m_geometryCode);
865 }
866 }
867 return retval;
868 }
869
870 QT_END_NAMESPACE
871