1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qssgrendershadermetadata_p.h"
5
6#include <QPair>
7#include <QJsonDocument>
8#include <QJsonArray>
9#include <QJsonObject>
10#include <QJsonValue>
11
12#include <QtDebug>
13
14// Example snippet based on viewProperties.glsllib
15// The comment in the ifdefed block is necessary to keep weird shader compilers (Vivante) happy.
16
17// #ifdef QQ3D_SHADER_META
18// /*{
19// "uniforms": [ { "type": "mat44", "name": "qt_viewProjectionMatrix" },
20// { "type": "mat4", "name": "qt_viewMatrix" },
21// { "type": "vec2", "name": "qt_cameraProperties" },
22// { "type": "vec3", "name": "qt_cameraPosition", "condition": "!SSAO_CUSTOM_MATERIAL_GLSLLIB" }
23// ]
24// }*/
25// #endif // QQ3D_SHADER_META
26
27namespace QSSGRenderShaderMetadata {
28
29const char *shaderMetaStart() { return "#ifdef QQ3D_SHADER_META"; }
30const char *shaderMetaEnd() { return "#endif"; }
31
32Uniform::Condition Uniform::conditionFromString(const QString &condition)
33{
34 if (condition.isEmpty())
35 return Uniform::Condition::None;
36
37 if (condition.at(i: 0) == QChar::fromLatin1(c: '!'))
38 return Uniform::Negated;
39
40 return Uniform::Regular;
41}
42
43QSSGShaderGeneratorStage InputOutput::stageFromString(const QString &stage)
44{
45 if (stage == QLatin1String("vertex")) {
46 return QSSGShaderGeneratorStage::Vertex;
47 } else if (stage == QLatin1String("fragment"))
48 return QSSGShaderGeneratorStage::Fragment;
49 else {
50 qWarning(msg: "Unknown stage in shader metadata: %s, assuming vertex", qPrintable(stage));
51 return QSSGShaderGeneratorStage::Vertex;
52 }
53}
54
55ShaderMetaData getShaderMetaData(const QByteArray &data)
56{
57 ShaderMetaData result;
58 if (data.isEmpty())
59 return result;
60
61 int jsonStart = 0, jsonEnd = 0;
62 for ( ; ; ) {
63 jsonStart = data.indexOf(bv: shaderMetaStart(), from: jsonEnd);
64 if (jsonStart)
65 jsonEnd = data.indexOf(bv: shaderMetaEnd(), from: jsonStart);
66
67 if (jsonEnd) // adjust start position
68 jsonStart += int(strlen(s: shaderMetaStart()));
69
70 if (jsonStart <= 0 || jsonEnd <= 0)
71 break;
72
73 const int size = jsonEnd - jsonStart;
74 // /*{"inputs":[]}*/ => 17
75 if (size < 17) {
76 qWarning(msg: "Shader metadata section found, but content to small to be valid!");
77 break;
78 }
79
80 QByteArray jsonData = data.mid(index: jsonStart, len: size).trimmed();
81 if (!jsonData.startsWith(QByteArrayLiteral("/*{"))) {
82 qWarning(msg: "Missing /*{ prefix");
83 break;
84 }
85 if (!jsonData.endsWith(QByteArrayLiteral("}*/"))) {
86 qWarning(msg: "Missing }*/ suffix");
87 break;
88 }
89 jsonData = jsonData.mid(index: 2, len: jsonData.size() - 4);
90
91 QJsonParseError error;
92 const auto doc = QJsonDocument::fromJson(json: jsonData, error: &error);
93 if (error.error != QJsonParseError::NoError) {
94 qWarning() << "Shader metadata parse error at offset: " << error.offset;
95 break;
96 }
97
98 static const auto toUniform = [](const QJsonObject &uObj) {
99 Uniform uniform;
100 auto it = uObj.constBegin();
101 const auto end = uObj.constEnd();
102 if (it != end) {
103 it = uObj.constFind(key: QLatin1String("type"));
104 uniform.type = (it != end) ? it->toString().toLatin1() : QByteArray();
105 it = uObj.constFind(key: QLatin1String("name"));
106 uniform.name = (it != end) ? it->toString().toLatin1() : QByteArray();
107
108 it = uObj.constFind(key: QLatin1String("condition"));
109 const QString conditionString = (it != end) ? it->toString() : QString();
110 uniform.condition = Uniform::conditionFromString(condition: conditionString);
111 if (uniform.condition == Uniform::Negated)
112 uniform.conditionName = conditionString.mid(position: 1).toLatin1();
113 else if (uniform.condition == Uniform::Regular)
114 uniform.conditionName = conditionString.toLatin1();
115 }
116 return uniform;
117 };
118
119 static const auto toInputOutput = [](const QJsonObject &uObj) {
120 InputOutput inOutVar;
121 auto it = uObj.constBegin();
122 const auto end = uObj.constEnd();
123 if (it != end) {
124 it = uObj.constFind(key: QLatin1String("type"));
125 inOutVar.type = (it != end) ? it->toString().toLatin1() : QByteArray();
126 it = uObj.constFind(key: QLatin1String("name"));
127 inOutVar.name = (it != end) ? it->toString().toLatin1() : QByteArray();
128 it = uObj.constFind(key: QLatin1String("stage"));
129 inOutVar.stage = InputOutput::stageFromString(stage: (it != end) ? it->toString() : QString());
130 }
131 return inOutVar;
132 };
133
134 const QJsonObject obj = doc.object();
135 auto it = obj.constBegin();
136 const auto end = obj.constEnd();
137 if (it != end) {
138 // Uniforms
139 it = obj.constFind(key: QLatin1String("uniforms"));
140 if (it != obj.constEnd()) {
141 // Check if it's an array or a single object
142 if (it->type() == QJsonValue::Array) {
143 const auto uniformArray = it->toArray();
144 for (const auto valueRef : uniformArray) {
145 if (!valueRef.isObject())
146 continue;
147
148 const QJsonObject obj = valueRef.toObject();
149 const auto uniform = toUniform(obj);
150 if (!uniform.type.isEmpty() && !uniform.name.isEmpty()) {
151 result.uniforms.push_back(t: uniform);
152 } else {
153 qWarning(msg: "Invalid uniform, skipping");
154 }
155 }
156 } else if (it->type() == QJsonValue::Object) {
157 const auto uniform = toUniform(it->toObject());
158 if (!uniform.type.isEmpty() && !uniform.name.isEmpty())
159 result.uniforms.push_back(t: uniform);
160 else
161 qWarning(msg: "Invalid uniform, skipping");
162 }
163 }
164
165 // Inputs
166 it = obj.constFind(key: QLatin1String("inputs"));
167 if (it != end) {
168 if (it->type() == QJsonValue::Array) {
169 for (const auto valueRef : it->toArray()) {
170 if (!valueRef.isObject())
171 continue;
172 const auto inOutVar = toInputOutput(valueRef.toObject());
173 if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty())
174 result.inputs.push_back(t: inOutVar);
175 else
176 qWarning(msg: "Invalid input variable, skipping");
177 }
178 } else if (it->type() == QJsonValue::Object) {
179 const QJsonObject obj = it->toObject();
180 const auto inOutVar = toInputOutput(obj);
181 if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty()) {
182 result.inputs.push_back(t: inOutVar);
183 } else {
184 qWarning(msg: "Invalid input variable, skipping");
185 }
186 }
187 }
188
189 // Outputs
190 it = obj.constFind(key: QLatin1String("outputs"));
191 if (it != end) {
192 if (it->type() == QJsonValue::Array) {
193 for (const auto valueRef : it->toArray()) {
194 if (!valueRef.isObject())
195 continue;
196 const auto inOutVar = toInputOutput(valueRef.toObject());
197 if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty())
198 result.outputs.push_back(t: inOutVar);
199 else
200 qWarning(msg: "Invalid output variable, skipping");
201 }
202 } else if (it->type() == QJsonValue::Object) {
203 const QJsonObject inputJObj = it->toObject();
204 const auto inOutVar = toInputOutput(inputJObj);
205 if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty()) {
206 result.outputs.push_back(t: inOutVar);
207 } else {
208 qWarning(msg: "Invalid output variable, skipping");
209 }
210 }
211 }
212 }
213 }
214
215 return result;
216}
217
218} // namespace
219

source code of qtquick3d/src/runtimerender/qssgrendershadermetadata.cpp