Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_render/src/view/visibility/range.rs
6598 views
1
//! Specific distances from the camera in which entities are visible, also known
2
//! as *hierarchical levels of detail* or *HLOD*s.
3
4
use super::VisibilityRange;
5
use bevy_app::{App, Plugin};
6
use bevy_ecs::{
7
entity::Entity,
8
lifecycle::RemovedComponents,
9
query::Changed,
10
resource::Resource,
11
schedule::IntoScheduleConfigs as _,
12
system::{Query, Res, ResMut},
13
};
14
use bevy_math::{vec4, Vec4};
15
use bevy_platform::collections::HashMap;
16
use bevy_utils::prelude::default;
17
use nonmax::NonMaxU16;
18
use wgpu::{BufferBindingType, BufferUsages};
19
20
use 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.
33
pub 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).
38
const 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.
42
pub struct RenderVisibilityRangePlugin;
43
44
impl 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)]
62
pub 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.
84
struct 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
91
impl 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
104
impl 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`].
167
pub 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.
184
pub 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
}
229
230