Cogs.Core
ShaderBuilderWebGPU.cpp
1#include "ShaderBuilder.h"
2#include "ShaderBuilderHelpersWebGPU.h"
3#include "Context.h"
4#include "Utilities/Strings.h"
5#include "Utilities/Preprocessor.h"
6#include "ResourceStore.h"
7#include "Renderer/EnginePermutations.h"
8#include "Rendering/IGraphicsDevice.h"
9
10#include "Foundation/Logging/Logger.h"
11
12#include <array>
13#include <sstream>
14#include <map>
15
16namespace
17{
18
20 constexpr size_t InitialBufferCapasity = 4096u;
21
22 constexpr std::array semanticNames
23 {
24 "a_POSITION",
25 "a_NORMAL",
26 "a_COLOR",
27 "a_TEXCOORD",
28 "a_TANGENT",
29 "a_INSTANCEVECTOR",
30 "a_INSTANCEMATRIX",
31 };
32
33 [[nodiscard]]
34 std::string_view getSemanticName(size_t semantic_ind) {
35 std::string_view name;
36 if (semantic_ind < semanticNames.size()) {
37 name = semanticNames[semantic_ind];
38 }
39 return name;
40 }
41
42 using namespace Cogs::Core;
46 using Cogs::BindingResourceType;
47 using Cogs::ResourceDimensions;
48 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("ShaderBuilderWebGPU");
49
50 enum ShaderInterface { None = 0, VertexIn = 1, VertexOut = 2, FragmentIn = 4, FragmentOut = 8 };
51
52
53 struct BuiltinAttributeDescription {
54 std::string builtinName;
55 MaterialDataType type = MaterialDataType::Unknown;
56 ShaderInterface stage = ShaderInterface::None;
57 };
58
59 struct BuiltinAttributes {
60 ShaderInterfaceMemberDefinition::SemanticName semanticName;
61 BuiltinAttributeDescription desc;
62 };
63
64 const std::array<BuiltinAttributes, 5> builtinAttributes{
65 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_InstanceID, {"instance_index", MaterialDataType::UInt, ShaderInterface::VertexIn}},
66 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID, {"vertex_index", MaterialDataType::UInt, ShaderInterface::VertexIn}},
67 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::Position, {"position", MaterialDataType::Float4, ShaderInterface(ShaderInterface::VertexOut | ShaderInterface::FragmentIn)}},
68 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_IsFrontFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
69 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_VFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
70 };
71
72 struct BuiltinDataType {
74 BuiltinAttributeDescription desc;
75 };
76
77 const std::array builtinDataTypes {
78 BuiltinDataType{MaterialDataType::SV_InstanceID, {"instance_index", MaterialDataType::UInt, ShaderInterface::VertexIn} },
79 BuiltinDataType{MaterialDataType::Position, {"position", MaterialDataType::Float4, ShaderInterface(ShaderInterface::VertexOut | ShaderInterface::FragmentIn)}},
80 BuiltinDataType{MaterialDataType::SV_IsFrontFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn} },
81 BuiltinDataType{MaterialDataType::VFACE, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
82 };
83
84
85 [[nodiscard]]
86 Cogs::BindGroupDescription& getOrCreateGroup(Cogs::BindGroupSetDescription& layoutSet, const uint16_t groupIndex)
87 {
88 if (layoutSet.numGroups <= groupIndex) {
89 layoutSet.numGroups = groupIndex + 1;
90 }
91 return layoutSet.groups[groupIndex];
92 }
93
94 [[nodiscard]]
95 BindGroupEntryDescription* findEntry(BindGroupDescription& group, size_t nameHash)
96 {
97 for (uint16_t i = 0; i < group.numEntries; ++i) {
98 if (group.entries[i].nameHash == nameHash) {
99 return &group.entries[i];
100 }
101 }
102 return nullptr;
103 }
104
105 [[nodiscard]]
106 BindGroupEntryDescription* findEntry(BindGroupDescription& group, std::string_view name)
107 {
108 return findEntry(group, Cogs::hash(name));
109 }
110
111 [[nodiscard]]
112 size_t nextBindingIndex(const BindGroupDescription& group)
113 {
114 size_t nextBinding = 0;
115 for (uint16_t i = 0; i < group.numEntries; ++i) {
116 nextBinding = std::max(nextBinding, size_t(group.entries[i].binding) + 1);
117 }
118 return nextBinding;
119 }
120
122 [[nodiscard]]
123 BindGroupEntryDescription* claimEntry(BindGroupDescription& group, std::string_view name)
124 {
125 if (group.numEntries >= Cogs::MaxBindGroupEntries) {
126 LOG_ERROR(logger, "Bind group is full (%d entries), dropping binding %s", int(Cogs::MaxBindGroupEntries), std::string(name).c_str());
127 return nullptr;
128 }
129 const uint32_t binding = static_cast<uint32_t>(nextBindingIndex(group));
130 BindGroupEntryDescription& entry = group.entries[group.numEntries++];
132 entry.binding = binding;
133 entry.nameHash = Cogs::hash(name);
134 return &entry;
135 }
136
137
138 ResourceDimensions bindingLayoutTextureDimension(const Cogs::Core::TextureDimensions dimensions)
139 {
140 switch (dimensions) {
141 case Cogs::Core::TextureDimensions::Texture2D: return ResourceDimensions::Texture2D;
142 case Cogs::Core::TextureDimensions::Texture2DArray: return ResourceDimensions::Texture2DArray;
143 case Cogs::Core::TextureDimensions::Texture3D: return ResourceDimensions::Texture3D;
144 case Cogs::Core::TextureDimensions::TexureCube: return ResourceDimensions::TextureCube;
145 default: return ResourceDimensions::Unknown;
146 }
147 }
148
149
150
151 [[nodiscard]]
152 std::string convertDefinesToConstExpressions(const std::string& s) {
153 std::map<std::string, std::string> addedDefines;
154 std::string result;
155 result.reserve(s.size());
156
157 std::istringstream iss(s);
158
159 std::string identifier;
160 std::string replacement;
161 for (std::string line; std::getline(iss, line); )
162 {
163 identifier.clear();
164 replacement.clear();
165
166 std::string_view sv(line);
167 auto pos = sv.find_first_not_of(" \t");
168 if (pos == std::string_view::npos || sv.substr(pos, 7) != "#define") {
169 result += line + "\n";
170 continue;
171 }
172 sv = sv.substr(pos + 7);
173
174 pos = sv.find_first_not_of(" \t");
175 if (pos == std::string_view::npos) {
176 result += line + "\n";
177 continue;
178 }
179 sv = sv.substr(pos);
180
181 auto end = sv.find_first_of(" \t");
182 if (end == std::string_view::npos) {
183 identifier = sv;
184 replacement = "1";
185 } else {
186 identifier = sv.substr(0, end);
187 sv = sv.substr(end);
188 pos = sv.find_first_not_of(" \t");
189 if (pos == std::string_view::npos) {
190 replacement = "1";
191 } else {
192 auto last = sv.find_last_not_of(" \t");
193 replacement = sv.substr(pos, last - pos + 1);
194 }
195 }
196
197 auto it = addedDefines.find(identifier);
198 if (it != addedDefines.end()) {
199 if (replacement != (*it).second) {
200 LOG_WARNING(logger, "Shader generation with inconsistent preprosessor define %s set to %s conflicts with previous define %s.", identifier.c_str(), replacement.c_str(), (*it).second.c_str());
201 }
202 continue;
203 }
204 result += "const " + identifier + " = " + replacement + ";\n";
205 addedDefines.try_emplace(identifier, replacement);
206 }
207 return result;
208 }
209
210 void addInclude(std::string& content, std::string_view prefix, std::string_view path)
211 {
212 content.append("#include \"");
213 content.append(prefix);
214 content.append(path);
215 content.append("\"\n");
216 }
217
218 void changeSuffix(std::string& dst, std::string_view from, std::string_view to)
219 {
220 if (dst.ends_with(from)) {
221 dst.replace(dst.size() - from.length(), from.length(), to);
222 }
223 }
224
225 [[nodiscard]]
226 std::string attributeVaryingType(const MaterialDataType type)
227 {
228 switch (type) {
229 case MaterialDataType::Float: return "f32 "; break;
230 case MaterialDataType::Float2: return "vec2f "; break;
231 case MaterialDataType::Float3: return "vec3f "; break;
232 case MaterialDataType::Float4: return "vec4f "; break;
233 case MaterialDataType::Float4x4: return "mat4x4f "; break;
234 case MaterialDataType::Int: return "i32 "; break;
235 case MaterialDataType::Int2: return "vec4i "; break;
236 case MaterialDataType::Int3: return "vec3i "; break;
237 case MaterialDataType::Int4: return "vec4i "; break;
238 case MaterialDataType::UInt: return "u32 "; break;
239 case MaterialDataType::UInt2: return "vec4u "; break;
240 case MaterialDataType::UInt3: return "vec3u "; break;
241 case MaterialDataType::UInt4: return "vec4u "; break;
242 case MaterialDataType::Bool: return "bool "; break;
243 case MaterialDataType::SV_IsFrontFace: return "bool "; break;
244 case MaterialDataType::VFACE: return "f32 "; break;
245 case MaterialDataType::Position: return "vec4f "; break;
246 default:
247 LOG_ERROR(logger, "Unsupported attribute type %d", int(type));
248 return "<illegal>";
249 break;
250 }
251 }
252
253 void addEffectDefinesAndStuff(std::string& output, const std::vector<std::pair<std::string, std::string>>& definitions, uint32_t multiViewCount) {
254 if (multiViewCount) {
255 std::string multiViewCountString = std::to_string(multiViewCount);
256 output.append("#extension GL_OVR_multiview : require\nlayout(num_views=");
257 output.append(multiViewCountString);
258 output.append(") in;\n");
259 output.append("#define COGS_MULTIVIEW ");
260 output.append(multiViewCountString);
261 output.append("\n");
262 }
263
264 std::unordered_set<std::string> addedDefines;
265
266 for (const std::pair<std::string, std::string>& define : definitions) {
267 if (addedDefines.contains(define.first)) {
268 continue;
269 }
270 output.append("#define ");
271 output.append(define.first);
272 output.append(" ");
273 output.append(define.second);
274 output.append("\n");
275 addedDefines.insert(define.first);
276 }
277 }
278
279 [[nodiscard]]
280 BuiltinAttributeDescription getBuiltinAttributeDescription(const ShaderInterfaceMemberDefinition& attribute) {
281 BuiltinAttributeDescription desc;
282 desc.stage = ShaderInterface::None;
283 for (const auto& builtin : builtinDataTypes)
284 {
285 if (builtin.type == attribute.type) {
286 desc = builtin.desc;
287 }
288 }
289 for (const auto& attr : builtinAttributes) {
290 if (attr.semanticName == attribute.semantic.name) {
291 desc = attr.desc;
292 }
293 }
294 return desc;
295 }
296
297 void addInterfaceStruct(std::string& defines, std::string& source, std::string_view name, const ShaderInterfaceDefinition& iface, ShaderInterface shaderInterface)
298 {
299 size_t next_loc = 0;
300 source.append("struct ");
301 source.append(name);
302 source.append(" {\n");
303
304 if (shaderInterface & ShaderInterface::VertexOut) {
305 source.append(" @builtin(position) Position: vec4f,\n");
306 }
307 for (const auto& attribute : iface.members) {
308 BuiltinAttributeDescription builtinAttributeDescription = getBuiltinAttributeDescription(attribute);
309 if (shaderInterface & ShaderInterface::VertexOut &&
310 (builtinAttributeDescription.stage & ShaderInterface::FragmentIn) != 0) {
311 continue;
312 }
313 if (shaderInterface & ShaderInterface::FragmentIn && attribute.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::None && attribute.type == MaterialDataType::Position) {
314 continue; // Position is often described both through semantic name and attribute type
315 }
316
317 if ((builtinAttributeDescription.stage & shaderInterface) != 0) {
318 source.append(" @builtin(" + builtinAttributeDescription.builtinName + ") ");
319 source.append(attribute.name);
320 source.append(" : ");
321 source.append(attributeVaryingType(builtinAttributeDescription.type));
322 source.append(",\n");
323 }
324 else {
325 if (shaderInterface & ShaderInterface::VertexIn) {
326 size_t nameInd = size_t (attribute.semantic.name) - 1;
327 std::string_view semanticName = getSemanticName(nameInd);
328 if (!semanticName.empty()) {
329 if(attribute.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::InstanceMatrix){
330 std::string defineName;
331 defineName.append(semanticName);
332 int loc = attribute.semantic.slot;
333 defineName.append(std::to_string(loc) + "_LOC");
334 defines.append("#define ");
335 defines.append(defineName + " " + std::to_string(next_loc) + "\n");
336 next_loc += 4;
337 for(int i=0; i<4; i++){
338 source.append(" @location(" + defineName + " + " + std::to_string(i) + ") ");
339 source.append(attribute.name + "_" + std::to_string(i));
340 source.append(" : vec4f,\n");
341 }
342 }
343 else
344 {
345 std::string defineName;
346 defineName.append(semanticName);
347 defineName.append(std::to_string(attribute.semantic.slot) + "_LOC");
348 defines.append("#define ");
349 defines.append(defineName + " " + std::to_string(next_loc++) + "\n");
350 source.append(" @location(" + defineName + ") ");
351 source.append(attribute.name);
352 source.append(" : ");
353 source.append(attributeVaryingType(attribute.type));
354 source.append(",\n");
355 }
356 }
357 }
358 else {
359 if (attribute.modifiers & ShaderInterfaceMemberDefinition::NointerpolationModifier) {
360 source.append(" @interpolate(flat)");
361 }
362 source.append(" @location(" + std::to_string(next_loc++) + ") ");
363 source.append(attribute.name);
364 source.append(" : ");
365 source.append(attributeVaryingType(attribute.type));
366 source.append(",\n");
367 }
368 }
369 }
370 source.append("};\n\n");
371 }
372
373 void addInterfaceConstructor(std::string& shaderSource, std::string_view name, const ShaderInterfaceDefinition& iface)
374 {
375 shaderSource.append("fn create");
376 shaderSource.append(name);
377 shaderSource.append("() -> ");
378 shaderSource.append(name);
379 shaderSource.append(" {\n");
380 shaderSource.append(" var t : ");
381 shaderSource.append(name);
382 shaderSource.append(";\n");
383 for (const auto& attribute : iface.members) {
384 const char* initializer = nullptr;
385 switch (attribute.type) {
386 case MaterialDataType::Float: initializer = "0.0;\n"; break;
387 case MaterialDataType::Float2: initializer = "vec2f(0);\n"; break;
388 case MaterialDataType::Float3: initializer = "vec3f(0);\n"; break;
389 case MaterialDataType::Float4: initializer = "vec4f(0);\n"; break;
390 case MaterialDataType::Float4x4: initializer = "mat4x4f(0);\n"; break;
391 default:
392 break;
393 }
394 if (initializer) {
395 shaderSource.append(" t.");
396 shaderSource.append(attribute.name);
397 shaderSource.append(" = ");
398 shaderSource.append(initializer);
399 }
400 }
401 shaderSource.append(" return t;\n}\n\n");
402 }
403
404 void addUniform(std::string& shaderSource,
405 BindGroupSetDescription& layoutSet,
406 int group,
407 uint32_t visibility,
408 std::string_view name,
409 std::string_view type,
410 bool useTemplate = false,
411 BindingResourceType resourceType = BindingResourceType::UniformBuffer,
412 ResourceDimensions textureDimension = ResourceDimensions::Unknown,
413 bool isDepthTexture = false) {
414 BindGroupDescription& bindings = getOrCreateGroup(layoutSet, static_cast<uint16_t>(group));
415 BindGroupEntryDescription* entry = findEntry(bindings, name);
416 if (!entry) {
417 entry = claimEntry(bindings, name);
418 if (!entry) return;
419 entry->resourceType = resourceType;
420 entry->textureDimension = textureDimension;
421 entry->isDepthTexture = isDepthTexture;
422 }
423 // The same uniform may be emitted for several stages; accumulate the stages that reference it.
424 entry->visibility |= visibility;
425
426 shaderSource.append("@group(");
427 shaderSource.append(std::to_string(group));
428 shaderSource.append(") @binding(");
429 shaderSource.append(std::to_string(entry->binding));
430 shaderSource.append(") var");
431 if (useTemplate) {
432 shaderSource.append("<uniform>");
433 }
434 shaderSource.append(" ");
435 shaderSource.append(name);
436 shaderSource.append(" : ");
437 shaderSource.append(type);
438 shaderSource.append(";\n");
439 }
440
441 void addUniformBuffer(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, const ConstantBufferDefinition& definition, BindGroupSetDescription& layoutSet, int group, uint32_t visibility)
442 {
443 if (!identifiersSeen.contains(Strings::add(definition.name))) {
444 return;
445 }
446 if (definition.values.empty()) return;
447 shaderSource.append("struct ");
448 shaderSource.append(definition.name);
449 shaderSource.append("_t {\n");
450 for (const ConstantBufferVariableDefinition& member : definition.values) {
451 shaderSource.append(member.name);
452 shaderSource.append(" : ");
453
454 switch (member.type) {
455 case MaterialDataType::Float: shaderSource.append("f32"); break;
456 case MaterialDataType::Float2: shaderSource.append("vec2f"); break;
457 case MaterialDataType::Float3: shaderSource.append("vec3f"); break;
458 case MaterialDataType::Float4Array:
459 shaderSource.append("array<vec4f, " + std::to_string(member.dimension) + ">");
460 assert(member.dimension != static_cast<size_t>(-1));
461 break;
462 case MaterialDataType::Float4: shaderSource.append("vec4f"); break;
463 case MaterialDataType::Float4x4Array:
464 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f"); break;
465 case MaterialDataType::Int: shaderSource.append("i32"); break;
466 case MaterialDataType::Int2: shaderSource.append("vec2i"); break;
467 case MaterialDataType::Int3: shaderSource.append("vec3i"); break;
468 case MaterialDataType::Int4: shaderSource.append("vec4i"); break;
469 case MaterialDataType::UInt: shaderSource.append("u32"); break;
470 case MaterialDataType::UInt2: shaderSource.append("vec2u"); break;
471 case MaterialDataType::UInt3: shaderSource.append("vec3u"); break;
472 case MaterialDataType::UInt4: shaderSource.append("vec4u"); break;
473 case MaterialDataType::Bool: shaderSource.append("u32"); break;
474 default:
475 LOG_ERROR(logger, "Invalid type for buffer varable %s in buffer %s.", member.name.c_str(), definition.name.c_str());
476 shaderSource.append("<invalid type>");
477 break;
478 }
479 shaderSource.append(",\n");
480 }
481 shaderSource.append("};\n");
482
483 addUniform(shaderSource, layoutSet, group, visibility, definition.name, definition.name + "_t", true);
484 }
485
486 void addTransferFunc(std::string& shaderSource, const ShaderDefinition& sourceDefinition, const ShaderDefinition& destinationDefinition)
487 {
488 shaderSource.append("fn transferAttributes(vertexIn : VertexIn, vertexOut : ptr<function, VertexOut>) {\n");
489 for (const ShaderInterfaceMemberDefinition& member : sourceDefinition.shaderInterface.members) {
490 if (member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID) continue;
491 for (const ShaderInterfaceMemberDefinition& dMember : destinationDefinition.shaderInterface.members) {
492 if (dMember.name == member.name) {
493 shaderSource.append(" (*vertexOut).");
494 shaderSource.append(member.name);
495 shaderSource.append(" = ");
496 if (dMember.type == MaterialDataType::Float3 && member.type == MaterialDataType::Float2 && member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::Normal) {
497 shaderSource.append("octDecode(vertexIn.");
498 shaderSource.append(member.name);
499 shaderSource.append("); \n");
500 break;
501 }
502 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float3) {
503 shaderSource.append("vec4f(vertexIn.");
504 shaderSource.append(member.name);
505 shaderSource.append(", 1.0);\n");
506 break;
507 }
508 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float2) {
509 shaderSource.append("vec4f(vertexIn.");
510 shaderSource.append(member.name);
511 shaderSource.append(", 0.0, 1.0);\n");
512 break;
513 }
514 bool close = false;
515 if (dMember.type != member.type) {
516 switch (dMember.type) {
517 case MaterialDataType::Float: shaderSource.append("f32("); close = true; break;
518 case MaterialDataType::Float2: shaderSource.append("vec2f("); close = true; break;
519 case MaterialDataType::Float3: shaderSource.append("vec3f("); close = true; break;
520 case MaterialDataType::Float4: shaderSource.append("vec4f("); close = true; break;
521 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f("); close = true; break;
522 case MaterialDataType::Int: shaderSource.append("i32("); close = true; break;
523 case MaterialDataType::Int2: shaderSource.append("vec2i("); close = true; break;
524 case MaterialDataType::Int3: shaderSource.append("vec3i("); close = true; break;
525 case MaterialDataType::Int4: shaderSource.append("vec4i("); close = true; break;
526 case MaterialDataType::UInt: shaderSource.append("u32("); close = true; break;
527 case MaterialDataType::UInt2: shaderSource.append("vec2u("); close = true; break;
528 case MaterialDataType::UInt3: shaderSource.append("vec3u("); close = true; break;
529 case MaterialDataType::UInt4: shaderSource.append("vec4u("); close = true; break;
530 default:
531 break;
532 }
533 }
534 if (dMember.type == member.type) {
535 shaderSource.append("vertexIn.");
536 shaderSource.append(member.name);
537 }
538 else{
539 LOG_WARNING(logger, "%s Skip transfer of %s. (Different types).", sourceDefinition.loadPath.c_str(), member.name.c_str());
540 }
541 if (close) shaderSource.append(1, ')');
542 shaderSource.append(";\n");
543 break;
544 }
545 }
546 }
547 shaderSource.append("}\n");
548 }
549
550 bool addOutputStruct(std::string& source, const std::vector<EffectOutputMemberDefinition> outputDefinition, const std::vector<std::pair<std::string, std::string>>& definitions, bool depthOnly) {
551 bool outputDepth = false;
552 bool hasColorAttachments = !outputDefinition.empty() && !depthOnly;
553
554 std::string targetString = "COGS_CUSTOM_DEPTH_WRITE";
555 auto it = std::find_if(definitions.begin(), definitions.end(),
556 [&targetString](const std::pair<std::string, std::string>& p){
557 return p.first == targetString;
558 });
559
560 if (it != definitions.end()) {
561 outputDepth = it->second != "0";
562 }
563
564 if (!outputDepth && !hasColorAttachments) {
565 return false;
566 }
567
568 source.append("struct FragmentOut {\n");
569
570 if (hasColorAttachments) {
571 for (auto member : outputDefinition) {
572 source.append(" @location(");
573 source.append(std::to_string(member.target));
574 source.append(") ");
575 source.append(member.name + " : ");
576
577 switch (member.dataType) {
578 case MaterialDataType::Float: source.append("f32"); break;
579 case MaterialDataType::Float2: source.append("vec2f"); break;
580 case MaterialDataType::Float3: source.append("vec3f"); break;
581 case MaterialDataType::Float4: source.append("vec4f"); break;
582 case MaterialDataType::Float4x4: source.append("mat4x4f"); break;
583 case MaterialDataType::Int: source.append("i32"); break;
584 case MaterialDataType::Int2: source.append("vec2i"); break;
585 case MaterialDataType::Int3: source.append("vec3i"); break;
586 case MaterialDataType::Int4: source.append("vec4i"); break;
587 case MaterialDataType::UInt: source.append("u32"); break;
588 case MaterialDataType::UInt2: source.append("vec2u"); break;
589 case MaterialDataType::UInt3: source.append("vec3u"); break;
590 case MaterialDataType::UInt4: source.append("vec4u"); break;
591 default:
592 break;
593 }
594 source.append(",\n");
595 }
596 }
597 if (outputDepth) {
598 source.append("@builtin(frag_depth) depth : f32\n");
599 }
600 source.append("};\n");
601 return true;
602 }
603
604
605 void addCallFunction(std::string& source, std::string_view name, std::string_view functionName, std::string_view inType, std::string_view outType) {
606 source.append("fn ");
607 source.append(name);
608 source.append("(In : ");
609 source.append(inType);
610 source.append(") ->");
611 source.append(outType);
612 source.append(" { \n");
613 source.append(" return ");
614 source.append(functionName);
615 source.append("(In);\n");
616 source.append("}\n\n");
617 }
618
619 [[nodiscard]]
620 std::string textureDataType(const MaterialDataType type)
621 {
622 switch (type) {
623 case MaterialDataType::Unknown: // Defaults to f32
624 case MaterialDataType::Float:;
625 case MaterialDataType::Float2:
626 case MaterialDataType::Float3:
627 case MaterialDataType::Float4:
628 return "f32"; break;
629 case MaterialDataType::Int:
630 case MaterialDataType::Int2:
631 case MaterialDataType::Int3:
632 case MaterialDataType::Int4:
633 return "i32"; break;
634 case MaterialDataType::UInt:
635 case MaterialDataType::UInt2:
636 case MaterialDataType::UInt3:
637 case MaterialDataType::UInt4:
638 return "u32"; break;
639 default:
640 LOG_ERROR(logger, "Unsupported texture datatype %d", int(type));
641 return "<illegal>";
642 break;
643 }
644 }
645
646 [[nodiscard]]
647 Cogs::BindingTextureSampleType deduceTextureSampleType(const MaterialDataType type, const bool isDepthTexture, const bool multisampled)
648 {
649 if (isDepthTexture) {
650 return Cogs::BindingTextureSampleType::Depth;
651 }
652
653 switch (type) {
654 case MaterialDataType::Int:
655 case MaterialDataType::Int2:
656 case MaterialDataType::Int3:
657 case MaterialDataType::Int4:
658 return Cogs::BindingTextureSampleType::Sint;
659 case MaterialDataType::UInt:
660 case MaterialDataType::UInt2:
661 case MaterialDataType::UInt3:
662 case MaterialDataType::UInt4:
663 return Cogs::BindingTextureSampleType::Uint;
664 default:
665 return multisampled
666 ? Cogs::BindingTextureSampleType::UnfilterableFloat
667 : Cogs::BindingTextureSampleType::Float;
668 }
669 }
670
671 [[nodiscard]]
672 Cogs::BindingSamplerBindingType deduceSamplerBindingType(const MaterialDataType type, const bool isDepthTexture, const bool multisampled)
673 {
674 if (isDepthTexture) {
675 return Cogs::BindingSamplerBindingType::Comparison;
676 }
677
678 switch (type) {
679 case MaterialDataType::Int:
680 case MaterialDataType::Int2:
681 case MaterialDataType::Int3:
682 case MaterialDataType::Int4:
683 case MaterialDataType::UInt:
684 case MaterialDataType::UInt2:
685 case MaterialDataType::UInt3:
686 case MaterialDataType::UInt4:
687 return Cogs::BindingSamplerBindingType::NonFiltering;
688 default:
689 return multisampled
690 ? Cogs::BindingSamplerBindingType::NonFiltering
691 : Cogs::BindingSamplerBindingType::Filtering;
692 }
693 }
694
695
696 void addTextures(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, BindGroupSetDescription& layoutSet, int group, uint32_t visibility, const std::vector<MaterialTextureDefinition>& definition, bool ignoreIdentifiersSeen = false)
697 {
698 for (auto& texture : definition) {
699 if (!identifiersSeen.contains(Strings::add(texture.name)) && !ignoreIdentifiersSeen) {
700 continue;
701 }
702
703 std::string samplerType = "sampler";
704
705 std::string type;
706 type.reserve(50);
707 type += "texture";
708
709 if (texture.isDepth) {
710 type += "_depth";
711 samplerType += "_comparison";
712 }
713
714 switch (texture.dimensions) {
715 case TextureDimensions::Texture2D:
716 type += "_2d";
717 break;
718 case TextureDimensions::TexureCube:
719 type += "_cube";
720 break;
721 case TextureDimensions::Texture2DArray:
722 type += "_2d_array";
723 break;
724 case TextureDimensions::Texture3D:
725 type += "_3d";
726 break;
727 default:
728 LOG_ERROR(logger, "Unsupported texture dimension %d", int(texture.dimensions));
729 break;
730 }
731
732 if (!texture.isDepth) {
733 std::string dataType = textureDataType(texture.format);
734 type += "<" + dataType + ">";
735 }
736
737 ResourceDimensions textureDimension = bindingLayoutTextureDimension(texture.dimensions);
738 const bool multisampled = false; // MaterialTextureDefinition does not expose sample count.
739 addUniform(shaderSource, layoutSet, group, visibility, texture.name, type, false, BindingResourceType::Texture, textureDimension, texture.isDepth);
740 {
741 BindGroupDescription& bindGroup = getOrCreateGroup(layoutSet, static_cast<uint16_t>(group));
742 BindGroupEntryDescription* textureEntry = findEntry(bindGroup, texture.name);
743 if (textureEntry) {
744 textureEntry->textureSampleType = deduceTextureSampleType(texture.format, texture.isDepth, multisampled);
745 textureEntry->multisampled = multisampled;
746 }
747 }
748 std::string samplerName = texture.name + "Sampler";
749 if (identifiersSeen.contains(Strings::add(samplerName)) && !ignoreIdentifiersSeen) {
750 addUniform(shaderSource, layoutSet, group, visibility, samplerName, samplerType, false, BindingResourceType::Sampler, textureDimension, texture.isDepth);
751 BindGroupDescription& bindGroup = getOrCreateGroup(layoutSet, static_cast<uint16_t>(group));
752 BindGroupEntryDescription* samplerEntry = findEntry(bindGroup, samplerName);
753 if (samplerEntry) {
754 samplerEntry->samplerBindingType = deduceSamplerBindingType(texture.format, texture.isDepth, multisampled);
755 }
756 }
757 }
758 }
759
760 void concatUniqueMembers(const ShaderInterfaceDefinition& a, const ShaderInterfaceDefinition& b, ShaderInterfaceDefinition& out) {
761 out.members.reserve(a.members.size() + b.members.size());
762 out = a;
763 for (auto member : b.members) {
764 bool found = false;
765 for (auto existingMember : a.members) {
766 if (member.name == existingMember.name) {
767 found = true;
768 break;
769 }
770 }
771 if (!found) {
772 out.members.push_back(member);
773 }
774 }
775 }
776} // anonymous namespace
777
778bool Cogs::Core::buildEffectWebGPU(Context* context,
779 std::vector<std::pair<std::string, std::string>>& definitions,
780 MaterialDefinition& materialDefinition,
781 const EnginePermutation& permutation,
782 const uint32_t multiViewCount)
783{
784 if (!materialDefinition.effect.geometryShader.entryPoint.empty()) {
785 LOG_ERROR(logger, "%s: Geometry shader not allowed in WebGPU.", materialDefinition.name.c_str());
786 return false;
787 }
788 if (!materialDefinition.effect.hullShader.entryPoint.empty()) {
789 LOG_ERROR(logger, "%s: Hull shader not allowed in WebGPU.", materialDefinition.name.c_str());
790 return false;
791 }
792 if (!materialDefinition.effect.domainShader.entryPoint.empty()) {
793 LOG_ERROR(logger, "%s: Domain shader not allowed in WebGPU.", materialDefinition.name.c_str());
794 return false;
795 }
796 if (!materialDefinition.effect.computeShader.entryPoint.empty()) {
797 LOG_ERROR(logger, "%s: Compute shader not allowed in WebGPU.", materialDefinition.name.c_str());
798 return false;
799 }
800
801 changeSuffix(materialDefinition.effect.vertexShader.customSourcePath, ".hlsl", ".wgsl");
802 changeSuffix(materialDefinition.effect.pixelShader.customSourcePath, ".hlsl", ".wgsl");
803 std::string prefix = "" + materialDefinition.name;
804
806 bindings = getEngineBindGroupDescription();
807 std::vector<bool> bindGroupUsed(static_cast<size_t>(Cogs::Core::BindGroup::Count), false);
808
809 {
811 std::string vs_header;
812 const std::string shaderName = prefix + "VertexShader" + permutation.getDefinition()->name + ".wgsl";
813 addEffectDefinesAndStuff(vs_header, definitions, multiViewCount);
814 if (!pp.process(context, vs_header)) return false;
815 vs_header.swap(pp.processed);
816 pp.processed.clear();
817
818 std::string vs_body;
819
820 {
821 ShaderInterfaceDefinition vertexInterface;
822 concatUniqueMembers(materialDefinition.effect.vertexShader.shaderInterface, permutation.getDefinition()->vertexInterface, vertexInterface);
823 addInterfaceStruct(vs_header, vs_body, "VertexIn", vertexInterface, ShaderInterface::VertexIn);
824 }
825 {
826 ShaderInterfaceDefinition surfaceInterface;
827 concatUniqueMembers(materialDefinition.effect.pixelShader.shaderInterface, permutation.getDefinition()->surfaceInterface, surfaceInterface);
828 addInterfaceStruct(vs_header, vs_body, "VertexOut", surfaceInterface, ShaderInterface::VertexOut);
829 }
830 addInterfaceConstructor(vs_body, "VertexOut", materialDefinition.effect.pixelShader.shaderInterface);
831 addTransferFunc(vs_body, materialDefinition.effect.vertexShader, materialDefinition.effect.pixelShader);
832 addInclude(vs_body, std::string_view(), materialDefinition.effect.vertexShader.customSourcePath);
833 std::string permutationVS = permutation.getDefinition()->vertexShader;
834 changeSuffix(permutationVS, ".hlsl", ".wgsl");
835
836 addCallFunction(vs_body, "callMaterialVertexFunction",
837 materialDefinition.effect.vertexShader.entryPoint.empty() ? "vertexFunction" : materialDefinition.effect.vertexShader.entryPoint,
838 "VertexIn", "VertexOut");
839 addInclude(vs_body, std::string_view(), "Engine/EngineVS.wgsl");
840 addCallFunction(vs_body, "callVertexFunction",
841 "invokeMaterial",
842 "VertexIn", "VertexOut");
843 addInclude(vs_body, std::string_view(), permutationVS);
844 if (!pp.process(context, vs_body)) return false;
845 vs_body.swap(pp.processed);
846 pp.processed.clear();
847
848 std::string vs_uniforms;
849 addTextures(vs_uniforms, pp.identifiersSeen, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityVertex, materialDefinition.properties.textures, false);
850
851 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
852 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityVertex);
853 }
854 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
855 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityVertex);
856 }
857 addEngineUniformsToSourceWebGPU(vs_uniforms, pp.identifiersSeen, bindGroupUsed);
858
859 // AnimationBuffer is only referenced by skinned permutations (see StandardMaterialVS.wgsl's
860 // COGS_VARIANT_SKINNED_BASIC branch), so only claim a bind group entry for it when the vertex
861 // body actually used it. Adding it unconditionally would force every non-skinned draw to bind
862 // a buffer its shader never declares.
863 if (pp.identifiersSeen.contains(Strings::add("AnimationBuffer"))) {
864 addInclude(vs_uniforms, std::string_view(), "Engine/AnimationBuffer.wgsl");
865 addUniform(vs_uniforms, bindings, static_cast<int>(Cogs::Core::BindGroup::Object), Cogs::BindingVisibilityVertex, "AnimationBuffer", "AnimationBuffer_t", true);
866 bindGroupUsed[static_cast<size_t>(Cogs::Core::BindGroup::Object)] = true;
867 }
868
869 addInclude(vs_uniforms, std::string_view(), "Engine/Common.wgsl");
870
871 if (!pp.process(context, vs_uniforms)) return false;
872 vs_uniforms.swap(pp.processed);
873 pp.processed.clear();
874
875 std::string all = vs_header + vs_uniforms + vs_body;
876 all = convertDefinesToConstExpressions(all);
877
878 std::string vspath = "Shaders/" + shaderName;
879 context->resourceStore->addResource(vspath, all);
880 }
881 {
883 std::string fs_header;
884 const std::string shaderName = prefix + "PixelShader" + permutation.getDefinition()->name + ".wgsl";
885 addEffectDefinesAndStuff(fs_header, definitions, 0 /* multiview handled in VS. */);
886
887 std::string fs_body;
888 bool fs_returnsStruct = false;
889 {
890 ShaderInterfaceDefinition surfaceInterface = materialDefinition.effect.pixelShader.shaderInterface;
891 surfaceInterface.members.insert(surfaceInterface.members.end(), permutation.getDefinition()->surfaceInterface.members.begin(),
892 permutation.getDefinition()->surfaceInterface.members.end());
893 addInterfaceStruct(fs_header, fs_body, "VertexIn", surfaceInterface, ShaderInterface::FragmentIn);
894 fs_returnsStruct = addOutputStruct(fs_header, permutation.getDefinition()->outputs.members, definitions, permutation.isDepthOnly());
895 }
896
897 addInclude(fs_header, std::string_view(), materialDefinition.effect.pixelShader.customSourcePath);
898 std::string permutationFS = permutation.getDefinition()->pixelShader;
899 changeSuffix(permutationFS, ".hlsl", ".wgsl");
900
901 addCallFunction(fs_header, "callMaterialSurfaceFunction",
902 materialDefinition.effect.pixelShader.entryPoint.empty() ? "surfaceFunction" : materialDefinition.effect.pixelShader.entryPoint,
903 "VertexIn", "SurfaceOut");
904 addInclude(fs_header, std::string_view(), "Engine/EnginePS.wgsl");
905 addCallFunction(fs_header, "callSurfaceFunction",
906 "invokeMaterial",
907 "VertexIn", "SurfaceOut");
908 addInclude(fs_header, std::string_view(), permutationFS);
909 if (fs_returnsStruct) {
910 fs_header.append(
911 R"(
912@fragment
913fn main(In : VertexIn) -> FragmentOut {
914 return permutationMain(In);
915}
916)");
917 } else {
918 fs_header.append(
919 R"(
920@fragment
921fn main(In : VertexIn) {
922 permutationMain(In);
923}
924)");
925
926 }
927 if (!pp.process(context, fs_header)) return false;
928 fs_header.swap(pp.processed);
929 pp.processed.clear();
930
931 std::string fs_uniforms;
932 fs_uniforms.reserve(InitialBufferCapasity);
933 addTextures(fs_uniforms, pp.identifiersSeen, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityFragment, materialDefinition.properties.textures, false);
934 addEngineUniformsToSourceWebGPU(fs_uniforms, pp.identifiersSeen, bindGroupUsed);
935 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
936 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityFragment);
937 }
938 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
939 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings, static_cast<int>(Cogs::Core::BindGroup::Default), Cogs::BindingVisibilityFragment);
940 }
941 addInclude(fs_uniforms, std::string_view(), "Engine/Common.wgsl");
942 if (!pp.process(context, fs_uniforms)) return false;
943 fs_uniforms.swap(pp.processed);
944 pp.processed.clear();
945
946 std::string all = fs_header + fs_uniforms + fs_body;
947 all = convertDefinesToConstExpressions(all);
948
949 std::string fspath = "Shaders/" + shaderName;
950 context->resourceStore->addResource(fspath, all);
951 }
952
953 // Export the bind groups that were actually referenced.
954 // The Default group holds this material's own bindings and is never flagged by the engine helper.
955 bindGroupUsed[static_cast<size_t>(Cogs::Core::BindGroup::Default)] = bindings.groups[0].numEntries > 0;
956 materialDefinition.effect.bindGroupLayouts.numGroups = 0;
957 for (size_t g = 0; g < bindGroupUsed.size(); g++) {
958 BindGroupDescription& target = materialDefinition.effect.bindGroupLayouts.groups[g];
959 target.numEntries = 0;
960 if (!bindGroupUsed[g]) continue;
961
962 // Copied verbatim: engine bind groups are shared across effects and must stay identical, so
963 // entries keep their slots even when no stage references them. Dropping them would renumber
964 // the remainder and make otherwise-equal groups compare unequal.
965 target = bindings.groups[g];
966 materialDefinition.effect.bindGroupLayouts.numGroups = static_cast<uint16_t>(g + 1);
967 }
968
969 definitions.clear();
970 return true;
971}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
Definition: Context.h:210
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
TextureDimensions
Texture dimensions.
MaterialDataType
Defines available data types for material properties.
Definition: MaterialTypes.h:20
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
BindGroupSetDescription bindGroupLayouts
Optional backend-neutral bind-group layout metadata, typically emitted by shader builders.
Partial C preprocessor.
Definition: Preprocessor.h:42
bool process(Context *context, const StringView input)
Run a text block through the preprocessor.
std::unordered_set< StringRef > identifiersSeen
Set of identifiers encountered in active text.
Definition: Preprocessor.h:51
std::string processed
Resulting processed text.
Definition: Preprocessor.h:48