diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-04 11:56:03 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-04 11:56:03 +0100 |
| commit | 2e34965e5e48175c5ee6016af1a858f7f3f02c1d (patch) | |
| tree | a41ea2fdc50a18183742bffaf0705b394f2be69f /src | |
| parent | 0d29f340b9de8f6a14baa0a2c6f3c57b8d1458a5 (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.cc | 15 | ||||
| -rw-r--r-- | src/gpu/effects/shader_composer.cc | 48 | ||||
| -rw-r--r-- | src/gpu/effects/shader_composer.h | 6 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.cc | 5 | ||||
| -rw-r--r-- | src/tests/test_shader_composer.cc | 55 |
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; } |
