Skip to main content

bevy_core_pipeline/blit/
mod.rs

1use crate::FullscreenShader;
2use bevy_app::{App, Plugin};
3use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
4use bevy_camera::CompositingSpace;
5use bevy_ecs::prelude::*;
6use bevy_render::{
7    render_resource::{
8        binding_types::{sampler, texture_2d},
9        *,
10    },
11    renderer::RenderDevice,
12    GpuResourceAppExt, RenderApp, RenderStartup,
13};
14use bevy_shader::Shader;
15use bevy_utils::default;
16
17/// Adds support for specialized "blit pipelines", which can be used to write one texture to another.
18pub struct BlitPlugin;
19
20impl Plugin for BlitPlugin {
21    fn build(&self, app: &mut App) {
22        embedded_asset!(app, "blit.wgsl");
23
24        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
25            return;
26        };
27
28        render_app
29            .allow_ambiguous_resource::<SpecializedRenderPipelines<BlitPipeline>>()
30            .init_gpu_resource::<SpecializedRenderPipelines<BlitPipeline>>()
31            .add_systems(RenderStartup, init_blit_pipeline);
32    }
33}
34
35#[derive(Resource)]
36pub struct BlitPipeline {
37    pub layout: BindGroupLayoutDescriptor,
38    pub sampler: Sampler,
39    pub fullscreen_shader: FullscreenShader,
40    pub fragment_shader: Handle<Shader>,
41}
42
43pub fn init_blit_pipeline(
44    mut commands: Commands,
45    render_device: Res<RenderDevice>,
46    fullscreen_shader: Res<FullscreenShader>,
47    asset_server: Res<AssetServer>,
48) {
49    let layout = BindGroupLayoutDescriptor::new(
50        "blit_bind_group_layout",
51        &BindGroupLayoutEntries::sequential(
52            ShaderStages::FRAGMENT,
53            (
54                texture_2d(TextureSampleType::Float { filterable: false }),
55                sampler(SamplerBindingType::NonFiltering),
56            ),
57        ),
58    );
59
60    let sampler = render_device.create_sampler(&SamplerDescriptor::default());
61
62    commands.insert_resource(BlitPipeline {
63        layout,
64        sampler,
65        fullscreen_shader: fullscreen_shader.clone(),
66        fragment_shader: load_embedded_asset!(asset_server.as_ref(), "blit.wgsl"),
67    });
68}
69
70impl BlitPipeline {
71    pub fn create_bind_group(
72        &self,
73        render_device: &RenderDevice,
74        src_texture: &TextureView,
75        pipeline_cache: &PipelineCache,
76    ) -> BindGroup {
77        render_device.create_bind_group(
78            None,
79            &pipeline_cache.get_bind_group_layout(&self.layout),
80            &BindGroupEntries::sequential((src_texture, &self.sampler)),
81        )
82    }
83}
84
85#[derive(PartialEq, Eq, Hash, Clone, Copy)]
86pub struct BlitPipelineKey {
87    pub target_format: TextureFormat,
88    pub blend_state: Option<BlendState>,
89    pub samples: u32,
90    /// Color space of the source texture. When `Some(Srgb)` or `Some(Oklab)`, the blit converts
91    /// to linear RGB before writing to the output target.
92    pub source_space: Option<CompositingSpace>,
93}
94
95impl SpecializedRenderPipeline for BlitPipeline {
96    type Key = BlitPipelineKey;
97
98    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
99        let mut shader_defs = Vec::new();
100        match key.source_space {
101            Some(CompositingSpace::Srgb) => shader_defs.push("SRGB_TO_LINEAR".into()),
102            Some(CompositingSpace::Oklab) => shader_defs.push("OKLAB_TO_LINEAR".into()),
103            Some(CompositingSpace::Linear) | None => {}
104        }
105
106        RenderPipelineDescriptor {
107            label: Some("blit pipeline".into()),
108            layout: vec![self.layout.clone()],
109            vertex: self.fullscreen_shader.to_vertex_state(),
110            fragment: Some(FragmentState {
111                shader: self.fragment_shader.clone(),
112                shader_defs,
113                targets: vec![Some(ColorTargetState {
114                    format: key.target_format,
115                    blend: key.blend_state,
116                    write_mask: ColorWrites::ALL,
117                })],
118                ..default()
119            }),
120            multisample: MultisampleState {
121                count: key.samples,
122                ..default()
123            },
124            ..default()
125        }
126    }
127}