Skip to main content

bevy_shader_plugin/
pipeline.rs

1use std::collections::HashMap;
2
3use bevy::{
4    asset::AssetServer,
5    core_pipeline::FullscreenShader,
6    prelude::*,
7    render::{
8        render_resource::{
9            BindGroupLayout, BlendState, CachedRenderPipelineId, ColorTargetState, ColorWrites,
10            FragmentState, MultisampleState, PipelineCache, RenderPipelineDescriptor,
11            TextureFormat,
12        },
13        view::ExtractedView,
14    },
15};
16
17use crate::{FragmentExtraLayouts, bindings::AutoBufferLayoutDescriptors};
18
19/// Inserted during `Plugin::build` so `init_pipeline` can read the shader path
20/// and entry point.
21#[derive(Resource)]
22pub struct FullscreenPipelineConfig {
23    pub shader_path: &'static str,
24    pub entry_point: Option<&'static str>,
25}
26
27/// Render-world resource created by `init_pipeline`.
28///
29/// A camera's texture format depends on its window surface (and on HDR), so one
30/// pipeline is queued per distinct format the cameras use.
31#[derive(Resource)]
32pub struct FullscreenPipeline {
33    /// Pipeline description shared by every format; only the color target differs.
34    descriptor: RenderPipelineDescriptor,
35    pipelines: HashMap<TextureFormat, CachedRenderPipelineId>,
36    /// Compiled [`BindGroupLayout`] for each manual extra group registered via
37    /// [`FragmentExtraLayouts`]. Index 0 corresponds to the first manual extra group.
38    pub extra_layouts: Vec<BindGroupLayout>,
39}
40
41impl FullscreenPipeline {
42    /// The pipeline for views rendering to `format`, once it has been queued.
43    pub fn pipeline_id(&self, format: TextureFormat) -> Option<CachedRenderPipelineId> {
44        self.pipelines.get(&format).copied()
45    }
46}
47
48/// `RenderStartup` system. Prepares the pipeline description using the auto-buffer group
49/// layouts (compiled by the shared core) followed by the manual extra groups. Pipelines
50/// are queued per camera format by [`queue_fullscreen_pipelines`].
51pub(crate) fn init_pipeline(
52    mut commands: Commands,
53    asset_server: Res<AssetServer>,
54    fullscreen_shader: Res<FullscreenShader>,
55    pipeline_cache: Res<PipelineCache>,
56    config: Res<FullscreenPipelineConfig>,
57    extra_layouts: Res<FragmentExtraLayouts>,
58    auto_buffer_descriptors: Res<AutoBufferLayoutDescriptors>,
59) {
60    let mut all_layouts = auto_buffer_descriptors.0.clone();
61    all_layouts.extend(extra_layouts.0.iter().cloned());
62
63    let descriptor = RenderPipelineDescriptor {
64        label: Some("fullscreen_fragment_pipeline".into()),
65        layout: all_layouts,
66        vertex: fullscreen_shader.to_vertex_state(),
67        fragment: Some(FragmentState {
68            shader: asset_server.load(config.shader_path),
69            entry_point: config.entry_point.map(Into::into),
70            // Filled in per camera format by `queue_fullscreen_pipelines`.
71            targets: Vec::new(),
72            ..default()
73        }),
74        multisample: MultisampleState {
75            count: 1,
76            mask: !0,
77            alpha_to_coverage_enabled: false,
78        },
79        ..default()
80    };
81
82    let extra_compiled: Vec<BindGroupLayout> = extra_layouts
83        .0
84        .iter()
85        .map(|desc| pipeline_cache.get_bind_group_layout(desc))
86        .collect();
87
88    commands.insert_resource(FullscreenPipeline {
89        descriptor,
90        pipelines: HashMap::new(),
91        extra_layouts: extra_compiled,
92    });
93}
94
95/// Queues a pipeline for every camera texture format that doesn't have one yet.
96pub(crate) fn queue_fullscreen_pipelines(
97    pipeline: Option<ResMut<FullscreenPipeline>>,
98    pipeline_cache: Res<PipelineCache>,
99    views: Query<&ExtractedView>,
100) {
101    let Some(mut pipeline) = pipeline else {
102        return;
103    };
104    for view in &views {
105        let format = view.target_format;
106        if pipeline.pipelines.contains_key(&format) {
107            continue;
108        }
109        let mut descriptor = pipeline.descriptor.clone();
110        if let Some(fragment) = &mut descriptor.fragment {
111            fragment.targets = vec![Some(ColorTargetState {
112                format,
113                blend: Some(BlendState::ALPHA_BLENDING),
114                write_mask: ColorWrites::ALL,
115            })];
116        }
117        let id = pipeline_cache.queue_render_pipeline(descriptor);
118        pipeline.pipelines.insert(format, id);
119    }
120}