1 | // Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qshadernodesloader_p.h" |
5 | |
6 | #include <QtCore/qdebug.h> |
7 | #include <QtCore/qiodevice.h> |
8 | #include <QtCore/qjsonarray.h> |
9 | #include <QtCore/qjsondocument.h> |
10 | #include <QtCore/qjsonobject.h> |
11 | #include <QtCore/qmetaobject.h> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | namespace Qt3DRender |
16 | { |
17 | QShaderNodesLoader::QShaderNodesLoader() noexcept |
18 | : m_status(Null), |
19 | m_device(nullptr) |
20 | { |
21 | } |
22 | |
23 | QShaderNodesLoader::Status QShaderNodesLoader::status() const noexcept |
24 | { |
25 | return m_status; |
26 | } |
27 | |
28 | QHash<QString, QShaderNode> QShaderNodesLoader::nodes() const noexcept |
29 | { |
30 | return m_nodes; |
31 | } |
32 | |
33 | QIODevice *QShaderNodesLoader::device() const noexcept |
34 | { |
35 | return m_device; |
36 | } |
37 | |
38 | void QShaderNodesLoader::setDevice(QIODevice *device) noexcept |
39 | { |
40 | m_device = device; |
41 | m_nodes.clear(); |
42 | m_status = !m_device ? Null |
43 | : (m_device->openMode() & QIODevice::ReadOnly) ? Waiting |
44 | : Error; |
45 | } |
46 | |
47 | void QShaderNodesLoader::load() |
48 | { |
49 | if (m_status == Error) |
50 | return; |
51 | |
52 | auto error = QJsonParseError(); |
53 | const QJsonDocument document = QJsonDocument::fromJson(json: m_device->readAll(), error: &error); |
54 | |
55 | if (error.error != QJsonParseError::NoError) { |
56 | qWarning() << "Invalid JSON document:" << error.errorString(); |
57 | m_status = Error; |
58 | return; |
59 | } |
60 | |
61 | if (document.isEmpty() || !document.isObject()) { |
62 | qWarning() << "Invalid JSON document, root should be an object" ; |
63 | m_status = Error; |
64 | return; |
65 | } |
66 | |
67 | const QJsonObject root = document.object(); |
68 | load(prototypesObject: root); |
69 | } |
70 | |
71 | void QShaderNodesLoader::load(const QJsonObject &prototypesObject) |
72 | { |
73 | bool hasError = false; |
74 | |
75 | for (const QString &property : prototypesObject.keys()) { |
76 | const QJsonValue nodeValue = prototypesObject.value(key: property); |
77 | if (!nodeValue.isObject()) { |
78 | qWarning() << "Invalid node found" ; |
79 | hasError = true; |
80 | break; |
81 | } |
82 | |
83 | const QJsonObject nodeObject = nodeValue.toObject(); |
84 | |
85 | auto node = QShaderNode(); |
86 | |
87 | const QJsonValue inputsValue = nodeObject.value(QStringLiteral("inputs" )); |
88 | if (inputsValue.isArray()) { |
89 | const QJsonArray inputsArray = inputsValue.toArray(); |
90 | for (const QJsonValue inputValue : inputsArray) { |
91 | if (!inputValue.isString()) { |
92 | qWarning() << "Non-string value in inputs" ; |
93 | hasError = true; |
94 | break; |
95 | } |
96 | |
97 | auto input = QShaderNodePort(); |
98 | input.direction = QShaderNodePort::Input; |
99 | input.name = inputValue.toString(); |
100 | node.addPort(port: input); |
101 | } |
102 | } |
103 | |
104 | const QJsonValue outputsValue = nodeObject.value(QStringLiteral("outputs" )); |
105 | if (outputsValue.isArray()) { |
106 | const QJsonArray outputsArray = outputsValue.toArray(); |
107 | for (const QJsonValue outputValue : outputsArray) { |
108 | if (!outputValue.isString()) { |
109 | qWarning() << "Non-string value in outputs" ; |
110 | hasError = true; |
111 | break; |
112 | } |
113 | |
114 | auto output = QShaderNodePort(); |
115 | output.direction = QShaderNodePort::Output; |
116 | output.name = outputValue.toString(); |
117 | node.addPort(port: output); |
118 | } |
119 | } |
120 | |
121 | const QJsonValue parametersValue = nodeObject.value(QStringLiteral("parameters" )); |
122 | if (parametersValue.isObject()) { |
123 | const QJsonObject parametersObject = parametersValue.toObject(); |
124 | for (const QString ¶meterName : parametersObject.keys()) { |
125 | const QJsonValue parameterValue = parametersObject.value(key: parameterName); |
126 | if (parameterValue.isObject()) { |
127 | const QJsonObject parameterObject = parameterValue.toObject(); |
128 | const QString type = parameterObject.value(QStringLiteral("type" )).toString(); |
129 | const QMetaType typeId = QMetaType::fromName(name: type.toUtf8()); |
130 | |
131 | const QString value = parameterObject.value(QStringLiteral("value" )).toString(); |
132 | auto variant = QVariant(value); |
133 | |
134 | if (typeId.flags() & QMetaType::IsEnumeration) { |
135 | const QMetaObject *metaObject = typeId.metaObject(); |
136 | const char *className = metaObject->className(); |
137 | const QByteArray enumName = type.mid(position: static_cast<int>(qstrlen(str: className)) + 2).toUtf8(); |
138 | const QMetaEnum metaEnum = metaObject->enumerator(index: metaObject->indexOfEnumerator(name: enumName)); |
139 | const int enumValue = metaEnum.keyToValue(key: value.toUtf8()); |
140 | variant = QVariant(enumValue); |
141 | variant.convert(type: typeId); |
142 | } else { |
143 | variant.convert(type: typeId); |
144 | } |
145 | node.setParameter(name: parameterName, value: variant); |
146 | } else { |
147 | node.setParameter(name: parameterName, value: parameterValue.toVariant()); |
148 | } |
149 | } |
150 | } |
151 | |
152 | const QJsonValue rulesValue = nodeObject.value(QStringLiteral("rules" )); |
153 | if (rulesValue.isArray()) { |
154 | const QJsonArray rulesArray = rulesValue.toArray(); |
155 | for (const QJsonValue ruleValue : rulesArray) { |
156 | if (!ruleValue.isObject()) { |
157 | qWarning() << "Rules should be objects" ; |
158 | hasError = true; |
159 | break; |
160 | } |
161 | |
162 | const QJsonObject ruleObject = ruleValue.toObject(); |
163 | |
164 | const QJsonValue formatValue = ruleObject.value(QStringLiteral("format" )); |
165 | if (!formatValue.isObject()) { |
166 | qWarning() << "Format is mandatory in rules and should be an object" ; |
167 | hasError = true; |
168 | break; |
169 | } |
170 | |
171 | const QJsonObject formatObject = formatValue.toObject(); |
172 | auto format = QShaderFormat(); |
173 | |
174 | const QJsonValue apiValue = formatObject.value(QStringLiteral("api" )); |
175 | if (!apiValue.isString()) { |
176 | qWarning() << "Format API must be a string" ; |
177 | hasError = true; |
178 | break; |
179 | } |
180 | |
181 | const QString api = apiValue.toString(); |
182 | format.setApi(api == QStringLiteral("OpenGLES" ) ? QShaderFormat::OpenGLES |
183 | : api == QStringLiteral("OpenGLNoProfile" ) ? QShaderFormat::OpenGLNoProfile |
184 | : api == QStringLiteral("OpenGLCoreProfile" ) ? QShaderFormat::OpenGLCoreProfile |
185 | : api == QStringLiteral("OpenGLCompatibilityProfile" ) ? QShaderFormat::OpenGLCompatibilityProfile |
186 | : api == QStringLiteral("VulkanFlavoredGLSL" ) ? QShaderFormat::VulkanFlavoredGLSL |
187 | : api == QStringLiteral("RHI" ) ? QShaderFormat::RHI |
188 | : QShaderFormat::NoApi); |
189 | if (format.api() == QShaderFormat::NoApi) { |
190 | qWarning() << "Format API must be one of: OpenGLES, OpenGLNoProfile, OpenGLCoreProfile OpenGLCompatibilityProfile, VulkanFlavoredGLSL or RHI" ; |
191 | hasError = true; |
192 | break; |
193 | } |
194 | |
195 | const QJsonValue majorValue = formatObject.value(QStringLiteral("major" )); |
196 | const QJsonValue minorValue = formatObject.value(QStringLiteral("minor" )); |
197 | if (!majorValue.isDouble() || !minorValue.isDouble()) { |
198 | qWarning() << "Format major and minor version must be values" ; |
199 | hasError = true; |
200 | break; |
201 | } |
202 | format.setVersion(QVersionNumber(majorValue.toInt(), minorValue.toInt())); |
203 | |
204 | const QJsonValue extensionsValue = formatObject.value(QStringLiteral("extensions" )); |
205 | const QJsonArray extensionsArray = extensionsValue.toArray(); |
206 | auto extensions = QStringList(); |
207 | std::transform(first: extensionsArray.constBegin(), last: extensionsArray.constEnd(), |
208 | result: std::back_inserter(x&: extensions), |
209 | unary_op: [] (const QJsonValue &extensionValue) { return extensionValue.toString(); }); |
210 | format.setExtensions(extensions); |
211 | |
212 | const QString vendor = formatObject.value(QStringLiteral("vendor" )).toString(); |
213 | format.setVendor(vendor); |
214 | |
215 | const QJsonValue substitutionValue = ruleObject.value(QStringLiteral("substitution" )); |
216 | if (!substitutionValue.isString()) { |
217 | qWarning() << "Substitution needs to be a string" ; |
218 | hasError = true; |
219 | break; |
220 | } |
221 | |
222 | // We default out to a Fragment ShaderType if nothing is specified |
223 | // as that was the initial behavior we introduced |
224 | const QString shaderType = formatObject.value(QStringLiteral("shaderType" )).toString(); |
225 | format.setShaderType(shaderType == QStringLiteral("Fragment" ) ? QShaderFormat::Fragment |
226 | : shaderType == QStringLiteral("Vertex" ) ? QShaderFormat::Vertex |
227 | : shaderType == QStringLiteral("TessellationControl" ) ? QShaderFormat::TessellationControl |
228 | : shaderType == QStringLiteral("TessellationEvaluation" ) ? QShaderFormat::TessellationEvaluation |
229 | : shaderType == QStringLiteral("Geometry" ) ? QShaderFormat::Geometry |
230 | : shaderType == QStringLiteral("Compute" ) ? QShaderFormat::Compute |
231 | : QShaderFormat::Fragment); |
232 | |
233 | const QByteArray substitution = substitutionValue.toString().toUtf8(); |
234 | |
235 | // WA for QTBUG-99019 |
236 | const auto wIt = (format.shaderType() == QShaderFormat::Fragment) |
237 | ? ruleObject.constFind(QStringLiteral("headerSnippetsFrag" )) |
238 | : ruleObject.constEnd(); |
239 | const QJsonValue snippetsValue = (wIt != ruleObject.constEnd()) |
240 | ? *wIt |
241 | : ruleObject.value(QStringLiteral("headerSnippets" )); |
242 | const QJsonArray snippetsArray = snippetsValue.toArray(); |
243 | auto snippets = QByteArrayList(); |
244 | std::transform(first: snippetsArray.constBegin(), last: snippetsArray.constEnd(), |
245 | result: std::back_inserter(x&: snippets), |
246 | unary_op: [] (const QJsonValue &snippetValue) { return snippetValue.toString().toUtf8(); }); |
247 | |
248 | node.addRule(format, rule: QShaderNode::Rule(substitution, snippets)); |
249 | } |
250 | } |
251 | |
252 | m_nodes.insert(key: property, value: node); |
253 | } |
254 | |
255 | if (hasError) { |
256 | m_status = Error; |
257 | m_nodes.clear(); |
258 | } else { |
259 | m_status = Ready; |
260 | } |
261 | } |
262 | } |
263 | QT_END_NAMESPACE |
264 | |