1use bevy_app::{App, Plugin};
18use bevy_asset::{load_internal_asset, Handle};
19use bevy_derive::{Deref, DerefMut};
20use bevy_ecs::{
21 component::Component,
22 entity::Entity,
23 query::{QueryItem, With},
24 reflect::ReflectComponent,
25 schedule::IntoSystemConfigs as _,
26 system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
27 world::{FromWorld, World},
28};
29use bevy_image::BevyDefault as _;
30use bevy_math::ops;
31use bevy_reflect::{prelude::ReflectDefault, Reflect};
32use bevy_render::{
33 camera::{PhysicalCameraParameters, Projection},
34 extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
35 render_graph::{
36 NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
37 },
38 render_resource::{
39 binding_types::{
40 sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,
41 },
42 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
43 CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,
44 Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
45 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
46 ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,
47 TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
48 },
49 renderer::{RenderContext, RenderDevice},
50 sync_component::SyncComponentPlugin,
51 sync_world::RenderEntity,
52 texture::{CachedTexture, TextureCache},
53 view::{
54 prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
55 ViewUniformOffset, ViewUniforms,
56 },
57 Extract, ExtractSchedule, Render, RenderApp, RenderSet,
58};
59use bevy_utils::{info_once, prelude::default, warn_once};
60use smallvec::SmallVec;
61
62use crate::{
63 core_3d::{
64 graph::{Core3d, Node3d},
65 Camera3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED,
66 },
67 fullscreen_vertex_shader::fullscreen_shader_vertex_state,
68};
69
70const DOF_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(2031861180739216043);
71
72pub struct DepthOfFieldPlugin;
74
75#[derive(Component, Clone, Copy, Reflect)]
80#[reflect(Component, Default)]
81pub struct DepthOfField {
82 pub mode: DepthOfFieldMode,
84
85 pub focal_distance: f32,
87
88 pub sensor_height: f32,
97
98 pub aperture_f_stops: f32,
101
102 pub max_circle_of_confusion_diameter: f32,
109
110 pub max_depth: f32,
120}
121
122#[deprecated(since = "0.15.0", note = "Renamed to `DepthOfField`")]
123pub type DepthOfFieldSettings = DepthOfField;
124
125#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
127#[reflect(Default, PartialEq)]
128pub enum DepthOfFieldMode {
129 Bokeh,
138
139 #[default]
149 Gaussian,
150}
151
152#[derive(Clone, Copy, Component, ShaderType)]
154pub struct DepthOfFieldUniform {
155 focal_distance: f32,
157
158 focal_length: f32,
161
162 coc_scale_factor: f32,
167
168 max_circle_of_confusion_diameter: f32,
171
172 max_depth: f32,
175
176 pad_a: u32,
178 pad_b: u32,
180 pad_c: u32,
182}
183
184#[derive(Clone, Copy, PartialEq, Eq, Hash)]
186pub struct DepthOfFieldPipelineKey {
187 pass: DofPass,
189 hdr: bool,
191 multisample: bool,
193}
194
195#[derive(Clone, Copy, PartialEq, Eq, Hash)]
197enum DofPass {
198 GaussianHorizontal,
200 GaussianVertical,
202 BokehPass0,
204 BokehPass1,
206}
207
208impl Plugin for DepthOfFieldPlugin {
209 fn build(&self, app: &mut App) {
210 load_internal_asset!(app, DOF_SHADER_HANDLE, "dof.wgsl", Shader::from_wgsl);
211
212 app.register_type::<DepthOfField>();
213 app.register_type::<DepthOfFieldMode>();
214 app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());
215
216 app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());
217
218 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
219 return;
220 };
221
222 render_app
223 .init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()
224 .init_resource::<DepthOfFieldGlobalBindGroup>()
225 .add_systems(ExtractSchedule, extract_depth_of_field_settings)
226 .add_systems(
227 Render,
228 (
229 configure_depth_of_field_view_targets,
230 prepare_auxiliary_depth_of_field_textures,
231 )
232 .after(prepare_view_targets)
233 .in_set(RenderSet::ManageViews),
234 )
235 .add_systems(
236 Render,
237 (
238 prepare_depth_of_field_view_bind_group_layouts,
239 prepare_depth_of_field_pipelines,
240 )
241 .chain()
242 .in_set(RenderSet::Prepare),
243 )
244 .add_systems(
245 Render,
246 prepare_depth_of_field_global_bind_group.in_set(RenderSet::PrepareBindGroups),
247 )
248 .add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)
249 .add_render_graph_edges(
250 Core3d,
251 (Node3d::Bloom, Node3d::DepthOfField, Node3d::Tonemapping),
252 );
253 }
254
255 fn finish(&self, app: &mut App) {
256 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
257 return;
258 };
259
260 render_app.init_resource::<DepthOfFieldGlobalBindGroupLayout>();
261 }
262}
263
264#[derive(Default)]
266pub struct DepthOfFieldNode;
267
268#[derive(Resource, Clone)]
271pub struct DepthOfFieldGlobalBindGroupLayout {
272 layout: BindGroupLayout,
274 color_texture_sampler: Sampler,
276}
277
278#[derive(Resource, Default, Deref, DerefMut)]
281pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);
282
283#[derive(Component)]
284pub enum DepthOfFieldPipelines {
285 Gaussian {
286 horizontal: CachedRenderPipelineId,
287 vertical: CachedRenderPipelineId,
288 },
289 Bokeh {
290 pass_0: CachedRenderPipelineId,
291 pass_1: CachedRenderPipelineId,
292 },
293}
294
295struct DepthOfFieldPipelineRenderInfo {
296 pass_label: &'static str,
297 view_bind_group_label: &'static str,
298 pipeline: CachedRenderPipelineId,
299 is_dual_input: bool,
300 is_dual_output: bool,
301}
302
303#[derive(Component, Deref, DerefMut)]
309pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);
310
311#[derive(Component, Clone)]
313pub struct ViewDepthOfFieldBindGroupLayouts {
314 single_input: BindGroupLayout,
316
317 dual_input: Option<BindGroupLayout>,
321}
322
323pub struct DepthOfFieldPipeline {
326 view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,
328 global_bind_group_layout: BindGroupLayout,
331}
332
333impl ViewNode for DepthOfFieldNode {
334 type ViewQuery = (
335 Read<ViewUniformOffset>,
336 Read<ViewTarget>,
337 Read<ViewDepthTexture>,
338 Read<DepthOfFieldPipelines>,
339 Read<ViewDepthOfFieldBindGroupLayouts>,
340 Read<DynamicUniformIndex<DepthOfFieldUniform>>,
341 Option<Read<AuxiliaryDepthOfFieldTexture>>,
342 );
343
344 fn run<'w>(
345 &self,
346 _: &mut RenderGraphContext,
347 render_context: &mut RenderContext<'w>,
348 (
349 view_uniform_offset,
350 view_target,
351 view_depth_texture,
352 view_pipelines,
353 view_bind_group_layouts,
354 depth_of_field_uniform_index,
355 auxiliary_dof_texture,
356 ): QueryItem<'w, Self::ViewQuery>,
357 world: &'w World,
358 ) -> Result<(), NodeRunError> {
359 let pipeline_cache = world.resource::<PipelineCache>();
360 let view_uniforms = world.resource::<ViewUniforms>();
361 let global_bind_group = world.resource::<DepthOfFieldGlobalBindGroup>();
362
363 for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {
368 let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (
369 pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),
370 view_uniforms.uniforms.binding(),
371 &**global_bind_group,
372 ) else {
373 return Ok(());
374 };
375
376 let postprocess = view_target.post_process_write();
381
382 let view_bind_group = if pipeline_render_info.is_dual_input {
383 let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (
384 auxiliary_dof_texture,
385 view_bind_group_layouts.dual_input.as_ref(),
386 ) else {
387 warn_once!("Should have created the auxiliary depth of field texture by now");
388 continue;
389 };
390 render_context.render_device().create_bind_group(
391 Some(pipeline_render_info.view_bind_group_label),
392 dual_input_bind_group_layout,
393 &BindGroupEntries::sequential((
394 view_uniforms_binding,
395 view_depth_texture.view(),
396 postprocess.source,
397 &auxiliary_dof_texture.default_view,
398 )),
399 )
400 } else {
401 render_context.render_device().create_bind_group(
402 Some(pipeline_render_info.view_bind_group_label),
403 &view_bind_group_layouts.single_input,
404 &BindGroupEntries::sequential((
405 view_uniforms_binding,
406 view_depth_texture.view(),
407 postprocess.source,
408 )),
409 )
410 };
411
412 let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();
414 color_attachments.push(Some(RenderPassColorAttachment {
415 view: postprocess.destination,
416 resolve_target: None,
417 ops: Operations {
418 load: LoadOp::Clear(default()),
419 store: StoreOp::Store,
420 },
421 }));
422
423 if pipeline_render_info.is_dual_output {
428 let Some(auxiliary_dof_texture) = auxiliary_dof_texture else {
429 warn_once!("Should have created the auxiliary depth of field texture by now");
430 continue;
431 };
432 color_attachments.push(Some(RenderPassColorAttachment {
433 view: &auxiliary_dof_texture.default_view,
434 resolve_target: None,
435 ops: Operations {
436 load: LoadOp::Clear(default()),
437 store: StoreOp::Store,
438 },
439 }));
440 }
441
442 let render_pass_descriptor = RenderPassDescriptor {
443 label: Some(pipeline_render_info.pass_label),
444 color_attachments: &color_attachments,
445 ..default()
446 };
447
448 let mut render_pass = render_context
449 .command_encoder()
450 .begin_render_pass(&render_pass_descriptor);
451 render_pass.set_pipeline(render_pipeline);
452 render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);
454 render_pass.set_bind_group(
456 1,
457 global_bind_group,
458 &[depth_of_field_uniform_index.index()],
459 );
460 render_pass.draw(0..3, 0..1);
462 }
463
464 Ok(())
465 }
466}
467
468impl Default for DepthOfField {
469 fn default() -> Self {
470 let physical_camera_default = PhysicalCameraParameters::default();
471 Self {
472 focal_distance: 10.0,
473 aperture_f_stops: physical_camera_default.aperture_f_stops,
474 sensor_height: physical_camera_default.sensor_height,
475 max_circle_of_confusion_diameter: 64.0,
476 max_depth: f32::INFINITY,
477 mode: DepthOfFieldMode::Bokeh,
478 }
479 }
480}
481
482impl DepthOfField {
483 pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {
494 DepthOfField {
495 sensor_height: camera.sensor_height,
496 aperture_f_stops: camera.aperture_f_stops,
497 ..default()
498 }
499 }
500}
501
502impl FromWorld for DepthOfFieldGlobalBindGroupLayout {
503 fn from_world(world: &mut World) -> Self {
504 let render_device = world.resource::<RenderDevice>();
505
506 let layout = render_device.create_bind_group_layout(
509 Some("depth of field global bind group layout"),
510 &BindGroupLayoutEntries::sequential(
511 ShaderStages::FRAGMENT,
512 (
513 uniform_buffer::<DepthOfFieldUniform>(true),
515 sampler(SamplerBindingType::Filtering),
517 ),
518 ),
519 );
520
521 let sampler = render_device.create_sampler(&SamplerDescriptor {
523 label: Some("depth of field sampler"),
524 mag_filter: FilterMode::Linear,
525 min_filter: FilterMode::Linear,
526 ..default()
527 });
528
529 DepthOfFieldGlobalBindGroupLayout {
530 color_texture_sampler: sampler,
531 layout,
532 }
533 }
534}
535
536pub fn prepare_depth_of_field_view_bind_group_layouts(
539 mut commands: Commands,
540 view_targets: Query<(Entity, &DepthOfField, &Msaa)>,
541 render_device: Res<RenderDevice>,
542) {
543 for (view, depth_of_field, msaa) in view_targets.iter() {
544 let single_input = render_device.create_bind_group_layout(
546 Some("depth of field bind group layout (single input)"),
547 &BindGroupLayoutEntries::sequential(
548 ShaderStages::FRAGMENT,
549 (
550 uniform_buffer::<ViewUniform>(true),
551 if *msaa != Msaa::Off {
552 texture_depth_2d_multisampled()
553 } else {
554 texture_depth_2d()
555 },
556 texture_2d(TextureSampleType::Float { filterable: true }),
557 ),
558 ),
559 );
560
561 let dual_input = match depth_of_field.mode {
564 DepthOfFieldMode::Gaussian => None,
565 DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(
566 Some("depth of field bind group layout (dual input)"),
567 &BindGroupLayoutEntries::sequential(
568 ShaderStages::FRAGMENT,
569 (
570 uniform_buffer::<ViewUniform>(true),
571 if *msaa != Msaa::Off {
572 texture_depth_2d_multisampled()
573 } else {
574 texture_depth_2d()
575 },
576 texture_2d(TextureSampleType::Float { filterable: true }),
577 texture_2d(TextureSampleType::Float { filterable: true }),
578 ),
579 ),
580 )),
581 };
582
583 commands
584 .entity(view)
585 .insert(ViewDepthOfFieldBindGroupLayouts {
586 single_input,
587 dual_input,
588 });
589 }
590}
591
592pub fn configure_depth_of_field_view_targets(
600 mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,
601) {
602 for mut camera_3d in view_targets.iter_mut() {
603 let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);
604 depth_texture_usages |= TextureUsages::TEXTURE_BINDING;
605 camera_3d.depth_texture_usages = depth_texture_usages.into();
606 }
607}
608
609pub fn prepare_depth_of_field_global_bind_group(
612 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
613 mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,
614 depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
615 render_device: Res<RenderDevice>,
616) {
617 let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {
618 return;
619 };
620
621 **dof_bind_group = Some(render_device.create_bind_group(
622 Some("depth of field global bind group"),
623 &global_bind_group_layout.layout,
624 &BindGroupEntries::sequential((
625 depth_of_field_uniforms, &global_bind_group_layout.color_texture_sampler, )),
628 ));
629}
630
631pub fn prepare_auxiliary_depth_of_field_textures(
634 mut commands: Commands,
635 render_device: Res<RenderDevice>,
636 mut texture_cache: ResMut<TextureCache>,
637 mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,
638) {
639 for (entity, view_target, depth_of_field) in view_targets.iter_mut() {
640 if depth_of_field.mode != DepthOfFieldMode::Bokeh {
642 continue;
643 }
644
645 let texture_descriptor = TextureDescriptor {
647 label: Some("depth of field auxiliary texture"),
648 size: view_target.main_texture().size(),
649 mip_level_count: 1,
650 sample_count: view_target.main_texture().sample_count(),
651 dimension: TextureDimension::D2,
652 format: view_target.main_texture_format(),
653 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
654 view_formats: &[],
655 };
656
657 let texture = texture_cache.get(&render_device, texture_descriptor);
658
659 commands
660 .entity(entity)
661 .insert(AuxiliaryDepthOfFieldTexture(texture));
662 }
663}
664
665pub fn prepare_depth_of_field_pipelines(
667 mut commands: Commands,
668 pipeline_cache: Res<PipelineCache>,
669 mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,
670 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
671 view_targets: Query<(
672 Entity,
673 &ExtractedView,
674 &DepthOfField,
675 &ViewDepthOfFieldBindGroupLayouts,
676 &Msaa,
677 )>,
678) {
679 for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
680 let dof_pipeline = DepthOfFieldPipeline {
681 view_bind_group_layouts: view_bind_group_layouts.clone(),
682 global_bind_group_layout: global_bind_group_layout.layout.clone(),
683 };
684
685 let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);
687
688 match depth_of_field.mode {
690 DepthOfFieldMode::Gaussian => {
691 commands
692 .entity(entity)
693 .insert(DepthOfFieldPipelines::Gaussian {
694 horizontal: pipelines.specialize(
695 &pipeline_cache,
696 &dof_pipeline,
697 DepthOfFieldPipelineKey {
698 hdr,
699 multisample,
700 pass: DofPass::GaussianHorizontal,
701 },
702 ),
703 vertical: pipelines.specialize(
704 &pipeline_cache,
705 &dof_pipeline,
706 DepthOfFieldPipelineKey {
707 hdr,
708 multisample,
709 pass: DofPass::GaussianVertical,
710 },
711 ),
712 });
713 }
714
715 DepthOfFieldMode::Bokeh => {
716 commands
717 .entity(entity)
718 .insert(DepthOfFieldPipelines::Bokeh {
719 pass_0: pipelines.specialize(
720 &pipeline_cache,
721 &dof_pipeline,
722 DepthOfFieldPipelineKey {
723 hdr,
724 multisample,
725 pass: DofPass::BokehPass0,
726 },
727 ),
728 pass_1: pipelines.specialize(
729 &pipeline_cache,
730 &dof_pipeline,
731 DepthOfFieldPipelineKey {
732 hdr,
733 multisample,
734 pass: DofPass::BokehPass1,
735 },
736 ),
737 });
738 }
739 }
740 }
741}
742
743impl SpecializedRenderPipeline for DepthOfFieldPipeline {
744 type Key = DepthOfFieldPipelineKey;
745
746 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
747 let (mut layout, mut shader_defs) = (vec![], vec![]);
749 let mut targets = vec![Some(ColorTargetState {
750 format: if key.hdr {
751 ViewTarget::TEXTURE_FORMAT_HDR
752 } else {
753 TextureFormat::bevy_default()
754 },
755 blend: None,
756 write_mask: ColorWrites::ALL,
757 })];
758
759 match key.pass {
761 DofPass::GaussianHorizontal | DofPass::GaussianVertical => {
762 layout.push(self.view_bind_group_layouts.single_input.clone());
764 }
765 DofPass::BokehPass0 => {
766 layout.push(self.view_bind_group_layouts.single_input.clone());
768 targets.push(targets[0].clone());
769 }
770 DofPass::BokehPass1 => {
771 let dual_input_bind_group_layout = self
774 .view_bind_group_layouts
775 .dual_input
776 .as_ref()
777 .expect("Dual-input depth of field bind group should have been created by now")
778 .clone();
779 layout.push(dual_input_bind_group_layout);
780 shader_defs.push("DUAL_INPUT".into());
781 }
782 }
783
784 layout.push(self.global_bind_group_layout.clone());
786
787 if key.multisample {
788 shader_defs.push("MULTISAMPLED".into());
789 }
790
791 RenderPipelineDescriptor {
792 label: Some("depth of field pipeline".into()),
793 layout,
794 push_constant_ranges: vec![],
795 vertex: fullscreen_shader_vertex_state(),
796 primitive: default(),
797 depth_stencil: None,
798 multisample: default(),
799 fragment: Some(FragmentState {
800 shader: DOF_SHADER_HANDLE,
801 shader_defs,
802 entry_point: match key.pass {
803 DofPass::GaussianHorizontal => "gaussian_horizontal".into(),
804 DofPass::GaussianVertical => "gaussian_vertical".into(),
805 DofPass::BokehPass0 => "bokeh_pass_0".into(),
806 DofPass::BokehPass1 => "bokeh_pass_1".into(),
807 },
808 targets,
809 }),
810 zero_initialize_workgroup_memory: false,
811 }
812 }
813}
814
815fn extract_depth_of_field_settings(
817 mut commands: Commands,
818 mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,
819) {
820 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
821 info_once!(
822 "Disabling depth of field on this platform because depth textures aren't supported correctly"
823 );
824 return;
825 }
826
827 for (entity, depth_of_field, projection) in query.iter_mut() {
828 let mut entity_commands = commands
829 .get_entity(entity)
830 .expect("Depth of field entity wasn't synced.");
831
832 let Projection::Perspective(ref perspective_projection) = *projection else {
834 entity_commands.remove::<(
836 DepthOfField,
837 DepthOfFieldUniform,
838 DepthOfFieldPipelines,
840 AuxiliaryDepthOfFieldTexture,
841 ViewDepthOfFieldBindGroupLayouts,
842 )>();
843 continue;
844 };
845
846 let focal_length =
847 calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);
848
849 entity_commands.insert((
851 *depth_of_field,
852 DepthOfFieldUniform {
853 focal_distance: depth_of_field.focal_distance,
854 focal_length,
855 coc_scale_factor: focal_length * focal_length
856 / (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),
857 max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,
858 max_depth: depth_of_field.max_depth,
859 pad_a: 0,
860 pad_b: 0,
861 pad_c: 0,
862 },
863 ));
864 }
865}
866
867pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {
871 0.5 * sensor_height / ops::tan(0.5 * fov)
872}
873
874impl DepthOfFieldPipelines {
875 fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {
878 match *self {
879 DepthOfFieldPipelines::Gaussian {
880 horizontal: horizontal_pipeline,
881 vertical: vertical_pipeline,
882 } => [
883 DepthOfFieldPipelineRenderInfo {
884 pass_label: "depth of field pass (horizontal Gaussian)",
885 view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",
886 pipeline: horizontal_pipeline,
887 is_dual_input: false,
888 is_dual_output: false,
889 },
890 DepthOfFieldPipelineRenderInfo {
891 pass_label: "depth of field pass (vertical Gaussian)",
892 view_bind_group_label: "depth of field view bind group (vertical Gaussian)",
893 pipeline: vertical_pipeline,
894 is_dual_input: false,
895 is_dual_output: false,
896 },
897 ],
898
899 DepthOfFieldPipelines::Bokeh {
900 pass_0: pass_0_pipeline,
901 pass_1: pass_1_pipeline,
902 } => [
903 DepthOfFieldPipelineRenderInfo {
904 pass_label: "depth of field pass (bokeh pass 0)",
905 view_bind_group_label: "depth of field view bind group (bokeh pass 0)",
906 pipeline: pass_0_pipeline,
907 is_dual_input: false,
908 is_dual_output: true,
909 },
910 DepthOfFieldPipelineRenderInfo {
911 pass_label: "depth of field pass (bokeh pass 1)",
912 view_bind_group_label: "depth of field view bind group (bokeh pass 1)",
913 pipeline: pass_1_pipeline,
914 is_dual_input: true,
915 is_dual_output: false,
916 },
917 ],
918 }
919 }
920}