summaryrefslogtreecommitdiff
path: root/rust/crates/gpu/src/effect_pipeline.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/crates/gpu/src/effect_pipeline.rs')
-rw-r--r--rust/crates/gpu/src/effect_pipeline.rs194
1 files changed, 194 insertions, 0 deletions
diff --git a/rust/crates/gpu/src/effect_pipeline.rs b/rust/crates/gpu/src/effect_pipeline.rs
new file mode 100644
index 00000000..c600551a
--- /dev/null
+++ b/rust/crates/gpu/src/effect_pipeline.rs
@@ -0,0 +1,194 @@
+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<String, UniformValue>,
+}
+
+#[derive(Clone, Debug)]
+pub enum UniformValue {
+ Number(f32),
+ Vector(Vec<f32>),
+}
+
+#[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<wgpu::Texture, GpuError> {
+ let mut current_texture: Option<wgpu::Texture> = 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<EffectUniformBuffer, GpuError> {
+ 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<f32, GpuError> {
+ 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]])
+}