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::FilterMode::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: 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        });
186        rpass.set_pipeline(&self.pipeline);
187        rpass.set_bind_group(0, &self.bindgroup, &[]);
188        rpass.draw(0..3, 0..1);
189    }
190}
191
192/// Renderer that displays results on the screen.
193struct TargetRenderer {
194    pipeline: wgpu::RenderPipeline,
195    bindgroup_layout: wgpu::BindGroupLayout,
196    bindgroup_left: wgpu::BindGroup,
197    bindgroup_right: wgpu::BindGroup,
198    sampler: wgpu::Sampler,
199}
200
201impl TargetRenderer {
202    fn init(
203        device: &wgpu::Device,
204        shader: &wgpu::ShaderModule,
205        format: wgpu::TextureFormat,
206        targets: &TextureTargets,
207    ) -> TargetRenderer {
208        let texture_bind_group_layout =
209            device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
210                entries: &[
211                    wgpu::BindGroupLayoutEntry {
212                        binding: 0,
213                        visibility: wgpu::ShaderStages::FRAGMENT,
214                        ty: wgpu::BindingType::Texture {
215                            multisampled: false,
216                            sample_type: wgpu::TextureSampleType::Float { filterable: true },
217                            view_dimension: wgpu::TextureViewDimension::D2,
218                        },
219                        count: None,
220                    },
221                    wgpu::BindGroupLayoutEntry {
222                        binding: 1,
223                        visibility: wgpu::ShaderStages::FRAGMENT,
224                        ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
225                        count: None,
226                    },
227                ],
228                label: None,
229            });
230
231        let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
232            label: None,
233            bind_group_layouts: &[&texture_bind_group_layout],
234            push_constant_ranges: &[],
235        });
236
237        let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
238            address_mode_u: wgpu::AddressMode::Repeat,
239            address_mode_v: wgpu::AddressMode::Repeat,
240            address_mode_w: wgpu::AddressMode::Repeat,
241            mag_filter: wgpu::FilterMode::Nearest,
242            min_filter: wgpu::FilterMode::Nearest,
243            mipmap_filter: wgpu::FilterMode::Nearest,
244            ..Default::default()
245        });
246
247        let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
248            label: None,
249            layout: Some(&pipeline_layout),
250            vertex: wgpu::VertexState {
251                module: shader,
252                entry_point: Some("vs_main"),
253                compilation_options: Default::default(),
254                buffers: &[],
255            },
256            fragment: Some(wgpu::FragmentState {
257                module: shader,
258                entry_point: Some("fs_display_main"),
259                compilation_options: Default::default(),
260                targets: &[Some(wgpu::ColorTargetState {
261                    format,
262                    blend: None,
263                    write_mask: Default::default(),
264                })],
265            }),
266            primitive: wgpu::PrimitiveState::default(),
267            depth_stencil: None,
268            multisample: wgpu::MultisampleState::default(),
269            multiview: None,
270            cache: None,
271        });
272
273        let (bg_left, bg_right) =
274            Self::create_bindgroups(device, &texture_bind_group_layout, targets, &sampler);
275        Self {
276            pipeline: render_pipeline,
277            bindgroup_layout: texture_bind_group_layout,
278            bindgroup_left: bg_left,
279            bindgroup_right: bg_right,
280            sampler,
281        }
282    }
283    fn create_bindgroups(
284        device: &wgpu::Device,
285        layout: &wgpu::BindGroupLayout,
286        texture_targets: &TextureTargets,
287        sampler: &wgpu::Sampler,
288    ) -> (wgpu::BindGroup, wgpu::BindGroup) {
289        let left = device.create_bind_group(&wgpu::BindGroupDescriptor {
290            layout,
291            entries: &[
292                wgpu::BindGroupEntry {
293                    binding: 0,
294                    resource: wgpu::BindingResource::TextureView(&texture_targets.red_view),
295                },
296                wgpu::BindGroupEntry {
297                    binding: 1,
298                    resource: wgpu::BindingResource::Sampler(sampler),
299                },
300            ],
301            label: None,
302        });
303
304        let right = device.create_bind_group(&wgpu::BindGroupDescriptor {
305            layout,
306            entries: &[
307                wgpu::BindGroupEntry {
308                    binding: 0,
309                    resource: wgpu::BindingResource::TextureView(&texture_targets.green_view),
310                },
311                wgpu::BindGroupEntry {
312                    binding: 1,
313                    resource: wgpu::BindingResource::Sampler(sampler),
314                },
315            ],
316            label: None,
317        });
318        (left, right)
319    }
320
321    fn draw(
322        &self,
323        encoder: &mut wgpu::CommandEncoder,
324        surface_view: &wgpu::TextureView,
325        width: u32,
326        height: u32,
327    ) {
328        let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
329            label: None,
330            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
331                view: surface_view,
332                depth_slice: None,
333                resolve_target: None,
334                ops: wgpu::Operations {
335                    load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
336                    store: wgpu::StoreOp::Store,
337                },
338            })],
339            depth_stencil_attachment: None,
340            timestamp_writes: None,
341            occlusion_query_set: None,
342        });
343        rpass.set_pipeline(&self.pipeline);
344        rpass.set_bind_group(0, &self.bindgroup_left, &[]);
345
346        let height = height as f32;
347        let half_w = width as f32 * 0.5;
348
349        // draw results in two separate viewports that split the screen:
350
351        rpass.set_viewport(0.0, 0.0, half_w, height, 0.0, 1.0);
352        rpass.draw(0..3, 0..1);
353
354        rpass.set_viewport(half_w, 0.0, half_w, height, 0.0, 1.0);
355        rpass.set_bind_group(0, &self.bindgroup_right, &[]);
356        rpass.draw(0..3, 0..1);
357    }
358
359    fn rebuild_resources(&mut self, device: &wgpu::Device, texture_targets: &TextureTargets) {
360        (self.bindgroup_left, self.bindgroup_right) = Self::create_bindgroups(
361            device,
362            &self.bindgroup_layout,
363            texture_targets,
364            &self.sampler,
365        )
366    }
367}
368
369struct TextureTargets {
370    red_view: wgpu::TextureView,
371    green_view: wgpu::TextureView,
372}
373
374impl TextureTargets {
375    fn new(
376        device: &wgpu::Device,
377        format: wgpu::TextureFormat,
378        width: u32,
379        height: u32,
380    ) -> TextureTargets {
381        let size = wgpu::Extent3d {
382            width,
383            height,
384            depth_or_array_layers: 1,
385        };
386
387        let red_texture = device.create_texture(&wgpu::TextureDescriptor {
388            label: None,
389            size,
390            mip_level_count: 1,
391            sample_count: 1,
392            dimension: wgpu::TextureDimension::D2,
393            format,
394            usage: wgpu::TextureUsages::COPY_DST
395                | wgpu::TextureUsages::TEXTURE_BINDING
396                | wgpu::TextureUsages::RENDER_ATTACHMENT,
397            view_formats: &[format],
398        });
399        let green_texture = device.create_texture(&wgpu::TextureDescriptor {
400            label: None,
401            size,
402            mip_level_count: 1,
403            sample_count: 1,
404            dimension: wgpu::TextureDimension::D2,
405            format,
406            usage: wgpu::TextureUsages::COPY_DST
407                | wgpu::TextureUsages::TEXTURE_BINDING
408                | wgpu::TextureUsages::RENDER_ATTACHMENT,
409            view_formats: &[format],
410        });
411        let red_view = red_texture.create_view(&wgpu::TextureViewDescriptor {
412            format: Some(format),
413            dimension: Some(wgpu::TextureViewDimension::D2),
414            ..wgpu::TextureViewDescriptor::default()
415        });
416        let green_view = green_texture.create_view(&wgpu::TextureViewDescriptor {
417            format: Some(format),
418            dimension: Some(wgpu::TextureViewDimension::D2),
419            ..wgpu::TextureViewDescriptor::default()
420        });
421        TextureTargets {
422            red_view,
423            green_view,
424        }
425    }
426}
427
428struct Example {
429    drawer: TargetRenderer,
430    multi_target_renderer: MultiTargetRenderer,
431    texture_targets: TextureTargets,
432    screen_width: u32,
433    screen_height: u32,
434}
435
436impl crate::framework::Example for Example {
437    fn init(
438        config: &wgpu::SurfaceConfiguration,
439        _adapter: &wgpu::Adapter,
440        device: &wgpu::Device,
441        queue: &wgpu::Queue,
442    ) -> Self {
443        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
444            label: None,
445            source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
446                "shader.wgsl"
447            ))),
448        });
449        // Renderer that draws to 2 textures at the same time:
450        let multi_target_renderer = MultiTargetRenderer::init(
451            device,
452            queue,
453            &shader,
454            // ColorTargetStates specify how the data will be written to the
455            // output textures:
456            &[
457                Some(wgpu::ColorTargetState {
458                    format: config.view_formats[0],
459                    blend: None,
460                    write_mask: Default::default(),
461                }),
462                Some(wgpu::ColorTargetState {
463                    format: config.view_formats[0],
464                    blend: None,
465                    write_mask: Default::default(),
466                }),
467            ],
468        );
469
470        // create our target textures that will receive the simultaneous rendering:
471        let texture_targets =
472            TextureTargets::new(device, config.view_formats[0], config.width, config.height);
473
474        // helper renderer that displays the results in 2 separate viewports:
475        let drawer =
476            TargetRenderer::init(device, &shader, config.view_formats[0], &texture_targets);
477
478        Self {
479            texture_targets,
480            multi_target_renderer,
481            drawer,
482            screen_width: config.width,
483            screen_height: config.height,
484        }
485    }
486
487    fn resize(
488        &mut self,
489        config: &wgpu::SurfaceConfiguration,
490        device: &wgpu::Device,
491        _queue: &wgpu::Queue,
492    ) {
493        self.screen_width = config.width;
494        self.screen_height = config.height;
495        self.texture_targets =
496            TextureTargets::new(device, config.view_formats[0], config.width, config.height);
497        self.drawer.rebuild_resources(device, &self.texture_targets);
498    }
499
500    fn update(&mut self, _event: winit::event::WindowEvent) {}
501
502    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
503        let mut encoder =
504            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
505
506        // draw to 2 textures at the same time:
507        self.multi_target_renderer.draw(
508            &mut encoder,
509            &[
510                Some(wgpu::RenderPassColorAttachment {
511                    view: &self.texture_targets.red_view,
512                    depth_slice: None,
513                    resolve_target: None,
514                    ops: Default::default(),
515                }),
516                Some(wgpu::RenderPassColorAttachment {
517                    view: &self.texture_targets.green_view,
518                    depth_slice: None,
519                    resolve_target: None,
520                    ops: Default::default(),
521                }),
522            ],
523        );
524
525        // display results of the both drawn textures on screen:
526        self.drawer
527            .draw(&mut encoder, view, self.screen_width, self.screen_height);
528
529        queue.submit(Some(encoder.finish()));
530    }
531}
532
533pub fn main() {
534    crate::framework::run::<Example>(EXAMPLE_NAME);
535}
536
537#[cfg(test)]
538#[wgpu_test::gpu_test]
539static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
540    name: EXAMPLE_NAME,
541    image_path: "/examples/features/src/multiple_render_targets/screenshot.png",
542    width: 1024,
543    height: 768,
544    optional_features: wgpu::Features::default(),
545    base_test_parameters: wgpu_test::TestParameters::default(),
546    // Bounded by lavapipe
547    comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
548    _phantom: std::marker::PhantomData::<Example>,
549};