1#include "ShaderBuilderPostProcess.h"
2#include "ShaderBuilderHelpersWebGPU.h"
4#include "Utilities/Strings.h"
5#include "Utilities/Preprocessor.h"
6#include "ResourceStore.h"
7#include "Renderer/Renderer.h"
8#include "Rendering/IGraphicsDevice.h"
9#include "Renderer/Tasks/PostProcessTask.h"
10#include "Foundation/Logging/Logger.h"
11#include "Rendering/IContext.h"
12#include "Rendering/IEffects.h"
13#include "Renderer/RenderTarget.h"
21 constexpr size_t InitialBufferCapacity = 4096u;
27 void removeSuffix(std::string& dst,
const std::string_view& suffix)
29 auto pos = dst.find(suffix);
30 if (pos == std::string::npos)
return;
34 [[nodiscard]] std::string_view parameterType(
const ParsedDataType type)
37 case ParsedDataType::Float:
return "f32";
break;
38 case ParsedDataType::Float2:
return "vec2f";
break;
39 case ParsedDataType::Float3:
return "vec3f";
break;
40 case ParsedDataType::Float4:
return "vec4f";
break;
41 case ParsedDataType::Float4x4:
return "mat4x4f";
break;
42 case ParsedDataType::Int:
return "i32";
break;
43 case ParsedDataType::Int2:
return "vec2i";
break;
44 case ParsedDataType::Int3:
return "vec3i";
break;
45 case ParsedDataType::Int4:
return "vec4i";
break;
46 case ParsedDataType::UInt:
return "u32";
break;
47 case ParsedDataType::UInt2:
return "vec2u";
break;
48 case ParsedDataType::UInt3:
return "vec3u";
break;
49 case ParsedDataType::UInt4:
return "vec4u";
break;
51 LOG_ERROR(logger,
"Unsupported attribute type %d",
int(type));
58 Cogs::BindingTextureSampleType bindingTextureSampleType(
const ParsedDataType type,
const bool isDepthTexture)
61 return Cogs::BindingTextureSampleType::Depth;
65 case ParsedDataType::Int:
66 case ParsedDataType::Int2:
67 case ParsedDataType::Int3:
68 case ParsedDataType::Int4:
69 return Cogs::BindingTextureSampleType::Sint;
70 case ParsedDataType::UInt:
71 case ParsedDataType::UInt2:
72 case ParsedDataType::UInt3:
73 case ParsedDataType::UInt4:
74 return Cogs::BindingTextureSampleType::Uint;
76 return Cogs::BindingTextureSampleType::Float;
83 std::string convertDefinesToConstExpressions(
const std::string& s) {
84 std::map<std::string, std::string> addedDefines;
86 result.reserve(s.size());
88 std::istringstream iss(s);
90 std::string identifier;
91 std::string replacement;
92 for (std::string line; std::getline(iss, line); )
97 std::string_view sv(line);
98 auto pos = sv.find_first_not_of(
" \t");
99 if (pos == std::string_view::npos || sv.substr(pos, 7) !=
"#define") {
100 result += line +
"\n";
103 sv = sv.substr(pos + 7);
105 pos = sv.find_first_not_of(
" \t");
106 if (pos == std::string_view::npos) {
107 result += line +
"\n";
112 auto end = sv.find_first_of(
" \t");
113 if (end == std::string_view::npos) {
117 identifier = sv.substr(0, end);
119 pos = sv.find_first_not_of(
" \t");
120 if (pos == std::string_view::npos) {
123 auto last = sv.find_last_not_of(
" \t");
124 replacement = sv.substr(pos, last - pos + 1);
128 auto it = addedDefines.find(identifier);
129 if (it != addedDefines.end()) {
130 if (replacement != (*it).second) {
131 LOG_WARNING(logger,
"Shader generation with inconsistent preprosessor define %s set to %s conflicts with previous define %s.", identifier.c_str(), replacement.c_str(), (*it).second.c_str());
135 result +=
"const " + identifier +
" = " + replacement +
";\n";
136 addedDefines.try_emplace(identifier, replacement);
141 void addInterface(std::string& src) {
145 @builtin(position) position: vec4f,
146 @location(0) TexCoords: vec2f,
147 @location(1) NormalizedCoords: vec2f,
153 void addDefine(std::string& output,
const std::string& name,
const std::string& value)
155 output.append(
"#define ");
158 output.append(value);
162 void addDefines(std::string& output,
const std::vector<std::pair<std::string, std::string>>& definitions)
164 for (
const std::pair<std::string, std::string>& define : definitions) {
165 addDefine(output, define.first, define.second);
169 void addOutputStruct(std::string& src,
const PipelineOptions& options,
bool writeDepth) {
170 bool hasCustomTargets =
false;
171 src.append(
"struct FragmentOut {\n");
173 src.append(
" @builtin(frag_depth) fragDepth: f32,\n");
175 std::string prefix =
"target_";
176 for (
auto o : options) {
179 for (
auto& c : key) {
180 c =
static_cast<decltype(key)::value_type
>(std::tolower(c));
182 if (key.substr(0, prefix.size()) != prefix)
continue;
183 hasCustomTargets =
true;
185 int location = std::stoi(key.substr(prefix.size()));
187 Cogs::Core::split(o.second,
" ", tokens);
188 if (tokens.size() != 2) {
189 LOG_ERROR(logger,
"Unable to parse pipeline output option %s", o.second.c_str());
192 std::string_view datatype =
"Undefined";
194 case Cogs::hash(
"float"): datatype = parameterType(ParsedDataType::Float);
break;
195 case Cogs::hash(
"float2"): datatype = parameterType(ParsedDataType::Float2);
break;
196 case Cogs::hash(
"float3"): datatype = parameterType(ParsedDataType::Float3);
break;
197 case Cogs::hash(
"float4"): datatype = parameterType(ParsedDataType::Float4);
break;
198 case Cogs::hash(
"uint"): datatype = parameterType(ParsedDataType::UInt);
break;
199 case Cogs::hash(
"uint2"): datatype = parameterType(ParsedDataType::UInt2);
break;
200 case Cogs::hash(
"uint3"): datatype = parameterType(ParsedDataType::UInt3);
break;
201 case Cogs::hash(
"uint4"): datatype = parameterType(ParsedDataType::UInt4);
break;
202 case Cogs::hash(
"int"): datatype = parameterType(ParsedDataType::Int);
break;
203 case Cogs::hash(
"int2"): datatype = parameterType(ParsedDataType::Int2);
break;
204 case Cogs::hash(
"int3"): datatype = parameterType(ParsedDataType::Int3);
break;
205 case Cogs::hash(
"int4"): datatype = parameterType(ParsedDataType::Int4);
break;
207 src.append(
" @location(" + std::to_string(location) +
") ");
208 src.append(tokens[0]);
210 src.append(datatype);
213 if (!hasCustomTargets) {
214 src.append(
" @location(0) fragColor : vec4f,\n");
216 src.append(
"};\n\n");
220 struct EffectParameterField
223 ParsedDataType type = ParsedDataType::Unknown;
226 void emitEffectParametersStruct(std::string& src,
const std::vector<EffectParameterField>& fields)
228 if (fields.empty())
return;
230 src.append(
"struct EffectParameters_t {\n");
231 for (
const EffectParameterField& field : fields) {
233 src.append(field.name);
235 src.append(parameterType(field.type));
248 constexpr BindGroup group = BindGroup::Default;
250 bindings.numGroups = std::max(bindings.numGroups,
static_cast<uint16_t
>(
static_cast<size_t>(group) + 1));
252 std::vector<EffectParameterField> parameterFields;
255 switch (p.definition->type) {
256 case ParsedDataType::Float:
257 case ParsedDataType::Float2:
258 case ParsedDataType::Float3:
259 case ParsedDataType::Float4:
260 case ParsedDataType::Float4x4:
261 case ParsedDataType::Int:
262 case ParsedDataType::Int2:
263 case ParsedDataType::Int3:
264 case ParsedDataType::Int4:
265 case ParsedDataType::UInt:
266 case ParsedDataType::UInt2:
267 case ParsedDataType::UInt3:
268 case ParsedDataType::UInt4:
269 parameterFields.push_back({ p.definition->key, p.definition->type });
272 case ParsedDataType::Texture2D:
274 assert(defaultGroup.numEntries + 2 <= Cogs::MaxBindGroupEntries);
275 const bool isDepthTexture = (p.definition->texture.flags & ParsedValueTextureFlags::DepthTexture) != 0;
276 if (p.definition->texture.samples > 1) {
277 LOG_ERROR(logger,
"Sampling from multisampled textures is not implemented for WebGPU post-process effects (%s)", p.definition->key.c_str());
281 textureEntry.binding = defaultGroup.numEntries++;
282 textureEntry.nameHash =
Cogs::hash(p.definition->key.c_str());
283 textureEntry.resourceType = Cogs::BindingResourceType::Texture;
284 textureEntry.visibility = Cogs::BindingVisibilityFragment;
285 textureEntry.textureDimension = Cogs::ResourceDimensions::Texture2D;
286 textureEntry.textureSampleType = bindingTextureSampleType(p.definition->texture.dataType, isDepthTexture);
287 textureEntry.isDepthTexture = isDepthTexture;
290 const std::string samplerName = p.definition->key +
"Sampler";
292 samplerEntry.binding = defaultGroup.numEntries++;
293 samplerEntry.nameHash =
Cogs::hash(samplerName.c_str());
294 samplerEntry.resourceType = Cogs::BindingResourceType::Sampler;
295 samplerEntry.visibility = Cogs::BindingVisibilityFragment;
296 samplerEntry.samplerBindingType = isDepthTexture ? Cogs::BindingSamplerBindingType::NonFiltering : Cogs::BindingSamplerBindingType::Filtering;
297 samplerEntry.isDepthTexture = isDepthTexture;
302 case ParsedDataType::Buffer:
303 LOG_ERROR(logger,
"Render buffers are not supported for WebGPU post-process effects (%s)", p.definition->key.c_str());
306 case ParsedDataType::ConstantBuffer:
316 if (!parameterFields.empty()) {
317 assert(defaultGroup.numEntries < Cogs::MaxBindGroupEntries);
319 entry.binding = defaultGroup.numEntries++;
320 entry.nameHash =
Cogs::hash(
"EffectParameters");
321 entry.resourceType = Cogs::BindingResourceType::UniformBuffer;
322 entry.visibility = Cogs::BindingVisibilityFragment;
324 emitEffectParametersStruct(uniforms, parameterFields);
325 uniforms.append(
"@group(");
326 uniforms.append(std::to_string(
static_cast<size_t>(group)));
327 uniforms.append(
") @binding(");
328 uniforms.append(std::to_string(entry.binding));
329 uniforms.append(
") var<uniform> EffectParameters : EffectParameters_t;\n");
346 addDefine(body,
"COGS_MAX_LIGHTS", std::to_string(context->renderer->
getMaxLights()));
347 addDefines(body, desc.definitions);
348 addOutputStruct(body, task->options, task->writeDepth || task->depthTest);
352 std::string uniforms;
353 addTaskUniforms(uniforms, bindings, task->properties);
354 body.append(uniforms);
356 std::string prefix_ps = desc.ps;
357 removeSuffix(prefix_ps,
".hlsl");
358 body.append(
"#include \"" + prefix_ps +
".wgsl\"\n");
363 pp.
processed.reserve(::InitialBufferCapacity);
364 if (!pp.
process(context->context, body))
return false;
369 std::string engineUniforms;
370 std::vector<bool> bindGroupUsed;
371 addEngineUniformsToSourceWebGPU(engineUniforms, pp.
identifiersSeen, bindGroupUsed);
372 if (!engineUniforms.empty()) {
373 if (!pp.
process(context->context, engineUniforms))
return false;
377 bindGroupUsed.resize(
static_cast<size_t>(BindGroup::Count),
false);
378 if (bindings.groups[
static_cast<size_t>(BindGroup::Default)].numEntries > 0) {
379 bindGroupUsed[
static_cast<size_t>(BindGroup::Default)] =
true;
383 for (
size_t g = 0; g < bindGroupUsed.size(); ++g) {
384 if (bindGroupUsed[g]) {
385 usedBindings.groups[g] = bindings.groups[g];
386 usedBindings.numGroups =
static_cast<uint16_t
>(g + 1);
389 desc.bindGroupLayoutDesc = usedBindings;
391 std::string all = engineUniforms + body;
392 all = convertDefinesToConstExpressions(all);
394 auto code =
hash(all);
395 std::string file_prefix = prefix_ps +
"_" + std::to_string(code);
396 context->context->
resourceStore->addResource(file_prefix +
".wgsl", all);
397 desc.ps = file_prefix +
".hlsl";
398 desc.definitions.clear();
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
unsigned getMaxLights() const override
Get the maximum number of lights.
Log implementation class.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
void addSamplerToSource(std::string &source, const std::string &name, BindGroup group, const Cogs::BindGroupEntryDescription &entry)
Emits a valid WGSL sampler declaration ("@group()@binding() var") for an engine-provided sampler entr...
void addTextureToSource(std::string &source, const std::string &name, BindGroup group, const Cogs::BindGroupEntryDescription &entry)
Emits a valid WGSL texture declaration ("@group()@binding() var") for an engine-provided texture entr...
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
COGSFOUNDATION_API size_t hashLowercase(std::string_view str, size_t hashValue=Cogs::hash()) noexcept
Get the hash code of the string converted to lowercase.
bool process(Context *context, const StringView input)
Run a text block through the preprocessor.
std::unordered_set< StringRef > identifiersSeen
Set of identifiers encountered in active text.
std::string processed
Resulting processed text.