1const EXAMPLE_NAME: &str = "multiple_render_targets";
2
3struct MultiTargetRenderer {
5 pipeline: wgpu::RenderPipeline,
6 bindgroup: wgpu::BindGroup,
7}
8
9fn create_ball_texture_data(width: usize, height: usize) -> Vec<u8> {
10 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, 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 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
193struct 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 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 let multi_target_renderer = MultiTargetRenderer::init(
453 device,
454 queue,
455 &shader,
456 &[
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 let texture_targets =
474 TextureTargets::new(device, config.view_formats[0], config.width, config.height);
475
476 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 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 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 comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
550 _phantom: std::marker::PhantomData::<Example>,
551};