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