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