bevy_core_pipeline/core_2d/
mod.rs

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