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)) {
123 sta = WGPUStorageTextureAccess_ReadOnly;
126 else if (starts_with(type_it, end,
"write", type_it)) {
127 sta = WGPUStorageTextureAccess_WriteOnly;
129 else if (starts_with(type_it, end,
"readwrite", type_it)) {
133 sta = WGPUStorageTextureAccess_ReadWrite;
139 std::vector<Cogs::WebGPUConstantBufferBinding> extractConstantBindingLayout(std::string shaderSource, Cogs::WebGPUConstantBufferUsage usage) {
140 std::vector<Cogs::WebGPUConstantBufferBinding> result;
141 std::istringstream iss(shaderSource);
142 std::string expr = R
"(^\s*@group\(([0-9]+)\) @binding\(([0-9]+)\)\s+var(<uniform>)?\s+([^\s]+)[\s]*:\s?(.*)\s?;)";
143 std::regex regex_expression(expr);
145 for (std::string line; std::getline(iss, line); )
148 if (std::regex_search(line, match, regex_expression))
150 size_t group = std::stoi(match.str(1));
151 unsigned int loc = std::stoi(match.str(2));
152 bool isUniform = match.str(3) ==
"<uniform>";
153 std::string name = match.str(4);
154 std::string type = match.str(5);
156 std::string::const_iterator type_it = type.begin();
160 bool alreadyInserted =
false;
161 for (
auto& e : result) {
162 if (e.group == group && e.bg_ent.binding == loc) {
163 e.bg_ent.visibility |= usage;
164 alreadyInserted =
true;
168 if (alreadyInserted) {
171 WGPUBindGroupLayoutEntry bg_ent = {};
172 bg_ent.binding =
static_cast<uint32_t
>(loc);
173 bg_ent.visibility = (WGPUShaderStageFlags)usage;
176 bg_ent.buffer.type = WGPUBufferBindingType_Uniform;
179 else if (starts_with(type_it, type.end(),
"texture_storage_", type_it)) {
180 bg_ent.storageTexture.viewDimension = extractViewDimention(type_it, type.end());
181 eat_white(type_it, type.end());
182 if (!starts_with(type_it, type.end(),
"<", type_it)) {
183 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
185 bg_ent.storageTexture.format = extractTextureFormat(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());
190 eat_white(type_it, type.end());
191 bg_ent.storageTexture.access = extractStorageTextureAccess(type_it, type.end());
192 eat_white(type_it, type.end());
193 if (!starts_with(type_it, type.end(),
">", type_it)) {
194 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
197 else if (starts_with(type_it, type.end(),
"texture_", type_it)) {
198 bool isDepth =
false;
200 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
201 bg_ent.texture.viewDimension = WGPUTextureViewDimension_2D;
202 bg_ent.texture.multisampled = 0;
203 if (starts_with(type_it, type.end(),
"depth_", type_it)) {
206 bg_ent.texture.viewDimension = extractViewDimention(type_it, type.end());
207 eat_white(type_it, type.end());
209 bg_ent.texture.sampleType = WGPUTextureSampleType_Depth;
210 }
else if (starts_with(type_it, type.end(),
"<f32>", type_it)) {
211 bg_ent.texture.sampleType = WGPUTextureSampleType_Float;
213 else if (starts_with(type_it, type.end(),
"<i32>", type_it)) {
214 bg_ent.texture.sampleType = WGPUTextureSampleType_Sint;
216 else if (starts_with(type_it, type.end(),
"<u32>", type_it)) {
217 bg_ent.texture.sampleType = WGPUTextureSampleType_Uint;
220 LOG_DEBUG(logger,
"Texture type %s not yet supported", type.c_str());
223 else if (type ==
"sampler") {
225 bg_ent.sampler.type = WGPUSamplerBindingType_Filtering;
227 else if (type ==
"sampler_comparison") {
229 bg_ent.sampler.type = WGPUSamplerBindingType_Comparison;
232 LOG_DEBUG(logger,
"Unknown uniform type%s", type.c_str());
236 result.push_back(binding);
242 const char* semanticNames[]
254 WGPUVertexFormat format = WGPUVertexFormat::WGPUVertexFormat_Undefined;
257 case Cogs::hash(
"float"): format = WGPUVertexFormat::WGPUVertexFormat_Float32;
break;
258 case Cogs::hash(
"vec2f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x2;
break;
259 case Cogs::hash(
"vec3f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x3;
break;
260 case Cogs::hash(
"vec4f"): format = WGPUVertexFormat::WGPUVertexFormat_Float32x4;
break;
261 case Cogs::hash(
"i32"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32;
break;
262 case Cogs::hash(
"vec2i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x2;
break;
263 case Cogs::hash(
"vec3i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x3;
break;
264 case Cogs::hash(
"vec4i"): format = WGPUVertexFormat::WGPUVertexFormat_Sint32x4;
break;
265 case Cogs::hash(
"u32"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32;
break;
266 case Cogs::hash(
"vec2u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x2;
break;
267 case Cogs::hash(
"vec3u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x3;
break;
268 case Cogs::hash(
"vec4u"): format = WGPUVertexFormat::WGPUVertexFormat_Uint32x4;
break;
270 LOG_ERROR(logger,
"Unsupported vertex attribute type %s", std::string(s).c_str());
304 std::istringstream iss(shaderSource);
305 std::string expr = R
"(\s*const ([^\s^0-9]+)([0-9]+)_LOC\s*=\s*([0-9]+))";
306 std::regex regex_expression(expr);
308 for (std::string line; std::getline(iss, line); )
311 if (std::regex_search(line, match, regex_expression))
313 std::string semanticName = match.str(1);
314 uint8_t slot =
static_cast<uint8_t
>(std::stoi(match.str(2)));
315 uint8_t loc =
static_cast<uint8_t
>(std::stoi(match.str(3)));
316 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
317 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
329 iss.seekg(0, std::ios::beg);
330 std::string expr_location = R
"(@location\(\s*([^\s\)\d]+)([\d]+)_LOC\s*\)\s*([^\s:]+)\s*:\s*([\da-zA-Z]+)[,\s\n])";
331 std::regex regex_location(expr_location);
332 for (std::string line; std::getline(iss, line); )
335 if (std::regex_search(line, match, regex_location))
337 std::string semanticName = match.str(1);
338 uint8_t slot =
static_cast<uint8_t
>(std::stoi(match.str(2)));
339 std::string name = match.str(3);
340 std::string type = match.str(4);
341 uint8_t semantic = std::numeric_limits<uint8_t>::max();
342 for (uint8_t i = 0; i < std::size(semanticNames); i++) {
343 if (strcmp(semanticName.c_str(), semanticNames[i]) == 0) {
348 if (semantic == std::numeric_limits<uint8_t>::max()) {
349 LOG_DEBUG(logger,
"unknown semantic name for vertex attribute %s", semanticName.c_str());
352 for (
size_t i = 0; i < nAttribs; i++) {
353 if (bindings[i].semantic == semantic && bindings[i].slot == slot) {
365 void dumpSource(
const std::string& source,
int firstLine = 1,
int lastLine = std::numeric_limits<int>::max())
367 const char* start = source.data();
368 const char* curr = start;
371 while ((*curr !=
'\0') && (line <= lastLine)) {
372 const char* p = curr;
373 while ((*p !=
'\0') && (*p !=
'\n') && (*p !=
'\r')) { p++; }
375 if (firstLine <= line) {
376 LOG_DEBUG(logger,
"%3d: %s", line, std::string(curr, p - curr).c_str());
382 if (*curr !=
'\0') { curr++; };
383 if ((*curr !=
'\0') && (*curr != *p) && ((*curr ==
'\n') || (*curr ==
'\r'))) { curr++; }
387 void printShaderError(
struct WGPUCompilationInfo
const* compilationInfo,
const std::string* source =
nullptr)
389 for (
size_t i = 0; i < compilationInfo->messageCount; i++) {
390 const WGPUCompilationMessage& message = compilationInfo->messages[i];
392 dumpSource(*source, ((
int)message.lineNum) - 3, ((
int)message.lineNum));
395 std::string category_str;
396 switch (message.type) {
397 case WGPUCompilationMessageType::WGPUCompilationMessageType_Info:
398 category = Cogs::Logging::Category::Info;
399 category_str =
"Info";
401 case WGPUCompilationMessageType::WGPUCompilationMessageType_Error:
402 category = Cogs::Logging::Category::Error;
403 category_str =
"Error";
405 case WGPUCompilationMessageType::WGPUCompilationMessageType_Force32:
406 category = Cogs::Logging::Category::Error;
407 category_str =
"Force32";
410 category = Cogs::Logging::Category::Error;
411 category_str =
"Unknown";
418 void compile_callback(WGPUCompilationInfoRequestStatus status,
struct WGPUCompilationInfo
const* compilationInfo,
void* userdata)
420 const std::string* source =
reinterpret_cast<std::string*
>(userdata);
421 if (status != WGPUCompilationInfoRequestStatus_Success || (compilationInfo !=
nullptr && compilationInfo->messageCount != 0))
423 printShaderError(compilationInfo, source);
430 void EffectsWebGPU::initialize(GraphicsDeviceWebGPU *device_in, IBuffers * buffers_in)
432 EffectsCommon::initialize(buffers_in);
433 graphicsDevice = device_in;
442 if (!HandleIsValid(handle))
return;
444 wgpuShaderModuleRelease(effect.vs_module);
445 wgpuShaderModuleRelease(effect.fs_module);
446 this->effects.removeResource(handle);
457 WGPUDevice device = graphicsDevice->device;
460 Utilities::readFile(handler, fileName, csSource);
463 effect.cs_entry =
"main";
464 effect.name = std::string(fileName);
466 std::string cs_source;
467 for(
auto &def : definitions){
468 cs_source +=
"const " + def.first +
" = " + def.second +
";\n";
470 cs_source += csSource.content;
473 LOG_INFO(logger,
"Compiling WebGPU CS:\n%s", cs_source.c_str());
476 WGPUShaderModuleDescriptor descriptor = {};
477 descriptor.label = fileName.data();
478 WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
479 wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
480 wgsl_desc.code = cs_source.c_str();
481 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
483 effect.cs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
486 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(cs_source, Cogs::WebGPUConstantBufferUsage::ShaderStage_Compute);
487 addConstantBufferBindings(effect, bindings);
490 wgpuShaderModuleGetCompilationInfo(effect.cs_module, compile_callback,
static_cast<void*
>(&cs_source));
494 return this->effects.addResource(std::move(effect));
510 bool useSortedDefines =
false;
512 std::sort(unique_definitions.begin(), unique_definitions.end());
513 unique_definitions.erase(std::unique(unique_definitions.begin(), unique_definitions.end()), unique_definitions.end());
514 if (unique_definitions.size() != effect_desc.
definitions.size()) {
515 useSortedDefines =
true;
516 LOG_WARNING(logger,
"Redefinition of precompiler defines not supported in WebGPU backend");
520 assert(!hsSource.origin.size());
521 assert(!dsSource.origin.size());
522 assert(!gsSource.origin.size());
524 WGPUDevice device = graphicsDevice->device;
526 EffectWebGPU effect = {};
527 effect.vs_entry = std::string(vsEntryPoint);
528 effect.fs_entry = std::string(fsEntryPoint);
529 effect.name = std::string(effect_desc.
name);
531 LOG_INFO(logger,
"Compiling WebGPU Effect: %s", effect.name.c_str());
533 std::string definitions_source;
534 for (
auto& def : useSortedDefines ? unique_definitions : effect_desc.
definitions) {
535 definitions_source +=
"const " + def.first +
" = " + def.second +
";\n";
537 std::string vs_source = definitions_source + vsSource.content;
540 LOG_INFO(logger,
"Compiling WebGPU VS:\n%s", vs_source.c_str());
543 WGPUShaderModuleDescriptor descriptor = {};
544 descriptor.label = effect_desc.
name.
data();
545 WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
546 wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
547 wgsl_desc.code = vs_source.c_str();
548 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
550 effect.vs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
554 wgpuShaderModuleGetCompilationInfo(effect.vs_module, compile_callback,
static_cast<void*
>(& vs_source));
557 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(vs_source, Cogs::WebGPUConstantBufferUsage::ShaderStage_Vertex);
558 addConstantBufferBindings(effect, bindings);
560 effect.num_attribs = extractVertexAttribLocation(vs_source, effect.semanticSlotBindings, effect.maxVertexAttribs);
564 if (fsSource.content.size()) {
565 std::string fs_source;
566 fs_source = definitions_source + fsSource.content;
569 LOG_INFO(logger,
"Compiling WebGPU FS:\n%s", fs_source.c_str());
572 WGPUShaderModuleDescriptor descriptor = {};
573 descriptor.label = effect_desc.
name.
data();
574 WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
575 wgsl_desc.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
576 wgsl_desc.code = fs_source.c_str();
577 descriptor.nextInChain = (WGPUChainedStruct*)&wgsl_desc;
579 effect.fs_module = wgpuDeviceCreateShaderModule(device, &descriptor);
583 wgpuShaderModuleGetCompilationInfo(effect.fs_module, compile_callback,
static_cast<void*
>(& fs_source));
586 std::vector<Cogs::WebGPUConstantBufferBinding> bindings = extractConstantBindingLayout(fs_source, Cogs::WebGPUConstantBufferUsage::ShaderStage_Fragment);
587 addConstantBufferBindings(effect, bindings);
589 return this->effects.addResource(std::move(effect));
596 for (
size_t i = 0; i < effect.num_bindings; i++) {
597 if (effect.constantBufferBindings[i].nameHash == nameHash) {
608 if (HandleIsValid(constantBuffer)) {
614 bool EffectsWebGPU::addConstantBufferBindings(
EffectWebGPU& effect,
const std::vector<Cogs::WebGPUConstantBufferBinding>& bindings) {
615 for (
auto binding : bindings) {
618 for (slot = 0; slot < effect.num_bindings; slot++) {
619 auto& curr = effect.constantBufferBindings[slot];
620 if (curr.nameHash == binding.nameHash) {
621 if (curr.group == binding.group && curr.bg_ent.binding == binding.bg_ent.binding) {
622 curr.bg_ent.visibility = (WebGPUConstantBufferUsage)(curr.bg_ent.visibility | binding.bg_ent.visibility);
626 LOG_ERROR(logger,
"Inconsistent binding location");
633 if (effect.num_bindings < EffectWebGPU::maxConstantBuffers) {
634 effect.constantBufferBindings[effect.num_bindings++] = binding;
637 LOG_ERROR(logger,
"Number of bindings exceed EffectWebGPU::maxConstantBuffers=%zu", EffectWebGPU::maxConstantBuffers);
645 const WebGPUConstantBufferBinding* EffectsWebGPU::getConstantBufferBindings(EffectHandle effectHandle,
size_t& num_bindings) {
646 const EffectWebGPU& effect = effects[effectHandle];
647 num_bindings = effect.num_bindings;
648 return effect.constantBufferBindings;
652 std::string constantBufferName = std::string(name);
654 if (HandleIsValid(constantBuffer)) {
661 std::string constantBufferName = std::string(name);
662 if (!constantBufferName.ends_with(
"Sampler")) {
663 constantBufferName +=
"Sampler";
667 if (HandleIsValid(constantBuffer)) {
671 if (constantBufferName.ends_with(
"TextureSampler")) {
672 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.