summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-04 11:56:03 +0100
committerskal <pascal.massimino@gmail.com>2026-02-04 11:56:03 +0100
commit2e34965e5e48175c5ee6016af1a858f7f3f02c1d (patch)
treea41ea2fdc50a18183742bffaf0705b394f2be69f /src
parent0d29f340b9de8f6a14baa0a2c6f3c57b8d1458a5 (diff)
feat(gpu): Implement recursive WGSL composition and modularize shaders (Task #50)
- Updated ShaderComposer to support recursive #include "snippet_name" with cycle detection. - Extracted granular WGSL snippets: math/sdf_shapes, math/sdf_utils, render/shadows, render/scene_query, render/lighting_utils. - Refactored Renderer3D to use #include in shaders, simplifying C++ dependency lists. - Fixed WGPUShaderSourceWGSL usage on macOS to correctly handle composed shader strings. - Added comprehensive unit tests for recursive composition in test_shader_composer. - Verified system stability with test_3d_render and full test suite. - Marked Task #50 as recurrent for future code hygiene.
Diffstat (limited to 'src')
-rw-r--r--src/3d/renderer.cc15
-rw-r--r--src/gpu/effects/shader_composer.cc48
-rw-r--r--src/gpu/effects/shader_composer.h6
-rw-r--r--src/gpu/effects/shaders.cc5
-rw-r--r--src/tests/test_shader_composer.cc55
5 files changed, 112 insertions, 17 deletions
diff --git a/src/3d/renderer.cc b/src/3d/renderer.cc
index 778509f..e4320f4 100644
--- a/src/3d/renderer.cc
+++ b/src/3d/renderer.cc
@@ -66,8 +66,8 @@ void Renderer3D::create_skybox_pipeline() {
const uint8_t* shader_code_asset =
GetAsset(AssetId::ASSET_SHADER_SKYBOX, nullptr);
- std::string shader_source = ShaderComposer::Get().Compose(
- {"common_uniforms"}, (const char*)shader_code_asset);
+ std::string shader_source =
+ ShaderComposer::Get().Compose({}, (const char*)shader_code_asset);
#if defined(DEMO_CROSS_COMPILE_WIN32)
WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
@@ -78,7 +78,7 @@ void Renderer3D::create_skybox_pipeline() {
#else
WGPUShaderSourceWGSL wgsl_desc = {};
wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
- wgsl_desc.code = {shader_source.c_str(), shader_source.length()};
+ wgsl_desc.code = str_view(shader_source.c_str());
WGPUShaderModuleDescriptor shader_desc = {};
shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain;
#endif
@@ -236,10 +236,9 @@ void Renderer3D::create_pipeline() {
WGPUPipelineLayout pipeline_layout =
wgpuDeviceCreatePipelineLayout(device_, &pl_desc);
- std::string main_code =
- (const char*)GetAsset(AssetId::ASSET_SHADER_RENDERER_3D);
- std::string shader_source = ShaderComposer::Get().Compose(
- {"common_uniforms", "sdf_primitives", "lighting", "ray_box"}, main_code);
+ const char* asset_data = (const char*)GetAsset(AssetId::ASSET_SHADER_RENDERER_3D);
+ std::string main_code = asset_data;
+ std::string shader_source = ShaderComposer::Get().Compose({}, main_code);
#if defined(DEMO_CROSS_COMPILE_WIN32)
WGPUShaderModuleWGSLDescriptor wgsl_desc = {};
@@ -250,7 +249,7 @@ void Renderer3D::create_pipeline() {
#else
WGPUShaderSourceWGSL wgsl_desc = {};
wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
- wgsl_desc.code = {shader_source.c_str(), shader_source.length()};
+ wgsl_desc.code = str_view(shader_source.c_str());
WGPUShaderModuleDescriptor shader_desc = {};
shader_desc.nextInChain = (const WGPUChainedStruct*)&wgsl_desc.chain;
#endif
diff --git a/src/gpu/effects/shader_composer.cc b/src/gpu/effects/shader_composer.cc
index 3e08df9..8d66ad7 100644
--- a/src/gpu/effects/shader_composer.cc
+++ b/src/gpu/effects/shader_composer.cc
@@ -2,6 +2,7 @@
// It implements the ShaderComposer class.
#include "gpu/effects/shader_composer.h"
+#include <set>
#include <sstream>
ShaderComposer& ShaderComposer::Get() {
@@ -14,22 +15,59 @@ void ShaderComposer::RegisterSnippet(const std::string& name,
snippets_[name] = code;
}
+void ShaderComposer::ResolveRecursive(const std::string& source,
+ std::stringstream& ss,
+ std::set<std::string>& included) {
+ std::istringstream stream(source);
+ std::string line;
+ while (std::getline(stream, line)) {
+ // Check for #include "snippet_name"
+ if (line.compare(0, 9, "#include ") == 0) {
+ size_t start = line.find('"');
+ size_t end = line.find('"', start + 1);
+ if (start != std::string::npos && end != std::string::npos) {
+ std::string name = line.substr(start + 1, end - start - 1);
+ if (included.find(name) == included.end()) {
+ included.insert(name);
+ auto it = snippets_.find(name);
+ if (it != snippets_.end()) {
+ ss << "// --- Included: " << name << " ---\n";
+ ResolveRecursive(it->second, ss, included);
+ ss << "// --- End Include: " << name << " ---\n";
+ } else {
+ ss << "// ERROR: Snippet not found: " << name << "\n";
+ }
+ }
+ }
+ } else {
+ ss << line << "\n";
+ }
+ }
+}
+
std::string
ShaderComposer::Compose(const std::vector<std::string>& dependencies,
const std::string& main_code) {
std::stringstream ss;
ss << "// Generated by ShaderComposer\n\n";
+ std::set<std::string> included;
+
+ // Process explicit dependencies first
for (const auto& dep : dependencies) {
- auto it = snippets_.find(dep);
- if (it != snippets_.end()) {
- ss << "// --- Snippet: " << dep << " ---\n";
- ss << it->second << "\n";
+ if (included.find(dep) == included.end()) {
+ included.insert(dep);
+ auto it = snippets_.find(dep);
+ if (it != snippets_.end()) {
+ ss << "// --- Dependency: " << dep << " ---\n";
+ ResolveRecursive(it->second, ss, included);
+ ss << "\n";
+ }
}
}
ss << "// --- Main Code ---\n";
- ss << main_code;
+ ResolveRecursive(main_code, ss, included);
return ss.str();
}
diff --git a/src/gpu/effects/shader_composer.h b/src/gpu/effects/shader_composer.h
index 49bf00c..a63a6a4 100644
--- a/src/gpu/effects/shader_composer.h
+++ b/src/gpu/effects/shader_composer.h
@@ -4,6 +4,7 @@
#pragma once
#include <map>
+#include <set>
#include <string>
#include <vector>
@@ -15,10 +16,15 @@ class ShaderComposer {
void RegisterSnippet(const std::string& name, const std::string& code);
// Assemble a final shader string by prepending required snippets
+ // and recursively resolving #include "snippet_name" directives.
std::string Compose(const std::vector<std::string>& dependencies,
const std::string& main_code);
private:
ShaderComposer() = default;
+
+ void ResolveRecursive(const std::string& source, std::stringstream& ss,
+ std::set<std::string>& included);
+
std::map<std::string, std::string> snippets_;
};
diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc
index b2d184d..7255cf5 100644
--- a/src/gpu/effects/shaders.cc
+++ b/src/gpu/effects/shaders.cc
@@ -31,6 +31,11 @@ void InitShaderComposer() {
};
register_if_exists("common_uniforms", AssetId::ASSET_SHADER_COMMON_UNIFORMS);
+ register_if_exists("math/sdf_shapes", AssetId::ASSET_SHADER_MATH_SDF_SHAPES);
+ register_if_exists("math/sdf_utils", AssetId::ASSET_SHADER_MATH_SDF_UTILS);
+ register_if_exists("render/shadows", AssetId::ASSET_SHADER_RENDER_SHADOWS);
+ register_if_exists("render/scene_query", AssetId::ASSET_SHADER_RENDER_SCENE_QUERY);
+ register_if_exists("render/lighting_utils", AssetId::ASSET_SHADER_RENDER_LIGHTING_UTILS);
register_if_exists("sdf_primitives", AssetId::ASSET_SHADER_SDF_PRIMITIVES);
diff --git a/src/tests/test_shader_composer.cc b/src/tests/test_shader_composer.cc
index 16dabba..1dd8298 100644
--- a/src/tests/test_shader_composer.cc
+++ b/src/tests/test_shader_composer.cc
@@ -26,12 +26,12 @@ void test_composition() {
std::string result = sc.Compose({"math", "util"}, main_code);
// Verify order and presence
- assert(result.find("Snippet: math") != std::string::npos);
- assert(result.find("Snippet: util") != std::string::npos);
+ assert(result.find("Dependency: math") != std::string::npos);
+ assert(result.find("Dependency: util") != std::string::npos);
assert(result.find("Main Code") != std::string::npos);
- size_t pos_math = result.find("Snippet: math");
- size_t pos_util = result.find("Snippet: util");
+ size_t pos_math = result.find("Dependency: math");
+ size_t pos_util = result.find("Dependency: util");
size_t pos_main = result.find("Main Code");
assert(pos_math < pos_util);
@@ -75,9 +75,56 @@ void test_asset_composition() {
std::cout << "Asset-based composition logic verified." << std::endl;
}
+void test_recursive_composition() {
+ std::cout << "Testing Recursive Shader Composition..." << std::endl;
+ auto& sc = ShaderComposer::Get();
+
+ sc.RegisterSnippet("base", "fn base() {}");
+ sc.RegisterSnippet("mid", "#include \"base\"\nfn mid() { base(); }");
+ sc.RegisterSnippet("top", "#include \"mid\"\n#include \"base\"\nfn top() { mid(); base(); }");
+
+ std::string main_code = "#include \"top\"\nfn main() { top(); }";
+ std::string result = sc.Compose({}, main_code);
+
+ // Verify each is included exactly once despite multiple includes
+ size_t count_base = 0;
+ size_t pos = result.find("fn base()");
+ while (pos != std::string::npos) {
+ count_base++;
+ pos = result.find("fn base()", pos + 1);
+ }
+ assert(count_base == 1);
+
+ assert(result.find("Included: top") != std::string::npos);
+ assert(result.find("Included: mid") != std::string::npos);
+ assert(result.find("Included: base") != std::string::npos);
+
+ std::cout << "Recursive composition logic verified." << std::endl;
+}
+
+void test_renderer_composition() {
+ std::cout << "Testing Renderer Shader Composition..." << std::endl;
+ auto& sc = ShaderComposer::Get();
+
+ sc.RegisterSnippet("common_uniforms", "struct GlobalUniforms { view_proj: mat4x4<f32> };");
+ sc.RegisterSnippet("math/sdf_shapes", "fn sdSphere() {}");
+ sc.RegisterSnippet("render/scene_query", "#include \"math/sdf_shapes\"\nfn map_scene() {}");
+
+ std::string main_code = "#include \"common_uniforms\"\n#include \"render/scene_query\"\nfn main() {}";
+ std::string result = sc.Compose({}, main_code);
+
+ assert(result.find("struct GlobalUniforms") != std::string::npos);
+ assert(result.find("fn sdSphere") != std::string::npos);
+ assert(result.find("fn map_scene") != std::string::npos);
+
+ std::cout << "Renderer composition logic verified." << std::endl;
+}
+
int main() {
test_composition();
test_asset_composition();
+ test_recursive_composition();
+ test_renderer_composition();
std::cout << "--- ALL SHADER COMPOSER TESTS PASSED ---" << std::endl;
return 0;
}