bevy_core_pipeline/core_2d/
mod.rs

1mod camera_2d;
2mod main_opaque_pass_2d_node;
3mod main_transparent_pass_2d_node;
4
5pub mod graph {
6    use bevy_render::render_graph::{RenderLabel, RenderSubGraph};
7
8    #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderSubGraph)]
9    pub struct Core2d;
10
11    pub mod input {
12        pub const VIEW_ENTITY: &str = "view_entity";
13    }
14
15    #[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
16    pub enum Node2d {
17        MsaaWriteback,
18        StartMainPass,
19        MainOpaquePass,
20        MainTransparentPass,
21        EndMainPass,
22        Bloom,
23        PostProcessing,
24        Tonemapping,
25        Fxaa,
26        Smaa,
27        Upscaling,
28        ContrastAdaptiveSharpening,
29        EndMainPassPostProcessing,
30    }
31}
32
33use core::ops::Range;
34
35use bevy_asset::UntypedAssetId;
36use bevy_utils::HashMap;
37pub use camera_2d::*;
38pub use main_opaque_pass_2d_node::*;
39pub use main_transparent_pass_2d_node::*;
40
41use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode};
42use bevy_app::{App, Plugin};
43use bevy_ecs::{entity::EntityHashSet, prelude::*};
44use bevy_math::FloatOrd;
45use bevy_render::sync_world::MainEntity;
46use bevy_render::{
47    camera::{Camera, ExtractedCamera},
48    extract_component::ExtractComponentPlugin,
49    render_graph::{EmptyNode, RenderGraphApp, ViewNodeRunner},
50    render_phase::{
51        sort_phase_system, BinnedPhaseItem, CachedRenderPipelinePhaseItem, DrawFunctionId,
52        DrawFunctions, PhaseItem, PhaseItemExtraIndex, SortedPhaseItem, ViewBinnedRenderPhases,
53        ViewSortedRenderPhases,
54    },
55    render_resource::{
56        BindGroupId, CachedRenderPipelineId, Extent3d, TextureDescriptor, TextureDimension,
57        TextureFormat, TextureUsages,
58    },
59    renderer::RenderDevice,
60    sync_world::RenderEntity,
61    texture::TextureCache,
62    view::{Msaa, ViewDepthTexture},
63    Extract, ExtractSchedule, Render, RenderApp, RenderSet,
64};
65
66use self::graph::{Core2d, Node2d};
67
68pub const CORE_2D_DEPTH_FORMAT: TextureFormat = TextureFormat::Depth32Float;
69
70pub struct Core2dPlugin;
71
72impl Plugin for Core2dPlugin {
73    fn build(&self, app: &mut App) {
74        app.register_type::<Camera2d>()
75            .add_plugins(ExtractComponentPlugin::<Camera2d>::default());
76
77        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
78            return;
79        };
80        render_app
81            .init_resource::<DrawFunctions<Opaque2d>>()
82            .init_resource::<DrawFunctions<AlphaMask2d>>()
83            .init_resource::<DrawFunctions<Transparent2d>>()
84            .init_resource::<ViewSortedRenderPhases<Transparent2d>>()
85            .init_resource::<ViewBinnedRenderPhases<Opaque2d>>()
86            .init_resource::<ViewBinnedRenderPhases<AlphaMask2d>>()
87            .add_systems(ExtractSchedule, extract_core_2d_camera_phases)
88            .add_systems(
89                Render,
90                (
91                    sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
92                    prepare_core_2d_depth_textures.in_set(RenderSet::PrepareResources),
93                ),
94            );
95
96        render_app
97            .add_render_sub_graph(Core2d)
98            .add_render_graph_node::<EmptyNode>(Core2d, Node2d::StartMainPass)
99            .add_render_graph_node::<ViewNodeRunner<MainOpaquePass2dNode>>(
100                Core2d,
101                Node2d::MainOpaquePass,
102            )
103            .add_render_graph_node::<ViewNodeRunner<MainTransparentPass2dNode>>(
104                Core2d,
105                Node2d::MainTransparentPass,
106            )
107            .add_render_graph_node::<EmptyNode>(Core2d, Node2d::EndMainPass)
108            .add_render_graph_node::<ViewNodeRunner<TonemappingNode>>(Core2d, Node2d::Tonemapping)
109            .add_render_graph_node::<EmptyNode>(Core2d, Node2d::EndMainPassPostProcessing)
110            .add_render_graph_node::<ViewNodeRunner<UpscalingNode>>(Core2d, Node2d::Upscaling)
111            .add_render_graph_edges(
112                Core2d,
113                (
114                    Node2d::StartMainPass,
115                    Node2d::MainOpaquePass,
116                    Node2d::MainTransparentPass,
117                    Node2d::EndMainPass,
118                    Node2d::Tonemapping,
119                    Node2d::EndMainPassPostProcessing,
120                    Node2d::Upscaling,
121                ),
122            );
123    }
124}
125
126/// Opaque 2D [`BinnedPhaseItem`]s.
127pub struct Opaque2d {
128    /// The key, which determines which can be batched.
129    pub key: Opaque2dBinKey,
130    /// An entity from which data will be fetched, including the mesh if
131    /// applicable.
132    pub representative_entity: (Entity, MainEntity),
133    /// The ranges of instances.
134    pub batch_range: Range<u32>,
135    /// An extra index, which is either a dynamic offset or an index in the
136    /// indirect parameters list.
137    pub extra_index: PhaseItemExtraIndex,
138}
139
140/// Data that must be identical in order to batch phase items together.
141#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
142pub struct Opaque2dBinKey {
143    /// The identifier of the render pipeline.
144    pub pipeline: CachedRenderPipelineId,
145    /// The function used to draw.
146    pub draw_function: DrawFunctionId,
147    /// The asset that this phase item is associated with.
148    ///
149    /// Normally, this is the ID of the mesh, but for non-mesh items it might be
150    /// the ID of another type of asset.
151    pub asset_id: UntypedAssetId,
152    /// The ID of a bind group specific to the material.
153    pub material_bind_group_id: Option<BindGroupId>,
154}
155
156impl PhaseItem for Opaque2d {
157    #[inline]
158    fn entity(&self) -> Entity {
159        self.representative_entity.0
160    }
161
162    fn main_entity(&self) -> MainEntity {
163        self.representative_entity.1
164    }
165
166    #[inline]
167    fn draw_function(&self) -> DrawFunctionId {
168        self.key.draw_function
169    }
170
171    #[inline]
172    fn batch_range(&self) -> &Range<u32> {
173        &self.batch_range
174    }
175
176    #[inline]
177    fn batch_range_mut(&mut self) -> &mut Range<u32> {
178        &mut self.batch_range
179    }
180
181    fn extra_index(&self) -> PhaseItemExtraIndex {
182        self.extra_index
183    }
184
185    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
186        (&mut self.batch_range, &mut self.extra_index)
187    }
188}
189
190impl BinnedPhaseItem for Opaque2d {
191    type BinKey = Opaque2dBinKey;
192
193    fn new(
194        key: Self::BinKey,
195        representative_entity: (Entity, MainEntity),
196        batch_range: Range<u32>,
197        extra_index: PhaseItemExtraIndex,
198    ) -> Self {
199        Opaque2d {
200            key,
201            representative_entity,
202            batch_range,
203            extra_index,
204        }
205    }
206}
207
208impl CachedRenderPipelinePhaseItem for Opaque2d {
209    #[inline]
210    fn cached_pipeline(&self) -> CachedRenderPipelineId {
211        self.key.pipeline
212    }
213}
214
215/// Alpha mask 2D [`BinnedPhaseItem`]s.
216pub struct AlphaMask2d {
217    /// The key, which determines which can be batched.
218    pub key: AlphaMask2dBinKey,
219    /// An entity from which data will be fetched, including the mesh if
220    /// applicable.
221    pub representative_entity: (Entity, MainEntity),
222    /// The ranges of instances.
223    pub batch_range: Range<u32>,
224    /// An extra index, which is either a dynamic offset or an index in the
225    /// indirect parameters list.
226    pub extra_index: PhaseItemExtraIndex,
227}
228
229/// Data that must be identical in order to batch phase items together.
230#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
231pub struct AlphaMask2dBinKey {
232    /// The identifier of the render pipeline.
233    pub pipeline: CachedRenderPipelineId,
234    /// The function used to draw.
235    pub draw_function: DrawFunctionId,
236    /// The asset that this phase item is associated with.
237    ///
238    /// Normally, this is the ID of the mesh, but for non-mesh items it might be
239    /// the ID of another type of asset.
240    pub asset_id: UntypedAssetId,
241    /// The ID of a bind group specific to the material.
242    pub material_bind_group_id: Option<BindGroupId>,
243}
244
245impl PhaseItem for AlphaMask2d {
246    #[inline]
247    fn entity(&self) -> Entity {
248        self.representative_entity.0
249    }
250
251    #[inline]
252    fn main_entity(&self) -> MainEntity {
253        self.representative_entity.1
254    }
255
256    #[inline]
257    fn draw_function(&self) -> DrawFunctionId {
258        self.key.draw_function
259    }
260
261    #[inline]
262    fn batch_range(&self) -> &Range<u32> {
263        &self.batch_range
264    }
265
266    #[inline]
267    fn batch_range_mut(&mut self) -> &mut Range<u32> {
268        &mut self.batch_range
269    }
270
271    fn extra_index(&self) -> PhaseItemExtraIndex {
272        self.extra_index
273    }
274
275    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
276        (&mut self.batch_range, &mut self.extra_index)
277    }
278}
279
280impl BinnedPhaseItem for AlphaMask2d {
281    type BinKey = AlphaMask2dBinKey;
282
283    fn new(
284        key: Self::BinKey,
285        representative_entity: (Entity, MainEntity),
286        batch_range: Range<u32>,
287        extra_index: PhaseItemExtraIndex,
288    ) -> Self {
289        AlphaMask2d {
290            key,
291            representative_entity,
292            batch_range,
293            extra_index,
294        }
295    }
296}
297
298impl CachedRenderPipelinePhaseItem for AlphaMask2d {
299    #[inline]
300    fn cached_pipeline(&self) -> CachedRenderPipelineId {
301        self.key.pipeline
302    }
303}
304
305/// Transparent 2D [`SortedPhaseItem`]s.
306pub struct Transparent2d {
307    pub sort_key: FloatOrd,
308    pub entity: (Entity, MainEntity),
309    pub pipeline: CachedRenderPipelineId,
310    pub draw_function: DrawFunctionId,
311    pub batch_range: Range<u32>,
312    pub extra_index: PhaseItemExtraIndex,
313}
314
315impl PhaseItem for Transparent2d {
316    #[inline]
317    fn entity(&self) -> Entity {
318        self.entity.0
319    }
320
321    #[inline]
322    fn main_entity(&self) -> MainEntity {
323        self.entity.1
324    }
325
326    #[inline]
327    fn draw_function(&self) -> DrawFunctionId {
328        self.draw_function
329    }
330
331    #[inline]
332    fn batch_range(&self) -> &Range<u32> {
333        &self.batch_range
334    }
335
336    #[inline]
337    fn batch_range_mut(&mut self) -> &mut Range<u32> {
338        &mut self.batch_range
339    }
340
341    #[inline]
342    fn extra_index(&self) -> PhaseItemExtraIndex {
343        self.extra_index
344    }
345
346    #[inline]
347    fn batch_range_and_extra_index_mut(&mut self) -> (&mut Range<u32>, &mut PhaseItemExtraIndex) {
348        (&mut self.batch_range, &mut self.extra_index)
349    }
350}
351
352impl SortedPhaseItem for Transparent2d {
353    type SortKey = FloatOrd;
354
355    #[inline]
356    fn sort_key(&self) -> Self::SortKey {
357        self.sort_key
358    }
359
360    #[inline]
361    fn sort(items: &mut [Self]) {
362        // radsort is a stable radix sort that performed better than `slice::sort_by_key` or `slice::sort_unstable_by_key`.
363        radsort::sort_by_key(items, |item| item.sort_key().0);
364    }
365}
366
367impl CachedRenderPipelinePhaseItem for Transparent2d {
368    #[inline]
369    fn cached_pipeline(&self) -> CachedRenderPipelineId {
370        self.pipeline
371    }
372}
373
374pub fn extract_core_2d_camera_phases(
375    mut transparent_2d_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
376    mut opaque_2d_phases: ResMut<ViewBinnedRenderPhases<Opaque2d>>,
377    mut alpha_mask_2d_phases: ResMut<ViewBinnedRenderPhases<AlphaMask2d>>,
378    cameras_2d: Extract<Query<(RenderEntity, &Camera), With<Camera2d>>>,
379    mut live_entities: Local<EntityHashSet>,
380) {
381    live_entities.clear();
382
383    for (entity, camera) in &cameras_2d {
384        if !camera.is_active {
385            continue;
386        }
387        transparent_2d_phases.insert_or_clear(entity);
388        opaque_2d_phases.insert_or_clear(entity);
389        alpha_mask_2d_phases.insert_or_clear(entity);
390
391        live_entities.insert(entity);
392    }
393
394    // Clear out all dead views.
395    transparent_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
396    opaque_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
397    alpha_mask_2d_phases.retain(|camera_entity, _| live_entities.contains(camera_entity));
398}
399
400pub fn prepare_core_2d_depth_textures(
401    mut commands: Commands,
402    mut texture_cache: ResMut<TextureCache>,
403    render_device: Res<RenderDevice>,
404    transparent_2d_phases: Res<ViewSortedRenderPhases<Transparent2d>>,
405    opaque_2d_phases: Res<ViewBinnedRenderPhases<Opaque2d>>,
406    views_2d: Query<(Entity, &ExtractedCamera, &Msaa), (With<Camera2d>,)>,
407) {
408    let mut textures = HashMap::default();
409    for (view, camera, msaa) in &views_2d {
410        if !opaque_2d_phases.contains_key(&view) || !transparent_2d_phases.contains_key(&view) {
411            continue;
412        };
413
414        let Some(physical_target_size) = camera.physical_target_size else {
415            continue;
416        };
417
418        let cached_texture = textures
419            .entry(camera.target.clone())
420            .or_insert_with(|| {
421                // The size of the depth texture
422                let size = Extent3d {
423                    depth_or_array_layers: 1,
424                    width: physical_target_size.x,
425                    height: physical_target_size.y,
426                };
427
428                let descriptor = TextureDescriptor {
429                    label: Some("view_depth_texture"),
430                    size,
431                    mip_level_count: 1,
432                    sample_count: msaa.samples(),
433                    dimension: TextureDimension::D2,
434                    format: CORE_2D_DEPTH_FORMAT,
435                    usage: TextureUsages::RENDER_ATTACHMENT,
436                    view_formats: &[],
437                };
438
439                texture_cache.get(&render_device, descriptor)
440            })
441            .clone();
442
443        commands
444            .entity(view)
445            .insert(ViewDepthTexture::new(cached_texture, Some(0.0)));
446    }
447}