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