// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "qssgrendershadermetadata_p.h" #include #include #include #include #include #include // 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 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 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()) { 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()) result.uniforms.push_back(uniform); else qWarning("Invalid uniform, skipping"); } } // Inputs it = obj.constFind(QLatin1String("inputs")); if (it != end) { if (it->type() == QJsonValue::Array) { for (const auto valueRef : it->toArray()) { 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) { for (const auto valueRef : it->toArray()) { 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