bevy_render/view/visibility/
range.rs

1//! Specific distances from the camera in which entities are visible, also known
2//! as *hierarchical levels of detail* or *HLOD*s.
3
4use super::VisibilityRange;
5use bevy_app::{App, Plugin};
6use bevy_ecs::{
7    entity::Entity,
8    lifecycle::RemovedComponents,
9    query::Changed,
10    resource::Resource,
11    schedule::IntoScheduleConfigs as _,
12    system::{Query, Res, ResMut},
13};
14use bevy_math::{vec4, Vec4};
15use bevy_platform::collections::HashMap;
16use bevy_utils::prelude::default;
17use nonmax::NonMaxU16;
18use wgpu::{BufferBindingType, BufferUsages};
19
20use crate::{
21    render_resource::BufferVec,
22    renderer::{RenderDevice, RenderQueue},
23    sync_world::{MainEntity, MainEntityHashMap},
24    Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
25};
26
27/// We need at least 4 storage buffer bindings available to enable the
28/// visibility range buffer.
29///
30/// Even though we only use one storage buffer, the first 3 available storage
31/// buffers will go to various light-related buffers. We will grab the fourth
32/// buffer slot.
33pub const VISIBILITY_RANGES_STORAGE_BUFFER_COUNT: u32 = 4;
34
35/// The size of the visibility ranges buffer in elements (not bytes) when fewer
36/// than 6 storage buffers are available and we're forced to use a uniform
37/// buffer instead (most notably, on WebGL 2).
38const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64;
39
40/// A plugin that enables [`RenderVisibilityRanges`]s, which allow entities to be
41/// hidden or shown based on distance to the camera.
42pub struct RenderVisibilityRangePlugin;
43
44impl Plugin for RenderVisibilityRangePlugin {
45    fn build(&self, app: &mut App) {
46        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
47            return;
48        };
49
50        render_app
51            .init_resource::<RenderVisibilityRanges>()
52            .add_systems(ExtractSchedule, extract_visibility_ranges)
53            .add_systems(
54                Render,
55                write_render_visibility_ranges.in_set(RenderSystems::PrepareResourcesFlush),
56            );
57    }
58}
59
60/// Stores information related to [`VisibilityRange`]s in the render world.
61#[derive(Resource)]
62pub struct RenderVisibilityRanges {
63    /// Information corresponding to each entity.
64    entities: MainEntityHashMap<RenderVisibilityEntityInfo>,
65
66    /// Maps a [`VisibilityRange`] to its index within the `buffer`.
67    ///
68    /// This map allows us to deduplicate identical visibility ranges, which
69    /// saves GPU memory.
70    range_to_index: HashMap<VisibilityRange, NonMaxU16>,
71
72    /// The GPU buffer that stores [`VisibilityRange`]s.
73    ///
74    /// Each [`Vec4`] contains the start margin start, start margin end, end
75    /// margin start, and end margin end distances, in that order.
76    buffer: BufferVec<Vec4>,
77
78    /// True if the buffer has been changed since the last frame and needs to be
79    /// reuploaded to the GPU.
80    buffer_dirty: bool,
81}
82
83/// Per-entity information related to [`VisibilityRange`]s.
84struct RenderVisibilityEntityInfo {
85    /// The index of the range within the GPU buffer.
86    buffer_index: NonMaxU16,
87    /// True if the range is abrupt: i.e. has no crossfade.
88    is_abrupt: bool,
89}
90
91impl Default for RenderVisibilityRanges {
92    fn default() -> Self {
93        Self {
94            entities: default(),
95            range_to_index: default(),
96            buffer: BufferVec::new(
97                BufferUsages::STORAGE | BufferUsages::UNIFORM | BufferUsages::VERTEX,
98            ),
99            buffer_dirty: true,
100        }
101    }
102}
103
104impl RenderVisibilityRanges {
105    /// Clears out the [`RenderVisibilityRanges`] in preparation for a new
106    /// frame.
107    fn clear(&mut self) {
108        self.entities.clear();
109        self.range_to_index.clear();
110        self.buffer.clear();
111        self.buffer_dirty = true;
112    }
113
114    /// Inserts a new entity into the [`RenderVisibilityRanges`].
115    fn insert(&mut self, entity: MainEntity, visibility_range: &VisibilityRange) {
116        // Grab a slot in the GPU buffer, or take the existing one if there
117        // already is one.
118        let buffer_index = *self
119            .range_to_index
120            .entry(visibility_range.clone())
121            .or_insert_with(|| {
122                NonMaxU16::try_from(self.buffer.push(vec4(
123                    visibility_range.start_margin.start,
124                    visibility_range.start_margin.end,
125                    visibility_range.end_margin.start,
126                    visibility_range.end_margin.end,
127                )) as u16)
128                .unwrap_or_default()
129            });
130
131        self.entities.insert(
132            entity,
133            RenderVisibilityEntityInfo {
134                buffer_index,
135                is_abrupt: visibility_range.is_abrupt(),
136            },
137        );
138    }
139
140    /// Returns the index in the GPU buffer corresponding to the visible range
141    /// for the given entity.
142    ///
143    /// If the entity has no visible range, returns `None`.
144    #[inline]
145    pub fn lod_index_for_entity(&self, entity: MainEntity) -> Option<NonMaxU16> {
146        self.entities.get(&entity).map(|info| info.buffer_index)
147    }
148
149    /// Returns true if the entity has a visibility range and it isn't abrupt:
150    /// i.e. if it has a crossfade.
151    #[inline]
152    pub fn entity_has_crossfading_visibility_ranges(&self, entity: MainEntity) -> bool {
153        self.entities
154            .get(&entity)
155            .is_some_and(|info| !info.is_abrupt)
156    }
157
158    /// Returns a reference to the GPU buffer that stores visibility ranges.
159    #[inline]
160    pub fn buffer(&self) -> &BufferVec<Vec4> {
161        &self.buffer
162    }
163}
164
165/// Extracts all [`VisibilityRange`] components from the main world to the
166/// render world and inserts them into [`RenderVisibilityRanges`].
167pub fn extract_visibility_ranges(
168    mut render_visibility_ranges: ResMut<RenderVisibilityRanges>,
169    visibility_ranges_query: Extract<Query<(Entity, &VisibilityRange)>>,
170    changed_ranges_query: Extract<Query<Entity, Changed<VisibilityRange>>>,
171    mut removed_visibility_ranges: Extract<RemovedComponents<VisibilityRange>>,
172) {
173    if changed_ranges_query.is_empty() && removed_visibility_ranges.read().next().is_none() {
174        return;
175    }
176
177    render_visibility_ranges.clear();
178    for (entity, visibility_range) in visibility_ranges_query.iter() {
179        render_visibility_ranges.insert(entity.into(), visibility_range);
180    }
181}
182
183/// Writes the [`RenderVisibilityRanges`] table to the GPU.
184pub fn write_render_visibility_ranges(
185    render_device: Res<RenderDevice>,
186    render_queue: Res<RenderQueue>,
187    mut render_visibility_ranges: ResMut<RenderVisibilityRanges>,
188) {
189    // If there haven't been any changes, early out.
190    if !render_visibility_ranges.buffer_dirty {
191        return;
192    }
193
194    // Mess with the length of the buffer to meet API requirements if necessary.
195    match render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT)
196    {
197        // If we're using a uniform buffer, we must have *exactly*
198        // `VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE` elements.
199        BufferBindingType::Uniform
200            if render_visibility_ranges.buffer.len() > VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>
201        {
202            render_visibility_ranges
203                .buffer
204                .truncate(VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE);
205        }
206        BufferBindingType::Uniform
207            if render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>
208        {
209            while render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE {
210                render_visibility_ranges.buffer.push(default());
211            }
212        }
213
214        // Otherwise, if we're using a storage buffer, just ensure there's
215        // something in the buffer, or else it won't get allocated.
216        BufferBindingType::Storage { .. } if render_visibility_ranges.buffer.is_empty() => {
217            render_visibility_ranges.buffer.push(default());
218        }
219
220        _ => {}
221    }
222
223    // Schedule the write.
224    render_visibility_ranges
225        .buffer
226        .write_buffer(&render_device, &render_queue);
227    render_visibility_ranges.buffer_dirty = false;
228}