Cogs.Core
MaterialManager.cpp
1#include "MaterialManager.h"
2
3#include <bitset>
4
5#include "Context.h"
6
7#include "Material.h"
8#include "MaterialBuilder.h"
9#include "ResourceStore.h"
10#include "ShaderBuilder.h"
11
12#include "Components/Core/ClipShapeComponent.h"
13#include "Serialization/MaterialReader.h"
14#include "Systems/Core/CameraSystem.h"
15
16#include "TextureManager.h"
17#include "EffectManager.h"
18#include "DefaultMaterial.h"
19#include "MaterialDefinition.h"
20
21#include "Foundation/Logging/Logger.h"
22#include "Foundation/Platform/FileSystemWatcher.h"
23#include "Foundation/Platform/IO.h"
24#include "Foundation/Platform/Timer.h"
25
26namespace
27{
28 Cogs::Logging::Log logger = Cogs::Logging::getLogger("MaterialManager");
29}
30
31namespace Cogs::Core
32{
34 {
35 enum EShaderTypes
36 {
37 Vertex = 0,
38 Hull,
39 Domain,
40 Geometry,
41 Pixel,
42 ShaderTypeCount
43 };
44 };
45
46 const static char * ShaderNames[ShaderTypes::ShaderTypeCount] = {
47 "VertexShader",
48 "HullShader",
49 "DomainShader",
50 "GeometryShader",
51 "PixelShader",
52 };
53}
54
56{
57 materials.clear();
59
60 reportLeaks("Material");
61}
62
64{
65 materialBase = MaterialHandle();
66 materials.clear();
68}
69
71{
72 context->resourceStore->addSearchPath("../Data/Materials/");
73 context->resourceStore->addSearchPath("Materials/");
74
75 materialBase = loadMaterial("MaterialBase.material");
76
77 defaultResource = loadMaterial("DefaultMaterial.material");
78
79 static_cast<void>(loadMaterial("LineMaterial.material"));
80 static_cast<void>(loadMaterial("PointMaterial.material"));
81 static_cast<void>(loadMaterial("StandardMaterial.material"));
82
84
85 defaultResource->setTextureProperty(DefaultMaterial::DiffuseMap, context->textureManager->white);
86}
87
89{
90 return defaultResource;
91}
92
93Cogs::Core::MaterialHandle Cogs::Core::MaterialManager::getMaterial(const StringView & name)
94{
95 auto found = materials.find(name.to_string());
96
97 if (found != materials.end()) return found->second;
98
100}
101
103{
104 auto existing = getMaterial(fileName);
105
106 if (existing) {
107 return existing;
108 }
109
110 //TODO: Do full path resolve of fileName param before load check, send resolved path as resourcePath to
111 // load info.
112
113 auto loaded = getAllocatedResources();
114
115 for (auto & m : loaded) {
116 if (fileName == m->getSource()) {
117 return MaterialHandle(m);
118 }
119 }
120
121 MaterialLoadInfo & loadInfo = *createLoadInfo();
122 loadInfo.resourceId = resourceId;
123 loadInfo.resourcePath = fileName.to_string();
124 loadInfo.materialLoadFlags = materialLoadFlags;
125
126 if (context->variables->get("resources.materials.autoReload", false)) {
127 loadInfo.loadFlags |= ResourceLoadFlags::AutoReload;
128 }
129
130 return loadResource(&loadInfo);
131}
132
134{
135 auto material = get(loadInfo->handle);
136
137 if (loadInfo->resourcePath.size()) {
138 if (!parseMaterial(context, loadInfo->resourcePath, material->definition)) {
139 loadInfo->handle->setFailedLoad();
140 setProcessed(loadInfo);
141 return;
142 }
143 }
144
145 if (!setupMaterial(material)) {
146 LOG_WARNING(logger, "Failed set up material %.*s", StringViewFormat(material->getName()));
147 loadInfo->handle->setFailedLoad();
148 setProcessed(loadInfo);
149 return;
150 }
151
152 auto found = materials.find(material->getName().to_string());
153
154 if (found != materials.end()) {
155 LOG_WARNING(logger, "Overwriting registered material with name %s. Existing instances will not be affected, but retrieving by name will return new material.", material->getName().data());
156 }
157
158 materials[material->getName().to_string()] = loadInfo->handle;
159
160 setProcessed(loadInfo);
161}
162
163void Cogs::Core::MaterialManager::handleReload(ResourceHandleBase handle)
164{
165 MaterialHandle material(handle);
166
167 auto loadInfo = createLoadInfo();
168 loadInfo->resourceId = material->getId();
169 loadInfo->resourcePath = material->getSource().to_string();
170 loadInfo->resourceName = material->getName().to_string();
171 loadInfo->loadFlags = ResourceLoadFlags::Reload;
172 loadInfo->handle = material;
173
174 static_cast<void>(loadResource(loadInfo));
175}
176
177bool Cogs::Core::MaterialManager::setupMaterial(Material * material)
178{
179 auto & definition = material->definition;
180
181 if (!definition.isTemplate()) {
182 if (!material->constantBuffers.buffers.size()) {
183 if (!applyMaterialDefinition(context, material->definition, *material)) return false;
184 } else {
185 material->constantBuffers.buffers.clear();
186 MaterialDefinition definition_;
187 if (!applyMaterialDefinition(context, definition_, *material)) return false;
188 material->definition = definition_;
189 ++material->constantBuffers.buffersGeneration;
190 }
191 }
192
193 if (definition.isTemplate()) {
194 material->setResident();
195 return true;
196 }
197
198 material->permutationKeys.resize(definition.permutations.size());
199
200 for (auto & materialPermutation : definition.permutations) {
201 material->permutationKeys[materialPermutation.permutationIndex] = materialPermutation.permutationName;
202 }
203 return true;
204}
205
206namespace
207{
208 using namespace Cogs::Core;
209
210 MaterialDataType formatShaderType(Cogs::Format format)
211 {
212 // This should match the appropriate shader type
213 switch (format) {
214 case Cogs::Format::R8_UNORM:
215 case Cogs::Format::R16_UNORM:
216 case Cogs::Format::R8_SNORM:
217 case Cogs::Format::R16_SNORM:
218 case Cogs::Format::R16_FLOAT:
219 case Cogs::Format::R32_FLOAT:
220 return MaterialDataType::Float;// return "float";
221 case Cogs::Format::R8G8_UNORM:
222 case Cogs::Format::R16G16_UNORM:
223 case Cogs::Format::R8G8_SNORM:
224 case Cogs::Format::R16G16_SNORM:
225 case Cogs::Format::R16G16_FLOAT:
226 case Cogs::Format::R32G32_FLOAT:
227 return MaterialDataType::Float2;//return "float2";
228 case Cogs::Format::R8G8B8_UNORM:
229 case Cogs::Format::R16G16B16_UNORM:
230 case Cogs::Format::R8G8B8_SNORM:
231 case Cogs::Format::R16G16B16_SNORM:
232 case Cogs::Format::R16G16B16_FLOAT:
233 case Cogs::Format::R32G32B32_FLOAT:
234 case Cogs::Format::R8G8B8_UNORM_SRGB:
235 case Cogs::Format::R11G11B10_FLOAT:
236 case Cogs::Format::R5G6B5_UNORM:
237 return MaterialDataType::Float3;//return "float3";
238 case Cogs::Format::R8G8B8A8_UNORM:
239 case Cogs::Format::R16G16B16A16_UNORM:
240 case Cogs::Format::R8G8B8A8_SNORM:
241 case Cogs::Format::R16G16B16A16_SNORM:
242 case Cogs::Format::R16G16B16A16_FLOAT:
243 case Cogs::Format::R32G32B32A32_FLOAT:
244 case Cogs::Format::R8G8B8A8_UNORM_SRGB:
245 case Cogs::Format::R10G10B10A2_UNORM:
246 case Cogs::Format::R5G5B5A1_UNORM:
247 case Cogs::Format::R4G4B4A4_UNORM:
248 case Cogs::Format::R9G9B9E5_FLOAT:
249 return MaterialDataType::Float4;//return "float4";
250 case Cogs::Format::R8_UINT:
251 case Cogs::Format::R16_UINT:
252 case Cogs::Format::R32_UINT:
253 return MaterialDataType::UInt;//return "uint";
254 case Cogs::Format::R8G8_UINT:
255 case Cogs::Format::R16G16_UINT:
256 case Cogs::Format::R32G32_UINT:
257 return MaterialDataType::UInt2;//return "uint2";
258 case Cogs::Format::R8G8B8_UINT:
259 case Cogs::Format::R16G16B16_UINT:
260 case Cogs::Format::R32G32B32_UINT:
261 return MaterialDataType::UInt3;//return "uint3";
262 case Cogs::Format::R8G8B8A8_UINT:
263 case Cogs::Format::R16G16B16A16_UINT:
264 case Cogs::Format::R32G32B32A32_UINT:
265 case Cogs::Format::R10G10B10A2_UINT:
266 return MaterialDataType::UInt4;//return "uint4";
267 case Cogs::Format::R8_SINT:
268 case Cogs::Format::R16_SINT:
269 case Cogs::Format::R32_SINT:
270 return MaterialDataType::Int;//return "int";
271 case Cogs::Format::R8G8_SINT:
272 case Cogs::Format::R16G16_SINT:
273 case Cogs::Format::R32G32_SINT:
274 return MaterialDataType::Int2;//return "int2";
275 case Cogs::Format::R8G8B8_SINT:
276 case Cogs::Format::R16G16B16_SINT:
277 case Cogs::Format::R32G32B32_SINT:
278 return MaterialDataType::Int3;//return "int3";
279 case Cogs::Format::R8G8B8A8_SINT:
280 case Cogs::Format::R16G16B16A16_SINT:
281 case Cogs::Format::R32G32B32A32_SINT:
282 return MaterialDataType::Int4;//return "int4";
283 case Cogs::Format::MAT4X4_FLOAT:
284 return MaterialDataType::Float4x4;//return "float4x4";
285 default:
286 LOG_ERROR(logger, "Illegal format %u", unsigned(format));
287 return MaterialDataType::Unknown;
288 }
289 }
290
291 const char* formatShaderTypeString(Cogs::Format format)
292 {
293 switch (formatShaderType(format)) {
294 case MaterialDataType::Float: return "float";
295 case MaterialDataType::Float2: return "float2";
296 case MaterialDataType::Float3: return "float3";
297 case MaterialDataType::Float4: return "float4";
298 case MaterialDataType::UInt: return "uint";
299 case MaterialDataType::UInt2: return "uint2";
300 case MaterialDataType::UInt3: return "uint3";
301 case MaterialDataType::UInt4: return "uint4";
302 case MaterialDataType::Int: return "int";
303 case MaterialDataType::Int2: return "int2";
304 case MaterialDataType::Int3: return "int3";
305 case MaterialDataType::Int4: return "int4";
306 case MaterialDataType::Float4x4: return "float4x4";
307 default:
308 return "error";
309 }
310 }
311
313 void moveSystemGeneratedParametersLast(std::vector<ShaderInterfaceMemberDefinition>& parameters)
314 {
315 std::sort(parameters.begin(), parameters.end(),
317 {
318 int aValue = (a.type == MaterialDataType::SV_IsFrontFace || a.type == MaterialDataType::VFACE || a.type == MaterialDataType::SV_InstanceID) ? 1 : 0;
319 int bValue = (b.type == MaterialDataType::SV_IsFrontFace || b.type == MaterialDataType::VFACE || b.type == MaterialDataType::SV_InstanceID) ? 1 : 0;
320 return aValue < bValue;
321 });
322 }
323
324 void addShaderStageInterfaceMembers(const std::string& materialName,
325 std::vector<ShaderInterfaceMemberDefinition>& existingMembers,
326 std::span<const ShaderInterfaceMemberDefinition> newMembers)
327 {
328 for (const ShaderInterfaceMemberDefinition& member : newMembers) {
329
330 // If semantic exists, we just override the name and modifiers
331 for (ShaderInterfaceMemberDefinition& existing : existingMembers) {
332
333 // Try to filter out duplicates.
334
335 if ((member.semantic.name != ShaderInterfaceMemberDefinition::SemanticName::None) &&
336 (member.semantic.name == existing.semantic.name) &&
337 (member.semantic.slot == existing.semantic.slot))
338 {
339
340 // Ignore cases where there is no conflict
341 if (member.modifiers == existing.modifiers &&
342 member.name == existing.name &&
343 member.type == existing.type &&
344 member.dimension == existing.dimension &&
345 member.dimensionString == existing.dimensionString)
346 {
347 // This can happen because permutations inherit root material etc.
348 // We retain the minimum inheritance level
349 existing.inheritanceLevel = std::min(existing.inheritanceLevel, member.inheritanceLevel);
350 }
351 else if (member.inheritanceLevel < existing.inheritanceLevel) {
352 LOG_DEBUG(logger, "%s: Replacing '%s' (level=%d) with '%s' (level=%d) (semantic=%d:%d)",
353 materialName.c_str(),
354 existing.name.c_str(), int(existing.inheritanceLevel),
355 member.name.c_str(), int(member.inheritanceLevel),
356 int(existing.semantic.name), int(existing.semantic.slot));
357 existing.name = member.name;
358 existing.modifiers = member.modifiers;
359 existing.type = member.type;
360 existing.dimension = member.dimension;
361 existing.dimensionString = member.dimensionString;
362 }
363 else if (existing.inheritanceLevel < member.inheritanceLevel) {
364 LOG_DEBUG(logger, "%s: Keeping '%s' (level=%d) over '%s' (level=%d) (semantic=%d:%d)",
365 materialName.c_str(),
366 existing.name.c_str(), int(existing.inheritanceLevel),
367 member.name.c_str(), int(member.inheritanceLevel),
368 int(existing.semantic.name), int(existing.semantic.slot));
369 }
370 else {
371 LOG_ERROR(logger, "%s: Conflicting inheritance levels: '%s' (level=%d) over '%s' (level=%d) (semantic=%d:%d)",
372 materialName.c_str(),
373 existing.name.c_str(), int(existing.inheritanceLevel),
374 member.name.c_str(), int(member.inheritanceLevel),
375 int(existing.semantic.name), int(existing.semantic.slot));
376 }
377 goto next;
378 }
379
380 if (member.name == existing.name) {
381 // If name match, assume it is a duplicate
382 goto next;
383 }
384 }
385
386 // Create new interface member
387 existingMembers.emplace_back(member);
388 next:
389 ;
390 }
391 }
392
393
394 void resolveShaderInterfaceMembers(MaterialDefinition& materialPermutation)
395 {
396 std::vector<ShaderInterfaceMemberDefinition>& vertexMembers = materialPermutation.effect.vertexShader.shaderInterface.members;
397 std::vector<ShaderInterfaceMemberDefinition>& geometryMembers = materialPermutation.effect.geometryShader.shaderInterface.members;
398 std::vector<ShaderInterfaceMemberDefinition>& surfaceMembers = materialPermutation.effect.pixelShader.shaderInterface.members;
399
400 std::vector<ShaderInterfaceMemberDefinition> tmp;
401 addShaderStageInterfaceMembers(materialPermutation.name, tmp, vertexMembers);
402 vertexMembers.swap(tmp);
403
404 tmp.clear();
405 addShaderStageInterfaceMembers(materialPermutation.name, tmp, geometryMembers);
406 geometryMembers.swap(tmp);
407
408 tmp.clear();
409 addShaderStageInterfaceMembers(materialPermutation.name, tmp, surfaceMembers);
410 surfaceMembers.swap(tmp);
411 }
412
414 void addShaderInterfaceMembersFromVariants(MaterialDefinition& materialPermutation,
415 std::span<const ShaderVariantSelector> selectors,
416 std::span<const ShaderVariantDefinition> variants)
417 {
418 std::vector<ShaderInterfaceMemberDefinition>& vertexMembers = materialPermutation.effect.vertexShader.shaderInterface.members;
419 std::vector<ShaderInterfaceMemberDefinition>& geometryMembers = materialPermutation.effect.geometryShader.shaderInterface.members;
420 std::vector<ShaderInterfaceMemberDefinition>& surfaceMembers = materialPermutation.effect.pixelShader.shaderInterface.members;
421
422 for (const ShaderVariantSelector& v : selectors) {
423 const ShaderVariantDefinition& variant = variants[v.index];
424 if ((variant.type == ShaderVariantType::Bool && v.value == 1) ||
425 (variant.type == ShaderVariantType::Format && v.value != 0))
426 {
427 size_t a = vertexMembers.size();
428 addShaderStageInterfaceMembers(materialPermutation.name, vertexMembers, variant.vertexInterface.members);
429 size_t b = vertexMembers.size();
430
431 // Override format if requested
432 if (variant.type == ShaderVariantType::Format) {
433 for (size_t i = a; i < b; i++) {
434 if (vertexMembers[i].type == MaterialDataType::Unknown) {
435 vertexMembers[i].type = formatShaderType(Cogs::Format(selectors[variant.index].value));
436 }
437 }
438 }
439 addShaderStageInterfaceMembers(materialPermutation.name, geometryMembers, variant.geometryInterface.members);
440 addShaderStageInterfaceMembers(materialPermutation.name, surfaceMembers, variant.surfaceInterface.members);
441 }
442 }
443 moveSystemGeneratedParametersLast(materialPermutation.effect.pixelShader.shaderInterface.members);
444 }
445
446
448 void addDefinesFromVariants(std::vector<std::pair<std::string, std::string>>& definitions,
449 const MaterialInstance* materialInstance,
450 std::span<const ShaderVariantSelector> selectors,
451 std::span<const ShaderVariantDefinition> variants)
452 {
453 for (const ShaderVariantSelector& selector : selectors) {
454 const ShaderVariantDefinition& variant = variants[selector.index];
455
456 if (variant.type == ShaderVariantType::Bool && selector.value == 1) {
457 definitions.emplace_back(variant.value, "1");
458 }
459
460 else if (variant.type == ShaderVariantType::Int) {
461 definitions.emplace_back(variant.value, std::to_string(selector.value));
462 }
463
464 else if (variant.type == ShaderVariantType::Enum) {
465 for (auto & enumerator : variant.values) {
466 if (enumerator.index == selector.value) {
467 definitions.emplace_back(enumerator.value, "1");
468 break;
469 }
470 }
471 }
472
473 else if (variant.type == ShaderVariantType::Format && selector.value != 0) {
474
475 const char* value = nullptr;
476 switch (formatShaderType(Cogs::Format(selector.value))) {
477 case MaterialDataType::Float: value = "COGS_FLOAT"; break;
478 case MaterialDataType::Float2: value = "COGS_FLOAT2"; break;
479 case MaterialDataType::Float3: value = "COGS_FLOAT3"; break;
480 case MaterialDataType::Float4: value = "COGS_FLOAT4"; break;
481 case MaterialDataType::UInt: value = "COGS_UINT"; break;
482 case MaterialDataType::UInt2: value = "COGS_UINT2"; break;
483 case MaterialDataType::UInt3: value = "COGS_UINT3"; break;
484 case MaterialDataType::UInt4: value = "COGS_UINT4"; break;
485 case MaterialDataType::Int: value = "COGS_INT"; break;
486 case MaterialDataType::Int2: value = "COGS_INT2"; break;
487 case MaterialDataType::Int3: value = "COGS_INT3"; break;
488 case MaterialDataType::Int4: value = "COGS_INT4"; break;
489 case MaterialDataType::Float4x4: value = "COGS_FLOAT4X4"; break;
490 default:
491 LOG_ERROR(logger, "Variant selector value is not a valid vertex format");
492 value = "ERROR";
493 break;
494 }
495 definitions.emplace_back(variant.value, value);
496 }
497
498 else if (variant.type == ShaderVariantType::String) {
499 if (selector.value == size_t(-1)) continue;
500
501 assert(selector.value <= materialInstance->variantStrings.size() && "Variant selector string index out of range.");
502 definitions.emplace_back(variant.value, materialInstance->variantStrings[selector.value]);
503 }
504 }
505 }
506
507 bool sanityCheckVertexElements(std::span<const Cogs::VertexElement> vertexElements)
508 {
509 for (size_t i = 1; i < vertexElements.size(); i++) {
510 for (size_t j = i; j < vertexElements.size(); j++) {
511
512 if ((vertexElements[i - 1].semantic == vertexElements[j].semantic) &&
513 (vertexElements[i - 1].semanticIndex == vertexElements[j].semanticIndex))
514 {
515 LOG_ERROR(logger, "Invalid streams layout, vertex element semantic %.*s%u is specified more than once",
516 StringViewFormat(Cogs::getElementSemanticName(vertexElements[j].semantic)),
517 unsigned(vertexElements[j].semanticIndex));
518 return false;
519 }
520 }
521 }
522 return true;
523 }
524
525
526 void adjustVariantsUsingVertexElements(ShaderVariantSelectors& variantSelectors,
527 const MaterialInstance* materialInstance,
528 std::span<const Cogs::VertexElement> vertexElements)
529 {
530 const size_t indexLimit = variantSelectors.size();
531 const ShaderVariants& variantDefinitions = materialInstance->material->definition.variants;
532 assert(variantDefinitions.size() == indexLimit);
533
534 std::string variantKeyBase;
535 for (const Cogs::VertexElement& element : vertexElements) {
536
537 switch (element.semantic) {
538 case Cogs::ElementSemantic::Position: variantKeyBase = "VertexStreamPosition"; break;
539 case Cogs::ElementSemantic::Normal: variantKeyBase = "VertexStreamNormal"; break;
540 case Cogs::ElementSemantic::Color: variantKeyBase = "VertexStreamColor"; break;
541 case Cogs::ElementSemantic::TextureCoordinate: variantKeyBase = "VertexStreamTexCoord"; break;
542 case Cogs::ElementSemantic::Tangent: variantKeyBase = "VertexStreamTangent"; break;
543 case Cogs::ElementSemantic::InstanceVector: variantKeyBase = "InstanceVector"; break;
544 case Cogs::ElementSemantic::InstanceMatrix: variantKeyBase = "InstanceMatrix"; break;
545 default:
546 assert(false && "Illegal semantic");
547 break;
548 }
549
550 assert(element.semanticIndex < 10);
551 variantKeyBase.push_back(char('0' + element.semanticIndex));
552 if (size_t index = materialInstance->material->getVariantIndex(variantKeyBase); index != Material::NoVariantIndex) {
553 assert(index < indexLimit);
554 assert(variantSelectors[index].index == index);
555 assert(variantDefinitions[index].type == ShaderVariantType::Format && "Error in MaterialBase.material");
556 variantSelectors[index].value = size_t(element.format);
557 }
558
559 }
560 }
561
562 [[nodiscard]] bool checkSingleRequirement(const ShaderVariantSelectors& variantSelectors,
563 const MaterialInstance* materialInstance,
564 const ShaderVariantRequirement& requirement)
565 {
566 if (requirement.variant.empty()) {
567 LOG_ERROR(logger, "Empty variant requirement");
568 return false;
569 }
570
571 // Just check for presence, here we allow | in key
572 if (requirement.value.empty()) {
573 const char* a = requirement.variant.c_str();
574 assert(a);
575 do {
576 const char* b = a;
577 while (*b != '\0' && *b != '|') { b++; }
578 Cogs::StringView key(a, b - a);
579
580 if (size_t ix = materialInstance->material->getVariantIndex(key); ix != Material::NoVariantIndex) {
581 const ShaderVariantDefinition& target = materialInstance->material->definition.variants[ix];
582 switch (target.type) {
583 case ShaderVariantType::Bool:
584 case ShaderVariantType::Int:
585 case ShaderVariantType::Format:
586 if (variantSelectors[ix].value != 0) return true;
587 break;
588
589 case ShaderVariantType::Enum:
590 case ShaderVariantType::String:
591 LOG_ERROR(logger, "Requirement '%.*s' references variant with unsupported type", StringViewFormat(key));
592 break;
593 default:
594 assert(false);
595 }
596 }
597 else {
598 LOG_ERROR(logger, "Variant requirement '%.*s' refers non-existing variant", StringViewFormat(key));
599 }
600
601 if (*b == '\0') {
602 break;
603 }
604
605 a = b + 1;
606 } while (true);
607
608 return false;
609 }
610
611 size_t ix = materialInstance->material->getVariantIndex(requirement.variant);
612
613 if (ix == Material::NoVariantIndex) {
614 LOG_ERROR(logger, "Variant requirement %s=%s refers non-existing variant", requirement.variant.c_str(), requirement.value.c_str());
615 return false;
616 }
617
618 assert(ix < materialInstance->material->definition.variants.size());
619 const ShaderVariantDefinition& target = materialInstance->material->definition.variants[ix];
620
621 // We have an expression. We allow multiple checks to be or'ed together using |
622 bool anySuccess = false;
623 Cogs::StringView expression = requirement.value;
624 size_t a = 0;
625 do {
626 size_t b = expression.find_first_of('|', a);
627 assert(a <= b);
628 if (a < b) {
629 Cogs::StringView value = expression.substr(a, b == Cogs::StringView::NoPosition ? b : b - a);
630
631 // Check for specific value
632 switch (target.type) {
633 case ShaderVariantType::Format:
634
635 if (variantSelectors[ix].value && value == formatShaderTypeString(Cogs::Format(variantSelectors[ix].value))) {
636 anySuccess = true;
637 }
638 else if (const Cogs::FormatInfo* info = Cogs::getFormatInfo(Cogs::Format(variantSelectors[ix].value)); info) {
639 if (value == info->vName || value == info->name) {
640 anySuccess = true;
641 }
642 }
643 break;
644
645 case ShaderVariantType::Bool:
646 case ShaderVariantType::Int:
647 case ShaderVariantType::Enum:
648 case ShaderVariantType::String:
649 LOG_ERROR(logger, "Requirement '%s' references variant with type with unimplemented handling", requirement.variant.c_str());
650 return false;
651 default:
652 assert(false);
653 }
654 }
655 if (b == Cogs::StringView::NoPosition) break;
656 a = b + 1;
657 } while (!anySuccess);
658
659 return anySuccess;
660 }
661
662 void checkVariantTriggers(ShaderVariantSelectors& variantSelectors,
663 const MaterialInstance* materialInstance)
664 {
665 std::vector<size_t> unsatisifiedSet;
666 for (const ShaderVariantSelector& selector : variantSelectors) {
667 const ShaderVariantDefinition& variant = materialInstance->material->definition.variants[selector.index];
668 if (/*selector.value == 0 &&*/ variant.type == ShaderVariantType::Bool && !variant.triggers.empty()) {
669 unsatisifiedSet.push_back(selector.index);
670 }
671 }
672
673 bool anyChange = true;
674 std::vector<size_t> nextSet;
675 while (!unsatisifiedSet.empty() && anyChange) {
676 anyChange = false;
677 nextSet.clear();
678
679 while (!unsatisifiedSet.empty()) {
680 size_t unsatisfiedIx = unsatisifiedSet.back();
681 unsatisifiedSet.pop_back();
682
683 const ShaderVariantDefinition& unsatisifed = materialInstance->material->definition.variants[unsatisfiedIx];
684
685 bool allSatisfied = true;
686 for (const ShaderVariantRequirement& requirement : unsatisifed.triggers) {
687 allSatisfied = checkSingleRequirement(variantSelectors, materialInstance, requirement);
688 if (!allSatisfied) break;
689 }
690
691 if (allSatisfied) {
692 variantSelectors[unsatisfiedIx].value = 1;
693 anyChange = true;
694 }
695 else {
696 nextSet.push_back(unsatisfiedIx);
697 }
698 }
699 unsatisifiedSet.swap(nextSet);
700 }
701 }
702
703 [[nodiscard]] bool checkMaterialRequirements(const ShaderVariantSelectors& variantSelectors,
704 const MaterialInstance* materialInstance)
705 {
706
707 for (const ShaderVariantRequirement& requirement : materialInstance->material->definition.requirements) {
708 if (!checkSingleRequirement(variantSelectors, materialInstance, requirement)) {
709 LOG_ERROR(logger, "Material requirement %s=%s not met", requirement.variant.c_str(), requirement.value.c_str());
710 return false;
711 }
712 }
713 return true;
714 }
715
716 [[nodiscard]] bool checkVariantRequirements(const ShaderVariantSelectors& variantSelectors,
717 const MaterialInstance* materialInstance)
718 {
719 for (const ShaderVariantSelector& selector : variantSelectors) {
720
721 const ShaderVariantDefinition& variant = materialInstance->material->definition.variants[selector.index];
722 if (selector.value && variant.type == ShaderVariantType::Bool && !variant.requirements.empty()) {
723
724 for(const ShaderVariantRequirement& requirement : variant.requirements) {
725
726 if (!checkSingleRequirement(variantSelectors, materialInstance, requirement)) {
727 LOG_ERROR(logger, "Material '%.*s' instance '%.*s' variant requirement %s=%s not met",
728 StringViewFormat(materialInstance->material->getName()),
729 StringViewFormat(materialInstance->getName()),
730 requirement.variant.c_str(),
731 requirement.value.c_str());
732 return false;
733 }
734
735 }
736 }
737 }
738 return true;
739
740 }
741
742
743 bool matchInterfaceMemberToVertexElement(std::span<size_t> memberElementIndex,
744 std::span<const ShaderInterfaceMemberDefinition> interfaceMembers,
745 std::span<const Cogs::VertexElement> vertexElements)
746
747 {
748 assert(memberElementIndex.size() == interfaceMembers.size());
749
750 for (size_t j = 0; j < memberElementIndex.size(); j++) {
751 const ShaderInterfaceMemberDefinition& member = interfaceMembers[j];
752
753 // Match Cogs.Core semantic name to Cogs.Rendering semantic name
755 switch (member.semantic.name) {
756
757 // Semantic not set, report issue and give up.
758 case ShaderInterfaceMemberDefinition::SemanticName::None:
759 LOG_ERROR(logger, "Vertex shader input '%s' has no associated semantic", member.name.c_str());
760 return false;
761
762 case ShaderInterfaceMemberDefinition::SemanticName::Position: semanticName = Cogs::ElementSemantic::Position; break;
763 case ShaderInterfaceMemberDefinition::SemanticName::Normal: semanticName = Cogs::ElementSemantic::Normal; break;
764 case ShaderInterfaceMemberDefinition::SemanticName::Color: semanticName = Cogs::ElementSemantic::Color; break;
765 case ShaderInterfaceMemberDefinition::SemanticName::Texcoord: semanticName = Cogs::ElementSemantic::TextureCoordinate; break;
766 case ShaderInterfaceMemberDefinition::SemanticName::Tangent: semanticName = Cogs::ElementSemantic::Tangent; break;
767 case ShaderInterfaceMemberDefinition::SemanticName::InstanceVector: semanticName = Cogs::ElementSemantic::InstanceVector; break;
768 case ShaderInterfaceMemberDefinition::SemanticName::InstanceMatrix: semanticName = Cogs::ElementSemantic::InstanceMatrix; break;
769 default:
770
771 // System semantics are not sourced by vertex streams
772 assert(size_t(ShaderInterfaceMemberDefinition::SemanticName::FirstSystemValueSemantic) <= size_t(member.semantic.name));
773 memberElementIndex[j] = ~size_t(0);
774 continue;
775 }
776
777 // Try to match semantic name and slot to the vertex streams
778 for (size_t i = 0; i < vertexElements.size(); i++) {
779
780 const Cogs::VertexElement& element = vertexElements[i];
781 if ((element.semantic == semanticName) && (element.semanticIndex == member.semantic.slot)) {
782
783 // Yay.
784 memberElementIndex[j] = i;
785 goto found;
786 }
787 }
788
789 // Didn't find a match, report issue and give up.
790 LOG_ERROR(logger, "Failed to match vertex shader input '%s' with semantic %.*s:%u to a vertex stream semantic",
791 member.name.c_str(),
792 StringViewFormat(ShaderInterfaceMemberDefinition::semanticNameString(member.semantic.name)),
793 unsigned(member.semantic.slot));
794 return false;
795 found:
796 ;
797 }
798 return true;
799 }
800
801 void setEnumVariant(ShaderVariantSelectors& variantSelectors, const MaterialInstance* materialInstance, const Cogs::StringView& key, const Cogs::StringView& value)
802 {
803 if (size_t index = materialInstance->material->getVariantIndex(key); index != Material::NoVariantIndex) {
804 assert(index < variantSelectors.size());
805 assert(variantSelectors[index].index == index);
806 const ShaderVariants& variantDefinitions = materialInstance->material->definition.variants;
807 assert(variantDefinitions[index].type == ShaderVariantType::Enum && "Error in MaterialBase.material");
808 for (const ShaderVariantEnum& e : variantDefinitions[index].values)
809 if (e.key == value) {
810 variantSelectors[index].value = e.index;
811 }
812 }
813 }
814
815 void setBoolVariant(ShaderVariantSelectors& variantSelectors, const MaterialInstance* materialInstance, const Cogs::StringView& key, bool value)
816 {
817 if (size_t index = materialInstance->material->getVariantIndex(key); index != Material::NoVariantIndex) {
818 assert(index < variantSelectors.size());
819 assert(variantSelectors[index].index == index);
820 const ShaderVariants& variantDefinitions = materialInstance->material->definition.variants;
821 assert(variantDefinitions[index].type == ShaderVariantType::Bool && "Error in MaterialBase.material");
822 variantSelectors[index].value = value ? 1 : 0;
823 }
824 }
825
826}
827
829 const MaterialInstance* materialInstance,
830 const MeshStreamsLayout* streamsLayout,
831 const EnginePermutation* permutation,
832 const RenderPassOptions& passOptions,
833 const ClipShapeType clipShape)
834{
835 assert(materialInstance);
836 assert(streamsLayout);
837 assert(permutation);
838 static int id = 0;
839
840 Cogs::GraphicsDeviceType graphicsDeviceType = context->renderer->getDevice()->getType();
841
842 if (material->definition.permutations.empty() || materialInstance->permutationIndex > (material->definition.permutations.size() - 1)) {
843 LOG_ERROR(logger, "Permutation out of range.");
845 }
846
847 // Concatenate all vertex elements into a single array for convenience
848 std::vector<VertexElement> vertexElements;
849 for (size_t i = 0; i < streamsLayout->numStreams; i++) {
850 const VertexFormat* format = VertexFormats::getVertexFormat(streamsLayout->vertexFormats[i]);
851 vertexElements.insert(vertexElements.end(),
852 format->elements.begin(),
853 format->elements.end());
854 }
855 if (!sanityCheckVertexElements(vertexElements)) {
857 }
858
859 ShaderVariantSelectors variantSelectors = materialInstance->variantSelectors;
860 adjustVariantsUsingVertexElements(variantSelectors, materialInstance, vertexElements);
861
862 if (clipShape != ClipShapeType::None) {
863
864 bool clipInPixelShader = graphicsDeviceType == GraphicsDeviceType::OpenGLES30;
865
866 switch (clipShape) {
868 break;
870 setEnumVariant(variantSelectors, materialInstance, "ClipShape", "Cube");
871 break;
873 setEnumVariant(variantSelectors, materialInstance, "ClipShape", "InvertedCube");
874 clipInPixelShader = true;
875 break;
876 default:
877 assert(false && "Invalid enum");
878 }
879
880 if (clipInPixelShader) {
881 setBoolVariant(variantSelectors, materialInstance, "ClipInPixelShader", true);
882 }
883 else {
884 setBoolVariant(variantSelectors, materialInstance, "ClipInVertexShader", true);
885 }
886 }
887
888
889 checkVariantTriggers(variantSelectors, materialInstance);
890
891
892 if (!checkMaterialRequirements(variantSelectors, materialInstance)
893 || !checkVariantRequirements(variantSelectors, materialInstance))
894 {
896 }
897
898 // Create a new material permutation
899 // ---------------------------------
900
901 // Start off with a copy from the material definition and add a running number to its name
902 MaterialDefinition materialPermutation = material->definition.permutations[materialInstance->permutationIndex];
903 materialPermutation.name += std::to_string(id++);
904 materialPermutation.effect.streamsLayout = *streamsLayout;
905
906 // Set shared variant values from material
907 assert(variantSelectors.size() == material->definition.variants.size());
908 for (const ShaderVariantDefinition& definition : material->definition.variants) {
909 if (definition.isShared) {
910 variantSelectors[definition.index].value = definition.defaultValue;
911 }
912 }
913
914 // Resolve any interface members if there are conflicts
915 resolveShaderInterfaceMembers(materialPermutation);
916
917 // Add shader stage interface members, handling conflicts as they appear
918 addShaderInterfaceMembersFromVariants(materialPermutation, variantSelectors, materialPermutation.variants);
919 addShaderInterfaceMembersFromVariants(materialPermutation, permutation->getSelectors(), permutation->getVariants());
920
921 // Match vertex stage elements to input streams
922 std::vector<size_t> memberElementIndex(materialPermutation.effect.vertexShader.shaderInterface.members.size());
923 if (!matchInterfaceMemberToVertexElement(memberElementIndex,
924 materialPermutation.effect.vertexShader.shaderInterface.members,
925 vertexElements))
926 {
928 }
929
930 const std::string& permutationName = permutation->getDefinition()->name;
931
932 // Set up the effect definition
933 materialPermutation.effect.name = materialPermutation.name + permutationName;
934 materialPermutation.effect.vertexShader.loadPath = materialPermutation.name + ShaderNames[ShaderTypes::Vertex] + permutationName;
935 if (materialPermutation.effect.hullShader.customSourcePath.size()) {
936 materialPermutation.effect.hullShader.loadPath = materialPermutation.name + ShaderNames[ShaderTypes::Hull] + permutationName;
937 materialPermutation.effect.domainShader.loadPath = materialPermutation.name + ShaderNames[ShaderTypes::Domain] + permutationName;
938 }
939 if (materialPermutation.effect.geometryShader.customSourcePath.size()) {
940 materialPermutation.effect.geometryShader.loadPath = materialPermutation.name + ShaderNames[ShaderTypes::Geometry] + permutationName;
941 }
942 materialPermutation.effect.pixelShader.loadPath = materialPermutation.name + ShaderNames[ShaderTypes::Pixel] + permutationName;
943
944 // Aggregate preprocessor definitions
945 std::vector<std::pair<std::string, std::string>> definitions{
946
947 // Define values that BASE_MATERIAL_VERTEX_XYZ can take
948 { "COGS_FLOAT", "1" },
949 { "COGS_FLOAT2", "2" },
950 { "COGS_FLOAT3", "3" },
951 { "COGS_FLOAT4", "4" },
952 { "COGS_UINT", "5" },
953 { "COGS_UINT2", "6" },
954 { "COGS_UINT3", "7" },
955 { "COGS_UINT4", "8" },
956 { "COGS_INT", "9" },
957 { "COGS_INT2", "10" },
958 { "COGS_INT3", "11" },
959 { "COGS_INT4", "12" },
960 { "COGS_FLOAT4X4", "13" }
961
962 };
963
964 for (auto & d : materialPermutation.effect.definitions) {
965 definitions.emplace_back(d.first, d.second);
966 }
967 for (auto & d : materialPermutation.effect.definitions) {
968 definitions.emplace_back(d.first, d.second);
969 }
970 for (auto & d : permutation->getDefinition()->definitions) {
971 definitions.emplace_back(d.first, d.second);
972 }
973 addDefinesFromVariants(definitions, materialInstance, permutation->getSelectors(), permutation->getVariants());
974 addDefinesFromVariants(definitions, materialInstance, variantSelectors, materialPermutation.variants);
975
976 {
977 bool success;
978 switch (graphicsDeviceType)
979 {
981 success = buildEffectES3(context, definitions, materialPermutation, *permutation, passOptions.multiViews);
982 break;
984 success = buildEffectWebGPU(context, definitions, materialPermutation, *permutation, passOptions.multiViews);
985 break;
986 default:
987 success = buildEffect(context, definitions, materialPermutation, *permutation);
988 break;
989 }
990 if (!success) {
991 LOG_ERROR(logger, "Failed to build material shader source");
993 }
994 }
995
996 materialPermutation.effect.definitions.clear();
997 for (const std::pair<std::string,std::string>& d : definitions) {
998 materialPermutation.effect.definitions.emplace_back(PreprocessorDefinition{ d.first, d.second });
999 }
1000
1001 EffectHandle effect = context->effectManager->loadEffect(materialPermutation.effect);
1002
1003 effect->material = material;
1004
1005 if (context->variables->get("resources.effects.autoReload", false)) {
1006 auto watchShader = [&](const StringView & fileName) {
1007 auto path = context->resourceStore->getResourcePath(fileName);
1008
1009 auto callback = [this, e = effect.get(), path](FileSystemWatcher::Event) {
1010 context->resourceStore->purge(path);
1011
1012 context->effectManager->handleReload(ResourceHandleBase(e));
1013 };
1014
1015 auto absolute = IO::absolute(path);
1016
1017 context->watcher->watchFile(absolute, callback);
1018 };
1019
1020 watchShader(materialPermutation.effect.vertexShader.customSourcePath);
1021 watchShader(materialPermutation.effect.pixelShader.customSourcePath);
1022
1023 watchShader(permutation->getDefinition()->vertexShader);
1024 watchShader(permutation->getDefinition()->pixelShader);
1025 }
1026
1027 return effect;
1028}
1029
1031{
1032 return context->renderer->getResources()->updateResource(handle);
1033}
1034
1036{
1037 context->renderer->getResources()->releaseResource(resource);
1038}
1039
1041{
1042 reportLeaks("MaterialInstance");
1043}
1044
1046{
1047 ResourceManager::initialize();
1048
1049 resources.resize(16384);
1050}
1051
1052void Cogs::Core::MaterialInstanceManager::initializeDefaultMaterialInstance()
1053{
1054 defaultResource = createMaterialInstance(context->materialManager->getDefaultMaterial());
1055}
1056
1058{
1059 auto material = materialHandle.resolve();
1060
1061 auto handle = create();
1062 auto instance = handle.resolve();
1063
1064 instance->setupInstance(material);
1065
1066 instance->setLoaded();
1067 instance->setChanged();
1068
1069 return handle;
1070}
1071
1072Cogs::Core::MaterialInstanceHandle Cogs::Core::MaterialInstanceManager::getMaterialInstance(const StringView & name)
1073{
1074 return getByName(name);
1075}
1076
1078{
1079 return context->renderer->getResources()->updateResource(handle);
1080}
1081
1083{
1084 context->renderer->getResources()->releaseResource(resource);
1085}
1086
1087int Cogs::Core::MaterialInstanceManager::getUpdateQuota() const
1088{
1089 return context->variables->get("resources.materialInstances.frameUpdateQuota", 0);
1090}
void initialize() override
Initialize the MaterialInstanceManager.
~MaterialInstanceManager()
Destructs the MaterialInstanceManager.
MaterialInstanceHandle createMaterialInstance(const MaterialHandle &material)
Create a new MaterialInstance from the Material given held by material.
void handleDeletion(MaterialInstance *resource) override
Overridden to handle material instance deletion, removing the resource from the renderer.
ActivationResult handleActivation(MaterialInstanceHandle handle, MaterialInstance *resource) override
Overridden to handle activation of MaterialInstances, updating the resource in the renderer.
MaterialHandle loadMaterial(const StringView &fileName, MaterialLoadFlags materialLoadFlags=MaterialLoadFlags::None, ResourceId resourceId=NoResourceId)
Loads a Material from the given fileName.
void handleDeletion(Material *resource) override
Overridden to handle material deletion, removing the resource from the renderer.
ActivationResult handleActivation(MaterialHandle handle, Material *resource) override
Overridden to handle activation of Materials, updating the resource in the renderer.
MaterialHandle getDefaultMaterial()
Get the default material.
void releaseAll()
Clear out all materials, done as part of shutdown process.
EffectHandle loadMaterialVariant(Material *material, const MaterialInstance *materialInstance, const MeshStreamsLayout *streamsLayout, const EnginePermutation *permutation, const RenderPassOptions &passOptions, const ClipShapeType clipShape)
void handleLoad(MaterialLoadInfo *loadInfo) override
Overridden to handle loading of Material resources.
void initializeDefaultMaterial()
Initializes the MaterialManager, setting up the default material.
~MaterialManager()
Destructs the MaterialManager.
void clear() override
Clear the resource manager, cleaning up resources held by member handles.
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
size_t find_first_of(char character, size_t pos=0) const noexcept
Find the first occurance of the given character from the specified starting position.
Definition: StringView.cpp:46
constexpr StringView substr(size_t offset, size_t count=NoPosition) const noexcept
Get the given sub string.
Definition: StringView.h:258
static constexpr size_t NoPosition
No position.
Definition: StringView.h:43
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ClipShapeType
Specifices what kind of shape a clip shape has.
@ InvertedCube
Clip the inside of a cube.
@ None
No clipping at all.
@ Cube
Clip the outside of a cube,.
ActivationResult
Defines results for resource activation.
Definition: ResourceBase.h:14
MaterialDataType
Defines available data types for material properties.
Definition: MaterialTypes.h:20
MaterialLoadFlags
Material loading flags.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
GraphicsDeviceType
Contains types of graphics devices that may be supported.
Definition: Base.h:48
@ OpenGLES30
Graphics device using the OpenGLES 3.0 API.
@ WebGPU
Graphics device using the WebGPU API Backend.
std::pair< std::string, std::string > PreprocessorDefinition
Preprocessor definition.
Definition: IEffects.h:10
ElementSemantic
Element semantics used to map data to the shader stage.
Definition: VertexFormat.h:14
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ Normal
Normal semantic.
@ InstanceMatrix
Instance matrix semantic.
@ InstanceVector
Instance vector semantic.
@ Color
Color semantic.
@ TextureCoordinate
Texture coordinate semantic.
std::vector< MaterialPropertyBuffer > buffers
Constant buffer instances.
uint16_t buffersGeneration
If the constant buffer bindings need updates.
static void initialize(MaterialManager *materialManager)
Initialize the default material, updating all material property keys.
std::string name
Name of the effect.
MeshStreamsLayout streamsLayout
The vertex layout this effect expects.
PreprocessorDefinitions definitions
Preprocessor definitions.
struct Material * material
Owning material resource.
Definition: Effect.h:37
Material instances represent a specialized Material combined with state for all its buffers and prope...
std::vector< std::string > variantStrings
String storage for string variants.
size_t permutationIndex
Index of material permutation to use.
Material * material
Material resource this MaterialInstance is created from.
ShaderVariantSelectors variantSelectors
Variant selectors.
Defines loading information for Material resources.
Material resources define the how of geometry rendering (the what is defined by Mesh and Texture reso...
Definition: Material.h:82
VertexFormatHandle vertexFormats[maxStreams]
StringView getName() const
Get the name of the resource.
Definition: ResourceBase.h:307
Resource handle base class handling reference counting of resources derived from ResourceBase.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
std::string resourcePath
Resource path. Used to locate resource.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38
uint16_t semanticIndex
Index for the semantic mapping.
Definition: VertexFormat.h:42
ElementSemantic semantic
Semantic mapping of the element (position, normal, etc...).
Definition: VertexFormat.h:41
Vertex format structure used to describe a single vertex for the input assembler.
Definition: VertexFormat.h:60
std::vector< VertexElement > elements
Vector containing all vertex elements of this format.
Definition: VertexFormat.h:62