1488 lines
64 KiB
C++
1488 lines
64 KiB
C++
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "ParametersProcessor.h"
|
|
|
|
#include <filamat/Enums.h>
|
|
#include <utils/CString.h>
|
|
#include <utils/sstream.h>
|
|
#include <utils/Status.h>
|
|
|
|
#include <private/filament/BufferInterfaceBlock.h>
|
|
#include <private/filament/Variant.h>
|
|
|
|
#include <backend/DriverEnums.h>
|
|
|
|
#include <math/vec3.h>
|
|
|
|
#include <algorithm>
|
|
#include <iostream>
|
|
#include <string_view>
|
|
#include <ctype.h>
|
|
|
|
using namespace filamat;
|
|
using namespace utils;
|
|
|
|
namespace matp {
|
|
|
|
template <class T>
|
|
static utils::Status logEnumIssue(const std::string& key, const JsonishString& value,
|
|
const std::unordered_map<std::string, T>& map) noexcept {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "Error while processing key '" << key << "' value." << utils::io::endl;
|
|
errorMessage << "Value '" << value.getString() << "' is invalid. Valid values are:"
|
|
<< utils::io::endl;
|
|
for (const auto& entries : map) {
|
|
errorMessage << " " << entries.first << utils::io::endl;
|
|
}
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
template <class T>
|
|
static bool isStringValidEnum(const std::unordered_map<std::string, T>& map,
|
|
const std::string& s) noexcept {
|
|
return map.find(s) != map.end();
|
|
}
|
|
|
|
template <class T>
|
|
static T stringToEnum(const std::unordered_map<std::string, T>& map,
|
|
const std::string& s) noexcept {
|
|
return map.at(s);
|
|
}
|
|
|
|
static MaterialBuilder::Variable intToVariable(size_t i) noexcept {
|
|
switch (i) {
|
|
case 0: return MaterialBuilder::Variable::CUSTOM0;
|
|
case 1: return MaterialBuilder::Variable::CUSTOM1;
|
|
case 2: return MaterialBuilder::Variable::CUSTOM2;
|
|
case 3: return MaterialBuilder::Variable::CUSTOM3;
|
|
case 4: return MaterialBuilder::Variable::CUSTOM4;
|
|
default: return MaterialBuilder::Variable::CUSTOM0;
|
|
}
|
|
}
|
|
|
|
static utils::Status processName(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.name(value.toJsonString()->getString().c_str());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processApiLevel(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.setApiLevel(value.toJsonNumber()->getFloat());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processInterpolation(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::Interpolation> strToEnum {
|
|
{ "smooth", MaterialBuilder::Interpolation::SMOOTH },
|
|
{ "flat", MaterialBuilder::Interpolation::FLAT },
|
|
};
|
|
auto interpolationString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, interpolationString->getString())) {
|
|
return logEnumIssue("interpolation", *interpolationString, strToEnum);
|
|
}
|
|
builder.interpolation(stringToEnum(strToEnum, interpolationString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
/**
|
|
* Parses the supplied type string to return the array size it defines, or 0
|
|
* if the type is not an array. If the type is an array, the type string is
|
|
* modified to remove the array declaration part and keep only the base type.
|
|
*
|
|
* For instance:
|
|
* "float" returns 0 and "float" is unmodified
|
|
* "float[4]" returns 4 and "float[4]" is changed to "float"
|
|
* "float[]" returns -1 and "float[]" is changed to "float"
|
|
* "float[3" returns 0 and "float[3" is unmodified
|
|
* "float[2]foo" returns 0 and "float[2]foo" is unmodified
|
|
* "float[2foo]" returns 0 and "float[2foo]" is unmodified
|
|
*/
|
|
static ssize_t extractArraySize(std::string& type) {
|
|
auto start = type.find_first_of('[');
|
|
// Not an array
|
|
if (start == std::string::npos) {
|
|
return 0;
|
|
}
|
|
|
|
auto end = type.find_first_of(']', start);
|
|
// If we cannot find ']' or if it's not the last character, return to fail later
|
|
if (end == std::string::npos || end != type.length() - 1) {
|
|
return 0;
|
|
}
|
|
|
|
// If not all the characters in the array declaration are digits, return to fail later
|
|
if (!std::all_of(type.cbegin() + start + 1, type.cbegin() + end,
|
|
[](char c) { return isdigit(c); })) {
|
|
return 0;
|
|
}
|
|
|
|
// Remove the [...] bit
|
|
type.erase(start);
|
|
|
|
// handle an empty size array: []
|
|
if (end - start == 1) {
|
|
return -1;
|
|
}
|
|
|
|
// Return the size (we already validated this part of the string contains only digits)
|
|
return (ssize_t)std::stoul(type.c_str() + start + 1, nullptr);
|
|
}
|
|
|
|
static utils::Status processParameter(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept {
|
|
|
|
const JsonishValue* typeValue = jsonObject.getValue("type");
|
|
if (!typeValue) {
|
|
return utils::Status::invalidArgument("parameters: entry without key 'type'.");
|
|
}
|
|
if (typeValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("parameters: type value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("parameters: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("parameters: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* transformNameValue = jsonObject.getValue("transformName");
|
|
if (transformNameValue && transformNameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument(
|
|
"parameters: transformName value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* precisionValue = jsonObject.getValue("precision");
|
|
if (precisionValue) {
|
|
if (precisionValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("parameters: precision must be a STRING.");
|
|
}
|
|
auto precisionString = precisionValue->toJsonString();
|
|
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
|
|
return logEnumIssue("parameters", *precisionString, Enums::map<ParameterPrecision>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* formatValue = jsonObject.getValue("format");
|
|
if (formatValue) {
|
|
if (formatValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("parameters: format must be a STRING.");
|
|
}
|
|
auto formatString = formatValue->toJsonString();
|
|
if (!Enums::isValid<SamplerFormat>(formatString->getString())){
|
|
return logEnumIssue("parameters", *formatString, Enums::map<SamplerFormat>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* filterableValue = jsonObject.getValue("filterable");
|
|
if (filterableValue) {
|
|
if (filterableValue->getType() != JsonishValue::BOOL) {
|
|
return utils::Status::invalidArgument("parameters: filterable must be a BOOL.");
|
|
}
|
|
}
|
|
const JsonishValue* multiSampleValue = jsonObject.getValue("multisample");
|
|
if (multiSampleValue) {
|
|
if (multiSampleValue->getType() != JsonishValue::BOOL) {
|
|
return utils::Status::invalidArgument("parameters: multisample must be a BOOL.");
|
|
}
|
|
}
|
|
|
|
auto typeString = typeValue->toJsonString()->getString();
|
|
auto nameString = nameValue->toJsonString()->getString();
|
|
|
|
const JsonishValue* stagesValue = jsonObject.getValue("stages");
|
|
using filament::backend::ShaderStageFlags;
|
|
std::optional<ShaderStageFlags> stages;
|
|
if (stagesValue) {
|
|
ShaderStageFlags parsedStages = ShaderStageFlags::NONE;
|
|
if (stagesValue->getType() != JsonishValue::ARRAY) {
|
|
return utils::Status::invalidArgument("parameters: stages must be an ARRAY.");
|
|
}
|
|
for (auto value: stagesValue->toJsonArray()->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::STRING) {
|
|
using namespace std::literals;
|
|
using Qualifier = filament::BufferInterfaceBlock::Qualifier;
|
|
auto stageString = value->toJsonString()->getString();
|
|
if (Enums::isValid<ShaderStageType>(stageString)) {
|
|
parsedStages |= Enums::toEnum<ShaderStageType>(stageString);
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "stages: the stage '" << stageString <<
|
|
"' for parameter with name '" << nameString <<
|
|
"' is not a valid shader stage.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument(
|
|
"parameters: stages must be an array of STRINGs.");
|
|
}
|
|
stages = parsedStages;
|
|
}
|
|
|
|
size_t const arraySize = extractArraySize(typeString);
|
|
|
|
if (Enums::isValid<UniformType>(typeString)) {
|
|
if (stages.has_value()) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "parameters: the uniform parameter with name '" << nameString << "'"
|
|
<< " has shader stages specified. Shader stages are only supported for"
|
|
<< " samplers.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
MaterialBuilder::UniformType const type = Enums::toEnum<UniformType>(typeString);
|
|
ParameterPrecision precision = ParameterPrecision::DEFAULT;
|
|
if (precisionValue) {
|
|
precision =
|
|
Enums::toEnum<ParameterPrecision>(precisionValue->toJsonString()->getString());
|
|
}
|
|
if (arraySize == 0) {
|
|
builder.parameter(nameString.c_str(), type, precision);
|
|
} else {
|
|
builder.parameter(nameString.c_str(), arraySize, type, precision);
|
|
}
|
|
} else if (Enums::isValid<SamplerType>(typeString)) {
|
|
if (arraySize > 0) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "parameters: the parameter with name '" << nameString << "'"
|
|
<< " is an array of samplers of size " << arraySize << ". Arrays of samplers"
|
|
<< " are currently not supported.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
MaterialBuilder::SamplerType const type = Enums::toEnum<SamplerType>(typeString);
|
|
|
|
auto format = formatValue ? Enums::toEnum<SamplerFormat>(
|
|
formatValue->toJsonString()->getString()) : SamplerFormat::FLOAT;
|
|
|
|
auto precision = precisionValue ? Enums::toEnum<ParameterPrecision>(
|
|
precisionValue->toJsonString()->getString()) : ParameterPrecision::DEFAULT;
|
|
|
|
if (format == SamplerFormat::SHADOW) {
|
|
return utils::Status::invalidArgument(
|
|
"Materials should not be able to define a shadow sampler");
|
|
}
|
|
|
|
if (format == SamplerFormat::INT && filterableValue) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "parameters: the parameter with name '" << nameString << "'"
|
|
<< " is an integer sampler. The `filterable` attribute must not be defined.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
// For samplers without `filterable` defined, we use the following logic
|
|
// - float sampler can be filterable or not, default to filterable
|
|
// - int sampler is not filterable (checked above)
|
|
// - shadow sampler must be filterable (checked above)
|
|
auto filterable =
|
|
filterableValue ? filterableValue->toJsonBool()->getBool() : true;
|
|
|
|
auto multisample = multiSampleValue ? multiSampleValue->toJsonBool()->getBool() : false;
|
|
|
|
if (transformNameValue) {
|
|
if (type != MaterialBuilder::SamplerType::SAMPLER_EXTERNAL) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "parameters: the parameter with name '" << nameString << "'"
|
|
<< " is a sampler of type " << typeString << " and has a transformName."
|
|
<< " Transform names are only supported for external samplers.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
auto transformName = transformNameValue->toJsonString()->getString();
|
|
builder.parameter(nameString.c_str(), type, format, precision, filterable,
|
|
multisample, transformName.c_str(), stages);
|
|
} else {
|
|
builder.parameter(nameString.c_str(), type, format, precision, filterable,
|
|
multisample, "", stages);
|
|
}
|
|
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "parameters: the type '" << typeString
|
|
<< "' for parameter with name '" << nameString << "' is neither a valid uniform "
|
|
<< "type nor a valid sampler type.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processParameters(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
|
|
utils::Status status;
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processParameter(builder, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("parameters must be an array of OBJECTs.");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static utils::Status processConstant(MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept {
|
|
const JsonishValue* typeValue = jsonObject.getValue("type");
|
|
if (!typeValue) {
|
|
return utils::Status::invalidArgument("constants: entry without key 'type'.");
|
|
}
|
|
if (typeValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("constants: type value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("constants: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("constants: name value must be STRING.");
|
|
}
|
|
|
|
auto typeString = typeValue->toJsonString()->getString();
|
|
auto nameString = nameValue->toJsonString()->getString();
|
|
const JsonishValue* defaultValue = jsonObject.getValue("default");
|
|
|
|
if (Enums::isValid<ConstantType>(typeString)) {
|
|
auto type = Enums::toEnum<ConstantType>(typeString);
|
|
switch (type) {
|
|
case ConstantType::INT: {
|
|
int32_t intDefault = 0;
|
|
if (defaultValue) {
|
|
if (defaultValue->getType() != JsonishValue::NUMBER) {
|
|
return utils::Status::invalidArgument(
|
|
"constants: INT constants must have NUMBER default value");
|
|
}
|
|
// FIXME: Jsonish doesn't distinguish between integers and floats.
|
|
intDefault = (int32_t)defaultValue->toJsonNumber()->getFloat();
|
|
}
|
|
builder.constant(nameString.c_str(), type, intDefault);
|
|
break;
|
|
}
|
|
case ConstantType::FLOAT: {
|
|
float floatDefault = 0.0f;
|
|
if (defaultValue) {
|
|
if (defaultValue->getType() != JsonishValue::NUMBER) {
|
|
return utils::Status::invalidArgument(
|
|
"constants: FLOAT constants must have NUMBER default value");
|
|
}
|
|
floatDefault = defaultValue->toJsonNumber()->getFloat();
|
|
}
|
|
builder.constant(nameString.c_str(), type, floatDefault);
|
|
break;
|
|
}
|
|
case ConstantType::BOOL:
|
|
bool boolDefault = false;
|
|
if (defaultValue) {
|
|
if (defaultValue->getType() != JsonishValue::BOOL) {
|
|
return utils::Status::invalidArgument(
|
|
"constants: BOOL constants must have BOOL default value");
|
|
}
|
|
boolDefault = defaultValue->toJsonBool()->getBool();
|
|
}
|
|
builder.constant(nameString.c_str(), type, boolDefault);
|
|
break;
|
|
}
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "constants: the type '" << typeString
|
|
<< "' for constant with name '" << nameString << "' is not a valid constant "
|
|
<< "parameter type.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processConstants(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
|
|
utils::Status status;
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processConstant(builder, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("constants must be an array of OBJECTs.");
|
|
}
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processBufferField(filament::BufferInterfaceBlock::Builder& builder,
|
|
const JsonishObject& jsonObject) noexcept {
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("buffers: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("buffers: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* typeValue = jsonObject.getValue("type");
|
|
if (!typeValue) {
|
|
return utils::Status::invalidArgument("buffers: entry without key 'type'.");
|
|
}
|
|
if (typeValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("buffers: type value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* precisionValue = jsonObject.getValue("precision");
|
|
if (precisionValue) {
|
|
if (precisionValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("buffers: precision must be a STRING.");
|
|
}
|
|
|
|
auto precisionString = precisionValue->toJsonString();
|
|
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
|
|
return logEnumIssue("buffers", *precisionString, Enums::map<ParameterPrecision>());
|
|
}
|
|
}
|
|
|
|
auto typeString = typeValue->toJsonString()->getString();
|
|
auto nameString = nameValue->toJsonString()->getString();
|
|
|
|
size_t arraySize = extractArraySize(typeString);
|
|
|
|
if (Enums::isValid<UniformType>(typeString)) {
|
|
MaterialBuilder::UniformType type = Enums::toEnum<UniformType>(typeString);
|
|
ParameterPrecision precision = ParameterPrecision::DEFAULT;
|
|
if (precisionValue) {
|
|
precision = Enums::toEnum<ParameterPrecision>(
|
|
precisionValue->toJsonString()->getString());
|
|
}
|
|
if (arraySize == -1) {
|
|
builder.addVariableSizedArray({
|
|
{ nameString.data(), nameString.size() }, 0, type, precision });
|
|
} else {
|
|
builder.add({{
|
|
{ nameString.data(), nameString.size() }, uint32_t(arraySize), type, precision } });
|
|
}
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "buffers: the type '" << typeString << "' for parameter with name '"
|
|
<< nameString << "' is not a valid buffer field type.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processBuffer(MaterialBuilder& builder,
|
|
const JsonishObject& jsonObject) noexcept {
|
|
|
|
filament::BufferInterfaceBlock::Builder bibb;
|
|
|
|
bibb.target(filament::BufferInterfaceBlock::Target::SSBO);
|
|
bibb.alignment(filament::BufferInterfaceBlock::Alignment::std430);
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("buffers: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("buffers: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* qualifiersValue = jsonObject.getValue("qualifiers");
|
|
if (!qualifiersValue) {
|
|
return utils::Status::invalidArgument("buffers: entry without key 'qualifiers'.");
|
|
}
|
|
if (qualifiersValue->getType() != JsonishValue::ARRAY) {
|
|
return utils::Status::invalidArgument("buffers: qualifiers value must be an ARRAY.");
|
|
}
|
|
|
|
const JsonishValue* fieldsValue = jsonObject.getValue("fields");
|
|
if (!fieldsValue) {
|
|
return utils::Status::invalidArgument("buffers: entry without key 'fields'.");
|
|
}
|
|
if (fieldsValue->getType() != JsonishValue::ARRAY) {
|
|
return utils::Status::invalidArgument("buffers: fields value must be an ARRAY.");
|
|
}
|
|
|
|
auto nameString = nameValue->toJsonString()->getString();
|
|
bibb.name({ nameString.data(), nameString.size() });
|
|
|
|
for (auto value : qualifiersValue->toJsonArray()->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::STRING) {
|
|
using namespace std::literals;
|
|
using Qualifier = filament::BufferInterfaceBlock::Qualifier;
|
|
auto qualifierString = value->toJsonString()->getString();
|
|
if (qualifierString == "coherent"sv) {
|
|
bibb.qualifier(Qualifier::COHERENT);
|
|
} else if (qualifierString == "writeonly"sv) {
|
|
bibb.qualifier(Qualifier::WRITEONLY);
|
|
} else if (qualifierString == "readonly"sv) {
|
|
bibb.qualifier(Qualifier::READONLY);
|
|
} else if (qualifierString == "volatile"sv) {
|
|
bibb.qualifier(Qualifier::VOLATILE);
|
|
} else if (qualifierString == "restrict"sv) {
|
|
bibb.qualifier(Qualifier::RESTRICT);
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("buffers: qualifiers must be an array of STRINGs.");
|
|
}
|
|
|
|
utils::Status status;
|
|
for (auto value : fieldsValue->toJsonArray()->getElements()) {
|
|
|
|
if (bibb.hasVariableSizeArray()) {
|
|
return utils::Status::invalidArgument(
|
|
"buffers: a variable size array must be the only and last field.");
|
|
}
|
|
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processBufferField(bibb, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("buffers: fields must be an array of OBJECTs.");
|
|
}
|
|
|
|
builder.buffer(bibb.build());
|
|
return status;
|
|
}
|
|
|
|
static utils::Status processBuffers(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
utils::Status status;
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processBuffer(builder, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("buffers must be an array of OBJECTs.");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
static utils::Status processSubpass(
|
|
MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept {
|
|
|
|
const JsonishValue* typeValue = jsonObject.getValue("type");
|
|
if (!typeValue) {
|
|
return utils::Status::invalidArgument("subpasses: entry without key 'type'.");
|
|
}
|
|
if (typeValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("subpasses: type value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("subpasses: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("subpasses: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* precisionValue = jsonObject.getValue("precision");
|
|
if (precisionValue) {
|
|
if (precisionValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("subpasses: precision must be a STRING.");
|
|
}
|
|
|
|
auto precisionString = precisionValue->toJsonString();
|
|
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
|
|
return logEnumIssue("subpasses", *precisionString, Enums::map<ParameterPrecision>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* formatValue = jsonObject.getValue("format");
|
|
if (formatValue) {
|
|
if (formatValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("subpasses: format must be a STRING.");
|
|
}
|
|
|
|
auto formatString = formatValue->toJsonString();
|
|
if (!Enums::isValid<SamplerFormat>(formatString->getString())){
|
|
return logEnumIssue("subpasses", *formatString, Enums::map<SamplerFormat>());
|
|
}
|
|
}
|
|
|
|
auto typeString = typeValue->toJsonString()->getString();
|
|
auto nameString = nameValue->toJsonString()->getString();
|
|
|
|
size_t arraySize = extractArraySize(typeString);
|
|
|
|
if (Enums::isValid<SubpassType>(typeString)) {
|
|
if (arraySize > 0) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "subpasses: the parameter with name '" << nameString << "'"
|
|
<< " is an array of subpasses of size " << arraySize << ". Arrays of subpasses"
|
|
<< " are currently not supported.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
MaterialBuilder::SubpassType type = Enums::toEnum<SubpassType>(typeString);
|
|
if (precisionValue && formatValue) {
|
|
auto format = Enums::toEnum<SamplerFormat>(formatValue->toJsonString()->getString());
|
|
auto precision =
|
|
Enums::toEnum<ParameterPrecision>(precisionValue->toJsonString()->getString());
|
|
builder.subpass(type, format, precision, nameString.c_str());
|
|
} else if (formatValue) {
|
|
auto format = Enums::toEnum<SamplerFormat>(formatValue->toJsonString()->getString());
|
|
builder.subpass(type, format, nameString.c_str());
|
|
} else if (precisionValue) {
|
|
auto precision =
|
|
Enums::toEnum<ParameterPrecision>(precisionValue->toJsonString()->getString());
|
|
builder.subpass(type, precision, nameString.c_str());
|
|
} else {
|
|
builder.subpass(type, nameString.c_str());
|
|
}
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "subpasses: the type '" << typeString
|
|
<< "' for parameter with name '" << nameString << "' is neither a valid uniform "
|
|
<< "type nor a valid sampler type.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processSubpasses(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
|
|
utils::Status status;
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processSubpass(builder, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("subpasses must be an array of OBJECTs.");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static utils::Status processVariables(MaterialBuilder& builder, const JsonishValue& value) {
|
|
const JsonishArray* jsonArray = value.toJsonArray();
|
|
const auto& elements = jsonArray->getElements();
|
|
|
|
if (elements.size() > MaterialBuilder::MATERIAL_VARIABLES_COUNT) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "variables: Max array size is "
|
|
<< MaterialBuilder::MATERIAL_VARIABLES_COUNT << ".";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
for (size_t i = 0; i < elements.size(); i++) {
|
|
ParameterPrecision precision = ParameterPrecision::DEFAULT;
|
|
MaterialBuilder::Variable v = intToVariable(i);
|
|
std::string nameString;
|
|
|
|
auto elementValue = elements[i];
|
|
if (elementValue->getType() == JsonishValue::Type::OBJECT) {
|
|
|
|
JsonishObject const& jsonObject = *elementValue->toJsonObject();
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("variables: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("variables: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* precisionValue = jsonObject.getValue("precision");
|
|
if (precisionValue) {
|
|
if (precisionValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("variables: precision must be a STRING.");
|
|
}
|
|
auto precisionString = precisionValue->toJsonString();
|
|
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
|
|
return logEnumIssue("variables", *precisionString, Enums::map<ParameterPrecision>());
|
|
}
|
|
}
|
|
|
|
nameString = nameValue->toJsonString()->getString();
|
|
if (precisionValue) {
|
|
precision = Enums::toEnum<ParameterPrecision>(
|
|
precisionValue->toJsonString()->getString());
|
|
}
|
|
builder.variable(v, nameString.c_str(), precision);
|
|
} else if (elementValue->getType() == JsonishValue::Type::STRING) {
|
|
nameString = elementValue->toJsonString()->getString();
|
|
builder.variable(v, nameString.c_str());
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "variables: array index " << i << " is not a STRING. found:" <<
|
|
JsonishValue::typeToString(elementValue->getType());
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processRequires(MaterialBuilder& builder, const JsonishValue& value) {
|
|
using Attribute = filament::VertexAttribute;
|
|
static const std::unordered_map<std::string, Attribute> strToEnum {
|
|
{ "color", Attribute::COLOR },
|
|
{ "position", Attribute::POSITION },
|
|
{ "tangents", Attribute::TANGENTS },
|
|
{ "uv0", Attribute::UV0 },
|
|
{ "uv1", Attribute::UV1 },
|
|
{ "custom0", Attribute::CUSTOM0 },
|
|
{ "custom1", Attribute(Attribute::CUSTOM0 + 1) },
|
|
{ "custom2", Attribute(Attribute::CUSTOM0 + 2) },
|
|
{ "custom3", Attribute(Attribute::CUSTOM0 + 3) },
|
|
{ "custom4", Attribute(Attribute::CUSTOM0 + 4) },
|
|
{ "custom5", Attribute(Attribute::CUSTOM0 + 5) },
|
|
{ "custom6", Attribute(Attribute::CUSTOM0 + 6) },
|
|
{ "custom7", Attribute(Attribute::CUSTOM0 + 7) },
|
|
};
|
|
for (auto v : value.toJsonArray()->getElements()) {
|
|
if (v->getType() != JsonishValue::Type::STRING) {
|
|
return utils::Status::invalidArgument("requires: entries must be STRINGs.");
|
|
}
|
|
|
|
auto jsonString = v->toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("requires", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.require(stringToEnum(strToEnum, jsonString->getString()));
|
|
}
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processBlending(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::BlendingMode> strToEnum {
|
|
{ "add", MaterialBuilder::BlendingMode::ADD },
|
|
{ "masked", MaterialBuilder::BlendingMode::MASKED },
|
|
{ "opaque", MaterialBuilder::BlendingMode::OPAQUE },
|
|
{ "transparent", MaterialBuilder::BlendingMode::TRANSPARENT },
|
|
{ "fade", MaterialBuilder::BlendingMode::FADE },
|
|
{ "multiply", MaterialBuilder::BlendingMode::MULTIPLY },
|
|
{ "screen", MaterialBuilder::BlendingMode::SCREEN },
|
|
{ "custom", MaterialBuilder::BlendingMode::CUSTOM },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("blending", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.blending(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processBlendFunction(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::BlendFunction> strToEnum{
|
|
{ "zero", MaterialBuilder::BlendFunction::ZERO },
|
|
{ "one", MaterialBuilder::BlendFunction::ONE },
|
|
{ "srcColor", MaterialBuilder::BlendFunction::SRC_COLOR },
|
|
{ "oneMinusSrcColor", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_COLOR },
|
|
{ "dstColor", MaterialBuilder::BlendFunction::DST_COLOR },
|
|
{ "oneMinusDstColor", MaterialBuilder::BlendFunction::ONE_MINUS_DST_COLOR },
|
|
{ "srcAlpha", MaterialBuilder::BlendFunction::SRC_ALPHA },
|
|
{ "oneMinusSrcAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_SRC_ALPHA },
|
|
{ "dstAlpha", MaterialBuilder::BlendFunction::DST_ALPHA },
|
|
{ "oneMinusDstAlpha", MaterialBuilder::BlendFunction::ONE_MINUS_DST_ALPHA },
|
|
{ "srcAlphaSaturate", MaterialBuilder::BlendFunction::SRC_ALPHA_SATURATE },
|
|
};
|
|
|
|
if (value.getType() != JsonishValue::Type::OBJECT) {
|
|
return utils::Status::invalidArgument("blendFunction must be an OBJECT.");
|
|
}
|
|
|
|
JsonishObject const* const jsonObject = value.toJsonObject();
|
|
|
|
MaterialBuilder::BlendFunction srcRGB, srcA, dstRGB, dstA;
|
|
std::pair<const char*, MaterialBuilder::BlendFunction*> functions[] = {
|
|
{ "srcRGB", &srcRGB },
|
|
{ "srcA", &srcA },
|
|
{ "dstRGB", &dstRGB },
|
|
{ "dstA", &dstA },
|
|
};
|
|
|
|
for (auto&& entry : functions) {
|
|
const char* key = entry.first;
|
|
const JsonishValue* v = jsonObject->getValue(key);
|
|
if (!v) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "blendFunction: entry without '" << key << "' key.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
if (v->getType() != JsonishValue::STRING) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "blendFunction: '" << key << "' value must be STRING.";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
*entry.second = stringToEnum(strToEnum, v->toJsonString()->getString());
|
|
}
|
|
builder.customBlendFunctions(srcRGB, srcA, dstRGB, dstA);
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processPostLightingBlending(
|
|
MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::BlendingMode> strToEnum {
|
|
{ "add", MaterialBuilder::BlendingMode::ADD },
|
|
{ "opaque", MaterialBuilder::BlendingMode::OPAQUE },
|
|
{ "transparent", MaterialBuilder::BlendingMode::TRANSPARENT },
|
|
{ "multiply", MaterialBuilder::BlendingMode::MULTIPLY },
|
|
{ "screen", MaterialBuilder::BlendingMode::SCREEN },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("postLightingBlending", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.postLightingBlending(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processVertexDomain(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::VertexDomain> strToEnum {
|
|
{ "device", MaterialBuilder::VertexDomain::DEVICE},
|
|
{ "object", MaterialBuilder::VertexDomain::OBJECT},
|
|
{ "world", MaterialBuilder::VertexDomain::WORLD},
|
|
{ "view", MaterialBuilder::VertexDomain::VIEW},
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("vertex_domain", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.vertexDomain(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processCulling(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::CullingMode> strToEnum {
|
|
{ "back", MaterialBuilder::CullingMode::BACK },
|
|
{ "front", MaterialBuilder::CullingMode::FRONT },
|
|
{ "frontAndBack", MaterialBuilder::CullingMode::FRONT_AND_BACK },
|
|
{ "none", MaterialBuilder::CullingMode::NONE },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("culling", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.culling(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processQuality(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::ShaderQuality> strToEnum {
|
|
{ "low", MaterialBuilder::ShaderQuality::LOW },
|
|
{ "normal", MaterialBuilder::ShaderQuality::NORMAL },
|
|
{ "high", MaterialBuilder::ShaderQuality::HIGH },
|
|
{ "default", MaterialBuilder::ShaderQuality::DEFAULT },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("quality", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.quality(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processFeatureLevel(MaterialBuilder& builder, const JsonishValue& value) {
|
|
using filament::backend::FeatureLevel;
|
|
JsonishNumber const* const number = value.toJsonNumber();
|
|
FeatureLevel featureLevel;
|
|
if (number->getFloat() == 0.0f) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_0;
|
|
} else if (number->getFloat() == 1.0f) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_1;
|
|
} else if (number->getFloat() == 2.0f) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_2;
|
|
} else if (number->getFloat() == 3.0f) {
|
|
featureLevel = FeatureLevel::FEATURE_LEVEL_3;
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "featureLevel: invalid value " << number->getFloat();
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
builder.featureLevel(featureLevel);
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processGroupSizes(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
|
|
filament::math::uint3 groupSize{ 1, 1, 1 };
|
|
size_t index = 0;
|
|
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (index >= 3) {
|
|
return utils::Status::invalidArgument("groupSize: must be an array no larger than 3");
|
|
}
|
|
if (value->getType() == JsonishValue::Type::NUMBER) {
|
|
JsonishNumber const* const number = value->toJsonNumber();
|
|
float aFloat = number->getFloat();
|
|
if (aFloat > 0 && floor(aFloat) == aFloat) {
|
|
groupSize[index] = uint32_t(floor(aFloat));
|
|
} else {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage<< "groupSize: invalid value " << aFloat;
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
index++;
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("groupSize must be an array of NUMBERs.");
|
|
}
|
|
builder.groupSize(groupSize);
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processStereoscopicType(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::StereoscopicType> strToEnum{
|
|
{ "instanced", MaterialBuilder::StereoscopicType::INSTANCED },
|
|
{ "multiview", MaterialBuilder::StereoscopicType::MULTIVIEW },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("stereoscopicType", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.stereoscopicType(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processOutput(
|
|
MaterialBuilder& builder, const JsonishObject& jsonObject) noexcept {
|
|
|
|
const JsonishValue* nameValue = jsonObject.getValue("name");
|
|
if (!nameValue) {
|
|
return utils::Status::invalidArgument("outputs: entry without 'name' key.");
|
|
}
|
|
if (nameValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("outputs: name value must be STRING.");
|
|
}
|
|
|
|
const JsonishValue* targetValue = jsonObject.getValue("target");
|
|
if (targetValue) {
|
|
if (targetValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("outputs: target must be a STRING.");
|
|
}
|
|
|
|
auto targetString = targetValue->toJsonString();
|
|
if (!Enums::isValid<OutputTarget>(targetString->getString())) {
|
|
return logEnumIssue("target", *targetString, Enums::map<OutputTarget>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* precisionValue = jsonObject.getValue("precision");
|
|
if (precisionValue) {
|
|
if (precisionValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("parameters: precision must be a STRING.");
|
|
}
|
|
|
|
auto precisionString = precisionValue->toJsonString();
|
|
if (!Enums::isValid<ParameterPrecision>(precisionString->getString())){
|
|
return logEnumIssue("parameters", *precisionString, Enums::map<ParameterPrecision>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* typeValue = jsonObject.getValue("type");
|
|
if (typeValue) {
|
|
if (typeValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("outputs: type must be a STRING.");
|
|
}
|
|
|
|
auto typeString = typeValue->toJsonString();
|
|
if (!Enums::isValid<OutputType>(typeString->getString())) {
|
|
return logEnumIssue("type", *typeString, Enums::map<OutputType>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* qualifierValue = jsonObject.getValue("qualifier");
|
|
if (qualifierValue) {
|
|
if (qualifierValue->getType() != JsonishValue::STRING) {
|
|
return utils::Status::invalidArgument("outputs: qualifier must be a STRING.");
|
|
}
|
|
|
|
auto qualifierString = qualifierValue->toJsonString();
|
|
if (!Enums::isValid<OutputQualifier>(qualifierString->getString())) {
|
|
return logEnumIssue("qualifier", *qualifierString, Enums::map<OutputQualifier>());
|
|
}
|
|
}
|
|
|
|
const JsonishValue* locationValue = jsonObject.getValue("location");
|
|
if (locationValue) {
|
|
if (locationValue->getType() != JsonishValue::NUMBER) {
|
|
return utils::Status::invalidArgument("outputs: location must be a NUMBER.");
|
|
}
|
|
}
|
|
|
|
const char* name = nameValue->toJsonString()->getString().c_str();
|
|
|
|
OutputTarget target = OutputTarget::COLOR;
|
|
if (targetValue) {
|
|
target = Enums::toEnum<OutputTarget>(targetValue->toJsonString()->getString());
|
|
}
|
|
|
|
ParameterPrecision precision = ParameterPrecision::DEFAULT;
|
|
if (precisionValue) {
|
|
precision = Enums::toEnum<ParameterPrecision>(precisionValue->toJsonString()->getString());
|
|
}
|
|
|
|
OutputType type = OutputType::FLOAT4;
|
|
if (target == OutputTarget::DEPTH) {
|
|
// The default type for depth targets is float.
|
|
type = OutputType::FLOAT;
|
|
}
|
|
if (typeValue) {
|
|
type = Enums::toEnum<OutputType>(typeValue->toJsonString()->getString());
|
|
}
|
|
|
|
OutputQualifier qualifier = OutputQualifier::OUT;
|
|
if (qualifierValue) {
|
|
qualifier = Enums::toEnum<OutputQualifier>(qualifierValue->toJsonString()->getString());
|
|
}
|
|
|
|
int location = -1;
|
|
if (locationValue) {
|
|
location = static_cast<int>(locationValue->toJsonNumber()->getFloat());
|
|
}
|
|
|
|
builder.output(qualifier, target, precision, type, name, location);
|
|
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processOutputs(MaterialBuilder& builder, const JsonishValue& v) {
|
|
auto jsonArray = v.toJsonArray();
|
|
|
|
utils::Status status;
|
|
for (auto value : jsonArray->getElements()) {
|
|
if (value->getType() == JsonishValue::Type::OBJECT) {
|
|
utils::Status s = processOutput(builder, *value->toJsonObject());
|
|
if (!s.isOk()) {
|
|
status = s;
|
|
}
|
|
continue;
|
|
}
|
|
return utils::Status::invalidArgument("outputs must be an array of OBJECTs.");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
static utils::Status processColorWrite(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.colorWrite(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processDepthWrite(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.depthWrite(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processInstanced(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.instanced(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processDepthCull(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.depthCulling(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processDoubleSided(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.doubleSided(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processTransparencyMode(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::TransparencyMode> strToEnum {
|
|
{ "default", MaterialBuilder::TransparencyMode::DEFAULT },
|
|
{ "twoPassesOneSide", MaterialBuilder::TransparencyMode::TWO_PASSES_ONE_SIDE },
|
|
{ "twoPassesTwoSides", MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("transparency_mode", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.transparencyMode(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processMaskThreshold(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.maskThreshold(value.toJsonNumber()->getFloat());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processAlphaToCoverage(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.alphaToCoverage(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processShadowMultiplier(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.shadowMultiplier(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processTransparentShadow(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.transparentShadow(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processSpecularAntiAliasing(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.specularAntiAliasing(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processSpecularAntiAliasingVariance(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.specularAntiAliasingVariance(value.toJsonNumber()->getFloat());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processSpecularAntiAliasingThreshold(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.specularAntiAliasingThreshold(value.toJsonNumber()->getFloat());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processClearCoatIorChange(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.clearCoatIorChange(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processFlipUV(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.flipUV(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processLinearFog(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.linearFog(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processShadowFarAttenuation(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.shadowFarAttenuation(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processMultiBounceAO(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.multiBounceAmbientOcclusion(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processFramebufferFetch(MaterialBuilder& builder, const JsonishValue& value) {
|
|
if (value.toJsonBool()->getBool()) {
|
|
builder.enableFramebufferFetch();
|
|
}
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processVertexDomainDeviceJittered(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.vertexDomainDeviceJittered(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processLegacyMorphing(MaterialBuilder& builder, const JsonishValue& value) {
|
|
if (value.toJsonBool()->getBool()) {
|
|
builder.useLegacyMorphing();
|
|
}
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processCustomSurfaceShading(MaterialBuilder& builder, const JsonishValue& value) {
|
|
builder.customSurfaceShading(value.toJsonBool()->getBool());
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processSpecularAmbientOcclusion(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::SpecularAmbientOcclusion> strToEnum {
|
|
{ "none", MaterialBuilder::SpecularAmbientOcclusion::NONE },
|
|
{ "simple", MaterialBuilder::SpecularAmbientOcclusion::SIMPLE },
|
|
{ "bentNormals", MaterialBuilder::SpecularAmbientOcclusion::BENT_NORMALS },
|
|
// Backward compatibility
|
|
{ "false", MaterialBuilder::SpecularAmbientOcclusion::NONE },
|
|
{ "true", MaterialBuilder::SpecularAmbientOcclusion::SIMPLE },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("specularAO", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.specularAmbientOcclusion(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processShading(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::Shading> strToEnum {
|
|
{ "cloth", MaterialBuilder::Shading::CLOTH },
|
|
{ "lit", MaterialBuilder::Shading::LIT },
|
|
{ "subsurface", MaterialBuilder::Shading::SUBSURFACE },
|
|
{ "unlit", MaterialBuilder::Shading::UNLIT },
|
|
{ "specularGlossiness", MaterialBuilder::Shading::SPECULAR_GLOSSINESS },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("shading", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.shading(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processDomain(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::MaterialDomain> strToEnum {
|
|
{ "surface", MaterialBuilder::MaterialDomain::SURFACE },
|
|
{ "postprocess", MaterialBuilder::MaterialDomain::POST_PROCESS },
|
|
{ "compute", MaterialBuilder::MaterialDomain::COMPUTE },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("domain", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.materialDomain(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processRefractionMode(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::RefractionMode> strToEnum{
|
|
{ "none", MaterialBuilder::RefractionMode::NONE },
|
|
{ "cubemap", MaterialBuilder::RefractionMode::CUBEMAP },
|
|
{ "screenspace", MaterialBuilder::RefractionMode::SCREEN_SPACE },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("refraction", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.refractionMode(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processReflectionMode(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::ReflectionMode> strToEnum {
|
|
{ "default", MaterialBuilder::ReflectionMode ::DEFAULT },
|
|
{ "screenspace", MaterialBuilder::ReflectionMode::SCREEN_SPACE },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("reflection_mode", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.reflectionMode(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processRefractionType(MaterialBuilder& builder, const JsonishValue& value) {
|
|
static const std::unordered_map<std::string, MaterialBuilder::RefractionType> strToEnum {
|
|
{ "solid", MaterialBuilder::RefractionType::SOLID },
|
|
{ "thin", MaterialBuilder::RefractionType::THIN },
|
|
};
|
|
auto jsonString = value.toJsonString();
|
|
if (!isStringValidEnum(strToEnum, jsonString->getString())) {
|
|
return logEnumIssue("refractionType", *jsonString, strToEnum);
|
|
}
|
|
|
|
builder.refractionType(stringToEnum(strToEnum, jsonString->getString()));
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
static utils::Status processVariantFilter(MaterialBuilder& builder, const JsonishValue& value) {
|
|
// We avoid using an initializer list for this particular map to avoid build errors that are
|
|
// due to static initialization ordering.
|
|
using filament::Variant;
|
|
static const std::unordered_map<std::string, filament::UserVariantFilterBit> strToEnum = [] {
|
|
std::unordered_map<std::string, filament::UserVariantFilterBit> strToEnum;
|
|
strToEnum["directionalLighting"] = filament::UserVariantFilterBit::DIRECTIONAL_LIGHTING;
|
|
strToEnum["dynamicLighting"] = filament::UserVariantFilterBit::DYNAMIC_LIGHTING;
|
|
strToEnum["shadowReceiver"] = filament::UserVariantFilterBit::SHADOW_RECEIVER;
|
|
strToEnum["skinning"] = filament::UserVariantFilterBit::SKINNING;
|
|
strToEnum["vsm"] = filament::UserVariantFilterBit::VSM;
|
|
strToEnum["fog"] = filament::UserVariantFilterBit::FOG;
|
|
strToEnum["ssr"] = filament::UserVariantFilterBit::SSR;
|
|
strToEnum["stereo"] = filament::UserVariantFilterBit::STE;
|
|
return strToEnum;
|
|
}();
|
|
|
|
filament::UserVariantFilterMask variantFilter = {};
|
|
const JsonishArray* jsonArray = value.toJsonArray();
|
|
const auto& elements = jsonArray->getElements();
|
|
|
|
for (size_t i = 0; i < elements.size(); i++) {
|
|
auto elementValue = elements[i];
|
|
if (elementValue->getType() != JsonishValue::Type::STRING) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "variant_filter: array index " << i <<
|
|
" is not a STRING. found:" <<
|
|
JsonishValue::typeToString(elementValue->getType());
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
const std::string& s = elementValue->toJsonString()->getString();
|
|
if (!isStringValidEnum(strToEnum, s)) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "variant_filter: variant " << s << " is not a valid variant";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
variantFilter |= (uint32_t)strToEnum.at(s);
|
|
}
|
|
|
|
builder.variantFilter(variantFilter);
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
static utils::Status processUseDefaultDepthVariant(MaterialBuilder& builder, const JsonishValue& value) {
|
|
if (value.toJsonBool()->getBool()) {
|
|
builder.useDefaultDepthVariant();
|
|
}
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
ParametersProcessor::ParametersProcessor() {
|
|
using Type = JsonishValue::Type;
|
|
mParameters["name"] = { &processName, Type::STRING };
|
|
mParameters["interpolation"] = { &processInterpolation, Type::STRING };
|
|
mParameters["parameters"] = { &processParameters, Type::ARRAY };
|
|
mParameters["constants"] = { &processConstants, Type::ARRAY };
|
|
mParameters["buffers"] = { &processBuffers, Type::ARRAY };
|
|
mParameters["subpasses"] = { &processSubpasses, Type::ARRAY };
|
|
mParameters["variables"] = { &processVariables, Type::ARRAY };
|
|
mParameters["requires"] = { &processRequires, Type::ARRAY };
|
|
mParameters["blending"] = { &processBlending, Type::STRING };
|
|
mParameters["blendFunction"] = { &processBlendFunction, Type::OBJECT };
|
|
mParameters["postLightingBlending"] = { &processPostLightingBlending, Type::STRING };
|
|
mParameters["vertexDomain"] = { &processVertexDomain, Type::STRING };
|
|
mParameters["culling"] = { &processCulling, Type::STRING };
|
|
mParameters["colorWrite"] = { &processColorWrite, Type::BOOL };
|
|
mParameters["depthWrite"] = { &processDepthWrite, Type::BOOL };
|
|
mParameters["instanced"] = { &processInstanced, Type::BOOL };
|
|
mParameters["depthCulling"] = { &processDepthCull, Type::BOOL };
|
|
mParameters["doubleSided"] = { &processDoubleSided, Type::BOOL };
|
|
mParameters["transparency"] = { &processTransparencyMode, Type::STRING };
|
|
mParameters["reflections"] = { &processReflectionMode, Type::STRING };
|
|
mParameters["maskThreshold"] = { &processMaskThreshold, Type::NUMBER };
|
|
mParameters["alphaToCoverage"] = { &processAlphaToCoverage, Type::BOOL };
|
|
mParameters["shadowMultiplier"] = { &processShadowMultiplier, Type::BOOL };
|
|
mParameters["transparentShadow"] = { &processTransparentShadow, Type::BOOL };
|
|
mParameters["shadingModel"] = { &processShading, Type::STRING };
|
|
mParameters["variantFilter"] = { &processVariantFilter, Type::ARRAY };
|
|
mParameters["specularAntiAliasing"] = { &processSpecularAntiAliasing, Type::BOOL };
|
|
mParameters["specularAntiAliasingVariance"] = { &processSpecularAntiAliasingVariance, Type::NUMBER };
|
|
mParameters["specularAntiAliasingThreshold"] = { &processSpecularAntiAliasingThreshold, Type::NUMBER };
|
|
mParameters["clearCoatIorChange"] = { &processClearCoatIorChange, Type::BOOL };
|
|
mParameters["flipUV"] = { &processFlipUV, Type::BOOL };
|
|
mParameters["multiBounceAmbientOcclusion"] = { &processMultiBounceAO, Type::BOOL };
|
|
mParameters["specularAmbientOcclusion"] = { &processSpecularAmbientOcclusion, Type::STRING };
|
|
mParameters["domain"] = { &processDomain, Type::STRING };
|
|
mParameters["refractionMode"] = { &processRefractionMode, Type::STRING };
|
|
mParameters["refractionType"] = { &processRefractionType, Type::STRING };
|
|
mParameters["framebufferFetch"] = { &processFramebufferFetch, Type::BOOL };
|
|
mParameters["vertexDomainDeviceJittered"] = { &processVertexDomainDeviceJittered, Type::BOOL };
|
|
mParameters["legacyMorphing"] = { &processLegacyMorphing, Type::BOOL };
|
|
mParameters["outputs"] = { &processOutputs, Type::ARRAY };
|
|
mParameters["quality"] = { &processQuality, Type::STRING };
|
|
mParameters["customSurfaceShading"] = { &processCustomSurfaceShading, Type::BOOL };
|
|
mParameters["featureLevel"] = { &processFeatureLevel, Type::NUMBER };
|
|
mParameters["groupSize"] = { &processGroupSizes, Type::ARRAY };
|
|
mParameters["stereoscopicType"] = { &processStereoscopicType, Type::STRING };
|
|
mParameters["useDefaultDepthVariant"] = { &processUseDefaultDepthVariant, Type::BOOL };
|
|
mParameters["linearFog"] = { &processLinearFog, Type::BOOL };
|
|
mParameters["shadowFarAttenuation"] = { &processShadowFarAttenuation, Type::BOOL };
|
|
mParameters["apiLevel"] = { &processApiLevel, Type::NUMBER };
|
|
}
|
|
|
|
utils::Status ParametersProcessor::process(MaterialBuilder& builder, const JsonishObject& jsonObject) {
|
|
for(const auto& entry : jsonObject.getEntries()) {
|
|
const std::string& key = entry.first;
|
|
const JsonishValue* field = entry.second;
|
|
if (mParameters.find(key) == mParameters.end()) {
|
|
std::cerr << "Ignoring config entry (unknown key): \"" << key << "\"" << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// Verify type is what was expected.
|
|
if (mParameters.at(key).rootAssert != field->getType()) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "Value for key:\"" << key << "\" is not what was expected."
|
|
<< utils::io::endl;
|
|
errorMessage << "Got :\"" << JsonishValue::typeToString(field->getType())
|
|
<< "\" but expected '"
|
|
<< JsonishValue::typeToString(mParameters.at(key).rootAssert) << "'";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
auto fPointer = mParameters[key].callback;
|
|
if (utils::Status status = fPointer(builder, *field); !status.isOk()) {
|
|
std::cerr << "Error while processing material json, key:\"" << key << "\"" << std::endl;
|
|
return status;
|
|
}
|
|
}
|
|
return utils::Status::ok();;
|
|
}
|
|
|
|
utils::Status ParametersProcessor::process(filamat::MaterialBuilder& builder, const std::string& key, const std::string& value) {
|
|
if (mParameters.find(key) == mParameters.end()) {
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "Ignoring config entry (unknown key): \"" << key << "\"";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
std::unique_ptr<JsonishValue> var;
|
|
switch (mParameters.at(key).rootAssert) {
|
|
case JsonishValue::Type::BOOL: {
|
|
std::string lower;
|
|
std::transform(value.begin(), value.end(), std::back_inserter(lower), ::tolower);
|
|
if (lower.empty() || lower == "false" || lower == "f" || lower == "0") {
|
|
var = std::make_unique<JsonishBool>(false);
|
|
}
|
|
else {
|
|
var = std::make_unique<JsonishBool>(true);
|
|
}
|
|
break;
|
|
}
|
|
case JsonishValue::Type::NUMBER:
|
|
var = std::make_unique<JsonishNumber>(std::stof(value));
|
|
break;
|
|
case JsonishValue::Type::STRING:
|
|
var = std::make_unique<JsonishString>(value);
|
|
break;
|
|
default:
|
|
utils::io::sstream errorMessage;
|
|
errorMessage << "Unsupported type: \""
|
|
<< JsonishValue::typeToString(mParameters.at(key).rootAssert) << "\"";
|
|
return utils::Status::invalidArgument(errorMessage.c_str());
|
|
}
|
|
|
|
auto fPointer = mParameters[key].callback;
|
|
if (utils::Status status = fPointer(builder, *var); !status.isOk()) {
|
|
std::cerr << "Error while processing material param, key:\"" << key << "\"" << std::endl;
|
|
return status;
|
|
}
|
|
return utils::Status::ok();
|
|
}
|
|
|
|
} // namespace matp
|