1use bevy_app::{App, Plugin};
18use bevy_asset::{load_internal_asset, weak_handle, Handle};
19use bevy_derive::{Deref, DerefMut};
20use bevy_ecs::{
21 component::Component,
22 entity::Entity,
23 query::{QueryItem, With},
24 reflect::ReflectComponent,
25 resource::Resource,
26 schedule::IntoScheduleConfigs as _,
27 system::{lifetimeless::Read, Commands, Query, Res, ResMut},
28 world::{FromWorld, World},
29};
30use bevy_image::BevyDefault as _;
31use bevy_math::ops;
32use bevy_reflect::{prelude::ReflectDefault, Reflect};
33use bevy_render::{
34 camera::{PhysicalCameraParameters, Projection},
35 extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
36 render_graph::{
37 NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
38 },
39 render_resource::{
40 binding_types::{
41 sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,
42 },
43 BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
44 CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,
45 Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
46 RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
47 ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,
48 TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
49 },
50 renderer::{RenderContext, RenderDevice},
51 sync_component::SyncComponentPlugin,
52 sync_world::RenderEntity,
53 texture::{CachedTexture, TextureCache},
54 view::{
55 prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
56 ViewUniformOffset, ViewUniforms,
57 },
58 Extract, ExtractSchedule, Render, RenderApp, RenderSet,
59};
60use bevy_utils::{default, once};
61use smallvec::SmallVec;
62use tracing::{info, warn};
63
64use crate::{
65 core_3d::{
66 graph::{Core3d, Node3d},
67 Camera3d, DEPTH_TEXTURE_SAMPLING_SUPPORTED,
68 },
69 fullscreen_vertex_shader::fullscreen_shader_vertex_state,
70};
71
72const DOF_SHADER_HANDLE: Handle<Shader> = weak_handle!("c3580ddc-2cbc-4535-a02b-9a2959066b52");
73
74pub struct DepthOfFieldPlugin;
76
77#[derive(Component, Clone, Copy, Reflect)]
82#[reflect(Component, Clone, Default)]
83pub struct DepthOfField {
84 pub mode: DepthOfFieldMode,
86
87 pub focal_distance: f32,
89
90 pub sensor_height: f32,
99
100 pub aperture_f_stops: f32,
103
104 pub max_circle_of_confusion_diameter: f32,
111
112 pub max_depth: f32,
122}
123
124#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
126#[reflect(Default, Clone, PartialEq)]
127pub enum DepthOfFieldMode {
128 Bokeh,
137
138 #[default]
148 Gaussian,
149}
150
151#[derive(Clone, Copy, Component, ShaderType)]
153pub struct DepthOfFieldUniform {
154 focal_distance: f32,
156
157 focal_length: f32,
160
161 coc_scale_factor: f32,
166
167 max_circle_of_confusion_diameter: f32,
170
171 max_depth: f32,
174
175 pad_a: u32,
177 pad_b: u32,
179 pad_c: u32,
181}
182
183#[derive(Clone, Copy, PartialEq, Eq, Hash)]
185pub struct DepthOfFieldPipelineKey {
186 pass: DofPass,
188 hdr: bool,
190 multisample: bool,
192}
193
194#[derive(Clone, Copy, PartialEq, Eq, Hash)]
196enum DofPass {
197 GaussianHorizontal,
199 GaussianVertical,
201 BokehPass0,
203 BokehPass1,
205}
206
207impl Plugin for DepthOfFieldPlugin {
208 fn build(&self, app: &mut App) {
209 load_internal_asset!(app, DOF_SHADER_HANDLE, "dof.wgsl", Shader::from_wgsl);
210
211 app.register_type::<DepthOfField>();
212 app.register_type::<DepthOfFieldMode>();
213 app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());
214
215 app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());
216
217 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
218 return;
219 };
220
221 render_app
222 .init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()
223 .init_resource::<DepthOfFieldGlobalBindGroup>()
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(RenderSet::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(RenderSet::Prepare),
242 )
243 .add_systems(
244 Render,
245 prepare_depth_of_field_global_bind_group.in_set(RenderSet::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 fn finish(&self, app: &mut App) {
255 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
256 return;
257 };
258
259 render_app.init_resource::<DepthOfFieldGlobalBindGroupLayout>();
260 }
261}
262
263#[derive(Default)]
265pub struct DepthOfFieldNode;
266
267#[derive(Resource, Clone)]
270pub struct DepthOfFieldGlobalBindGroupLayout {
271 layout: BindGroupLayout,
273 color_texture_sampler: Sampler,
275}
276
277#[derive(Resource, Default, Deref, DerefMut)]
280pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);
281
282#[derive(Component)]
283pub enum DepthOfFieldPipelines {
284 Gaussian {
285 horizontal: CachedRenderPipelineId,
286 vertical: CachedRenderPipelineId,
287 },
288 Bokeh {
289 pass_0: CachedRenderPipelineId,
290 pass_1: CachedRenderPipelineId,
291 },
292}
293
294struct DepthOfFieldPipelineRenderInfo {
295 pass_label: &'static str,
296 view_bind_group_label: &'static str,
297 pipeline: CachedRenderPipelineId,
298 is_dual_input: bool,
299 is_dual_output: bool,
300}
301
302#[derive(Component, Deref, DerefMut)]
308pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);
309
310#[derive(Component, Clone)]
312pub struct ViewDepthOfFieldBindGroupLayouts {
313 single_input: BindGroupLayout,
315
316 dual_input: Option<BindGroupLayout>,
320}
321
322pub struct DepthOfFieldPipeline {
325 view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,
327 global_bind_group_layout: BindGroupLayout,
330}
331
332impl ViewNode for DepthOfFieldNode {
333 type ViewQuery = (
334 Read<ViewUniformOffset>,
335 Read<ViewTarget>,
336 Read<ViewDepthTexture>,
337 Read<DepthOfFieldPipelines>,
338 Read<ViewDepthOfFieldBindGroupLayouts>,
339 Read<DynamicUniformIndex<DepthOfFieldUniform>>,
340 Option<Read<AuxiliaryDepthOfFieldTexture>>,
341 );
342
343 fn run<'w>(
344 &self,
345 _: &mut RenderGraphContext,
346 render_context: &mut RenderContext<'w>,
347 (
348 view_uniform_offset,
349 view_target,
350 view_depth_texture,
351 view_pipelines,
352 view_bind_group_layouts,
353 depth_of_field_uniform_index,
354 auxiliary_dof_texture,
355 ): QueryItem<'w, Self::ViewQuery>,
356 world: &'w World,
357 ) -> Result<(), NodeRunError> {
358 let pipeline_cache = world.resource::<PipelineCache>();
359 let view_uniforms = world.resource::<ViewUniforms>();
360 let global_bind_group = world.resource::<DepthOfFieldGlobalBindGroup>();
361
362 for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {
367 let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (
368 pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),
369 view_uniforms.uniforms.binding(),
370 &**global_bind_group,
371 ) else {
372 return Ok(());
373 };
374
375 let postprocess = view_target.post_process_write();
380
381 let view_bind_group = if pipeline_render_info.is_dual_input {
382 let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (
383 auxiliary_dof_texture,
384 view_bind_group_layouts.dual_input.as_ref(),
385 ) else {
386 once!(warn!(
387 "Should have created the auxiliary depth of field texture by now"
388 ));
389 continue;
390 };
391 render_context.render_device().create_bind_group(
392 Some(pipeline_render_info.view_bind_group_label),
393 dual_input_bind_group_layout,
394 &BindGroupEntries::sequential((
395 view_uniforms_binding,
396 view_depth_texture.view(),
397 postprocess.source,
398 &auxiliary_dof_texture.default_view,
399 )),
400 )
401 } else {
402 render_context.render_device().create_bind_group(
403 Some(pipeline_render_info.view_bind_group_label),
404 &view_bind_group_layouts.single_input,
405 &BindGroupEntries::sequential((
406 view_uniforms_binding,
407 view_depth_texture.view(),
408 postprocess.source,
409 )),
410 )
411 };
412
413 let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();
415 color_attachments.push(Some(RenderPassColorAttachment {
416 view: postprocess.destination,
417 resolve_target: None,
418 ops: Operations {
419 load: LoadOp::Clear(default()),
420 store: StoreOp::Store,
421 },
422 }));
423
424 if pipeline_render_info.is_dual_output {
429 let Some(auxiliary_dof_texture) = auxiliary_dof_texture else {
430 once!(warn!(
431 "Should have created the auxiliary depth of field texture by now"
432 ));
433 continue;
434 };
435 color_attachments.push(Some(RenderPassColorAttachment {
436 view: &auxiliary_dof_texture.default_view,
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 render_pass.set_pipeline(render_pipeline);
455 render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);
457 render_pass.set_bind_group(
459 1,
460 global_bind_group,
461 &[depth_of_field_uniform_index.index()],
462 );
463 render_pass.draw(0..3, 0..1);
465 }
466
467 Ok(())
468 }
469}
470
471impl Default for DepthOfField {
472 fn default() -> Self {
473 let physical_camera_default = PhysicalCameraParameters::default();
474 Self {
475 focal_distance: 10.0,
476 aperture_f_stops: physical_camera_default.aperture_f_stops,
477 sensor_height: physical_camera_default.sensor_height,
478 max_circle_of_confusion_diameter: 64.0,
479 max_depth: f32::INFINITY,
480 mode: DepthOfFieldMode::Bokeh,
481 }
482 }
483}
484
485impl DepthOfField {
486 pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {
497 DepthOfField {
498 sensor_height: camera.sensor_height,
499 aperture_f_stops: camera.aperture_f_stops,
500 ..default()
501 }
502 }
503}
504
505impl FromWorld for DepthOfFieldGlobalBindGroupLayout {
506 fn from_world(world: &mut World) -> Self {
507 let render_device = world.resource::<RenderDevice>();
508
509 let layout = render_device.create_bind_group_layout(
512 Some("depth of field global bind group layout"),
513 &BindGroupLayoutEntries::sequential(
514 ShaderStages::FRAGMENT,
515 (
516 uniform_buffer::<DepthOfFieldUniform>(true),
518 sampler(SamplerBindingType::Filtering),
520 ),
521 ),
522 );
523
524 let sampler = render_device.create_sampler(&SamplerDescriptor {
526 label: Some("depth of field sampler"),
527 mag_filter: FilterMode::Linear,
528 min_filter: FilterMode::Linear,
529 ..default()
530 });
531
532 DepthOfFieldGlobalBindGroupLayout {
533 color_texture_sampler: sampler,
534 layout,
535 }
536 }
537}
538
539pub fn prepare_depth_of_field_view_bind_group_layouts(
542 mut commands: Commands,
543 view_targets: Query<(Entity, &DepthOfField, &Msaa)>,
544 render_device: Res<RenderDevice>,
545) {
546 for (view, depth_of_field, msaa) in view_targets.iter() {
547 let single_input = render_device.create_bind_group_layout(
549 Some("depth of field bind group layout (single input)"),
550 &BindGroupLayoutEntries::sequential(
551 ShaderStages::FRAGMENT,
552 (
553 uniform_buffer::<ViewUniform>(true),
554 if *msaa != Msaa::Off {
555 texture_depth_2d_multisampled()
556 } else {
557 texture_depth_2d()
558 },
559 texture_2d(TextureSampleType::Float { filterable: true }),
560 ),
561 ),
562 );
563
564 let dual_input = match depth_of_field.mode {
567 DepthOfFieldMode::Gaussian => None,
568 DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(
569 Some("depth of field bind group layout (dual input)"),
570 &BindGroupLayoutEntries::sequential(
571 ShaderStages::FRAGMENT,
572 (
573 uniform_buffer::<ViewUniform>(true),
574 if *msaa != Msaa::Off {
575 texture_depth_2d_multisampled()
576 } else {
577 texture_depth_2d()
578 },
579 texture_2d(TextureSampleType::Float { filterable: true }),
580 texture_2d(TextureSampleType::Float { filterable: true }),
581 ),
582 ),
583 )),
584 };
585
586 commands
587 .entity(view)
588 .insert(ViewDepthOfFieldBindGroupLayouts {
589 single_input,
590 dual_input,
591 });
592 }
593}
594
595pub fn configure_depth_of_field_view_targets(
603 mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,
604) {
605 for mut camera_3d in view_targets.iter_mut() {
606 let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);
607 depth_texture_usages |= TextureUsages::TEXTURE_BINDING;
608 camera_3d.depth_texture_usages = depth_texture_usages.into();
609 }
610}
611
612pub fn prepare_depth_of_field_global_bind_group(
615 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
616 mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,
617 depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
618 render_device: Res<RenderDevice>,
619) {
620 let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {
621 return;
622 };
623
624 **dof_bind_group = Some(render_device.create_bind_group(
625 Some("depth of field global bind group"),
626 &global_bind_group_layout.layout,
627 &BindGroupEntries::sequential((
628 depth_of_field_uniforms, &global_bind_group_layout.color_texture_sampler, )),
631 ));
632}
633
634pub fn prepare_auxiliary_depth_of_field_textures(
637 mut commands: Commands,
638 render_device: Res<RenderDevice>,
639 mut texture_cache: ResMut<TextureCache>,
640 mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,
641) {
642 for (entity, view_target, depth_of_field) in view_targets.iter_mut() {
643 if depth_of_field.mode != DepthOfFieldMode::Bokeh {
645 continue;
646 }
647
648 let texture_descriptor = TextureDescriptor {
650 label: Some("depth of field auxiliary texture"),
651 size: view_target.main_texture().size(),
652 mip_level_count: 1,
653 sample_count: view_target.main_texture().sample_count(),
654 dimension: TextureDimension::D2,
655 format: view_target.main_texture_format(),
656 usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
657 view_formats: &[],
658 };
659
660 let texture = texture_cache.get(&render_device, texture_descriptor);
661
662 commands
663 .entity(entity)
664 .insert(AuxiliaryDepthOfFieldTexture(texture));
665 }
666}
667
668pub fn prepare_depth_of_field_pipelines(
670 mut commands: Commands,
671 pipeline_cache: Res<PipelineCache>,
672 mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,
673 global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
674 view_targets: Query<(
675 Entity,
676 &ExtractedView,
677 &DepthOfField,
678 &ViewDepthOfFieldBindGroupLayouts,
679 &Msaa,
680 )>,
681) {
682 for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
683 let dof_pipeline = DepthOfFieldPipeline {
684 view_bind_group_layouts: view_bind_group_layouts.clone(),
685 global_bind_group_layout: global_bind_group_layout.layout.clone(),
686 };
687
688 let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);
690
691 match depth_of_field.mode {
693 DepthOfFieldMode::Gaussian => {
694 commands
695 .entity(entity)
696 .insert(DepthOfFieldPipelines::Gaussian {
697 horizontal: pipelines.specialize(
698 &pipeline_cache,
699 &dof_pipeline,
700 DepthOfFieldPipelineKey {
701 hdr,
702 multisample,
703 pass: DofPass::GaussianHorizontal,
704 },
705 ),
706 vertical: pipelines.specialize(
707 &pipeline_cache,
708 &dof_pipeline,
709 DepthOfFieldPipelineKey {
710 hdr,
711 multisample,
712 pass: DofPass::GaussianVertical,
713 },
714 ),
715 });
716 }
717
718 DepthOfFieldMode::Bokeh => {
719 commands
720 .entity(entity)
721 .insert(DepthOfFieldPipelines::Bokeh {
722 pass_0: pipelines.specialize(
723 &pipeline_cache,
724 &dof_pipeline,
725 DepthOfFieldPipelineKey {
726 hdr,
727 multisample,
728 pass: DofPass::BokehPass0,
729 },
730 ),
731 pass_1: pipelines.specialize(
732 &pipeline_cache,
733 &dof_pipeline,
734 DepthOfFieldPipelineKey {
735 hdr,
736 multisample,
737 pass: DofPass::BokehPass1,
738 },
739 ),
740 });
741 }
742 }
743 }
744}
745
746impl SpecializedRenderPipeline for DepthOfFieldPipeline {
747 type Key = DepthOfFieldPipelineKey;
748
749 fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
750 let (mut layout, mut shader_defs) = (vec![], vec![]);
752 let mut targets = vec![Some(ColorTargetState {
753 format: if key.hdr {
754 ViewTarget::TEXTURE_FORMAT_HDR
755 } else {
756 TextureFormat::bevy_default()
757 },
758 blend: None,
759 write_mask: ColorWrites::ALL,
760 })];
761
762 match key.pass {
764 DofPass::GaussianHorizontal | DofPass::GaussianVertical => {
765 layout.push(self.view_bind_group_layouts.single_input.clone());
767 }
768 DofPass::BokehPass0 => {
769 layout.push(self.view_bind_group_layouts.single_input.clone());
771 targets.push(targets[0].clone());
772 }
773 DofPass::BokehPass1 => {
774 let dual_input_bind_group_layout = self
777 .view_bind_group_layouts
778 .dual_input
779 .as_ref()
780 .expect("Dual-input depth of field bind group should have been created by now")
781 .clone();
782 layout.push(dual_input_bind_group_layout);
783 shader_defs.push("DUAL_INPUT".into());
784 }
785 }
786
787 layout.push(self.global_bind_group_layout.clone());
789
790 if key.multisample {
791 shader_defs.push("MULTISAMPLED".into());
792 }
793
794 RenderPipelineDescriptor {
795 label: Some("depth of field pipeline".into()),
796 layout,
797 push_constant_ranges: vec![],
798 vertex: fullscreen_shader_vertex_state(),
799 primitive: default(),
800 depth_stencil: None,
801 multisample: default(),
802 fragment: Some(FragmentState {
803 shader: DOF_SHADER_HANDLE,
804 shader_defs,
805 entry_point: match key.pass {
806 DofPass::GaussianHorizontal => "gaussian_horizontal".into(),
807 DofPass::GaussianVertical => "gaussian_vertical".into(),
808 DofPass::BokehPass0 => "bokeh_pass_0".into(),
809 DofPass::BokehPass1 => "bokeh_pass_1".into(),
810 },
811 targets,
812 }),
813 zero_initialize_workgroup_memory: false,
814 }
815 }
816}
817
818fn extract_depth_of_field_settings(
820 mut commands: Commands,
821 mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,
822) {
823 if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
824 once!(info!(
825 "Disabling depth of field on this platform because depth textures aren't supported correctly"
826 ));
827 return;
828 }
829
830 for (entity, depth_of_field, projection) in query.iter_mut() {
831 let mut entity_commands = commands
832 .get_entity(entity)
833 .expect("Depth of field entity wasn't synced.");
834
835 let Projection::Perspective(ref perspective_projection) = *projection else {
837 entity_commands.remove::<(
839 DepthOfField,
840 DepthOfFieldUniform,
841 DepthOfFieldPipelines,
843 AuxiliaryDepthOfFieldTexture,
844 ViewDepthOfFieldBindGroupLayouts,
845 )>();
846 continue;
847 };
848
849 let focal_length =
850 calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);
851
852 entity_commands.insert((
854 *depth_of_field,
855 DepthOfFieldUniform {
856 focal_distance: depth_of_field.focal_distance,
857 focal_length,
858 coc_scale_factor: focal_length * focal_length
859 / (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),
860 max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,
861 max_depth: depth_of_field.max_depth,
862 pad_a: 0,
863 pad_b: 0,
864 pad_c: 0,
865 },
866 ));
867 }
868}
869
870pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {
874 0.5 * sensor_height / ops::tan(0.5 * fov)
875}
876
877impl DepthOfFieldPipelines {
878 fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {
881 match *self {
882 DepthOfFieldPipelines::Gaussian {
883 horizontal: horizontal_pipeline,
884 vertical: vertical_pipeline,
885 } => [
886 DepthOfFieldPipelineRenderInfo {
887 pass_label: "depth of field pass (horizontal Gaussian)",
888 view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",
889 pipeline: horizontal_pipeline,
890 is_dual_input: false,
891 is_dual_output: false,
892 },
893 DepthOfFieldPipelineRenderInfo {
894 pass_label: "depth of field pass (vertical Gaussian)",
895 view_bind_group_label: "depth of field view bind group (vertical Gaussian)",
896 pipeline: vertical_pipeline,
897 is_dual_input: false,
898 is_dual_output: false,
899 },
900 ],
901
902 DepthOfFieldPipelines::Bokeh {
903 pass_0: pass_0_pipeline,
904 pass_1: pass_1_pipeline,
905 } => [
906 DepthOfFieldPipelineRenderInfo {
907 pass_label: "depth of field pass (bokeh pass 0)",
908 view_bind_group_label: "depth of field view bind group (bokeh pass 0)",
909 pipeline: pass_0_pipeline,
910 is_dual_input: false,
911 is_dual_output: true,
912 },
913 DepthOfFieldPipelineRenderInfo {
914 pass_label: "depth of field pass (bokeh pass 1)",
915 view_bind_group_label: "depth of field view bind group (bokeh pass 1)",
916 pipeline: pass_1_pipeline,
917 is_dual_input: true,
918 is_dual_output: false,
919 },
920 ],
921 }
922 }
923}