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