Revision control
Copy as Markdown
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qssgrendershadermetadata_p.h"
#include <QPair>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QtDebug>
// Example snippet
// The comment in the ifdefed block is necessary to keep weird shader compilers (Vivante) happy.
// #ifdef QQ3D_SHADER_META
// /*{
// "uniforms": [ { "type": "mat44", "name": "qt_viewProjectionMatrix" },
// { "type": "mat4", "name": "qt_viewMatrix" },
// { "type": "vec2", "name": "qt_cameraProperties" },
// { "type": "vec3", "name": "qt_cameraPosition", "condition": "!SSAO_CUSTOM_MATERIAL_GLSLLIB" }
// ]
// }*/
// #endif // QQ3D_SHADER_META
QT_BEGIN_NAMESPACE
namespace QSSGRenderShaderMetadata {
const char *shaderMetaStart() { return "#ifdef QQ3D_SHADER_META"; }
const char *shaderMetaEnd() { return "#endif"; }
Uniform::Condition Uniform::conditionFromString(const QString &condition)
{
if (condition.isEmpty())
return Uniform::Condition::None;
if (condition.at(0) == QChar::fromLatin1('!'))
return Uniform::Negated;
return Uniform::Regular;
}
QSSGShaderGeneratorStage InputOutput::stageFromString(const QString &stage)
{
if (stage == QLatin1String("vertex")) {
return QSSGShaderGeneratorStage::Vertex;
} else if (stage == QLatin1String("fragment"))
return QSSGShaderGeneratorStage::Fragment;
else {
qWarning("Unknown stage in shader metadata: %s, assuming vertex", qPrintable(stage));
return QSSGShaderGeneratorStage::Vertex;
}
}
ShaderMetaData getShaderMetaData(const QByteArray &data)
{
ShaderMetaData result;
if (data.isEmpty())
return result;
int jsonStart = 0, jsonEnd = 0;
for ( ; ; ) {
jsonStart = data.indexOf(shaderMetaStart(), jsonEnd);
if (jsonStart)
jsonEnd = data.indexOf(shaderMetaEnd(), jsonStart);
if (jsonEnd) // adjust start position
jsonStart += int(strlen(shaderMetaStart()));
if (jsonStart <= 0 || jsonEnd <= 0)
break;
const int size = jsonEnd - jsonStart;
// /*{"inputs":[]}*/ => 17
if (size < 17) {
qWarning("Shader metadata section found, but content to small to be valid!");
break;
}
QByteArray jsonData = data.mid(jsonStart, size).trimmed();
if (!jsonData.startsWith(QByteArrayLiteral("/*{"))) {
qWarning("Missing /*{ prefix");
break;
}
if (!jsonData.endsWith(QByteArrayLiteral("}*/"))) {
qWarning("Missing }*/ suffix");
break;
}
jsonData = jsonData.mid(2, jsonData.size() - 4);
QJsonParseError error;
const auto doc = QJsonDocument::fromJson(jsonData, &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "Shader metadata parse error at offset: " << error.offset;
break;
}
static const auto toUniform = [](const QJsonObject &uObj) {
Uniform uniform;
auto it = uObj.constBegin();
const auto end = uObj.constEnd();
if (it != end) {
it = uObj.constFind(QLatin1String("type"));
uniform.type = (it != end) ? it->toString().toLatin1() : QByteArray();
it = uObj.constFind(QLatin1String("name"));
uniform.name = (it != end) ? it->toString().toLatin1() : QByteArray();
it = uObj.constFind(QLatin1String("multiview_dependent"));
uniform.multiview = (it != end) ? it->toBool() : false;
it = uObj.constFind(QLatin1String("condition"));
const QString conditionString = (it != end) ? it->toString() : QString();
uniform.condition = Uniform::conditionFromString(conditionString);
if (uniform.condition == Uniform::Negated)
uniform.conditionName = conditionString.mid(1).toLatin1();
else if (uniform.condition == Uniform::Regular)
uniform.conditionName = conditionString.toLatin1();
}
return uniform;
};
static const auto toImage = [](const QJsonObject &uObj, const Uniform &uniform)
{
Image img(uniform);
auto it = uObj.constBegin();
const auto end = uObj.constEnd();
if (it != end) {
it = uObj.constFind(QLatin1String("imgtype"));
img.imageType = (it != end) ? it->toString().toLatin1() : QByteArray();
it = uObj.constFind(QLatin1String("qualifiers"));
img.qualifiers = (it != end) ? it->toString().toLatin1() : QByteArray();
}
return img;
};
static const auto toInputOutput = [](const QJsonObject &uObj) {
InputOutput inOutVar;
auto it = uObj.constBegin();
const auto end = uObj.constEnd();
if (it != end) {
it = uObj.constFind(QLatin1String("type"));
inOutVar.type = (it != end) ? it->toString().toLatin1() : QByteArray();
it = uObj.constFind(QLatin1String("name"));
inOutVar.name = (it != end) ? it->toString().toLatin1() : QByteArray();
it = uObj.constFind(QLatin1String("stage"));
inOutVar.stage = InputOutput::stageFromString((it != end) ? it->toString() : QString());
it = uObj.constFind(QLatin1String("flat"));
inOutVar.flat = (it != end) ? it->toBool() : false;
}
return inOutVar;
};
const QJsonObject obj = doc.object();
auto it = obj.constBegin();
const auto end = obj.constEnd();
if (it != end) {
// Uniforms
it = obj.constFind(QLatin1String("uniforms"));
if (it != obj.constEnd()) {
// Check if it's an array or a single object
if (it->type() == QJsonValue::Array) {
const auto uniformArray = it->toArray();
for (const auto valueRef : uniformArray) {
if (!valueRef.isObject())
continue;
const QJsonObject obj = valueRef.toObject();
const auto uniform = toUniform(obj);
if (!uniform.type.isEmpty() && !uniform.name.isEmpty()) {
if (obj.constFind(QLatin1String("imgtype")) != obj.constEnd())
result.images.push_back(toImage(obj, uniform));
else
result.uniforms.push_back(uniform);
} else {
qWarning("Invalid uniform, skipping");
}
}
} else if (it->type() == QJsonValue::Object) {
const auto uniform = toUniform(it->toObject());
if (!uniform.type.isEmpty() && !uniform.name.isEmpty()) {
if (obj.constFind(QLatin1String("imgtype")) != obj.constEnd())
result.images.push_back(toImage(obj, uniform));
else
result.uniforms.push_back(uniform);
} else {
qWarning("Invalid uniform, skipping");
}
}
}
// Inputs
it = obj.constFind(QLatin1String("inputs"));
if (it != end) {
if (it->type() == QJsonValue::Array) {
const auto array = it->toArray();
for (const auto &valueRef : array) {
if (!valueRef.isObject())
continue;
const auto inOutVar = toInputOutput(valueRef.toObject());
if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty())
result.inputs.push_back(inOutVar);
else
qWarning("Invalid input variable, skipping");
}
} else if (it->type() == QJsonValue::Object) {
const QJsonObject obj = it->toObject();
const auto inOutVar = toInputOutput(obj);
if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty()) {
result.inputs.push_back(inOutVar);
} else {
qWarning("Invalid input variable, skipping");
}
}
}
// Outputs
it = obj.constFind(QLatin1String("outputs"));
if (it != end) {
if (it->type() == QJsonValue::Array) {
const auto array = it->toArray();
for (const auto &valueRef : array) {
if (!valueRef.isObject())
continue;
const auto inOutVar = toInputOutput(valueRef.toObject());
if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty())
result.outputs.push_back(inOutVar);
else
qWarning("Invalid output variable, skipping");
}
} else if (it->type() == QJsonValue::Object) {
const QJsonObject inputJObj = it->toObject();
const auto inOutVar = toInputOutput(inputJObj);
if (!inOutVar.type.isEmpty() && !inOutVar.name.isEmpty()) {
result.outputs.push_back(inOutVar);
} else {
qWarning("Invalid output variable, skipping");
}
}
}
}
}
return result;
}
} // namespace QSSGRenderShaderMetadata
QT_END_NAMESPACE