Path: blob/main/crates/bevy_render/src/view/visibility/range.rs
6598 views
//! Specific distances from the camera in which entities are visible, also known1//! as *hierarchical levels of detail* or *HLOD*s.23use super::VisibilityRange;4use bevy_app::{App, Plugin};5use bevy_ecs::{6entity::Entity,7lifecycle::RemovedComponents,8query::Changed,9resource::Resource,10schedule::IntoScheduleConfigs as _,11system::{Query, Res, ResMut},12};13use bevy_math::{vec4, Vec4};14use bevy_platform::collections::HashMap;15use bevy_utils::prelude::default;16use nonmax::NonMaxU16;17use wgpu::{BufferBindingType, BufferUsages};1819use crate::{20render_resource::BufferVec,21renderer::{RenderDevice, RenderQueue},22sync_world::{MainEntity, MainEntityHashMap},23Extract, ExtractSchedule, Render, RenderApp, RenderSystems,24};2526/// We need at least 4 storage buffer bindings available to enable the27/// visibility range buffer.28///29/// Even though we only use one storage buffer, the first 3 available storage30/// buffers will go to various light-related buffers. We will grab the fourth31/// buffer slot.32pub const VISIBILITY_RANGES_STORAGE_BUFFER_COUNT: u32 = 4;3334/// The size of the visibility ranges buffer in elements (not bytes) when fewer35/// than 6 storage buffers are available and we're forced to use a uniform36/// buffer instead (most notably, on WebGL 2).37const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64;3839/// A plugin that enables [`RenderVisibilityRanges`]s, which allow entities to be40/// hidden or shown based on distance to the camera.41pub struct RenderVisibilityRangePlugin;4243impl Plugin for RenderVisibilityRangePlugin {44fn build(&self, app: &mut App) {45let Some(render_app) = app.get_sub_app_mut(RenderApp) else {46return;47};4849render_app50.init_resource::<RenderVisibilityRanges>()51.add_systems(ExtractSchedule, extract_visibility_ranges)52.add_systems(53Render,54write_render_visibility_ranges.in_set(RenderSystems::PrepareResourcesFlush),55);56}57}5859/// Stores information related to [`VisibilityRange`]s in the render world.60#[derive(Resource)]61pub struct RenderVisibilityRanges {62/// Information corresponding to each entity.63entities: MainEntityHashMap<RenderVisibilityEntityInfo>,6465/// Maps a [`VisibilityRange`] to its index within the `buffer`.66///67/// This map allows us to deduplicate identical visibility ranges, which68/// saves GPU memory.69range_to_index: HashMap<VisibilityRange, NonMaxU16>,7071/// The GPU buffer that stores [`VisibilityRange`]s.72///73/// Each [`Vec4`] contains the start margin start, start margin end, end74/// margin start, and end margin end distances, in that order.75buffer: BufferVec<Vec4>,7677/// True if the buffer has been changed since the last frame and needs to be78/// reuploaded to the GPU.79buffer_dirty: bool,80}8182/// Per-entity information related to [`VisibilityRange`]s.83struct RenderVisibilityEntityInfo {84/// The index of the range within the GPU buffer.85buffer_index: NonMaxU16,86/// True if the range is abrupt: i.e. has no crossfade.87is_abrupt: bool,88}8990impl Default for RenderVisibilityRanges {91fn default() -> Self {92Self {93entities: default(),94range_to_index: default(),95buffer: BufferVec::new(96BufferUsages::STORAGE | BufferUsages::UNIFORM | BufferUsages::VERTEX,97),98buffer_dirty: true,99}100}101}102103impl RenderVisibilityRanges {104/// Clears out the [`RenderVisibilityRanges`] in preparation for a new105/// frame.106fn clear(&mut self) {107self.entities.clear();108self.range_to_index.clear();109self.buffer.clear();110self.buffer_dirty = true;111}112113/// Inserts a new entity into the [`RenderVisibilityRanges`].114fn insert(&mut self, entity: MainEntity, visibility_range: &VisibilityRange) {115// Grab a slot in the GPU buffer, or take the existing one if there116// already is one.117let buffer_index = *self118.range_to_index119.entry(visibility_range.clone())120.or_insert_with(|| {121NonMaxU16::try_from(self.buffer.push(vec4(122visibility_range.start_margin.start,123visibility_range.start_margin.end,124visibility_range.end_margin.start,125visibility_range.end_margin.end,126)) as u16)127.unwrap_or_default()128});129130self.entities.insert(131entity,132RenderVisibilityEntityInfo {133buffer_index,134is_abrupt: visibility_range.is_abrupt(),135},136);137}138139/// Returns the index in the GPU buffer corresponding to the visible range140/// for the given entity.141///142/// If the entity has no visible range, returns `None`.143#[inline]144pub fn lod_index_for_entity(&self, entity: MainEntity) -> Option<NonMaxU16> {145self.entities.get(&entity).map(|info| info.buffer_index)146}147148/// Returns true if the entity has a visibility range and it isn't abrupt:149/// i.e. if it has a crossfade.150#[inline]151pub fn entity_has_crossfading_visibility_ranges(&self, entity: MainEntity) -> bool {152self.entities153.get(&entity)154.is_some_and(|info| !info.is_abrupt)155}156157/// Returns a reference to the GPU buffer that stores visibility ranges.158#[inline]159pub fn buffer(&self) -> &BufferVec<Vec4> {160&self.buffer161}162}163164/// Extracts all [`VisibilityRange`] components from the main world to the165/// render world and inserts them into [`RenderVisibilityRanges`].166pub fn extract_visibility_ranges(167mut render_visibility_ranges: ResMut<RenderVisibilityRanges>,168visibility_ranges_query: Extract<Query<(Entity, &VisibilityRange)>>,169changed_ranges_query: Extract<Query<Entity, Changed<VisibilityRange>>>,170mut removed_visibility_ranges: Extract<RemovedComponents<VisibilityRange>>,171) {172if changed_ranges_query.is_empty() && removed_visibility_ranges.read().next().is_none() {173return;174}175176render_visibility_ranges.clear();177for (entity, visibility_range) in visibility_ranges_query.iter() {178render_visibility_ranges.insert(entity.into(), visibility_range);179}180}181182/// Writes the [`RenderVisibilityRanges`] table to the GPU.183pub fn write_render_visibility_ranges(184render_device: Res<RenderDevice>,185render_queue: Res<RenderQueue>,186mut render_visibility_ranges: ResMut<RenderVisibilityRanges>,187) {188// If there haven't been any changes, early out.189if !render_visibility_ranges.buffer_dirty {190return;191}192193// Mess with the length of the buffer to meet API requirements if necessary.194match render_device.get_supported_read_only_binding_type(VISIBILITY_RANGES_STORAGE_BUFFER_COUNT)195{196// If we're using a uniform buffer, we must have *exactly*197// `VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE` elements.198BufferBindingType::Uniform199if render_visibility_ranges.buffer.len() > VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>200{201render_visibility_ranges202.buffer203.truncate(VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE);204}205BufferBindingType::Uniform206if render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE =>207{208while render_visibility_ranges.buffer.len() < VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE {209render_visibility_ranges.buffer.push(default());210}211}212213// Otherwise, if we're using a storage buffer, just ensure there's214// something in the buffer, or else it won't get allocated.215BufferBindingType::Storage { .. } if render_visibility_ranges.buffer.is_empty() => {216render_visibility_ranges.buffer.push(default());217}218219_ => {}220}221222// Schedule the write.223render_visibility_ranges224.buffer225.write_buffer(&render_device, &render_queue);226render_visibility_ranges.buffer_dirty = false;227}228229230