1#include "MaterialReader.h"
3#include <glm/gtc/type_ptr.hpp>
5#include "Foundation/Logging/Logger.h"
10#include "Resources/ResourceStore.h"
11#include "Resources/MaterialManager.h"
13#include "Utilities/Parsing.h"
15#include "Renderer/IRenderer.h"
23 bool traceParsing =
false;
27 const size_t spaceIndex = valueString.
find(
" ");
32 value.type = MaterialDataType::Unknown;
36 switch (typeString.
hash()) {
61 for (
size_t i = 0; i + 1 < typeString.
size(); i++) {
62 if ((typeString[i] ==
'[') && (
'0' <= typeString[i + 1]) && (typeString[i + 1] <=
'9')) {
64 size_t dimension = typeString[i + 1] -
'0';
65 for (
size_t j = i + 2; j < typeString.
size(); j++) {
66 if (
'0' <= typeString[j] && typeString[j] <=
'9') {
67 dimension = 10 * dimension + (typeString[j] -
'0');
69 else if ((typeString[j] ==
']') && (j + 1 == typeString.
size())) {
74 value.dimension = dimension;
75 value.dimensionString = typeString.
substr(i + 1, j - i - 1).
to_string();
80 value.dimension = dimension;
81 value.dimensionString = typeString.
substr(i + 1, j - i - 1).
to_string();
95 LOG_ERROR(logger,
"Failed to parse datatype '%.*s'", StringViewFormat(valueString));
103 thread_local static std::vector<Cogs::StringView> tokens;
106 tokenize(valueString, tokens);
108 if (tokens.empty()) {
109 LOG_ERROR(logger,
"Empty property value");
113 if (!parseType(tokens[0], value,
false)) {
117 tokens.erase(tokens.begin());
119 value.flags = MaterialPropertyFlags::None;
120 while (!tokens.empty()) {
121 if (tokens[0] ==
"srgb") {
122 value.flags = value.flags | MaterialPropertyFlags::sRGB;
124 else if (tokens[0] ==
"normalized") {
125 value.flags = value.flags | MaterialPropertyFlags::Normalized;
127 else if (tokens[0] ==
"partitionOfUnity") {
128 value.flags = value.flags | MaterialPropertyFlags::PartitionOfUnity;
133 tokens.erase(tokens.begin());
136 switch (value.type) {
137 case MaterialDataType::Float:
138 value.floatValue = float(0.f);
139 if (!tokens.empty()) {
140 parseValues(&value.floatValue, tokens, 1);
143 case MaterialDataType::Float2:
144 value.float2Value = glm::vec2();
145 if (!tokens.empty()) {
146 parseValues(glm::value_ptr(value.float2Value), tokens, 2);
149 case MaterialDataType::Float3:
150 value.float3Value = glm::vec3();
151 if (!tokens.empty()) {
152 parseValues(glm::value_ptr(value.float3Value), tokens, 3);
155 case MaterialDataType::Float4:
156 value.float4Value = glm::vec4();
157 if (!tokens.empty()) {
158 parseValues(glm::value_ptr(value.float4Value), tokens, 4);
161 case MaterialDataType::Float4x4:
162 value.float4x4Value = glm::mat4();
163 if (!tokens.empty()) {
164 parseValues(glm::value_ptr(value.float4x4Value), tokens, 16);
168 case MaterialDataType::Float4Array:
169 if (value.dimension ==
size_t(-1)) {
170 LOG_ERROR(logger,
"Property buffer Float4Array element without known size");
173 value.float4Value = glm::vec4();
174 if (!tokens.empty()) {
175 parseValues(glm::value_ptr(value.float4Value), tokens, 4);
178 case MaterialDataType::Float4x4Array:
179 if (value.dimension ==
size_t(-1)) {
180 LOG_ERROR(logger,
"Property buffer Float4x4Array element without known size");
183 value.float4x4Value = glm::mat4();
184 if (!tokens.empty()) {
185 parseValues(glm::value_ptr(value.float4x4Value), tokens, 16);
189 case MaterialDataType::UInt:
191 if (!tokens.empty()) {
192 parseValues(&value.uintValue, tokens, 1);
195 case MaterialDataType::UInt2:
196 value.uint2Value = glm::ivec2();
197 if (!tokens.empty()) {
198 parseValues(glm::value_ptr(value.uint2Value), tokens, 2);
201 case MaterialDataType::UInt3:
202 value.uint3Value = glm::ivec3();
203 if (!tokens.empty()) {
204 parseValues(glm::value_ptr(value.uint3Value), tokens, 3);
207 case MaterialDataType::UInt4:
208 value.uint4Value = glm::ivec4();
209 if (!tokens.empty()) {
210 parseValues(glm::value_ptr(value.uint4Value), tokens, 4);
213 case MaterialDataType::Int:
214 value.intValue = int(0);
215 if (!tokens.empty()) {
216 parseValues(&value.intValue, tokens, 1);
219 case MaterialDataType::Int2:
220 value.int2Value = glm::ivec2();
221 if (!tokens.empty()) {
222 parseValues(glm::value_ptr(value.int2Value), tokens, 2);
225 case MaterialDataType::Int3:
226 value.int3Value = glm::ivec3();
227 if (!tokens.empty()) {
228 parseValues(glm::value_ptr(value.int3Value), tokens, 3);
231 case MaterialDataType::Int4:
232 value.int4Value = glm::ivec4();
233 if (!tokens.empty()) {
234 parseValues(glm::value_ptr(value.int4Value), tokens, 4);
238 case MaterialDataType::Bool:
239 value.boolValue =
false;
240 if (!tokens.empty()) {
241 parseValues(&value.boolValue, tokens, 1);
245 LOG_ERROR(logger,
"Property buffer does not support %s elements", DataTypeNames[
size_t(value.type)]);
254 for (
auto& m : jsonBuffer.GetObject()) {
255 auto propertyKey = toKey(m.name);
258 parsedValue.name = propertyKey.to_string();
260 if (m.value.IsString()) {
261 if (!parsePropertyValue(toKey(m.value), parsedValue)) {
266 LOG_ERROR(logger,
"Material property %.*s must be specified as strings.", StringViewFormat(propertyKey));
270 buffer.values.push_back(parsedValue);
275 [[nodiscard]]
bool readMaterialProperty(
const std::string& propertyName,
280 std::string propertyType;
282 switch (value.GetType())
286 if (traceParsing) LOG_DEBUG(logger,
" Found buffer %s.", propertyName.c_str());
288 materialProperties.buffers.emplace_back();
289 auto& buffer = materialProperties.buffers.back();
290 buffer.name = propertyName;
291 buffer.isPerInstance = !isShared;
292 return readMaterialBuffer(value, buffer);
296 propertyType = toString(value);
302 auto arr = value.GetArray();
305 LOG_ERROR(logger,
"Property array cannot be empty %s.", propertyName.c_str());
309 propertyType = toString(arr[0]);
317 bool is_array =
false;
318 uint32_t arr_size = 0;
319 auto arr_i = propertyType.find(
'[');
320 if (arr_i != std::string::npos) {
322 auto arr_ii = propertyType.find(
']');
323 auto n = propertyType.substr(arr_i + 1, arr_ii);
324 arr_size = std::stoul(n);
325 propertyType = propertyType.substr(0, arr_i);
338 LOG_ERROR(logger,
"Unsupported property type '%s'", propertyType.c_str());
342 .name = propertyName,
343 .precision = MaterialTypePrecision::Default,
344 .isPerInstance = !isShared,
348 .arraySize = arr_size,
349 .dimensions = dimensions
352 if (value.IsArray()) {
355 for (
const auto& item : value.GetArray()) {
356 if (!item.IsString()) {
357 LOG_WARNING(logger,
"Non-string texture options encountered.");
364 case Cogs::hash(
"texture1d"): [[fallthrough]];
365 case Cogs::hash(
"texture2d"): [[fallthrough]];
366 case Cogs::hash(
"texture2darray"): [[fallthrough]];
367 case Cogs::hash(
"texture3d"): [[fallthrough]];
373 textureDefinition.isSrgb = true;
378 textureDefinition.isDepth = true;
383 textureDefinition.precision = MaterialTypePrecision::Low;
386 textureDefinition.precision = MaterialTypePrecision::Medium;
389 textureDefinition.precision = MaterialTypePrecision::High;
431 LOG_WARNING(logger,
"Unrecognized texture option '%s' encountered.", item.GetString());
442 if (!jsonObject.IsString()) {
443 LOG_ERROR(logger,
"Interpolation modifier is not a JSON string");
448 switch (value.
hash()) {
465 LOG_ERROR(logger,
"Unrecognized interpolation modifier '%.*s'", StringViewFormat(value));
471 [[nodiscard]]
bool addVariantFlag(Cogs::Core::ShaderVarientFlags& flags,
const Cogs::StringView& name)
473 switch (name.
hash()) {
474 case Cogs::hash(
"DepthOnly"): flags |= ShaderVarientFlags::DepthOnly;
break;
475 case Cogs::hash(
"NoShading"): flags |= ShaderVarientFlags::NoShading;
break;
476 case Cogs::hash(
"ReverseDepth"): flags |= ShaderVarientFlags::ReverseDepth;
break;
478 LOG_ERROR(logger,
"Unrecognized variant flag '%.*s'", StringViewFormat(name));
484 [[nodiscard]]
bool parseShaderInterface(
const Value& jsonObject,
ShaderInterfaceDefinition& interfaceDefinition, uint8_t inheritanceLevel,
bool allowFormat)
486 if (!jsonObject.IsObject()) {
487 LOG_ERROR(logger,
"Shader interface is not an JSON object");
491 for (
auto& m : jsonObject.GetObject()) {
498 auto arr = obj.GetArray();
502 if (!parseType(toKey(arr[0]), value, allowFormat))
return false;
504 if (value.dimension ==
size_t(-1)) {
505 if (arr[1].IsString()) {
506 value.dimensionString = toString(arr[1]);
509 value.dimension = arr[1].GetInt();
513 if (arr.Size() >= 2) {
514 if (arr[1].IsString()) {
515 value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::None;
516 value.semantic.slot = 0;
517 Cogs::StringView semanticName(arr[1].GetString(), arr[1].GetStringLength());
520 for (
size_t i = 0; i < semanticName.size(); i++) {
521 if (
'0' <= semanticName[i] && semanticName[i] <=
'9') {
522 value.semantic.slot = semanticName[i] -
'0';
524 while ((k < semanticName.size()) &&
525 (
'0' <= semanticName[k]) &&
526 (semanticName[k] <=
'9'))
528 value.semantic.slot = uint8_t(10u * value.semantic.slot + (semanticName[k] -
'0'));
530 semanticName = semanticName.substr(0, i);
534 switch (semanticName.hashLowercase()) {
549 LOG_ERROR(logger,
"Unrecognized semantic '%.*s'", StringViewFormat(semanticName));
554 LOG_ERROR(logger,
"Semantic is not a string");
559 if (arr.Size() >= 3) {
560 auto& modifiers = arr[2];
561 if (modifiers.IsString()) {
562 if (!parseInterpolationModifier(modifiers, value))
return false;
564 else if (modifiers.IsArray()) {
565 for (
auto& modifier : modifiers.GetArray()) {
566 if (!parseInterpolationModifier(modifier, value))
return false;
572 else if (obj.IsString()) {
573 if (!parseType(toKey(obj), value, allowFormat))
return false;
577 value.inheritanceLevel = inheritanceLevel;
579 interfaceDefinition.members.push_back(value);
585 [[nodiscard]]
bool parseRequiresStatement(
const Value& requiresStatement, std::vector<ShaderVariantRequirement>& requirements,
const Cogs::StringView& name)
587 if (requiresStatement.IsString()) {
590 else if (requiresStatement.IsArray()) {
591 for (
auto& item : requiresStatement.GetArray()) {
592 if (item.IsString()) {
596 LOG_ERROR(logger,
"Material %.*s has non-string element in requires array.", StringViewFormat(name));
601 else if (requiresStatement.IsObject()) {
602 for (
auto& item : requiresStatement.GetObject()) {
603 if (item.value.IsString()) {
607 LOG_ERROR(logger,
"Material %.*s has non-string value in requires object.", StringViewFormat(name));
613 LOG_ERROR(logger,
"Material %.*s has unexpected value in requires object.", StringViewFormat(name));
621 if (triggersStatement.IsString()) {
624 else if (triggersStatement.IsArray()) {
625 for (
auto& item : triggersStatement.GetArray()) {
626 if (item.IsString()) {
630 LOG_ERROR(logger,
"Material variant %s has non-string element in triggers array.", variant.name.c_str());
635 else if (triggersStatement.IsObject()) {
636 for (
auto& item : triggersStatement.GetObject()) {
637 if (item.value.IsString()) {
641 LOG_ERROR(logger,
"Material variant %s has non-string value in triggers object.", variant.name.c_str());
647 LOG_ERROR(logger,
"Material variant %s has unexpected value in triggers object.", variant.name.c_str());
655 switch (variant.type) {
657 case ShaderVariantType::Bool:
658 if (!defaultValue.IsBool()) {
659 LOG_ERROR(logger,
"Variant %s defaultValue property is not a bool", variant.name.c_str());
662 variant.defaultValue = defaultValue.GetBool();
665 case ShaderVariantType::Int:
666 if (!defaultValue.IsInt()) {
667 LOG_ERROR(logger,
"Variant %s defaultValue property is not an int", variant.name.c_str());
670 variant.defaultValue = defaultValue.GetInt();
673 case ShaderVariantType::Enum:
674 if (!defaultValue.IsString()) {
675 LOG_ERROR(logger,
"Variant %s defaultValue property is not a string", variant.name.c_str());
680 for (
size_t i = 0; i < variant.values.size(); ++i) {
681 if (variant.values[i].key == val) {
682 variant.defaultValue =
static_cast<int>(i);
686 LOG_ERROR(logger,
"Variant %s defaultValue '%.*s' is not a valid enum value", variant.name.c_str(), StringViewFormat(val));
693 case ShaderVariantType::Format:
694 if (!defaultValue.IsString()) {
695 LOG_ERROR(logger,
"Variant %s defaultValue property is not a string", variant.name.c_str());
700 Cogs::DataFormat parsedValue = Cogs::parseDataFormat(value_);
701 if (parsedValue == Cogs::DataFormat::Unknown) {
702 LOG_ERROR(logger,
"Failed to parse format '%.*s'", StringViewFormat(value_));
705 variant.defaultValue = size_t(parsedValue);
708 case ShaderVariantType::String:
709 if (!defaultValue.IsString()) {
710 LOG_ERROR(logger,
"Variant %s defaultValue property is not a string", variant.name.c_str());
713 variant.defaultString = toString(defaultValue);
724 auto it = variantObject.FindMember(
"type");
725 if (it == variantObject.MemberEnd()) {
726 LOG_ERROR(logger,
"Material variant %s missing type.", variant.name.c_str());
730 bool acceptValueString =
false;
731 bool acceptValues =
false;
732 bool acceptRequires =
false;
733 bool acceptTriggers =
false;
735 switch (type.
hash()) {
737 variant.type = ShaderVariantType::Bool;
738 variant.defaultValue = 0;
739 acceptValueString =
true;
740 acceptRequires =
true;
741 acceptTriggers =
true;
745 variant.type = ShaderVariantType::Int;
746 variant.defaultValue = 0;
747 acceptValueString =
true;
751 variant.type = ShaderVariantType::Format;
752 variant.defaultValue = 0;
753 acceptValueString =
true;
757 variant.type = ShaderVariantType::Enum;
762 variant.type = ShaderVariantType::String;
763 variant.defaultValue = -1;
764 acceptValueString =
true;
768 LOG_ERROR(logger,
"Unknown variant type %.*s.", StringViewFormat(type));
772 bool hasValueString =
false;
773 variant.flags = ShaderVarientFlags::None;
774 for (
auto& variantItem : variantObject.GetObject()) {
781 if (!parseDefaultValueStatement(variantItem.value, variant)) return false;
785 if (!acceptValueString) {
786 LOG_ERROR(logger,
"Variant type does not support value keyword");
789 if (!variantItem.value.IsString()) {
790 LOG_ERROR(logger,
"Variant value is not a string");
793 variant.value = toString(variantItem.value);
794 hasValueString =
true;
799 LOG_ERROR(logger,
"Variant type does not support value keyword");
802 for (
auto& v : variantItem.value.GetObject()) {
803 if (!v.value.IsString()) {
804 LOG_ERROR(logger,
"Variant %s values contains a non-string item", variant.name.c_str());
807 variant.values.emplace_back(
ShaderVariantEnum{ variant.values.size(), toString(v.name), toString(v.value) });
812 case
Cogs::hash(
"requires"):
813 if (!acceptRequires) {
814 LOG_ERROR(logger,
"Variant type does not support requires keyword");
817 if (!parseRequiresStatement(variantItem.value, variant.requirements, variant.name))
return false;
820 case
Cogs::hash(
"triggers"):
821 if (!acceptTriggers) {
822 LOG_ERROR(logger,
"Variant type does not support triggers keyword");
825 if (!parseTriggersStatement(variantItem.value, variant))
return false;
828 if (!parseShaderInterface(variantItem.value, variant.vertexInterface, 0, variant.type == ShaderVariantType::Format)) return false;
831 if (!parseShaderInterface(variantItem.value, variant.geometryInterface, 0, false)) return false;
834 if (!parseShaderInterface(variantItem.value, variant.surfaceInterface, 0, false)) return false;
837 if (variantItem.value.IsString()) {
838 if (!addVariantFlag(variant.flags, toView(variantItem.value)))
return false;
840 else if (variantItem.value.IsArray()) {
841 for (
auto& element : variantItem.value.GetArray()) {
842 if (!element.IsString()) {
843 LOG_ERROR(logger,
"Variant flag is not a string");
846 if (!addVariantFlag(variant.flags, toView(element)))
return false;
850 LOG_ERROR(logger,
"Variant flag is neither string nor array");
855 LOG_ERROR(logger,
"Unexpected variant keyword %.*s", StringViewFormat(keyword));
860 switch (variant.type) {
861 case ShaderVariantType::Bool:
862 case ShaderVariantType::Int:
863 case ShaderVariantType::Format:
864 case ShaderVariantType::String:
865 if (!hasValueString) {
866 LOG_ERROR(logger,
"Variant is missing value keyword");
870 case ShaderVariantType::Enum:
871 if (variant.values.empty()) {
872 LOG_ERROR(logger,
"Variant enum type has no values");
883 [[nodiscard]]
bool readMaterialOptions(
const Value& jsonMaterialOptions,
MaterialDefinition& materialDefinition)
885 if (jsonMaterialOptions.IsObject()) {
886 for (
auto& o : jsonMaterialOptions.GetObject()) {
887 if (o.value.IsString()) {
888 materialDefinition.options[toString(o.name)] = toString(o.value);
891 LOG_ERROR(logger,
"readMaterialOptions: Item value is not a JSON string");
898 LOG_ERROR(logger,
"readMaterialOptions: value is not a JSON object");
903 [[nodiscard]]
bool matchGraphicsType(
bool& include,
Cogs::GraphicsDeviceType deviceType,
const Value& jsonDeviceType)
905 if (!jsonDeviceType.IsString()) {
906 LOG_ERROR(logger,
"matchGraphicsType: device type is not a JSON string");
910 switch (toView(jsonDeviceType).hash())
913 case
Cogs::hash(
"Direct3D11"):
917 case
Cogs::hash(
"Direct3D12"):
924 case
Cogs::hash(
"OpenGL20"):
928 case
Cogs::hash(
"OpenGLES30"):
935 LOG_ERROR(logger,
"matchGraphicsType: Unknown device type \"%s\"", jsonDeviceType.GetString());
941 [[nodiscard]]
bool readShaderDefinition(
const Value& jsonObject,
ShaderDefinition& shaderDefinition)
943 if (!jsonObject.IsObject()) {
944 LOG_ERROR(logger,
"readShaderDefinition: value is not a JSON object");
947 for (
auto& m : jsonObject.GetObject()) {
949 switch (key.
hash()) {
951 shaderDefinition.entryPoint = toString(m.value);
954 shaderDefinition.customSourcePath = toString(m.value);
957 if (m.value.IsObject()) {
958 for (
auto& a : m.value.GetObject()) {
959 shaderDefinition.attributes.emplace_back(
AttributeDefinition{ toString(a.name), toString(a.value) });
963 LOG_ERROR(logger,
"readShaderDefinition: attributes value is not a JSON array");
968 LOG_ERROR(logger,
"readShaderDefinition: Unrecognized key '%.*s'", StringViewFormat(key));
975 [[nodiscard]]
bool readMaterialMetadata(
Context* context,
const Value& jsonMaterial,
MaterialDefinition& materialDefinition);
977 [[nodiscard]]
bool readMaterialEffect(
const Value& jsonMaterial,
EffectDefinition& effectDefinition);
982 if (!jsonPermuation.IsObject()) {
983 LOG_ERROR(logger,
"readPermutation: %s/%s: Permutation is not an JSON object", materialDefinition.name.c_str(), permutationName.c_str());
986 if (jsonPermuation.HasMember(
"devices")) {
987 auto& jsonDeviceType = jsonPermuation[
"devices"];
988 bool include =
false;
989 if (jsonDeviceType.IsString()) {
991 if (!matchGraphicsType(include, deviceType, jsonDeviceType))
return false;
993 else if (jsonDeviceType.IsArray()) {
995 for (
auto& item : jsonDeviceType.GetArray()) {
996 if (!item.IsString()) {
997 LOG_ERROR(logger,
"readPermuation: %s/%s: Devices array item is not a JSON string", materialDefinition.name.c_str(), permutationName.c_str());
1000 if (!matchGraphicsType(include, deviceType, item))
return false;
1004 LOG_ERROR(logger,
"readPermuation: %s@%s: Devices are neither JSON string nor array", materialDefinition.name.c_str(), permutationName.c_str());
1013 static int matId = 0;
1015 materialDefinition.permutations.emplace_back();
1017 permutationDefinition.name = materialDefinition.name + std::to_string(matId++) + permutationName;
1018 permutationDefinition.permutationIndex = materialDefinition.permutations.size() - 1;
1019 permutationDefinition.permutationName = permutationName;
1021 if (!readMaterialMetadata(context, jsonPermuation, permutationDefinition))
return false;
1022 if (!readMaterialEffect(jsonPermuation, permutationDefinition.effect))
return false;
1028 [[nodiscard]]
bool readMaterialMetadata(
Context* context,
const Value& jsonMaterial,
MaterialDefinition& materialDefinition)
1030 if (!jsonMaterial.IsObject()) {
1031 LOG_ERROR(logger,
"readMaterialMetadata: Material is not an JSON object");
1035 for (
auto& m : jsonMaterial.GetObject()) {
1038 switch (memberKey.
hash()) {
1041 if (m.value.IsString()) {
1042 materialDefinition.name = toString(m.value);
1043 if (traceParsing) LOG_DEBUG(logger,
"Found new material definition %s.", materialDefinition.name.c_str());
1046 LOG_ERROR(logger,
"readMaterialMetadata: Material name value is not an JSON string");
1051 if (m.value.IsString()) {
1053 LOG_WARNING(logger,
"readMaterialMetadata: %s: Unrecognized flag '%.*s'", materialDefinition.name.c_str(), StringViewFormat(flagsString));
1056 LOG_ERROR(logger,
"readMaterialMetadata: %s: Material flags value is not an JSON string", materialDefinition.name.c_str());
1061 if (m.value.IsString()) {
1062 materialDefinition.inherits = toString(m.value);
1063 if (traceParsing) LOG_DEBUG(logger,
"Found material base type %s.", materialDefinition.inherits.c_str());
1066 LOG_ERROR(logger,
"readMaterialMetadata: %s: Material inherits value is not an JSON string", materialDefinition.name.c_str());
1071 case
Cogs::hash(
"requires"):
1072 if (!parseRequiresStatement(m.value, materialDefinition.requirements, materialDefinition.name)) return false;
1076 if (traceParsing) LOG_DEBUG(logger,
"Found material variants.");
1077 if (!readMaterialVariants(context, m.value, materialDefinition.variants,
false))
return false;
1081 if (traceParsing) LOG_DEBUG(logger,
"Found material variants.");
1082 if (!readMaterialVariants(context, m.value, materialDefinition.variants,
true))
return false;
1086 if (traceParsing) LOG_DEBUG(logger,
"Found material options.");
1087 if (!readMaterialOptions(m.value, materialDefinition))
return false;
1091 if (traceParsing) LOG_DEBUG(logger,
"Found material properties.");
1092 if (!readMaterialProperties(context, m.value, materialDefinition.properties,
false))
return false;
1096 if (traceParsing) LOG_DEBUG(logger,
"Found shared material properties.");
1097 if (!readMaterialProperties(context, m.value, materialDefinition.properties,
true))
return false;
1101 if (traceParsing) LOG_DEBUG(logger,
"Found effect permutations.");
1102 if (m.value.IsObject()) {
1105 for (
auto& p : m.value.GetObject()) {
1106 auto permutationName = toString(p.name);
1108 if (traceParsing) LOG_DEBUG(logger,
"Found effect permutation %s.", permutationName.c_str());
1110 if (p.value.IsArray()) {
1111 for (
auto& profile : p.value.GetArray()) {
1112 bool included =
false;
1113 if (!readPermutation(context, included, deviceType, profile, materialDefinition, permutationName))
return false;
1114 if (included)
break;
1118 bool included =
false;
1119 if (!readPermutation(context, included, deviceType, p.value, materialDefinition, permutationName))
return false;
1124 LOG_ERROR(logger,
"readMaterialMetadata: %s: Material permutations value is not an JSON object", materialDefinition.name.c_str());
1130 if (m.value.IsArray()) {
1132 for (
auto& element : m.value.GetArray()) {
1134 if (element.IsString()) {
1137 if (enginePermutation) {
1138 materialDefinition.enginePermutationMask |= size_t(1) << enginePermutation->getIndex();
1141 LOG_ERROR(logger,
"readMaterialMetadata: %s: unrecognized engine permutation '%.*s'", materialDefinition.name.c_str(), StringViewFormat(str));
1146 LOG_ERROR(logger,
"readMaterialMetadata: %s: Material enginePermutations item is not an JSON string", materialDefinition.name.c_str());
1152 LOG_ERROR(logger,
"readMaterialMetadata: %s: Material enginePermutations value is not an JSON array", materialDefinition.name.c_str());
1158 case
Cogs::hash(
"vertexFunction"):
1159 case
Cogs::hash(
"hullInterface"):
1160 case
Cogs::hash(
"hullFunction"):
1161 case
Cogs::hash(
"domainInterface"):
1162 case
Cogs::hash(
"domainFunction"):
1163 case
Cogs::hash(
"geometryInterface"):
1164 case
Cogs::hash(
"geometryFunction"):
1165 case
Cogs::hash(
"surfaceInterface"):
1166 case
Cogs::hash(
"surfaceFunction"):
1167 case
Cogs::hash(
"definitions"):
1168 case
Cogs::hash(
"defines"):
1176 LOG_ERROR(logger,
"readMaterialMetadata: %s: Unrecognized key '%.*s'", materialDefinition.name.c_str(), StringViewFormat(memberKey));
1183 [[nodiscard]]
bool readMaterialEffect(
const Value& jsonMaterial,
EffectDefinition& effectDefinition)
1185 if (!jsonMaterial.IsObject()) {
1186 LOG_ERROR(logger,
"readMaterialEffect: Material is not an JSON object");
1190 for (
auto& m : jsonMaterial.GetObject()) {
1193 switch (memberKey.
hash()) {
1196 if (traceParsing) LOG_DEBUG(logger,
"Found vertex interface.");
1197 if (!parseShaderInterface(m.value, effectDefinition.vertexShader.shaderInterface, 1,
false))
return false;
1201 if (traceParsing) LOG_DEBUG(logger,
"Found vertex function.");
1202 if (!readShaderDefinition(m.value, effectDefinition.vertexShader))
return false;
1206 if (traceParsing) LOG_DEBUG(logger,
"Found hull interface.");
1207 if (!parseShaderInterface(m.value, effectDefinition.hullShader.shaderInterface, 1,
false))
return false;
1211 if (traceParsing) LOG_DEBUG(logger,
"Found hull function.");
1212 if (!readShaderDefinition(m.value, effectDefinition.hullShader))
return false;
1216 if (traceParsing) LOG_DEBUG(logger,
"Found domain interface.");
1217 if (!parseShaderInterface(m.value, effectDefinition.domainShader.shaderInterface, 1,
false))
return false;
1221 if (traceParsing) LOG_DEBUG(logger,
"Found domain function.");
1222 if (!readShaderDefinition(m.value, effectDefinition.domainShader))
return false;
1226 if (traceParsing) LOG_DEBUG(logger,
"Found geometry interface.");
1227 if (!parseShaderInterface(m.value, effectDefinition.geometryShader.shaderInterface, 1,
false))
return false;
1231 if (traceParsing) LOG_DEBUG(logger,
"Found geometry function.");
1232 if (!readShaderDefinition(m.value, effectDefinition.geometryShader))
return false;
1236 if (traceParsing) LOG_DEBUG(logger,
"Found surface interface.");
1237 if (!parseShaderInterface(m.value, effectDefinition.pixelShader.shaderInterface, 1,
false))
return false;
1241 if (traceParsing) LOG_DEBUG(logger,
"Found surface function.");
1242 if (!readShaderDefinition(m.value, effectDefinition.pixelShader))
return false;
1246 case
Cogs::hash(
"defines"):
1247 if (m.value.IsObject()) {
1248 for (
auto& d : m.value.GetObject()) {
1249 if (d.value.IsString()) {
1253 LOG_ERROR(logger,
"readMaterialEffect: %.*s item %.*s value is not a JSON string", StringViewFormat(memberKey), StringViewFormat(toView(d.name)));
1259 LOG_ERROR(logger,
"readMaterialEffect: %.*s is not a JSON object", StringViewFormat(memberKey));
1265 case
Cogs::hash(
"flags"):
1266 case
Cogs::hash(
"inherits"):
1267 case
Cogs::hash(
"variants"):
1268 case
Cogs::hash(
"sharedVariants"):
1269 case
Cogs::hash(
"options"):
1270 case
Cogs::hash(
"require"):
1271 case
Cogs::hash(
"requires"):
1272 case
Cogs::hash(
"properties"):
1273 case
Cogs::hash(
"sharedProperties"):
1274 case
Cogs::hash(
"permutations"):
1275 case
Cogs::hash(
"enginePermutations"):
1283 LOG_ERROR(logger,
"readMaterialEffect: Unrecognized key '%.*s'", StringViewFormat(memberKey));
1294 definition.shaderInterface.members.emplace_back(sourceMember);
1295 definition.shaderInterface.members.back().inheritanceLevel += 2;
1298 if (definition.entryPoint.empty()) {
1299 definition.customSourcePath = source.customSourcePath;
1300 definition.loadPath = source.loadPath;
1301 definition.entryPoint = source.entryPoint;
1302 definition.attributes = source.attributes;
1309 definition.flags &= source.flags;
1312 for (
auto& buffer : source.properties.buffers) {
1313 definition.properties.buffers.push_back(buffer);
1316 for (
auto& texture : source.properties.textures) {
1317 definition.properties.textures.push_back(texture);
1321 definition.requirements.insert(definition.requirements.end(),
1322 source.requirements.begin(),
1323 source.requirements.end());
1325 if (!inheritMaterialVariants(definition.variants, source.variants, definition.name, source.name, properties))
return false;
1330 inheritShaderDefinition(parentEffect.vertexShader, definition.effect.vertexShader);
1331 inheritShaderDefinition(parentEffect.hullShader, definition.effect.hullShader);
1332 inheritShaderDefinition(parentEffect.domainShader, definition.effect.domainShader);
1333 inheritShaderDefinition(parentEffect.geometryShader, definition.effect.geometryShader);
1334 inheritShaderDefinition(parentEffect.pixelShader, definition.effect.pixelShader);
1341 for (
auto& buffer : definition.properties.buffers) {
1342 std::sort(buffer.values.begin(), buffer.values.end(), [](
const auto& a,
const auto& b)
1344 return DataTypeSizes[(size_t)a.type] > DataTypeSizes[(size_t)b.type];
1348 auto& pixelShaderInterface = definition.effect.pixelShader.shaderInterface;
1350 std::sort(pixelShaderInterface.members.begin(), pixelShaderInterface.members.end(), [](
const auto& a,
const auto& b)
1352 return DataTypeSizes[(size_t)a.type] > DataTypeSizes[(size_t)b.type];
1358 if (definition.name ==
"MaterialBase")
return true;
1360 if (definition.inherits.empty()) {
1361 definition.inherits =
"MaterialBase";
1364 const MaterialDefinition* parent = context->materialDefinitionManager->get(definition.inherits);
1366 LOG_ERROR(logger,
"Invalid parent material: %s.", definition.inherits.c_str());
1370 if (!inheritDefinition(*parent, definition))
return false;
1373 if (!inheritDefinition(definition, p))
return false;
1375 if (p.permutationName.size()) {
1378 for (
auto& pp : parent->permutations) {
1379 if (pp.permutationName == p.permutationName) {
1380 parentPermutation = &pp;
1384 if (parentPermutation) {
1385 if (!inheritDefinition(*parentPermutation, p,
false))
return false;
1398bool Cogs::Core::readMaterialProperties(
Context* ,
const Value& jsonValue,
MaterialProperties& materialProperties,
bool isShared)
1400 if (!jsonValue.IsObject()) {
1401 LOG_ERROR(logger,
"readMaterialProperties: Material properties is not a JSON object");
1404 for (
auto& m : jsonValue.GetObject()) {
1405 if (!readMaterialProperty(toString(m.name), m.value, materialProperties, isShared)) {
1413bool Cogs::Core::readMaterialVariants(
Context* ,
const Value& value, std::vector<ShaderVariantDefinition>& variants,
bool isShared)
1415 if (!value.IsObject()) {
1416 LOG_ERROR(logger,
"readMaterialVariants: Variant value is not an object");
1420 for (
auto& o : value.GetObject()) {
1422 variant.index = variants.size() - 1;
1423 variant.name = toString(o.name);
1424 variant.isShared = isShared;
1426 if (o.value.IsObject()) {
1427 if (!readMaterialVariant(o.value, variant)) {
1432 if (o.value.IsString()) {
1433 variant.value = o.value.GetString();
1435 else if (o.value.IsBool()) {
1436 variant.value = o.value.GetBool() ?
"true" :
"false";
1439 LOG_ERROR(logger,
"readMaterialVariants: Variant %s value is neither object, string nor bool.", variant.name.c_str());
1442 variant.valueOnly =
true;
1450 Document document = parseJson(context, path, JsonParseFlags::None);
1452 if (!document.IsObject()) {
1453 LOG_ERROR(logger,
"Cannot read invalid material.");
1457 bool isDefined =
false;
1458 for (
auto & m : document.GetObject()) {
1459 auto & jsonValue = m.value;
1461 switch (toView(m.name).
hash())
1465 LOG_ERROR(logger,
"Material already defined.");
1470 if (!readMaterialMetadata(context, jsonValue, definition))
return false;
1472 if (!definition.permutations.empty()) {
1473 if (!readMaterialEffect(jsonValue, definition.effect))
return false;
1476 definition.permutations.emplace_back();
1478 p.name = definition.name;
1479 if (!readMaterialEffect(jsonValue, p.effect))
return false;
1485 LOG_ERROR(logger,
"Material already defined.");
1490 if (!readMaterialMetadata(context, jsonValue, definition))
return false;
1491 if (!readMaterialEffect(jsonValue, definition.effect))
return false;
1492 definition.flags |= MaterialDefinitionFlags::IsTemplate;
1498 LOG_ERROR(logger,
"No material or material template defined.");
1502 if (!applyParent(context, definition))
return false;
1503 orderInterfaces(definition);
1510bool Cogs::Core::inheritMaterialVariants(std::vector<ShaderVariantDefinition> & definitionVariants,
1511 std::span<const ShaderVariantDefinition> sourceVariants,
1514 bool errorOnExisting)
1516 if (!sourceVariants.empty()) {
1517 std::vector<ShaderVariantDefinition> oldVariants = std::move(definitionVariants);
1519 definitionVariants.assign(sourceVariants.begin(), sourceVariants.end());
1522 for (
auto& member : newVariant.vertexInterface.members) { member.inheritanceLevel += 2; }
1523 for (
auto& member : newVariant.geometryInterface.members) { member.inheritanceLevel += 2; }
1524 for (
auto& member : newVariant.surfaceInterface.members) { member.inheritanceLevel += 2; }
1529 if (variant.valueOnly) {
1532 if (variant.name == inheritedVariant.name) {
1533 if (inheritedVariant.type == ShaderVariantType::Enum) {
1534 for (
auto& v : inheritedVariant.values) {
1535 if (v.key == variant.value) {
1536 inheritedVariant.defaultValue =
static_cast<int>(v.index);
1542 else if (inheritedVariant.type == ShaderVariantType::Bool) {
1544 inheritedVariant.defaultValue = parseBool(variant.value);
1548 LOG_ERROR(logger,
"Variant '%s' set from derived '%.*s', but enum '%s' invalid.", variant.name.c_str(), StringViewFormat(definitionName), variant.value.c_str());
1557 LOG_ERROR(logger,
"Variant '%s' set from derived but no matching variant found in base '%.*s'",
1558 variant.name.c_str(),
1559 StringViewFormat(sourceName));
1565 for (
auto& v : definitionVariants) {
1566 if (variant.name == v.name) {
1569 if (errorOnExisting) {
1570 LOG_ERROR(logger,
"Variant '%s' already exists in '%.*s'", variant.name.c_str(), StringViewFormat(definitionName));
1577 definitionVariants.emplace_back(std::move(variant));
1578 definitionVariants.back().index = definitionVariants.size() - 1;
A Context instance contains all the services, systems and runtime components needed to use Cogs.
class IRenderer * renderer
Renderer.
virtual IGraphicsDevice * getDevice()=0
Get the graphics device used by the renderer.
virtual EnginePermutations & getEnginePermutations()=0
Get the reference to the EnginePermutations structure.
virtual GraphicsDeviceType getType() const
Get the type of the graphics device.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
constexpr size_t size() const noexcept
Get the size of the string.
constexpr StringView substr(size_t offset, size_t count=NoPosition) const noexcept
Get the given sub string.
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
static constexpr size_t NoPosition
No position.
size_t find(const StringView &other, size_t offset=0) const noexcept
Find the given string segment inside the string.
std::string to_string() const
String conversion method.
constexpr size_t hash() const noexcept
Get the hash code of the string.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ Normal
Regular shaded rendering.
TextureDimensions
Texture dimensions.
MaterialDataType
Defines available data types for material properties.
@ Color
Use color property from point set if present, fallback to basecolor.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
GraphicsDeviceType
Contains types of graphics devices that may be supported.
@ Direct3D12
Graphics device using the Direct3D 12 API.
@ OpenGLES30
Graphics device using the OpenGLES 3.0 API.
@ Vulkan
Graphics device using the Vulkan API.
@ Direct3D11
Graphics device using the Direct3D 11 API.
@ OpenGL20
Graphics device using OpenGL, supporting at least OpenGL 2.0.
@ WebGPU
Graphics device using the WebGPU API Backend.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
COGSFOUNDATION_API size_t hashLowercase(std::string_view str, size_t hashValue=Cogs::hash()) noexcept
Get the hash code of the string converted to lowercase.
std::pair< std::string, std::string > PreprocessorDefinition
Preprocessor definition.
Defines a loadable effect.
PreprocessorDefinitions definitions
Preprocessor definitions.