diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-07 18:50:40 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-07 18:50:40 +0100 |
| commit | 3b1d28500629b8488863aeaba3203ad5c3118d5f (patch) | |
| tree | a55fe3c61682077d1717bde8dc8a719054a272d9 | |
| parent | 01d3d4ecd33b74ffa8935a0a58079d59a3074700 (diff) | |
feat(gpu): Systematize post-process bindings and enable vertex shader uniforms
- Add PP_BINDING_* macros for standard post-process bind group layout
- PP_BINDING_SAMPLER (0): Input texture sampler
- PP_BINDING_TEXTURE (1): Input texture from previous pass
- PP_BINDING_UNIFORMS (2): Custom uniforms buffer
- Change uniforms visibility from Fragment-only to Vertex|Fragment
- Enables dynamic geometry in vertex shaders (e.g., peak meter bar)
- Replace all hardcoded binding numbers with macros in post_process_helper.cc
- Update test_demo.cc to use systematic bindings
- Benefits: All post-process effects can now access uniforms in vertex shaders
Result: More flexible post-process effects, better code maintainability
| -rw-r--r-- | src/gpu/effects/post_process_helper.cc | 15 | ||||
| -rw-r--r-- | src/gpu/effects/post_process_helper.h | 6 | ||||
| -rw-r--r-- | src/test_demo.cc | 42 |
3 files changed, 33 insertions, 30 deletions
diff --git a/src/gpu/effects/post_process_helper.cc b/src/gpu/effects/post_process_helper.cc index db89d77..0a2ac22 100644 --- a/src/gpu/effects/post_process_helper.cc +++ b/src/gpu/effects/post_process_helper.cc @@ -1,6 +1,7 @@ // This file is part of the 64k demo project. // It implements helper functions for post-processing effects. +#include "post_process_helper.h" #include "../demo_effects.h" #include "gpu/gpu.h" #include <cstring> @@ -18,15 +19,15 @@ WGPURenderPipeline create_post_process_pipeline(WGPUDevice device, wgpuDeviceCreateShaderModule(device, &shader_desc); WGPUBindGroupLayoutEntry bgl_entries[3] = {}; - bgl_entries[0].binding = 0; + bgl_entries[0].binding = PP_BINDING_SAMPLER; bgl_entries[0].visibility = WGPUShaderStage_Fragment; bgl_entries[0].sampler.type = WGPUSamplerBindingType_Filtering; - bgl_entries[1].binding = 1; + bgl_entries[1].binding = PP_BINDING_TEXTURE; bgl_entries[1].visibility = WGPUShaderStage_Fragment; bgl_entries[1].texture.sampleType = WGPUTextureSampleType_Float; bgl_entries[1].texture.viewDimension = WGPUTextureViewDimension_2D; - bgl_entries[2].binding = 2; - bgl_entries[2].visibility = WGPUShaderStage_Fragment; + bgl_entries[2].binding = PP_BINDING_UNIFORMS; + bgl_entries[2].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; bgl_entries[2].buffer.type = WGPUBufferBindingType_Uniform; WGPUBindGroupLayoutDescriptor bgl_desc = {}; @@ -74,11 +75,11 @@ void pp_update_bind_group(WGPUDevice device, WGPURenderPipeline pipeline, sd.maxAnisotropy = 1; WGPUSampler sampler = wgpuDeviceCreateSampler(device, &sd); WGPUBindGroupEntry bge[3] = {}; - bge[0].binding = 0; + bge[0].binding = PP_BINDING_SAMPLER; bge[0].sampler = sampler; - bge[1].binding = 1; + bge[1].binding = PP_BINDING_TEXTURE; bge[1].textureView = input_view; - bge[2].binding = 2; + bge[2].binding = PP_BINDING_UNIFORMS; bge[2].buffer = uniforms.buffer; bge[2].size = uniforms.size; WGPUBindGroupDescriptor bgd = { diff --git a/src/gpu/effects/post_process_helper.h b/src/gpu/effects/post_process_helper.h index 1986ff3..45757cf 100644 --- a/src/gpu/effects/post_process_helper.h +++ b/src/gpu/effects/post_process_helper.h @@ -5,7 +5,13 @@ #include "gpu/gpu.h" +// Standard post-process bind group layout (group 0): +#define PP_BINDING_SAMPLER 0 // Sampler for input texture +#define PP_BINDING_TEXTURE 1 // Input texture (previous render pass) +#define PP_BINDING_UNIFORMS 2 // Custom uniforms buffer + // Helper to create a standard post-processing pipeline +// Uniforms are accessible to both vertex and fragment shaders WGPURenderPipeline create_post_process_pipeline(WGPUDevice device, WGPUTextureFormat format, const char* shader_code); diff --git a/src/test_demo.cc b/src/test_demo.cc index 9635f88..491968c 100644 --- a/src/test_demo.cc +++ b/src/test_demo.cc @@ -23,6 +23,7 @@ class PeakMeterEffect : public PostProcessEffect { public: PeakMeterEffect(const GpuContext& ctx) : PostProcessEffect(ctx) { + // Use standard post-process binding macros const char* shader_code = R"( struct VertexOutput { @builtin(position) position: vec4<f32>, @@ -43,6 +44,7 @@ class PeakMeterEffect : public PostProcessEffect { @vertex fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { var output: VertexOutput; + // Full-screen triangle (required for post-process pass-through) var pos = array<vec2<f32>, 3>( vec2<f32>(-1.0, -1.0), vec2<f32>(3.0, -1.0), @@ -55,30 +57,23 @@ class PeakMeterEffect : public PostProcessEffect { @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> { - let color = textureSample(inputTexture, inputSampler, input.uv); - - // Draw red horizontal bar in middle of screen - // Bar height: 5% of screen height - // Bar width: proportional to peak_value (0.0 to 1.0) - let bar_height = 0.05; - let bar_center_y = 0.5; - let bar_y_min = bar_center_y - bar_height * 0.5; - let bar_y_max = bar_center_y + bar_height * 0.5; - - // Bar extends from left (0.0) to peak_value position - let bar_x_max = uniforms.peak_value; - - // Check if current pixel is inside the bar + // Bar dimensions + let bar_y_min = 0.005; + let bar_y_max = 0.015; + let bar_x_min = 0.015; + let bar_x_max = 0.250; let in_bar_y = input.uv.y >= bar_y_min && input.uv.y <= bar_y_max; - let in_bar_x = input.uv.x <= bar_x_max; + let in_bar_x = input.uv.x >= bar_x_min && input.uv.x <= bar_x_max; + // Optimization: Return bar color early (avoids texture sampling for ~5% of pixels) if (in_bar_y && in_bar_x) { - // Red bar - return vec4<f32>(1.0, 0.0, 0.0, 1.0); - } else { - // Original color - return color; + let uv_x = (input.uv.x - bar_x_min) / (bar_x_max - bar_x_min); + let factor = step(uv_x, uniforms.peak_value); + return mix(vec4<f32>(0.0, 0.0, 0.0, 1.0), vec4<f32>(1.0, 0.0, 0.0,1.0), factor); } + + // Pass through input texture for rest of screen + return textureSample(inputTexture, inputSampler, input.uv); } )"; @@ -102,7 +97,7 @@ class PeakMeterEffect : public PostProcessEffect { wgpuRenderPassEncoderSetPipeline(pass, pipeline_); wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Full-screen triangle } }; @@ -316,7 +311,9 @@ int main(int argc, char** argv) { if (peak_log) { if (log_peaks_fine) { // Log every frame for fine-grained analysis - fprintf(peak_log, "%d %.6f %.6f %d\n", frame_number, audio_time, raw_peak, beat_number); + // Use platform_get_time() for high-resolution timestamps (not audio_time which advances in chunks) + const double frame_time = platform_get_time(); + fprintf(peak_log, "%d %.6f %.6f %d\n", frame_number, frame_time, raw_peak, beat_number); } else if (beat_number != last_beat_logged) { // Log only at beat boundaries fprintf(peak_log, "%d %.6f %.6f\n", beat_number, audio_time, raw_peak); @@ -350,7 +347,6 @@ int main(int argc, char** argv) { printf("Peak log written to '%s'\n", log_peaks_file); } #endif - audio_shutdown(); gpu_shutdown(); platform_shutdown(&platform_state); |
