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 };
89
90 struct EngineTexture {
91 std::string name;
93 bool isDepthTexture = false;
94 };
95
96 const std::array engineTextures = {
97 EngineTexture{.name = "environmentSky", .dimensions = TextureDimensions::TexureCube},
98 EngineTexture{.name = "environmentRadiance", .dimensions = TextureDimensions::TexureCube},
99 EngineTexture{.name = "environmentIrradiance", .dimensions = TextureDimensions::TexureCube},
100 EngineTexture{.name = "ambientIrradiance", .dimensions = TextureDimensions::TexureCube},
101 EngineTexture{.name = "brdfLUT", .dimensions = TextureDimensions::Texture2D},
102 EngineTexture{.name = "cascadedShadowMap", .dimensions = TextureDimensions::Texture2DArray, .isDepthTexture = true},
103 EngineTexture{.name = "cubeShadowMap", .dimensions = TextureDimensions::TexureCube, .isDepthTexture = true},
104 };
105
106 struct WebGPULocation {
107 size_t location = 0;
108 std::string name;
109 Cogs::Core::TextureDimensions dimensions = Cogs::Core::TextureDimensions::Texture2D;
110 bool isDepthTexture = false;
111 WebGPULocation(size_t location, const std::string &name, Cogs::Core::TextureDimensions dimensions = Cogs::Core::TextureDimensions::Texture2D, bool isDepthTexture = false) :
112 location(location), name(name), dimensions(dimensions), isDepthTexture(isDepthTexture) { }
113 WebGPULocation() = default;
114
115 };
116
117 using WebGPUBindingGroup = std::vector<WebGPULocation>;
118 using WebGPUBindingGroupVector = std::vector<WebGPUBindingGroup>;
119
120 [[nodiscard]]
121 bool hasBinding(const WebGPUBindingGroupVector& bindingGroups, std::string_view name) {
122 for (const WebGPUBindingGroup& bindings : bindingGroups) {
123 for (const WebGPULocation& location : bindings) {
124 if (location.name == name) {
125 return true;
126 }
127 }
128 }
129 return false;
130 }
131
132 void createBufferBinding(WebGPUBindingGroupVector& bindingGroups, const MaterialDefinition& materialDefinition) {
133 if (bindingGroups.empty()) {
134 bindingGroups.emplace_back();
135 }
136 WebGPUBindingGroup& locations = bindingGroups[0];
137 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
138 for (auto& buffer : requiredEngineBuffers) {
139 WebGPULocation loc(locations.size(), buffer);
140 locations.push_back(loc);
141 }
142
143 for (auto& buffer : optionalEngineBuffers) {
144 locations.emplace_back(locations.size(), buffer);
145 }
146
147
148 for (auto& buffer : materialDefinition.properties.buffers) {
149 if (std::find(std::begin(requiredEngineBuffers), std::end(requiredEngineBuffers), buffer.name) == std::end(requiredEngineBuffers)) {
150 if (!hasBinding(bindingGroups, buffer.name)) {
151 locations.emplace_back(locations.size(), buffer.name);
152 }
153 }
154 }
155 }
156
157 [[nodiscard]]
158 std::string convertDefinesToConstExpressions(const std::string& s) {
159 std::map<std::string, std::string> addedDefines;
160 std::string result;
161 result.reserve(s.size());
162
163 std::istringstream iss(s);
164
165 std::regex e1("^\\s*#define\\s+([^\\s]+)\\s+([^\\s]+)"); // matches #define CNAME value
166 std::regex e2("^\\s*#define\\s+([^\\s]+)\\s*$"); // matches #define CNAME
167 std::string identifier;
168 std::string replacement;
169 for (std::string line; std::getline(iss, line); )
170 {
171 identifier.clear();
172 replacement.clear();
173 std::smatch match;
174
175 if (std::regex_search(line, match, e1))
176 {
177 identifier += match.str(1);
178 replacement += match.str(2);
179 }
180 else if (std::regex_search(line, match, e2)) {
181 identifier += match.str(1);
182 replacement += "1";
183 }
184 if (identifier.empty()) {
185 result += line + "\n";
186 continue;
187 }
188 auto it = addedDefines.find(identifier);
189 if (it != addedDefines.end()) {
190 if (replacement != (*it).second) {
191 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());
192 }
193 continue;
194 }
195 result += "const " + identifier + " = " + replacement + ";\n";
196 addedDefines.try_emplace(identifier, replacement);
197 }
198 return result;
199 }
200
201 void addInclude(std::string& content, std::string_view prefix, std::string_view path)
202 {
203 content.append("#include \"");
204 content.append(prefix);
205 content.append(path);
206 content.append("\"\n");
207 }
208
209 void changeSuffix(std::string& dst, std::string_view from, std::string_view to)
210 {
211 if (dst.ends_with(from)) {
212 dst.replace(dst.size() - from.length(), from.length(), to);
213 }
214 }
215
216 [[nodiscard]]
217 std::string attributeVaryingType(const MaterialDataType type)
218 {
219 switch (type) {
220 case MaterialDataType::Float: return "f32 "; break;
221 case MaterialDataType::Float2: return "vec2f "; break;
222 case MaterialDataType::Float3: return "vec3f "; break;
223 case MaterialDataType::Float4: return "vec4f "; break;
224 case MaterialDataType::Float4x4: return "mat4x4f "; break;
225 case MaterialDataType::Int: return "i32 "; break;
226 case MaterialDataType::Int2: return "vec4i "; break;
227 case MaterialDataType::Int3: return "vec3i "; break;
228 case MaterialDataType::Int4: return "vec4i "; break;
229 case MaterialDataType::UInt: return "u32 "; break;
230 case MaterialDataType::UInt2: return "vec4u "; break;
231 case MaterialDataType::UInt3: return "vec3u "; break;
232 case MaterialDataType::UInt4: return "vec4u "; break;
233 case MaterialDataType::Bool: return "bool "; break;
234 case MaterialDataType::SV_IsFrontFace: return "bool "; break;
235 case MaterialDataType::VFACE: return "f32 "; break;
236 case MaterialDataType::Position: return "vec4f "; break;
237 default:
238 LOG_ERROR(logger, "Unsupported attribute type %d", int(type));
239 return "<illegal>";
240 break;
241 }
242 }
243
244 void createTextureBindings(WebGPUBindingGroupVector& bindingGroups, const MaterialDefinition& materialDefinition) {
245 if (bindingGroups.empty()) {
246 bindingGroups.emplace_back();
247 }
248 WebGPUBindingGroup& locations = bindingGroups[0];
249
250 for (auto& texture : materialDefinition.properties.textures) {
251 if (!hasBinding(bindingGroups, texture.name)) {
252 std::string name = texture.name;
253 locations.emplace_back(locations.size(), name, texture.dimensions);
254 locations.emplace_back(locations.size(), name + "Sampler", texture.dimensions);
255 }
256 }
257 }
258
259 void createEngineTextureBindings(WebGPUBindingGroupVector& bindingGroups) {
260 if (bindingGroups.empty()) {
261 bindingGroups.emplace_back();
262 }
263 WebGPUBindingGroup& locations = bindingGroups[0];
264
265 for (auto& tex : engineTextures) {
266 if (!hasBinding(bindingGroups, tex.name)) {
267 std::string name = tex.name;
268 locations.emplace_back(locations.size(), name, tex.dimensions, tex.isDepthTexture);
269 locations.emplace_back(locations.size(), name + "Sampler", tex.dimensions, tex.isDepthTexture);
270 }
271 }
272 }
273
274 void addEffectDefinesAndStuff(std::string& output, const std::vector<std::pair<std::string, std::string>>& definitions, uint32_t multiViewCount) {
275 if (multiViewCount) {
276 std::string multiViewCountString = std::to_string(multiViewCount);
277 output.append("#extension GL_OVR_multiview : require\nlayout(num_views=");
278 output.append(multiViewCountString);
279 output.append(") in;\n");
280 output.append("#define COGS_MULTIVIEW ");
281 output.append(multiViewCountString);
282 output.append("\n");
283 }
284
285 std::unordered_set<std::string> addedDefines;
286
287 for (const std::pair<std::string, std::string>& define : definitions) {
288 if (addedDefines.contains(define.first)) {
289 continue;
290 }
291 output.append("#define ");
292 output.append(define.first);
293 output.append(" ");
294 output.append(define.second);
295 output.append("\n");
296 addedDefines.insert(define.first);
297 }
298 }
299
300 [[nodiscard]]
301 BuiltinAttributeDescription getBuiltinAttributeDescription(const ShaderInterfaceMemberDefinition& attribute) {
302 BuiltinAttributeDescription desc;
303 desc.stage = ShaderInterface::None;
304 for (const auto& builtin : builtinDataTypes)
305 {
306 if (builtin.type == attribute.type) {
307 desc = builtin.desc;
308 }
309 }
310 for (const auto& attr : builtinAttributes) {
311 if (attr.semanticName == attribute.semantic.name) {
312 desc = attr.desc;
313 }
314 }
315 return desc;
316 }
317
318 void addInterfaceStruct(std::string& defines, std::string& source, std::string_view name, const ShaderInterfaceDefinition& iface, ShaderInterface shaderInterface)
319 {
320 size_t next_loc = 0;
321 source.append("struct ");
322 source.append(name);
323 source.append(" {\n");
324
325 if (shaderInterface & ShaderInterface::VertexOut) {
326 source.append(" @builtin(position) Position: vec4f,\n");
327 }
328 for (const auto& attribute : iface.members) {
329 BuiltinAttributeDescription builtinAttributeDescription = getBuiltinAttributeDescription(attribute);
330 if (shaderInterface & ShaderInterface::VertexOut &&
331 (builtinAttributeDescription.stage & ShaderInterface::FragmentIn) != 0) {
332 continue;
333 }
334 if (shaderInterface & ShaderInterface::FragmentIn && attribute.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::None && attribute.type == MaterialDataType::Position) {
335 continue; // Position is often described both through semantic name and attribute type
336 }
337
338 if ((builtinAttributeDescription.stage & shaderInterface) != 0) {
339 source.append(" @builtin(" + builtinAttributeDescription.builtinName + ") ");
340 source.append(attribute.name);
341 source.append(" : ");
342 source.append(attributeVaryingType(builtinAttributeDescription.type));
343 source.append(",\n");
344 }
345 else {
346 if (shaderInterface & ShaderInterface::VertexIn) {
347 defines.append("#define ");
348 size_t nameInd = size_t (attribute.semantic.name) - 1;
349 std::string_view semanticName = getSemanticName(nameInd);
350 if (!semanticName.empty()) {
351 std::string defineName;
352 defineName.append(semanticName);
353 defineName.append(std::to_string(attribute.semantic.slot) + "_LOC");
354 defines.append(defineName + " " + std::to_string(next_loc++) + "\n");
355 source.append(" @location(" + defineName + ") ");
356 }
357 }
358 else {
359 source.append(" @location(" + std::to_string(next_loc++) + ") ");
360 }
361 source.append(attribute.name);
362 source.append(" : ");
363 if (attribute.type == MaterialDataType::UInt4) {
364 source.append("vec4u");
365 }
366 else {
367 source.append(attributeVaryingType(attribute.type));
368 }
369 source.append(",\n");
370 }
371 }
372 source.append("};\n\n");
373 }
374
375 void addInterfaceConstructor(std::string& shaderSource, std::string_view name, const ShaderInterfaceDefinition& iface)
376 {
377 shaderSource.append("fn create");
378 shaderSource.append(name);
379 shaderSource.append("() -> ");
380 shaderSource.append(name);
381 shaderSource.append(" {\n");
382 shaderSource.append(" var t : ");
383 shaderSource.append(name);
384 shaderSource.append(";\n");
385 for (const auto& attribute : iface.members) {
386 const char* initializer = nullptr;
387 switch (attribute.type) {
388 case MaterialDataType::Float: initializer = "0.0;\n"; break;
389 case MaterialDataType::Float2: initializer = "vec2f(0);\n"; break;
390 case MaterialDataType::Float3: initializer = "vec3f(0);\n"; break;
391 case MaterialDataType::Float4: initializer = "vec4f(0);\n"; break;
392 case MaterialDataType::Float4x4: initializer = "mat4x4f(0);\n"; break;
393 default:
394 break;
395 }
396 if (initializer) {
397 shaderSource.append(" t.");
398 shaderSource.append(attribute.name);
399 shaderSource.append(" = ");
400 shaderSource.append(initializer);
401 }
402 }
403 shaderSource.append(" return t;\n}\n\n");
404 }
405
406 void addUniform(std::string& shaderSource, WebGPUBindingGroupVector& bindingGroups, std::string_view name, std::string_view type, bool useTemplate = false) {
407 WebGPUBindingGroup& bindings = bindingGroups[0];
408 WebGPULocation location;
409 for (const WebGPULocation& curr_location : bindings) {
410 if (curr_location.name == name) {
411 location = curr_location;
412 break;
413 }
414 }
415 if (location.name != name) {
416 LOG_ERROR(logger, "Texture not in bindgroup %s", std::string(name).c_str());
417 return;
418 }
419 shaderSource.append("@group(");
420 shaderSource.append(std::to_string(0));
421 shaderSource.append(") @binding(");
422 shaderSource.append(std::to_string(location.location));
423 shaderSource.append(") var");
424 if (useTemplate) {
425 shaderSource.append("<uniform>");
426 }
427 shaderSource.append(" ");
428 shaderSource.append(name);
429 shaderSource.append(" : ");
430 shaderSource.append(type);
431 shaderSource.append(";\n");
432 }
433
434 void addUniformBuffer(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, const ConstantBufferDefinition& definition, WebGPUBindingGroupVector& bindingGroups)
435 {
436 if (!identifiersSeen.contains(Strings::add(definition.name))) {
437 return;
438 }
439 if (definition.values.empty()) return;
440 shaderSource.append("struct ");
441 shaderSource.append(definition.name);
442 shaderSource.append("_t {\n");
443 for (const ConstantBufferVariableDefinition& member : definition.values) {
444 shaderSource.append(member.name);
445 shaderSource.append(" : ");
446
447 switch (member.type) {
448 case MaterialDataType::Float: shaderSource.append("f32"); break;
449 case MaterialDataType::Float2: shaderSource.append("vec2f"); break;
450 case MaterialDataType::Float3: shaderSource.append("vec3f"); break;
451 case MaterialDataType::Float4: shaderSource.append("vec4f"); break;
452 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f"); break;
453 case MaterialDataType::Int: shaderSource.append("i32"); break;
454 case MaterialDataType::Int2: shaderSource.append("vec2i"); break;
455 case MaterialDataType::Int3: shaderSource.append("vec3i"); break;
456 case MaterialDataType::Int4: shaderSource.append("vec4i"); break;
457 case MaterialDataType::UInt: shaderSource.append("u32"); break;
458 case MaterialDataType::UInt2: shaderSource.append("vec2u"); break;
459 case MaterialDataType::UInt3: shaderSource.append("vec3u"); break;
460 case MaterialDataType::UInt4: shaderSource.append("vec4u"); break;
461 case MaterialDataType::Bool: shaderSource.append("u32"); break;
462 default:
463 shaderSource.append("<invalid type>");
464 break;
465 }
466 if (member.type == MaterialDataType::Float4Array || member.type == MaterialDataType::Float4x4Array) {
467 assert(member.dimension != static_cast<size_t>(-1));
468 shaderSource.append("[");
469 shaderSource.append(std::to_string(member.dimension));
470 shaderSource.append("]");
471 }
472 shaderSource.append(",\n");
473 }
474 shaderSource.append("};\n");
475
476 addUniform(shaderSource, bindingGroups, definition.name, definition.name + "_t", true);
477 }
478
479 void addEngineBuffers(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups) {
480 for (const std::string& name : requiredEngineBuffers) {
481 addUniform(shaderSource, bindingGroups, name, name + "_t", true);
482 }
483 for (const std::string& name : optionalEngineBuffers) {
484
485 if (identifiersSeen.contains(Strings::add(name))) {
486 addUniform(shaderSource, bindingGroups, name, name + "_t", true);
487 }
488 }
489 }
490
491 void addTransferFunc(std::string& shaderSource, const ShaderDefinition& sourceDefinition, const ShaderDefinition& destinationDefinition)
492 {
493 shaderSource.append("fn transferAttributes(vertexIn : VertexIn, vertexOut : ptr<function, VertexOut>) {\n");
494 for (const ShaderInterfaceMemberDefinition& member : sourceDefinition.shaderInterface.members) {
495 if (member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::SV_VertexID) continue;
496 for (const ShaderInterfaceMemberDefinition& dMember : destinationDefinition.shaderInterface.members) {
497 if (dMember.name == member.name) {
498 shaderSource.append(" (*vertexOut).");
499 shaderSource.append(member.name);
500 shaderSource.append(" = ");
501 if (dMember.type == MaterialDataType::Float3 && member.type == MaterialDataType::Float2 && member.semantic.name == ShaderInterfaceMemberDefinition::SemanticName::Normal) {
502 shaderSource.append("octDecode(vertexIn.");
503 shaderSource.append(member.name);
504 shaderSource.append("); \n");
505 break;
506 }
507 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float3) {
508 shaderSource.append("vec4f(vertexIn.");
509 shaderSource.append(member.name);
510 shaderSource.append(", 1.0);\n");
511 break;
512 }
513 if (dMember.type == MaterialDataType::Float4 && member.type == MaterialDataType::Float2) {
514 shaderSource.append("vec4f(vertexIn.");
515 shaderSource.append(member.name);
516 shaderSource.append(", 0.0, 1.0);\n");
517 break;
518 }
519 bool close = false;
520 if (dMember.type != member.type) {
521 switch (dMember.type) {
522 case MaterialDataType::Float: shaderSource.append("f32("); close = true; break;
523 case MaterialDataType::Float2: shaderSource.append("vec2f("); close = true; break;
524 case MaterialDataType::Float3: shaderSource.append("vec3f("); close = true; break;
525 case MaterialDataType::Float4: shaderSource.append("vec4f("); close = true; break;
526 case MaterialDataType::Float4x4: shaderSource.append("mat4x4f("); close = true; break;
527 case MaterialDataType::Int: shaderSource.append("i32("); close = true; break;
528 case MaterialDataType::Int2: shaderSource.append("vec2i("); close = true; break;
529 case MaterialDataType::Int3: shaderSource.append("vec3i("); close = true; break;
530 case MaterialDataType::Int4: shaderSource.append("vec4i("); close = true; break;
531 case MaterialDataType::UInt: shaderSource.append("u32("); close = true; break;
532 case MaterialDataType::UInt2: shaderSource.append("vec2u("); close = true; break;
533 case MaterialDataType::UInt3: shaderSource.append("vec3u("); close = true; break;
534 case MaterialDataType::UInt4: shaderSource.append("vec4u("); close = true; break;
535 default:
536 break;
537 }
538 }
539 shaderSource.append("vertexIn.");
540 shaderSource.append(member.name);
541 if (close) shaderSource.append(1, ')');
542 shaderSource.append(";\n");
543 break;
544 }
545 }
546 }
547 shaderSource.append("}\n");
548 }
549
550 void addCallFunction(std::string& source, std::string_view name, std::string_view functionName, std::string_view inType, std::string_view outType) {
551 source.append("fn ");
552 source.append(name);
553 source.append("(In : ");
554 source.append(inType);
555 source.append(") ->");
556 source.append(outType);
557 source.append(" { \n");
558 source.append(" return ");
559 source.append(functionName);
560 source.append("(In);\n");
561 source.append("}\n\n");
562 }
563
564 void addTextures(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups, const std::vector<MaterialTextureDefinition>& definition, bool ignoreIdentifiersSeen = false)
565 {
566 for (auto& texture : definition) {
567 if (!identifiersSeen.contains(Strings::add(texture.name)) && !ignoreIdentifiersSeen) {
568 continue;
569 }
570 std::string type;
571 type = "texture";
572 switch (texture.dimensions) {
573 case TextureDimensions::Texture2D:
574 type += "_2d<f32>";
575 break;
576 case TextureDimensions::TexureCube:
577 type += "_cube<f32>";
578 break;
579 case TextureDimensions::Texture2DArray:
580 type += "_2d_array<f32>";
581 break;
582 case TextureDimensions::Texture3D:
583 type += "_3d<f32>";
584 break;
585 default:
586 LOG_ERROR(logger, "Unsupported texture type %d", int(texture.dimensions));
587 break;
588 }
589 addUniform(shaderSource, bindingGroups, texture.name, type);
590 addUniform(shaderSource, bindingGroups, texture.name + "Sampler", "sampler");
591 }
592 }
593
594 void addEngineTextures(std::string& shaderSource, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen, WebGPUBindingGroupVector& bindingGroups)
595 {
596 for (auto& texture : engineTextures) {
597 if (!identifiersSeen.contains(Strings::add(texture.name))) {
598 continue;
599 }
600 std::string type;
601 type.reserve(50);
602 std::string samplerType = "sampler";
603 type += "texture";
604
605 if (texture.isDepthTexture) {
606 type += "_depth";
607 samplerType += "_comparison";
608 }
609
610 switch (texture.dimensions) {
611 case TextureDimensions::Texture2D:
612 type += "_2d";
613 break;
614 case TextureDimensions::TexureCube:
615 type += "_cube";
616 break;
617 case TextureDimensions::Texture2DArray:
618 type += "_2d_array";
619 break;
620 case TextureDimensions::Texture3D:
621 type += "_3d";
622 break;
623 default:
624 LOG_ERROR(logger, "Unsupported texture type %d", int(texture.dimensions));
625 break;
626 }
627 if (!texture.isDepthTexture) {
628 type += "<f32>";
629 }
630 addUniform(shaderSource, bindingGroups, texture.name, type);
631 addUniform(shaderSource, bindingGroups, texture.name + "Sampler", samplerType);
632 }
633 }
634
635} // anonymous namespace
636
637bool Cogs::Core::buildEffectWebGPU(Context* context,
638 std::vector<std::pair<std::string, std::string>>& definitions,
639 MaterialDefinition& materialDefinition,
640 const EnginePermutation& permutation,
641 const uint32_t multiViewCount)
642{
643 if (!materialDefinition.effect.geometryShader.entryPoint.empty()) {
644 LOG_ERROR(logger, "%s: Geometry shader not allowed in WebGPU.", materialDefinition.name.c_str());
645 return false;
646 }
647 if (!materialDefinition.effect.hullShader.entryPoint.empty()) {
648 LOG_ERROR(logger, "%s: Hull shader not allowed in WebGPU.", materialDefinition.name.c_str());
649 return false;
650 }
651 if (!materialDefinition.effect.domainShader.entryPoint.empty()) {
652 LOG_ERROR(logger, "%s: Domain shader not allowed in WebGPU.", materialDefinition.name.c_str());
653 return false;
654 }
655 if (!materialDefinition.effect.computeShader.entryPoint.empty()) {
656 LOG_ERROR(logger, "%s: Compute shader not allowed in WebGPU.", materialDefinition.name.c_str());
657 return false;
658 }
659
660 changeSuffix(materialDefinition.effect.vertexShader.customSourcePath, ".hlsl", ".wgsl");
661 changeSuffix(materialDefinition.effect.pixelShader.customSourcePath, ".hlsl", ".wgsl");
662 std::string prefix = "" + materialDefinition.name;
663
664 WebGPUBindingGroupVector bindings;
665 createBufferBinding(bindings, materialDefinition);
666 createTextureBindings(bindings, materialDefinition);
667 createEngineTextureBindings(bindings);
668
669 {
671 std::string vs_header;
672 const std::string shaderName = prefix + "VertexShader" + permutation.getDefinition()->name + ".wgsl";
673 addEffectDefinesAndStuff(vs_header, definitions, multiViewCount);
674 if (!pp.process(context, vs_header)) return false;
675 vs_header.swap(pp.processed);
676 pp.processed.clear();
677
678 std::string vs_body;
679 addInterfaceStruct(vs_header, vs_body, "VertexIn", materialDefinition.effect.vertexShader.shaderInterface, ShaderInterface::VertexIn);
680 addInterfaceStruct(vs_header, vs_body, "VertexOut", materialDefinition.effect.pixelShader.shaderInterface, ShaderInterface::VertexOut);
681 addInterfaceConstructor(vs_body, "VertexOut", materialDefinition.effect.pixelShader.shaderInterface);
682 addTransferFunc(vs_body, materialDefinition.effect.vertexShader, materialDefinition.effect.pixelShader);
683 addInclude(vs_body, std::string_view(), materialDefinition.effect.vertexShader.customSourcePath);
684 std::string permutationVS = permutation.getDefinition()->vertexShader;
685 changeSuffix(permutationVS, ".hlsl", ".wgsl");
686
687 addCallFunction(vs_body, "callMaterialVertexFunction",
688 materialDefinition.effect.vertexShader.entryPoint.empty() ? "vertexFunction" : materialDefinition.effect.vertexShader.entryPoint,
689 "VertexIn", "VertexOut");
690 addInclude(vs_body, std::string_view(), "Engine/EngineVS.wgsl");
691 addCallFunction(vs_body, "callVertexFunction",
692 "invokeMaterial",
693 "VertexIn", "VertexOut");
694 addInclude(vs_body, std::string_view(), permutationVS);
695 if (!pp.process(context, vs_body)) return false;
696 vs_body.swap(pp.processed);
697 pp.processed.clear();
698
699 std::string vs_uniforms;
700
701 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
702 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings);
703 }
704 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
705 addUniformBuffer(vs_uniforms, pp.identifiersSeen, buffer, bindings);
706 }
707 addEngineBuffers(vs_uniforms, pp.identifiersSeen, bindings);
708 addInclude(vs_uniforms, std::string_view(), "Engine/Common.wgsl");
709
710 if (!pp.process(context, vs_uniforms)) return false;
711 vs_uniforms.swap(pp.processed);
712 pp.processed.clear();
713
714 std::string all = vs_header + vs_uniforms + vs_body;
715 all = convertDefinesToConstExpressions(all);
716
717 std::string vspath = "Shaders/" + shaderName;
718 context->resourceStore->addResource(vspath, all);
719 }
720 {
722 std::string fs_header;
723 const std::string shaderName = prefix + "PixelShader" + permutation.getDefinition()->name + ".wgsl";
724 addEffectDefinesAndStuff(fs_header, definitions, 0 /* multiview handled in VS. */);
725
726 std::string fs_body;
727
728 addInterfaceStruct(fs_header, fs_body, "VertexIn", materialDefinition.effect.pixelShader.shaderInterface, ShaderInterface::FragmentIn);
729 addInclude(fs_header, std::string_view(), materialDefinition.effect.pixelShader.customSourcePath);
730 std::string permutationFS = permutation.getDefinition()->pixelShader;
731 changeSuffix(permutationFS, ".hlsl", ".wgsl");
732
733 addCallFunction(fs_header, "callMaterialSurfaceFunction",
734 materialDefinition.effect.pixelShader.entryPoint.empty() ? "surfaceFunction" : materialDefinition.effect.pixelShader.entryPoint,
735 "VertexIn", "SurfaceOut");
736 addInclude(fs_header, std::string_view(), "Engine/EnginePS.wgsl");
737 addCallFunction(fs_header, "callSurfaceFunction",
738 "invokeMaterial",
739 "VertexIn", "SurfaceOut");
740 addInclude(fs_header, std::string_view(), permutationFS);
741 if (!pp.process(context, fs_header)) return false;
742 fs_header.swap(pp.processed);
743 pp.processed.clear();
744
745 std::string fs_uniforms;
746 fs_uniforms.reserve(InitialBufferCapasity);
747 addTextures(fs_uniforms, pp.identifiersSeen, bindings, materialDefinition.properties.textures, true);
748 addEngineTextures(fs_uniforms, pp.identifiersSeen, bindings);
749 for (const ConstantBufferDefinition& buffer : permutation.getDefinition()->properties.buffers) {
750 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings);
751 }
752 for (const ConstantBufferDefinition& buffer : materialDefinition.properties.buffers) {
753 addUniformBuffer(fs_uniforms, pp.identifiersSeen, buffer, bindings);
754 }
755 addEngineBuffers(fs_uniforms, pp.identifiersSeen, bindings);
756 addInclude(fs_uniforms, std::string_view(), "Engine/Common.wgsl");
757 if (!pp.process(context, fs_uniforms)) return false;
758 fs_uniforms.swap(pp.processed);
759 pp.processed.clear();
760
761 std::string all = fs_header + fs_uniforms + fs_body;
762 all = convertDefinesToConstExpressions(all);
763
764 std::string fspath = "Shaders/" + shaderName;
765 context->resourceStore->addResource(fspath, all);
766 }
767 definitions.clear();
768 return true;
769}
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:139
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:180
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