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