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
13QT_BEGIN_NAMESPACE
14
15namespace Qt3DRender
16{
17QShaderNodesLoader::QShaderNodesLoader() noexcept
18 : m_status(Null),
19 m_device(nullptr)
20{
21}
22
23QShaderNodesLoader::Status QShaderNodesLoader::status() const noexcept
24{
25 return m_status;
26}
27
28QHash<QString, QShaderNode> QShaderNodesLoader::nodes() const noexcept
29{
30 return m_nodes;
31}
32
33QIODevice *QShaderNodesLoader::device() const noexcept
34{
35 return m_device;
36}
37
38void 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
47void 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
71void 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 &parameterName : 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}
263QT_END_NAMESPACE
264

source code of qt3d/src/render/shadergraph/qshadernodesloader.cpp