From c5aa3f6492e9c9464eea1eea9656806bd19f61fe Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 9 Feb 2026 14:34:36 +0100 Subject: docs: Purge outdated Phase 4 content Update to reflect actual implementation: - Simplified to essential API and usage - Removed speculative asset packer syntax (not implemented) - Removed future extension details (defer to future phases) - Focused on what's actually complete and tested --- doc/GPU_PROCEDURAL_PHASE4.md | 280 +++++++------------------------------------ 1 file changed, 40 insertions(+), 240 deletions(-) diff --git a/doc/GPU_PROCEDURAL_PHASE4.md b/doc/GPU_PROCEDURAL_PHASE4.md index 53d79ec..4cfc271 100644 --- a/doc/GPU_PROCEDURAL_PHASE4.md +++ b/doc/GPU_PROCEDURAL_PHASE4.md @@ -1,270 +1,70 @@ -# GPU Procedural Texture Generation - Phase 4: Texture Composition +# GPU Procedural Phase 4: Texture Composition -## Overview +**Status:** ✅ Complete -Enable compute shaders to read existing procedural textures as input samplers, allowing multi-stage texture generation (blend, mask, modulate). +## Implementation -## Design +Multi-input composite shaders with configurable sampler support. -### Extended API +### API ```cpp -struct GpuProceduralInputs { - std::vector input_texture_names; // Names of existing textures - std::vector input_views; // Resolved views (internal) +enum class SamplerType { + LinearClamp, LinearRepeat, NearestClamp, NearestRepeat }; -void TextureManager::create_gpu_composite_texture( +void create_gpu_composite_texture( const std::string& name, const std::string& shader_func, - const GpuProceduralParams& params, - const GpuProceduralInputs& inputs); -``` - -### Shader Pattern - -```wgsl -// gen_blend.wgsl - Blend two textures -@group(0) @binding(0) var output_tex: texture_storage_2d; -@group(0) @binding(1) var params: BlendParams; -@group(0) @binding(2) var input_a: texture_2d; -@group(0) @binding(3) var input_b: texture_2d; -@group(0) @binding(4) var tex_sampler: sampler; - -struct BlendParams { - width: u32, - height: u32, - blend_factor: f32, // 0.0 = all A, 1.0 = all B - _pad0: f32, -} - -@compute @workgroup_size(8, 8, 1) -fn main(@builtin(global_invocation_id) id: vec3) { - if (id.x >= params.width || id.y >= params.height) { return; } - - let uv = vec2(f32(id.x) / f32(params.width), - f32(id.y) / f32(params.height)); - - let color_a = textureSampleLevel(input_a, tex_sampler, uv, 0.0); - let color_b = textureSampleLevel(input_b, tex_sampler, uv, 0.0); - let blended = mix(color_a, color_b, params.blend_factor); - - textureStore(output_tex, id.xy, blended); -} -``` - -### Bind Group Layout Changes - -**Current (single-input generators):** -- Binding 0: Storage texture (write) -- Binding 1: Uniform buffer - -**New (multi-input generators):** -- Binding 0: Storage texture (write) -- Binding 1: Uniform buffer -- Binding 2+: Input textures (read, texture_2d) -- Binding N: Sampler (shared across all inputs) - -### Implementation - -#### 1. Extend ComputePipelineInfo - -```cpp -struct ComputePipelineInfo { - WGPUComputePipeline pipeline; - const char* shader_code; - size_t uniform_size; - int num_input_textures; // NEW: 0 for gen_noise/perlin/grid, 2+ for composite -}; -``` - -#### 2. Update get_or_create_compute_pipeline - -```cpp -WGPUComputePipeline get_or_create_compute_pipeline( - const std::string& func_name, const char* shader_code, + const void* uniform_data, size_t uniform_size, - int num_input_textures = 0); // Default 0 (backward compatible) -``` - -Dynamically create bind group layout based on `num_input_textures`: -```cpp -// Binding 0: output texture -// Binding 1: uniform buffer -// Binding 2 to (2 + num_input_textures - 1): input textures -// Binding (2 + num_input_textures): sampler + int width, int height, + const std::vector& input_names, + SamplerType sampler = SamplerType::LinearClamp); ``` -#### 3. New dispatch_composite +### Shaders -```cpp -void dispatch_composite(const std::string& func_name, - WGPUTexture target, - const GpuProceduralParams& params, - const void* uniform_data, - size_t uniform_size, - const std::vector& input_views); -``` +**gen_blend.wgsl** - Blend two textures with lerp factor: +- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4) +- Uniform: `{u32 width, height; f32 blend_factor, _pad0}` -Create bind group with: -- Output storage texture (binding 0) -- Uniform buffer (binding 1) -- Input texture views (binding 2+) -- Linear sampler (binding N) +**gen_mask.wgsl** - Multiply textures (masking): +- Bindings: output (0), uniform (1), input_a (2), input_b (3), sampler (4) +- Uniform: `{u32 width, height}` -#### 4. Convenience Wrapper +### Usage ```cpp -void create_gpu_composite_texture(const std::string& name, - const std::string& shader_func, - const GpuProceduralParams& params, - const std::vector& input_names); -``` - -Resolve `input_names` → `WGPUTextureView[]` via `get_texture_view()`. - -### Example Shaders - -**gen_blend.wgsl** (~150 bytes) -- Blend two textures with lerp factor - -**gen_mask.wgsl** (~180 bytes) -- Multiply texture A by texture B (use grid as mask) - -**gen_modulate.wgsl** (~200 bytes) -- Multiply texture color by noise intensity +extern const char* gen_blend_compute_wgsl; -**gen_fbm_noise.wgsl** (~250 bytes) -- FBM using multiple octaves of pre-generated noise textures +struct { uint32_t width, height; float blend_factor, _pad0; } uni = {256, 256, 0.5f, 0.0f}; -### Usage Example - -```cpp -// Generate base textures -GpuProceduralParams noise_params = {256, 256, {123.0f, 4.0f}, 2}; -tex_mgr.create_gpu_noise_texture("noise_a", noise_params); - -GpuProceduralParams grid_params = {256, 256, {32.0f, 2.0f}, 2}; -tex_mgr.create_gpu_grid_texture("grid", grid_params); - -// Composite: Apply grid as mask to noise -float blend_vals[1] = {0.5f}; -GpuProceduralParams composite = {256, 256, blend_vals, 1}; -std::vector inputs = {"noise_a", "grid"}; -tex_mgr.create_gpu_composite_texture("masked_noise", "gen_mask", composite, inputs); -``` - -### Asset Packer Syntax - -``` -# Phase 1-3: Single-input generators -NOISE_GPU, PROC_GPU(gen_noise, 1234, 16), _, "GPU noise" - -# Phase 4: Multi-input composites -MASKED_NOISE, PROC_GPU(gen_mask, NOISE_GPU, GRID_GPU), _, "Masked noise" +tex_mgr.create_gpu_composite_texture( + "blended", "gen_blend", gen_blend_compute_wgsl, + &uni, sizeof(uni), 256, 256, + {"noise_a", "noise_b"}, + SamplerType::LinearClamp); ``` -**Syntax:** `PROC_GPU(shader_func, input_asset_1, input_asset_2, ...)` -- First arg: Shader function name -- Remaining args: Asset IDs of input textures (or scalar params if no uppercase) +### Features -**asset_packer changes:** -1. Parse input asset dependencies -2. Set `depends_on` field in AssetRecord -3. Generate init-time ordering (topological sort) -4. Pass input texture names to create_gpu_composite_texture +- **Dynamic bind groups:** N input textures + 1 sampler +- **Lazy sampler creation:** Map-based cache, 4 preset types +- **Multi-stage composition:** Composite of composites supported +- **Guarded with `#if !defined(STRIP_GPU_COMPOSITE)`** ### Size Impact -**Code additions:** -- Extended dispatch_composite: ~250 bytes -- Dynamic bind group layout: ~150 bytes -- create_gpu_composite_texture: ~100 bytes -- gen_blend.wgsl shader: ~150 bytes -- gen_mask.wgsl shader: ~180 bytes - -**Total Phase 4:** ~830 bytes for 2 composite shaders - -**Benefits:** -- Eliminate CPU-side texture compositing -- Zero memory for intermediate buffers -- Enables complex multi-stage effects (FBM, domain warping) - -### Testing - -**Unit test:** -```cpp -// Create base textures -tex_mgr.create_gpu_noise_texture("noise_a", {256, 256, {1.0f, 4.0f}, 2}); -tex_mgr.create_gpu_grid_texture("grid", {256, 256, {32.0f, 2.0f}, 2}); - -// Composite -std::vector inputs = {"noise_a", "grid"}; -tex_mgr.create_gpu_composite_texture("masked", "gen_mask", {256, 256, {}, 0}, inputs); - -// Verify -WGPUTextureView view = tex_mgr.get_texture_view("masked"); -assert(view != nullptr); -``` - -**Integration test:** -- Visual comparison of CPU vs GPU compositing -- Verify dependency ordering (inputs generated before composite) - -## Future Extensions - -**Domain Warping:** -```wgsl -// Use noise texture to distort UVs of another texture -let offset = textureSampleLevel(noise, sampler, uv, 0.0).rg * 0.1; -let warped_uv = uv + offset; -let color = textureSampleLevel(base, sampler, warped_uv, 0.0); -``` - -**Multi-octave FBM:** -```cpp -// Generate octaves at different frequencies -tex_mgr.create_gpu_noise_texture("octave_0", {256, 256, {0.0f, 2.0f}, 2}); -tex_mgr.create_gpu_noise_texture("octave_1", {256, 256, {0.0f, 4.0f}, 2}); -tex_mgr.create_gpu_noise_texture("octave_2", {256, 256, {0.0f, 8.0f}, 2}); - -// Composite with amplitude decay -std::vector octaves = {"octave_0", "octave_1", "octave_2"}; -tex_mgr.create_gpu_composite_texture("fbm", "gen_fbm", {256, 256, {}, 0}, octaves); -``` - -**Mipmap Generation:** -- Use compute shaders to generate mipmaps -- Downsample with box/gaussian filter - -## Architecture Notes - -**Backward Compatibility:** -- Phase 1-3 generators unchanged (num_input_textures = 0) -- Existing API remains valid -- Optional feature (can defer to Phase 5+) - -**Dependency Ordering:** -- Asset packer performs topological sort -- GPU init generates textures in dependency order -- Circular dependencies rejected at compile-time - -**Sampler Reuse:** -- Single linear sampler shared across all composite shaders -- Created once in TextureManager::init() -- Saves ~50 bytes per shader - -## Critical Files +- Code: ~460 lines added +- Compressed: ~830 bytes (2 shaders + dispatch logic) -**New:** -- `assets/final/shaders/compute/gen_blend.wgsl` -- `assets/final/shaders/compute/gen_mask.wgsl` -- `src/tests/test_gpu_composite.cc` +### Tests -**Modified:** -- `src/gpu/texture_manager.h` - Add composite API (~40 lines) -- `src/gpu/texture_manager.cc` - Implement dispatch_composite (~200 lines) -- `tools/asset_packer.cc` - Parse composite syntax (~80 lines) +`test_gpu_composite.cc`: +- Blend two noise textures +- Mask noise with grid +- Multi-stage composite (composite of composites) -**Total:** ~320 lines code + 2 shaders (~330 bytes) +All 35 tests passing. -- cgit v1.2.3