wgpu_examples/multiple_render_targets/
mod.rs

1const EXAMPLE_NAME: &str = "multiple_render_targets";
2
3/// Renderer that draws its outputs to two output texture targets at the same time.
4struct MultiTargetRenderer {
5    pipeline: wgpu::RenderPipeline,
6    bindgroup: wgpu::BindGroup,
7}
8
9fn create_ball_texture_data(width: usize, height: usize) -> Vec<u8> {
10    // Creates black and white pixel data for the texture to sample.
11    let mut img_data = Vec::with_capacity(width * height);
12    let center: glam::Vec2 = glam::Vec2::new(width as f32 * 0.5, height as f32 * 0.5);
13    let half_distance = width as f32 * 0.5;
14    for y in 0..width {
15        for x in 0..height {
16            let cur_pos = glam::Vec2::new(x as f32, y as f32);
17            let distance_to_center_normalized = 1.0 - (cur_pos - center).length() / half_distance;
18            let val: u8 = (u8::MAX as f32 * distance_to_center_normalized) as u8;
19            img_data.push(val)
20        }
21    }
22    img_data
23}
24
25impl MultiTargetRenderer {
26    fn create_image_texture(
27        device: &wgpu::Device,
28        queue: &wgpu::Queue,
29    ) -> (wgpu::Texture, wgpu::TextureView) {
30        const WIDTH: usize = 256;
31        const HEIGHT: usize = 256;
32
33        let size = wgpu::Extent3d {
34            width: WIDTH as u32,
35            height: HEIGHT as u32,
36            depth_or_array_layers: 1,
37        };
38
39        let texture = device.create_texture(&wgpu::TextureDescriptor {
40            label: Some("data texture"),
41            size,
42            mip_level_count: 1,
43            sample_count: 1,
44            dimension: wgpu::TextureDimension::D2,
45            format: wgpu::TextureFormat::R8Unorm, // we need only the red channel for black/white image,
46            usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
47            view_formats: &[],
48        });
49
50        let ball_texture_data = &create_ball_texture_data(WIDTH, HEIGHT);
51
52        queue.write_texture(
53            wgpu::TexelCopyTextureInfo {
54                aspect: wgpu::TextureAspect::All,
55                texture: &texture,
56                mip_level: 0,
57                origin: wgpu::Origin3d::ZERO,
58            },
59            ball_texture_data,
60            wgpu::TexelCopyBufferLayout {
61                offset: 0,
62                bytes_per_row: Some(WIDTH as u32),
63                rows_per_image: Some(HEIGHT as u32),
64            },
65            size,
66        );
67
68        let view = texture.create_view(&wgpu::TextureViewDescriptor {
69            label: Some("view"),
70            format: None,
71            dimension: Some(wgpu::TextureViewDimension::D2),
72            usage: None,
73            aspect: wgpu::TextureAspect::All,
74            base_mip_level: 0,
75            mip_level_count: None,
76            base_array_layer: 0,
77            array_layer_count: None,
78        });
79
80        (texture, view)
81    }
82
83    fn init(
84        device: &wgpu::Device,
85        queue: &wgpu::Queue,
86        shader: &wgpu::ShaderModule,
87        target_states: &[Option<wgpu::ColorTargetState>],
88    ) -> MultiTargetRenderer {
89        let texture_bind_group_layout =
90            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
91                entries: &[
92                    wgpu::BindGroupLayoutEntry {
93                        binding: 0,
94                        visibility: wgpu::ShaderStages::FRAGMENT,
95                        ty: wgpu::BindingType::Texture {
96                            multisampled: false,
97                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
98                            view_dimension: wgpu::TextureViewDimension::D2,
99                        },
100                        count: None,
101                    },
102                    wgpu::BindGroupLayoutEntry {
103                        binding: 1,
104                        visibility: wgpu::ShaderStages::FRAGMENT,
105                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
106                        count: None,
107                    },
108                ],
109                label: None,
110            });
111
112        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
113            label: None,
114            bind_group_layouts: &[&texture_bind_group_layout],
115            push_constant_ranges: &[],
116        });
117
118        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
119            address_mode_u: wgpu::AddressMode::Repeat,
120            address_mode_v: wgpu::AddressMode::Repeat,
121            address_mode_w: wgpu::AddressMode::Repeat,
122            mag_filter: wgpu::FilterMode::Nearest,
123            min_filter: wgpu::FilterMode::Nearest,
124            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
125            ..Default::default()
126        });
127
128        let (_, texture_view) = Self::create_image_texture(device, queue);
129
130        let bindgroup = device.create_bind_group(&wgpu::BindGroupDescriptor {
131            layout: &texture_bind_group_layout,
132            entries: &[
133                wgpu::BindGroupEntry {
134                    binding: 0,
135                    resource: wgpu::BindingResource::TextureView(&texture_view),
136                },
137                wgpu::BindGroupEntry {
138                    binding: 1,
139                    resource: wgpu::BindingResource::Sampler(&sampler),
140                },
141            ],
142            label: None,
143        });
144
145        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
146            label: None,
147            layout: Some(&pipeline_layout),
148            vertex: wgpu::VertexState {
149                module: shader,
150                entry_point: Some("vs_main"),
151                compilation_options: Default::default(),
152                buffers: &[],
153            },
154            fragment: Some(wgpu::FragmentState {
155                module: shader,
156                entry_point: Some("fs_multi_main"),
157                // IMPORTANT: specify the color states for the outputs:
158                compilation_options: Default::default(),
159                targets: target_states,
160            }),
161            primitive: wgpu::PrimitiveState::default(),
162            depth_stencil: None,
163            multisample: wgpu::MultisampleState::default(),
164            multiview_mask: None,
165            cache: None,
166        });
167
168        Self {
169            pipeline,
170            bindgroup,
171        }
172    }
173
174    fn draw(
175        &self,
176        encoder: &mut wgpu::CommandEncoder,
177        targets: &[Option<wgpu::RenderPassColorAttachment>],
178    ) {
179        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
180            label: None,
181            color_attachments: targets,
182            depth_stencil_attachment: None,
183            timestamp_writes: None,
184            occlusion_query_set: None,
185            multiview_mask: None,
186        });
187        rpass.set_pipeline(&self.pipeline);
188        rpass.set_bind_group(0, &self.bindgroup, &[]);
189        rpass.draw(0..3, 0..1);
190    }
191}
192
193/// Renderer that displays results on the screen.
194struct TargetRenderer {
195    pipeline: wgpu::RenderPipeline,
196    bindgroup_layout: wgpu::BindGroupLayout,
197    bindgroup_left: wgpu::BindGroup,
198    bindgroup_right: wgpu::BindGroup,
199    sampler: wgpu::Sampler,
200}
201
202impl TargetRenderer {
203    fn init(
204        device: &wgpu::Device,
205        shader: &wgpu::ShaderModule,
206        format: wgpu::TextureFormat,
207        targets: &TextureTargets,
208    ) -> TargetRenderer {
209        let texture_bind_group_layout =
210            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
211                entries: &[
212                    wgpu::BindGroupLayoutEntry {
213                        binding: 0,
214                        visibility: wgpu::ShaderStages::FRAGMENT,
215                        ty: wgpu::BindingType::Texture {
216                            multisampled: false,
217                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
218                            view_dimension: wgpu::TextureViewDimension::D2,
219                        },
220                        count: None,
221                    },
222                    wgpu::BindGroupLayoutEntry {
223                        binding: 1,
224                        visibility: wgpu::ShaderStages::FRAGMENT,
225                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
226                        count: None,
227                    },
228                ],
229                label: None,
230            });
231
232        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
233            label: None,
234            bind_group_layouts: &[&texture_bind_group_layout],
235            push_constant_ranges: &[],
236        });
237
238        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
239            address_mode_u: wgpu::AddressMode::Repeat,
240            address_mode_v: wgpu::AddressMode::Repeat,
241            address_mode_w: wgpu::AddressMode::Repeat,
242            mag_filter: wgpu::FilterMode::Nearest,
243            min_filter: wgpu::FilterMode::Nearest,
244            mipmap_filter: wgpu::MipmapFilterMode::Nearest,
245            ..Default::default()
246        });
247
248        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
249            label: None,
250            layout: Some(&pipeline_layout),
251            vertex: wgpu::VertexState {
252                module: shader,
253                entry_point: Some("vs_main"),
254                compilation_options: Default::default(),
255                buffers: &[],
256            },
257            fragment: Some(wgpu::FragmentState {
258                module: shader,
259                entry_point: Some("fs_display_main"),
260                compilation_options: Default::default(),
261                targets: &[Some(wgpu::ColorTargetState {
262                    format,
263                    blend: None,
264                    write_mask: Default::default(),
265                })],
266            }),
267            primitive: wgpu::PrimitiveState::default(),
268            depth_stencil: None,
269            multisample: wgpu::MultisampleState::default(),
270            multiview_mask: None,
271            cache: None,
272        });
273
274        let (bg_left, bg_right) =
275            Self::create_bindgroups(device, &texture_bind_group_layout, targets, &sampler);
276        Self {
277            pipeline: render_pipeline,
278            bindgroup_layout: texture_bind_group_layout,
279            bindgroup_left: bg_left,
280            bindgroup_right: bg_right,
281            sampler,
282        }
283    }
284    fn create_bindgroups(
285        device: &wgpu::Device,
286        layout: &wgpu::BindGroupLayout,
287        texture_targets: &TextureTargets,
288        sampler: &wgpu::Sampler,
289    ) -> (wgpu::BindGroup, wgpu::BindGroup) {
290        let left = device.create_bind_group(&wgpu::BindGroupDescriptor {
291            layout,
292            entries: &[
293                wgpu::BindGroupEntry {
294                    binding: 0,
295                    resource: wgpu::BindingResource::TextureView(&texture_targets.red_view),
296                },
297                wgpu::BindGroupEntry {
298                    binding: 1,
299                    resource: wgpu::BindingResource::Sampler(sampler),
300                },
301            ],
302            label: None,
303        });
304
305        let right = device.create_bind_group(&wgpu::BindGroupDescriptor {
306            layout,
307            entries: &[
308                wgpu::BindGroupEntry {
309                    binding: 0,
310                    resource: wgpu::BindingResource::TextureView(&texture_targets.green_view),
311                },
312                wgpu::BindGroupEntry {
313                    binding: 1,
314                    resource: wgpu::BindingResource::Sampler(sampler),
315                },
316            ],
317            label: None,
318        });
319        (left, right)
320    }
321
322    fn draw(
323        &self,
324        encoder: &mut wgpu::CommandEncoder,
325        surface_view: &wgpu::TextureView,
326        width: u32,
327        height: u32,
328    ) {
329        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
330            label: None,
331            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
332                view: surface_view,
333                depth_slice: None,
334                resolve_target: None,
335                ops: wgpu::Operations {
336                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
337                    store: wgpu::StoreOp::Store,
338                },
339            })],
340            depth_stencil_attachment: None,
341            timestamp_writes: None,
342            occlusion_query_set: None,
343            multiview_mask: None,
344        });
345        rpass.set_pipeline(&self.pipeline);
346        rpass.set_bind_group(0, &self.bindgroup_left, &[]);
347
348        let height = height as f32;
349        let half_w = width as f32 * 0.5;
350
351        // draw results in two separate viewports that split the screen:
352
353        rpass.set_viewport(0.0, 0.0, half_w, height, 0.0, 1.0);
354        rpass.draw(0..3, 0..1);
355
356        rpass.set_viewport(half_w, 0.0, half_w, height, 0.0, 1.0);
357        rpass.set_bind_group(0, &self.bindgroup_right, &[]);
358        rpass.draw(0..3, 0..1);
359    }
360
361    fn rebuild_resources(&mut self, device: &wgpu::Device, texture_targets: &TextureTargets) {
362        (self.bindgroup_left, self.bindgroup_right) = Self::create_bindgroups(
363            device,
364            &self.bindgroup_layout,
365            texture_targets,
366            &self.sampler,
367        )
368    }
369}
370
371struct TextureTargets {
372    red_view: wgpu::TextureView,
373    green_view: wgpu::TextureView,
374}
375
376impl TextureTargets {
377    fn new(
378        device: &wgpu::Device,
379        format: wgpu::TextureFormat,
380        width: u32,
381        height: u32,
382    ) -> TextureTargets {
383        let size = wgpu::Extent3d {
384            width,
385            height,
386            depth_or_array_layers: 1,
387        };
388
389        let red_texture = device.create_texture(&wgpu::TextureDescriptor {
390            label: None,
391            size,
392            mip_level_count: 1,
393            sample_count: 1,
394            dimension: wgpu::TextureDimension::D2,
395            format,
396            usage: wgpu::TextureUsages::COPY_DST
397                | wgpu::TextureUsages::TEXTURE_BINDING
398                | wgpu::TextureUsages::RENDER_ATTACHMENT,
399            view_formats: &[format],
400        });
401        let green_texture = device.create_texture(&wgpu::TextureDescriptor {
402            label: None,
403            size,
404            mip_level_count: 1,
405            sample_count: 1,
406            dimension: wgpu::TextureDimension::D2,
407            format,
408            usage: wgpu::TextureUsages::COPY_DST
409                | wgpu::TextureUsages::TEXTURE_BINDING
410                | wgpu::TextureUsages::RENDER_ATTACHMENT,
411            view_formats: &[format],
412        });
413        let red_view = red_texture.create_view(&wgpu::TextureViewDescriptor {
414            format: Some(format),
415            dimension: Some(wgpu::TextureViewDimension::D2),
416            ..wgpu::TextureViewDescriptor::default()
417        });
418        let green_view = green_texture.create_view(&wgpu::TextureViewDescriptor {
419            format: Some(format),
420            dimension: Some(wgpu::TextureViewDimension::D2),
421            ..wgpu::TextureViewDescriptor::default()
422        });
423        TextureTargets {
424            red_view,
425            green_view,
426        }
427    }
428}
429
430struct Example {
431    drawer: TargetRenderer,
432    multi_target_renderer: MultiTargetRenderer,
433    texture_targets: TextureTargets,
434    screen_width: u32,
435    screen_height: u32,
436}
437
438impl crate::framework::Example for Example {
439    fn init(
440        config: &wgpu::SurfaceConfiguration,
441        _adapter: &wgpu::Adapter,
442        device: &wgpu::Device,
443        queue: &wgpu::Queue,
444    ) -> Self {
445        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
446            label: None,
447            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
448                "shader.wgsl"
449            ))),
450        });
451        // Renderer that draws to 2 textures at the same time:
452        let multi_target_renderer = MultiTargetRenderer::init(
453            device,
454            queue,
455            &shader,
456            // ColorTargetStates specify how the data will be written to the
457            // output textures:
458            &[
459                Some(wgpu::ColorTargetState {
460                    format: config.view_formats[0],
461                    blend: None,
462                    write_mask: Default::default(),
463                }),
464                Some(wgpu::ColorTargetState {
465                    format: config.view_formats[0],
466                    blend: None,
467                    write_mask: Default::default(),
468                }),
469            ],
470        );
471
472        // create our target textures that will receive the simultaneous rendering:
473        let texture_targets =
474            TextureTargets::new(device, config.view_formats[0], config.width, config.height);
475
476        // helper renderer that displays the results in 2 separate viewports:
477        let drawer =
478            TargetRenderer::init(device, &shader, config.view_formats[0], &texture_targets);
479
480        Self {
481            texture_targets,
482            multi_target_renderer,
483            drawer,
484            screen_width: config.width,
485            screen_height: config.height,
486        }
487    }
488
489    fn resize(
490        &mut self,
491        config: &wgpu::SurfaceConfiguration,
492        device: &wgpu::Device,
493        _queue: &wgpu::Queue,
494    ) {
495        self.screen_width = config.width;
496        self.screen_height = config.height;
497        self.texture_targets =
498            TextureTargets::new(device, config.view_formats[0], config.width, config.height);
499        self.drawer.rebuild_resources(device, &self.texture_targets);
500    }
501
502    fn update(&mut self, _event: winit::event::WindowEvent) {}
503
504    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
505        let mut encoder =
506            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
507
508        // draw to 2 textures at the same time:
509        self.multi_target_renderer.draw(
510            &mut encoder,
511            &[
512                Some(wgpu::RenderPassColorAttachment {
513                    view: &self.texture_targets.red_view,
514                    depth_slice: None,
515                    resolve_target: None,
516                    ops: Default::default(),
517                }),
518                Some(wgpu::RenderPassColorAttachment {
519                    view: &self.texture_targets.green_view,
520                    depth_slice: None,
521                    resolve_target: None,
522                    ops: Default::default(),
523                }),
524            ],
525        );
526
527        // display results of the both drawn textures on screen:
528        self.drawer
529            .draw(&mut encoder, view, self.screen_width, self.screen_height);
530
531        queue.submit(Some(encoder.finish()));
532    }
533}
534
535pub fn main() {
536    crate::framework::run::<Example>(EXAMPLE_NAME);
537}
538
539#[cfg(test)]
540#[wgpu_test::gpu_test]
541pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
542    name: EXAMPLE_NAME,
543    image_path: "/examples/features/src/multiple_render_targets/screenshot.png",
544    width: 1024,
545    height: 768,
546    optional_features: wgpu::Features::default(),
547    base_test_parameters: wgpu_test::TestParameters::default(),
548    // Bounded by lavapipe
549    comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
550    _phantom: std::marker::PhantomData::<Example>,
551};