1/****************************************************************************
2**
3** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qshadernodesloader_p.h"
41
42#include <QtCore/qdebug.h>
43#include <QtCore/qiodevice.h>
44#include <QtCore/qjsonarray.h>
45#include <QtCore/qjsondocument.h>
46#include <QtCore/qjsonobject.h>
47#include <QtCore/qmetaobject.h>
48
49QT_BEGIN_NAMESPACE
50
51QShaderNodesLoader::QShaderNodesLoader() noexcept
52 : m_status(Null),
53 m_device(nullptr)
54{
55}
56
57QShaderNodesLoader::Status QShaderNodesLoader::status() const noexcept
58{
59 return m_status;
60}
61
62QHash<QString, QShaderNode> QShaderNodesLoader::nodes() const noexcept
63{
64 return m_nodes;
65}
66
67QIODevice *QShaderNodesLoader::device() const noexcept
68{
69 return m_device;
70}
71
72void QShaderNodesLoader::setDevice(QIODevice *device) noexcept
73{
74 m_device = device;
75 m_nodes.clear();
76 m_status = !m_device ? Null
77 : (m_device->openMode() & QIODevice::ReadOnly) ? Waiting
78 : Error;
79}
80
81void QShaderNodesLoader::load()
82{
83 if (m_status == Error)
84 return;
85
86 auto error = QJsonParseError();
87 const QJsonDocument document = QJsonDocument::fromJson(m_device->readAll(), &error);
88
89 if (error.error != QJsonParseError::NoError) {
90 qWarning() << "Invalid JSON document:" << error.errorString();
91 m_status = Error;
92 return;
93 }
94
95 if (document.isEmpty() || !document.isObject()) {
96 qWarning() << "Invalid JSON document, root should be an object";
97 m_status = Error;
98 return;
99 }
100
101 const QJsonObject root = document.object();
102 load(root);
103}
104
105void QShaderNodesLoader::load(const QJsonObject &prototypesObject)
106{
107 bool hasError = false;
108
109 for (const QString &property : prototypesObject.keys()) {
110 const QJsonValue nodeValue = prototypesObject.value(property);
111 if (!nodeValue.isObject()) {
112 qWarning() << "Invalid node found";
113 hasError = true;
114 break;
115 }
116
117 const QJsonObject nodeObject = nodeValue.toObject();
118
119 auto node = QShaderNode();
120
121 const QJsonValue inputsValue = nodeObject.value(QStringLiteral("inputs"));
122 if (inputsValue.isArray()) {
123 const QJsonArray inputsArray = inputsValue.toArray();
124 for (const QJsonValue inputValue : inputsArray) {
125 if (!inputValue.isString()) {
126 qWarning() << "Non-string value in inputs";
127 hasError = true;
128 break;
129 }
130
131 auto input = QShaderNodePort();
132 input.direction = QShaderNodePort::Input;
133 input.name = inputValue.toString();
134 node.addPort(input);
135 }
136 }
137
138 const QJsonValue outputsValue = nodeObject.value(QStringLiteral("outputs"));
139 if (outputsValue.isArray()) {
140 const QJsonArray outputsArray = outputsValue.toArray();
141 for (const QJsonValue outputValue : outputsArray) {
142 if (!outputValue.isString()) {
143 qWarning() << "Non-string value in outputs";
144 hasError = true;
145 break;
146 }
147
148 auto output = QShaderNodePort();
149 output.direction = QShaderNodePort::Output;
150 output.name = outputValue.toString();
151 node.addPort(output);
152 }
153 }
154
155 const QJsonValue parametersValue = nodeObject.value(QStringLiteral("parameters"));
156 if (parametersValue.isObject()) {
157 const QJsonObject parametersObject = parametersValue.toObject();
158 for (const QString &parameterName : parametersObject.keys()) {
159 const QJsonValue parameterValue = parametersObject.value(parameterName);
160 if (parameterValue.isObject()) {
161 const QJsonObject parameterObject = parameterValue.toObject();
162 const QString type = parameterObject.value(QStringLiteral("type")).toString();
163 const auto metaType = QMetaType::fromName(type.toUtf8());
164
165 const QString value = parameterObject.value(QStringLiteral("value")).toString();
166 auto variant = QVariant(value);
167
168 if (metaType.flags() & QMetaType::IsEnumeration) {
169 const QMetaObject *metaObject = metaType.metaObject();
170 const char *className = metaObject->className();
171 const QByteArray enumName = type.mid(static_cast<int>(qstrlen(className)) + 2).toUtf8();
172 const QMetaEnum metaEnum = metaObject->enumerator(metaObject->indexOfEnumerator(enumName));
173 const int enumValue = metaEnum.keyToValue(value.toUtf8());
174 variant = QVariant(enumValue);
175 variant.convert(metaType);
176 } else {
177 variant.convert(metaType);
178 }
179 node.setParameter(parameterName, variant);
180 } else {
181 node.setParameter(parameterName, parameterValue.toVariant());
182 }
183 }
184 }
185
186 const QJsonValue rulesValue = nodeObject.value(QStringLiteral("rules"));
187 if (rulesValue.isArray()) {
188 const QJsonArray rulesArray = rulesValue.toArray();
189 for (const QJsonValue ruleValue : rulesArray) {
190 if (!ruleValue.isObject()) {
191 qWarning() << "Rules should be objects";
192 hasError = true;
193 break;
194 }
195
196 const QJsonObject ruleObject = ruleValue.toObject();
197
198 const QJsonValue formatValue = ruleObject.value(QStringLiteral("format"));
199 if (!formatValue.isObject()) {
200 qWarning() << "Format is mandatory in rules and should be an object";
201 hasError = true;
202 break;
203 }
204
205 const QJsonObject formatObject = formatValue.toObject();
206 auto format = QShaderFormat();
207
208 const QJsonValue apiValue = formatObject.value(QStringLiteral("api"));
209 if (!apiValue.isString()) {
210 qWarning() << "Format API must be a string";
211 hasError = true;
212 break;
213 }
214
215 const QString api = apiValue.toString();
216 format.setApi(api == QStringLiteral("OpenGLES") ? QShaderFormat::OpenGLES
217 : api == QStringLiteral("OpenGLNoProfile") ? QShaderFormat::OpenGLNoProfile
218 : api == QStringLiteral("OpenGLCoreProfile") ? QShaderFormat::OpenGLCoreProfile
219 : api == QStringLiteral("OpenGLCompatibilityProfile") ? QShaderFormat::OpenGLCompatibilityProfile
220 : api == QStringLiteral("VulkanFlavoredGLSL") ? QShaderFormat::VulkanFlavoredGLSL
221 : QShaderFormat::NoApi);
222 if (format.api() == QShaderFormat::NoApi) {
223 qWarning() << "Format API must be one of: OpenGLES, OpenGLNoProfile, OpenGLCoreProfile or OpenGLCompatibilityProfile, VulkanFlavoredGLSL";
224 hasError = true;
225 break;
226 }
227
228 const QJsonValue majorValue = formatObject.value(QStringLiteral("major"));
229 const QJsonValue minorValue = formatObject.value(QStringLiteral("minor"));
230 if (!majorValue.isDouble() || !minorValue.isDouble()) {
231 qWarning() << "Format major and minor version must be values";
232 hasError = true;
233 break;
234 }
235 format.setVersion(QVersionNumber(majorValue.toInt(), minorValue.toInt()));
236
237 const QJsonValue extensionsValue = formatObject.value(QStringLiteral("extensions"));
238 const QJsonArray extensionsArray = extensionsValue.toArray();
239 auto extensions = QStringList();
240 std::transform(extensionsArray.constBegin(), extensionsArray.constEnd(),
241 std::back_inserter(extensions),
242 [] (const QJsonValue &extensionValue) { return extensionValue.toString(); });
243 format.setExtensions(extensions);
244
245 const QString vendor = formatObject.value(QStringLiteral("vendor")).toString();
246 format.setVendor(vendor);
247
248 const QJsonValue substitutionValue = ruleObject.value(QStringLiteral("substitution"));
249 if (!substitutionValue.isString()) {
250 qWarning() << "Substitution needs to be a string";
251 hasError = true;
252 break;
253 }
254
255 // We default out to a Fragment ShaderType if nothing is specified
256 // as that was the initial behavior we introduced
257 const QString shaderType = formatObject.value(QStringLiteral("shaderType")).toString();
258 format.setShaderType(shaderType == QStringLiteral("Fragment") ? QShaderFormat::Fragment
259 : shaderType == QStringLiteral("Vertex") ? QShaderFormat::Vertex
260 : shaderType == QStringLiteral("TessellationControl") ? QShaderFormat::TessellationControl
261 : shaderType == QStringLiteral("TessellationEvaluation") ? QShaderFormat::TessellationEvaluation
262 : shaderType == QStringLiteral("Geometry") ? QShaderFormat::Geometry
263 : shaderType == QStringLiteral("Compute") ? QShaderFormat::Compute
264 : QShaderFormat::Fragment);
265
266 const QByteArray substitution = substitutionValue.toString().toUtf8();
267
268 const QJsonValue snippetsValue = ruleObject.value(QStringLiteral("headerSnippets"));
269 const QJsonArray snippetsArray = snippetsValue.toArray();
270 auto snippets = QByteArrayList();
271 std::transform(snippetsArray.constBegin(), snippetsArray.constEnd(),
272 std::back_inserter(snippets),
273 [] (const QJsonValue &snippetValue) { return snippetValue.toString().toUtf8(); });
274
275 node.addRule(format, QShaderNode::Rule(substitution, snippets));
276 }
277 }
278
279 m_nodes.insert(property, node);
280 }
281
282 if (hasError) {
283 m_status = Error;
284 m_nodes.clear();
285 } else {
286 m_status = Ready;
287 }
288}
289
290QT_END_NAMESPACE
291