1 | // Copyright (C) 2008-2012 NVIDIA Corporation. |
2 | // Copyright (C) 2019 The Qt Company Ltd. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
4 | |
5 | #include "qssgrendershaderlibrarymanager_p.h" |
6 | |
7 | #include <QtQuick3DRuntimeRender/private/qssgrendercontextcore_p.h> |
8 | #include <QtQuick3DRuntimeRender/private/qssgrenderloadedtexture_p.h> |
9 | |
10 | #include <QXmlStreamReader> |
11 | #include <QFileInfo> |
12 | #include <QCryptographicHash> |
13 | |
14 | #include <QtQuick3DRuntimeRender/private/qssgruntimerenderlogging_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | QString QSSGShaderLibraryManager::getShaderCodeLibraryDirectory() |
19 | { |
20 | return QStringLiteral("res/effectlib" ); |
21 | } |
22 | static QByteArray includeSearch() { return QByteArrayLiteral("#include \"" ); }; |
23 | static QByteArray () { return QByteArrayLiteral("/****************************************************************************" ); } |
24 | static QByteArray () { return QByteArrayLiteral("****************************************************************************/" ); } |
25 | |
26 | QSSGShaderLibraryManager::QSSGShaderLibraryManager() {} |
27 | |
28 | QSSGShaderLibraryManager::~QSSGShaderLibraryManager() {} |
29 | |
30 | static inline char stageKey(QSSGShaderCache::ShaderType type) |
31 | { |
32 | switch (type) { |
33 | case QSSGShaderCache::ShaderType::Vertex: |
34 | return 'V'; |
35 | case QSSGShaderCache::ShaderType::Fragment: |
36 | return 'F'; |
37 | default: |
38 | break; |
39 | } |
40 | return '?'; |
41 | } |
42 | |
43 | void QSSGShaderLibraryManager::setShaderSource(const QByteArray &inShaderPathKey, QSSGShaderCache::ShaderType type, |
44 | const QByteArray &inSource, const QSSGCustomShaderMetaData &meta) |
45 | { |
46 | QWriteLocker locker(&m_lock); |
47 | |
48 | const QByteArray perStageKey = stageKey(type) + inShaderPathKey; |
49 | { |
50 | auto it = m_expandedFiles.find(key: perStageKey); |
51 | if (it != m_expandedFiles.end()) |
52 | it.value() = inSource; |
53 | else |
54 | m_expandedFiles.insert(key: perStageKey, value: inSource); |
55 | } |
56 | |
57 | { |
58 | auto it = m_metadata.find(key: perStageKey); |
59 | if (it != m_metadata.end()) |
60 | it.value() = meta; |
61 | else |
62 | m_metadata.insert(key: perStageKey, value: meta); |
63 | } |
64 | } |
65 | |
66 | void QSSGShaderLibraryManager::resolveIncludeFiles(QByteArray &theReadBuffer, const QByteArray &inMaterialInfoString) |
67 | { |
68 | // Now do search and replace for the headers |
69 | for (int thePos = theReadBuffer.indexOf(bv: includeSearch()); thePos != -1; |
70 | thePos = theReadBuffer.indexOf(bv: includeSearch(), from: thePos + 1)) { |
71 | int theEndQuote = theReadBuffer.indexOf(c: '\"', from: thePos + includeSearch().size() + 1); |
72 | // Indicates an unterminated include file. |
73 | if (theEndQuote == -1) { |
74 | qCCritical(INVALID_OPERATION, "Unterminated include in file: %s" , inMaterialInfoString.constData()); |
75 | theReadBuffer.clear(); |
76 | break; |
77 | } |
78 | const int theActualBegin = thePos + includeSearch().size(); |
79 | const auto &theInclude = theReadBuffer.mid(index: theActualBegin, len: theEndQuote - theActualBegin); |
80 | // If we haven't included the file yet this round |
81 | auto contents = getIncludeContents(inShaderPathKey: theInclude); |
82 | // Strip copywrite headers from include if present |
83 | if (contents.startsWith(bv: copyrightHeaderStart())) { |
84 | int clipPos = contents.indexOf(bv: copyrightHeaderEnd()) ; |
85 | if (clipPos >= 0) |
86 | contents.remove(index: 0, len: clipPos + copyrightHeaderEnd().size()); |
87 | } |
88 | // Write insert comment for begin source |
89 | contents.prepend(QByteArrayLiteral("\n// begin \"" ) + theInclude + QByteArrayLiteral("\"\n" )); |
90 | // Write insert comment for end source |
91 | contents.append(QByteArrayLiteral("\n// end \"" ) + theInclude + QByteArrayLiteral("\"\n" )); |
92 | |
93 | theReadBuffer = theReadBuffer.replace(index: thePos, len: (theEndQuote + 1) - thePos, s: contents); |
94 | } |
95 | } |
96 | |
97 | QByteArray QSSGShaderLibraryManager::getIncludeContents(const QByteArray &inShaderPathKey) |
98 | { |
99 | QWriteLocker locker(&m_lock); |
100 | |
101 | auto theInsert = m_expandedFiles.constFind(key: inShaderPathKey); |
102 | const bool found = (theInsert != m_expandedFiles.cend()); |
103 | |
104 | QByteArray theReadBuffer; |
105 | if (!found) { |
106 | const QString defaultDir = getShaderCodeLibraryDirectory(); |
107 | const auto ver = QByteArrayLiteral("rhi" ); |
108 | |
109 | QString fullPath; |
110 | QSharedPointer<QIODevice> theStream; |
111 | QTextStream stream(&fullPath); |
112 | stream << defaultDir << QLatin1Char('/') << ver << QLatin1Char('/') << QString::fromLocal8Bit(ba: inShaderPathKey); |
113 | theStream = QSSGInputUtil::getStreamForFile(inPath: fullPath, inQuiet: true); |
114 | if (theStream.isNull()) { |
115 | fullPath.clear(); |
116 | QTextStream stream(&fullPath); |
117 | stream << defaultDir << QLatin1Char('/') << QString::fromLocal8Bit(ba: inShaderPathKey); |
118 | theStream = QSSGInputUtil::getStreamForFile(inPath: fullPath, inQuiet: false); |
119 | } |
120 | if (!theStream.isNull()) { |
121 | char readBuf[1024]; |
122 | qint64 amountRead = 0; |
123 | do { |
124 | amountRead = theStream->read(data: readBuf, maxlen: 1024); |
125 | if (amountRead) |
126 | theReadBuffer.append(s: readBuf, len: int(amountRead)); |
127 | } while (amountRead); |
128 | } else { |
129 | qCCritical(INVALID_OPERATION, "Failed to find include file %s" , qPrintable(QString::fromLocal8Bit(inShaderPathKey))); |
130 | Q_ASSERT(false); |
131 | } |
132 | theInsert = m_expandedFiles.insert(key: inShaderPathKey, value: theReadBuffer); |
133 | } else { |
134 | theReadBuffer = theInsert.value(); |
135 | } |
136 | |
137 | locker.unlock(); |
138 | resolveIncludeFiles(theReadBuffer, inMaterialInfoString: inShaderPathKey); |
139 | |
140 | return theReadBuffer; |
141 | } |
142 | |
143 | QByteArray QSSGShaderLibraryManager::getShaderSource(const QByteArray &inShaderPathKey, QSSGShaderCache::ShaderType type) |
144 | { |
145 | QReadLocker locker(&m_lock); |
146 | |
147 | const QByteArray perStageKey = stageKey(type) + inShaderPathKey; |
148 | auto it = m_expandedFiles.constFind(key: perStageKey); |
149 | if (it != m_expandedFiles.cend()) |
150 | return it.value(); |
151 | |
152 | qWarning(msg: "No shader source stored for key %s" , perStageKey.constData()); |
153 | return QByteArray(); |
154 | } |
155 | |
156 | QSSGCustomShaderMetaData QSSGShaderLibraryManager::getShaderMetaData(const QByteArray &inShaderPathKey, QSSGShaderCache::ShaderType type) |
157 | { |
158 | QReadLocker locker(&m_lock); |
159 | |
160 | const QByteArray perStageKey = stageKey(type) + inShaderPathKey; |
161 | auto it = m_metadata.constFind(key: perStageKey); |
162 | if (it != m_metadata.cend()) |
163 | return it.value(); |
164 | |
165 | qWarning(msg: "No shader metadata stored for key %s" , perStageKey.constData()); |
166 | return {}; |
167 | } |
168 | |
169 | void QSSGShaderLibraryManager::loadPregeneratedShaderInfo() |
170 | { |
171 | const auto collectionFilePath = QString::fromLatin1(ba: QSSGShaderCache::resourceFolder() + QSSGShaderCache::shaderCollectionFile()); |
172 | QFile file(collectionFilePath); |
173 | if (file.exists()) { |
174 | QQsbIODeviceCollection qsbc(file); |
175 | if (qsbc.map(mode: QQsbIODeviceCollection::Read)) |
176 | m_preGeneratedShaderEntries = qsbc.availableEntries(); |
177 | qsbc.unmap(); |
178 | } |
179 | } |
180 | |
181 | static int calcLightPoint(const QSSGShaderDefaultMaterialKey &key, int i) { |
182 | QSSGShaderDefaultMaterialKeyProperties prop; |
183 | return prop.m_lightFlags[i].getValue(inDataStore: key) + prop.m_lightSpotFlags[i].getValue(inDataStore: key) * 2 |
184 | + prop.m_lightAreaFlags[i].getValue(inDataStore: key) * 4 + prop.m_lightShadowFlags[i].getValue(inDataStore: key) * 8; |
185 | }; |
186 | |
187 | bool QSSGShaderLibraryManager::compare(const QSSGShaderDefaultMaterialKey &key1, const QSSGShaderDefaultMaterialKey &key2) |
188 | { |
189 | QSSGShaderDefaultMaterialKeyProperties props; |
190 | #define COMPARE_PROP(x) \ |
191 | if (props.x.getValue(key1) < props.x.getValue(key2)) return true; |
192 | |
193 | COMPARE_PROP(m_hasLighting) |
194 | COMPARE_PROP(m_hasIbl) |
195 | COMPARE_PROP(m_specularEnabled) |
196 | COMPARE_PROP(m_fresnelEnabled) |
197 | COMPARE_PROP(m_vertexColorsEnabled) |
198 | COMPARE_PROP(m_specularModel) |
199 | COMPARE_PROP(m_vertexAttributes) |
200 | COMPARE_PROP(m_alphaMode) |
201 | |
202 | for (int i = 0; i < QSSGShaderDefaultMaterialKeyProperties::ImageMapCount; i++) { |
203 | COMPARE_PROP(m_imageMaps[i]) |
204 | } |
205 | for (int i = 0; i < QSSGShaderDefaultMaterialKeyProperties::SingleChannelImageCount; i++) { |
206 | COMPARE_PROP(m_textureChannels[i]) |
207 | } |
208 | COMPARE_PROP(m_lightCount) |
209 | for (int i = 0; i < QSSGShaderDefaultMaterialKeyProperties::LightCount; i++) { |
210 | int lp1 = calcLightPoint(key: key1, i); |
211 | int lp2 = calcLightPoint(key: key2, i); |
212 | if (lp1 < lp2) |
213 | return true; |
214 | } |
215 | #undef COMPARE_PROP |
216 | return false; |
217 | } |
218 | |
219 | QT_END_NAMESPACE |
220 | |