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