Skip to main content

bevy_core_pipeline/core_2d/
mod.rs

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