wgpu_examples/render_with_compute/
mod.rs

1//! This renders to the screen with compute shaders. Note that due to limitations in Firefox,
2//! the wait will cause FPS to be capped at 10 when running on webgpu on Firefox. It is
3//! therefore not recommended to use this code, at least until
4//! <https://bugzilla.mozilla.org/show_bug.cgi?id=1870699> (and possibly further work) is resolved.
5
6use std::time::Instant;
7
8#[derive(bytemuck::Pod, bytemuck::Zeroable, Clone, Copy, Debug)]
9#[repr(C)]
10#[repr(align(16))]
11struct GlobalParams {
12    time: f32,
13    frame: f32,
14    _padding: [u8; 8],
15}
16
17pub struct Example {
18    pipeline: wgpu::ComputePipeline,
19    texture_view: wgpu::TextureView,
20    global_params: wgpu::Buffer,
21    bg: wgpu::BindGroup,
22    bgl: wgpu::BindGroupLayout,
23    blitter: wgpu::util::TextureBlitter,
24    frame_count: u32,
25    start_time: Option<Instant>,
26}
27impl crate::framework::Example for Example {
28    fn init(
29        config: &wgpu::SurfaceConfiguration,
30        _adapter: &wgpu::Adapter,
31        device: &wgpu::Device,
32        _queue: &wgpu::Queue,
33    ) -> Self {
34        let sm = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl"));
35        let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
36            label: None,
37            entries: &[
38                wgpu::BindGroupLayoutEntry {
39                    binding: 0,
40                    visibility: wgpu::ShaderStages::COMPUTE,
41                    ty: wgpu::BindingType::Buffer {
42                        ty: wgpu::BufferBindingType::Uniform,
43                        has_dynamic_offset: false,
44                        min_binding_size: None,
45                    },
46                    count: None,
47                },
48                wgpu::BindGroupLayoutEntry {
49                    binding: 1,
50                    visibility: wgpu::ShaderStages::COMPUTE,
51                    ty: wgpu::BindingType::StorageTexture {
52                        access: wgpu::StorageTextureAccess::WriteOnly,
53                        format: wgpu::TextureFormat::Rgba8Unorm,
54                        view_dimension: wgpu::TextureViewDimension::D2,
55                    },
56                    count: None,
57                },
58            ],
59        });
60        let ppl = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
61            label: None,
62            bind_group_layouts: &[Some(&bgl)],
63            immediate_size: 0,
64        });
65        let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
66            label: None,
67            layout: Some(&ppl),
68            module: &sm,
69            entry_point: None,
70            compilation_options: Default::default(),
71            cache: None,
72        });
73        let blitter = wgpu::util::TextureBlitter::new(device, config.format);
74        let global_params = device.create_buffer(&wgpu::BufferDescriptor {
75            label: None,
76            size: size_of::<GlobalParams>() as u64,
77            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
78            mapped_at_creation: false,
79        });
80
81        let (texture_view, bg) =
82            create_tv_and_bg(device, &bgl, &global_params, config.width, config.height);
83        Self {
84            pipeline,
85            texture_view,
86            global_params,
87            bg,
88            bgl,
89            blitter,
90            frame_count: 0,
91            start_time: None,
92        }
93    }
94
95    fn required_limits() -> wgpu::Limits {
96        wgpu::Limits {
97            max_storage_textures_per_shader_stage: 1,
98            ..Default::default()
99        }
100    }
101
102    fn resize(
103        &mut self,
104        config: &wgpu::SurfaceConfiguration,
105        device: &wgpu::Device,
106        _queue: &wgpu::Queue,
107    ) {
108        let (texture_view, bg) = create_tv_and_bg(
109            device,
110            &self.bgl,
111            &self.global_params,
112            config.width,
113            config.height,
114        );
115        self.bg = bg;
116        self.texture_view = texture_view;
117    }
118
119    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
120        let now = Instant::now();
121        let start = *self.start_time.get_or_insert(now);
122        let time_since_start = (now - start).as_secs_f32();
123        queue.write_buffer(
124            &self.global_params,
125            0,
126            bytemuck::bytes_of(&GlobalParams {
127                time: time_since_start,
128                frame: self.frame_count as f32,
129                _padding: [0; 8],
130            }),
131        );
132        let mut encoder = device.create_command_encoder(&Default::default());
133
134        {
135            let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
136                label: None,
137                timestamp_writes: None,
138            });
139            pass.set_pipeline(&self.pipeline);
140            pass.set_bind_group(0, &self.bg, &[]);
141            const SHADER_WORKGROUP_DIM: u32 = 16;
142            pass.dispatch_workgroups(
143                self.texture_view
144                    .texture()
145                    .width()
146                    .div_ceil(SHADER_WORKGROUP_DIM),
147                self.texture_view
148                    .texture()
149                    .height()
150                    .div_ceil(SHADER_WORKGROUP_DIM),
151                1,
152            );
153        }
154        self.blitter
155            .copy(device, &mut encoder, &self.texture_view, view);
156
157        queue.submit([encoder.finish()]);
158        device.poll(wgpu::PollType::wait_indefinitely()).unwrap();
159
160        self.frame_count += 1;
161    }
162
163    fn update(&mut self, _event: winit::event::WindowEvent) {}
164}
165
166fn create_tv_and_bg(
167    device: &wgpu::Device,
168    bgl: &wgpu::BindGroupLayout,
169    global_params: &wgpu::Buffer,
170    width: u32,
171    height: u32,
172) -> (wgpu::TextureView, wgpu::BindGroup) {
173    let texture = device.create_texture(&wgpu::TextureDescriptor {
174        label: None,
175        size: wgpu::Extent3d {
176            width,
177            height,
178            depth_or_array_layers: 1,
179        },
180        mip_level_count: 1,
181        sample_count: 1,
182        dimension: wgpu::TextureDimension::D2,
183        format: wgpu::TextureFormat::Rgba8Unorm,
184        usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
185        view_formats: &[],
186    });
187    let view = texture.create_view(&Default::default());
188    let bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
189        label: None,
190        layout: bgl,
191        entries: &[
192            wgpu::BindGroupEntry {
193                binding: 0,
194                resource: wgpu::BindingResource::Buffer(global_params.as_entire_buffer_binding()),
195            },
196            wgpu::BindGroupEntry {
197                binding: 1,
198                resource: wgpu::BindingResource::TextureView(&view),
199            },
200        ],
201    });
202    (view, bg)
203}
204
205pub fn main() {
206    crate::framework::run::<Example>("render-with-compute");
207}