kiss3d/post_processing/
waves.rs

1//! An useless post-process effect: waves deformations.
2
3// This a simple post-process. I do this only to learn how works post-processing; so it might be
4// useless for anybody else.
5// This is inspired _a lot_ by: http://en.wikibooks.org/wiki/Opengl::Programming/Post-Processing
6
7use std::f32;
8
9use crate::context::Context;
10use crate::post_processing::post_processing_effect::{PostProcessingContext, PostProcessingEffect};
11use crate::resource::RenderTarget;
12use bytemuck::{Pod, Zeroable};
13
14/// Vertex data for full-screen quad.
15#[repr(C)]
16#[derive(Copy, Clone, Debug, Pod, Zeroable)]
17struct QuadVertex {
18    position: [f32; 2],
19}
20
21/// Uniforms for waves effect.
22#[repr(C)]
23#[derive(Copy, Clone, Debug, Pod, Zeroable)]
24struct WavesUniforms {
25    offset: f32,
26    _padding: [f32; 3],
27}
28
29/// An useless post-processing effect mainly to test that everything works correctly.
30///
31/// It deforms the displayed scene with a wave effect.
32pub struct Waves {
33    pipeline: wgpu::RenderPipeline,
34    texture_bind_group_layout: wgpu::BindGroupLayout,
35    #[allow(dead_code)]
36    uniform_bind_group_layout: wgpu::BindGroupLayout,
37    uniform_buffer: wgpu::Buffer,
38    uniform_bind_group: wgpu::BindGroup,
39    vertex_buffer: wgpu::Buffer,
40    time: f32,
41}
42
43impl Default for Waves {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl Waves {
50    /// Creates a new Waves post processing effect.
51    pub fn new() -> Waves {
52        let ctxt = Context::get();
53
54        // Create bind group layout for texture + sampler
55        let texture_bind_group_layout =
56            ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
57                label: Some("waves_texture_bind_group_layout"),
58                entries: &[
59                    wgpu::BindGroupLayoutEntry {
60                        binding: 0,
61                        visibility: wgpu::ShaderStages::FRAGMENT,
62                        ty: wgpu::BindingType::Texture {
63                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
64                            view_dimension: wgpu::TextureViewDimension::D2,
65                            multisampled: false,
66                        },
67                        count: None,
68                    },
69                    wgpu::BindGroupLayoutEntry {
70                        binding: 1,
71                        visibility: wgpu::ShaderStages::FRAGMENT,
72                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
73                        count: None,
74                    },
75                ],
76            });
77
78        // Create bind group layout for uniforms
79        let uniform_bind_group_layout =
80            ctxt.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
81                label: Some("waves_uniform_bind_group_layout"),
82                entries: &[wgpu::BindGroupLayoutEntry {
83                    binding: 0,
84                    visibility: wgpu::ShaderStages::FRAGMENT,
85                    ty: wgpu::BindingType::Buffer {
86                        ty: wgpu::BufferBindingType::Uniform,
87                        has_dynamic_offset: false,
88                        min_binding_size: None,
89                    },
90                    count: None,
91                }],
92            });
93
94        let pipeline_layout = ctxt.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
95            label: Some("waves_pipeline_layout"),
96            bind_group_layouts: &[&texture_bind_group_layout, &uniform_bind_group_layout],
97            push_constant_ranges: &[],
98        });
99
100        // Load shader
101        let shader =
102            ctxt.create_shader_module(Some("waves_shader"), include_str!("../builtin/waves.wgsl"));
103
104        // Vertex buffer layout
105        let vertex_buffer_layout = wgpu::VertexBufferLayout {
106            array_stride: std::mem::size_of::<QuadVertex>() as wgpu::BufferAddress,
107            step_mode: wgpu::VertexStepMode::Vertex,
108            attributes: &[wgpu::VertexAttribute {
109                offset: 0,
110                shader_location: 0,
111                format: wgpu::VertexFormat::Float32x2,
112            }],
113        };
114
115        let pipeline = ctxt.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
116            label: Some("waves_pipeline"),
117            layout: Some(&pipeline_layout),
118            vertex: wgpu::VertexState {
119                module: &shader,
120                entry_point: Some("vs_main"),
121                buffers: &[vertex_buffer_layout],
122                compilation_options: Default::default(),
123            },
124            fragment: Some(wgpu::FragmentState {
125                module: &shader,
126                entry_point: Some("fs_main"),
127                targets: &[Some(wgpu::ColorTargetState {
128                    format: ctxt.surface_format,
129                    blend: None,
130                    write_mask: wgpu::ColorWrites::ALL,
131                })],
132                compilation_options: Default::default(),
133            }),
134            primitive: wgpu::PrimitiveState {
135                topology: wgpu::PrimitiveTopology::TriangleStrip,
136                strip_index_format: None,
137                front_face: wgpu::FrontFace::Ccw,
138                cull_mode: None,
139                polygon_mode: wgpu::PolygonMode::Fill,
140                unclipped_depth: false,
141                conservative: false,
142            },
143            depth_stencil: None,
144            multisample: wgpu::MultisampleState {
145                count: 1,
146                mask: !0,
147                alpha_to_coverage_enabled: false,
148            },
149            multiview: None,
150            cache: None,
151        });
152
153        // Create full-screen quad vertices
154        let vertices = [
155            QuadVertex {
156                position: [-1.0, -1.0],
157            },
158            QuadVertex {
159                position: [1.0, -1.0],
160            },
161            QuadVertex {
162                position: [-1.0, 1.0],
163            },
164            QuadVertex {
165                position: [1.0, 1.0],
166            },
167        ];
168
169        let vertex_buffer = ctxt.create_buffer_init(
170            Some("waves_vertex_buffer"),
171            bytemuck::cast_slice(&vertices),
172            wgpu::BufferUsages::VERTEX,
173        );
174
175        // Create uniform buffer
176        let uniform_buffer = ctxt.create_buffer_simple(
177            Some("waves_uniform_buffer"),
178            std::mem::size_of::<WavesUniforms>() as u64,
179            wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
180        );
181
182        // Create uniform bind group
183        let uniform_bind_group = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
184            label: Some("waves_uniform_bind_group"),
185            layout: &uniform_bind_group_layout,
186            entries: &[wgpu::BindGroupEntry {
187                binding: 0,
188                resource: uniform_buffer.as_entire_binding(),
189            }],
190        });
191
192        Waves {
193            pipeline,
194            texture_bind_group_layout,
195            uniform_bind_group_layout,
196            uniform_buffer,
197            uniform_bind_group,
198            vertex_buffer,
199            time: 0.0,
200        }
201    }
202}
203
204impl PostProcessingEffect for Waves {
205    fn update(&mut self, dt: f32, _: f32, _: f32, _: f32, _: f32) {
206        self.time += dt;
207    }
208
209    fn draw(&mut self, target: &RenderTarget, context: &mut PostProcessingContext) {
210        let ctxt = Context::get();
211
212        // Get the source texture and sampler from the render target
213        let (color_view, sampler) = match target {
214            RenderTarget::Offscreen(o) => (&o.color_view, &o.sampler),
215            RenderTarget::Screen => return, // Can't post-process the screen directly
216        };
217
218        // Update uniforms
219        let move_amount = self.time * 2.0 * f32::consts::PI * 0.75; // 3/4 of a wave cycle per second
220        let uniforms = WavesUniforms {
221            offset: move_amount,
222            _padding: [0.0; 3],
223        };
224        ctxt.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(&uniforms));
225
226        // Create texture bind group for this frame
227        let texture_bind_group = ctxt.create_bind_group(&wgpu::BindGroupDescriptor {
228            label: Some("waves_texture_bind_group"),
229            layout: &self.texture_bind_group_layout,
230            entries: &[
231                wgpu::BindGroupEntry {
232                    binding: 0,
233                    resource: wgpu::BindingResource::TextureView(color_view),
234                },
235                wgpu::BindGroupEntry {
236                    binding: 1,
237                    resource: wgpu::BindingResource::Sampler(sampler),
238                },
239            ],
240        });
241
242        // Create render pass to the output view
243        {
244            let mut render_pass = context
245                .encoder
246                .begin_render_pass(&wgpu::RenderPassDescriptor {
247                    label: Some("waves_render_pass"),
248                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
249                        view: context.output_view,
250                        resolve_target: None,
251                        ops: wgpu::Operations {
252                            load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
253                            store: wgpu::StoreOp::Store,
254                        },
255                        depth_slice: None,
256                    })],
257                    depth_stencil_attachment: None,
258                    timestamp_writes: None,
259                    occlusion_query_set: None,
260                });
261
262            render_pass.set_pipeline(&self.pipeline);
263            render_pass.set_bind_group(0, &texture_bind_group, &[]);
264            render_pass.set_bind_group(1, &self.uniform_bind_group, &[]);
265            render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
266            render_pass.draw(0..4, 0..1);
267        }
268    }
269}