// Peak meter overlay for audio debugging #include "effects/peak_meter_effect.h" #include "gpu/post_process_helper.h" #include "gpu/shader_composer.h" #include "util/fatal_error.h" PeakMeter::PeakMeter(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs) : Effect(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr) { HEADLESS_RETURN_IF_NULL(ctx_.device); uniforms_buffer_.init(ctx_.device); const char* shader_main = R"( struct VertexOutput { @builtin(position) position: vec4, @location(0) uv: vec2, }; @group(0) @binding(0) var inputSampler: sampler; @group(0) @binding(1) var inputTexture: texture_2d; @group(0) @binding(2) var uniforms: CommonUniforms; @vertex fn vs_main(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput { var output: VertexOutput; var pos = array, 3>( vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0) ); output.position = vec4(pos[vertexIndex], 0.0, 1.0); output.uv = pos[vertexIndex] * 0.5 + 0.5; return output; } @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4 { 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_min && input.uv.x <= bar_x_max; if (in_bar_y && in_bar_x) { let uv_x = (input.uv.x - bar_x_min) / (bar_x_max - bar_x_min); let factor = step(uv_x, uniforms.audio_intensity); return mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(1.0, 0.0, 0.0, 1.0), factor); } return textureSample(inputTexture, inputSampler, input.uv); } )"; std::string shader_code = ShaderComposer::Get().Compose({"common_uniforms"}, shader_main); pipeline_ = create_post_process_pipeline( ctx_.device, WGPUTextureFormat_RGBA8Unorm, shader_code.c_str()); } PeakMeter::~PeakMeter() { if (bind_group_) wgpuBindGroupRelease(bind_group_); if (pipeline_) wgpuRenderPipelineRelease(pipeline_); } void PeakMeter::render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) { WGPUTextureView input_view = nodes.get_view(input_nodes_[0]); WGPUTextureView output_view = nodes.get_view(output_nodes_[0]); uniforms_buffer_.update(ctx_.queue, params); pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view, uniforms_buffer_.get(), {nullptr, 0}); WGPURenderPassColorAttachment color_attachment = {}; color_attachment.view = output_view; color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; color_attachment.loadOp = WGPULoadOp_Load; color_attachment.storeOp = WGPUStoreOp_Store; color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; WGPURenderPassDescriptor pass_desc = {}; pass_desc.colorAttachmentCount = 1; pass_desc.colorAttachments = &color_attachment; WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); wgpuRenderPassEncoderSetPipeline(pass, pipeline_); wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); wgpuRenderPassEncoderEnd(pass); wgpuRenderPassEncoderRelease(pass); }