diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-09 13:59:07 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-09 13:59:07 +0100 |
| commit | 744bcadfe8f4bb1b2d4f1daf9f880fa511d65405 (patch) | |
| tree | 49ec05f0b4d6f04d04ab7904164fb602bcd7bee7 /src/gpu | |
| parent | c712874ece1ca7073904f5fb84cc866d28084de0 (diff) | |
feat(gpu): Phase 2 - Add gen_perlin and gen_grid GPU compute shaders
Complete Phase 2 implementation:
- gen_perlin.wgsl: FBM with configurable octaves, amplitude decay
- gen_grid.wgsl: Grid pattern with configurable spacing/thickness
- TextureManager extensions: create_gpu_perlin_texture(), create_gpu_grid_texture()
- Asset packer now validates gen_noise, gen_perlin, gen_grid for PROC_GPU()
- 3 compute pipelines (lazy-init on first use)
Shader parameters:
- gen_perlin: seed, frequency, amplitude, amplitude_decay, octaves (32 bytes)
- gen_grid: width, height, grid_size, thickness (16 bytes)
test_3d_render migration:
- Replaced CPU sky texture (gen_perlin) with GPU version
- Replaced CPU noise texture (gen_noise) with GPU version
- Added new GPU grid texture (256x256, 32px grid, 2px lines)
Size impact:
- gen_perlin.wgsl: ~200 bytes (compressed)
- gen_grid.wgsl: ~100 bytes (compressed)
- Total Phase 2 code: ~300 bytes
- Cumulative (Phase 1+2): ~600 bytes
Testing:
- All 34 tests passing (100%)
- test_gpu_procedural validates all generators
- test_3d_render uses 3 GPU textures (noise, perlin, grid)
Next: Phase 3 - Variable dimensions, async generation, pipeline caching
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/gpu')
| -rw-r--r-- | src/gpu/effects/shaders.cc | 8 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.h | 2 | ||||
| -rw-r--r-- | src/gpu/texture_manager.cc | 354 | ||||
| -rw-r--r-- | src/gpu/texture_manager.h | 10 |
4 files changed, 374 insertions, 0 deletions
diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc index 5c6dd37..6ed82d5 100644 --- a/src/gpu/effects/shaders.cc +++ b/src/gpu/effects/shaders.cc @@ -103,6 +103,14 @@ const char* gen_noise_compute_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_COMPUTE_GEN_NOISE); +const char* gen_perlin_compute_wgsl = + + SafeGetAsset(AssetId::ASSET_SHADER_COMPUTE_GEN_PERLIN); + +const char* gen_grid_compute_wgsl = + + SafeGetAsset(AssetId::ASSET_SHADER_COMPUTE_GEN_GRID); + const char* vignette_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_VIGNETTE); diff --git a/src/gpu/effects/shaders.h b/src/gpu/effects/shaders.h index b629e30..a0f91da 100644 --- a/src/gpu/effects/shaders.h +++ b/src/gpu/effects/shaders.h @@ -19,3 +19,5 @@ extern const char* distort_shader_wgsl; extern const char* chroma_aberration_shader_wgsl; extern const char* vignette_shader_wgsl; extern const char* gen_noise_compute_wgsl; +extern const char* gen_perlin_compute_wgsl; +extern const char* gen_grid_compute_wgsl; diff --git a/src/gpu/texture_manager.cc b/src/gpu/texture_manager.cc index aff106a..9a19957 100644 --- a/src/gpu/texture_manager.cc +++ b/src/gpu/texture_manager.cc @@ -22,6 +22,8 @@ void TextureManager::init(WGPUDevice device, WGPUQueue queue) { device_ = device; queue_ = queue; noise_compute_pipeline_ = nullptr; + perlin_compute_pipeline_ = nullptr; + grid_compute_pipeline_ = nullptr; } void TextureManager::shutdown() { @@ -34,6 +36,14 @@ void TextureManager::shutdown() { wgpuComputePipelineRelease(noise_compute_pipeline_); noise_compute_pipeline_ = nullptr; } + if (perlin_compute_pipeline_) { + wgpuComputePipelineRelease(perlin_compute_pipeline_); + perlin_compute_pipeline_ = nullptr; + } + if (grid_compute_pipeline_) { + wgpuComputePipelineRelease(grid_compute_pipeline_); + grid_compute_pipeline_ = nullptr; + } } void TextureManager::create_procedural_texture( @@ -297,6 +307,350 @@ void TextureManager::create_gpu_noise_texture( #endif } +void TextureManager::dispatch_perlin_compute(WGPUTexture target, + const GpuProceduralParams& params) { + // Lazy-init compute pipeline + if (!perlin_compute_pipeline_) { + extern const char* gen_perlin_compute_wgsl; + ShaderComposer& composer = ShaderComposer::Get(); + std::string resolved_shader = composer.Compose({}, gen_perlin_compute_wgsl); + + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(resolved_shader.c_str()); + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = &wgsl_src.chain; + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(device_, &shader_desc); + + WGPUBindGroupLayoutEntry bgl_entries[2] = {}; + bgl_entries[0].binding = 0; + bgl_entries[0].visibility = WGPUShaderStage_Compute; + bgl_entries[0].storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + bgl_entries[0].storageTexture.format = WGPUTextureFormat_RGBA8Unorm; + bgl_entries[0].storageTexture.viewDimension = WGPUTextureViewDimension_2D; + bgl_entries[1].binding = 1; + bgl_entries[1].visibility = WGPUShaderStage_Compute; + bgl_entries[1].buffer.type = WGPUBufferBindingType_Uniform; + bgl_entries[1].buffer.minBindingSize = 32; // sizeof(PerlinParams) + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 2; + bgl_desc.entries = bgl_entries; + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + + WGPUPipelineLayoutDescriptor pl_desc = {}; + pl_desc.bindGroupLayoutCount = 1; + pl_desc.bindGroupLayouts = &bind_group_layout; + WGPUPipelineLayout pipeline_layout = + wgpuDeviceCreatePipelineLayout(device_, &pl_desc); + + WGPUComputePipelineDescriptor pipeline_desc = {}; + pipeline_desc.layout = pipeline_layout; + pipeline_desc.compute.module = shader_module; + pipeline_desc.compute.entryPoint = str_view("main"); + + perlin_compute_pipeline_ = + wgpuDeviceCreateComputePipeline(device_, &pipeline_desc); + + wgpuPipelineLayoutRelease(pipeline_layout); + wgpuBindGroupLayoutRelease(bind_group_layout); + wgpuShaderModuleRelease(shader_module); + } + + // Create uniform buffer + struct PerlinParams { + uint32_t width; + uint32_t height; + float seed; + float frequency; + float amplitude; + float amplitude_decay; + uint32_t octaves; + float _pad0; + }; + PerlinParams uniform_data = { + (uint32_t)params.width, + (uint32_t)params.height, + params.params[0], // seed + params.params[1], // frequency + params.num_params > 2 ? params.params[2] : 1.0f, // amplitude + params.num_params > 3 ? params.params[3] : 0.5f, // amplitude_decay + params.num_params > 4 ? (uint32_t)params.params[4] : 4u, // octaves + 0.0f // padding + }; + + WGPUBufferDescriptor buf_desc = {}; + buf_desc.size = sizeof(PerlinParams); + buf_desc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst; + buf_desc.mappedAtCreation = WGPUOptionalBool_True; + WGPUBuffer uniform_buf = wgpuDeviceCreateBuffer(device_, &buf_desc); + void* mapped = wgpuBufferGetMappedRange(uniform_buf, 0, sizeof(PerlinParams)); + memcpy(mapped, &uniform_data, sizeof(PerlinParams)); + wgpuBufferUnmap(uniform_buf); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = WGPUTextureFormat_RGBA8Unorm; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = 1; + view_desc.arrayLayerCount = 1; + WGPUTextureView target_view = wgpuTextureCreateView(target, &view_desc); + + WGPUBindGroupLayoutEntry bgl_entries[2] = {}; + bgl_entries[0].binding = 0; + bgl_entries[0].visibility = WGPUShaderStage_Compute; + bgl_entries[0].storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + bgl_entries[0].storageTexture.format = WGPUTextureFormat_RGBA8Unorm; + bgl_entries[0].storageTexture.viewDimension = WGPUTextureViewDimension_2D; + bgl_entries[1].binding = 1; + bgl_entries[1].visibility = WGPUShaderStage_Compute; + bgl_entries[1].buffer.type = WGPUBufferBindingType_Uniform; + bgl_entries[1].buffer.minBindingSize = 32; + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 2; + bgl_desc.entries = bgl_entries; + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + + WGPUBindGroupEntry bg_entries[2] = {}; + bg_entries[0].binding = 0; + bg_entries[0].textureView = target_view; + bg_entries[1].binding = 1; + bg_entries[1].buffer = uniform_buf; + bg_entries[1].size = sizeof(PerlinParams); + + WGPUBindGroupDescriptor bg_desc = {}; + bg_desc.layout = bind_group_layout; + bg_desc.entryCount = 2; + bg_desc.entries = bg_entries; + WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device_, &bg_desc); + + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, &enc_desc); + WGPUComputePassEncoder pass = wgpuCommandEncoderBeginComputePass(encoder, nullptr); + wgpuComputePassEncoderSetPipeline(pass, perlin_compute_pipeline_); + wgpuComputePassEncoderSetBindGroup(pass, 0, bind_group, 0, nullptr); + wgpuComputePassEncoderDispatchWorkgroups(pass, (params.width + 7) / 8, + (params.height + 7) / 8, 1); + wgpuComputePassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_desc = {}; + WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, &cmd_desc); + wgpuQueueSubmit(queue_, 1, &cmd); + + wgpuCommandBufferRelease(cmd); + wgpuCommandEncoderRelease(encoder); + wgpuComputePassEncoderRelease(pass); + wgpuBindGroupRelease(bind_group); + wgpuBindGroupLayoutRelease(bind_group_layout); + wgpuBufferRelease(uniform_buf); + wgpuTextureViewRelease(target_view); +} + +void TextureManager::create_gpu_perlin_texture( + const std::string& name, const GpuProceduralParams& params) { + WGPUTextureDescriptor tex_desc = {}; + tex_desc.usage = + WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding; + tex_desc.dimension = WGPUTextureDimension_2D; + tex_desc.size = {(uint32_t)params.width, (uint32_t)params.height, 1}; + tex_desc.format = WGPUTextureFormat_RGBA8Unorm; + tex_desc.mipLevelCount = 1; + tex_desc.sampleCount = 1; + WGPUTexture texture = wgpuDeviceCreateTexture(device_, &tex_desc); + + dispatch_perlin_compute(texture, params); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = WGPUTextureFormat_RGBA8Unorm; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = 1; + view_desc.arrayLayerCount = 1; + WGPUTextureView view = wgpuTextureCreateView(texture, &view_desc); + + GpuTexture gpu_tex; + gpu_tex.texture = texture; + gpu_tex.view = view; + gpu_tex.width = params.width; + gpu_tex.height = params.height; + textures_[name] = gpu_tex; + +#if !defined(STRIP_ALL) + printf("Generated GPU perlin texture: %s (%dx%d)\n", name.c_str(), + params.width, params.height); +#endif +} + +void TextureManager::dispatch_grid_compute(WGPUTexture target, + const GpuProceduralParams& params) { + // Lazy-init compute pipeline + if (!grid_compute_pipeline_) { + extern const char* gen_grid_compute_wgsl; + ShaderComposer& composer = ShaderComposer::Get(); + std::string resolved_shader = composer.Compose({}, gen_grid_compute_wgsl); + + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(resolved_shader.c_str()); + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = &wgsl_src.chain; + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(device_, &shader_desc); + + WGPUBindGroupLayoutEntry bgl_entries[2] = {}; + bgl_entries[0].binding = 0; + bgl_entries[0].visibility = WGPUShaderStage_Compute; + bgl_entries[0].storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + bgl_entries[0].storageTexture.format = WGPUTextureFormat_RGBA8Unorm; + bgl_entries[0].storageTexture.viewDimension = WGPUTextureViewDimension_2D; + bgl_entries[1].binding = 1; + bgl_entries[1].visibility = WGPUShaderStage_Compute; + bgl_entries[1].buffer.type = WGPUBufferBindingType_Uniform; + bgl_entries[1].buffer.minBindingSize = 16; // sizeof(GridParams) + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 2; + bgl_desc.entries = bgl_entries; + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + + WGPUPipelineLayoutDescriptor pl_desc = {}; + pl_desc.bindGroupLayoutCount = 1; + pl_desc.bindGroupLayouts = &bind_group_layout; + WGPUPipelineLayout pipeline_layout = + wgpuDeviceCreatePipelineLayout(device_, &pl_desc); + + WGPUComputePipelineDescriptor pipeline_desc = {}; + pipeline_desc.layout = pipeline_layout; + pipeline_desc.compute.module = shader_module; + pipeline_desc.compute.entryPoint = str_view("main"); + + grid_compute_pipeline_ = + wgpuDeviceCreateComputePipeline(device_, &pipeline_desc); + + wgpuPipelineLayoutRelease(pipeline_layout); + wgpuBindGroupLayoutRelease(bind_group_layout); + wgpuShaderModuleRelease(shader_module); + } + + // Create uniform buffer + struct GridParams { + uint32_t width; + uint32_t height; + uint32_t grid_size; + uint32_t thickness; + }; + GridParams uniform_data = { + (uint32_t)params.width, + (uint32_t)params.height, + params.num_params > 0 ? (uint32_t)params.params[0] : 32u, // grid_size + params.num_params > 1 ? (uint32_t)params.params[1] : 2u // thickness + }; + + WGPUBufferDescriptor buf_desc = {}; + buf_desc.size = sizeof(GridParams); + buf_desc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst; + buf_desc.mappedAtCreation = WGPUOptionalBool_True; + WGPUBuffer uniform_buf = wgpuDeviceCreateBuffer(device_, &buf_desc); + void* mapped = wgpuBufferGetMappedRange(uniform_buf, 0, sizeof(GridParams)); + memcpy(mapped, &uniform_data, sizeof(GridParams)); + wgpuBufferUnmap(uniform_buf); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = WGPUTextureFormat_RGBA8Unorm; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = 1; + view_desc.arrayLayerCount = 1; + WGPUTextureView target_view = wgpuTextureCreateView(target, &view_desc); + + WGPUBindGroupLayoutEntry bgl_entries[2] = {}; + bgl_entries[0].binding = 0; + bgl_entries[0].visibility = WGPUShaderStage_Compute; + bgl_entries[0].storageTexture.access = WGPUStorageTextureAccess_WriteOnly; + bgl_entries[0].storageTexture.format = WGPUTextureFormat_RGBA8Unorm; + bgl_entries[0].storageTexture.viewDimension = WGPUTextureViewDimension_2D; + bgl_entries[1].binding = 1; + bgl_entries[1].visibility = WGPUShaderStage_Compute; + bgl_entries[1].buffer.type = WGPUBufferBindingType_Uniform; + bgl_entries[1].buffer.minBindingSize = 16; + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 2; + bgl_desc.entries = bgl_entries; + WGPUBindGroupLayout bind_group_layout = + wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); + + WGPUBindGroupEntry bg_entries[2] = {}; + bg_entries[0].binding = 0; + bg_entries[0].textureView = target_view; + bg_entries[1].binding = 1; + bg_entries[1].buffer = uniform_buf; + bg_entries[1].size = sizeof(GridParams); + + WGPUBindGroupDescriptor bg_desc = {}; + bg_desc.layout = bind_group_layout; + bg_desc.entryCount = 2; + bg_desc.entries = bg_entries; + WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device_, &bg_desc); + + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, &enc_desc); + WGPUComputePassEncoder pass = wgpuCommandEncoderBeginComputePass(encoder, nullptr); + wgpuComputePassEncoderSetPipeline(pass, grid_compute_pipeline_); + wgpuComputePassEncoderSetBindGroup(pass, 0, bind_group, 0, nullptr); + wgpuComputePassEncoderDispatchWorkgroups(pass, (params.width + 7) / 8, + (params.height + 7) / 8, 1); + wgpuComputePassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_desc = {}; + WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, &cmd_desc); + wgpuQueueSubmit(queue_, 1, &cmd); + + wgpuCommandBufferRelease(cmd); + wgpuCommandEncoderRelease(encoder); + wgpuComputePassEncoderRelease(pass); + wgpuBindGroupRelease(bind_group); + wgpuBindGroupLayoutRelease(bind_group_layout); + wgpuBufferRelease(uniform_buf); + wgpuTextureViewRelease(target_view); +} + +void TextureManager::create_gpu_grid_texture( + const std::string& name, const GpuProceduralParams& params) { + WGPUTextureDescriptor tex_desc = {}; + tex_desc.usage = + WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding; + tex_desc.dimension = WGPUTextureDimension_2D; + tex_desc.size = {(uint32_t)params.width, (uint32_t)params.height, 1}; + tex_desc.format = WGPUTextureFormat_RGBA8Unorm; + tex_desc.mipLevelCount = 1; + tex_desc.sampleCount = 1; + WGPUTexture texture = wgpuDeviceCreateTexture(device_, &tex_desc); + + dispatch_grid_compute(texture, params); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = WGPUTextureFormat_RGBA8Unorm; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = 1; + view_desc.arrayLayerCount = 1; + WGPUTextureView view = wgpuTextureCreateView(texture, &view_desc); + + GpuTexture gpu_tex; + gpu_tex.texture = texture; + gpu_tex.view = view; + gpu_tex.width = params.width; + gpu_tex.height = params.height; + textures_[name] = gpu_tex; + +#if !defined(STRIP_ALL) + printf("Generated GPU grid texture: %s (%dx%d)\n", name.c_str(), + params.width, params.height); +#endif +} + #if !defined(STRIP_ALL) WGPUTextureView TextureManager::get_or_generate_gpu_texture( const std::string& name, const GpuProceduralParams& params) { diff --git a/src/gpu/texture_manager.h b/src/gpu/texture_manager.h index 0cffe0c..b2dea84 100644 --- a/src/gpu/texture_manager.h +++ b/src/gpu/texture_manager.h @@ -46,6 +46,10 @@ class TextureManager { // GPU procedural generation void create_gpu_noise_texture(const std::string& name, const GpuProceduralParams& params); + void create_gpu_perlin_texture(const std::string& name, + const GpuProceduralParams& params); + void create_gpu_grid_texture(const std::string& name, + const GpuProceduralParams& params); #if !defined(STRIP_ALL) // On-demand lazy generation (stripped in final builds) @@ -59,8 +63,14 @@ class TextureManager { private: void dispatch_noise_compute(WGPUTexture target, const GpuProceduralParams& params); + void dispatch_perlin_compute(WGPUTexture target, + const GpuProceduralParams& params); + void dispatch_grid_compute(WGPUTexture target, + const GpuProceduralParams& params); WGPUDevice device_; WGPUQueue queue_; std::map<std::string, GpuTexture> textures_; WGPUComputePipeline noise_compute_pipeline_ = nullptr; + WGPUComputePipeline perlin_compute_pipeline_ = nullptr; + WGPUComputePipeline grid_compute_pipeline_ = nullptr; }; |
