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::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 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
192struct 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 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 let multi_target_renderer = MultiTargetRenderer::init(
451 device,
452 queue,
453 &shader,
454 &[
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 let texture_targets =
472 TextureTargets::new(device, config.view_formats[0], config.width, config.height);
473
474 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 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 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 comparisons: &[wgpu_test::ComparisonType::Mean(0.005)],
548 _phantom: std::marker::PhantomData::<Example>,
549};