Cogs.Core
ShaderBuilderWebGPU.cpp
1#include "ShaderBuilder.h"
2#include "Context.h"
3#include "Utilities/Strings.h"
4#include "Utilities/Preprocessor.h"
5#include "ResourceStore.h"
6#include "Renderer/EnginePermutations.h"
7#include "Rendering/IGraphicsDevice.h"
8
9#include "Foundation/Logging/Logger.h"
10
11#include <array>
12#include <sstream>
13#include <regex>
14#include <map>
15
16namespace
17{
19 constexpr size_t InitialBufferCapasity = 4096u;
20
21 constexpr std::array semanticNames
22 {
23 "a_POSITION",
24 "a_NORMAL",
25 "a_COLOR",
26 "a_TEXCOORD",
27 "a_TANGENT",
28 "a_INSTANCEVECTOR",
29 "a_INSTANCEMATRIX",
30 };
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;
43 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("ShaderBuilderWebGPU");
44
45 enum ShaderInterface { None = 0, VertexIn = 1, VertexOut = 2, FragmentIn = 4, FragmentOut = 8 };
46
47 struct BuiltinAttributeDescription {
48 std::string builtinName;
49 MaterialDataType type = MaterialDataType::Unknown;
50 ShaderInterface stage = ShaderInterface::None;
51 };
52
53 struct BuiltinAttributes {
54 ShaderInterfaceMemberDefinition::SemanticName semanticName;
55 BuiltinAttributeDescription desc;
56 };
57
58 const std::array<BuiltinAttributes, 5> builtinAttributes{
59 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_InstanceID, {"instance_index", MaterialDataType::UInt, ShaderInterface::VertexIn}},
60 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID, {"vertex_index", MaterialDataType::UInt, ShaderInterface::VertexIn}},
61 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::Position, {"position", MaterialDataType::Float4, ShaderInterface(ShaderInterface::VertexOut | ShaderInterface::FragmentIn)}},
62 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_IsFrontFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
63 BuiltinAttributes{ShaderInterfaceMemberDefinition::SemanticName::SV_VFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
64 };
65
66 struct BuiltinDataType {
68 BuiltinAttributeDescription desc;
69 };
70
71 const std::array builtinDataTypes {
72 BuiltinDataType{MaterialDataType::SV_InstanceID, {"instance_index", MaterialDataType::UInt, ShaderInterface::VertexIn} },
73 BuiltinDataType{MaterialDataType::Position, {"position", MaterialDataType::Float4, ShaderInterface(ShaderInterface::VertexOut | ShaderInterface::FragmentIn)}},
74
75 BuiltinDataType{MaterialDataType::Position, {"position", MaterialDataType::Float4, ShaderInterface::FragmentIn} },
76 BuiltinDataType{MaterialDataType::SV_IsFrontFace, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn} },
77 BuiltinDataType{MaterialDataType::VFACE, {"front_facing", MaterialDataType::Bool, ShaderInterface::FragmentIn}},
78 };
79
80 const std::array requiredEngineBuffers = {
81 "ObjectBuffer",
82 "SceneBuffer",
83 "LightBuffer",
84 };
85
86 const std::array optionalEngineBuffers = {
87 "AnimationBuffer",
88 "ShadowBuffer",
89 };
90
91 struct EngineTexture {
92 std::string name;
94 bool isDepthTexture = false;
95 };
96
97 const std::array engineTextures = {
98 EngineTexture{.name = "environmentSky", .dimensions = TextureDimensions::TexureCube},
99 EngineTexture{.name = "environmentRadiance", .dimensions = TextureDimensions::TexureCube},
100 EngineTexture{.name = "environmentIrradiance", .dimensions = TextureDimensions::TexureCube},
101 EngineTexture{.name = "ambientIrradiance", .dimensions = TextureDimensions::TexureCube},
102 EngineTexture{.name = "brdfLUT", .dimensions = TextureDimensions::Texture2D},
103 EngineTexture{.name = "cascadedShadowMap", .dimensions = TextureDimensions::Texture2DArray, .isDepthTexture = true},
104 EngineTexture{.name = "cubeShadowMap", .dimensions = TextureDimensions::TexureCube, .isDepthTexture = true},
105 };
106
107 struct WebGPULocation {
108 size_t location = 0;
109 std::string name;
110 Cogs::Core::TextureDimensions dimensions = Cogs::Core::TextureDimensions::Texture2D;
111 bool isDepthTexture = false;
112 WebGPULocation(size_t location, const std::string &name, Cogs::Core::TextureDimensions dimensions = Cogs::Core::TextureDimensions::Texture2D, bool isDepthTexture = false) :
113 location(location), name(name), dimensions(dimensions), isDepthTexture(isDepthTexture) { }
114 WebGPULocation() = default;
115
116 };
117
118 using WebGPUBindingGroup = std::vector<WebGPULocation>;
119 using WebGPUBindingGroupVector = std::vector<WebGPUBindingGroup>;
120
121 [[nodiscard]]
122 bool hasBinding(const WebGPUBindingGroupVector& bindingGroups, std::string_view name) {
123 for (const WebGPUBindingGroup& bindings : bindingGroups) {
124 for (const WebGPULocation& location : bindings) {
125 if (location.name == name) {
126 return true;
127 }
128 }
129 }
130 return false;
131 }
132
133 void createBufferBinding(WebGPUBindingGroupVector& bindingGroups, const MaterialDefinition& materialDefinition) {
134 if (bindingGroups.empty()) {
135 bindingGroups.emplace_back();
136 }
137 WebGPUBindingGroup& locations = bindingGroups[0];
138 locations.emplace_back(0, "BLOCKED"); // The location will be used as a handle in EffectsWebGPU. We want to avoid 0 as it is considered as HoHandle
139 for (auto& buffer : requiredEngineBuffers) {
140 WebGPULocation loc(locations.size(), buffer);
141 locations.push_back(loc);
142 }
143
144 for (auto& buffer : optionalEngineBuffers) {
145 locations.emplace_back(locations.size(), buffer);
146 }
147
148
149 for (auto& buffer : materialDefinition.properties.buffers) {
150 if (std::find(std::begin(requiredEngineBuffers), std::end(requiredEngineBuffers), buffer.name) == std::end(requiredEngineBuffers)) {
151 if (!hasBinding(bindingGroups, buffer.name)) {
152 locations.emplace_back(locations.size(), buffer.name);
153 }
154 }
155 }
156 }
157
158 [[nodiscard]]
159 std::string convertDefinesToConstExpressions(const std::string& s) {
160 std::map<std::string, std::string> addedDefines;
161 std::string result;
162 result.reserve(s.size());
163
164 std::istringstream iss(s);
165
166 std::regex e1("^\\s*#define\\s+([^\\s]+)\\s+([^\\s]+)"); // matches #define CNAME value
167 std::regex e2("^\\s*#define\\s+([^\\s]+)\\s*$"); // matches #define CNAME
168 std::string identifier;
169 std::string replacement;
170 for (std::string line; std::getline(iss, line); )
171 {
172 identifier.clear();
173 replacement.clear();
174 std::smatch match;
175
176 if (std::regex_search(line, match, e1))
177 {
178 identifier += match.str(1);
179 replacement += match.str(2);
180 }
181 else if (std::regex_search(line, match, e2)) {
182 identifier += match.str(1);
183 replacement += "1";
184 }
185 if (identifier.empty()) {
186 result += line + "\n";
187 continue;
188 }
189 auto it = addedDefines.find(identifier);
190 if (it != addedDefines.end()) {
191 if (replacement != (*it).second) {
192 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());
193 }
194 continue;
195 }
196 result += "const " + identifier + " = " + replacement + ";\n";
197 addedDefines.try_emplace(identifier, replacement);
198 }
199 return result;
200 }
201
202 void addInclude(std::string& content, std::string_view prefix, std::string_view path)
203 {
204 content.append("#include \"");
205 content.append(prefix);
206 content.append(path);
207 content.append("\"\n");
208 }
209
210 void changeSuffix(std::string& dst, std::string_view from, std::string_view to)
211 {
212 if (dst.ends_with(from)) {
213 dst.replace(dst.size() - from.length(), from.length(), to);
214 }
215 }
216
217 [[nodiscard]]
218 std::string attributeVaryingType(const MaterialDataType type)
219 {
220 switch (type) {
221 case MaterialDataType::Float: return "f32 "; break;
222 case MaterialDataType::Float2: return "vec2f "; break;
223 case MaterialDataType::Float3: return "vec3f "; break;
224 case MaterialDataType::Float4: return "vec4f "; break;
225 case MaterialDataType::Float4x4: return "mat4x4f "; break;
226 case MaterialDataType::Int: return "i32 "; break;
227 case MaterialDataType::Int2: return "vec4i "; break;
228 case MaterialDataType::Int3: return "vec3i "; break;
229 case MaterialDataType::Int4: return "vec4i "; break;
230 case MaterialDataType::UInt: return "u32 "; break;
231 case MaterialDataType::UInt2: return "vec4u "; break;
232 case MaterialDataType::UInt3: return "vec3u "; break;
233 case MaterialDataType::UInt4: return "vec4u "; break;
234 case MaterialDataType::Bool: return "bool "; break;
235 case MaterialDataType::SV_IsFrontFace: return "bool "; break;
236 case MaterialDataType::VFACE: return "f32 "; break;
237 case MaterialDataType::Position: return "vec4f "; break;
238 default:
239 LOG_ERROR(logger, "Unsupported attribute type %d", int(type));
240 return "<illegal>";
241 break;
242 }
243 }
244
245 void createTextureBindings(WebGPUBindingGroupVector& bindingGroups, const MaterialDefinition& materialDefinition) {
246 if (bindingGroups.empty()) {
247 bindingGroups.emplace_back();
248 }
249 WebGPUBindingGroup& locations = bindingGroups[0];
250
251 for (auto& texture : materialDefinition.properties.textures) {
252 if (!hasBinding(bindingGroups, texture.name)) {
253 std::string name = texture.name;
254 locations.emplace_back(locations.size(), name, texture.dimensions);
255 locations.emplace_back(locations.size(), name + "Sampler", texture.dimensions);
256 }
257 }
258 }
259
260 void createEngineTextureBindings(WebGPUBindingGroupVector& bindingGroups) {
261 if (bindingGroups.empty()) {
262 bindingGroups.emplace_back();
263 }
264 WebGPUBindingGroup& locations = bindingGroups[0];
265
266 for (auto& tex : engineTextures) {
267 if (!hasBinding(bindingGroups, tex.name)) {
268 std::string name = tex.name;
269 locations.emplace_back(locations.size(), name, tex.dimensions, tex.isDepthTexture);
270 locations.emplace_back(locations.size(), name + "Sampler", tex.dimensions, tex.isDepthTexture);
271 }
272 }
273 }
274
275 void addEffectDefinesAndStuff(std::string& output, const std::vector<std::pair<std::string, std::string>>& definitions, uint32_t multiViewCount) {
276 if (multiViewCount) {
277 std::string multiViewCountString = std::to_string(multiViewCount);
278 output.append("#extension GL_OVR_multiview : require\nlayout(num_views=");
279 output.append(multiViewCountString);
280 output.append(") in;\n");
281 output.append("#define COGS_MULTIVIEW ");
282 output.append(multiViewCountString);
283 output.append("\n");
284 }
285
286 std::unordered_set<std::string> addedDefines;
287
288 for (const std::pair<std::string, std::string>& define : definitions) {
289 if (addedDefines.contains(define.first)) {
290 continue;
291 }
292 output.append("#define ");
293 output.append(define.first);
294 output.append(" ");
295 output.append(define.second);
296 output.append("\n");
297 addedDefines.insert(define.first);
298 }
299 }
300
301 [[nodiscard]]
302 BuiltinAttributeDescription getBuiltinAttributeDescription(const ShaderInterfaceMemberDefinition& attribute) {
303 BuiltinAttributeDescription desc;
304 desc.stage = ShaderInterface::None;
305 for (const auto& builtin : builtinDataTypes)
306 {
307 if (builtin.type == attribute.type) {
308 desc = builtin.desc;
309 }
310 }
311 for (const auto& attr : builtinAttributes) {
312 if (attr.semanticName == attribute.semantic.name) {
313 desc = attr.desc;
314 }
315 }
316 return desc;
317 }
318
319 void addInterfaceStruct(std::string& defines, std::string& source, std::string_view name, const ShaderInterfaceDefinition& iface, ShaderInterface shaderInterface)
320 {
321 size_t next_loc = 0;
322 source.append("struct ");
323 source.append(name);
324 source.append(" {\n");
325
326 if (shaderInterface & ShaderInterface::VertexOut) {
327 source.append(" @builtin(position) Position: vec4f,\n");
328 }
329 for (const auto& attribute : iface.members) {
330 BuiltinAttributeDescription builtinAttributeDescription = getBuiltinAttributeDescription(attribute);
331 if (shaderInterface & ShaderInterface::VertexOut &&
332 (builtinAttributeDescription.stage & ShaderInterface::FragmentIn) != 0) {
333 continue;
334 }
335 if (shaderInterface & ShaderInterface::FragmentIn && attribute.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::None && attribute.type == MaterialDataType::Position) {
336 continue; // Position is often described both through semantic name and attribute type
337 }
338
339 if ((builtinAttributeDescription.stage & shaderInterface) != 0) {
340 source.append(" @builtin(" + builtinAttributeDescription.builtinName + ") ");
341 source.append(attribute.name);
342 source.append(" : ");
343 source.append(attributeVaryingType(builtinAttributeDescription.type));
344 source.append(",\n");
345 }
346 else {
347 if (shaderInterface & ShaderInterface::VertexIn) {
348 defines.append("#define ");
349 size_t nameInd = size_t (attribute.semantic.name) - 1;
350 std::string_view semanticName = getSemanticName(nameInd);
351 if (!semanticName.empty()) {
352 std::string defineName;
353 defineName.append(semanticName);
354 defineName.append(std::to_string(attribute.semantic.slot) + "_LOC");
355 defines.append(defineName + " " + std::to_string(next_loc++) + "\n");
356 source.append(" @location(" + defineName + ") ");
357 }
358 }
359 else {
360 source.append(" @location(" + std::to_string(next_loc++) + ") ");
361 }
362 source.append(attribute.name);
363 source.append(" : ");
364 if (attribute.type == MaterialDataType::UInt4) {
365 source.append("vec4u");
366 }
367 else {
368 source.append(attributeVaryingType(attribute.type));
369 }
370 source.append(",\n");
371 }
372 }
373 source.append("};\n\n");
374 }
375
376 void addInterfaceConstructor(std::string& shaderSource, std::string_view name, const ShaderInterfaceDefinition& iface)
377 {
378 shaderSource.append("fn create");
379 shaderSource.append(name);
380 shaderSource.append("() -> ");
381 shaderSource.append(name);
382 shaderSource.append(" {\n");
383 shaderSource.append(" var t : ");
384 shaderSource.append(name);
385 shaderSource.append(";\n");
386 for (const auto& attribute : iface.members) {
387 const char* initializer = nullptr;
388 switch (attribute.type) {
389 case MaterialDataType::Float: initializer = "0.0;\n"; break;
390 case MaterialDataType::Float2: initializer = "vec2f(0);\n"; break;
391 case MaterialDataType::Float3: initializer = "vec3f(0);\n"; break;
392 case MaterialDataType::Float4: initializer = "vec4f(0);\n"; break;
393 case MaterialDataType::Float4x4: initializer = "mat4x4f(0);\n"; break;
394 default:
395 break;
396 }
397 if (initializer) {
398 shaderSource.append(" t.");
399 shaderSource.append(attribute.name);
400 shaderSource.append(" = ");
401 shaderSource.append(initializer);
402 }
403 }
404 shaderSource.append(" return t;\n}\n\n");
405 }
406
407 void addUniform(std::string& shaderSource, WebGPUBindingGroupVector& bindingGroups, std::string_view name, std::string_view type, bool useTemplate = false) {
408 WebGPUBindingGroup& bindings = bindingGroups[0];
409 WebGPULocation location;
410 for (const WebGPULocation& curr_location : bindings) {
411 if (curr_location.name == name) {
412 location = curr_location;
413 break;
414 }
415 }
416 if (location.name != name) {
417 LOG_WARNING(logger, "adding to bindgroup %s", std::string(name).c_str());
418 location = WebGPULocation(bindings.size(), std::string(name));
419 bindings.push_back(location);
420 }
421 shaderSource.append("@group(");
422 shaderSource.append(std::to_string(0));
423 shaderSource.append(") @binding(");
424 shaderSource.append(std::to_string(location.location));
425 shaderSource.append(") var");
426 if (useTemplate) {
427 shaderSource.append("<uniform>");
428 }
429 shaderSource.append(" ");
430 shaderSource.append(name);
431 shaderSource.append(" : ");
432 shaderSource.append(type);
433 shaderSource.append(";\n");
434 }
435
436 void addUniformBuffer(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, const ConstantBufferDefinition& definition, WebGPUBindingGroupVector& bindingGroups)
437 {
438 if (!identifiersSeen.contains(Strings::add(definition.name))) {
439 return;
440 }
441 if (definition.values.empty()) return;
442 shaderSource.append("struct ");
443 shaderSource.append(definition.name);
444 shaderSource.append("_t {\n");
445 for (const ConstantBufferVariableDefinition& member : definition.values) {
446 shaderSource.append(member.name);
447 shaderSource.append(" : ");
448
449 switch (member.type) {
450 case MaterialDataType::Float: shaderSource.append("f32"); break;
451 case MaterialDataType::Float2: shaderSource.append("vec2f"); break;
452 case MaterialDataType::Float3: shaderSource.append("vec3f"); break;
453 case MaterialDataType::Float4Array:
454 shaderSource.append("array<vec4f, " + std::to_string(member.dimension) + ">");
455 assert(member.dimension != static_cast<size_t>(-1));
456 break;
457 case MaterialDataType::Float4: shaderSource.append("vec4f"); break;
458 case MaterialDataType::Float4x4Array:
459 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f"); break;
460 case MaterialDataType::Int: shaderSource.append("i32"); break;
461 case MaterialDataType::Int2: shaderSource.append("vec2i"); break;
462 case MaterialDataType::Int3: shaderSource.append("vec3i"); break;
463 case MaterialDataType::Int4: shaderSource.append("vec4i"); break;
464 case MaterialDataType::UInt: shaderSource.append("u32"); break;
465 case MaterialDataType::UInt2: shaderSource.append("vec2u"); break;
466 case MaterialDataType::UInt3: shaderSource.append("vec3u"); break;
467 case MaterialDataType::UInt4: shaderSource.append("vec4u"); break;
468 case MaterialDataType::Bool: shaderSource.append("u32"); break;
469 default:
470 LOG_ERROR(logger, "Invalid type for buffer varable %s in buffer %s.", member.name.c_str(), definition.name.c_str());
471 shaderSource.append("<invalid type>");
472 break;
473 }
474 shaderSource.append(",\n");
475 }
476 shaderSource.append("};\n");
477
478 addUniform(shaderSource, bindingGroups, definition.name, definition.name + "_t", true);
479 }
480
481 void addEngineBuffers(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups) {
482 for (const std::string& name : requiredEngineBuffers) {
483 addUniform(shaderSource, bindingGroups, name, name + "_t", true);
484 if (name == "SceneBuffer") {
485 shaderSource.append("#define COGS_VIEWGETTERS_REFERENCED 1\n");
486 }
487 }
488 for (const std::string& name : optionalEngineBuffers) {
489
490 if (identifiersSeen.contains(Strings::add(name))) {
491 addUniform(shaderSource, bindingGroups, name, name + "_t", true);
492 }
493 }
494 }
495
496 void addTransferFunc(std::string& shaderSource, const ShaderDefinition& sourceDefinition, const ShaderDefinition& destinationDefinition)
497 {
498 shaderSource.append("fn transferAttributes(vertexIn : VertexIn, vertexOut : ptr<function, VertexOut>) {\n");
499 for (const ShaderInterfaceMemberDefinition& member : sourceDefinition.shaderInterface.members) {
500 if (member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID) continue;
501 for (const ShaderInterfaceMemberDefinition& dMember : destinationDefinition.shaderInterface.members) {
502 if (dMember.name == member.name) {
503 shaderSource.append(" (*vertexOut).");
504 shaderSource.append(member.name);
505 shaderSource.append(" = ");
506 if (dMember.type == MaterialDataType::Float3 && member.type == MaterialDataType::Float2 && member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::Normal) {
507 shaderSource.append("octDecode(vertexIn.");
508 shaderSource.append(member.name);
509 shaderSource.append("); \n");
510 break;
511 }
512 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float3) {
513 shaderSource.append("vec4f(vertexIn.");
514 shaderSource.append(member.name);
515 shaderSource.append(", 1.0);\n");
516 break;
517 }
518 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float2) {
519 shaderSource.append("vec4f(vertexIn.");
520 shaderSource.append(member.name);
521 shaderSource.append(", 0.0, 1.0);\n");
522 break;
523 }
524 bool close = false;
525 if (dMember.type != member.type) {
526 switch (dMember.type) {
527 case MaterialDataType::Float: shaderSource.append("f32("); close = true; break;
528 case MaterialDataType::Float2: shaderSource.append("vec2f("); close = true; break;
529 case MaterialDataType::Float3: shaderSource.append("vec3f("); close = true; break;
530 case MaterialDataType::Float4: shaderSource.append("vec4f("); close = true; break;
531 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f("); close = true; break;
532 case MaterialDataType::Int: shaderSource.append("i32("); close = true; break;
533 case MaterialDataType::Int2: shaderSource.append("vec2i("); close = true; break;
534 case MaterialDataType::Int3: shaderSource.append("vec3i("); close = true; break;
535 case MaterialDataType::Int4: shaderSource.append("vec4i("); close = true; break;
536 case MaterialDataType::UInt: shaderSource.append("u32("); close = true; break;
537 case MaterialDataType::UInt2: shaderSource.append("vec2u("); close = true; break;
538 case MaterialDataType::UInt3: shaderSource.append("vec3u("); close = true; break;
539 case MaterialDataType::UInt4: shaderSource.append("vec4u("); close = true; break;
540 default:
541 break;
542 }
543 }
544 shaderSource.append("vertexIn.");
545 shaderSource.append(member.name);
546 if (close) shaderSource.append(1, ')');
547 shaderSource.append(";\n");
548 break;
549 }
550 }
551 }
552 shaderSource.append("}\n");
553 }
554
555 bool addOutputStruct(std::string& source, const std::vector<EffectOutputMemberDefinition> outputDefinition, const std::vector<std::pair<std::string, std::string>>& definitions, bool depthOnly) {
556 bool outputDepth = false;
557 bool hasColorAttachments = !outputDefinition.empty() && !depthOnly;
558
559 std::string targetString = "COGS_CUSTOM_DEPTH_WRITE";
560 auto it = std::find_if(definitions.begin(), definitions.end(),
561 [&targetString](const std::pair<std::string, std::string>& p){
562 return p.first == targetString;
563 });
564
565 if (it != definitions.end()) {
566 outputDepth = it->second != "0";
567 }
568
569 if (!outputDepth && !hasColorAttachments) {
570 return false;
571 }
572
573 source.append("struct FragmentOut {\n");
574
575 if (hasColorAttachments) {
576 for (auto member : outputDefinition) {
577 source.append(" @location(");
578 source.append(std::to_string(member.target));
579 source.append(") ");
580 source.append(member.name + " : ");
581
582 switch (member.dataType) {
583 case MaterialDataType::Float: source.append("f32"); break;
584 case MaterialDataType::Float2: source.append("vec2f"); break;
585 case MaterialDataType::Float3: source.append("vec3f"); break;
586 case MaterialDataType::Float4: source.append("vec4f"); break;
587 case MaterialDataType::Float4x4: source.append("mat4x4f"); break;
588 case MaterialDataType::Int: source.append("i32"); break;
589 case MaterialDataType::Int2: source.append("vec2i"); break;
590 case MaterialDataType::Int3: source.append("vec3i"); break;
591 case MaterialDataType::Int4: source.append("vec4i"); break;
592 case MaterialDataType::UInt: source.append("u32"); break;
593 case MaterialDataType::UInt2: source.append("vec2u"); break;
594 case MaterialDataType::UInt3: source.append("vec3u"); break;
595 case MaterialDataType::UInt4: source.append("vec4u"); break;
596 default:
597 break;
598 }
599 source.append(",\n");
600 }
601 }
602 if (outputDepth) {
603 source.append("@builtin(frag_depth) depth : f32\n");
604 }
605 source.append("};\n");
606 return true;
607 }
608
609
610 void addCallFunction(std::string& source, std::string_view name, std::string_view functionName, std::string_view inType, std::string_view outType) {
611 source.append("fn ");
612 source.append(name);
613 source.append("(In : ");
614 source.append(inType);
615 source.append(") ->");
616 source.append(outType);
617 source.append(" { \n");
618 source.append(" return ");
619 source.append(functionName);
620 source.append("(In);\n");
621 source.append("}\n\n");
622 }
623
624 [[nodiscard]]
625 std::string textureDataType(const MaterialDataType type)
626 {
627 switch (type) {
628 case MaterialDataType::Unknown: // Defaults to f32
629 case MaterialDataType::Float:;
630 case MaterialDataType::Float2:
631 case MaterialDataType::Float3:
632 case MaterialDataType::Float4:
633 return "f32"; break;
634 case MaterialDataType::Int:
635 case MaterialDataType::Int2:
636 case MaterialDataType::Int3:
637 case MaterialDataType::Int4:
638 return "i32"; break;
639 case MaterialDataType::UInt:
640 case MaterialDataType::UInt2:
641 case MaterialDataType::UInt3:
642 case MaterialDataType::UInt4:
643 return "u32"; break;
644 default:
645 LOG_ERROR(logger, "Unsupported texture datatype %d", int(type));
646 return "<illegal>";
647 break;
648 }
649 }
650
651
652 void addTextures(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups, const std::vector<MaterialTextureDefinition>& definition, bool ignoreIdentifiersSeen = false)
653 {
654 for (auto& texture : definition) {
655 if (!identifiersSeen.contains(Strings::add(texture.name)) && !ignoreIdentifiersSeen) {
656 continue;
657 }
658 std::string type;
659 std::string dataType = textureDataType(texture.format);
660 type = "texture";
661 switch (texture.dimensions) {
662 case TextureDimensions::Texture2D:
663 type += "_2d<" + dataType + ">";
664 break;
665 case TextureDimensions::TexureCube:
666 type += "_cube<" + dataType + ">";
667 break;
668 case TextureDimensions::Texture2DArray:
669 type += "_2d_array<" + dataType + ">";
670 break;
671 case TextureDimensions::Texture3D:
672 type += "_3d<" + dataType + ">";
673 break;
674 default:
675 LOG_ERROR(logger, "Unsupported texture type %d", int(texture.dimensions));
676 break;
677 }
678 addUniform(shaderSource, bindingGroups, texture.name, type);
679 std::string samplerName = texture.name + "Sampler";
680 if (identifiersSeen.contains(Strings::add(samplerName)) && !ignoreIdentifiersSeen) {
681 addUniform(shaderSource, bindingGroups, samplerName, "sampler");;
682 }
683 }
684 }
685
686 void addEngineTextures(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups)
687 {
688 for (auto& texture : engineTextures) {
689 if (!identifiersSeen.contains(Strings::add(texture.name))) {
690 continue;
691 }
692 std::string type;
693 type.reserve(50);
694 std::string samplerType = "sampler";
695 type += "texture";
696
697 if (texture.isDepthTexture) {
698 type += "_depth";
699 samplerType += "_comparison";
700 }
701
702 switch (texture.dimensions) {
703 case TextureDimensions::Texture2D:
704 type += "_2d";
705 break;
706 case TextureDimensions::TexureCube:
707 type += "_cube";
708 break;
709 case TextureDimensions::Texture2DArray:
710 type += "_2d_array";
711 break;
712 case TextureDimensions::Texture3D:
713 type += "_3d";
714 break;
715 default:
716 LOG_ERROR(logger, "Unsupported texture type %d", int(texture.dimensions));
717 break;
718 }
719 if (!texture.isDepthTexture) {
720 type += "<f32>";
721 }
722 addUniform(shaderSource, bindingGroups, texture.name, type);
723 addUniform(shaderSource, bindingGroups, texture.name + "Sampler", samplerType);
724 }
725 }
726
727 void concatUniqueMembers(const ShaderInterfaceDefinition& a, const ShaderInterfaceDefinition& b, ShaderInterfaceDefinition& out) {
728 out.members.reserve(a.members.size() + b.members.size());
729 out = a;
730 for (auto member : b.members) {
731 bool found = false;
732 for (auto existingMember : a.members) {
733 if (member.name == existingMember.name) {
734 found = true;
735 break;
736 }
737 }
738 if (!found) {
739 out.members.push_back(member);
740 }
741 }
742 }
743} // anonymous namespace
744
745bool Cogs::Core::buildEffectWebGPU(Context* context,
746 std::vector<std::pair<std::string, std::string>>& definitions,
747 MaterialDefinition& materialDefinition,
748 const EnginePermutation& permutation,
749 const uint32_t multiViewCount)
750{
751 if (!materialDefinition.effect.geometryShader.entryPoint.empty()) {
752 LOG_ERROR(logger, "%s: Geometry shader not allowed in WebGPU.", materialDefinition.name.c_str());
753 return false;
754 }
755 if (!materialDefinition.effect.hullShader.entryPoint.empty()) {
756 LOG_ERROR(logger, "%s: Hull shader not allowed in WebGPU.", materialDefinition.name.c_str());
757 return false;
758 }
759 if (!materialDefinition.effect.domainShader.entryPoint.empty()) {
760 LOG_ERROR(logger, "%s: Domain shader not allowed in WebGPU.", materialDefinition.name.c_str());
761 return false;
762 }
763 if (!materialDefinition.effect.computeShader.entryPoint.empty()) {
764 LOG_ERROR(logger, "%s: Compute shader not allowed in WebGPU.", materialDefinition.name.c_str());
765 return false;
766 }
767
768 changeSuffix(materialDefinition.effect.vertexShader.customSourcePath, ".hlsl", ".wgsl");
769 changeSuffix(materialDefinition.effect.pixelShader.customSourcePath, ".hlsl", ".wgsl");
770 std::string prefix = "" + materialDefinition.name;
771
772 WebGPUBindingGroupVector bindings;
773 createBufferBinding(bindings, materialDefinition);
774 createTextureBindings(bindings, materialDefinition);
775 createEngineTextureBindings(bindings);
776
777 {
779 std::string vs_header;
780 const std::string shaderName = prefix + "VertexShader" + permutation.getDefinition()->name + ".wgsl";
781 addEffectDefinesAndStuff(vs_header, definitions, multiViewCount);
782 if (!pp.process(context, vs_header)) return false;
783 vs_header.swap(pp.processed);
784 pp.processed.clear();
785
786 std::string vs_body;
787
788 {
789 ShaderInterfaceDefinition vertexInterface;
790 concatUniqueMembers(materialDefinition.effect.vertexShader.shaderInterface, permutation.getDefinition()->vertexInterface, vertexInterface);
791 addInterfaceStruct(vs_header, vs_body, "VertexIn", vertexInterface, ShaderInterface::VertexIn);
792 }
793 {
794 ShaderInterfaceDefinition surfaceInterface;
795 concatUniqueMembers(materialDefinition.effect.pixelShader.shaderInterface, permutation.getDefinition()->surfaceInterface, surfaceInterface);
796 addInterfaceStruct(vs_header, vs_body, "VertexOut", surfaceInterface, ShaderInterface::VertexOut);
797 }
798 addInterfaceConstructor(vs_body, "VertexOut", materialDefinition.effect.pixelShader.shaderInterface);
799 addTransferFunc(vs_body, materialDefinition.effect.vertexShader, materialDefinition.effect.pixelShader);
800 addInclude(vs_body, std::string_view(), materialDefinition.effect.vertexShader.customSourcePath);
801 std::string permutationVS = permutation.getDefinition()->vertexShader;
802 changeSuffix(permutationVS, ".hlsl", ".wgsl");
803
804 addCallFunction(vs_body, "callMaterialVertexFunction",
805 materialDefinition.effect.vertexShader.entryPoint.empty() ? "vertexFunction" : materialDefinition.effect.vertexShader.entryPoint,
806 "VertexIn", "VertexOut");
807 addInclude(vs_body, std::string_view(), "Engine/EngineVS.wgsl");
808 addCallFunction(vs_body, "callVertexFunction",
809 "invokeMaterial",
810 "VertexIn", "VertexOut");
811 addInclude(vs_body, std::string_view(), permutationVS);
812 if (!pp.process(context, vs_body)) return false;
813 vs_body.swap(pp.processed);
814 pp.processed.clear();
815
816 std::string vs_uniforms;
817 addTextures(vs_uniforms, pp.identifiersSeen, bindings, materialDefinition.properties.textures, false);
818
819 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
820 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings);
821 }
822 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
823 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings);
824 }
825 addEngineBuffers(vs_uniforms, pp.identifiersSeen, bindings);
826 addInclude(vs_uniforms, std::string_view(), "Engine/Common.wgsl");
827
828 if (!pp.process(context, vs_uniforms)) return false;
829 vs_uniforms.swap(pp.processed);
830 pp.processed.clear();
831
832 std::string all = vs_header + vs_uniforms + vs_body;
833 all = convertDefinesToConstExpressions(all);
834
835 std::string vspath = "Shaders/" + shaderName;
836 context->resourceStore->addResource(vspath, all);
837 }
838 {
840 std::string fs_header;
841 const std::string shaderName = prefix + "PixelShader" + permutation.getDefinition()->name + ".wgsl";
842 addEffectDefinesAndStuff(fs_header, definitions, 0 /* multiview handled in VS. */);
843
844 std::string fs_body;
845 bool fs_returnsStruct = false;
846 {
847 ShaderInterfaceDefinition surfaceInterface = materialDefinition.effect.pixelShader.shaderInterface;
848 surfaceInterface.members.insert(surfaceInterface.members.end(), permutation.getDefinition()->surfaceInterface.members.begin(),
849 permutation.getDefinition()->surfaceInterface.members.end());
850 addInterfaceStruct(fs_header, fs_body, "VertexIn", surfaceInterface, ShaderInterface::FragmentIn);
851 fs_returnsStruct = addOutputStruct(fs_header, permutation.getDefinition()->outputs.members, definitions, permutation.isDepthOnly());
852 }
853
854 addInclude(fs_header, std::string_view(), materialDefinition.effect.pixelShader.customSourcePath);
855 std::string permutationFS = permutation.getDefinition()->pixelShader;
856 changeSuffix(permutationFS, ".hlsl", ".wgsl");
857
858 addCallFunction(fs_header, "callMaterialSurfaceFunction",
859 materialDefinition.effect.pixelShader.entryPoint.empty() ? "surfaceFunction" : materialDefinition.effect.pixelShader.entryPoint,
860 "VertexIn", "SurfaceOut");
861 addInclude(fs_header, std::string_view(), "Engine/EnginePS.wgsl");
862 addCallFunction(fs_header, "callSurfaceFunction",
863 "invokeMaterial",
864 "VertexIn", "SurfaceOut");
865 addInclude(fs_header, std::string_view(), permutationFS);
866 if (fs_returnsStruct) {
867 fs_header.append(
868 R"(
869@fragment
870fn main(In : VertexIn) -> FragmentOut {
871 return permutationMain(In);
872}
873)");
874 } else {
875 fs_header.append(
876 R"(
877@fragment
878fn main(In : VertexIn) {
879 permutationMain(In);
880}
881)");
882
883 }
884 if (!pp.process(context, fs_header)) return false;
885 fs_header.swap(pp.processed);
886 pp.processed.clear();
887
888 std::string fs_uniforms;
889 fs_uniforms.reserve(InitialBufferCapasity);
890 addTextures(fs_uniforms, pp.identifiersSeen, bindings, materialDefinition.properties.textures, false);
891 addEngineTextures(fs_uniforms, pp.identifiersSeen, bindings);
892 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
893 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings);
894 }
895 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
896 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings);
897 }
898 addEngineBuffers(fs_uniforms, pp.identifiersSeen, bindings);
899 addInclude(fs_uniforms, std::string_view(), "Engine/Common.wgsl");
900 if (!pp.process(context, fs_uniforms)) return false;
901 fs_uniforms.swap(pp.processed);
902 pp.processed.clear();
903
904 std::string all = fs_header + fs_uniforms + fs_body;
905 all = convertDefinesToConstExpressions(all);
906
907 std::string fspath = "Shaders/" + shaderName;
908 context->resourceStore->addResource(fspath, all);
909 }
910 definitions.clear();
911 return true;
912}
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
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