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 | |
27 | namespace QSSGRenderShaderMetadata { |
28 | |
29 | const char *shaderMetaStart() { return "#ifdef QQ3D_SHADER_META" ; } |
30 | const char *shaderMetaEnd() { return "#endif" ; } |
31 | |
32 | Uniform::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 | |
43 | QSSGShaderGeneratorStage 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 | |
55 | ShaderMetaData 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 | |