1use bevy_app::{App, Plugin};
18use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
19use bevy_camera::{Camera3d, PhysicalCameraParameters, Projection};
20use bevy_derive::{Deref, DerefMut};
21use bevy_ecs::{
22 component::Component,
23 entity::Entity,
24 query::{QueryItem, With},
25 reflect::ReflectComponent,
26 resource::Resource,
27 schedule::IntoScheduleConfigs as _,
28 system::{lifetimeless::Read, Commands, Query, Res, ResMut},
29 world::World,
30};
31use bevy_image::BevyDefault as _;
32use bevy_math::ops;
33use bevy_reflect::{prelude::ReflectDefault, Reflect};
34use bevy_render::{
35 diagnostic::RecordDiagnostics,
36 extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
37 render_graph::{
38 NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,
39 },
40 render_resource::{
41 binding_types::{
42 sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,
43 },
44 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
45 CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,
46 Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
47 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
48 ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,
49 TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
50 },
51 renderer::{RenderContext, RenderDevice},
52 sync_component::SyncComponentPlugin,
53 sync_world::RenderEntity,
54 texture::{CachedTexture, TextureCache},
55 view::{
56 prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
57 ViewUniformOffset, ViewUniforms,
58 },
59 Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
60};
61use bevy_shader::Shader;
62use bevy_utils::{default, once};
63use smallvec::SmallVec;
64use tracing::{info, warn};
65
66use bevy_core_pipeline::{
67 core_3d::{
68 graph::{Core3d, Node3d},
69 DEPTH_TEXTURE_SAMPLING_SUPPORTED,
70 },
71 FullscreenShader,
72};
73
74#[derive(Default)]
76pub struct DepthOfFieldPlugin;
77
78#[derive(Component, Clone, Copy, Reflect)]
83#[reflect(Component, Clone, Default)]
84pub struct DepthOfField {
85 pub mode: DepthOfFieldMode,
87
88 pub focal_distance: f32,
90
91 pub sensor_height: f32,
100
101 pub aperture_f_stops: f32,
104
105 pub max_circle_of_confusion_diameter: f32,
112
113 pub max_depth: f32,
123}
124
125#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
127#[reflect(Default, Clone, 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 embedded_asset!(app, "dof.wgsl");
211
212 app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());
213
214 app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());
215
216 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
217 return;
218 };
219
220 render_app
221 .init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()
222 .init_resource::<DepthOfFieldGlobalBindGroup>()
223 .add_systems(RenderStartup, init_dof_global_bind_group_layout)
224 .add_systems(ExtractSchedule, extract_depth_of_field_settings)
225 .add_systems(
226 Render,
227 (
228 configure_depth_of_field_view_targets,
229 prepare_auxiliary_depth_of_field_textures,
230 )
231 .after(prepare_view_targets)
232 .in_set(RenderSystems::ManageViews),
233 )
234 .add_systems(
235 Render,
236 (
237 prepare_depth_of_field_view_bind_group_layouts,
238 prepare_depth_of_field_pipelines,
239 )
240 .chain()
241 .in_set(RenderSystems::Prepare),
242 )
243 .add_systems(
244 Render,
245 prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups),
246 )
247 .add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)
248 .add_render_graph_edges(
249 Core3d,
250 (Node3d::Bloom, Node3d::DepthOfField, Node3d::Tonemapping),
251 );
252 }
253}
254
255#[derive(Default)]
257pub struct DepthOfFieldNode;
258
259#[derive(Resource, Clone)]
262pub struct DepthOfFieldGlobalBindGroupLayout {
263 layout: BindGroupLayout,
265 color_texture_sampler: Sampler,
267}
268
269#[derive(Resource, Default, Deref, DerefMut)]
272pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);
273
274#[derive(Component)]
275pub enum DepthOfFieldPipelines {
276 Gaussian {
277 horizontal: CachedRenderPipelineId,
278 vertical: CachedRenderPipelineId,
279 },
280 Bokeh {
281 pass_0: CachedRenderPipelineId,
282 pass_1: CachedRenderPipelineId,
283 },
284}
285
286struct DepthOfFieldPipelineRenderInfo {
287 pass_label: &'static str,
288 view_bind_group_label: &'static str,
289 pipeline: CachedRenderPipelineId,
290 is_dual_input: bool,
291 is_dual_output: bool,
292}
293
294#[derive(Component, Deref, DerefMut)]
300pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);
301
302#[derive(Component, Clone)]
304pub struct ViewDepthOfFieldBindGroupLayouts {
305 single_input: BindGroupLayout,
307
308 dual_input: Option<BindGroupLayout>,
312}
313
314pub struct DepthOfFieldPipeline {
317 view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,
319 global_bind_group_layout: BindGroupLayout,
322 fullscreen_shader: FullscreenShader,
324 fragment_shader: Handle<Shader>,
326}
327
328impl ViewNode for DepthOfFieldNode {
329 type ViewQuery = (
330 Read<ViewUniformOffset>,
331 Read<ViewTarget>,
332 Read<ViewDepthTexture>,
333 Read<DepthOfFieldPipelines>,
334 Read<ViewDepthOfFieldBindGroupLayouts>,
335 Read<DynamicUniformIndex<DepthOfFieldUniform>>,
336 Option<Read<AuxiliaryDepthOfFieldTexture>>,
337 );
338
339 fn run<'w>(
340 &self,
341 _: &mut RenderGraphContext,
342 render_context: &mut RenderContext<'w>,
343 (
344 view_uniform_offset,
345 view_target,
346 view_depth_texture,
347 view_pipelines,
348 view_bind_group_layouts,
349 depth_of_field_uniform_index,
350 auxiliary_dof_texture,
351 ): QueryItem<'w, '_, Self::ViewQuery>,
352 world: &'w World,
353 ) -> Result<(), NodeRunError> {
354 let pipeline_cache = world.resource::<PipelineCache>();
355 let view_uniforms = world.resource::<ViewUniforms>();
356 let global_bind_group = world.resource::<DepthOfFieldGlobalBindGroup>();
357
358 let diagnostics = render_context.diagnostic_recorder();
359
360 for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {
365 let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (
366 pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),
367 view_uniforms.uniforms.binding(),
368 &**global_bind_group,
369 ) else {
370 return Ok(());
371 };
372
373 let postprocess = view_target.post_process_write();
378
379 let view_bind_group = if pipeline_render_info.is_dual_input {
380 let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (
381 auxiliary_dof_texture,
382 view_bind_group_layouts.dual_input.as_ref(),
383 ) else {
384 once!(warn!(
385 "Should have created the auxiliary depth of field texture by now"
386 ));
387 continue;
388 };
389 render_context.render_device().create_bind_group(
390 Some(pipeline_render_info.view_bind_group_label),
391 dual_input_bind_group_layout,
392 &BindGroupEntries::sequential((
393 view_uniforms_binding,
394 view_depth_texture.view(),
395 postprocess.source,
396 &auxiliary_dof_texture.default_view,
397 )),
398 )
399 } else {
400 render_context.render_device().create_bind_group(
401 Some(pipeline_render_info.view_bind_group_label),
402 &view_bind_group_layouts.single_input,
403 &BindGroupEntries::sequential((
404 view_uniforms_binding,
405 view_depth_texture.view(),
406 postprocess.source,
407 )),
408 )
409 };
410
411 let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();
413 color_attachments.push(Some(RenderPassColorAttachment {
414 view: postprocess.destination,
415 depth_slice: None,
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 once!(warn!(
430 "Should have created the auxiliary depth of field texture by now"
431 ));
432 continue;
433 };
434 color_attachments.push(Some(RenderPassColorAttachment {
435 view: &auxiliary_dof_texture.default_view,
436 depth_slice: None,
437 resolve_target: None,
438 ops: Operations {
439 load: LoadOp::Clear(default()),
440 store: StoreOp::Store,
441 },
442 }));
443 }
444
445 let render_pass_descriptor = RenderPassDescriptor {
446 label: Some(pipeline_render_info.pass_label),
447 color_attachments: &color_attachments,
448 ..default()
449 };
450
451 let mut render_pass = render_context
452 .command_encoder()
453 .begin_render_pass(&render_pass_descriptor);
454 let pass_span =
455 diagnostics.pass_span(&mut render_pass, pipeline_render_info.pass_label);
456
457 render_pass.set_pipeline(render_pipeline);
458 render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);
460 render_pass.set_bind_group(
462 1,
463 global_bind_group,
464 &[depth_of_field_uniform_index.index()],
465 );
466 render_pass.draw(0..3, 0..1);
468
469 pass_span.end(&mut render_pass);
470 }
471
472 Ok(())
473 }
474}
475
476impl Default for DepthOfField {
477 fn default() -> Self {
478 let physical_camera_default = PhysicalCameraParameters::default();
479 Self {
480 focal_distance: 10.0,
481 aperture_f_stops: physical_camera_default.aperture_f_stops,
482 sensor_height: physical_camera_default.sensor_height,
483 max_circle_of_confusion_diameter: 64.0,
484 max_depth: f32::INFINITY,
485 mode: DepthOfFieldMode::Bokeh,
486 }
487 }
488}
489
490impl DepthOfField {
491 pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {
502 DepthOfField {
503 sensor_height: camera.sensor_height,
504 aperture_f_stops: camera.aperture_f_stops,
505 ..default()
506 }
507 }
508}
509
510pub fn init_dof_global_bind_group_layout(mut commands: Commands, render_device: Res<RenderDevice>) {
511 let layout = render_device.create_bind_group_layout(
514 Some("depth of field global bind group layout"),
515 &BindGroupLayoutEntries::sequential(
516 ShaderStages::FRAGMENT,
517 (
518 uniform_buffer::<DepthOfFieldUniform>(true),
520 sampler(SamplerBindingType::Filtering),
522 ),
523 ),
524 );
525
526 let sampler = render_device.create_sampler(&SamplerDescriptor {
528 label: Some("depth of field sampler"),
529 mag_filter: FilterMode::Linear,
530 min_filter: FilterMode::Linear,
531 ..default()
532 });
533
534 commands.insert_resource(DepthOfFieldGlobalBindGroupLayout {
535 color_texture_sampler: sampler,
536 layout,
537 });
538}
539
540pub fn prepare_depth_of_field_view_bind_group_layouts(
543 mut commands: Commands,
544 view_targets: Query<(Entity, &DepthOfField, &Msaa)>,
545 render_device: Res<RenderDevice>,
546) {
547 for (view, depth_of_field, msaa) in view_targets.iter() {
548 let single_input = render_device.create_bind_group_layout(
550 Some("depth of field bind group layout (single input)"),
551 &BindGroupLayoutEntries::sequential(
552 ShaderStages::FRAGMENT,
553 (
554 uniform_buffer::<ViewUniform>(true),
555 if *msaa != Msaa::Off {
556 texture_depth_2d_multisampled()
557 } else {
558 texture_depth_2d()
559 },
560 texture_2d(TextureSampleType::Float { filterable: true }),
561 ),
562 ),
563 );
564
565 let dual_input = match depth_of_field.mode {
568 DepthOfFieldMode::Gaussian => None,
569 DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(
570 Some("depth of field bind group layout (dual input)"),
571 &BindGroupLayoutEntries::sequential(
572 ShaderStages::FRAGMENT,
573 (
574 uniform_buffer::<ViewUniform>(true),
575 if *msaa != Msaa::Off {
576 texture_depth_2d_multisampled()
577 } else {
578 texture_depth_2d()
579 },
580 texture_2d(TextureSampleType::Float { filterable: true }),
581 texture_2d(TextureSampleType::Float { filterable: true }),
582 ),
583 ),
584 )),
585 };
586
587 commands
588 .entity(view)
589 .insert(ViewDepthOfFieldBindGroupLayouts {
590 single_input,
591 dual_input,
592 });
593 }
594}
595
596pub fn configure_depth_of_field_view_targets(
604 mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,
605) {
606 for mut camera_3d in view_targets.iter_mut() {
607 let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);
608 depth_texture_usages |= TextureUsages::TEXTURE_BINDING;
609 camera_3d.depth_texture_usages = depth_texture_usages.into();
610 }
611}
612
613pub fn prepare_depth_of_field_global_bind_group(
616 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
617 mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,
618 depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
619 render_device: Res<RenderDevice>,
620) {
621 let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {
622 return;
623 };
624
625 **dof_bind_group = Some(render_device.create_bind_group(
626 Some("depth of field global bind group"),
627 &global_bind_group_layout.layout,
628 &BindGroupEntries::sequential((
629 depth_of_field_uniforms, &global_bind_group_layout.color_texture_sampler, )),
632 ));
633}
634
635pub fn prepare_auxiliary_depth_of_field_textures(
638 mut commands: Commands,
639 render_device: Res<RenderDevice>,
640 mut texture_cache: ResMut<TextureCache>,
641 mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,
642) {
643 for (entity, view_target, depth_of_field) in view_targets.iter_mut() {
644 if depth_of_field.mode != DepthOfFieldMode::Bokeh {
646 continue;
647 }
648
649 let texture_descriptor = TextureDescriptor {
651 label: Some("depth of field auxiliary texture"),
652 size: view_target.main_texture().size(),
653 mip_level_count: 1,
654 sample_count: view_target.main_texture().sample_count(),
655 dimension: TextureDimension::D2,
656 format: view_target.main_texture_format(),
657 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
658 view_formats: &[],
659 };
660
661 let texture = texture_cache.get(&render_device, texture_descriptor);
662
663 commands
664 .entity(entity)
665 .insert(AuxiliaryDepthOfFieldTexture(texture));
666 }
667}
668
669pub fn prepare_depth_of_field_pipelines(
671 mut commands: Commands,
672 pipeline_cache: Res<PipelineCache>,
673 mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,
674 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
675 view_targets: Query<(
676 Entity,
677 &ExtractedView,
678 &DepthOfField,
679 &ViewDepthOfFieldBindGroupLayouts,
680 &Msaa,
681 )>,
682 fullscreen_shader: Res<FullscreenShader>,
683 asset_server: Res<AssetServer>,
684) {
685 for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
686 let dof_pipeline = DepthOfFieldPipeline {
687 view_bind_group_layouts: view_bind_group_layouts.clone(),
688 global_bind_group_layout: global_bind_group_layout.layout.clone(),
689 fullscreen_shader: fullscreen_shader.clone(),
690 fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),
691 };
692
693 let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);
695
696 match depth_of_field.mode {
698 DepthOfFieldMode::Gaussian => {
699 commands
700 .entity(entity)
701 .insert(DepthOfFieldPipelines::Gaussian {
702 horizontal: pipelines.specialize(
703 &pipeline_cache,
704 &dof_pipeline,
705 DepthOfFieldPipelineKey {
706 hdr,
707 multisample,
708 pass: DofPass::GaussianHorizontal,
709 },
710 ),
711 vertical: pipelines.specialize(
712 &pipeline_cache,
713 &dof_pipeline,
714 DepthOfFieldPipelineKey {
715 hdr,
716 multisample,
717 pass: DofPass::GaussianVertical,
718 },
719 ),
720 });
721 }
722
723 DepthOfFieldMode::Bokeh => {
724 commands
725 .entity(entity)
726 .insert(DepthOfFieldPipelines::Bokeh {
727 pass_0: pipelines.specialize(
728 &pipeline_cache,
729 &dof_pipeline,
730 DepthOfFieldPipelineKey {
731 hdr,
732 multisample,
733 pass: DofPass::BokehPass0,
734 },
735 ),
736 pass_1: pipelines.specialize(
737 &pipeline_cache,
738 &dof_pipeline,
739 DepthOfFieldPipelineKey {
740 hdr,
741 multisample,
742 pass: DofPass::BokehPass1,
743 },
744 ),
745 });
746 }
747 }
748 }
749}
750
751impl SpecializedRenderPipeline for DepthOfFieldPipeline {
752 type Key = DepthOfFieldPipelineKey;
753
754 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
755 let (mut layout, mut shader_defs) = (vec![], vec![]);
757 let mut targets = vec![Some(ColorTargetState {
758 format: if key.hdr {
759 ViewTarget::TEXTURE_FORMAT_HDR
760 } else {
761 TextureFormat::bevy_default()
762 },
763 blend: None,
764 write_mask: ColorWrites::ALL,
765 })];
766
767 match key.pass {
769 DofPass::GaussianHorizontal | DofPass::GaussianVertical => {
770 layout.push(self.view_bind_group_layouts.single_input.clone());
772 }
773 DofPass::BokehPass0 => {
774 layout.push(self.view_bind_group_layouts.single_input.clone());
776 targets.push(targets[0].clone());
777 }
778 DofPass::BokehPass1 => {
779 let dual_input_bind_group_layout = self
782 .view_bind_group_layouts
783 .dual_input
784 .as_ref()
785 .expect("Dual-input depth of field bind group should have been created by now")
786 .clone();
787 layout.push(dual_input_bind_group_layout);
788 shader_defs.push("DUAL_INPUT".into());
789 }
790 }
791
792 layout.push(self.global_bind_group_layout.clone());
794
795 if key.multisample {
796 shader_defs.push("MULTISAMPLED".into());
797 }
798
799 RenderPipelineDescriptor {
800 label: Some("depth of field pipeline".into()),
801 layout,
802 vertex: self.fullscreen_shader.to_vertex_state(),
803 fragment: Some(FragmentState {
804 shader: self.fragment_shader.clone(),
805 shader_defs,
806 entry_point: Some(match key.pass {
807 DofPass::GaussianHorizontal => "gaussian_horizontal".into(),
808 DofPass::GaussianVertical => "gaussian_vertical".into(),
809 DofPass::BokehPass0 => "bokeh_pass_0".into(),
810 DofPass::BokehPass1 => "bokeh_pass_1".into(),
811 }),
812 targets,
813 }),
814 ..default()
815 }
816 }
817}
818
819fn extract_depth_of_field_settings(
821 mut commands: Commands,
822 mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,
823) {
824 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
825 once!(info!(
826 "Disabling depth of field on this platform because depth textures aren't supported correctly"
827 ));
828 return;
829 }
830
831 for (entity, depth_of_field, projection) in query.iter_mut() {
832 let mut entity_commands = commands
833 .get_entity(entity)
834 .expect("Depth of field entity wasn't synced.");
835
836 let Projection::Perspective(ref perspective_projection) = *projection else {
838 entity_commands.remove::<(
840 DepthOfField,
841 DepthOfFieldUniform,
842 DepthOfFieldPipelines,
844 AuxiliaryDepthOfFieldTexture,
845 ViewDepthOfFieldBindGroupLayouts,
846 )>();
847 continue;
848 };
849
850 let focal_length =
851 calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);
852
853 entity_commands.insert((
855 *depth_of_field,
856 DepthOfFieldUniform {
857 focal_distance: depth_of_field.focal_distance,
858 focal_length,
859 coc_scale_factor: focal_length * focal_length
860 / (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),
861 max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,
862 max_depth: depth_of_field.max_depth,
863 pad_a: 0,
864 pad_b: 0,
865 pad_c: 0,
866 },
867 ));
868 }
869}
870
871pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {
875 0.5 * sensor_height / ops::tan(0.5 * fov)
876}
877
878impl DepthOfFieldPipelines {
879 fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {
882 match *self {
883 DepthOfFieldPipelines::Gaussian {
884 horizontal: horizontal_pipeline,
885 vertical: vertical_pipeline,
886 } => [
887 DepthOfFieldPipelineRenderInfo {
888 pass_label: "depth of field pass (horizontal Gaussian)",
889 view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",
890 pipeline: horizontal_pipeline,
891 is_dual_input: false,
892 is_dual_output: false,
893 },
894 DepthOfFieldPipelineRenderInfo {
895 pass_label: "depth of field pass (vertical Gaussian)",
896 view_bind_group_label: "depth of field view bind group (vertical Gaussian)",
897 pipeline: vertical_pipeline,
898 is_dual_input: false,
899 is_dual_output: false,
900 },
901 ],
902
903 DepthOfFieldPipelines::Bokeh {
904 pass_0: pass_0_pipeline,
905 pass_1: pass_1_pipeline,
906 } => [
907 DepthOfFieldPipelineRenderInfo {
908 pass_label: "depth of field pass (bokeh pass 0)",
909 view_bind_group_label: "depth of field view bind group (bokeh pass 0)",
910 pipeline: pass_0_pipeline,
911 is_dual_input: false,
912 is_dual_output: true,
913 },
914 DepthOfFieldPipelineRenderInfo {
915 pass_label: "depth of field pass (bokeh pass 1)",
916 view_bind_group_label: "depth of field view bind group (bokeh pass 1)",
917 pipeline: pass_1_pipeline,
918 is_dual_input: true,
919 is_dual_output: false,
920 },
921 ],
922 }
923 }
924}