Cogs.Core
ShaderBuilderPostProcessWebGPU.cpp
1#include "ShaderBuilderPostProcess.h"
2#include "Context.h"
3#include "Utilities/Strings.h"
4#include "Utilities/Preprocessor.h"
5#include "ResourceStore.h"
6#include "Renderer/EnginePermutations.h"
7#include "Rendering/IGraphicsDevice.h"
8#include "Renderer/Tasks/PostProcessTask.h"
9#include "Foundation/Logging/Logger.h"
10#include "Rendering/IContext.h"
11#include "Rendering/IBuffers.h"
12#include "Renderer/IRenderer.h"
13#include "Renderer/RenderTarget.h"
14
15#include <glm/gtc/type_ptr.hpp>
16
17#include <array>
18#include <sstream>
19#include <map>
20
21
22namespace {
23 constexpr size_t InitialBufferCapasity = 4096u;
24
25 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("ShaderBuilderPostProcessWebGPU");
26
27 constexpr std::array engineSamplers
28 {
29 "linearSampler",
30 "linearClampSampler",
31 "pointSampler",
32 "pointClampSampler",
33 };
34
35 void changeSuffix(std::string& dst, const std::string_view& from, const std::string_view& to)
36 {
37 auto pos = dst.find(from);
38 if (pos == std::string::npos) return;
39 dst.replace(pos, to.length(), to);
40 }
41
42 void removeSuffix(std::string& dst, const std::string_view& suffix)
43 {
44 auto pos = dst.find(suffix);
45 if (pos == std::string::npos) return;
46 dst.erase(pos);
47 }
48 using namespace Cogs::Core;
49 [[nodiscard]] std::string_view parameterType(const ParsedDataType type)
50 {
51 switch (type) {
52 case ParsedDataType::Float: return "f32"; break;
53 case ParsedDataType::Float2: return "vec2f"; break;
54 case ParsedDataType::Float3: return "vec3f"; break;
55 case ParsedDataType::Float4: return "vec4f"; break;
56 case ParsedDataType::Float4x4: return "mat4x4f"; break;
57 case ParsedDataType::Int: return "i32"; break;
58 case ParsedDataType::Int2: return "vec2i"; break;
59 case ParsedDataType::Int3: return "vec3i"; break;
60 case ParsedDataType::Int4: return "vec4i"; break;
61 case ParsedDataType::UInt: return "u32"; break;
62 case ParsedDataType::UInt2: return "vec2u"; break;
63 case ParsedDataType::UInt3: return "vec3u"; break;
64 case ParsedDataType::UInt4: return "vec4u"; break;
65 default:
66 LOG_ERROR(logger, "Unsupported attribute type %d", int(type));
67 return "<illegal>";
68 break;
69 }
70 }
71
72 [[nodiscard]]
73 std::string convertDefinesToConstExpressions(const std::string& s) {
74 std::map<std::string, std::string> addedDefines;
75 std::string result;
76 result.reserve(s.size());
77
78 std::istringstream iss(s);
79
80 std::string identifier;
81 std::string replacement;
82 for (std::string line; std::getline(iss, line); )
83 {
84 identifier.clear();
85 replacement.clear();
86
87 std::string_view sv(line);
88 auto pos = sv.find_first_not_of(" \t");
89 if (pos == std::string_view::npos || sv.substr(pos, 7) != "#define") {
90 result += line + "\n";
91 continue;
92 }
93 sv = sv.substr(pos + 7);
94
95 pos = sv.find_first_not_of(" \t");
96 if (pos == std::string_view::npos) {
97 result += line + "\n";
98 continue;
99 }
100 sv = sv.substr(pos);
101
102 auto end = sv.find_first_of(" \t");
103 if (end == std::string_view::npos) {
104 identifier = sv;
105 replacement = "1";
106 } else {
107 identifier = sv.substr(0, end);
108 sv = sv.substr(end);
109 pos = sv.find_first_not_of(" \t");
110 if (pos == std::string_view::npos) {
111 replacement = "1";
112 } else {
113 auto last = sv.find_last_not_of(" \t");
114 replacement = sv.substr(pos, last - pos + 1);
115 }
116 }
117
118 auto it = addedDefines.find(identifier);
119 if (it != addedDefines.end()) {
120 if (replacement != (*it).second) {
121 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());
122 }
123 continue;
124 }
125 result += "const " + identifier + " = " + replacement + ";\n";
126 addedDefines.try_emplace(identifier, replacement);
127 }
128 return result;
129 }
130
131 void addInterface(std::string& src) {
132 src.append(
133 R"(
134struct VertexIn {
135 @builtin(position) position: vec4f,
136 @location(0) TexCoords: vec2f,
137 @location(1) NormalizedCoords: vec2f,
138};
139
140)");
141 }
142
143 void addDefines(std::string& output,
144 const std::vector<std::pair<std::string, std::string>>& definitions)
145 {
146 for (const std::pair<std::string, std::string>& define : definitions) {
147 output.append("#define ");
148 output.append(define.first);
149 output.append(" ");
150 output.append(define.second);
151 output.append("\n");
152 }
153 }
154
155 void addUniforms(std::string& src, const std::vector<ProcessTaskProperty>& properties, int group, int& bindings) {
156 std::string parameterUBOsrc;
157
158 bool hasParameters = false;
159 parameterUBOsrc.append("");
160 parameterUBOsrc.append("struct EffectParameters_t {\n");
161
162 for (const ProcessTaskProperty& p : properties) {
163 switch (p.definition->type) {
164 case ParsedDataType::Float:
165 case ParsedDataType::Float2:
166 case ParsedDataType::Float3:
167 case ParsedDataType::Float4:
168 case ParsedDataType::Float4x4:
169 case ParsedDataType::Int:
170 case ParsedDataType::Int2:
171 case ParsedDataType::Int3:
172 case ParsedDataType::Int4:
173 case ParsedDataType::UInt:
174 case ParsedDataType::UInt2:
175 case ParsedDataType::UInt3:
176 case ParsedDataType::UInt4:
177 parameterUBOsrc.append(" " + p.definition->key + " : ");
178 parameterUBOsrc.append(parameterType(p.definition->type));
179 parameterUBOsrc.append(",\n");
180 hasParameters = true;
181 break;
182
183 case ParsedDataType::Texture2D:
184 {
185 std::string texType = "texture";
186 if ((p.definition->texture.flags & ParsedValueTextureFlags::DepthTexture) != 0) {
187 texType.append("_depth");
188 }
189 if (p.definition->texture.samples > 1) {
190 texType.append("_multisampled");
191 LOG_ERROR(logger, "Sampling from multisampled textures is not allowed on this platform, well, it is, just not implemented");
192 }
193 texType.append("_2d");
194 std::string_view texture_template = "<f32>";
195 switch (p.definition->texture.dataType) {
196 case ParsedDataType::Int:
197 case ParsedDataType::Int2:
198 case ParsedDataType::Int3:
199 case ParsedDataType::Int4:
200 texture_template = "<i32>";
201 break;
202 case ParsedDataType::UInt:
203 case ParsedDataType::UInt2:
204 case ParsedDataType::UInt3:
205 case ParsedDataType::UInt4:
206 texture_template = "<u32>";
207 break;
208 default:
209 break;
210 }
211 if ((p.definition->texture.flags & ParsedValueTextureFlags::DepthTexture) == 0) { // depth textures are always f32 and must be declared without template argument
212 texType.append(texture_template);
213 }
214 src.append("@group(");
215 src.append(std::to_string(group));
216 src.append(") @binding(");
217 src.append(std::to_string(++bindings));
218 src.append(") var");
219 if (false) {
220 src.append("<uniform>");
221 }
222 src.append(" ");
223 src.append(p.definition->key);
224 src.append(" : " + texType);
225 src.append(";\n");
226
227 // Not sure if we will support these in the future, seems to only take extra resources when the effect author knows which sampler to use
228 src.append("@group(");
229 src.append(std::to_string(group));
230 src.append(") @binding(");
231 src.append(std::to_string(++bindings));
232 src.append(") var");
233 if (false) {
234 src.append("<uniform>");
235 }
236 src.append(" ");
237 src.append(p.definition->key);
238 src.append("Sampler : sampler");
239 src.append(";\n");
240 }
241 break;
242 case ParsedDataType::Buffer:
243 LOG_ERROR(logger, "Render buffers not supported on this platform");
244 break;
245 case ParsedDataType::ConstantBuffer:
246 if (p.definition->key == "SceneBuffer") {
247 src.append("@group(" + std::to_string(group) + ") @binding(" + std::to_string(++bindings) +") var<uniform> SceneBuffer : SceneBuffer_t; \n");
248 src.append("#define COGS_VIEWGETTERS_REFERENCED 1\n");
249 }
250 else if (p.definition->key == "ShadowBuffer") {
251 src.append("@group(" + std::to_string(group) + ") @binding(" + std::to_string(++bindings) +") var<uniform> ShadowBuffer : ShadowBuffer_t; \n");
252 }
253 else if (p.definition->value.starts_with("Cogs.")) {
254 std::string bufferName = p.definition->value.substr(5);
255 src.append("#include \"Engine/" + bufferName + ".wgsl\"\n");
256 src.append("@group(" + std::to_string(group) + ") @binding(" + std::to_string(++bindings) + ") var<uniform> " + p.definition->key + " : " + bufferName + "_t; \n");
257 }
258 break;
259 default:
260 break;
261 }
262 }
263 parameterUBOsrc.append("};\n");
264 if (hasParameters) {
265 src.append(parameterUBOsrc);
266 src.append("@group(" + std::to_string(group) + ") @binding(" + std::to_string(++bindings) + ") var<uniform> EffectParameters : EffectParameters_t;\n");
267 }
268 }
269
270 void addEngineSamplers(std::string& src/*, const std::unordered_set<Cogs::Core::StringRef>& identifiersSeen*/, int group, int& bindings) {
271 for (auto sampler : engineSamplers) {
272 src.append("@group(" + std::to_string(group) + ") @binding(" + std::to_string(++bindings) + ") var " + sampler + " : sampler;\n");
273 }
274 }
275
276 void addOutputStruct(std::string& src, const PipelineOptions& options, bool writeDepth) {
277 bool hasCustomTargets = false;
278 std::string prefix = "target_";
279 src.append("struct FragmentOut {\n");
280 if (writeDepth) {
281 src.append(" @builtin(frag_depth) fragDepth: f32,\n");
282 }
283 for (auto o : options) {
284 auto key = o.first;
285
286 for (auto& c : key) {
287 c = static_cast<decltype(key)::value_type>(std::tolower(c));
288 }
289 if (key.substr(0, prefix.size()) == prefix) {
290 if (!hasCustomTargets) {
291 hasCustomTargets = true;
292 }
293 int location = std::stoi(key.substr(prefix.size()));
294 TokenStream tokens;
295 Cogs::Core::split(o.second, " ", tokens);
296 if (tokens.size() != 2) {
297 LOG_ERROR(logger, "Unable to parse pipeline output option %s", o.second.c_str());
298 continue;
299 }
300 std::string_view datatype = "Undefined";
301 switch (tokens[1].hashLowercase()) {
302 case Cogs::hash("float"):
303 datatype = parameterType(ParsedDataType::Float);
304 break;
305 case Cogs::hash("float2"):
306 datatype = parameterType(ParsedDataType::Float2);
307 break;
308 case Cogs::hash("float3"):
309 datatype = parameterType(ParsedDataType::Float3);
310 break;
311 case Cogs::hash("float4"):
312 datatype = parameterType(ParsedDataType::Float4);
313 break;
314 case Cogs::hash("uint"):
315 datatype = parameterType(ParsedDataType::UInt);
316 break;
317 case Cogs::hash("uint2"):
318 datatype = parameterType(ParsedDataType::UInt2);
319 break;
320 case Cogs::hash("uint3"):
321 datatype = parameterType(ParsedDataType::UInt3);
322 break;
323 case Cogs::hash("uint4"):
324 datatype = parameterType(ParsedDataType::UInt4);
325 break;
326 case Cogs::hash("int"):
327 datatype = parameterType(ParsedDataType::Int);
328 break;
329 case Cogs::hash("int2"):
330 datatype = parameterType(ParsedDataType::Int2);
331 break;
332 case Cogs::hash("int3"):
333 datatype = parameterType(ParsedDataType::Int3);
334 break;
335 case Cogs::hash("int4"):
336 datatype = parameterType(ParsedDataType::Int4);
337 break;
338 }
339 src.append(" @location(" + std::to_string(location) + ") ");
340 src.append(tokens[0]);
341 src.append(" : ");
342 src.append(datatype);
343 src.append(",\n");
344 }
345 }
346 if (!hasCustomTargets) {
347 src.append(" @location(0) fragColor : vec4f,\n");
348 }
349 src.append("};\n\n");
350 }
351}
352
353namespace Cogs
354{
355 namespace Core {
356
357 bool buildPostProcessEffectWebGPU(RenderTaskContext* context,
358 EffectDescription& desc, PostProcessTask* task) {
359 int bindGroup = 0;
360 int bindings = 0;
361 std::string src;
362 addInterface(src);
363 addDefines(src, desc.definitions);
364
365 bool SceneBufferRef = false;
366 for (const ProcessTaskProperty& p : task->properties) {
367 if (p.definition->key == "SceneBuffer") {
368 SceneBufferRef = true;
369 }
370 }
371
372 addUniforms(src, task->properties, bindGroup, bindings);
373 addEngineSamplers(src/*, const std::unordered_set<Cogs::Core::StringRef>&identifiersSeen*/, bindGroup, bindings);
374 addOutputStruct(src, task->options, task->writeDepth || task->depthTest);
375 if(SceneBufferRef){
376 src.append("#include \"Engine/SceneBuffer.wgsl\"\n\n");
377 }
378
379 std::string prefix_ps = desc.ps;
380 removeSuffix(prefix_ps, ".hlsl");
381 src.append("#include \"" + prefix_ps + ".wgsl\"\n");
382 auto code = hash(src);
383 std::string file_prefix = prefix_ps + "_" + std::to_string(code);
384
386 pp.processed.reserve(::InitialBufferCapasity);
387
388 if (!pp.process(context->context, src)) return false;
389
390 src.swap(pp.processed);
391 src = convertDefinesToConstExpressions(src);
392 context->context->resourceStore->addResource(file_prefix + ".wgsl", src);
393 desc.ps = file_prefix + ".hlsl";
394 desc.definitions.clear();
395 return true;
396 }
397 }
398}
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
Definition: Context.h:210
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
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
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.
Partial C preprocessor.
Definition: Preprocessor.h:42
bool process(Context *context, const StringView input)
Run a text block through the preprocessor.
std::string processed
Resulting processed text.
Definition: Preprocessor.h:48