use std::collections::HashMap; use bytemuck::{Pod, Zeroable}; use wgpu::util::DeviceExt; use crate::{GpuError, context::GpuContext}; pub struct ApplyEffectsOptions<'a> { pub source: &'a wgpu::Texture, pub width: u32, pub height: u32, pub passes: &'a [EffectPass], } #[derive(Clone, Debug)] pub struct EffectPass { pub shader: String, pub uniforms: HashMap, } #[derive(Clone, Debug)] pub enum UniformValue { Number(f32), Vector(Vec), } #[repr(C)] #[derive(Clone, Copy, Pod, Zeroable)] struct EffectUniformBuffer { resolution: [f32; 2], direction: [f32; 2], scalars: [f32; 4], } pub fn apply_effects( context: &GpuContext, ApplyEffectsOptions { source, width, height, passes, }: ApplyEffectsOptions<'_>, ) -> Result { let mut current_texture: Option = None; for pass in passes { let input_texture = current_texture.as_ref().unwrap_or(source); let output_texture = context.create_render_texture(width, height, "gpu-effect-pass-output"); let input_view = input_texture.create_view(&wgpu::TextureViewDescriptor::default()); let output_view = output_texture.create_view(&wgpu::TextureViewDescriptor::default()); let texture_bind_group = context .device() .create_bind_group(&wgpu::BindGroupDescriptor { label: Some("gpu-effect-texture-bind-group"), layout: context .shader_registry() .effect_texture_bind_group_layout(), entries: &[ wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::TextureView(&input_view), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Sampler(context.linear_sampler()), }, ], }); let uniform_buffer = context .device() .create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("gpu-effect-uniform-buffer"), contents: bytemuck::bytes_of(&pack_effect_uniforms(pass, width, height)?), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); let uniform_bind_group = context .device() .create_bind_group(&wgpu::BindGroupDescriptor { label: Some("gpu-effect-uniform-bind-group"), layout: context .shader_registry() .effect_uniform_bind_group_layout(), entries: &[wgpu::BindGroupEntry { binding: 0, resource: uniform_buffer.as_entire_binding(), }], }); let pipeline = context.shader_registry().get_effect_pipeline(&pass.shader)?; let mut encoder = context .device() .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("gpu-effect-command-encoder"), }); { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("gpu-effect-render-pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &output_view, resolve_target: None, depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, multiview_mask: None, }); render_pass.set_pipeline(pipeline); render_pass.set_vertex_buffer(0, context.fullscreen_quad().slice(..)); render_pass.set_bind_group(0, &texture_bind_group, &[]); render_pass.set_bind_group(1, &uniform_bind_group, &[]); render_pass.draw(0..6, 0..1); } context.queue().submit([encoder.finish()]); current_texture = Some(output_texture); } current_texture.ok_or_else(|| GpuError::UnknownEffectShader { shader: "missing-effect-pass".to_string(), }) } fn pack_effect_uniforms( pass: &EffectPass, width: u32, height: u32, ) -> Result { let shader = pass.shader.as_str(); let sigma = read_number_uniform(pass, "u_sigma")?; let step = read_number_uniform(pass, "u_step")?; let direction = read_vec2_uniform(pass, "u_direction")?; for uniform in pass.uniforms.keys() { if uniform == "u_sigma" || uniform == "u_step" || uniform == "u_direction" { continue; } return Err(GpuError::UnsupportedUniform { shader: shader.to_string(), uniform: uniform.clone(), }); } Ok(EffectUniformBuffer { resolution: [width as f32, height as f32], direction, scalars: [sigma, step, 0.0, 0.0], }) } fn read_number_uniform(pass: &EffectPass, uniform: &str) -> Result { let Some(value) = pass.uniforms.get(uniform) else { return Err(GpuError::MissingUniform { shader: pass.shader.clone(), uniform: uniform.to_string(), }); }; match value { UniformValue::Number(value) => Ok(*value), UniformValue::Vector(_) => Err(GpuError::InvalidNumberUniform { shader: pass.shader.clone(), uniform: uniform.to_string(), }), } } fn read_vec2_uniform(pass: &EffectPass, uniform: &str) -> Result<[f32; 2], GpuError> { let Some(value) = pass.uniforms.get(uniform) else { return Err(GpuError::MissingUniform { shader: pass.shader.clone(), uniform: uniform.to_string(), }); }; let UniformValue::Vector(values) = value else { return Err(GpuError::InvalidVectorUniform { shader: pass.shader.clone(), uniform: uniform.to_string(), expected_length: 2, }); }; if values.len() != 2 { return Err(GpuError::InvalidVectorUniform { shader: pass.shader.clone(), uniform: uniform.to_string(), expected_length: 2, }); } Ok([values[0], values[1]]) }