blob: 463bdfde2b89f8020720d9957ce68fead18b8701 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2008-2012 NVIDIA Corporation.
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Quick 3D.
**
** $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 "qssgrendershadercache_p.h"
#include <QtQuick3DUtils/private/qssgutils_p.h>
#include <QtQuick3DRender/private/qssgrendercontext_p.h>
#include <QtQuick3DRender/private/qssgrendershaderprogram_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderinputstreamfactory_p.h>
#include <QtQuick3DRuntimeRender/private/qssgrenderer_p.h>
#include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h>
#include <QtCore/QRegularExpression>
#include <QtCore/QString>
QT_BEGIN_NAMESPACE
namespace {
// using QSSGRenderContextScopedProperty;
// const char *TessellationEnabledStr = "TessellationStageEnabled";
// const char *GeometryEnabledStr = "GeometryStageEnabled";
// inline void AppendFlagValue(QString &inStr, const char *flag)
//{
// if (inStr.length())
// inStr.append(QStringLiteral(","));
// inStr.append(QString::fromLocal8Bit(flag));
//}
// inline void CacheFlagsToStr(const QSSGShaderCacheProgramFlags &inFlags, QString &inString)
//{
// inString.clear();
// if (inFlags.isTessellationEnabled())
// AppendFlagValue(inString, TessellationEnabledStr);
// if (inFlags.isGeometryShaderEnabled())
// AppendFlagValue(inString, GeometryEnabledStr);
//}
// inline ShaderType StringToShaderType(QString &inShaderType)
//{
// ShaderType 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
// Q_ASSERT(false);
// return retval;
//}
// inline QSSGShaderCacheProgramFlags CacheFlagsToStr(const QString &inString)
//{
// QSSGShaderCacheProgramFlags retval;
// if (inString.contains(QString::fromLocal8Bit(TessellationEnabledStr)))
// retval.setTessellationEnabled(true);
// if (inString.contains(QString::fromLocal8Bit(GeometryEnabledStr)))
// retval.setGeometryShaderEnabled(true);
// return retval;
//}
// typedef QPair<const char *, QSSGRenderContextValues> TStringToContextValuePair;
/*GLES2 = 1 << 0,
GL2 = 1 << 1,
GLES3 = 1 << 2,
GL3 = 1 << 3,
GL4 = 1 << 4,
NullContext = 1 << 5,*/
// TStringToContextValuePair g_StringToContextTypeValue[] = {
// TStringToContextValuePair("GLES2", QSSGRenderContextValues::GLES2),
// TStringToContextValuePair("GL2", QSSGRenderContextValues::GL2),
// TStringToContextValuePair("GLES3", QSSGRenderContextValues::GLES3),
// TStringToContextValuePair("GLES3PLUS", QSSGRenderContextValues::GLES3PLUS),
// TStringToContextValuePair("GL3", QSSGRenderContextValues::GL3),
// TStringToContextValuePair("GL4", QSSGRenderContextValues::GL4),
// TStringToContextValuePair("NullContext", QSSGRenderContextValues::NullContext),
//};
// size_t g_NumStringToContextValueEntries =
// sizeof(g_StringToContextTypeValue) / sizeof(*g_StringToContextTypeValue);
// inline void ContextTypeToString(QSSGRenderContextType inType,
// QString &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(QString::fromLocal8Bit(g_StringToContextTypeValue[idx].first));
// }
// }
//}
// inline QSSGRenderContextType StringToContextType(const QString &inContextType)
//{
// QSSGRenderContextType 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('|', lastpos));
// if (pos == -1)
// pos = int(inContextType.size());
// {
// size_t sectionLen = size_t(qMin(pos - lastpos, lastTempBufIdx));
// ::memcpy(tempBuffer, inContextType.data() + 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 != -1);
// return retval;
//}
}
static QByteArray defaultShaderPrecision(const QByteArray &defPrecision)
{
static const QByteArray precision = qEnvironmentVariable("QT_QUICK3D_SHADER_PRECISION").toLatin1();
if (precision.isEmpty() || (precision != QByteArrayLiteral("mediump")
&& precision != QByteArrayLiteral("lowp")
&& precision != QByteArrayLiteral("highp"))) {
return defPrecision;
}
return precision;
}
static QByteArray defaultSamplerPrecision(const QByteArray &defPrecision)
{
static const QByteArray samplerPrecision = qEnvironmentVariable("QT_QUICK3D_SAMPLER_PRECISION").toLatin1();
if (samplerPrecision.isEmpty() || (samplerPrecision != QByteArrayLiteral("mediump")
&& samplerPrecision != QByteArrayLiteral("lowp")
&& samplerPrecision != QByteArrayLiteral("highp"))) {
return defPrecision;
}
return samplerPrecision;
}
static const char *defineTable[QSSGShaderDefines::Count] {
"QSSG_ENABLE_LIGHT_PROBE",
"QSSG_ENABLE_LIGHT_PROBE_2",
"QSSG_ENABLE_IBL_FOV",
"QSSG_ENABLE_SSM",
"QSSG_ENABLE_SSAO",
"QSSG_ENABLE_SSDO",
"QSSG_ENABLE_CG_LIGHTING"
};
const char *QSSGShaderDefines::asString(QSSGShaderDefines::Define def) { return defineTable[def]; }
uint qHash(const QSSGShaderCacheKey &key)
{
return key.m_hashCode;
}
uint hashShaderFeatureSet(const ShaderFeatureSetList &inFeatureSet)
{
uint retval(0);
for (int 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 ^= (inFeatureSet.at(idx).key ^ uint(inFeatureSet.at(idx).enabled));
}
return retval;
}
QSSGShaderCache::~QSSGShaderCache() {}
QSSGRef<QSSGShaderCache> QSSGShaderCache::createShaderCache(const QSSGRef<QSSGRenderContext> &inContext,
const QSSGRef<QSSGInputStreamFactory> &inInputStreamFactory,
QSSGPerfTimer *inPerfTimer)
{
return QSSGRef<QSSGShaderCache>(new QSSGShaderCache(inContext, inInputStreamFactory, inPerfTimer));
}
QSSGShaderCache::QSSGShaderCache(const QSSGRef<QSSGRenderContext> &ctx, const QSSGRef<QSSGInputStreamFactory> &inInputStreamFactory, QSSGPerfTimer *)
: m_renderContext(ctx), /*m_perfTimer(inPerfTimer),*/ m_inputStreamFactory(inInputStreamFactory), m_shaderCompilationEnabled(true)
{
}
QSSGRef<QSSGRenderShaderProgram> QSSGShaderCache::getProgram(const QByteArray &inKey, const ShaderFeatureSetList &inFeatures)
{
m_tempKey.m_key = inKey;
m_tempKey.m_features = inFeatures;
m_tempKey.generateHashCode();
const auto theIter = m_shaders.constFind(m_tempKey);
if (theIter != m_shaders.cend())
return theIter.value();
return nullptr;
}
void QSSGShaderCache::addBackwardCompatibilityDefines(ShaderType 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->supportsAdvancedBlendHwKHR())
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 QSSGShaderCache::addShaderExtensionStrings(ShaderType shaderType, bool isGLES)
{
if (isGLES) {
if (m_renderContext->supportsStandardDerivatives())
m_insertStr += "#extension GL_OES_standard_derivatives : enable\n";
else
m_insertStr += "#extension GL_OES_standard_derivatives : disable\n";
}
if (QSSGRendererInterface::isGlEs3Context(m_renderContext->renderContextType())) {
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->renderBackendCap(QSSGRenderBackend::QSSGRenderBackendCaps::gpuShader5))
m_insertStr += "#extension GL_EXT_gpu_shader5 : enable\n";
if (m_renderContext->supportsAdvancedBlendHwKHR())
m_insertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
}
} else {
if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment || shaderType == ShaderType::Geometry) {
if (m_renderContext->renderContextType() != QSSGRenderContextType::GLES2) {
m_insertStr += "#extension GL_ARB_gpu_shader5 : enable\n";
// m_insertStr += "#extension GL_ARB_shading_language_420pack : enable\n";
}
if (isGLES && m_renderContext->supportsTextureLod())
m_insertStr += "#extension GL_EXT_shader_texture_lod : enable\n";
if (m_renderContext->supportsShaderImageLoadStore())
m_insertStr += "#extension GL_ARB_shader_image_load_store : enable\n";
if (m_renderContext->supportsStorageBuffer())
m_insertStr += "#extension GL_ARB_shader_storage_buffer_object : enable\n";
if (m_renderContext->supportsAdvancedBlendHwKHR())
m_insertStr += "#extension GL_KHR_blend_equation_advanced : enable\n";
}
}
}
void QSSGShaderCache::addShaderPreprocessor(QByteArray &str, const QByteArray &inKey, ShaderType shaderType, const ShaderFeatureSetList &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.
const auto contextType = m_renderContext->renderContextType();
const bool isGlES = QSSGRendererInterface::isGlEsContext(contextType);
m_insertStr.clear();
m_insertStr.append(m_renderContext->shadingLanguageVersion());
if (inFeatures.size()) {
for (int idx = 0, end = inFeatures.size(); idx < end; ++idx) {
QSSGShaderPreprocessorFeature feature(inFeatures[idx]);
m_insertStr.append("#define ");
m_insertStr.append(inFeatures[idx].name);
m_insertStr.append(" ");
m_insertStr.append(feature.enabled ? "1" : "0");
m_insertStr.append("\n");
}
}
if (isGlES) {
if (!QSSGRendererInterface::isGlEs3Context(contextType)) {
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 (QSSGRendererInterface::isGlEs3Context(contextType)) {
const QByteArray precision = defaultShaderPrecision(QByteArrayLiteral("highp"));
const QByteArray samplerPrecision = defaultSamplerPrecision(QByteArrayLiteral("mediump"));
QByteArray precisionQualifiers = "precision " + precision + " float;\n";
precisionQualifiers += "precision " + precision + " int;\n";
m_insertStr.append(precisionQualifiers);
if (m_renderContext->renderBackendCap(QSSGRenderBackend::QSSGRenderBackendCaps::gpuShader5)) {
precisionQualifiers = "precision " + samplerPrecision + " sampler2D;\n";
precisionQualifiers += "precision " + samplerPrecision + " sampler2DArray;\n";
precisionQualifiers += "precision " + samplerPrecision + " sampler2DShadow;\n";
m_insertStr.append(precisionQualifiers);
if (m_renderContext->supportsShaderImageLoadStore()) {
precisionQualifiers = "precision " + samplerPrecision + " image2D;\n";
m_insertStr.append(precisionQualifiers);
}
}
addBackwardCompatibilityDefines(shaderType);
} else {
// GLES2
const QByteArray precision = defaultShaderPrecision(QByteArrayLiteral("mediump"));
QByteArray precisionQualifiers = "precision " + precision + " float;\n";
precisionQualifiers += "precision " + precision + " int;\n";
m_insertStr.append(precisionQualifiers);
m_insertStr.append("#define texture texture2D\n");
if (m_renderContext->supportsTextureLod())
m_insertStr.append("#define textureLod texture2DLodEXT\n");
else
m_insertStr.append("#define textureLod(s, co, lod) texture2D(s, co)\n");
}
} else {
if (!QSSGRendererInterface::isGl2Context(contextType)) {
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.isNull()) {
m_insertStr += "//Shader name -";
m_insertStr += inKey;
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);
}
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)
{
if (!m_shaderCompilationEnabled)
return nullptr;
QSSGShaderCacheKey tempKey(inKey);
tempKey.m_features = inFeatures;
tempKey.generateHashCode();
if (fromDisk) {
qCInfo(TRACE_INFO) << "Loading from persistent shader cache: '<" << tempKey.m_key << ">'";
} else {
qCInfo(TRACE_INFO) << "Compiling into shader cache: '" << tempKey.m_key << ">'";
}
// SStackPerfTimer __perfTimer(m_PerfTimer, "Shader Compilation");
m_vertexCode = inVert;
m_tessCtrlCode = inTessCtrl;
m_tessEvalCode = inTessEval;
m_geometryCode = inGeom;
m_fragmentCode = 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 & ShaderCacheProgramFlagValues::TessellationEnabled) {
Q_ASSERT(m_tessCtrlCode.size() && m_tessEvalCode.size());
addShaderPreprocessor(m_tessCtrlCode, inKey, ShaderType::TessControl, inFeatures);
addShaderPreprocessor(m_tessEvalCode, inKey, ShaderType::TessEval, inFeatures);
}
if (inFlags & ShaderCacheProgramFlagValues::GeometryShaderEnabled)
addShaderPreprocessor(m_geometryCode, inKey, ShaderType::Geometry, inFeatures);
auto shaderProgram = m_renderContext->compileSource(inKey.constData(),
toByteView(m_vertexCode),
toByteView(m_fragmentCode),
toByteView(m_tessCtrlCode),
toByteView(m_tessEvalCode),
toByteView(m_geometryCode),
separableProgram).m_shader;
const auto inserted = m_shaders.insert(tempKey, shaderProgram);
if (shaderProgram) {
// This is unnecessary memory waste in final deployed product, so we don't store this
// information when shaders were initialized from a cache.
// Unfortunately it is not practical to just regenerate shader source from scratch, when we
// want to export it, as the triggers and original sources are spread all over the place.
if (!m_shadersInitializedFromCache && inserted != m_shaders.end()) {
// Store sources for possible cache generation later
QSSGShaderSource ss;
for (int i = 0, end = inFeatures.size(); i < end; ++i)
ss.features.append(inFeatures[i]);
ss.key = inKey;
ss.flags = inFlags;
ss.vertexCode = inVert;
ss.fragmentCode = inFrag;
ss.tessCtrlCode = inTessCtrl;
ss.tessEvalCode = inTessEval;
ss.geometryCode = inGeom;
m_shaderSourceCache.append(ss);
}
// ### Shader Chache Writing Code is disabled
// if (m_ShaderCache) {
// IDOMWriter::Scope __writeScope(*m_ShaderCache, "Program");
// m_ShaderCache->Att("key", inKey.toLocal8Bit().constData());
// CacheFlagsToStr(inFlags, m_FlagString);
// if (m_FlagString.size())
// m_ShaderCache->Att("glflags", m_FlagString.toLocal8Bit().constData());
// // write out the GL version.
// {
// QSSGRenderContextType theContextType =
// m_RenderContext.GetRenderContextType();
// ContextTypeToString(theContextType, m_ContextTypeString);
// m_ShaderCache->Att("gl-context-type", m_ContextTypeString.toLocal8Bit().constData());
// }
// if (inFeatures.size()) {
// IDOMWriter::Scope __writeScope(*m_ShaderCache, "Features");
// for (int 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 inserted.value();
}
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)
{
const QSSGRef<QSSGRenderShaderProgram> &theProgram = getProgram(inKey, inFeatures);
if (theProgram)
return theProgram;
const QSSGRef<QSSGRenderShaderProgram> &retval = forceCompileProgram(inKey, inVert, inFrag, inTessCtrl, inTessEval, inGeom, inFlags, inFeatures, separableProgram);
// ### Shader Chache Writing Code is disabled
// if (m_CacheFilePath.toLocal8Bit().constData() && m_ShaderCache && m_ShaderCompilationEnabled) {
// CFileSeekableIOStream theStream(m_CacheFilePath.toLocal8Bit().constData(), FileWriteFlags());
// if (theStream.IsOpen()) {
// CDOMSerializer::WriteXMLHeader(theStream);
// CDOMSerializer::Write(*m_ShaderCache->GetTopElement(), theStream);
// }
// }
return retval;
}
void QSSGShaderCache::setShaderCachePersistenceEnabled(const QString &inDirectory)
{
// ### Shader Chache Writing Code is disabled
Q_UNUSED(inDirectory)
// if (inDirectory == nullptr) {
// m_ShaderCache = nullptr;
// return;
// }
// BootupDOMWriter();
// m_CacheFilePath = QDir(inDirectory).filePath(GetShaderCacheFileName()).toStdString();
// QSSGRef<IRefCountedInputStream> theInStream =
// m_InputStreamFactory.GetStreamForFile(m_CacheFilePath.c_str());
// if (theInStream) {
// SStackPerfTimer __perfTimer(m_PerfTimer, "ShaderCache - Load");
// QSSGRef<IDOMFactory> theFactory(
// IDOMFactory::CreateDOMFactory(m_RenderContext.GetAllocator(), theStringTable));
// QVector<SShaderPreprocessorFeature> theFeatures;
// SDOMElement *theElem = CDOMSerializer::Read(*theFactory, *theInStream).second;
// if (theElem) {
// QSSGRef<IDOMReader> theReader = IDOMReader::CreateDOMReader(
// m_RenderContext.GetAllocator(), *theElem, theStringTable, theFactory);
// quint32 theAttValue = 0;
// theReader->Att("cache_version", theAttValue);
// if (theAttValue == IShaderCache::GetShaderVersion()) {
// QString loadVertexData;
// QString loadFragmentData;
// QString loadTessControlData;
// QString loadTessEvalData;
// QString loadGeometryData;
// QString shaderTypeString;
// for (bool success = theReader->MoveToFirstChild(); success;
// success = theReader->MoveToNextSibling()) {
// const char *theKeyStr = nullptr;
// theReader->UnregisteredAtt("key", theKeyStr);
// QString theKey = QString::fromLocal8Bit(theKeyStr);
// if (theKey.IsValid()) {
// m_FlagString.clear();
// const char *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(
// QString::fromLocal8Bit(
// theAttribute->m_Name.c_str()),
// featureValue));
// }
// }
// }
// QSSGRenderContextType theContextType =
// StringToContextType(m_ContextTypeString);
// if (((quint32)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 char *theValue = nullptr;
// theReader->Value(theValue);
// loadVertexData.assign(theValue);
// while (theReader->MoveToNextSibling()) {
// theReader->Value(theValue);
// shaderTypeString.assign(
// theReader->GetElementName().c_str());
// ShaderType 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())) {
// QSSGRef<QSSGRenderShaderProgram> theShader = ForceCompileProgram(
// theKey, loadVertexData.toLocal8Bit().constData(),
// loadFragmentData.toLocal8Bit().constData(), loadTessControlData.toLocal8Bit().constData(),
// loadTessEvalData.toLocal8Bit().constData(), loadGeometryData.toLocal8Bit().constData(),
// theFlags, theFeatures, false, true /*fromDisk*/);
// // If something doesn't save or load correctly, get the runtime
// // to re-generate.
// if (!theShader)
// m_Shaders.remove(theKey);
// }
// }
// }
// }
// }
// }
// }
}
bool QSSGShaderCache::isShaderCachePersistenceEnabled() const
{
// ### Shader Chache Writing Code is disabled
// return m_ShaderCache != nullptr;
return false;
}
void QSSGShaderCache::setShaderCompilationEnabled(bool inEnableShaderCompilation)
{
m_shaderCompilationEnabled = inEnableShaderCompilation;
}
quint32 QSSGShaderCache::shaderCacheVersion() const
{
return 1;
}
quint32 QSSGShaderCache::shaderCacheFileId() const
{
return 0x26a9b358;
}
void QSSGShaderCache::importShaderCache(const QByteArray &shaderCache, QByteArray &errors)
{
#define BAILOUT(details) { \
QByteArray errorMsg = QByteArrayLiteral("importShaderCache failed to import shader cache: " details); \
qWarning() << errorMsg; \
errors.append(errorMsg); \
return; \
}
if (shaderCache.isEmpty())
BAILOUT("Shader cache Empty")
QDataStream data(shaderCache);
quint32 type;
quint32 version;
bool isBinary;
data >> type;
auto binaryShadersSupported = [this]() -> bool {
return !(m_renderContext->format().renderableType() == QSurfaceFormat::OpenGLES
&& m_renderContext->format().majorVersion() == 2);
};
if (type != shaderCacheFileId())
BAILOUT("Not a shader cache")
data >> isBinary;
if (isBinary && !binaryShadersSupported())
BAILOUT("Binary shaders are not supported")
data >> version;
if (version != shaderCacheVersion())
BAILOUT("Version mismatch")
#undef BAILOUT
int progCount;
data >> progCount;
m_shadersInitializedFromCache = progCount > 0;
for (int i = 0; i < progCount; ++i) {
QByteArray key;
int featCount;
data >> key;
data >> featCount;
ShaderFeatureSetList features;
for (int j = 0; j < featCount; ++j) {
QByteArray featName;
bool featVal;
data >> featName;
data >> featVal;
features.push_back(QSSGShaderPreprocessorFeature(featName, featVal));
}
QSSGRef<QSSGRenderShaderProgram> theShader;
QSSGShaderCacheKey tempKey(key);
tempKey.m_features = features;
tempKey.generateHashCode();
if (isBinary) {
quint32 format;
QByteArray binary;
data >> format;
data >> binary;
qCInfo(TRACE_INFO) << "Loading binary program from shader cache: '<" << key << ">'";
QSSGRenderVertFragCompilationResult result = m_renderContext->compileBinary(key, format, binary);
theShader = result.m_shader;
if (theShader.isNull())
errors += theShader->errorMessage();
else
m_shaders.insert(tempKey, theShader);
} else {
QByteArray loadVertexData;
QByteArray loadFragmentData;
QByteArray loadTessControlData;
QByteArray loadTessEvalData;
QByteArray loadGeometryData;
data >> loadVertexData;
data >> loadFragmentData;
data >> loadTessControlData;
data >> loadTessEvalData;
data >> loadGeometryData;
if (!loadVertexData.isEmpty() && (!loadFragmentData.isEmpty()
|| !loadGeometryData.isEmpty())) {
QByteArray error;
QSSGRenderVertFragCompilationResult result
= m_renderContext->compileSource(key, QSSGByteView(loadVertexData), QSSGByteView(loadFragmentData),
QSSGByteView(loadTessControlData), QSSGByteView(loadTessControlData),
QSSGByteView(loadGeometryData));
theShader = result.m_shader;
if (theShader.isNull())
errors += theShader->errorMessage();
else
m_shaders.insert(tempKey, theShader);
}
}
// If something doesn't save or load correctly, get the runtime to re-generate.
if (theShader.isNull()) {
qWarning() << __FUNCTION__ << "Failed to load a cached a shader:" << key;
m_shadersInitializedFromCache = false;
}
}
}
QByteArray QSSGShaderCache::exportShaderCache(bool binaryShaders)
{
if (m_shadersInitializedFromCache) {
qWarning() << __FUNCTION__ << "Warning: Shader cache export is not supported when"
" shaders were originally imported from a cache file.";
return {};
}
auto binaryShadersSupported = [this]() -> bool {
return !(m_renderContext->format().renderableType() == QSurfaceFormat::OpenGLES
&& m_renderContext->format().majorVersion() == 2);
};
// The assumption is that cache was generated on the same environment it will be read.
// Attempting to load a cache generated on another environment will likely lead to crash.
QByteArray retval;
QDataStream data(&retval, QIODevice::WriteOnly);
bool saveBinary = binaryShaders && binaryShadersSupported();
data << shaderCacheFileId();
data << saveBinary;
data << shaderCacheVersion();
data << m_shaderSourceCache.size();
for (const auto &ss : qAsConst(m_shaderSourceCache))
{
data << ss.key;
data << ss.features.size();
for (int i = 0, end = ss.features.size(); i < end; ++i) {
data << ss.features[i].name;
data << ss.features[i].enabled;
}
if (saveBinary) {
auto program = getProgram(ss.key, ss.features);
quint32 format = 0;
QByteArray binaryData;
program->getProgramBinary(format, binaryData);
data << format;
data << binaryData;
} else {
m_vertexCode = ss.vertexCode;
m_tessCtrlCode = ss.tessCtrlCode;
m_tessEvalCode = ss.tessEvalCode;
m_geometryCode = ss.geometryCode;
m_fragmentCode = ss.fragmentCode;
// Add defines and such so we can write unified shaders that work across platforms.
// vertex and fragment shaders are optional for separable shaders
if (m_vertexCode.size())
addShaderPreprocessor(m_vertexCode, ss.key, ShaderType::Vertex, ss.features);
if (m_fragmentCode.size())
addShaderPreprocessor(m_fragmentCode, ss.key, ShaderType::Fragment, ss.features);
// optional shaders
if (m_tessCtrlCode.size() && m_tessEvalCode.size()) {
addShaderPreprocessor(m_tessCtrlCode, ss.key, ShaderType::TessControl, ss.features);
addShaderPreprocessor(m_tessEvalCode, ss.key, ShaderType::TessEval, ss.features);
}
if (m_geometryCode.size())
addShaderPreprocessor(m_geometryCode, ss.key, ShaderType::Geometry, ss.features);
auto writeShaderElement = [&data](const QByteArray &shaderSource) {
QByteArray stripped = shaderSource;
int start = stripped.indexOf(QByteArrayLiteral("/*"));
while (start != -1) {
int end = stripped.indexOf(QByteArrayLiteral("*/"));
if (end == -1)
break; // Mismatched comment
stripped.replace(start, end - start + 2, QByteArray());
start = stripped.indexOf(QByteArrayLiteral("/*"));
}
data << stripped;
};
writeShaderElement(m_vertexCode);
writeShaderElement(m_fragmentCode);
writeShaderElement(m_tessCtrlCode);
writeShaderElement(m_tessEvalCode);
writeShaderElement(m_geometryCode);
}
}
return retval;
}
QT_END_NAMESPACE