Cogs.Core
MaterialReader.cpp
1#include "MaterialReader.h"
2
3#include <glm/gtc/type_ptr.hpp>
4
5#include "Foundation/Logging/Logger.h"
6
7#include "JsonParser.h"
8
9#include "Context.h"
10#include "Resources/ResourceStore.h"
11#include "Resources/MaterialManager.h"
12
13#include "Utilities/Parsing.h"
14
15#include "Renderer/IRenderer.h"
16
17namespace
18{
19 using namespace Cogs::Core;
20
21 Cogs::Logging::Log logger = Cogs::Logging::getLogger("MaterialReader");
22
23 bool traceParsing = false;
24
25 [[nodiscard]] bool parseType(Cogs::StringView valueString, ValueDefinition& value, bool allowFormat)
26 {
27 const size_t spaceIndex = valueString.find(" ");
28 const Cogs::StringView typeString = (spaceIndex != Cogs::StringView::NoPosition) ? valueString.substr(spaceIndex) : valueString;
29
30 // Magic hook for when type is format
31 if (allowFormat && typeString.hash() == Cogs::hash("format")) {
32 value.type = MaterialDataType::Unknown;
33 return true;
34 }
35
36 switch (typeString.hash()) {
37 case Cogs::hash("float"): value.type = MaterialDataType::Float; break;
38 case Cogs::hash("float2"): value.type = MaterialDataType::Float2; break;
39 case Cogs::hash("float3"): value.type = MaterialDataType::Float3; break;
40 case Cogs::hash("float4"): value.type = MaterialDataType::Float4; break;
41 case Cogs::hash("float4x4"): value.type = MaterialDataType::Float4x4; break;
42 case Cogs::hash("float4[]"): value.type = MaterialDataType::Float4Array; value.dimension = (size_t)-1; break;
43 case Cogs::hash("float4x4[]"): value.type = MaterialDataType::Float4x4Array; value.dimension = (size_t)-1; break;
44 case Cogs::hash("bool"): value.type = MaterialDataType::Bool; break;
45 case Cogs::hash("uint"): value.type = MaterialDataType::UInt; break;
46 case Cogs::hash("uint2"): value.type = MaterialDataType::UInt2; break;
47 case Cogs::hash("uint3"): value.type = MaterialDataType::UInt3; break;
48 case Cogs::hash("uint4"): value.type = MaterialDataType::UInt4; break;
49 case Cogs::hash("int"): value.type = MaterialDataType::Int; break;
50 case Cogs::hash("int2"): value.type = MaterialDataType::Int2; break;
51 case Cogs::hash("int3"): value.type = MaterialDataType::Int3; break;
52 case Cogs::hash("int4"): value.type = MaterialDataType::Int4; break;
53 case Cogs::hash("VFACE"): value.type = MaterialDataType::VFACE; break;
54 case Cogs::hash("SV_IsFrontFace"): value.type = MaterialDataType::SV_IsFrontFace; break;
55 case Cogs::hash("SV_InstanceID"): value.type = MaterialDataType::SV_InstanceID; break;
56 case Cogs::hash("SV_ClipDistance"): value.type = MaterialDataType::ClipDistance; break;
57 case Cogs::hash("SV_Position"): value.type = MaterialDataType::Position; break;
58 default:
59
60 // Try to match (.*)[d+] where first match gets checked against float4 and float4x4
61 for (size_t i = 0; i + 1 < typeString.size(); i++) {
62 if ((typeString[i] == '[') && ('0' <= typeString[i + 1]) && (typeString[i + 1] <= '9')) {
63
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');
68 }
69 else if ((typeString[j] == ']') && (j + 1 == typeString.size())) {
70 switch (typeString.substr(0, i).hash()) {
71
72 case Cogs::hash("float4"):
73 value.type = MaterialDataType::Float4Array;
74 value.dimension = dimension;
75 value.dimensionString = typeString.substr(i + 1, j - i - 1).to_string();
76 return true;
77
78 case Cogs::hash("float4x4"):
79 value.type = MaterialDataType::Float4x4Array;
80 value.dimension = dimension;
81 value.dimensionString = typeString.substr(i + 1, j - i - 1).to_string();
82 return true;
83
84 default:
85 break;
86 }
87 }
88 else {
89 break;
90 }
91 }
92 break;
93 }
94 }
95 LOG_ERROR(logger, "Failed to parse datatype '%.*s'", StringViewFormat(valueString));
96 return false;
97 }
98 return true;
99 }
100
101 [[nodiscard]] bool parsePropertyValue(Cogs::StringView valueString, ConstantBufferVariableDefinition& value)
102 {
103 thread_local static std::vector<Cogs::StringView> tokens;
104 tokens.clear();
105
106 tokenize(valueString, tokens);
107
108 if (tokens.empty()) {
109 LOG_ERROR(logger, "Empty property value");
110 return false;
111 }
112
113 if (!parseType(tokens[0], value, false)) {
114 return false;
115 }
116
117 tokens.erase(tokens.begin());
118
119 value.flags = MaterialPropertyFlags::None;
120 while (!tokens.empty()) {
121 if (tokens[0] == "srgb") {
122 value.flags = value.flags | MaterialPropertyFlags::sRGB;
123 }
124 else if (tokens[0] == "normalized") {
125 value.flags = value.flags | MaterialPropertyFlags::Normalized;
126 }
127 else if (tokens[0] == "partitionOfUnity") {
128 value.flags = value.flags | MaterialPropertyFlags::PartitionOfUnity;
129 }
130 else {
131 break;
132 }
133 tokens.erase(tokens.begin());
134 }
135
136 switch (value.type) {
137 case MaterialDataType::Float:
138 value.floatValue = float(0.f);
139 if (!tokens.empty()) {
140 parseValues(&value.floatValue, tokens, 1);
141 }
142 break;
143 case MaterialDataType::Float2:
144 value.float2Value = glm::vec2();
145 if (!tokens.empty()) {
146 parseValues(glm::value_ptr(value.float2Value), tokens, 2);
147 }
148 break;
149 case MaterialDataType::Float3:
150 value.float3Value = glm::vec3();
151 if (!tokens.empty()) {
152 parseValues(glm::value_ptr(value.float3Value), tokens, 3);
153 }
154 break;
155 case MaterialDataType::Float4:
156 value.float4Value = glm::vec4();
157 if (!tokens.empty()) {
158 parseValues(glm::value_ptr(value.float4Value), tokens, 4);
159 }
160 break;
161 case MaterialDataType::Float4x4:
162 value.float4x4Value = glm::mat4();
163 if (!tokens.empty()) {
164 parseValues(glm::value_ptr(value.float4x4Value), tokens, 16);
165 }
166 break;
167
168 case MaterialDataType::Float4Array:
169 if (value.dimension == size_t(-1)) {
170 LOG_ERROR(logger, "Property buffer Float4Array element without known size");
171 return false;
172 }
173 value.float4Value = glm::vec4();
174 if (!tokens.empty()) { // For now, allow one element that gets replicated
175 parseValues(glm::value_ptr(value.float4Value), tokens, 4);
176 }
177 break;
178 case MaterialDataType::Float4x4Array:
179 if (value.dimension == size_t(-1)) {
180 LOG_ERROR(logger, "Property buffer Float4x4Array element without known size");
181 return false;
182 }
183 value.float4x4Value = glm::mat4();
184 if (!tokens.empty()) { // For now, allow one element that gets replicated
185 parseValues(glm::value_ptr(value.float4x4Value), tokens, 16);
186 }
187 break;
188
189 case MaterialDataType::UInt:
190 value.uintValue = 0;
191 if (!tokens.empty()) {
192 parseValues(&value.uintValue, tokens, 1);
193 }
194 break;
195 case MaterialDataType::UInt2:
196 value.uint2Value = glm::ivec2();
197 if (!tokens.empty()) {
198 parseValues(glm::value_ptr(value.uint2Value), tokens, 2);
199 }
200 break;
201 case MaterialDataType::UInt3:
202 value.uint3Value = glm::ivec3();
203 if (!tokens.empty()) {
204 parseValues(glm::value_ptr(value.uint3Value), tokens, 3);
205 }
206 break;
207 case MaterialDataType::UInt4:
208 value.uint4Value = glm::ivec4();
209 if (!tokens.empty()) {
210 parseValues(glm::value_ptr(value.uint4Value), tokens, 4);
211 }
212 break;
213 case MaterialDataType::Int:
214 value.intValue = int(0);
215 if (!tokens.empty()) {
216 parseValues(&value.intValue, tokens, 1);
217 }
218 break;
219 case MaterialDataType::Int2:
220 value.int2Value = glm::ivec2();
221 if (!tokens.empty()) {
222 parseValues(glm::value_ptr(value.int2Value), tokens, 2);
223 }
224 break;
225 case MaterialDataType::Int3:
226 value.int3Value = glm::ivec3();
227 if (!tokens.empty()) {
228 parseValues(glm::value_ptr(value.int3Value), tokens, 3);
229 }
230 break;
231 case MaterialDataType::Int4:
232 value.int4Value = glm::ivec4();
233 if (!tokens.empty()) {
234 parseValues(glm::value_ptr(value.int4Value), tokens, 4);
235 }
236 break;
237
238 case MaterialDataType::Bool:
239 value.boolValue = false;
240 if (!tokens.empty()) {
241 parseValues(&value.boolValue, tokens, 1);
242 }
243 break;
244 default:
245 LOG_ERROR(logger, "Property buffer does not support %s elements", DataTypeNames[size_t(value.type)]);
246 return false;
247 }
248
249 return true;
250 }
251
252 [[nodiscard]] bool readMaterialBuffer(const Value& jsonBuffer, ConstantBufferDefinition& buffer)
253 {
254 for (auto& m : jsonBuffer.GetObject()) {
255 auto propertyKey = toKey(m.name);
256
258 parsedValue.name = propertyKey.to_string();
259
260 if (m.value.IsString()) {
261 if (!parsePropertyValue(toKey(m.value), parsedValue)) {
262 return false;
263 }
264 }
265 else {
266 LOG_ERROR(logger, "Material property %.*s must be specified as strings.", StringViewFormat(propertyKey));
267 return false;
268 }
269
270 buffer.values.push_back(parsedValue);
271 }
272 return true;
273 }
274
275 [[nodiscard]] bool readMaterialProperty(const std::string& propertyName,
276 const Value& value,
277 MaterialProperties& materialProperties,
278 bool isShared)
279 {
280 std::string propertyType;
281
282 switch (value.GetType())
283 {
284 case kObjectType:
285 {
286 if (traceParsing) LOG_DEBUG(logger, " Found buffer %s.", propertyName.c_str());
287
288 materialProperties.buffers.emplace_back();
289 auto& buffer = materialProperties.buffers.back();
290 buffer.name = propertyName;
291 buffer.isPerInstance = !isShared;
292 return readMaterialBuffer(value, buffer);
293 }
294 case kStringType:
295 {
296 propertyType = toString(value);
297 break;
298 }
299
300 case kArrayType:
301 {
302 auto arr = value.GetArray();
303
304 if (arr.Empty()) {
305 LOG_ERROR(logger, "Property array cannot be empty %s.", propertyName.c_str());
306 return false;
307 }
308
309 propertyType = toString(arr[0]);
310 break;
311 }
312 default:
313 break;
314 }
315
316 // Check for arrays of textures properties ("Texture2D[24]")
317 bool is_array = false;
318 uint32_t arr_size = 0;
319 auto arr_i = propertyType.find('[');
320 if (arr_i != std::string::npos) {
321 is_array = true;
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);
326 }
327
328 const size_t code = Cogs::StringView(propertyType).hash();
329
330 TextureDimensions dimensions = TextureDimensions::Texture2D;
331 switch (code) {
332 case Cogs::hash("Texture1D"): dimensions = TextureDimensions::Texture2D; break;
333 case Cogs::hash("Texture2D"): dimensions = TextureDimensions::Texture2D; break;
334 case Cogs::hash("Texture2DArray"): dimensions = TextureDimensions::Texture2DArray; break;
335 case Cogs::hash("Texture3D"): dimensions = TextureDimensions::Texture3D; break;
336 case Cogs::hash("TextureCube"): dimensions = TextureDimensions::TexureCube; break;
337 default:
338 LOG_ERROR(logger, "Unsupported property type '%s'", propertyType.c_str());
339 return false;
340 }
341 materialProperties.textures.emplace_back(MaterialTextureDefinition{
342 .name = propertyName,
343 .precision = MaterialTypePrecision::Default,
344 .isPerInstance = !isShared,
345 .isSrgb = false,
346 .isArray = is_array,
347 .isDepth = false,
348 .arraySize = arr_size,
349 .dimensions = dimensions
350 });
351
352 if (value.IsArray()) {
353
354 MaterialTextureDefinition& textureDefinition = materialProperties.textures.back();
355 for (const auto& item : value.GetArray()) {
356 if (!item.IsString()) {
357 LOG_WARNING(logger, "Non-string texture options encountered.");
358 continue;
359 }
360
361 switch (Cogs::hashLowercase(item.GetString())) {
362
363 // Texture dimension already handled, just skip
364 case Cogs::hash("texture1d"): [[fallthrough]];
365 case Cogs::hash("texture2d"): [[fallthrough]];
366 case Cogs::hash("texture2darray"): [[fallthrough]];
367 case Cogs::hash("texture3d"): [[fallthrough]];
368 case Cogs::hash("texturecube"):
369 continue;
370
371 // Enable sRGB
372 case Cogs::hash("srgb"):
373 textureDefinition.isSrgb = true;
374 break;
375
376 // Enable depth texture
377 case Cogs::hash("depth"):
378 textureDefinition.isDepth = true;
379 break;
380
381 // OpenGL ES precision specifiers
382 case Cogs::hash("lowp"):
383 textureDefinition.precision = MaterialTypePrecision::Low;
384 break;
385 case Cogs::hash("mediump"):
386 textureDefinition.precision = MaterialTypePrecision::Medium;
387 break;
388 case Cogs::hash("highp"):
389 textureDefinition.precision = MaterialTypePrecision::High;
390 break;
391
392 // Optional specification of texture format
393 case Cogs::hash("float"):
394 textureDefinition.format = MaterialDataType::Float;
395 break;
396 case Cogs::hash("float2"):
397 textureDefinition.format = MaterialDataType::Float2;
398 break;
399 case Cogs::hash("float3"):
400 textureDefinition.format = MaterialDataType::Float3;
401 break;
402 case Cogs::hash("float4"):
403 textureDefinition.format = MaterialDataType::Float4;
404 break;
405 case Cogs::hash("int"):
406 textureDefinition.format = MaterialDataType::Int;
407 break;
408 case Cogs::hash("int2"):
409 textureDefinition.format = MaterialDataType::Int2;
410 break;
411 case Cogs::hash("int3"):
412 textureDefinition.format = MaterialDataType::Int3;
413 break;
414 case Cogs::hash("int4"):
415 textureDefinition.format = MaterialDataType::Int4;
416 break;
417 case Cogs::hash("uint"):
418 textureDefinition.format = MaterialDataType::UInt;
419 break;
420 case Cogs::hash("uint2"):
421 textureDefinition.format = MaterialDataType::UInt2;
422 break;
423 case Cogs::hash("uint3"):
424 textureDefinition.format = MaterialDataType::UInt3;
425 break;
426 case Cogs::hash("uint4"):
427 textureDefinition.format = MaterialDataType::UInt4;
428 break;
429
430 default:
431 LOG_WARNING(logger, "Unrecognized texture option '%s' encountered.", item.GetString());
432 break;
433 }
434 }
435 }
436
437 return true;
438 }
439
440 [[nodiscard]] bool parseInterpolationModifier(const Value& jsonObject, ShaderInterfaceMemberDefinition& memberDefinition)
441 {
442 if (!jsonObject.IsString()) {
443 LOG_ERROR(logger, "Interpolation modifier is not a JSON string");
444 return false;
445 }
446
447 Cogs::StringView value = toView(jsonObject);
448 switch (value.hash()) {
449 case Cogs::hash("linear"):
450 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::LinearModifier);
451 break;
452 case Cogs::hash("centroid"):
453 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::CentroidModifier);
454 break;
455 case Cogs::hash("nointerpolation"):
456 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::NointerpolationModifier);
457 break;
458 case Cogs::hash("noperspective"):
459 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::NoperspectiveModifier);
460 break;
461 case Cogs::hash("sample"):
462 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::SampleModifier);
463 break;
464 default:
465 LOG_ERROR(logger, "Unrecognized interpolation modifier '%.*s'", StringViewFormat(value));
466 return false;
467 }
468 return true;
469 }
470
471 [[nodiscard]] bool addVariantFlag(Cogs::Core::ShaderVarientFlags& flags, const Cogs::StringView& name)
472 {
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;
477 default:
478 LOG_ERROR(logger, "Unrecognized variant flag '%.*s'", StringViewFormat(name));
479 return false;
480 }
481 return true;
482 }
483
484 [[nodiscard]] bool parseShaderInterface(const Value& jsonObject, ShaderInterfaceDefinition& interfaceDefinition, uint8_t inheritanceLevel, bool allowFormat)
485 {
486 if (!jsonObject.IsObject()) {
487 LOG_ERROR(logger, "Shader interface is not an JSON object");
488 return false;
489 }
490
491 for (auto& m : jsonObject.GetObject()) {
492 Cogs::StringView name = toView(m.name);
493 auto& obj = m.value;
494
496
497 if (obj.IsArray()) {
498 auto arr = obj.GetArray();
499
500
501
502 if (!parseType(toKey(arr[0]), value, allowFormat)) return false;
503
504 if (value.dimension == size_t(-1)) {
505 if (arr[1].IsString()) {
506 value.dimensionString = toString(arr[1]);
507 }
508 else {
509 value.dimension = arr[1].GetInt();
510 }
511 }
512 else {
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());
518
519 // Check for trailing digits, interpreted and chomp semanticName.
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';
523 size_t k = i + 1;
524 while ((k < semanticName.size()) &&
525 ('0' <= semanticName[k]) &&
526 (semanticName[k] <= '9'))
527 {
528 value.semantic.slot = uint8_t(10u * value.semantic.slot + (semanticName[k] - '0'));
529 }
530 semanticName = semanticName.substr(0, i);
531 break;
532 }
533 }
534 switch (semanticName.hashLowercase()) {
535 case Cogs::hash("position"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Position; break;
536 case Cogs::hash("normal"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Normal; break;
537 case Cogs::hash("color"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Color; break;
538 case Cogs::hash("texcoord"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Texcoord; break;
539 case Cogs::hash("tangent"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Tangent; break;
540 case Cogs::hash("instancevector"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::InstanceVector; break;
541 case Cogs::hash("instancematrix"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::InstanceMatrix; break;
542 case Cogs::hash("sv_position"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_Position; break;
543 case Cogs::hash("sv_vertexid"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID; break;
544 case Cogs::hash("sv_instanceid"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_InstanceID; break;
545 case Cogs::hash("sv_clipdistance"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_ClipDistance; break;
546 case Cogs::hash("sv_vface"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_VFace; break;
547 case Cogs::hash("sv_isfrontface"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_IsFrontFace; break;
548 default:
549 LOG_ERROR(logger, "Unrecognized semantic '%.*s'", StringViewFormat(semanticName));
550 return false;
551 }
552 }
553 else {
554 LOG_ERROR(logger, "Semantic is not a string");
555 return false;
556 }
557 }
558
559 if (arr.Size() >= 3) {
560 auto& modifiers = arr[2];
561 if (modifiers.IsString()) {
562 if (!parseInterpolationModifier(modifiers, value)) return false;
563 }
564 else if (modifiers.IsArray()) {
565 for (auto& modifier : modifiers.GetArray()) {
566 if (!parseInterpolationModifier(modifier, value)) return false;
567 }
568 }
569 }
570 }
571 }
572 else if (obj.IsString()) {
573 if (!parseType(toKey(obj), value, allowFormat)) return false;
574 }
575
576 value.name = name.to_string();
577 value.inheritanceLevel = inheritanceLevel;
578
579 interfaceDefinition.members.push_back(value);
580 }
581
582 return true;
583 }
584
585 [[nodiscard]] bool parseRequiresStatement(const Value& requiresStatement, std::vector<ShaderVariantRequirement>& requirements, const Cogs::StringView& name)
586 {
587 if (requiresStatement.IsString()) {
588 requirements.push_back(ShaderVariantRequirement{ toString(requiresStatement), {} });
589 }
590 else if (requiresStatement.IsArray()) {
591 for (auto& item : requiresStatement.GetArray()) {
592 if (item.IsString()) {
593 requirements.push_back(ShaderVariantRequirement{ toString(item), {} });
594 }
595 else {
596 LOG_ERROR(logger, "Material %.*s has non-string element in requires array.", StringViewFormat(name));
597 return false;
598 }
599 }
600 }
601 else if (requiresStatement.IsObject()) {
602 for (auto& item : requiresStatement.GetObject()) {
603 if (item.value.IsString()) {
604 requirements.push_back(ShaderVariantRequirement{ toString(item.name), toString(item.value) });
605 }
606 else {
607 LOG_ERROR(logger, "Material %.*s has non-string value in requires object.", StringViewFormat(name));
608 return false;
609 }
610 }
611 }
612 else {
613 LOG_ERROR(logger, "Material %.*s has unexpected value in requires object.", StringViewFormat(name));
614 return false;
615 }
616 return true;
617 }
618
619 [[nodiscard]] bool parseTriggersStatement(const Value& triggersStatement, ShaderVariantDefinition& variant)
620 {
621 if (triggersStatement.IsString()) {
622 variant.triggers.push_back(ShaderVariantRequirement{ toString(triggersStatement), {} });
623 }
624 else if (triggersStatement.IsArray()) {
625 for (auto& item : triggersStatement.GetArray()) {
626 if (item.IsString()) {
627 variant.triggers.push_back(ShaderVariantRequirement{ toString(item), {} });
628 }
629 else {
630 LOG_ERROR(logger, "Material variant %s has non-string element in triggers array.", variant.name.c_str());
631 return false;
632 }
633 }
634 }
635 else if (triggersStatement.IsObject()) {
636 for (auto& item : triggersStatement.GetObject()) {
637 if (item.value.IsString()) {
638 variant.triggers.push_back(ShaderVariantRequirement{ toString(item.name), toString(item.value) });
639 }
640 else {
641 LOG_ERROR(logger, "Material variant %s has non-string value in triggers object.", variant.name.c_str());
642 return false;
643 }
644 }
645 }
646 else {
647 LOG_ERROR(logger, "Material variant %s has unexpected value in triggers object.", variant.name.c_str());
648 return false;
649 }
650 return true;
651 }
652
653 [[nodiscard]] bool parseDefaultValueStatement(const Value& defaultValue, ShaderVariantDefinition& variant)
654 {
655 switch (variant.type) {
656
657 case ShaderVariantType::Bool:
658 if (!defaultValue.IsBool()) {
659 LOG_ERROR(logger, "Variant %s defaultValue property is not a bool", variant.name.c_str());
660 return false;
661 }
662 variant.defaultValue = defaultValue.GetBool();
663 break;
664
665 case ShaderVariantType::Int:
666 if (!defaultValue.IsInt()) {
667 LOG_ERROR(logger, "Variant %s defaultValue property is not an int", variant.name.c_str());
668 return false;
669 }
670 variant.defaultValue = defaultValue.GetInt();
671 break;
672
673 case ShaderVariantType::Enum:
674 if (!defaultValue.IsString()) {
675 LOG_ERROR(logger, "Variant %s defaultValue property is not a string", variant.name.c_str());
676 return false;
677 }
678 else {
679 Cogs::StringView val = toView(defaultValue);
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);
683 goto found;
684 }
685 }
686 LOG_ERROR(logger, "Variant %s defaultValue '%.*s' is not a valid enum value", variant.name.c_str(), StringViewFormat(val));
687 return false;
688 found:
689 ;
690 }
691 break;
692
693 case ShaderVariantType::Format:
694 if (!defaultValue.IsString()) {
695 LOG_ERROR(logger, "Variant %s defaultValue property is not a string", variant.name.c_str());
696 return false;
697 }
698 else {
699 Cogs::StringView value_ = toView(defaultValue);
700 Cogs::DataFormat parsedValue = Cogs::parseDataFormat(value_);
701 if (parsedValue == Cogs::DataFormat::Unknown) {
702 LOG_ERROR(logger, "Failed to parse format '%.*s'", StringViewFormat(value_));
703 return false;
704 }
705 variant.defaultValue = size_t(parsedValue);
706 }
707 break;
708 case ShaderVariantType::String:
709 if (!defaultValue.IsString()) {
710 LOG_ERROR(logger, "Variant %s defaultValue property is not a string", variant.name.c_str());
711 return false;
712 }
713 variant.defaultString = toString(defaultValue);
714 break;
715 default:
716 assert(false);
717 }
718
719 return true;
720 }
721
722 [[nodiscard]] bool readMaterialVariant(const Value& variantObject, ShaderVariantDefinition& variant)
723 {
724 auto it = variantObject.FindMember("type");
725 if (it == variantObject.MemberEnd()) {
726 LOG_ERROR(logger, "Material variant %s missing type.", variant.name.c_str());
727 return false;
728 }
729
730 bool acceptValueString = false;
731 bool acceptValues = false;
732 bool acceptRequires = false;
733 bool acceptTriggers = false;
734 Cogs::StringView type = toView(it->value);
735 switch (type.hash()) {
736 case Cogs::hash("bool"):
737 variant.type = ShaderVariantType::Bool;
738 variant.defaultValue = 0;
739 acceptValueString = true;
740 acceptRequires = true;
741 acceptTriggers = true;
742 break;
743
744 case Cogs::hash("int"):
745 variant.type = ShaderVariantType::Int;
746 variant.defaultValue = 0;
747 acceptValueString = true;
748 break;
749
750 case Cogs::hash("format"):
751 variant.type = ShaderVariantType::Format;
752 variant.defaultValue = 0;
753 acceptValueString = true;
754 break;
755
756 case Cogs::hash("enum"):
757 variant.type = ShaderVariantType::Enum;
758 acceptValues = true;
759 break;
760
761 case Cogs::hash("string"):
762 variant.type = ShaderVariantType::String;
763 variant.defaultValue = -1;
764 acceptValueString = true;
765 break;
766
767 default:
768 LOG_ERROR(logger, "Unknown variant type %.*s.", StringViewFormat(type));
769 return false;
770 }
771
772 bool hasValueString = false;
773 variant.flags = ShaderVarientFlags::None;
774 for (auto& variantItem : variantObject.GetObject()) {
775 Cogs::StringView keyword = toView(variantItem.name);
776 switch (keyword.hashLowercase()) {
777
778 case Cogs::hash("type"): break;
779
780 case Cogs::hash("defaultvalue"):
781 if (!parseDefaultValueStatement(variantItem.value, variant)) return false;
782 break;
783
784 case Cogs::hash("value"):
785 if (!acceptValueString) {
786 LOG_ERROR(logger, "Variant type does not support value keyword");
787 return false;
788 }
789 if (!variantItem.value.IsString()) {
790 LOG_ERROR(logger, "Variant value is not a string");
791 return false;
792 }
793 variant.value = toString(variantItem.value);
794 hasValueString = true;
795 break;
796
797 case Cogs::hash("values"):
798 if (!acceptValues) {
799 LOG_ERROR(logger, "Variant type does not support value keyword");
800 return false;
801 }
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());
805 return false;
806 }
807 variant.values.emplace_back(ShaderVariantEnum{ variant.values.size(), toString(v.name), toString(v.value) });
808 }
809 break;
810
811 case Cogs::hash("require"):
812 case Cogs::hash("requires"):
813 if (!acceptRequires) {
814 LOG_ERROR(logger, "Variant type does not support requires keyword");
815 return false;
816 }
817 if (!parseRequiresStatement(variantItem.value, variant.requirements, variant.name)) return false;
818 break;
819 case Cogs::hash("trigger"):
820 case Cogs::hash("triggers"):
821 if (!acceptTriggers) {
822 LOG_ERROR(logger, "Variant type does not support triggers keyword");
823 return false;
824 }
825 if (!parseTriggersStatement(variantItem.value, variant)) return false;
826 break;
827 case Cogs::hash("vertexinterface"):
828 if (!parseShaderInterface(variantItem.value, variant.vertexInterface, 0, variant.type == ShaderVariantType::Format)) return false;
829 break;
830 case Cogs::hash("geometryinterface"):
831 if (!parseShaderInterface(variantItem.value, variant.geometryInterface, 0, false)) return false;
832 break;
833 case Cogs::hash("surfaceinterface"):
834 if (!parseShaderInterface(variantItem.value, variant.surfaceInterface, 0, false)) return false;
835 break;
836 case Cogs::hash("flags"):
837 if (variantItem.value.IsString()) {
838 if (!addVariantFlag(variant.flags, toView(variantItem.value))) return false;
839 }
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");
844 return false;
845 }
846 if (!addVariantFlag(variant.flags, toView(element))) return false;
847 }
848 }
849 else {
850 LOG_ERROR(logger, "Variant flag is neither string nor array");
851 return false;
852 }
853 break;
854 default:
855 LOG_ERROR(logger, "Unexpected variant keyword %.*s", StringViewFormat(keyword));
856 break;
857 }
858 }
859
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");
867 return false;
868 }
869 break;
870 case ShaderVariantType::Enum:
871 if (variant.values.empty()) {
872 LOG_ERROR(logger, "Variant enum type has no values");
873 return false;
874 }
875 break;
876 default:
877 assert(false);
878 }
879
880 return true;
881 }
882
883 [[nodiscard]] bool readMaterialOptions(const Value& jsonMaterialOptions, MaterialDefinition& materialDefinition)
884 {
885 if (jsonMaterialOptions.IsObject()) {
886 for (auto& o : jsonMaterialOptions.GetObject()) {
887 if (o.value.IsString()) {
888 materialDefinition.options[toString(o.name)] = toString(o.value);
889 }
890 else {
891 LOG_ERROR(logger, "readMaterialOptions: Item value is not a JSON string");
892 return false;
893 }
894 }
895 return true;
896 }
897 else {
898 LOG_ERROR(logger, "readMaterialOptions: value is not a JSON object");
899 return false;
900 }
901 }
902
903 [[nodiscard]] bool matchGraphicsType(bool& include, Cogs::GraphicsDeviceType deviceType, const Value& jsonDeviceType)
904 {
905 if (!jsonDeviceType.IsString()) {
906 LOG_ERROR(logger, "matchGraphicsType: device type is not a JSON string");
907 return false;
908 }
909
910 switch (toView(jsonDeviceType).hash())
911 {
912 case Cogs::hash("D3D11"):
913 case Cogs::hash("Direct3D11"):
914 include = include || (deviceType == Cogs::GraphicsDeviceType::Direct3D11);
915 return true;
916 case Cogs::hash("D3D12"):
917 case Cogs::hash("Direct3D12"):
918 include = include || (deviceType == Cogs::GraphicsDeviceType::Direct3D12);
919 return true;
920 case Cogs::hash("Vulkan"):
921 include = include || (deviceType == Cogs::GraphicsDeviceType::Vulkan);
922 return true;
923 case Cogs::hash("GL20"):
924 case Cogs::hash("OpenGL20"):
925 include = include || (deviceType == Cogs::GraphicsDeviceType::OpenGL20);
926 return true;
927 case Cogs::hash("ES30"):
928 case Cogs::hash("OpenGLES30"):
929 include = include || (deviceType == Cogs::GraphicsDeviceType::OpenGLES30);
930 return true;
931 case Cogs::hash("WebGPU"):
932 include = include || (deviceType == Cogs::GraphicsDeviceType::WebGPU);
933 return true;
934 default:
935 LOG_ERROR(logger, "matchGraphicsType: Unknown device type \"%s\"", jsonDeviceType.GetString());
936 break;
937 }
938 return false;
939 }
940
941 [[nodiscard]] bool readShaderDefinition(const Value& jsonObject, ShaderDefinition& shaderDefinition)
942 {
943 if (!jsonObject.IsObject()) {
944 LOG_ERROR(logger, "readShaderDefinition: value is not a JSON object");
945 return false;
946 }
947 for (auto& m : jsonObject.GetObject()) {
948 Cogs::StringView key = toView(m.name);
949 switch (key.hash()) {
950 case Cogs::hash("entryPoint"):
951 shaderDefinition.entryPoint = toString(m.value);
952 break;
953 case Cogs::hash("file"):
954 shaderDefinition.customSourcePath = toString(m.value);
955 break;
956 case Cogs::hash("attributes"):
957 if (m.value.IsObject()) {
958 for (auto& a : m.value.GetObject()) {
959 shaderDefinition.attributes.emplace_back(AttributeDefinition{ toString(a.name), toString(a.value) });
960 }
961 }
962 else {
963 LOG_ERROR(logger, "readShaderDefinition: attributes value is not a JSON array");
964 return false;
965 }
966 break;
967 default:
968 LOG_ERROR(logger, "readShaderDefinition: Unrecognized key '%.*s'", StringViewFormat(key));
969 return false;
970 }
971 }
972 return true;
973 }
974
975 [[nodiscard]] bool readMaterialMetadata(Context* context, const Value& jsonMaterial, MaterialDefinition& materialDefinition);
976
977 [[nodiscard]] bool readMaterialEffect(const Value& jsonMaterial, EffectDefinition& effectDefinition);
978
979 [[nodiscard]] bool readPermutation(Context* context, bool& included, Cogs::GraphicsDeviceType deviceType, const Value& jsonPermuation, MaterialDefinition& materialDefinition, const std::string& permutationName)
980 {
981 included = false;
982 if (!jsonPermuation.IsObject()) {
983 LOG_ERROR(logger, "readPermutation: %s/%s: Permutation is not an JSON object", materialDefinition.name.c_str(), permutationName.c_str());
984 return false;
985 }
986 if (jsonPermuation.HasMember("devices")) {
987 auto& jsonDeviceType = jsonPermuation["devices"];
988 bool include = false;
989 if (jsonDeviceType.IsString()) {
990
991 if (!matchGraphicsType(include, deviceType, jsonDeviceType)) return false;
992 }
993 else if (jsonDeviceType.IsArray()) {
994 include = false;
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());
998 return false;
999 }
1000 if (!matchGraphicsType(include, deviceType, item)) return false;
1001 }
1002 }
1003 else {
1004 LOG_ERROR(logger, "readPermuation: %s@%s: Devices are neither JSON string nor array", materialDefinition.name.c_str(), permutationName.c_str());
1005 return false;
1006 }
1007 if (!include) {
1008 included = false;
1009 return true;
1010 }
1011 }
1012
1013 static int matId = 0;
1014
1015 materialDefinition.permutations.emplace_back();
1016 MaterialDefinition& permutationDefinition = materialDefinition.permutations.back();
1017 permutationDefinition.name = materialDefinition.name + std::to_string(matId++) + permutationName;
1018 permutationDefinition.permutationIndex = materialDefinition.permutations.size() - 1;
1019 permutationDefinition.permutationName = permutationName;
1020
1021 if (!readMaterialMetadata(context, jsonPermuation, permutationDefinition)) return false;
1022 if (!readMaterialEffect(jsonPermuation, permutationDefinition.effect)) return false;
1023
1024 included = true;
1025 return true;
1026 }
1027
1028 [[nodiscard]] bool readMaterialMetadata(Context* context, const Value& jsonMaterial, MaterialDefinition& materialDefinition)
1029 {
1030 if (!jsonMaterial.IsObject()) {
1031 LOG_ERROR(logger, "readMaterialMetadata: Material is not an JSON object");
1032 return false;
1033 }
1034
1035 for (auto& m : jsonMaterial.GetObject()) {
1036
1037 Cogs::StringView memberKey = toView(m.name);
1038 switch (memberKey.hash()) {
1039
1040 case Cogs::hash("name"):
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());
1044 }
1045 else {
1046 LOG_ERROR(logger, "readMaterialMetadata: Material name value is not an JSON string");
1047 }
1048 break;
1049
1050 case Cogs::hash("flags"):
1051 if (m.value.IsString()) {
1052 Cogs::StringView flagsString = toView(m.value);
1053 LOG_WARNING(logger, "readMaterialMetadata: %s: Unrecognized flag '%.*s'", materialDefinition.name.c_str(), StringViewFormat(flagsString));
1054 }
1055 else {
1056 LOG_ERROR(logger, "readMaterialMetadata: %s: Material flags value is not an JSON string", materialDefinition.name.c_str());
1057 }
1058 break;
1059
1060 case Cogs::hash("inherits"):
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());
1064 }
1065 else {
1066 LOG_ERROR(logger, "readMaterialMetadata: %s: Material inherits value is not an JSON string", materialDefinition.name.c_str());
1067 }
1068 break;
1069
1070 case Cogs::hash("require"):
1071 case Cogs::hash("requires"):
1072 if (!parseRequiresStatement(m.value, materialDefinition.requirements, materialDefinition.name)) return false;
1073 break;
1074
1075 case Cogs::hash("variants"):
1076 if (traceParsing) LOG_DEBUG(logger, "Found material variants.");
1077 if (!readMaterialVariants(context, m.value, materialDefinition.variants, false)) return false;
1078 break;
1079
1080 case Cogs::hash("sharedVariants"):
1081 if (traceParsing) LOG_DEBUG(logger, "Found material variants.");
1082 if (!readMaterialVariants(context, m.value, materialDefinition.variants, true)) return false;
1083 break;
1084
1085 case Cogs::hash("options"):
1086 if (traceParsing) LOG_DEBUG(logger, "Found material options.");
1087 if (!readMaterialOptions(m.value, materialDefinition)) return false;
1088 break;
1089
1090 case Cogs::hash("properties"):
1091 if (traceParsing) LOG_DEBUG(logger, "Found material properties.");
1092 if (!readMaterialProperties(context, m.value, materialDefinition.properties, false)) return false;
1093 break;
1094
1095 case Cogs::hash("sharedProperties"):
1096 if (traceParsing) LOG_DEBUG(logger, "Found shared material properties.");
1097 if (!readMaterialProperties(context, m.value, materialDefinition.properties, true)) return false;
1098 break;
1099
1100 case Cogs::hash("permutations"):
1101 if (traceParsing) LOG_DEBUG(logger, "Found effect permutations.");
1102 if (m.value.IsObject()) {
1103 Cogs::GraphicsDeviceType deviceType = context->renderer->getDevice()->getType();
1104
1105 for (auto& p : m.value.GetObject()) {
1106 auto permutationName = toString(p.name);
1107
1108 if (traceParsing) LOG_DEBUG(logger, "Found effect permutation %s.", permutationName.c_str());
1109
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; // Found a device match.
1115 }
1116 }
1117 else {
1118 bool included = false;
1119 if (!readPermutation(context, included, deviceType, p.value, materialDefinition, permutationName)) return false;
1120 }
1121 }
1122 }
1123 else {
1124 LOG_ERROR(logger, "readMaterialMetadata: %s: Material permutations value is not an JSON object", materialDefinition.name.c_str());
1125 return false;
1126 }
1127 break;
1128
1129 case Cogs::hash("enginePermutations"):
1130 if (m.value.IsArray()) {
1131
1132 for (auto& element : m.value.GetArray()) {
1133
1134 if (element.IsString()) {
1135 Cogs::StringView str = toView(element);
1136 const EnginePermutation* enginePermutation = context->renderer->getEnginePermutations().get(str);
1137 if (enginePermutation) {
1138 materialDefinition.enginePermutationMask |= size_t(1) << enginePermutation->getIndex();
1139 }
1140 else {
1141 LOG_ERROR(logger, "readMaterialMetadata: %s: unrecognized engine permutation '%.*s'", materialDefinition.name.c_str(), StringViewFormat(str));
1142 return false;
1143 }
1144 }
1145 else {
1146 LOG_ERROR(logger, "readMaterialMetadata: %s: Material enginePermutations item is not an JSON string", materialDefinition.name.c_str());
1147 return false;
1148 }
1149 }
1150 }
1151 else {
1152 LOG_ERROR(logger, "readMaterialMetadata: %s: Material enginePermutations value is not an JSON array", materialDefinition.name.c_str());
1153 return false;
1154 }
1155 break;
1156
1157 case Cogs::hash("vertexInterface"):
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"):
1169 // parsed by readMaterialEffect
1170 break;
1171 case Cogs::hash("devices"):
1172 // parsed by readPermutation
1173 break;
1174
1175 default:
1176 LOG_ERROR(logger, "readMaterialMetadata: %s: Unrecognized key '%.*s'", materialDefinition.name.c_str(), StringViewFormat(memberKey));
1177 return false;
1178 }
1179 }
1180 return true;
1181 }
1182
1183 [[nodiscard]] bool readMaterialEffect(const Value& jsonMaterial, EffectDefinition& effectDefinition)
1184 {
1185 if (!jsonMaterial.IsObject()) {
1186 LOG_ERROR(logger, "readMaterialEffect: Material is not an JSON object");
1187 return false;
1188 }
1189
1190 for (auto& m : jsonMaterial.GetObject()) {
1191
1192 Cogs::StringView memberKey = toView(m.name);
1193 switch (memberKey.hash()) {
1194
1195 case Cogs::hash("vertexInterface"):
1196 if (traceParsing) LOG_DEBUG(logger, "Found vertex interface.");
1197 if (!parseShaderInterface(m.value, effectDefinition.vertexShader.shaderInterface, 1, false)) return false;
1198 break;
1199
1200 case Cogs::hash("vertexFunction"):
1201 if (traceParsing) LOG_DEBUG(logger, "Found vertex function.");
1202 if (!readShaderDefinition(m.value, effectDefinition.vertexShader)) return false;
1203 break;
1204
1205 case Cogs::hash("hullInterface"):
1206 if (traceParsing) LOG_DEBUG(logger, "Found hull interface.");
1207 if (!parseShaderInterface(m.value, effectDefinition.hullShader.shaderInterface, 1, false)) return false;
1208 break;
1209
1210 case Cogs::hash("hullFunction"):
1211 if (traceParsing) LOG_DEBUG(logger, "Found hull function.");
1212 if (!readShaderDefinition(m.value, effectDefinition.hullShader)) return false;
1213 break;
1214
1215 case Cogs::hash("domainInterface"):
1216 if (traceParsing) LOG_DEBUG(logger, "Found domain interface.");
1217 if (!parseShaderInterface(m.value, effectDefinition.domainShader.shaderInterface, 1, false)) return false;
1218 break;
1219
1220 case Cogs::hash("domainFunction"):
1221 if (traceParsing) LOG_DEBUG(logger, "Found domain function.");
1222 if (!readShaderDefinition(m.value, effectDefinition.domainShader)) return false;
1223 break;
1224
1225 case Cogs::hash("geometryInterface"):
1226 if (traceParsing) LOG_DEBUG(logger, "Found geometry interface.");
1227 if (!parseShaderInterface(m.value, effectDefinition.geometryShader.shaderInterface, 1, false)) return false;
1228 break;
1229
1230 case Cogs::hash("geometryFunction"):
1231 if (traceParsing) LOG_DEBUG(logger, "Found geometry function.");
1232 if (!readShaderDefinition(m.value, effectDefinition.geometryShader)) return false;
1233 break;
1234
1235 case Cogs::hash("surfaceInterface"):
1236 if (traceParsing) LOG_DEBUG(logger, "Found surface interface.");
1237 if (!parseShaderInterface(m.value, effectDefinition.pixelShader.shaderInterface, 1, false)) return false;
1238 break;
1239
1240 case Cogs::hash("surfaceFunction"):
1241 if (traceParsing) LOG_DEBUG(logger, "Found surface function.");
1242 if (!readShaderDefinition(m.value, effectDefinition.pixelShader)) return false;
1243 break;
1244
1245 case Cogs::hash("definitions"):
1246 case Cogs::hash("defines"):
1247 if (m.value.IsObject()) {
1248 for (auto& d : m.value.GetObject()) {
1249 if (d.value.IsString()) {
1250 effectDefinition.definitions.push_back(Cogs::PreprocessorDefinition{ toString(d.name), toString(d.value) });
1251 }
1252 else {
1253 LOG_ERROR(logger, "readMaterialEffect: %.*s item %.*s value is not a JSON string", StringViewFormat(memberKey), StringViewFormat(toView(d.name)));
1254 return false;
1255 }
1256 }
1257 }
1258 else {
1259 LOG_ERROR(logger, "readMaterialEffect: %.*s is not a JSON object", StringViewFormat(memberKey));
1260 return false;
1261 }
1262 break;
1263
1264 case Cogs::hash("name"):
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"):
1276 // parsed by readMaterialMetadata
1277 break;
1278 case Cogs::hash("devices"):
1279 // parsed by readPermutation
1280 break;
1281
1282 default:
1283 LOG_ERROR(logger, "readMaterialEffect: Unrecognized key '%.*s'", StringViewFormat(memberKey));
1284 return false;
1285 }
1286 }
1287 return true;
1288 }
1289
1290 void inheritShaderDefinition(const ShaderDefinition& source, ShaderDefinition& definition)
1291 {
1292 // Resolving of conflicts are handled in \ref addShaderStageInterfaceMembersFromVariants in MaterialManager.
1293 for (const ShaderInterfaceMemberDefinition& sourceMember : source.shaderInterface.members) {
1294 definition.shaderInterface.members.emplace_back(sourceMember);
1295 definition.shaderInterface.members.back().inheritanceLevel += 2;
1296 }
1297
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;
1303 }
1304 }
1305
1306 [[nodiscard]] bool inheritDefinition(const MaterialDefinitionBase& source, MaterialDefinitionBase& definition, bool properties = true)
1307 {
1308 //TODO: Validate fallback status of material definitions.
1309 definition.flags &= source.flags;
1310
1311 if (properties) {
1312 for (auto& buffer : source.properties.buffers) {
1313 definition.properties.buffers.push_back(buffer);
1314 }
1315
1316 for (auto& texture : source.properties.textures) {
1317 definition.properties.textures.push_back(texture);
1318 }
1319 }
1320
1321 definition.requirements.insert(definition.requirements.end(),
1322 source.requirements.begin(),
1323 source.requirements.end());
1324
1325 if (!inheritMaterialVariants(definition.variants, source.variants, definition.name, source.name, properties)) return false;
1326
1327 //auto & effect = definition.effect;
1328 const EffectDefinition& parentEffect = source.effect;
1329
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);
1335
1336 return true;
1337 }
1338
1339 void orderInterfaces(MaterialDefinitionBase& definition)
1340 {
1341 for (auto& buffer : definition.properties.buffers) {
1342 std::sort(buffer.values.begin(), buffer.values.end(), [](const auto& a, const auto& b)
1343 {
1344 return DataTypeSizes[(size_t)a.type] > DataTypeSizes[(size_t)b.type];
1345 });
1346 }
1347
1348 auto& pixelShaderInterface = definition.effect.pixelShader.shaderInterface;
1349
1350 std::sort(pixelShaderInterface.members.begin(), pixelShaderInterface.members.end(), [](const auto& a, const auto& b)
1351 {
1352 return DataTypeSizes[(size_t)a.type] > DataTypeSizes[(size_t)b.type];
1353 });
1354 }
1355
1356 [[nodiscard]] bool applyParent(Context* context, MaterialDefinition& definition)
1357 {
1358 if (definition.name == "MaterialBase") return true;
1359
1360 if (definition.inherits.empty()) {
1361 definition.inherits = "MaterialBase";
1362 }
1363
1364 const MaterialDefinition* parent = context->materialDefinitionManager->get(definition.inherits);
1365 if (!parent) {
1366 LOG_ERROR(logger, "Invalid parent material: %s.", definition.inherits.c_str());
1367 return false;
1368 }
1369
1370 if (!inheritDefinition(*parent, definition)) return false;
1371
1372 for (MaterialDefinition& p : definition.permutations) {
1373 if (!inheritDefinition(definition, p)) return false;
1374
1375 if (p.permutationName.size()) {
1376 const MaterialDefinition* parentPermutation = nullptr;
1377
1378 for (auto& pp : parent->permutations) {
1379 if (pp.permutationName == p.permutationName) {
1380 parentPermutation = &pp;
1381 }
1382 }
1383
1384 if (parentPermutation) {
1385 if (!inheritDefinition(*parentPermutation, p, false)) return false;
1386 }
1387 }
1388
1389 orderInterfaces(p);
1390 }
1391
1392 return true;
1393 }
1394
1395}
1396
1397
1398bool Cogs::Core::readMaterialProperties(Context* /*context*/, const Value& jsonValue, MaterialProperties& materialProperties, bool isShared)
1399{
1400 if (!jsonValue.IsObject()) {
1401 LOG_ERROR(logger, "readMaterialProperties: Material properties is not a JSON object");
1402 return false;
1403 }
1404 for (auto& m : jsonValue.GetObject()) {
1405 if (!readMaterialProperty(toString(m.name), m.value, materialProperties, isShared)) {
1406 return false;
1407 }
1408 }
1409
1410 return true;
1411}
1412
1413bool Cogs::Core::readMaterialVariants(Context* /*context*/, const Value& value, std::vector<ShaderVariantDefinition>& variants, bool isShared)
1414{
1415 if (!value.IsObject()) {
1416 LOG_ERROR(logger, "readMaterialVariants: Variant value is not an object");
1417 return false;
1418 }
1419
1420 for (auto& o : value.GetObject()) {
1421 ShaderVariantDefinition& variant = variants.emplace_back();
1422 variant.index = variants.size() - 1;
1423 variant.name = toString(o.name);
1424 variant.isShared = isShared;
1425
1426 if (o.value.IsObject()) {
1427 if (!readMaterialVariant(o.value, variant)) {
1428 return false;
1429 }
1430 }
1431 else {
1432 if (o.value.IsString()) {
1433 variant.value = o.value.GetString();
1434 }
1435 else if (o.value.IsBool()) {
1436 variant.value = o.value.GetBool() ? "true" : "false";
1437 }
1438 else {
1439 LOG_ERROR(logger, "readMaterialVariants: Variant %s value is neither object, string nor bool.", variant.name.c_str());
1440 return false;
1441 }
1442 variant.valueOnly = true;
1443 }
1444 }
1445 return true;
1446}
1447
1448bool Cogs::Core::parseMaterial(Context * context, const StringView & path, MaterialDefinition & definition)
1449{
1450 Document document = parseJson(context, path, JsonParseFlags::None);
1451
1452 if (!document.IsObject()) {
1453 LOG_ERROR(logger, "Cannot read invalid material.");
1454 return false;
1455 }
1456
1457 bool isDefined = false;
1458 for (auto & m : document.GetObject()) {
1459 auto & jsonValue = m.value;
1460
1461 switch (toView(m.name).hash())
1462 {
1463 case Cogs::hash("Material"):
1464 if (isDefined) {
1465 LOG_ERROR(logger, "Material already defined.");
1466 return false;
1467 }
1468 isDefined = true;
1469
1470 if (!readMaterialMetadata(context, jsonValue, definition)) return false;
1471
1472 if (!definition.permutations.empty()) {
1473 if (!readMaterialEffect(jsonValue, definition.effect)) return false;
1474 }
1475 else {
1476 definition.permutations.emplace_back();
1477 MaterialDefinition& p = definition.permutations.back();
1478 p.name = definition.name;
1479 if (!readMaterialEffect(jsonValue, p.effect)) return false;
1480 }
1481 break;
1482
1483 case Cogs::hash("MaterialTemplate"):
1484 if (isDefined) {
1485 LOG_ERROR(logger, "Material already defined.");
1486 return false;
1487 }
1488 isDefined = true;
1489
1490 if (!readMaterialMetadata(context, jsonValue, definition)) return false;
1491 if (!readMaterialEffect(jsonValue, definition.effect)) return false;
1492 definition.flags |= MaterialDefinitionFlags::IsTemplate;
1493 break;
1494 }
1495 }
1496
1497 if (!isDefined) {
1498 LOG_ERROR(logger, "No material or material template defined.");
1499 return false;
1500 }
1501
1502 if (!applyParent(context, definition)) return false;
1503 orderInterfaces(definition);
1504
1505 context->materialDefinitionManager->add(MaterialDefinition(definition));
1506
1507 return true;
1508}
1509
1510bool Cogs::Core::inheritMaterialVariants(std::vector<ShaderVariantDefinition> & definitionVariants,
1511 std::span<const ShaderVariantDefinition> sourceVariants,
1512 const Cogs::StringView& definitionName,
1513 const Cogs::StringView& sourceName,
1514 bool errorOnExisting)
1515{
1516 if (!sourceVariants.empty()) {
1517 std::vector<ShaderVariantDefinition> oldVariants = std::move(definitionVariants);
1518
1519 definitionVariants.assign(sourceVariants.begin(), sourceVariants.end());
1520 for (ShaderVariantDefinition& newVariant : definitionVariants) {
1521 // Record inheritance level
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; }
1525 }
1526
1527 for (ShaderVariantDefinition& variant : oldVariants) {
1528
1529 if (variant.valueOnly) {
1530 bool found = false;
1531 for (ShaderVariantDefinition& inheritedVariant : definitionVariants) {
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);
1537 found = true;
1538 break;
1539 }
1540 }
1541 }
1542 else if (inheritedVariant.type == ShaderVariantType::Bool) {
1543 found = true;
1544 inheritedVariant.defaultValue = parseBool(variant.value);
1545 }
1546
1547 if (!found) {
1548 LOG_ERROR(logger, "Variant '%s' set from derived '%.*s', but enum '%s' invalid.", variant.name.c_str(), StringViewFormat(definitionName), variant.value.c_str());
1549 return false;
1550 }
1551
1552 break;
1553 }
1554 }
1555
1556 if (!found) {
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));
1560 return false;
1561 }
1562 }
1563 else {
1564 bool found = false;
1565 for (auto& v : definitionVariants) {
1566 if (variant.name == v.name) {
1567 found = true;
1568
1569 if (errorOnExisting) {
1570 LOG_ERROR(logger, "Variant '%s' already exists in '%.*s'", variant.name.c_str(), StringViewFormat(definitionName));
1571 return false;
1572 }
1573 }
1574 }
1575
1576 if (!found) {
1577 definitionVariants.emplace_back(std::move(variant));
1578 definitionVariants.back().index = definitionVariants.size() - 1;
1579 }
1580 }
1581 }
1582 }
1583 return true;
1584}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class IRenderer * renderer
Renderer.
Definition: Context.h:228
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.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
constexpr size_t size() const noexcept
Get the size of the string.
Definition: StringView.h:204
constexpr StringView substr(size_t offset, size_t count=NoPosition) const noexcept
Get the given sub string.
Definition: StringView.h:284
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
Definition: StringView.cpp:13
static constexpr size_t NoPosition
No position.
Definition: StringView.h:69
size_t find(const StringView &other, size_t offset=0) const noexcept
Find the given string segment inside the string.
Definition: StringView.cpp:18
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:226
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.
Definition: MaterialTypes.h:20
@ Color
Use color property from point set if present, fallback to basecolor.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
GraphicsDeviceType
Contains types of graphics devices that may be supported.
Definition: Base.h:48
@ 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.
Definition: HashFunctions.h:62
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.
Definition: IEffects.h:10
Defines a loadable effect.
PreprocessorDefinitions definitions
Preprocessor definitions.