1#include "EffectsWebGPU.h"
3#include "GraphicsDeviceWebGPU.h"
5#include "Foundation/Logging/Logger.h"
15 bool starts_with(std::string::const_iterator begin, std::string::const_iterator end,
const char* s, std::string::const_iterator& next) {
16 std::string::const_iterator it = begin;
18 while (it != end && *s !=
'\0') {
32 void eat_white(std::string::const_iterator& it, std::string::const_iterator end) {
33 while (it != end && std::isspace(*it)) {
38 WGPUTextureViewDimension extractViewDimention(std::string::const_iterator& type_it, std::string::const_iterator end) {
39 WGPUTextureViewDimension tvd = WGPUTextureViewDimension_Undefined;
40 if (starts_with(type_it, end,
"2d_array", type_it)) {
41 tvd = WGPUTextureViewDimension_2DArray;
43 else if (starts_with(type_it, end,
"2d", type_it)) {
44 tvd = WGPUTextureViewDimension_2D;
46 else if (starts_with(type_it, end,
"cube", type_it)) {
47 tvd = WGPUTextureViewDimension_Cube;
49 else if (starts_with(type_it, end,
"1d", type_it)) {
50 tvd = WGPUTextureViewDimension_1D;
52 else if (starts_with(type_it, end,
"cube_array", type_it)) {
53 tvd = WGPUTextureViewDimension_CubeArray;
55 else if (starts_with(type_it, end,
"3d", type_it)) {
56 tvd = WGPUTextureViewDimension_3D;
61 WGPUTextureFormat extractTextureFormat(std::string::const_iterator& type_it, std::string::const_iterator end) {
62 WGPUTextureFormat tf = WGPUTextureFormat_Undefined;
63 if (starts_with(type_it, end,
"rgba8unorm", type_it)) {
64 tf = WGPUTextureFormat_RGBA8Unorm;
66 else if (starts_with(type_it, end,
"rgba8snorm", type_it)) {
67 tf = WGPUTextureFormat_RGBA8Snorm;
69 else if (starts_with(type_it, end,
"rgba8uint", type_it)) {
70 tf = WGPUTextureFormat_RGBA8Uint;
72 else if (starts_with(type_it, end,
"rgba8sint", type_it)) {
73 tf = WGPUTextureFormat_RGBA8Sint;
75 else if (starts_with(type_it, end,
"rgba16uint", type_it)) {
76 tf = WGPUTextureFormat_RGBA16Uint;
78 else if (starts_with(type_it, end,
"rgba16sint", type_it)) {
79 tf = WGPUTextureFormat_RGBA16Sint;
81 else if (starts_with(type_it, end,
"rgba16float", type_it)) {
82 tf = WGPUTextureFormat_RGBA16Float;
84 else if (starts_with(type_it, end,
"r32uint", type_it)) {
85 tf = WGPUTextureFormat_R32Uint;
87 else if (starts_with(type_it, end,
"r32sint", type_it)) {
88 tf = WGPUTextureFormat_R32Sint;
90 else if (starts_with(type_it, end,
"r32float", type_it)) {
91 tf = WGPUTextureFormat_R32Float;
93 else if (starts_with(type_it, end,
"rg32uint", type_it)) {
94 tf = WGPUTextureFormat_RG32Uint;
96 else if (starts_with(type_it, end,
"rg32sint", type_it)) {
97 tf = WGPUTextureFormat_RG32Sint;
99 else if (starts_with(type_it, end,
"rg32float", type_it)) {
100 tf = WGPUTextureFormat_RG32Float;
102 else if (starts_with(type_it, end,
"rgba32uint", type_it)) {
103 tf = WGPUTextureFormat_RGBA32Uint;
105 else if (starts_with(type_it, end,
"rgba32sint", type_it)) {
106 tf = WGPUTextureFormat_RGBA32Sint;
108 else if (starts_with(type_it, end,
"rgba32float", type_it)) {
109 tf = WGPUTextureFormat_RGBA32Float;
111 else if (starts_with(type_it, end,
"bgra8unorm", type_it)) {
112 tf = WGPUTextureFormat_BGRA8Unorm;
117 WGPUStorageTextureAccess extractStorageTextureAccess(std::string::const_iterator& type_it, std::string::const_iterator end) {
118 WGPUStorageTextureAccess sta = WGPUStorageTextureAccess_Undefined;
119 if (starts_with(type_it, end,
"read", type_it)) {
120 sta = WGPUStorageTextureAccess_ReadOnly;
122 else if (starts_with(type_it, end,
"write", type_it)) {
123 sta = WGPUStorageTextureAccess_WriteOnly;
125 else if (starts_with(type_it, end,
"readwrite", type_it)) {
126 sta = WGPUStorageTextureAccess_ReadWrite;
131 std::vector<Cogs::WebGPUConstantBufferBinding> extractConstantBindingLayout(std::string shaderSource, WGPUShaderStage usage) {
132 std::vector<Cogs::WebGPUConstantBufferBinding> result;
133 std::istringstream iss(shaderSource);
134 std::string expr = R
"(^\s*@group\(([0-9]+)\) @binding\(([0-9]+)\)\s+var(<uniform>)?\s+([^\s]+)[\s]*:\s?(.*)\s?;)";
135 std::regex regex_expression(expr);
137 for (std::string line; std::getline(iss, line); )
140 if (std::regex_search(line, match, regex_expression))
142 size_t group = std::stoi(match.str(1));
143 unsigned int loc = std::stoi(match.str(2));
144 bool isUniform = match.str(3) ==
"<uniform>";
145 std::string name = match.str(4);
146 std::string type = match.str(5);
148 std::string::const_iterator type_it = type.begin();
152 bool alreadyInserted =
false;
153 for (
auto& e : result) {
154 if (e.group == group && e.bg_ent.binding == loc) {
155 e.bg_ent.visibility |= usage;
156 alreadyInserted =
true;
160 if (alreadyInserted) {
163 WGPUBindGroupLayoutEntry bg_ent = {};
164 bg_ent.binding =
static_cast<uint32_t
>(loc);
165 bg_ent.visibility = (WGPUShaderStage)usage;
168 bg_ent.buffer.type = WGPUBufferBindingType_Uniform;
171 else if (starts_with(type_it, type.end(),
"texture_storage_", type_it)) {
172 bg_ent.storageTexture.viewDimension = extractViewDimention(type_it, type.end());
173 eat_white(type_it, type.end());
174 if (!starts_with(type_it, type.end(),
"<", type_it)) {
175 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
177 bg_ent.storageTexture.format = extractTextureFormat(type_it, type.end());
178 eat_white(type_it, type.end());
179 if (!starts_with(type_it, type.end(),
",", type_it)) {
180 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
182 eat_white(type_it, type.end());
183 bg_ent.storageTexture.access = extractStorageTextureAccess(type_it, type.end());
184 eat_white(type_it, type.end());
185 if (!starts_with(type_it, type.end(),
">", type_it)) {
186 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
189 else if (starts_with(type_it, type.end(),
"texture_", type_it)) {
190 bool isDepth =
false;
192 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
193 bg_ent.texture.viewDimension = WGPUTextureViewDimension_2D;
194 bg_ent.texture.multisampled = 0;
195 if (starts_with(type_it, type.end(),
"depth_", type_it)) {
198 bg_ent.texture.viewDimension = extractViewDimention(type_it, type.end());
199 eat_white(type_it, type.end());
201 bg_ent.texture.sampleType = WGPUTextureSampleType_Depth;
202 }
else if (starts_with(type_it, type.end(),
"<f32>", type_it)) {
203 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
205 else if (starts_with(type_it, type.end(),
"<i32>", type_it)) {
206 bg_ent.texture.sampleType = WGPUTextureSampleType_Sint;
208 else if (starts_with(type_it, type.end(),
"<u32>", type_it)) {
209 bg_ent.texture.sampleType = WGPUTextureSampleType_Uint;
212 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
215 else if (type ==
"sampler") {
217 bg_ent.sampler.type = WGPUSamplerBindingType_Filtering;
219 else if (type ==
"sampler_comparison") {
221 bg_ent.sampler.type = WGPUSamplerBindingType_Comparison;
224 LOG_DEBUG(logger,
"Unknown uniform type%s", type.c_str());
228 result.push_back(binding);
234 const char* semanticNames[]
246 WGPUVertexFormat format =
static_cast<WGPUVertexFormat
>(0);
249 case Cogs::hash(
"f32"): format = WGPUVertexFormat::WGPUVertexFormat_Float32;
break;
250 case Cogs::hash(
"vec2f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x2;
break;
251 case Cogs::hash(
"vec3f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x3;
break;
252 case Cogs::hash(
"vec4f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x4;
break;
253 case Cogs::hash(
"i32"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32;
break;
254 case Cogs::hash(
"vec2i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x2;
break;
255 case Cogs::hash(
"vec3i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x3;
break;
256 case Cogs::hash(
"vec4i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x4;
break;
257 case Cogs::hash(
"u32"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32;
break;
258 case Cogs::hash(
"vec2u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x2;
break;
259 case Cogs::hash(
"vec3u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x3;
break;
260 case Cogs::hash(
"vec4u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x4;
break;
262 LOG_ERROR(logger,
"Unsupported vertex attribute type %s", std::string(s).c_str());
296 std::istringstream iss(shaderSource);
297 std::string expr = R
"(\s*const ([^\s^0-9]+)([0-9]+)_LOC\s*=\s*([0-9]+))";
298 std::regex regex_expression(expr);
300 for (std::string line; std::getline(iss, line); )
303 if (std::regex_search(line, match, regex_expression))
305 std::string semanticName = match.str(1);
306 uint8_t slot =
static_cast<uint8_t
>(std::stoi(match.str(2)));
307 uint8_t loc =
static_cast<uint8_t
>(std::stoi(match.str(3)));
308 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
309 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
321 iss.seekg(0, std::ios::beg);
322 std::string expr_location = R
"(@location\(\s*([^\s\)\d]+)([\d]+)_LOC\s*\)\s*([^\s:]+)\s*:\s*([\da-zA-Z]+)[,\s\n])";
323 std::regex regex_location(expr_location);
324 for (std::string line; std::getline(iss, line); )
327 if (std::regex_search(line, match, regex_location))
329 std::string semanticName = match.str(1);
330 uint8_t slot =
static_cast<uint8_t
>(std::stoi(match.str(2)));
331 std::string name = match.str(3);
332 std::string type = match.str(4);
333 uint8_t semantic = std::numeric_limits<uint8_t>::max();
334 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
335 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
340 if (semantic == std::numeric_limits<uint8_t>::max()) {
341 LOG_DEBUG(logger,
"unknown semantic name for vertex attribute %s", semanticName.c_str());
344 for (
size_t i = 0; i < nAttribs; i++) {
345 if (bindings[i].semantic == semantic && bindings[i].slot == slot) {
357 size_t i = (binding.group << 16) + (binding.bg_ent.binding + 1 );
361 void dumpSource(
const std::string& source,
int firstLine = 1,
int lastLine = std::numeric_limits<int>::max())
363 const char* start = source.data();
364 const char* curr = start;
367 while ((*curr !=
'\0') && (line <= lastLine)) {
368 const char* p = curr;
369 while ((*p !=
'\0') && (*p !=
'\n') && (*p !=
'\r')) { p++; }
371 if (firstLine <= line) {
372 LOG_DEBUG(logger,
"%3d: %s", line, std::string(curr, p - curr).c_str());
378 if (*curr !=
'\0') { curr++; };
379 if ((*curr !=
'\0') && (*curr != *p) && ((*curr ==
'\n') || (*curr ==
'\r'))) { curr++; }
383 void printShaderError(
struct WGPUCompilationInfo
const* compilationInfo,
const std::string* source =
nullptr)
385 for (
size_t i = 0; i < compilationInfo->messageCount; i++) {
386 const WGPUCompilationMessage& message = compilationInfo->messages[i];
388 dumpSource(*source, ((
int)message.lineNum) - 3, ((
int)message.lineNum));
391 std::string category_str;
392 switch (message.type) {
393 case WGPUCompilationMessageType::WGPUCompilationMessageType_Info:
394 category = Cogs::Logging::Category::Info;
395 category_str =
"Info";
397 case WGPUCompilationMessageType::WGPUCompilationMessageType_Error:
398 category = Cogs::Logging::Category::Error;
399 category_str =
"Error";
401 case WGPUCompilationMessageType::WGPUCompilationMessageType_Force32:
402 category = Cogs::Logging::Category::Error;
403 category_str =
"Force32";
406 category = Cogs::Logging::Category::Error;
407 category_str =
"Unknown";
414 void compile_callback(WGPUCompilationInfoRequestStatus status,
struct WGPUCompilationInfo
const* compilationInfo,
void* userdata1,
void* )
416 const std::string* source =
reinterpret_cast<std::string*
>(userdata1);
417 if (status != WGPUCompilationInfoRequestStatus_Success || (compilationInfo !=
nullptr && compilationInfo->messageCount != 0))
419 printShaderError(compilationInfo, source);
425 void EffectsWebGPU::initialize(GraphicsDeviceWebGPU *device_in, IBuffers * buffers_in)
427 EffectsCommon::initialize(buffers_in);
428 graphicsDevice = device_in;
437 if (!HandleIsValid(handle))
return;
439 wgpuShaderModuleRelease(effect.vs_module);
440 wgpuShaderModuleRelease(effect.fs_module);
441 this->effects.removeResource(handle);
452 WGPUDevice device = graphicsDevice->device;
455 Utilities::readFile(handler, fileName, csSource);
458 effect.cs_entry =
"main";
459 effect.name = std::string(fileName);
461 std::string cs_source;
462 for(
auto &def : definitions){
463 cs_source +=
"const " + def.first +
" = " + def.second +
";\n";
465 cs_source += csSource.content;
468 LOG_INFO(logger,
"Compiling WebGPU CS:\n%s", cs_source.c_str());
471 WGPUShaderModuleDescriptor descriptor = {};
472 descriptor.label = {fileName.data(), WGPU_STRLEN};
473 WGPUShaderSourceWGSL wgsl_desc = {};
474 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
475 wgsl_desc.code = {cs_source.c_str(), WGPU_STRLEN};
476 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
478 effect.cs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
481 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(cs_source, WGPUShaderStage_Compute);
482 addConstantBufferBindings(effect, bindings);
484 WGPUCompilationInfoCallbackInfo callbackInfo = {};
485 callbackInfo.nextInChain =
nullptr;
486 callbackInfo.callback = compile_callback;
487 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
488 callbackInfo.userdata1 =
static_cast<void*
>(&cs_source);
490 wgpuShaderModuleGetCompilationInfo(effect.cs_module, callbackInfo);
492 return this->effects.addResource(std::move(effect));
508 bool useSortedDefines =
false;
510 std::sort(unique_definitions.begin(), unique_definitions.end());
511 unique_definitions.erase(std::unique(unique_definitions.begin(), unique_definitions.end()), unique_definitions.end());
512 if (unique_definitions.size() != effect_desc.
definitions.size()) {
513 useSortedDefines =
true;
514 LOG_WARNING(logger,
"Redefinition of precompiler defines not supported in WebGPU backend");
518 assert(!hsSource.origin.size());
519 assert(!dsSource.origin.size());
520 assert(!gsSource.origin.size());
522 WGPUDevice device = graphicsDevice->device;
524 EffectWebGPU effect = {};
525 effect.vs_entry = std::string(vsEntryPoint);
526 effect.fs_entry = std::string(fsEntryPoint);
527 effect.name = std::string(effect_desc.
name);
529 LOG_INFO(logger,
"Compiling WebGPU Effect: %s", effect.name.c_str());
531 std::string definitions_source;
532 for (
auto& def : useSortedDefines ? unique_definitions : effect_desc.
definitions) {
533 definitions_source +=
"const " + def.first +
" = " + def.second +
";\n";
535 std::string vs_source = definitions_source + vsSource.content;
538 LOG_INFO(logger,
"Compiling WebGPU VS:\n%s", vs_source.c_str());
541 WGPUShaderModuleDescriptor descriptor = {};
542 descriptor.label = {effect_desc.
name.
data(), WGPU_STRLEN};
543 WGPUShaderSourceWGSL wgsl_desc = {};
544 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
545 wgsl_desc.code = {vs_source.c_str(), WGPU_STRLEN};
546 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
548 effect.vs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
551 WGPUCompilationInfoCallbackInfo callbackInfo = {};
552 callbackInfo.nextInChain =
nullptr;
553 callbackInfo.callback = compile_callback;
554 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
555 callbackInfo.userdata1 =
static_cast<void*
>(& vs_source);
556 wgpuShaderModuleGetCompilationInfo(effect.vs_module, callbackInfo);
559 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(vs_source, WGPUShaderStage_Vertex);
560 addConstantBufferBindings(effect, bindings);
562 effect.num_attribs = extractVertexAttribLocation(vs_source, effect.semanticSlotBindings, effect.maxVertexAttribs);
566 if (fsSource.content.size()) {
567 std::string fs_source;
568 fs_source = definitions_source + fsSource.content;
571 LOG_INFO(logger,
"Compiling WebGPU FS:\n%s", fs_source.c_str());
574 WGPUShaderModuleDescriptor descriptor = {};
575 descriptor.label = {effect_desc.
name.
data(), WGPU_STRLEN};
576 WGPUShaderSourceWGSL wgsl_desc = {};
577 wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
578 wgsl_desc.code = {fs_source.c_str(), WGPU_STRLEN};
579 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
581 effect.fs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
584 WGPUCompilationInfoCallbackInfo callbackInfo = {};
585 callbackInfo.nextInChain =
nullptr;
586 callbackInfo.callback = compile_callback;
587 callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
588 callbackInfo.userdata1 =
static_cast<void*
>(& fs_source);
589 wgpuShaderModuleGetCompilationInfo(effect.fs_module, callbackInfo);
592 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(fs_source, WGPUShaderStage_Fragment);
593 addConstantBufferBindings(effect, bindings);
595 return this->effects.addResource(std::move(effect));
602 for (
size_t i = 0; i < effect.num_bindings; i++) {
603 if (effect.constantBufferBindings[i].nameHash == nameHash) {
604 return encodeConstantBufferBindingHandle(effect.constantBufferBindings[i]);
614 if (HandleIsValid(constantBuffer)) {
620 bool EffectsWebGPU::addConstantBufferBindings(
EffectWebGPU& effect,
const std::vector<Cogs::WebGPUConstantBufferBinding>& bindings) {
621 for (
auto binding : bindings) {
624 for (slot = 0; slot < effect.num_bindings; slot++) {
625 auto& curr = effect.constantBufferBindings[slot];
626 if (curr.nameHash == binding.nameHash) {
627 if (curr.group == binding.group && curr.bg_ent.binding == binding.bg_ent.binding) {
628 curr.bg_ent.visibility = curr.bg_ent.visibility | binding.bg_ent.visibility;
632 LOG_ERROR(logger,
"Inconsistent binding location");
639 if (effect.num_bindings < EffectWebGPU::maxConstantBuffers) {
640 effect.constantBufferBindings[effect.num_bindings++] = binding;
643 LOG_ERROR(logger,
"Number of bindings exceed EffectWebGPU::maxConstantBuffers=%zu", EffectWebGPU::maxConstantBuffers);
651 const WebGPUConstantBufferBinding* EffectsWebGPU::getConstantBufferBindings(EffectHandle effectHandle,
size_t& num_bindings) {
652 const EffectWebGPU& effect = effects[effectHandle];
653 num_bindings = effect.num_bindings;
654 return effect.constantBufferBindings;
658 std::string constantBufferName = std::string(name);
660 if (HandleIsValid(constantBuffer)) {
667 std::string constantBufferName = std::string(name);
668 if (!constantBufferName.ends_with(
"Sampler")) {
669 constantBufferName +=
"Sampler";
673 if (HandleIsValid(constantBuffer)) {
677 if (constantBufferName.ends_with(
"TextureSampler")) {
678 constantBufferName.erase(constantBufferName.length() - strlen(
"TextureSampler"));
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...
Log implementation class.
void log(const Category category, uint32_t errorNumber, _Printf_format_string_ const char *fmt,...) const VALIDATE_ARGS(4)
Log a formatted message.
Provides a weakly referenced view over the contents of a string.
constexpr const char * data() const noexcept
Get the sequence of characters referenced by the string view.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
@ Unspecified
The default error number for legacy logger usage.
Category
Logging categories used to filter log messages.
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
std::vector< PreprocessorDefinition > PreprocessorDefinitions
A set of preprocessor definitions.
Contains an effect description used to load a single effect.
EffectFlags::EEffectFlags flags
Effect loading flags.
PreprocessorDefinitions definitions
Definitions.
StringView name
Name of the effect. Used for tracking purposes, like naming shader dumps.
EEffectFlags
Effect source flags.
@ LogShaderSource
Log the contents of the shader on error.
static const Handle_t NoHandle
Represents a handle to nothing.
handle_type handle
Internal resource handle.