Cogs.Core
EffectsWebGPU.cpp
1#include "EffectsWebGPU.h"
2
3#include "GraphicsDeviceWebGPU.h"
4
5#include "Foundation/Logging/Logger.h"
6
7#include <algorithm>
8#include <cinttypes>
9#include <sstream>
10#include <regex>
11
12namespace{
13 Cogs::Logging::Log logger = Cogs::Logging::getLogger("EffectsWebGPU");
14
16 bool starts_with(std::string::const_iterator begin, std::string::const_iterator end, const char* s, std::string::const_iterator& next) {
17 std::string::const_iterator it = begin;
18 next = begin;
19 while (it != end && *s != '\0') {
20 if (*it != *s) {
21 return false;
22 }
23 s++;
24 it++;
25 }
26 if (*s != '\0') {
27 return false;
28 }
29 next = it;
30 return true;
31 }
32
33 void eat_white(std::string::const_iterator& it, std::string::const_iterator end) {
34 while (it != end && std::isspace(*it)) {
35 it++;
36 }
37 }
38
39
40 WGPUTextureViewDimension extractViewDimention_native(std::string::const_iterator& type_it, std::string::const_iterator end) {
41 WGPUTextureViewDimension tvd = WGPUTextureViewDimension_Undefined;
42 if (starts_with(type_it, end, "2d_array", type_it)) {
43 tvd = WGPUTextureViewDimension_2DArray;
44 }
45 else if (starts_with(type_it, end, "2d", type_it)) {
46 tvd = WGPUTextureViewDimension_2D;
47 }
48 else if (starts_with(type_it, end, "cube", type_it)) {
49 tvd = WGPUTextureViewDimension_Cube;
50 }
51 else if (starts_with(type_it, end, "1d", type_it)) {
52 tvd = WGPUTextureViewDimension_1D;
53 }
54 else if (starts_with(type_it, end, "cube_array", type_it)) {
55 tvd = WGPUTextureViewDimension_CubeArray;
56 }
57 else if (starts_with(type_it, end, "3d", type_it)) {
58 tvd = WGPUTextureViewDimension_3D;
59 }
60 return tvd;
61 }
62
63 WGPUTextureFormat extractTextureFormat_native(std::string::const_iterator& type_it, std::string::const_iterator end) {
64 WGPUTextureFormat tf = WGPUTextureFormat_Undefined;
65 if (starts_with(type_it, end, "rgba8unorm", type_it)) {
66 tf = WGPUTextureFormat_RGBA8Unorm;
67 }
68 else if (starts_with(type_it, end, "rgba8snorm", type_it)) {
69 tf = WGPUTextureFormat_RGBA8Snorm;
70 }
71 else if (starts_with(type_it, end, "rgba8uint", type_it)) {
72 tf = WGPUTextureFormat_RGBA8Uint;
73 }
74 else if (starts_with(type_it, end, "rgba8sint", type_it)) {
75 tf = WGPUTextureFormat_RGBA8Sint;
76 }
77 else if (starts_with(type_it, end, "rgba16uint", type_it)) {
78 tf = WGPUTextureFormat_RGBA16Uint;
79 }
80 else if (starts_with(type_it, end, "rgba16sint", type_it)) {
81 tf = WGPUTextureFormat_RGBA16Sint;
82 }
83 else if (starts_with(type_it, end, "rgba16float", type_it)) {
84 tf = WGPUTextureFormat_RGBA16Float;
85 }
86 else if (starts_with(type_it, end, "r32uint", type_it)) {
87 tf = WGPUTextureFormat_R32Uint;
88 }
89 else if (starts_with(type_it, end, "r32sint", type_it)) {
90 tf = WGPUTextureFormat_R32Sint;
91 }
92 else if (starts_with(type_it, end, "r32float", type_it)) {
93 tf = WGPUTextureFormat_R32Float;
94 }
95 else if (starts_with(type_it, end, "rg32uint", type_it)) {
96 tf = WGPUTextureFormat_RG32Uint;
97 }
98 else if (starts_with(type_it, end, "rg32sint", type_it)) {
99 tf = WGPUTextureFormat_RG32Sint;
100 }
101 else if (starts_with(type_it, end, "rg32float", type_it)) {
102 tf = WGPUTextureFormat_RG32Float;
103 }
104 else if (starts_with(type_it, end, "rgba32uint", type_it)) {
105 tf = WGPUTextureFormat_RGBA32Uint;
106 }
107 else if (starts_with(type_it, end, "rgba32sint", type_it)) {
108 tf = WGPUTextureFormat_RGBA32Sint;
109 }
110 else if (starts_with(type_it, end, "rgba32float", type_it)) {
111 tf = WGPUTextureFormat_RGBA32Float;
112 }
113 else if (starts_with(type_it, end, "bgra8unorm", type_it)) {
114 tf = WGPUTextureFormat_BGRA8Unorm;
115 }
116 return tf;
117 }
118
119 WGPUStorageTextureAccess extractStorageTextureAccess_native(std::string::const_iterator& type_it, std::string::const_iterator end) {
120 WGPUStorageTextureAccess sta = WGPUStorageTextureAccess_Undefined;
121 if (starts_with(type_it, end, "read", type_it)) {
122 sta = WGPUStorageTextureAccess_ReadOnly;
123 }
124 else if (starts_with(type_it, end, "write", type_it)) {
125 sta = WGPUStorageTextureAccess_WriteOnly;
126 }
127 else if (starts_with(type_it, end, "readwrite", type_it)) {
128 sta = WGPUStorageTextureAccess_ReadWrite;
129 }
130 return sta;
131 }
132
133 std::vector<Cogs::WebGPUConstantBufferBinding> extractConstantBinding(std::string shaderSource, WGPUShaderStage usage) {
134 std::vector<Cogs::WebGPUConstantBufferBinding> result;
135 std::istringstream iss(shaderSource);
136 std::string expr = R"(^\s*@group\‍(([0-9]+)\) @binding\‍(([0-9]+)\)\s+var(<uniform>)?\s+([^\s]+)[\s]*:\s?(.*)\s?;)";
137 std::regex regex_expression(expr);
138
139 for (std::string line; std::getline(iss, line); )
140 {
141 std::smatch match;
142 if (std::regex_search(line, match, regex_expression))
143 {
144 size_t group = std::stoi(match.str(1));
145 unsigned int loc = std::stoi(match.str(2));
146 bool isUniform = match.str(3) == "<uniform>";
147 std::string name = match.str(4);
148 std::string type = match.str(5);
149
150 std::string::const_iterator type_it = type.begin();
151 size_t nameHash = Cogs::hash(name);
152 // Cogs::ConstantBufferBindingHandle location = (Cogs::ConstantBufferBindingHandle)loc;
153 // Cogs::WebGPUConstantBufferType bufferType = Cogs::WebGPUConstantBufferType::UniformBuffer;
154 bool alreadyInserted = false;
155 for (auto& e : result) {
156 if (e.group == group && e.bg_ent.binding == loc) {
157 e.bg_ent.visibility |= usage;
158 alreadyInserted = true;
159 break;
160 }
161 }
162 if (alreadyInserted) {
163 continue;
164 }
165 WGPUBindGroupLayoutEntry bg_ent = {};
166 bg_ent.binding = static_cast<uint32_t>(loc);
167 bg_ent.visibility = (WGPUShaderStage)usage;
168
169 if (isUniform) {
170 bg_ent.buffer.type = WGPUBufferBindingType_Uniform;
171 // bufferType = Cogs::WebGPUConstantBufferType::UniformBuffer;
172 }
173 else if (starts_with(type_it, type.end(), "texture_storage_", type_it)) { // type.starts_with("texture_")
174 bg_ent.storageTexture.viewDimension = extractViewDimention_native(type_it, type.end());
175 eat_white(type_it, type.end());
176 if (!starts_with(type_it, type.end(), "<", type_it)) {
177 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
178 }
179 bg_ent.storageTexture.format = extractTextureFormat_native(type_it, type.end());
180 eat_white(type_it, type.end());
181 if (!starts_with(type_it, type.end(), ",", type_it)) {
182 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
183 }
184 eat_white(type_it, type.end());
185 bg_ent.storageTexture.access = extractStorageTextureAccess_native(type_it, type.end());
186 eat_white(type_it, type.end());
187 if (!starts_with(type_it, type.end(), ">", type_it)) {
188 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
189 }
190 }
191 else if (starts_with(type_it, type.end(), "texture_", type_it)) {
192 bool isDepth = false;
193 bool isMS = false;
194 // bufferType = Cogs::WebGPUConstantBufferType::Texture;
195 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
196 bg_ent.texture.viewDimension = WGPUTextureViewDimension_2D;
197 bg_ent.texture.multisampled = 0;
198 if (starts_with(type_it, type.end(), "depth_", type_it)) {
199 isDepth = true;
200 }
201 if (starts_with(type_it, type.end(), "multisampled_", type_it)) {
202 bg_ent.texture.multisampled = 1;
203 isMS = true;
204 }
205 bg_ent.texture.viewDimension = extractViewDimention_native(type_it, type.end());
206 eat_white(type_it, type.end());
207 if (isDepth) {
208 bg_ent.texture.sampleType = WGPUTextureSampleType_Depth;
209 }
210 else if (starts_with(type_it, type.end(), "<f32>", type_it)) {
211 if (isMS) {
212 bg_ent.texture.sampleType = WGPUTextureSampleType_UnfilterableFloat;
213 }
214 else {
215 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
216 }
217 }
218 else if (starts_with(type_it, type.end(), "<i32>", type_it)) {
219 bg_ent.texture.sampleType = WGPUTextureSampleType_Sint;
220 }
221 else if (starts_with(type_it, type.end(), "<u32>", type_it)) {
222 bg_ent.texture.sampleType = WGPUTextureSampleType_Uint;
223 }
224 else {
225 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
226 }
227 }
228 else if (type == "sampler") {
229 // bufferType = Cogs::WebGPUConstantBufferType::Sampler;
230 bg_ent.sampler.type = WGPUSamplerBindingType_Filtering;
231 }
232 else if (type == "sampler_comparison") {
233 // bufferType = Cogs::WebGPUConstantBufferType::Sampler;
234 bg_ent.sampler.type = WGPUSamplerBindingType_Comparison;
235 }
236 else {
237 LOG_DEBUG(logger, "Unknown uniform type%s", type.c_str());
238 }
239
240 Cogs::WebGPUConstantBufferBinding binding{ .group = group, .nameHash = nameHash, .bg_ent = bg_ent, .name = name };
241 result.push_back(binding);
242 }
243 }
244 return result;
245 }
247
248
249// Fallback to shader source parsing
250 Cogs::BindingStorageTextureAccess extractStorageTextureAccess(std::string::const_iterator& type_it, std::string::const_iterator end) {
251 Cogs::BindingStorageTextureAccess sta = Cogs::BindingStorageTextureAccess::WriteOnly;
252 if (starts_with(type_it, end, "read_write", type_it) || starts_with(type_it, end, "readwrite", type_it)) {
253 sta = Cogs::BindingStorageTextureAccess::ReadWrite;
254 }
255 else if (starts_with(type_it, end, "read", type_it)) {
256 sta = Cogs::BindingStorageTextureAccess::ReadOnly;
257 }
258 else if (starts_with(type_it, end, "write", type_it)) {
259 sta = Cogs::BindingStorageTextureAccess::WriteOnly;
260 }
261 return sta;
262 }
263
264
265
266 Cogs::Format extractTextureFormat(std::string::const_iterator& type_it, std::string::const_iterator end) {
267 Cogs::Format tf = Cogs::Format::Unknown;
268 if (starts_with(type_it, end, "rgba8unorm", type_it)) {
269 tf = Cogs::Format::R8G8B8A8_UNORM;
270 }
271 else if (starts_with(type_it, end, "rgba8snorm", type_it)) {
272 tf = Cogs::Format::R8G8B8A8_SNORM;
273 }
274 else if (starts_with(type_it, end, "rgba8uint", type_it)) {
275 tf = Cogs::Format::R8G8B8A8_UINT;
276 }
277 else if (starts_with(type_it, end, "rgba8sint", type_it)) {
278 tf = Cogs::Format::R8G8B8A8_SINT;
279 }
280 else if (starts_with(type_it, end, "rgba16uint", type_it)) {
281 tf = Cogs::Format::R16G16B16A16_UINT;
282 }
283 else if (starts_with(type_it, end, "rgba16sint", type_it)) {
284 tf = Cogs::Format::R16G16B16A16_SINT;
285 }
286 else if (starts_with(type_it, end, "rgba16float", type_it)) {
287 tf = Cogs::Format::R16G16B16A16_FLOAT;
288 }
289 else if (starts_with(type_it, end, "r32uint", type_it)) {
290 tf = Cogs::Format::R32_UINT;
291 }
292 else if (starts_with(type_it, end, "r32sint", type_it)) {
293 tf = Cogs::Format::R32_SINT;
294 }
295 else if (starts_with(type_it, end, "r32float", type_it)) {
296 tf = Cogs::Format::R32_FLOAT;
297 }
298 else if (starts_with(type_it, end, "rg32uint", type_it)) {
299 tf = Cogs::Format::R32G32_UINT;
300 }
301 else if (starts_with(type_it, end, "rg32sint", type_it)) {
302 tf = Cogs::Format::R32G32_SINT;
303 }
304 else if (starts_with(type_it, end, "rg32float", type_it)) {
305 tf = Cogs::Format::R32G32_FLOAT;
306 }
307 else if (starts_with(type_it, end, "rgba32uint", type_it)) {
308 tf = Cogs::Format::R32G32B32A32_UINT;
309 }
310 else if (starts_with(type_it, end, "rgba32sint", type_it)) {
311 tf = Cogs::Format::R32G32B32A32_SINT;
312 }
313 else if (starts_with(type_it, end, "rgba32float", type_it)) {
314 tf = Cogs::Format::R32G32B32A32_FLOAT;
315 }
316 else if (starts_with(type_it, end, "bgra8unorm", type_it)) {
317 tf = Cogs::Format::B8G8R8A8;
318 }
319 return tf;
320 }
321
322 Cogs::ResourceDimensions extractViewDimention(std::string::const_iterator& type_it, std::string::const_iterator end) {
323 Cogs::ResourceDimensions tvd = Cogs::ResourceDimensions::Unknown;
324 if (starts_with(type_it, end, "2d_array", type_it)) {
325 tvd = Cogs::ResourceDimensions::Texture2DArray;
326 }
327 else if (starts_with(type_it, end, "2d", type_it)) {
328 tvd = Cogs::ResourceDimensions::Texture2D;
329 }
330 else if (starts_with(type_it, end, "cube", type_it)) {
331 tvd = Cogs::ResourceDimensions::TextureCube;
332 }
333 else if (starts_with(type_it, end, "1d", type_it)) {
334 tvd = Cogs::ResourceDimensions::Texture1D;
335 }
336 else if (starts_with(type_it, end, "cube_array", type_it)) {
337 tvd = Cogs::ResourceDimensions::Unknown; // WGPUTextureViewDimension_CubeArray;
338 LOG_ERROR_ONCE(logger, "Texture cube array not yet supported");
339 }
340 else if (starts_with(type_it, end, "3d", type_it)) {
341 tvd = Cogs::ResourceDimensions::Texture3D;
342 }
343 return tvd;
344 }
345
346 bool extractBindingLayout(const std::string &shaderSource, Cogs::BindingVisibilityFlags usage, Cogs::BindGroupSetDescription &layoutSet) {
347 std::istringstream iss(shaderSource);
348 std::string expr = R"(^\s*@group\‍(([0-9]+)\) @binding\‍(([0-9]+)\)\s+var(<uniform>)?\s+([^\s]+)[\s]*:\s?(.*)\s?;)";
349 std::regex regex_expression(expr);
350
351 for (std::string line; std::getline(iss, line); )
352 {
353 std::smatch match;
354 if (std::regex_search(line, match, regex_expression))
355 {
356 uint32_t group = std::stoi(match.str(1));
357 unsigned int loc = std::stoi(match.str(2));
358 bool isUniform = match.str(3) == "<uniform>";
359 std::string name = match.str(4);
360 std::string type = match.str(5);
361
362
363 std::string::const_iterator type_it = type.begin();
364 size_t nameHash = Cogs::hash(name);
365
366 // Cogs::ConstantBufferBindingHandle location = (Cogs::ConstantBufferBindingHandle)loc;
367 // Cogs::WebGPUConstantBufferType bufferType = Cogs::WebGPUConstantBufferType::UniformBuffer;
368 bool alreadyInserted = false;
369 if (group >= Cogs::MaxBindGroups) {
370 LOG_ERROR(logger, "Group number %u is out of range", group);
371 return false;
372 }
373 if (layoutSet.numGroups <= group) layoutSet.numGroups = static_cast<uint16_t>(group + 1);
374 Cogs::BindGroupDescription &bindGroup = layoutSet.groups[group];
375 for (size_t i = 0; i < bindGroup.numEntries; ++i) {
376 if (bindGroup.entries[i].binding != loc) continue;
377 if (bindGroup.entries[i].nameHash != nameHash) {
378 LOG_ERROR(logger, "Conflicting declarations for @group(%u) @binding(%u): '%s' does not match the resource already bound there.",
379 static_cast<unsigned int>(group), loc, name.c_str());
380 return false;
381 }
382 bindGroup.entries[i].visibility |= usage;
383 alreadyInserted = true;
384 break;
385 }
386
387 if (alreadyInserted) continue;
388 if (loc >= Cogs::MaxBindGroupEntries) {
389 LOG_ERROR(logger, "Binding number %d is out of range", loc);
390 return false;
391 }
392 Cogs::BindGroupEntryDescription &entry = bindGroup.entries[bindGroup.numEntries++];
393 entry = {};
394 entry.binding = static_cast<uint32_t>(loc);
395 entry.visibility = usage;
396 entry.nameHash = nameHash;
397
398 if (isUniform) {
399 entry.resourceType = Cogs::BindingResourceType::UniformBuffer;
400 }
401 else if (starts_with(type_it, type.end(), "texture_storage_", type_it)) { // type.starts_with("texture_")
402 entry.resourceType = Cogs::BindingResourceType::StorageTexture;
403 entry.textureDimension = extractViewDimention(type_it, type.end());
404 eat_white(type_it, type.end());
405 if (!starts_with(type_it, type.end(), "<", type_it)) {
406 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
407 }
408 entry.format = extractTextureFormat(type_it, type.end());
409 eat_white(type_it, type.end());
410 if (!starts_with(type_it, type.end(), ",", type_it)) {
411 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
412 }
413 eat_white(type_it, type.end());
414 entry.storageTextureAccess = extractStorageTextureAccess(type_it, type.end());
415 eat_white(type_it, type.end());
416 if (!starts_with(type_it, type.end(), ">", type_it)) {
417 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
418 }
419 }
420 else if (starts_with(type_it, type.end(), "texture_", type_it)) {
421 entry.resourceType = Cogs::BindingResourceType::Texture;
422 entry.textureDimension = Cogs::ResourceDimensions::Texture2D;
423 entry.textureSampleType = Cogs::BindingTextureSampleType::Float;
424 entry.isDepthTexture = false;
425 entry.multisampled = false;
426 if (starts_with(type_it, type.end(), "depth_", type_it)) {
427 entry.isDepthTexture = true;
428 }
429 if (starts_with(type_it, type.end(), "multisampled_", type_it)) {
430 entry.multisampled = true;
431 }
432 entry.textureDimension = extractViewDimention(type_it, type.end());
433 eat_white(type_it, type.end());
434 if (entry.isDepthTexture) {
435 entry.textureSampleType = Cogs::BindingTextureSampleType::Depth;
436 }
437 else if (starts_with(type_it, type.end(), "<f32>", type_it)) {
438 if (entry.multisampled) {
439 entry.textureSampleType = Cogs::BindingTextureSampleType::UnfilterableFloat;
440 }
441 else {
442 entry.textureSampleType = Cogs::BindingTextureSampleType::Float;
443 }
444 }
445 else if (starts_with(type_it, type.end(), "<i32>", type_it)) {
446 entry.textureSampleType = Cogs::BindingTextureSampleType::Sint;
447 }
448 else if (starts_with(type_it, type.end(), "<u32>", type_it)) {
449 entry.textureSampleType = Cogs::BindingTextureSampleType::Uint;
450 }
451 else {
452 LOG_DEBUG(logger, "Texture type %s not yet supported", type.c_str());
453 }
454 }
455 else if (type == "sampler") {
456 entry.resourceType = Cogs::BindingResourceType::Sampler;
457 entry.samplerBindingType = Cogs::BindingSamplerBindingType::Filtering;
458 }
459 else if (type == "sampler_comparison") {
460 entry.resourceType = Cogs::BindingResourceType::Sampler;
461 entry.samplerBindingType = Cogs::BindingSamplerBindingType::Comparison;
462 }
463 else {
464 LOG_DEBUG(logger, "Unknown uniform type%s", type.c_str());
465 }
466 }
467 }
468 return true;
469 }
470
471 // end of fallback
472
473 [[nodiscard]]
474 WGPUShaderStage bindingVisibilityToShaderStage(const uint32_t visibility)
475 {
476 uint32_t effectiveVisibility = visibility;
477 if (effectiveVisibility == Cogs::BindingVisibilityNone) {
478 effectiveVisibility = Cogs::BindingVisibilityVertex | Cogs::BindingVisibilityFragment;
479 }
480
481 uint32_t shaderStage = 0;
482 if ((effectiveVisibility & Cogs::BindingVisibilityVertex) != 0) {
483 shaderStage |= WGPUShaderStage_Vertex;
484 }
485 if ((effectiveVisibility & Cogs::BindingVisibilityFragment) != 0) {
486 shaderStage |= WGPUShaderStage_Fragment;
487 }
488 if ((effectiveVisibility & Cogs::BindingVisibilityCompute) != 0) {
489 shaderStage |= WGPUShaderStage_Compute;
490 }
491
492 return static_cast<WGPUShaderStage>(shaderStage);
493 }
494
495 [[nodiscard]]
496 WGPUTextureViewDimension ResourceDimensionsToViewDimension(const Cogs::ResourceDimensions dimension)
497 {
498 switch (dimension) {
499 case Cogs::ResourceDimensions::Texture1D:
500 return WGPUTextureViewDimension_1D;
501 case Cogs::ResourceDimensions::Texture2D:
502 return WGPUTextureViewDimension_2D;
503 case Cogs::ResourceDimensions::Texture2DArray:
504 return WGPUTextureViewDimension_2DArray;
505 case Cogs::ResourceDimensions::Texture3D:
506 return WGPUTextureViewDimension_3D;
507 case Cogs::ResourceDimensions::TextureCube:
508 return WGPUTextureViewDimension_Cube;
509 default:
510 return WGPUTextureViewDimension_Undefined;
511 }
512 }
513
514 [[nodiscard]]
515 WGPUStorageTextureAccess bindingStorageTextureAccessToWebGPU(Cogs::BindingStorageTextureAccess access) {
516 switch (access)
517 {
518 case Cogs::BindingStorageTextureAccess::WriteOnly:
519 return WGPUStorageTextureAccess_WriteOnly;
520 case Cogs::BindingStorageTextureAccess::ReadOnly:
521 return WGPUStorageTextureAccess_ReadOnly;
522 case Cogs::BindingStorageTextureAccess::ReadWrite:
523 return WGPUStorageTextureAccess_ReadWrite;
524 default:
525 return WGPUStorageTextureAccess_WriteOnly;
526 }
527 }
528
529
530 [[nodiscard]]
531 WGPUTextureSampleType bindingTextureSampleTypeToWebGPU(
532 const Cogs::BindingTextureSampleType sampleType,
533 const bool isDepthTexture,
534 const bool multisampled)
535 {
536 switch (sampleType) {
537 case Cogs::BindingTextureSampleType::Float:
538 return multisampled ? WGPUTextureSampleType_UnfilterableFloat : WGPUTextureSampleType_Float;
539 case Cogs::BindingTextureSampleType::UnfilterableFloat:
540 return WGPUTextureSampleType_UnfilterableFloat;
541 case Cogs::BindingTextureSampleType::Depth:
542 return WGPUTextureSampleType_Depth;
543 case Cogs::BindingTextureSampleType::Sint:
544 return WGPUTextureSampleType_Sint;
545 case Cogs::BindingTextureSampleType::Uint:
546 return WGPUTextureSampleType_Uint;
547 default:
548 return isDepthTexture ? WGPUTextureSampleType_Depth : WGPUTextureSampleType_Float;
549 }
550 }
551
552 [[nodiscard]]
553 WGPUSamplerBindingType bindingSamplerTypeToWebGPU(
554 const Cogs::BindingSamplerBindingType samplerType,
555 const bool isDepthTexture,
556 const size_t nameHash)
557 {
558 switch (samplerType) {
559 case Cogs::BindingSamplerBindingType::Filtering:
560 return WGPUSamplerBindingType_Filtering;
561 case Cogs::BindingSamplerBindingType::NonFiltering:
562 return WGPUSamplerBindingType_NonFiltering;
563 case Cogs::BindingSamplerBindingType::Comparison:
564 return WGPUSamplerBindingType_Comparison;
565 default:
566 return (isDepthTexture || nameHash == Cogs::hash("shadowSampler"))
567 ? WGPUSamplerBindingType_Comparison
568 : WGPUSamplerBindingType_Filtering;
569 }
570 }
571
572 [[nodiscard]]
573 WGPUTextureFormat bindingTextureFormatToWebGPU(Cogs::Format format) {
574 switch (format) {
575 case Cogs::Format::R8G8B8A8_UNORM:
576 return WGPUTextureFormat_RGBA8Unorm;
577 case Cogs::Format::R8G8B8A8_SNORM:
578 return WGPUTextureFormat_RGBA8Snorm;
579 case Cogs::Format::R8G8B8A8_UINT:
580 return WGPUTextureFormat_RGBA8Uint;
581 case Cogs::Format::R8G8B8A8_SINT:
582 return WGPUTextureFormat_RGBA8Sint;
583 case Cogs::Format::R16G16B16A16_UINT:
584 return WGPUTextureFormat_RGBA16Uint;
585 case Cogs::Format::R16G16B16A16_SINT:
586 return WGPUTextureFormat_RGBA16Sint;
587 case Cogs::Format::R16G16B16A16_FLOAT:
588 return WGPUTextureFormat_RGBA16Float;
589 case Cogs::Format::R32_UINT:
590 return WGPUTextureFormat_R32Uint;
591 case Cogs::Format::R32_SINT:
592 return WGPUTextureFormat_R32Sint;
593 case Cogs::Format::R32_FLOAT:
594 return WGPUTextureFormat_R32Float;
595 case Cogs::Format::R32G32_UINT:
596 return WGPUTextureFormat_RG32Uint;
597 case Cogs::Format::R32G32_SINT:
598 return WGPUTextureFormat_RG32Sint;
599 case Cogs::Format::R32G32_FLOAT:
600 return WGPUTextureFormat_RG32Float;
601 case Cogs::Format::R32G32B32A32_UINT:
602 return WGPUTextureFormat_RGBA32Uint;
603 case Cogs::Format::R32G32B32A32_SINT:
604 return WGPUTextureFormat_RGBA32Sint;
605 case Cogs::Format::R32G32B32A32_FLOAT:
606 return WGPUTextureFormat_RGBA32Float;
607 case Cogs::Format::B8G8R8A8:
608 return WGPUTextureFormat_BGRA8Unorm;
609 default:
610 return WGPUTextureFormat_Undefined;
611 }
612 }
613
614
615 [[nodiscard]]
616 bool getWebGPUConstantBufferBindingsFromMetadata(
617 const Cogs::BindGroupSetDescription* layoutSet,
618 std::vector<Cogs::WebGPUConstantBufferBinding>& outBindings)
619 {
620// const Cogs::BindGroupSetDescription* layoutSet = &effectDescription.bindGroupLayoutDesc;
621 outBindings.clear();
622
623 for (uint16_t groupIdx = 0; groupIdx < layoutSet->numGroups; groupIdx++) {
624 const Cogs::BindGroupDescription& group = layoutSet->groups[groupIdx];
625 if (group.numEntries == 0) continue;
626 for (uint16_t entryIdx = 0; entryIdx < group.numEntries; entryIdx++) {
627 const Cogs::BindGroupEntryDescription& entry = group.entries[entryIdx];
628 WGPUBindGroupLayoutEntry bg_ent = WGPU_BIND_GROUP_LAYOUT_ENTRY_INIT;
629 bg_ent.binding = entry.binding;
630 bg_ent.visibility = bindingVisibilityToShaderStage(entry.visibility);
631
632 switch (entry.resourceType) {
633 case Cogs::BindingResourceType::UniformBuffer:
634 bg_ent.buffer.type = WGPUBufferBindingType_Uniform;
635 break;
636 case Cogs::BindingResourceType::StorageBuffer:
637 bg_ent.buffer.type = WGPUBufferBindingType_Storage;
638 break;
639 case Cogs::BindingResourceType::Texture:
640 bg_ent.texture.viewDimension = ResourceDimensionsToViewDimension(entry.textureDimension);
641 bg_ent.texture.multisampled = entry.multisampled ? 1 : 0;
642 bg_ent.texture.sampleType = bindingTextureSampleTypeToWebGPU(entry.textureSampleType,
643 entry.isDepthTexture,
644 entry.multisampled);
645 break;
646 case Cogs::BindingResourceType::Sampler:
647 bg_ent.sampler.type = bindingSamplerTypeToWebGPU(entry.samplerBindingType,
648 entry.isDepthTexture,
649 entry.nameHash);
650 break;
651 case Cogs::BindingResourceType::StorageTexture:
652 bg_ent.storageTexture.viewDimension = ResourceDimensionsToViewDimension(entry.textureDimension);
653 bg_ent.storageTexture.access = bindingStorageTextureAccessToWebGPU(entry.storageTextureAccess);
654 bg_ent.storageTexture.format = bindingTextureFormatToWebGPU(entry.format);
655 break;
656 default:
657 LOG_ERROR(logger,
658 "Unsupported bind-group resource type %d for entry '%d''.",
659 int(entry.resourceType),
660 entry.binding);
661 return false;
662 }
663
665 binding.group = groupIdx;
666 binding.nameHash = entry.nameHash;
667 // binding.nameHash = entry.nameHash ? entry.nameHash : Cogs::hash(entry.name);
668 binding.bg_ent = bg_ent;
669 outBindings.push_back(std::move(binding));
670 }
671 }
672
673 return true;
674 }
675
676
677
678
679 const char* semanticNames[]
680 {
681 "a_POSITION",
682 "a_NORMAL",
683 "a_COLOR",
684 "a_TEXCOORD",
685 "a_TANGENT",
686 "a_INSTANCEVECTOR",
687 "a_INSTANCEMATRIX",
688 };
689
690 WGPUVertexFormat stringToVertexFormat(const Cogs::StringView &s) {
691 WGPUVertexFormat format = static_cast<WGPUVertexFormat>(0);
692 switch (Cogs::hash(s))
693 {
694 case Cogs::hash("f32"): format = WGPUVertexFormat::WGPUVertexFormat_Float32; break;
695 case Cogs::hash("vec2f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x2; break;
696 case Cogs::hash("vec3f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x3; break;
697 case Cogs::hash("vec4f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x4; break;
698 case Cogs::hash("i32"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32; break;
699 case Cogs::hash("vec2i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x2; break;
700 case Cogs::hash("vec3i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x3; break;
701 case Cogs::hash("vec4i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x4; break;
702 case Cogs::hash("u32"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32; break;
703 case Cogs::hash("vec2u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x2; break;
704 case Cogs::hash("vec3u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x3; break;
705 case Cogs::hash("vec4u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x4; break;
706 default:
707 LOG_ERROR(logger, "Unsupported vertex attribute type %s", std::string(s).c_str());
708 break;
709 }
710 return format;
711 }
712
713 // size_t extractVertexAttribLocation_old(std::string shaderSource, Cogs::SemanticSlotBinding bindings[], size_t /*maxBindings*/) {
714 // std::istringstream iss(shaderSource);
715 // std::string expr = R"(\s*const ([^\s^0-9]+)([0-9]+)_LOC\s*=\s*([0-9]+))";
716 // std::regex regex_expression(expr);
717 // size_t nAttribs = 0;
718 // for (std::string line; std::getline(iss, line); )
719 // {
720 // std::smatch match;
721 // if (std::regex_search(line, match, regex_expression))
722 // {
723 // std::string semanticName = match.str(1);
724 // uint8_t slot = static_cast<uint8_t>(std::stoi(match.str(2)));
725 // uint8_t loc = static_cast<uint8_t>(std::stoi(match.str(3)));
726 // for (uint8_t i = 0; i < std::size(semanticNames); i++) {
727 // if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
728 // Cogs::SemanticSlotBinding& b = bindings[nAttribs++];
729 // b.semantic = i;
730 // b.slot = slot;
731 // b.binding = loc;
732 // break;
733 // }
734 // }
735 // }
736 // }
737 // return nAttribs;
738 // }
739
740 size_t extractVertexAttribLocation(std::string shaderSource, Cogs::SemanticSlotBinding bindings[], size_t /*maxBindings*/) {
741 std::istringstream iss(shaderSource);
742 std::string expr = R"(\s*const ([^\s^0-9]+)([0-9]+)_LOC\s*=\s*([0-9]+))";
743 std::regex regex_expression(expr);
744 size_t nAttribs = 0;
745 for (std::string line; std::getline(iss, line); )
746 {
747 std::smatch match;
748 if (std::regex_search(line, match, regex_expression))
749 {
750 std::string semanticName = match.str(1);
751 uint8_t slot = static_cast<uint8_t>(std::stoi(match.str(2)));
752 uint8_t loc = static_cast<uint8_t>(std::stoi(match.str(3)));
753 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
754 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
755 Cogs::SemanticSlotBinding& b = bindings[nAttribs++];
756 b.semantic = i;
757 b.slot = slot;
758 b.binding = loc;
759 break;
760 }
761 }
762 }
763 }
764
765 iss.clear();
766 iss.seekg(0, std::ios::beg);
767 std::string expr_location = R"(@location\‍(\s*([^\s\)\d]+)([\d]+)_LOC\s*\)\s*([^\s:]+)\s*:\s*([\da-zA-Z]+)[,\s\n])";
768 std::regex regex_location(expr_location);
769 for (std::string line; std::getline(iss, line); )
770 {
771 std::smatch match;
772 if (std::regex_search(line, match, regex_location))
773 {
774 std::string semanticName = match.str(1);
775 uint8_t slot = static_cast<uint8_t>(std::stoi(match.str(2)));
776 std::string name = match.str(3);
777 std::string type = match.str(4);
778 uint8_t semantic = std::numeric_limits<uint8_t>::max();
779 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
780 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
781 semantic = i;
782 break;
783 }
784 }
785 if (semantic == std::numeric_limits<uint8_t>::max()) {
786 LOG_DEBUG(logger, "unknown semantic name for vertex attribute %s", semanticName.c_str());
787 continue;
788 }
789 for (size_t i = 0; i < nAttribs; i++) {
790 if (bindings[i].semantic == semantic && bindings[i].slot == slot) {
791 bindings[i].format = stringToVertexFormat(Cogs::StringView(type));
792 bindings[i].nameHash = Cogs::hash(name);
793 break;
794 }
795 }
796 }
797 }
798 return nAttribs;
799 }
800
801 Cogs::ConstantBufferBindingHandle encodeConstantBufferBindingHandle(uint16_t groupIdx, uint16_t binding) {
802 size_t i = (static_cast<size_t>(groupIdx) << 16) + (binding + 1);
804 }
805
806 void decodeConstantBufferBindingHandle(Cogs::ConstantBufferBindingHandle handle, size_t& group, uint32_t& binding)
807 {
808 int64_t i = handle.handle;
809 group = static_cast<size_t>(i >> 16);
810 binding = static_cast<uint32_t>((i & 0xFFFF) - 1);
811 }
812
813 Cogs::BindingResourceType bindingResourceTypeFromWebGPU(const WGPUBindGroupLayoutEntry& entry)
814 {
815 if (entry.buffer.type != WGPUBufferBindingType_Undefined) {
816 return (entry.buffer.type == WGPUBufferBindingType_Uniform) ? Cogs::BindingResourceType::UniformBuffer : Cogs::BindingResourceType::StorageBuffer;
817 }
818 if (entry.texture.sampleType != WGPUTextureSampleType_Undefined) {
819 return Cogs::BindingResourceType::Texture;
820 }
821 if (entry.sampler.type != WGPUSamplerBindingType_Undefined) {
822 return Cogs::BindingResourceType::Sampler;
823 }
824 if (entry.storageTexture.access != WGPUStorageTextureAccess_Undefined) {
825 return Cogs::BindingResourceType::StorageTexture;
826 }
827 return Cogs::BindingResourceType::UniformBuffer;
828 }
829
830 Cogs::ResourceDimensions textureDimensionFromWebGPU(WGPUTextureViewDimension dim)
831 {
832 switch (dim) {
833 case WGPUTextureViewDimension_1D: return Cogs::ResourceDimensions::Texture1D;
834 case WGPUTextureViewDimension_2D: return Cogs::ResourceDimensions::Texture2D;
835 case WGPUTextureViewDimension_2DArray: return Cogs::ResourceDimensions::Texture2DArray;
836 case WGPUTextureViewDimension_3D: return Cogs::ResourceDimensions::Texture3D;
837 case WGPUTextureViewDimension_Cube:
838 case WGPUTextureViewDimension_CubeArray:
839 return Cogs::ResourceDimensions::TextureCube;
840 default:
841 return Cogs::ResourceDimensions::Unknown;
842 }
843 }
844
845 Cogs::BindingTextureSampleType textureSampleTypeFromWebGPU(WGPUTextureSampleType sampleType)
846 {
847 switch (sampleType) {
848 case WGPUTextureSampleType_Float: return Cogs::BindingTextureSampleType::Float;
849 case WGPUTextureSampleType_UnfilterableFloat: return Cogs::BindingTextureSampleType::UnfilterableFloat;
850 case WGPUTextureSampleType_Depth: return Cogs::BindingTextureSampleType::Depth;
851 case WGPUTextureSampleType_Sint: return Cogs::BindingTextureSampleType::Sint;
852 case WGPUTextureSampleType_Uint: return Cogs::BindingTextureSampleType::Uint;
853 default:
854 return Cogs::BindingTextureSampleType::Unknown;
855 }
856 }
857
858 Cogs::BindingSamplerBindingType samplerTypeFromWebGPU(WGPUSamplerBindingType samplerType)
859 {
860 switch (samplerType) {
861 case WGPUSamplerBindingType_Filtering: return Cogs::BindingSamplerBindingType::Filtering;
862 case WGPUSamplerBindingType_NonFiltering: return Cogs::BindingSamplerBindingType::NonFiltering;
863 case WGPUSamplerBindingType_Comparison: return Cogs::BindingSamplerBindingType::Comparison;
864 default:
865 return Cogs::BindingSamplerBindingType::Unknown;
866 }
867 }
868
869 bool bindGroupEntryEquals(const WGPUBindGroupEntry& a, const WGPUBindGroupEntry& b)
870 {
871 return a.binding == b.binding &&
872 a.buffer == b.buffer &&
873 a.offset == b.offset &&
874 a.size == b.size &&
875 a.sampler == b.sampler &&
876 a.textureView == b.textureView;
877 }
878
879 void dumpSource(const std::string& source, int firstLine = 1, int lastLine = std::numeric_limits<int>::max())
880 {
881 const char* start = source.data();
882 const char* curr = start;
883 int line = 1;
884
885 while ((*curr != '\0') && (line <= lastLine)) {
886 const char* p = curr;
887 while ((*p != '\0') && (*p != '\n') && (*p != '\r')) { p++; }
888
889 if (firstLine <= line) {
890 LOG_DEBUG(logger, "%3d: %s", line, std::string(curr, p - curr).c_str());
891 }
892
893 line = line + 1;
894
895 curr = p;
896 if (*curr != '\0') { curr++; };
897 if ((*curr != '\0') && (*curr != *p) && ((*curr == '\n') || (*curr == '\r'))) { curr++; }
898 }
899 }
900
901 void printShaderError(struct WGPUCompilationInfo const* compilationInfo, const std::string* source = nullptr)
902 {
903 for (size_t i = 0; i < compilationInfo->messageCount; i++) {
904 const WGPUCompilationMessage& message = compilationInfo->messages[i];
905 if (source) {
906 dumpSource(*source, ((int)message.lineNum) - 3, ((int)message.lineNum));
907 }
909 std::string category_str;
910 switch (message.type) {
911 case WGPUCompilationMessageType::WGPUCompilationMessageType_Info:
912 category = Cogs::Logging::Category::Info;
913 category_str = "Info";
914 break;
915 case WGPUCompilationMessageType::WGPUCompilationMessageType_Error:
916 category = Cogs::Logging::Category::Error;
917 category_str = "Error";
918 break;
919 case WGPUCompilationMessageType::WGPUCompilationMessageType_Force32:
920 category = Cogs::Logging::Category::Error;
921 category_str = "Force32";
922 break;
923 default:
924 category = Cogs::Logging::Category::Error;
925 category_str = "Unknown";
926 }
927
928 logger.log(category, Cogs::Logging::ErrorGroup::Unspecified, "WGSL %s (%" PRIu64 ", %" PRIu64 "): %.*s", category_str.c_str(), message.lineNum, message.linePos, WGPUStringViewFormat(message.message));
929 }
930 }
931
932 void compile_callback(WGPUCompilationInfoRequestStatus status, struct WGPUCompilationInfo const* compilationInfo, void* userdata1, void* /* userdata2 */) // Does not seem to be called if the shader does not compile
933 {
934 const std::string* source = reinterpret_cast<std::string*>(userdata1);
935 if (status != WGPUCompilationInfoRequestStatus_Success || (compilationInfo != nullptr && compilationInfo->messageCount != 0))
936 {
937 if(status == WGPUCompilationInfoRequestStatus_CallbackCancelled){
938 LOG_INFO(logger, "WGPUCompilationInfoRequestStatus_CallbackCancelled");
939 }
940 if(compilationInfo){
941 printShaderError(compilationInfo, source);
942 }
943 }
944 }
945}
946
947namespace Cogs{
948 void EffectsWebGPU::initialize(GraphicsDeviceWebGPU *device_in, IBuffers * buffers_in)
949 {
950 EffectsCommon::initialize(buffers_in);
951 graphicsDevice = device_in;
952 }
953
955 {
956 std::vector<EffectHandle> handles;
957 for (auto& resource : effects) {
958 handles.push_back(effects.getHandle(resource));
959 }
960 for (auto& handle : handles) {
961 releaseEffect(handle);
962 }
963 }
964
966 {
967 if (!HandleIsValid(handle)) return;
968
969 ResourceCountersWebGPU &counters = graphicsDevice->counters;
970 EffectWebGPU &effect = this->effects[handle];
971 if(effect.vs_module){
972 wgpuShaderModuleRelease(effect.vs_module);
973 counters.shader_module--;
974 }
975 if(effect.fs_module){
976 wgpuShaderModuleRelease(effect.fs_module);
977 counters.shader_module--;
978 }
979 if(effect.cs_module){
980 wgpuShaderModuleRelease(effect.cs_module);
981 counters.shader_module--;
982 }
983 this->effects.removeResource(handle);
984 }
985
987 {
988 PreprocessorDefinitions defines = {};
989 return loadComputeEffect(fileName, defines, effectFlags);
990 }
991
993 {
994 WGPUDevice device = graphicsDevice->device;
995 ResourceCountersWebGPU &counters = graphicsDevice->counters;
996
997 ProcessedContent csSource;
998 Utilities::readFile(handler, fileName, csSource);
999
1000 EffectWebGPU effect = {};
1001 effect.cs_entry = "main";
1002 effect.name = std::string(fileName);
1003
1004 std::string cs_source;
1005 for(auto &def : definitions){
1006 cs_source += "const " + def.first + " = " + def.second + ";\n";
1007 }
1008 cs_source += csSource.content;
1009
1011 LOG_INFO(logger, "Compiling WebGPU CS:\n%s", cs_source.c_str());
1012 }
1013
1014 WGPUShaderModuleDescriptor descriptor = WGPU_SHADER_MODULE_DESCRIPTOR_INIT;
1015 descriptor.label = {fileName.data(), WGPU_STRLEN};
1016 WGPUShaderSourceWGSL wgsl_desc = WGPU_SHADER_SOURCE_WGSL_INIT;
1017 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
1018 wgsl_desc.code = {cs_source.c_str(), WGPU_STRLEN};
1019 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
1020
1021 effect.cs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
1022 counters.shader_module++;
1023
1024
1025 // effect.bindGroupSetDescription = effect_desc.bindGroupLayoutDesc; // should open up for something here
1026 if (!extractBindingLayout(cs_source, Cogs::BindingVisibilityFlags::BindingVisibilityCompute, effect.bindGroupSetDescription)) {
1028 }
1029
1030
1031 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBinding(cs_source, WGPUShaderStage_Compute);
1032 addConstantBufferBindings(effect, bindings);
1033
1034 WGPUCompilationInfoCallbackInfo callbackInfo = WGPU_COMPILATION_INFO_CALLBACK_INFO_INIT;
1035 callbackInfo.nextInChain = nullptr;
1036 callbackInfo.callback = compile_callback;
1037 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
1038 callbackInfo.userdata1 = static_cast<void*>(&cs_source);
1039
1040 wgpuShaderModuleGetCompilationInfo(effect.cs_module, callbackInfo);
1041
1043 return this->effects.addResource(std::move(effect));
1044 }
1045
1046 EffectHandle EffectsWebGPU::load(const ProcessedContent& vsSource,
1047 const ProcessedContent& hsSource,
1048 const ProcessedContent& dsSource,
1049 const ProcessedContent& gsSource,
1050 const ProcessedContent& fsSource,
1051 const StringView& vsEntryPoint,
1052 const StringView& /*hsEntryPoint*/,
1053 const StringView& /*dsEntryPoint*/,
1054 const StringView& /*gsEntryPoint*/,
1055 const StringView& fsEntryPoint,
1056 const EffectDescription& effect_desc)
1057 {
1058 EffectFlags::EEffectFlags effectFlags = effect_desc.flags;
1059 bool useSortedDefines = false;
1060 PreprocessorDefinitions unique_definitions = effect_desc.definitions;
1061 std::sort(unique_definitions.begin(), unique_definitions.end());
1062 unique_definitions.erase(std::unique(unique_definitions.begin(), unique_definitions.end()), unique_definitions.end());
1063 if (unique_definitions.size() != effect_desc.definitions.size()) {
1064 useSortedDefines = true;
1065 LOG_WARNING(logger, "Redefinition of precompiler defines not supported in WebGPU backend");
1066 }
1067
1068 // WebGPU does only support vertex and pixel shaders
1069 assert(!hsSource.origin.size());
1070 assert(!dsSource.origin.size());
1071 assert(!gsSource.origin.size());
1072
1073 WGPUDevice device = graphicsDevice->device;
1074 ResourceCountersWebGPU &counters = graphicsDevice->counters;
1075
1076 EffectWebGPU effect = {};
1077 effect.vs_entry = std::string(vsEntryPoint);
1078 effect.fs_entry = std::string(fsEntryPoint);
1079 effect.name = std::string(effect_desc.name);
1081 LOG_INFO(logger, "Compiling WebGPU Effect: %s", effect.name.c_str());
1082 }
1083 std::string definitions_source;
1084 for (auto& def : useSortedDefines ? unique_definitions : effect_desc.definitions) {
1085 definitions_source += "const " + def.first + " = " + def.second + ";\n";
1086 }
1087
1088 bool hasBindingMetadata = effect_desc.bindGroupLayoutDesc.numGroups > 0;
1089
1090 if (effect_desc.bindGroupLayoutDesc.numGroups > 0) {
1091 effect.bindGroupSetDescription = effect_desc.bindGroupLayoutDesc;
1092 hasBindingMetadata = true;
1093 } else {
1094 LOG_INFO_ONCE(logger, "Effect %s missing binding metadata parsing shader source as fallback", effect.name.c_str());
1095 }
1096
1097 std::string vs_source = definitions_source + vsSource.content;
1098 {
1100 LOG_INFO(logger, "Compiling WebGPU VS:\n%s", vs_source.c_str());
1101 }
1102
1103 WGPUShaderModuleDescriptor descriptor = WGPU_SHADER_MODULE_DESCRIPTOR_INIT;
1104 descriptor.label = { effect_desc.name.data(), WGPU_STRLEN };
1105 WGPUShaderSourceWGSL wgsl_desc = WGPU_SHADER_SOURCE_WGSL_INIT;
1106 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
1107 wgsl_desc.code = { vs_source.c_str(), WGPU_STRLEN };
1108 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
1109
1110 effect.vs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
1111 counters.shader_module++;
1112
1113 WGPUCompilationInfoCallbackInfo callbackInfo = WGPU_COMPILATION_INFO_CALLBACK_INFO_INIT;
1114 callbackInfo.nextInChain = nullptr;
1115 callbackInfo.callback = compile_callback;
1116 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
1117 callbackInfo.userdata1 = static_cast<void*>(&vs_source); // TODO Fix this for Async
1118 wgpuShaderModuleGetCompilationInfo(effect.vs_module, callbackInfo);
1119
1120 // wgpuShaderModuleSetLabel(effect.vs_module, effect_desc.name.data());
1121 {
1122 effect.num_attribs = extractVertexAttribLocation(vs_source, effect.semanticSlotBindings, effect.maxVertexAttribs);
1123 }
1124
1125 if (!hasBindingMetadata) {
1126 if (!extractBindingLayout(vs_source, Cogs::BindingVisibilityFlags::BindingVisibilityVertex, effect.bindGroupSetDescription)) {
1128 }
1129 }
1130 }
1131 // TODO only create one module if they are equal?
1132 if (fsSource.content.size()) {
1133 std::string fs_source;
1134 fs_source = definitions_source + fsSource.content;
1135
1137 LOG_INFO(logger, "Compiling WebGPU FS:\n%s", fs_source.c_str());
1138 }
1139
1140 WGPUShaderModuleDescriptor descriptor = WGPU_SHADER_MODULE_DESCRIPTOR_INIT;
1141 descriptor.label = {effect_desc.name.data(), WGPU_STRLEN};
1142 WGPUShaderSourceWGSL wgsl_desc = WGPU_SHADER_SOURCE_WGSL_INIT;
1143 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
1144 wgsl_desc.code = {fs_source.c_str(), WGPU_STRLEN};
1145 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
1146
1147 effect.fs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
1148 counters.shader_module++;
1149
1150 WGPUCompilationInfoCallbackInfo callbackInfo = WGPU_COMPILATION_INFO_CALLBACK_INFO_INIT;
1151 callbackInfo.nextInChain = nullptr;
1152 callbackInfo.callback = compile_callback;
1153 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
1154 callbackInfo.userdata1 = static_cast<void*>(& fs_source);
1155 wgpuShaderModuleGetCompilationInfo(effect.fs_module, callbackInfo);
1156
1157 if (!hasBindingMetadata) {
1158 if (!extractBindingLayout(fs_source, Cogs::BindingVisibilityFlags::BindingVisibilityFragment, effect.bindGroupSetDescription)) {
1160 }
1161 }
1162 // wgpuShaderModuleSetLabel(effect.fs_module, effect_desc.name.data());
1163 }
1164
1165 std::vector<Cogs::WebGPUConstantBufferBinding> metadataBindings;
1166 if (!getWebGPUConstantBufferBindingsFromMetadata(&effect.bindGroupSetDescription, metadataBindings)) {
1168 }
1169
1170 if (!addConstantBufferBindings(effect, metadataBindings)) {
1172 }
1173
1174
1175
1176 effect.updateBindGroupDescHashes();
1177 return this->effects.addResource(std::move(effect));
1178 }
1180 {
1181 auto nameHash = Cogs::hash(name);
1182 const EffectWebGPU& effect = effects[effectHandle];
1183 for (uint16_t g = 0; g < effect.bindGroupSetDescription.numGroups; g++) {
1184 const BindGroupDescription& group = effect.bindGroupSetDescription.groups[g];
1185 for (uint16_t i = 0; i < group.numEntries; i++) {
1186 if (group.entries[i].nameHash == nameHash) {
1187 return encodeConstantBufferBindingHandle(g, i);
1188 }
1189 }
1190 }
1192 }
1193
1194 const BindGroupDescription& EffectsWebGPU::getBindGroupDescription(EffectHandle effectHandle, uint32_t groupIndex) {
1195 if (!HandleIsValid(effectHandle) || !effects.hasResource(effectHandle)) {
1196 return IEffects::getBindGroupDescription(effectHandle, groupIndex); //empty description
1197 }
1198 const EffectWebGPU& effect = effects[effectHandle];
1199 if (groupIndex >= effect.bindGroupSetDescription.numGroups) {
1200 return IEffects::getBindGroupDescription(effectHandle, groupIndex); //empty description
1201 }
1202 return effect.bindGroupSetDescription.groups[groupIndex];
1203 }
1204
1206 static const size_t emptyBindGroupDescHash = BindGroupDescription().hash();
1207
1208 size_t EffectsWebGPU::getBindGroupDescHash(EffectHandle effectHandle, uint32_t groupIndex) {
1209 if (!HandleIsValid(effectHandle) || !effects.hasResource(effectHandle)) {
1210 return emptyBindGroupDescHash;
1211 }
1212 const EffectWebGPU& effect = effects[effectHandle];
1213 if (groupIndex >= effect.bindGroupSetDescription.numGroups) {
1214 return emptyBindGroupDescHash;
1215 }
1216 return effect.bindGroupDescHash[groupIndex];
1217 }
1218
1220 {
1221 ConstantBufferBindingHandle constantBuffer = getConstantBufferBinding(effectHandle, name);
1222 if (HandleIsValid(constantBuffer)) {
1223 return BufferBindingHandle(constantBuffer.handle);
1224 }
1226 }
1227
1228 bool EffectsWebGPU::addConstantBufferBindings(EffectWebGPU& effect, const std::vector<Cogs::WebGPUConstantBufferBinding>& bindings) {
1229 for (auto binding : bindings) {
1230 size_t slot;
1231 bool exists = false;
1232 for (slot = 0; slot < effect.num_bindings; slot++) { // Does not report error if maxConstantBuffers is exceeded
1233 auto& curr = effect.constantBufferBindings[slot];
1234 if (curr.nameHash == binding.nameHash) {
1235 if (curr.group == binding.group && curr.bg_ent.binding == binding.bg_ent.binding) {
1236 curr.bg_ent.visibility = curr.bg_ent.visibility | binding.bg_ent.visibility;
1237 exists = true;
1238 }
1239 else {
1240 LOG_ERROR(logger, "Inconsistent binding location");
1241 }
1242 }
1243 }
1244 if (exists) {
1245 continue;
1246 }
1247 if (effect.num_bindings < EffectWebGPU::maxConstantBuffers) {
1248 effect.constantBufferBindings[effect.num_bindings++] = binding;
1249 }
1250 else {
1251 LOG_ERROR(logger, "Number of bindings exceed EffectWebGPU::maxConstantBuffers=%zu", EffectWebGPU::maxConstantBuffers);
1252 return false;
1253 }
1254 }
1255 return true;
1256 }
1257
1258
1259 TextureBindingHandle EffectsWebGPU::getTextureBinding(EffectHandle effectHandle, const StringView& name, const unsigned int /*slot*/) {
1260 std::string constantBufferName = std::string(name);
1261 ConstantBufferBindingHandle constantBuffer = getConstantBufferBinding(effectHandle, StringView(constantBufferName));
1262 if (HandleIsValid(constantBuffer)) {
1263 return TextureBindingHandle(constantBuffer.handle);
1264 }
1266 }
1267
1269 std::string constantBufferName = std::string(name);
1270 if (!constantBufferName.ends_with("Sampler")) {
1271 constantBufferName += "Sampler";
1272 }
1273
1274 ConstantBufferBindingHandle constantBuffer = getConstantBufferBinding(effectHandle, StringView(constantBufferName));
1275 if (HandleIsValid(constantBuffer)) {
1276 return SamplerStateBindingHandle(constantBuffer.handle);
1277 }
1278
1279 if (constantBufferName.ends_with("TextureSampler")) { // Try again with shortened name
1280 constantBufferName.erase(constantBufferName.length() - strlen("TextureSampler"));
1281 StringView shortenedName(constantBufferName);
1282 return getSamplerStateBinding(effectHandle, shortenedName, slot);
1283 }
1284
1286 }
1287
1288 /*
1289 bool EffectsWebGPU::getBindGroupLayout(EffectHandle effectHandle, uint32_t groupIndex, BindGroupLayout& layoutOutput)
1290 {
1291 if (!HandleIsValid(effectHandle) || !effects.hasResource(effectHandle)) {
1292 return false;
1293 }
1294
1295 const EffectWebGPU& effect = effects[effectHandle];
1296 layoutOutput = {};
1297
1298 if (groupIndex < effect.bindGrouplayouts.groups.size()) {
1299 layoutOutput = effect.bindGrouplayouts.groups[0];
1300 return true;
1301 }
1302
1303 for (size_t i = 0; i < effect.num_bindings; ++i) {
1304 const auto& binding = effect.constantBufferBindings[i];
1305 if (binding.group != groupIndex) {
1306 continue;
1307 }
1308
1309 BindGroupLayoutEntry entry = {};
1310 entry.binding = binding.bg_ent.binding;
1311 entry.name = binding.name;
1312 entry.nameHash = binding.nameHash;
1313 entry.resourceType = bindingResourceTypeFromWebGPU(binding.bg_ent);
1314 entry.visibility = static_cast<uint32_t>(binding.bg_ent.visibility);
1315 entry.arrayCount = 1;
1316 entry.textureDimension = textureDimensionFromWebGPU(binding.bg_ent.texture.viewDimension);
1317 entry.textureSampleType = textureSampleTypeFromWebGPU(binding.bg_ent.texture.sampleType);
1318 entry.samplerBindingType = samplerTypeFromWebGPU(binding.bg_ent.sampler.type);
1319 entry.isDepthTexture = (binding.bg_ent.texture.sampleType == WGPUTextureSampleType_Depth);
1320 entry.multisampled = binding.bg_ent.texture.multisampled;
1321 entry.hasDynamicOffset = binding.bg_ent.buffer.hasDynamicOffset;
1322
1323 layoutOutput.entries.push_back(std::move(entry));
1324 }
1325
1326 return !layoutOutput.entries.empty();
1327 }
1328 */
1329
1331 if (!HandleIsValid(effectHandle) || !effects.hasResource(effectHandle)) {
1332 return 0;
1333 }
1334 const EffectWebGPU& effect = effects[effectHandle];
1335 return effect.bindGroupSetDescription.numGroups;
1336 }
1337}
uint32_t getNumBindGroups(EffectHandle effectHandle) override
Query the number of bind-group layouts exposed by the given effect.
TextureBindingHandle getTextureBinding(EffectHandle effectHandle, const StringView &name, const unsigned int slot) override
Get a handle to a texture object binding, mapping how to bind textures to the given effect.
virtual void releaseEffect(EffectHandle effectHandle) override
Release the effect with the given handle, freeing all resources generated during program loading.
BufferBindingHandle getBufferBinding(EffectHandle effectHandle, const StringView &name) override
Get a handle to a buffer binding.
virtual EffectHandle loadComputeEffect(const StringView &, EffectFlags::EEffectFlags) override
Load the compute shader with the given file name and create an effect.
ConstantBufferBindingHandle getConstantBufferBinding(EffectHandle effectHandle, const StringView &name) override
Get a handle to a constant buffer binding, mapping how to bind a constant buffer to the given effect.
virtual void releaseResources() override
Release all allocated effect resources.
SamplerStateBindingHandle getSamplerStateBinding(EffectHandle effectHandle, const StringView &, const unsigned int slot) override
Get a handle to a sampler state object binding, mapping how to bind the sampler state to the given ef...
size_t getBindGroupDescHash(EffectHandle effectHandle, uint32_t groupIndex)
Cached hash of the description returned by getBindGroupDescription, matching it for out-of-range grou...
Log implementation class.
Definition: LogManager.h:140
void log(const Category category, uint32_t errorNumber, _Printf_format_string_ const char *fmt,...) const VALIDATE_ARGS(4)
Log a formatted message.
Definition: LogManager.h:157
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
constexpr const char * data() const noexcept
Get the sequence of characters referenced by the string view.
Definition: StringView.h:197
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
@ Unspecified
The default error number for legacy logger usage.
Definition: LogManager.h:49
Category
Logging categories used to filter log messages.
Definition: LogManager.h:31
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
std::vector< PreprocessorDefinition > PreprocessorDefinitions
A set of preprocessor definitions.
Definition: IEffects.h:20
Contains an effect description used to load a single effect.
Definition: IEffects.h:62
EffectFlags::EEffectFlags flags
Effect loading flags.
Definition: IEffects.h:106
BindGroupSetDescription bindGroupLayoutDesc
Optional bind-group layout metadata. Backends may use this instead of source introspection.
Definition: IEffects.h:103
PreprocessorDefinitions definitions
Definitions.
Definition: IEffects.h:100
StringView name
Name of the effect. Used for tracking purposes, like naming shader dumps.
Definition: IEffects.h:97
EEffectFlags
Effect source flags.
Definition: IEffects.h:27
@ LogShaderSource
Log the contents of the shader on error.
Definition: IEffects.h:41
void updateBindGroupDescHashes()
Refresh bindGroupDescHash. Call once bindGroupSetDescription is final.
Definition: EffectsWebGPU.h:56
size_t bindGroupDescHash[MaxBindGroups]
Hash of each entry in bindGroupSetDescription.groups, letting bind group compatibility be tested with...
Definition: EffectsWebGPU.h:53
static const Handle_t NoHandle
Represents a handle to nothing.
Definition: Common.h:78
handle_type handle
Internal resource handle.
Definition: Common.h:75