summaryrefslogtreecommitdiffstats
path: root/src/runtimerender/Qt3DSRenderShaderCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtimerender/Qt3DSRenderShaderCache.cpp')
-rw-r--r--src/runtimerender/Qt3DSRenderShaderCache.cpp770
1 files changed, 770 insertions, 0 deletions
diff --git a/src/runtimerender/Qt3DSRenderShaderCache.cpp b/src/runtimerender/Qt3DSRenderShaderCache.cpp
new file mode 100644
index 0000000..77d4ee6
--- /dev/null
+++ b/src/runtimerender/Qt3DSRenderShaderCache.cpp
@@ -0,0 +1,770 @@
+/****************************************************************************
+**
+** Copyright (C) 2008-2012 NVIDIA Corporation.
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt 3D Studio.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include "Qt3DSRenderShaderCache.h"
+#include "render/Qt3DSRenderContext.h"
+#include "foundation/Qt3DSContainers.h"
+#include "foundation/Qt3DSAtomic.h"
+#include "StringTools.h"
+#include "foundation/XML.h"
+#include "foundation/IOStreams.h"
+#include "foundation/StringConversionImpl.h"
+#include "Qt3DSRenderInputStreamFactory.h"
+#include "foundation/FileTools.h"
+#include "render/Qt3DSRenderShaderProgram.h"
+#include "Qt3DSRenderer.h"
+#include <memory>
+#include "foundation/Qt3DSTime.h"
+#include "foundation/Qt3DSPerfTimer.h"
+#include "EASTL/sort.h"
+
+#include <QRegularExpression>
+#include <QString>
+
+using namespace qt3ds::render;
+
+namespace {
+using qt3ds::render::NVRenderContextScopedProperty;
+const char *TessellationEnabledStr = "TessellationStageEnabled";
+const char *GeometryEnabledStr = "GeometryStageEnabled";
+inline void AppendFlagValue(Qt3DSString &inStr, const char *flag)
+{
+ if (inStr.length())
+ inStr.append(QLatin1Char(','));
+ inStr.append(flag);
+}
+inline void CacheFlagsToStr(const SShaderCacheProgramFlags &inFlags, Qt3DSString &inString)
+{
+ inString.clear();
+ if (inFlags.IsTessellationEnabled())
+ AppendFlagValue(inString, TessellationEnabledStr);
+ if (inFlags.IsGeometryShaderEnabled())
+ AppendFlagValue(inString, GeometryEnabledStr);
+}
+
+struct ShaderType
+{
+ enum Enum { Vertex, TessControl, TessEval, Fragment, Geometry, Compute };
+};
+
+inline ShaderType::Enum StringToShaderType(Qt3DSString &inShaderType)
+{
+ ShaderType::Enum retval = ShaderType::Vertex;
+
+ if (inShaderType.size() == 0)
+ return retval;
+
+ if (!inShaderType.compare("VertexCode"))
+ retval = ShaderType::Vertex;
+ else if (!inShaderType.compare("FragmentCode"))
+ retval = ShaderType::Fragment;
+ else if (!inShaderType.compare("TessControlCode"))
+ retval = ShaderType::TessControl;
+ else if (!inShaderType.compare("TessEvalCode"))
+ retval = ShaderType::TessEval;
+ else if (!inShaderType.compare("GeometryCode"))
+ retval = ShaderType::Geometry;
+ else
+ QT3DS_ASSERT(false);
+
+ return retval;
+}
+
+inline SShaderCacheProgramFlags CacheFlagsToStr(const Qt3DSString &inString)
+{
+ SShaderCacheProgramFlags retval;
+ if (inString.indexOf(TessellationEnabledStr) != Qt3DSString::npos)
+ retval.SetTessellationEnabled(true);
+ if (inString.indexOf(GeometryEnabledStr) != Qt3DSString::npos)
+ retval.SetGeometryShaderEnabled(true);
+ return retval;
+}
+
+typedef eastl::pair<const char *, NVRenderContextValues::Enum> TStringToContextValuePair;
+
+/*GLES2 = 1 << 0,
+GL2 = 1 << 1,
+GLES3 = 1 << 2,
+GL3 = 1 << 3,
+GL4 = 1 << 4,
+NullContext = 1 << 5,*/
+TStringToContextValuePair g_StringToContextTypeValue[] = {
+ TStringToContextValuePair("GLES2", NVRenderContextValues::GLES2),
+ TStringToContextValuePair("GL2", NVRenderContextValues::GL2),
+ TStringToContextValuePair("GLES3", NVRenderContextValues::GLES3),
+ TStringToContextValuePair("GLES3PLUS", NVRenderContextValues::GLES3PLUS),
+ TStringToContextValuePair("GL3", NVRenderContextValues::GL3),
+ TStringToContextValuePair("GL4", NVRenderContextValues::GL4),
+ TStringToContextValuePair("NullContext", NVRenderContextValues::NullContext),
+};
+
+size_t g_NumStringToContextValueEntries =
+ sizeof(g_StringToContextTypeValue) / sizeof(*g_StringToContextTypeValue);
+
+inline void ContextTypeToString(qt3ds::render::NVRenderContextType inType,
+ Qt3DSString &outContextType)
+{
+ outContextType.clear();
+ for (size_t idx = 0, end = g_NumStringToContextValueEntries; idx < end; ++idx) {
+ if (inType & g_StringToContextTypeValue[idx].second) {
+ if (outContextType.size())
+ outContextType.append('|');
+ outContextType.append(g_StringToContextTypeValue[idx].first);
+ }
+ }
+}
+
+inline qt3ds::render::NVRenderContextType StringToContextType(const Qt3DSString &inContextType)
+{
+ qt3ds::render::NVRenderContextType retval;
+ char tempBuffer[128];
+ memZero(tempBuffer, 128);
+ const QString::size_type lastTempBufIdx = 127;
+ QString::size_type pos = 0, lastpos = 0;
+ if (inContextType.size() == 0)
+ return retval;
+
+ do {
+ pos = int(inContextType.indexOf(QLatin1Char('|'), lastpos));
+ if (pos == Qt3DSString::npos)
+ pos = int(inContextType.size());
+ {
+
+ QString::size_type sectionLen = NVMin(pos - lastpos, lastTempBufIdx);
+ qt3ds::intrinsics::memCopy(tempBuffer, inContextType.toUtf8().constData() + lastpos,
+ sectionLen);
+ tempBuffer[lastTempBufIdx] = 0;
+ for (size_t idx = 0, end = g_NumStringToContextValueEntries; idx < end; ++idx) {
+ if (strcmp(g_StringToContextTypeValue[idx].first, tempBuffer) == 0)
+ retval = retval | g_StringToContextTypeValue[idx].second;
+ }
+ }
+ // iterate past the bar
+ ++pos;
+ lastpos = pos;
+ } while (pos < inContextType.size() && pos != Qt3DSString::npos);
+
+ return retval;
+}
+
+struct SShaderCacheKey
+{
+ CRegisteredString m_Key;
+ eastl::vector<SShaderPreprocessorFeature> m_Features;
+ size_t m_HashCode;
+
+ SShaderCacheKey(CRegisteredString key = CRegisteredString())
+ : m_Key(key)
+ , m_HashCode(0)
+ {
+ }
+
+ SShaderCacheKey(const SShaderCacheKey &other)
+ : m_Key(other.m_Key)
+ , m_Features(other.m_Features)
+ , m_HashCode(other.m_HashCode)
+ {
+ }
+
+ SShaderCacheKey &operator=(const SShaderCacheKey &other)
+ {
+ m_Key = other.m_Key;
+ m_Features = other.m_Features;
+ m_HashCode = other.m_HashCode;
+ return *this;
+ }
+
+ void GenerateHashCode()
+ {
+ m_HashCode = m_Key.hash();
+ m_HashCode = m_HashCode
+ ^ HashShaderFeatureSet(toDataRef(m_Features.data(), (QT3DSU32)m_Features.size()));
+ }
+ bool operator==(const SShaderCacheKey &inOther) const
+ {
+ return m_Key == inOther.m_Key && m_Features == inOther.m_Features;
+ }
+};
+}
+
+namespace eastl {
+template <>
+struct hash<SShaderCacheKey>
+{
+ size_t operator()(const SShaderCacheKey &inKey) const { return inKey.m_HashCode; }
+};
+}
+
+namespace {
+
+struct ShaderCache : public IShaderCache
+{
+ typedef nvhash_map<SShaderCacheKey, NVScopedRefCounted<NVRenderShaderProgram>> TShaderMap;
+ NVRenderContext &m_RenderContext;
+ IPerfTimer &m_PerfTimer;
+ TShaderMap m_Shaders;
+ Qt3DSString m_CacheFilePath;
+ Qt3DSString m_VertexCode;
+ Qt3DSString m_TessCtrlCode;
+ Qt3DSString m_TessEvalCode;
+ Qt3DSString m_GeometryCode;
+ Qt3DSString m_FragmentCode;
+ Qt3DSString m_InsertStr;
+ Qt3DSString m_FlagString;
+ Qt3DSString m_ContextTypeString;
+ SShaderCacheKey m_TempKey;
+
+ NVScopedRefCounted<IDOMWriter> m_ShaderCache;
+ IInputStreamFactory &m_InputStreamFactory;
+ bool m_ShaderCompilationEnabled;
+ volatile QT3DSI32 mRefCount;
+
+ ShaderCache(NVRenderContext &ctx, IInputStreamFactory &inInputStreamFactory,
+ IPerfTimer &inPerfTimer)
+ : m_RenderContext(ctx)
+ , m_PerfTimer(inPerfTimer)
+ , m_Shaders(ctx.GetAllocator(), "ShaderCache::m_Shaders")
+ , m_InputStreamFactory(inInputStreamFactory)
+ , m_ShaderCompilationEnabled(true)
+ , mRefCount(0)
+ {
+ }
+ QT3DS_IMPLEMENT_REF_COUNT_ADDREF_RELEASE_OVERRIDE(m_RenderContext.GetAllocator())
+
+ NVRenderShaderProgram *GetProgram(CRegisteredString inKey,
+ NVConstDataRef<SShaderPreprocessorFeature> inFeatures) override
+ {
+ m_TempKey.m_Key = inKey;
+ m_TempKey.m_Features.assign(inFeatures.begin(), inFeatures.end());
+ m_TempKey.GenerateHashCode();
+ TShaderMap::iterator theIter = m_Shaders.find(m_TempKey);
+ if (theIter != m_Shaders.end())
+ return theIter->second;
+ return NULL;
+ }
+
+ void AddBackwardCompatibilityDefines(ShaderType::Enum shaderType)
+ {
+ if (shaderType == ShaderType::Vertex || shaderType == ShaderType::TessControl
+ || shaderType == ShaderType::TessEval || shaderType == ShaderType::Geometry) {
+ m_InsertStr += "#define attribute in\n";
+ m_InsertStr += "#define varying out\n";
+ } else if (shaderType == ShaderType::Fragment) {
+ m_InsertStr += "#define varying in\n";
+ m_InsertStr += "#define texture2D texture\n";
+ m_InsertStr += "#define gl_FragColor fragOutput\n";
+
+ if (m_RenderContext.IsAdvancedBlendHwSupportedKHR())
+ m_InsertStr += "layout(blend_support_all_equations) out;\n";
+
+ m_InsertStr += "#ifndef NO_FRAG_OUTPUT\n";
+ m_InsertStr += "out vec4 fragOutput;\n";
+ m_InsertStr += "#endif\n";
+ }
+ }
+
+ void AddShaderExtensionStrings(ShaderType::Enum shaderType, bool isGLES)
+ {
+ if (isGLES) {
+ if (m_RenderContext.IsStandardDerivativesSupported())
+ m_InsertStr += "#extension GL_OES_standard_derivatives : enable\n";
+ else
+ m_InsertStr += "#extension GL_OES_standard_derivatives : disable\n";
+ }
+
+ if (IQt3DSRenderer::IsGlEs3Context(m_RenderContext.GetRenderContextType())) {
+ if (shaderType == ShaderType::TessControl || shaderType == ShaderType::TessEval) {
+ m_InsertStr += "#extension GL_EXT_tessellation_shader : enable\n";
+ } else if (shaderType == ShaderType::Geometry) {
+ m_InsertStr += "#extension GL_EXT_geometry_shader : enable\n";
+ } else if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment) {
+ if (m_RenderContext.GetRenderBackendCap(render::NVRenderBackend::NVRenderBackendCaps::gpuShader5))
+ m_InsertStr += "#extension GL_EXT_gpu_shader5 : enable\n";
+ if (m_RenderContext.IsAdvancedBlendHwSupportedKHR())
+ m_InsertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
+ }
+ } else {
+ if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment
+ || shaderType == ShaderType::Geometry) {
+ if (m_RenderContext.GetRenderContextType() != NVRenderContextValues::GLES2) {
+ m_InsertStr += "#extension GL_ARB_gpu_shader5 : enable\n";
+ m_InsertStr += "#extension GL_ARB_shading_language_420pack : enable\n";
+ }
+ if (isGLES && m_RenderContext.IsTextureLodSupported())
+ m_InsertStr += "#extension GL_EXT_shader_texture_lod : enable\n";
+ if (m_RenderContext.IsShaderImageLoadStoreSupported())
+ m_InsertStr += "#extension GL_ARB_shader_image_load_store : enable\n";
+ if (m_RenderContext.IsAtomicCounterBufferSupported())
+ m_InsertStr += "#extension GL_ARB_shader_atomic_counters : enable\n";
+ if (m_RenderContext.IsStorageBufferSupported())
+ m_InsertStr += "#extension GL_ARB_shader_storage_buffer_object : enable\n";
+ if (m_RenderContext.IsAdvancedBlendHwSupportedKHR())
+ m_InsertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
+ }
+ }
+ }
+
+ void AddShaderPreprocessor(Qt3DSString &str, CRegisteredString inKey,
+ ShaderType::Enum shaderType,
+ NVConstDataRef<SShaderPreprocessorFeature> inFeatures)
+ {
+ // Don't use shading language version returned by the driver as it might
+ // differ from the context version. Instead use the context type to specify
+ // the version string.
+ bool isGlES = IQt3DSRenderer::IsGlEsContext(m_RenderContext.GetRenderContextType());
+ m_InsertStr.clear();
+ int minor = m_RenderContext.format().minorVersion();
+ QString versionStr;
+ QTextStream stream(&versionStr);
+ stream << "#version ";
+ const QT3DSU32 type = (QT3DSU32)m_RenderContext.GetRenderContextType();
+ switch (type) {
+ case NVRenderContextValues::GLES2:
+ stream << "1" << minor << "0\n";
+ break;
+ case NVRenderContextValues::GL2:
+ stream << "1" << minor << "0\n";
+ break;
+ case NVRenderContextValues::GLES3PLUS:
+ case NVRenderContextValues::GLES3:
+ stream << "3" << minor << "0 es\n";
+ break;
+ case NVRenderContextValues::GL3:
+ if (minor == 3)
+ stream << "3" << minor << "0\n";
+ else
+ stream << "1" << 3 + minor << "0\n";
+ break;
+ case NVRenderContextValues::GL4:
+ stream << "4" << minor << "0\n";
+ break;
+ default:
+ QT3DS_ASSERT(false);
+ break;
+ }
+
+ m_InsertStr.append(versionStr.toLatin1().data());
+
+ if (inFeatures.size()) {
+ for (QT3DSU32 idx = 0, end = inFeatures.size(); idx < end; ++idx) {
+ SShaderPreprocessorFeature feature(inFeatures[idx]);
+ m_InsertStr.append("#define ");
+ m_InsertStr.append(inFeatures[idx].m_Name.c_str());
+ m_InsertStr.append(" ");
+ m_InsertStr.append(feature.m_Enabled ? "1" : "0");
+ m_InsertStr.append("\n");
+ }
+ }
+
+ if (isGlES) {
+ if (!IQt3DSRenderer::IsGlEs3Context(m_RenderContext.GetRenderContextType())) {
+ if (shaderType == ShaderType::Fragment) {
+ m_InsertStr += "#define fragOutput gl_FragData[0]\n";
+ }
+ } else {
+ m_InsertStr += "#define texture2D texture\n";
+ }
+
+ // add extenions strings before any other non-processor token
+ AddShaderExtensionStrings(shaderType, isGlES);
+
+ // add precision qualifier depending on backend
+ if (IQt3DSRenderer::IsGlEs3Context(m_RenderContext.GetRenderContextType())) {
+ m_InsertStr.append("precision highp float;\n"
+ "precision highp int;\n");
+ if( m_RenderContext.GetRenderBackendCap(render::NVRenderBackend::NVRenderBackendCaps::gpuShader5) ) {
+ m_InsertStr.append("precision mediump sampler2D;\n"
+ "precision mediump sampler2DArray;\n"
+ "precision mediump sampler2DShadow;\n");
+ if (m_RenderContext.IsShaderImageLoadStoreSupported()) {
+ m_InsertStr.append("precision mediump image2D;\n");
+ }
+ }
+
+ AddBackwardCompatibilityDefines(shaderType);
+ } else {
+ // GLES2
+ m_InsertStr.append("precision mediump float;\n"
+ "precision mediump int;\n"
+ "#define texture texture2D\n");
+ if (m_RenderContext.IsTextureLodSupported())
+ m_InsertStr.append("#define textureLod texture2DLodEXT\n");
+ else
+ m_InsertStr.append("#define textureLod(s, co, lod) texture2D(s, co)\n");
+ }
+ } else {
+ if (!IQt3DSRenderer::IsGl2Context(m_RenderContext.GetRenderContextType())) {
+ m_InsertStr += "#define texture2D texture\n";
+
+ AddShaderExtensionStrings(shaderType, isGlES);
+
+ m_InsertStr += "#if __VERSION__ >= 330\n";
+
+ AddBackwardCompatibilityDefines(shaderType);
+
+ m_InsertStr += "#else\n";
+ if (shaderType == ShaderType::Fragment) {
+ m_InsertStr += "#define fragOutput gl_FragData[0]\n";
+ }
+ m_InsertStr += "#endif\n";
+ }
+ }
+
+ if (inKey.IsValid()) {
+ m_InsertStr += "//Shader name -";
+ m_InsertStr += inKey.c_str();
+ m_InsertStr += "\n";
+ }
+
+ if (shaderType == ShaderType::TessControl) {
+ m_InsertStr += "#define TESSELLATION_CONTROL_SHADER 1\n";
+ m_InsertStr += "#define TESSELLATION_EVALUATION_SHADER 0\n";
+ } else if (shaderType == ShaderType::TessEval) {
+ m_InsertStr += "#define TESSELLATION_CONTROL_SHADER 0\n";
+ m_InsertStr += "#define TESSELLATION_EVALUATION_SHADER 1\n";
+ }
+
+ str.insert(0, m_InsertStr);
+ }
+ // Compile this program overwriting any existing ones.
+ NVRenderShaderProgram *
+ ForceCompileProgram(CRegisteredString inKey, const char8_t *inVert, const char8_t *inFrag,
+ const char8_t *inTessCtrl, const char8_t *inTessEval, const char8_t *inGeom,
+ const SShaderCacheProgramFlags &inFlags,
+ NVConstDataRef<SShaderPreprocessorFeature> inFeatures,
+ bool separableProgram, bool fromDisk = false) override
+ {
+ if (m_ShaderCompilationEnabled == false)
+ return NULL;
+ SShaderCacheKey tempKey(inKey);
+ tempKey.m_Features.assign(inFeatures.begin(), inFeatures.end());
+ tempKey.GenerateHashCode();
+
+ eastl::pair<TShaderMap::iterator, bool> theInserter = m_Shaders.insert(tempKey);
+ if (fromDisk) {
+ qCInfo(TRACE_INFO) << "Loading from persistent shader cache: '<"
+ << tempKey.m_Key << ">'";
+ } else {
+ qCInfo(TRACE_INFO) << "Compiling into shader cache: '"
+ << tempKey.m_Key << ">'";
+ }
+
+ if (!inVert)
+ inVert = "";
+ if (!inTessCtrl)
+ inTessCtrl = "";
+ if (!inTessEval)
+ inTessEval = "";
+ if (!inGeom)
+ inGeom = "";
+ if (!inFrag)
+ inFrag = "";
+
+ SStackPerfTimer __perfTimer(m_PerfTimer, "Shader Compilation");
+ m_VertexCode.assign(inVert);
+ m_TessCtrlCode.assign(inTessCtrl);
+ m_TessEvalCode.assign(inTessEval);
+ m_GeometryCode.assign(inGeom);
+ m_FragmentCode.assign(inFrag);
+ // Add defines and such so we can write unified shaders that work across platforms.
+ // vertex and fragment shaders are optional for separable shaders
+ if (!separableProgram || !m_VertexCode.isEmpty())
+ AddShaderPreprocessor(m_VertexCode, inKey, ShaderType::Vertex, inFeatures);
+ if (!separableProgram || !m_FragmentCode.isEmpty())
+ AddShaderPreprocessor(m_FragmentCode, inKey, ShaderType::Fragment, inFeatures);
+ // optional shaders
+ if (inFlags.IsTessellationEnabled()) {
+ QT3DS_ASSERT(m_TessCtrlCode.size() && m_TessEvalCode.size());
+ AddShaderPreprocessor(m_TessCtrlCode, inKey, ShaderType::TessControl, inFeatures);
+ AddShaderPreprocessor(m_TessEvalCode, inKey, ShaderType::TessEval, inFeatures);
+ }
+ if (inFlags.IsGeometryShaderEnabled())
+ AddShaderPreprocessor(m_GeometryCode, inKey, ShaderType::Geometry, inFeatures);
+
+ theInserter.first->second =
+ m_RenderContext
+ .CompileSource(inKey, m_VertexCode.c_str(), QT3DSU32(m_VertexCode.size()),
+ m_FragmentCode.c_str(), QT3DSU32(m_FragmentCode.size()),
+ m_TessCtrlCode.c_str(), QT3DSU32(m_TessCtrlCode.size()),
+ m_TessEvalCode.c_str(), QT3DSU32(m_TessEvalCode.size()),
+ m_GeometryCode.c_str(), QT3DSU32(m_GeometryCode.size()),
+ separableProgram).mShader;
+ if (theInserter.first->second) {
+ if (m_ShaderCache) {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "Program");
+ m_ShaderCache->Att("key", inKey.c_str());
+ CacheFlagsToStr(inFlags, m_FlagString);
+ if (m_FlagString.size())
+ m_ShaderCache->Att("glflags", m_FlagString.c_str());
+ // write out the GL version.
+ {
+ qt3ds::render::NVRenderContextType theContextType =
+ m_RenderContext.GetRenderContextType();
+ ContextTypeToString(theContextType, m_ContextTypeString);
+ m_ShaderCache->Att("gl-context-type", m_ContextTypeString.c_str());
+ }
+ if (inFeatures.size()) {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "Features");
+ for (QT3DSU32 idx = 0, end = inFeatures.size(); idx < end; ++idx) {
+ m_ShaderCache->Att(inFeatures[idx].m_Name, inFeatures[idx].m_Enabled);
+ }
+ }
+
+ {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "VertexCode");
+ m_ShaderCache->Value(inVert);
+ }
+ {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "FragmentCode");
+ m_ShaderCache->Value(inFrag);
+ }
+ if (m_TessCtrlCode.size()) {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "TessControlCode");
+ m_ShaderCache->Value(inTessCtrl);
+ }
+ if (m_TessEvalCode.size()) {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "TessEvalCode");
+ m_ShaderCache->Value(inTessEval);
+ }
+ if (m_GeometryCode.size()) {
+ IDOMWriter::Scope __writeScope(*m_ShaderCache, "GeometryCode");
+ m_ShaderCache->Value(inGeom);
+ }
+ }
+ }
+ return theInserter.first->second;
+ }
+
+ virtual NVRenderShaderProgram *
+ CompileProgram(CRegisteredString inKey, const char8_t *inVert, const char8_t *inFrag,
+ const char8_t *inTessCtrl, const char8_t *inTessEval, const char8_t *inGeom,
+ const SShaderCacheProgramFlags &inFlags,
+ NVConstDataRef<SShaderPreprocessorFeature> inFeatures, bool separableProgram) override
+ {
+ NVRenderShaderProgram *theProgram = GetProgram(inKey, inFeatures);
+ if (theProgram)
+ return theProgram;
+
+ NVRenderShaderProgram *retval =
+ ForceCompileProgram(inKey, inVert, inFrag, inTessCtrl, inTessEval, inGeom, inFlags,
+ inFeatures, separableProgram);
+ if (m_CacheFilePath.c_str() && m_ShaderCache && m_ShaderCompilationEnabled) {
+ CFileSeekableIOStream theStream(m_CacheFilePath.c_str(), FileWriteFlags());
+ if (theStream.IsOpen()) {
+ NVScopedRefCounted<IStringTable> theStringTable(
+ IStringTable::CreateStringTable(m_RenderContext.GetAllocator()));
+ CDOMSerializer::WriteXMLHeader(theStream);
+ CDOMSerializer::Write(m_RenderContext.GetAllocator(),
+ *m_ShaderCache->GetTopElement(), theStream, *theStringTable);
+ }
+ }
+ return retval;
+ }
+
+ void BootupDOMWriter()
+ {
+ NVScopedRefCounted<IStringTable> theStringTable(
+ IStringTable::CreateStringTable(m_RenderContext.GetAllocator()));
+ m_ShaderCache = IDOMWriter::CreateDOMWriter(m_RenderContext.GetAllocator(),
+ "Qt3DSShaderCache", theStringTable)
+ .first;
+ m_ShaderCache->Att("cache_version", IShaderCache::GetShaderVersion());
+ }
+
+ void SetShaderCachePersistenceEnabled(const char8_t *inDirectory) override
+ {
+ if (inDirectory == NULL) {
+ m_ShaderCache = NULL;
+ return;
+ }
+ BootupDOMWriter();
+ m_CacheFilePath = QDir(inDirectory).filePath(GetShaderCacheFileName());
+
+ NVScopedRefCounted<IRefCountedInputStream> theInStream =
+ m_InputStreamFactory.GetStreamForFile(m_CacheFilePath.c_str());
+ if (theInStream) {
+ SStackPerfTimer __perfTimer(m_PerfTimer, "ShaderCache - Load");
+ NVScopedRefCounted<IStringTable> theStringTable(
+ IStringTable::CreateStringTable(m_RenderContext.GetAllocator()));
+ NVScopedRefCounted<IDOMFactory> theFactory(
+ IDOMFactory::CreateDOMFactory(m_RenderContext.GetAllocator(), theStringTable));
+ eastl::vector<SShaderPreprocessorFeature> theFeatures;
+
+ SDOMElement *theElem = CDOMSerializer::Read(*theFactory, *theInStream).second;
+ if (theElem) {
+ NVScopedRefCounted<IDOMReader> theReader = IDOMReader::CreateDOMReader(
+ m_RenderContext.GetAllocator(), *theElem, theStringTable, theFactory);
+ QT3DSU32 theAttValue = 0;
+ theReader->Att("cache_version", theAttValue);
+ if (theAttValue == IShaderCache::GetShaderVersion()) {
+ Qt3DSString loadVertexData;
+ Qt3DSString loadFragmentData;
+ Qt3DSString loadTessControlData;
+ Qt3DSString loadTessEvalData;
+ Qt3DSString loadGeometryData;
+ Qt3DSString shaderTypeString;
+ IStringTable &theStringTable(m_RenderContext.GetStringTable());
+ for (bool success = theReader->MoveToFirstChild(); success;
+ success = theReader->MoveToNextSibling()) {
+ const char8_t *theKeyStr = NULL;
+ theReader->UnregisteredAtt("key", theKeyStr);
+
+ CRegisteredString theKey = theStringTable.RegisterStr(theKeyStr);
+ if (theKey.IsValid()) {
+ m_FlagString.clear();
+ const char8_t *theFlagStr = "";
+ SShaderCacheProgramFlags theFlags;
+ if (theReader->UnregisteredAtt("glflags", theFlagStr)) {
+ m_FlagString.assign(theFlagStr);
+ theFlags = CacheFlagsToStr(m_FlagString);
+ }
+
+ m_ContextTypeString.clear();
+ if (theReader->UnregisteredAtt("gl-context-type", theFlagStr))
+ m_ContextTypeString.assign(theFlagStr);
+
+ theFeatures.clear();
+ {
+ IDOMReader::Scope __featureScope(*theReader);
+ if (theReader->MoveToFirstChild("Features")) {
+ for (SDOMAttribute *theAttribute =
+ theReader->GetFirstAttribute();
+ theAttribute;
+ theAttribute = theAttribute->m_NextAttribute) {
+ bool featureValue = false;
+ StringConversion<bool>().StrTo(theAttribute->m_Value,
+ featureValue);
+ theFeatures.push_back(SShaderPreprocessorFeature(
+ theStringTable.RegisterStr(
+ theAttribute->m_Name.c_str()),
+ featureValue));
+ }
+ }
+ }
+
+ qt3ds::render::NVRenderContextType theContextType =
+ StringToContextType(m_ContextTypeString);
+ if (((QT3DSU32)theContextType != 0)
+ && (theContextType & m_RenderContext.GetRenderContextType())
+ == theContextType) {
+ IDOMReader::Scope __readerScope(*theReader);
+ loadVertexData.clear();
+ loadFragmentData.clear();
+ loadTessControlData.clear();
+ loadTessEvalData.clear();
+ loadGeometryData.clear();
+
+ // Vertex *MUST* be the first
+ // Todo deal with pure compute shader programs
+ if (theReader->MoveToFirstChild("VertexCode")) {
+ const char8_t *theValue = NULL;
+ theReader->Value(theValue);
+ loadVertexData.assign(theValue);
+ while (theReader->MoveToNextSibling()) {
+ theReader->Value(theValue);
+
+ shaderTypeString.assign(
+ theReader->GetElementName().c_str());
+ ShaderType::Enum shaderType =
+ StringToShaderType(shaderTypeString);
+
+ if (shaderType == ShaderType::Fragment)
+ loadFragmentData.assign(theValue);
+ else if (shaderType == ShaderType::TessControl)
+ loadTessControlData.assign(theValue);
+ else if (shaderType == ShaderType::TessEval)
+ loadTessEvalData.assign(theValue);
+ else if (shaderType == ShaderType::Geometry)
+ loadGeometryData.assign(theValue);
+ }
+ }
+
+ if (loadVertexData.size()
+ && (loadFragmentData.size() || loadGeometryData.size())) {
+
+ NVRenderShaderProgram *theShader = ForceCompileProgram(
+ theKey, loadVertexData.c_str(), loadFragmentData.c_str(),
+ loadTessControlData.c_str(), loadTessEvalData.c_str(),
+ loadGeometryData.c_str(), theFlags,
+ qt3ds::foundation::toDataRef(theFeatures.data(),
+ (QT3DSU32)theFeatures.size()),
+ false, true /*fromDisk*/);
+ // If something doesn't save or load correctly, get the runtime
+ // to re-generate.
+ if (!theShader)
+ m_Shaders.erase(theKey);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ bool IsShaderCachePersistenceEnabled() const override { return m_ShaderCache != NULL; }
+
+ void SetShaderCompilationEnabled(bool inEnableShaderCompilation) override
+ {
+ m_ShaderCompilationEnabled = inEnableShaderCompilation;
+ }
+};
+}
+
+size_t qt3ds::render::HashShaderFeatureSet(NVConstDataRef<SShaderPreprocessorFeature> inFeatureSet)
+{
+ size_t retval(0);
+ for (QT3DSU32 idx = 0, end = inFeatureSet.size(); idx < end; ++idx) {
+ // From previous implementation, it seems we need to ignore the order of the features.
+ // But we need to bind the feature flag together with its name, so that the flags will
+ // influence
+ // the final hash not only by the true-value count.
+ retval = retval
+ ^ (inFeatureSet[idx].m_Name.hash() * eastl::hash<bool>()(inFeatureSet[idx].m_Enabled));
+ }
+ return retval;
+}
+
+bool SShaderPreprocessorFeature::operator<(const SShaderPreprocessorFeature &other) const
+{
+ return strcmp(m_Name.c_str(), other.m_Name.c_str()) < 0;
+}
+
+bool SShaderPreprocessorFeature::operator==(const SShaderPreprocessorFeature &other) const
+{
+ return m_Name == other.m_Name && m_Enabled == other.m_Enabled;
+}
+
+IShaderCache &IShaderCache::CreateShaderCache(NVRenderContext &inContext,
+ IInputStreamFactory &inInputStreamFactory,
+ IPerfTimer &inPerfTimer)
+{
+ return *QT3DS_NEW(inContext.GetAllocator(), ShaderCache)(inContext, inInputStreamFactory,
+ inPerfTimer);
+}