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