Cogs.Core
EnginePermutations.cpp
1#include "EnginePermutations.h"
2
3#include "Context.h"
4
5#include "ParameterBuffers.h"
6
7#include "Resources/Material.h"
8#include "Resources/MaterialBuilder.h"
9
10#include "Serialization/JsonParser.h"
11#include "Serialization/MaterialReader.h"
12
13#include "Utilities/Parsing.h"
14#include "Components/Core/LightComponent.h"
15#include "Services/Variables.h"
16#include "RenderList.h"
17
18#include "Foundation/HashSequence.h"
19#include "Foundation/Logging/Logger.h"
20
21namespace
22{
23 Cogs::Logging::Log logger = Cogs::Logging::getLogger("EnginePermutations");
24
25 const size_t kMaxEnginePermutations = 64;
26
27 [[nodiscard]] bool parseType(Cogs::StringView valueString, Cogs::Core::ValueDefinition& value, bool allowFormat)
28 {
29 using namespace Cogs::Core;
30 const size_t spaceIndex = valueString.find(" ");
31 const Cogs::StringView typeString = (spaceIndex != Cogs::StringView::NoPosition) ? valueString.substr(spaceIndex) : valueString;
32
33 // Magic hook for when type is format
34 if (allowFormat && typeString.hash() == Cogs::hash("format")) {
35 value.type = MaterialDataType::Unknown;
36 return true;
37 }
38
39 switch (typeString.hash()) {
40 case Cogs::hash("float"): value.type = MaterialDataType::Float; break;
41 case Cogs::hash("float2"): value.type = MaterialDataType::Float2; break;
42 case Cogs::hash("float3"): value.type = MaterialDataType::Float3; break;
43 case Cogs::hash("float4"): value.type = MaterialDataType::Float4; break;
44 case Cogs::hash("float4x4"): value.type = MaterialDataType::Float4x4; break;
45 case Cogs::hash("float4[]"): value.type = MaterialDataType::Float4Array; value.dimension = (size_t)-1; break;
46 case Cogs::hash("float4x4[]"): value.type = MaterialDataType::Float4x4Array; value.dimension = (size_t)-1; break;
47 case Cogs::hash("bool"): value.type = MaterialDataType::Bool; break;
48 case Cogs::hash("uint"): value.type = MaterialDataType::UInt; break;
49 case Cogs::hash("uint2"): value.type = MaterialDataType::UInt2; break;
50 case Cogs::hash("uint3"): value.type = MaterialDataType::UInt3; break;
51 case Cogs::hash("uint4"): value.type = MaterialDataType::UInt4; break;
52 case Cogs::hash("int"): value.type = MaterialDataType::Int; break;
53 case Cogs::hash("int2"): value.type = MaterialDataType::Int2; break;
54 case Cogs::hash("int3"): value.type = MaterialDataType::Int3; break;
55 case Cogs::hash("int4"): value.type = MaterialDataType::Int4; break;
56 case Cogs::hash("VFACE"): value.type = MaterialDataType::VFACE; break;
57 case Cogs::hash("SV_IsFrontFace"): value.type = MaterialDataType::SV_IsFrontFace; break;
58 case Cogs::hash("SV_InstanceID"): value.type = MaterialDataType::SV_InstanceID; break;
59 case Cogs::hash("SV_ClipDistance"): value.type = MaterialDataType::ClipDistance; break;
60 case Cogs::hash("SV_Position"): value.type = MaterialDataType::Position; break;
61 default:
62
63 // Try to match (.*)[d+] where first match gets checked against float4 and float4x4
64 for (size_t i = 0; i + 1 < typeString.size(); i++) {
65 if ((typeString[i] == '[') && ('0' <= typeString[i + 1]) && (typeString[i + 1] <= '9')) {
66
67 size_t dimension = typeString[i + 1] - '0';
68 for (size_t j = i + 2; j < typeString.size(); j++) {
69 if ('0' <= typeString[j] && typeString[j] <= '9') {
70 dimension = 10 * dimension + (typeString[j] - '0');
71 }
72 else if ((typeString[j] == ']') && (j + 1 == typeString.size())) {
73 switch (typeString.substr(0, i).hash()) {
74
75 case Cogs::hash("float4"):
76 value.type = MaterialDataType::Float4Array;
77 value.dimension = dimension;
78 value.dimensionString = typeString.substr(i + 1, j - i - 1).to_string();
79 return true;
80
81 case Cogs::hash("float4x4"):
82 value.type = MaterialDataType::Float4x4Array;
83 value.dimension = dimension;
84 value.dimensionString = typeString.substr(i + 1, j - i - 1).to_string();
85 return true;
86
87 default:
88 break;
89 }
90 }
91 else {
92 break;
93 }
94 }
95 break;
96 }
97 }
98 LOG_ERROR(logger, "Failed to parse datatype '%.*s'", StringViewFormat(valueString));
99 return false;
100 }
101 return true;
102 }
103
104 [[nodiscard]] bool parseInterpolationModifier(const Value& jsonObject, Cogs::Core::ShaderInterfaceMemberDefinition& memberDefinition)
105 {
106 using namespace Cogs::Core;
107 if (!jsonObject.IsString()) {
108 LOG_ERROR(logger, "Interpolation modifier is not a JSON string");
109 return false;
110 }
111
112 Cogs::StringView value = toView(jsonObject);
113 switch (value.hash()) {
114 case Cogs::hash("linear"):
115 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::LinearModifier);
116 break;
117 case Cogs::hash("centroid"):
118 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::CentroidModifier);
119 break;
120 case Cogs::hash("nointerpolation"):
121 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::NointerpolationModifier);
122 break;
123 case Cogs::hash("noperspective"):
124 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::NoperspectiveModifier);
125 break;
126 case Cogs::hash("sample"):
127 memberDefinition.modifiers = static_cast<ShaderInterfaceMemberDefinition::InterpolationModifiers>(memberDefinition.modifiers | ShaderInterfaceMemberDefinition::SampleModifier);
128 break;
129 default:
130 LOG_ERROR(logger, "Unrecognized interpolation modifier '%.*s'", StringViewFormat(value));
131 return false;
132 }
133 return true;
134 }
135
136 [[nodiscard]] bool parseShaderInterface(const Value& jsonObject, Cogs::Core::ShaderInterfaceDefinition& interfaceDefinition, uint8_t inheritanceLevel, bool allowFormat)
137 {
138 using namespace Cogs::Core;
139 if (!jsonObject.IsObject()) {
140 LOG_ERROR(logger, "Shader interface is not an JSON object");
141 return false;
142 }
143
144 for (auto& m : jsonObject.GetObject()) {
145 Cogs::StringView name = Cogs::Core::toView(m.name);
146 auto& obj = m.value;
147
149
150 if (obj.IsArray()) {
151 auto arr = obj.GetArray();
152
153
154
155 if (!parseType(toKey(arr[0]), value, allowFormat)) return false;
156
157 if (value.dimension == size_t(-1)) {
158 if (arr[1].IsString()) {
159 value.dimensionString = toString(arr[1]);
160 }
161 else {
162 value.dimension = arr[1].GetInt();
163 }
164 }
165 else {
166 if (arr.Size() >= 2) {
167 if (arr[1].IsString()) {
168 value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::None;
169 value.semantic.slot = 0;
170 Cogs::StringView semanticName(arr[1].GetString(), arr[1].GetStringLength());
171
172 // Check for trailing digits, interpreted and chomp semanticName.
173 for (size_t i = 0; i < semanticName.size(); i++) {
174 if ('0' <= semanticName[i] && semanticName[i] <= '9') {
175 value.semantic.slot = semanticName[i] - '0';
176 size_t k = i + 1;
177 while ((k < semanticName.size()) &&
178 ('0' <= semanticName[k]) &&
179 (semanticName[k] <= '9'))
180 {
181 value.semantic.slot = uint8_t(10u * value.semantic.slot + (semanticName[k] - '0'));
182 }
183 semanticName = semanticName.substr(0, i);
184 break;
185 }
186 }
187 switch (semanticName.hashLowercase()) {
188 case Cogs::hash("position"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Position; break;
189 case Cogs::hash("normal"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Normal; break;
190 case Cogs::hash("color"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Color; break;
191 case Cogs::hash("texcoord"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Texcoord; break;
192 case Cogs::hash("tangent"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::Tangent; break;
193 case Cogs::hash("instancevector"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::InstanceVector; break;
194 case Cogs::hash("instancematrix"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::InstanceMatrix; break;
195 case Cogs::hash("sv_position"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_Position; break;
196 case Cogs::hash("sv_vertexid"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID; break;
197 case Cogs::hash("sv_instanceid"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_InstanceID; break;
198 case Cogs::hash("sv_clipdistance"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_ClipDistance; break;
199 case Cogs::hash("sv_vface"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_VFace; break;
200 case Cogs::hash("sv_isfrontface"): value.semantic.name = ShaderInterfaceMemberDefinition::SemanticName::SV_IsFrontFace; break;
201 default:
202 LOG_ERROR(logger, "Unrecognized semantic '%.*s'", StringViewFormat(semanticName));
203 return false;
204 }
205 }
206 else {
207 LOG_ERROR(logger, "Semantic is not a string");
208 return false;
209 }
210 }
211
212 if (arr.Size() >= 3) {
213 auto& modifiers = arr[2];
214 if (modifiers.IsString()) {
215 if (!parseInterpolationModifier(modifiers, value)) return false;
216 }
217 else if (modifiers.IsArray()) {
218 for (auto& modifier : modifiers.GetArray()) {
219 if (!parseInterpolationModifier(modifier, value)) return false;
220 }
221 }
222 }
223 }
224 }
225 else if (obj.IsString()) {
226 if (!parseType(toKey(obj), value, allowFormat)) return false;
227 }
228
229 value.name = name.to_string();
230 value.inheritanceLevel = inheritanceLevel;
231
232 interfaceDefinition.members.push_back(value);
233 }
234
235 return true;
236 }
237
238 [[nodiscard]] bool parseOutputType(Cogs::StringView valueString, Cogs::Core::MaterialDataType& type)
239 {
240 using namespace Cogs::Core;
241 const size_t spaceIndex = valueString.find(" ");
242 const Cogs::StringView typeString = (spaceIndex != Cogs::StringView::NoPosition) ? valueString.substr(spaceIndex) : valueString;
243
244 switch (typeString.hash()) {
245 case Cogs::hash("float"): type = MaterialDataType::Float; break;
246 case Cogs::hash("float2"): type = MaterialDataType::Float2; break;
247 case Cogs::hash("float3"): type = MaterialDataType::Float3; break;
248 case Cogs::hash("float4"): type = MaterialDataType::Float4; break;
249 case Cogs::hash("uint"): type = MaterialDataType::UInt; break;
250 case Cogs::hash("uint2"): type = MaterialDataType::UInt2; break;
251 case Cogs::hash("uint3"): type = MaterialDataType::UInt3; break;
252 case Cogs::hash("uint4"): type = MaterialDataType::UInt4; break;
253 case Cogs::hash("int"): type = MaterialDataType::Int; break;
254 case Cogs::hash("int2"): type = MaterialDataType::Int2; break;
255 case Cogs::hash("int3"): type = MaterialDataType::Int3; break;
256 case Cogs::hash("int4"): type = MaterialDataType::Int4; break;
257 default:
258 LOG_ERROR(logger, "Failed to parse effect ouput datatype '%.*s'", StringViewFormat(valueString));
259 return false;
260 }
261 return true;
262 }
263
264 [[nodiscard]] bool parseEffectOutput(const Value& jsonObject, Cogs::Core::EffectOutputDefinition& outputDefinition)
265 {
266 using namespace Cogs::Core;
267 if (!jsonObject.IsObject()) {
268 LOG_ERROR(logger, "Effect output is not an JSON object");
269 return false;
270 }
271
272 for (auto& m : jsonObject.GetObject()) {
273 Cogs::StringView name = Cogs::Core::toView(m.name);
274 auto& obj = m.value;
275 EffectOutputMemberDefinition outputMember;
276 outputMember.name = name;
277 outputMember.target = static_cast<uint8_t>(outputDefinition.members.size());
278 outputMember.dataType = MaterialDataType::Float4;
280 if (!obj.IsObject()) {
281 LOG_ERROR(logger, "Effect output parameter description %s is not a json object.", outputMember.name.c_str());
282 return false;
283 }
284 for (auto& p : obj.GetObject()) {
285 Cogs::StringView parameterName = Cogs::Core::toView(p.name);
286 auto& parameter = p.value;
287 switch (Cogs::hashLowercase(parameterName)) {
288 case Cogs::hash("datatype"): {
289 // Check that it is a string
290 if (!parameter.IsString()) {
291 LOG_ERROR(logger, "Datatype of effect output parameter %s for %s is not a string.", parameterName.to_string().c_str(), outputMember.name.c_str());
292 return false;
293 }
294 std::string s = parameter.GetString();
295 if (!parseOutputType(s, outputMember.dataType)) return false;
296 break;
297 }
298 case Cogs::hash("target"): {
299 if (!parameter.IsInt()) {
300 LOG_ERROR(logger, "Target of effect output parameter %s for %s is not a number.", parameterName.to_string().c_str(), outputMember.name.c_str());
301 return false;
302 }
303
304 outputMember.target = static_cast<uint8_t>(parameter.GetInt());
305 break;
306 }
307 default:
308 LOG_ERROR(logger, "Unknown effect output parameter for %s.", parameterName.to_string().c_str());
309 return false;
310 }
311 }
312 outputDefinition.members.push_back(outputMember);
313 }
314 return true;
315 }
316}
317
318Cogs::Core::EnginePermutations::EnginePermutations(Context * context) :
319 permutationDefinitions(MemBlockType::RenderResourceStorage), mContext(context)
320{
321 enginePermutations.reserve(kMaxEnginePermutations);
322}
323
324Cogs::Core::EnginePermutations::~EnginePermutations()
325{
326 EnginePermutationDefinition* def = firstDefinition;
327 while (def) {
328 EnginePermutationDefinition* n = def->next;
329 permutationDefinitions.destroy(def);
330 def = n;
331 }
332}
333
334
335void Cogs::Core::EnginePermutations::initialize(Context * context)
336{
337 load("Default.permutations");
338
339 //FIXME: When order-dependence for permutation masking is fixed, this section may be removed.
340 assert(get(0) == get("Forward") && "Invalid default permutation order.");
341 assert(get(1) == get("Deferred") && "Invalid default permutation order.");
342 assert(get(2) == get("Shadow") && "Invalid default permutation order.");
343 assert(get(3) == get("Transparent") && "Invalid default permutation order.");
344
345 for (auto & e : enginePermutations) {
346 auto & definitions = e.getDefinition()->definitions;
347
348 definitions.push_back({ "COGS_MAX_LIGHTS", std::to_string(context->renderer->getMaxLights()) });
349
350 if (context->variables->get("renderer.samples", 1) > 1) {
351 definitions.push_back({ "COGS_MSAA", "1" });
352 }
353 }
354}
355
356Cogs::Core::EnginePermutationDefinition* Cogs::Core::EnginePermutations::createDefinition()
357{
358 EnginePermutationDefinition* rv = permutationDefinitions.create();
359 rv->next = firstDefinition;
360 firstDefinition = rv;
361 return rv;
362}
363
364
365Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::create(const StringView & name, EnginePermutation * base)
366{
367 auto definition = createDefinition();
368 definition->name = name.to_string();
369
370 return create(definition, base);
371}
372
373Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::create(EnginePermutationDefinition * definition, EnginePermutation * base)
374{
375 assert(enginePermutations.size() < kMaxEnginePermutations && "Permutation limit reached.");
376
377 auto name = StringView(definition->name);
378 auto nameHash = name.hash();
379
380 for (auto & e : enginePermutations) {
381 if (e.getNameHash() == nameHash) {
382 LOG_WARNING(logger, "Overwriting engine permutation %.*s.", StringViewFormat(name));
383 return &e;
384 }
385 }
386
387 enginePermutations.emplace_back();
388 auto enginePermutation = &enginePermutations.back();
389
390 enginePermutation->index = enginePermutations.size() - 1;
391 enginePermutation->permutationMask = 1u << enginePermutation->index;
392 enginePermutation->definition = definition;
393
394 if (base) {
395 assert(definition->inherits.empty());
396 bool ret = inherit(enginePermutation, base);
397 if (!ret) LOG_WARNING(logger, "EnginePermutation %s inherit %s failed", definition->name.c_str(), base->getName().c_str());
398 } else if (definition->inherits.size()) {
399 EnginePermutation* inherits = get(definition->inherits);
400 bool ret = inherit(enginePermutation, inherits);
401 if (!ret) LOG_WARNING(logger, "EnginePermutation %s inherit %s failed", definition->name.c_str(), inherits->getName().c_str());
402 }
403
404 enginePermutation->initialize(mContext, definition);
405
406 return enginePermutation;
407}
408
409bool Cogs::Core::EnginePermutations::inherit(EnginePermutation * enginePermutation, const EnginePermutation * base)
410{
411 enginePermutation->permutationMask = base->permutationMask;
412
413 auto inheritIfNotSet = [](std::string & a, const std::string & b) {
414 if (a.empty()) a = b;
415 };
416
417 inheritIfNotSet(enginePermutation->definition->vertexShader, base->definition->vertexShader);
418 inheritIfNotSet(enginePermutation->definition->geometryShader, base->definition->geometryShader);
419 inheritIfNotSet(enginePermutation->definition->pixelShader, base->definition->pixelShader);
420 if (enginePermutation->definition->outputs.members.empty()) {
421 enginePermutation->definition->outputs = base->definition->outputs;
422 }
423
424 enginePermutation->definition->flags |= base->definition->flags;
425 enginePermutation->definition->requiredFlags |= base->definition->requiredFlags;
426
427 for (auto & d : base->definition->definitions) {
428 enginePermutation->definition->definitions.emplace_back(d);
429 }
430
431 return inheritMaterialVariants(enginePermutation->definition->variants, base->definition->variants,
432 enginePermutation->definition->name, base->definition->name,
433 true);
434}
435
436const Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::get(size_t index) const
437{
438 if (index == static_cast<size_t>(-1) || index >= enginePermutations.size()) return &enginePermutations[0];
439
440 return &enginePermutations[index];
441}
442
443const Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::get(const StringView & name) const
444{
445 return get(getIndex(name));
446}
447
448Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::get(size_t index)
449{
450 if (index == static_cast<size_t>(-1) || index >= enginePermutations.size()) return &enginePermutations[0];
451
452 return &enginePermutations[index];
453}
454
455Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::get(const StringView & name)
456{
457 return get(getIndex(name));
458}
459
460Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::getInternal(size_t index)
461{
462 return &enginePermutations[index];
463}
464
465Cogs::Core::EnginePermutation * Cogs::Core::EnginePermutations::getInternal(const StringView & name)
466{
467 return getInternal(getIndex(name));
468}
469
470bool Cogs::Core::EnginePermutations::exists(const StringView & name) const
471{
472 const size_t nameHash = name.hash();
473
474 for (auto & ep : enginePermutations) {
475 if (nameHash == ep.nameHash) {
476 return true;
477 }
478 }
479
480 return false;
481}
482
483size_t Cogs::Core::EnginePermutations::getIndex(const StringView & name) const
484{
485 const size_t nameHash = name.hash();
486
487 for (auto & ep : enginePermutations) {
488 if (nameHash == ep.nameHash) {
489 return ep.index;
490 }
491 }
492
493 LOG_ERROR(logger, "Cannot retrieve invalid permutation index for %.*s.", StringViewFormat(name));
494
495 return (size_t)-1;
496}
497
498bool Cogs::Core::EnginePermutations::load(const StringView & resource, bool isContent)
499{
500 const auto document = isContent ? parseJson(resource, JsonParseFlags::None) : parseJson(mContext, resource);
501
502 if (!document.IsObject()) {
503 if (!isContent) {
504 LOG_ERROR(logger, "Could not read engine permutations from %.*s.", StringViewFormat(resource));
505 }
506
507 return false;
508 }
509
510 for (auto & m : document.GetObject()) {
511 auto section = toKey(m.name);
512
513 if (section == "permutations") {
514 for (auto & p : m.value.GetObject()) {
515 auto pKey = toKey(p.name);
516
517 auto definition = createDefinition();
518 definition->name = toString(p.name);
519
520 for (auto & v : p.value.GetObject()) {
521 auto vKey = toKey(v.name);
522
523 if (vKey == "vertexShader") {
524 definition->vertexShader = v.value.GetString();
525 } else if (vKey == "geometryShader") {
526 definition->geometryShader = v.value.GetString();
527 } else if (vKey == "pixelShader") {
528 definition->pixelShader = v.value.GetString();
529 } else if (vKey == "definitions") {
530 if (!v.value.IsObject()) {
531 LOG_ERROR(logger, "Cannot read definitions from %.*s.", StringViewFormat(pKey));
532 continue;
533 }
534
535 for (auto & d : v.value.GetObject()) {
536 definition->definitions.push_back({ toString(d.name), toString(d.value) });
537 }
538 } else if (vKey == "requiredFlags") {
539 if (v.value.IsArray()) {
540 auto arr = v.value.GetArray();
541
542 for (auto & element : arr) {
543 if (!element.IsString()) {
544 LOG_ERROR(logger, "Invalid flag type.");
545 continue;
546 }
547
548 auto key = toKey(element);
549 definition->requiredFlags |= parseEnum(key, RenderFlags::None);
550 }
551 } else if (v.value.IsString()) {
552 definition->requiredFlags |= parseEnumFlags(toKey(v.value), RenderFlags::None);
553 } else {
554 LOG_ERROR(logger, "Invalid type for requiredFlags.");
555 continue;
556 }
557 } else if (vKey == "inherits") {
558 definition->inherits = toString(v.value);
559 } else if (vKey == "flags") {
560 if (v.value.IsString()) {
561 definition->flags |= getFlag(toKey(v.value));
562 } else {
563 auto arr = v.value.GetArray();
564
565 for (const auto & element : arr) {
566 definition->flags |= getFlag(toKey(element));
567 }
568 }
569 } else if (vKey == "variants") {
570 if (!readMaterialVariants(mContext, v.value, definition->variants)) return false;
571 } else if (vKey == "properties") {
572 if (!readMaterialProperties(mContext, v.value, definition->properties, false)) return false;
573 }
574 else if (vKey == "vertexInterface") {
575 if (!parseShaderInterface(v.value, definition->vertexInterface, 0, false)) return false;
576 }
577 else if (vKey == "surfaceInterface") {
578 if (!parseShaderInterface(v.value, definition->surfaceInterface, 0, false)) return false;
579 } else if (vKey == "output") {
580 if (!parseEffectOutput(v.value, definition->outputs)) return false;
581 }
582
583
584 }
585
586 create(definition);
587 }
588 }
589 }
590
591 return true;
592}
593
594Cogs::Core::EnginePermutationFlags Cogs::Core::EnginePermutations::getFlag(const StringView & name)
595{
596 if (name == "DepthOnly")
597 return EnginePermutationFlags::DepthOnly;
598 else if (name == "NoShading")
599 return EnginePermutationFlags::NoShading;
600 else if (name == "ReverseDepth")
601 return EnginePermutationFlags::ReverseDepth;
602 else if (name == "HasBuiltins")
603 return EnginePermutationFlags::HasBuiltins;
604 else if (name != "None") {
605 LOG_WARNING(logger, "EnginePermutationFlags mismatch %.*s", StringViewFormat(name));
606 }
607 return EnginePermutationFlags::None;
608}
609
610void Cogs::Core::EnginePermutation::initialize(Context * context, EnginePermutationDefinition * definition_in)
611{
612 definition = definition_in;
613
614 nameHash = hash(definition->name);
615
616 selectors.resize(definition->variants.size());
617
618 for (const auto & v : definition->variants) {
619 selectors[v.index].index = v.index;
620 selectors[v.index].value = v.defaultValue;
621 }
622
623 applyConstantBuffers(context, definition->properties, constantBuffers);
624
625 if ((definition->requiredFlags & RenderFlags::CastShadows) != 0) {
626 requiredFlags |= RenderItemFlags::CastShadows;
627 }
628 updateFlags();
629
630 dirty = true;
631}
632
633namespace
634{
635 void applySelectorValue(bool & dirty, size_t & selector, size_t value)
636 {
637 if (value != selector) {
638 selector = value;
639 dirty = true;
640 }
641 }
642}
643
644void Cogs::Core::EnginePermutation::updateFlags()
645{
646 flags = definition->flags;
647 for (auto & v : definition->variants) {
648 if (selectors[v.index].value != 0) {
649 flags = flags | (EnginePermutationFlags)v.flags;
650 }
651 }
652}
653void Cogs::Core::EnginePermutation::setVariant(const StringView & key, const StringView & value)
654{
655 assert(selectors.size() == definition->variants.size() && "Variant selectors invalid.");
656
657 for (auto & v : definition->variants) {
658 if (key == v.name) {
659 if (v.type == ShaderVariantType::Bool) {
660 auto result = parseBool(value, v.defaultValue != 0);
661
662 applySelectorValue(dirty, selectors[v.index].value, result ? 1 : 0);
663 } else if (v.type == ShaderVariantType::Int) {
664 auto result = parseInt(value, v.defaultValue);
665
666 applySelectorValue(dirty, selectors[v.index].value, result);
667 } else if (v.type == ShaderVariantType::Enum) {
668 for (auto & e : v.values) {
669 if (value == e.key) {
670 applySelectorValue(dirty, selectors[v.index].value, e.index);
671 }
672 }
673 } else {
674 LOG_ERROR(logger, "Variant type %d not recognized.", static_cast<int>(v.type));
675 }
676
677 break;
678 }
679 }
680 updateFlags();
681}
682
683void Cogs::Core::EnginePermutation::setVariant(const StringView & key, bool value)
684{
685 for (auto & v : definition->variants) {
686 if (key == v.name) {
687 if (v.type != ShaderVariantType::Bool) {
688 LOG_WARNING(logger, "Variant type mismatch for %.*s (%d not Bool(%d)).", StringViewFormat(key), static_cast<int>(v.type), static_cast<int>(ShaderVariantType::Bool));
689 }
690 applySelectorValue(dirty, selectors[v.index].value, static_cast<size_t>(value));
691 updateFlags();
692 return;
693 }
694 }
695
696 LOG_WARNING(logger, "No matching boolean variant found for key %.*s.", StringViewFormat(key));
697}
698
699void Cogs::Core::EnginePermutation::setVariant(const StringView & key, int value)
700{
701 for (auto & v : definition->variants) {
702 if (key == v.name) {
703 if (v.type != ShaderVariantType::Int && v.type != ShaderVariantType::Enum) {
704 LOG_WARNING(logger, "Variant type mismatch for %.*s (%d not Int/Enum).", StringViewFormat(key), static_cast<int>(v.type));
705 }
706 applySelectorValue(dirty, selectors[v.index].value, static_cast<size_t>(value));
707 updateFlags();
708 return;
709 }
710 }
711
712 LOG_WARNING(logger, "No matching integer variant found for key %.*s.", StringViewFormat(key));
713}
714
715bool Cogs::Core::EnginePermutation::hasVariant(const StringView & key)
716{
717 for (auto & v : definition->variants)
718 if (key == v.name) return true;
719 return false;
720}
721
722void Cogs::Core::EnginePermutation::setProperty(const StringView & name, const void * value, int valueSize)
723{
724 auto key = constantBuffers.getPropertyKey(name);
725
726 if (key == NoProperty) {
727 LOG_WARNING(logger, "Could not find property %.*s in engine permutation %s.", StringViewFormat(name), definition->name.c_str());
728 return;
729 }
730
731 auto & variable = constantBuffers.variables[key];
732
733 if (variable.flags != MaterialPropertyFlags::None) {
734 enforcePropertyFlags(variable, variable.flags, &value);
735 }
736
737 constantBuffers.buffers[variable.buffer].setValue(variable.descriptor.offset, valueSize, static_cast<const uint8_t *>(value));
738 updateFlags();
739}
740
741size_t Cogs::Core::EnginePermutation::getCode() const
742{
743 if (dirty) {
744 code = hash(index, nameHash);
745
746 for (auto & d : definition->definitions) {
747 code = hash(d.second, code);
748 }
749 for (auto & s : selectors) {
750 code = hash(s.value, code);
751 }
752 dirty = false;
753 }
754 return code;
755}
756
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
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
virtual unsigned getMaxLights() const =0
Get the maximum number of lights.
Log implementation class.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
constexpr size_t size() const noexcept
Get the size of the string.
Definition: StringView.h:178
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
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:200
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ Normal
Regular shaded rendering.
MaterialDataType
Defines available data types for material properties.
Definition: MaterialTypes.h:20
@ CastShadows
Casts shadows.
@ None
No render flags set.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
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.