This PR adds two systems to the sprite module that culls Sprites and AtlasSprites that are not within the camera's view. This is achieved by removing / adding a new `Viewable` Component dynamically. Some of the render queries now use a `With<Viewable>` filter to only process the sprites that are actually on screen, which improves performance drastically for scene swith a large amount of sprites off-screen. https://streamable.com/vvzh2u This scene shows a map with a 320x320 tiles, with a grid size of 64p. This is exactly 102400 Sprites in the entire scene. Without this PR, this scene runs with 1 to 4 FPS. With this PR.. .. at 720p, there are around 600 visible sprites and runs at ~215 FPS .. at 1440p there are around 2000 visible sprites and runs at ~135 FPS The Systems this PR adds take around 1.2ms (with 100K+ sprites in the scene) Note: This is only implemented for Sprites and AtlasTextureSprites. There is no culling for 3D in this PR. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
104 lines
3.0 KiB
Rust
104 lines
3.0 KiB
Rust
use crate::ColorMaterial;
|
|
use bevy_asset::{Assets, Handle};
|
|
use bevy_core::Bytes;
|
|
use bevy_ecs::{
|
|
query::Without,
|
|
system::{Query, Res},
|
|
};
|
|
use bevy_math::Vec2;
|
|
use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid};
|
|
use bevy_render::{
|
|
draw::OutsideFrustum,
|
|
renderer::{RenderResource, RenderResourceType, RenderResources},
|
|
texture::Texture,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Default, Clone, TypeUuid, Reflect, RenderResources)]
|
|
#[render_resources(from_self)]
|
|
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
|
|
#[repr(C)]
|
|
pub struct Sprite {
|
|
pub size: Vec2,
|
|
pub flip_x: bool,
|
|
pub flip_y: bool,
|
|
pub resize_mode: SpriteResizeMode,
|
|
}
|
|
|
|
impl RenderResource for Sprite {
|
|
fn resource_type(&self) -> Option<RenderResourceType> {
|
|
Some(RenderResourceType::Buffer)
|
|
}
|
|
|
|
fn buffer_byte_len(&self) -> Option<usize> {
|
|
Some(12)
|
|
}
|
|
|
|
fn write_buffer_bytes(&self, buffer: &mut [u8]) {
|
|
// Write the size buffer
|
|
let (size_buf, flip_buf) = buffer.split_at_mut(8);
|
|
self.size.write_bytes(size_buf);
|
|
|
|
// First bit means flip x, second bit means flip y
|
|
flip_buf[0] = if self.flip_x { 0b01 } else { 0 } | if self.flip_y { 0b10 } else { 0 };
|
|
flip_buf[1] = 0;
|
|
flip_buf[2] = 0;
|
|
flip_buf[3] = 0;
|
|
}
|
|
|
|
fn texture(&self) -> Option<&Handle<Texture>> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Determines how `Sprite` resize should be handled
|
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
|
|
#[reflect_value(PartialEq, Serialize, Deserialize)]
|
|
pub enum SpriteResizeMode {
|
|
Manual,
|
|
Automatic,
|
|
}
|
|
|
|
impl Default for SpriteResizeMode {
|
|
fn default() -> Self {
|
|
SpriteResizeMode::Automatic
|
|
}
|
|
}
|
|
|
|
impl Sprite {
|
|
/// Creates new `Sprite` with `SpriteResizeMode::Manual` value for `resize_mode`
|
|
pub fn new(size: Vec2) -> Self {
|
|
Self {
|
|
size,
|
|
resize_mode: SpriteResizeMode::Manual,
|
|
flip_x: false,
|
|
flip_y: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sprite_system(
|
|
materials: Res<Assets<ColorMaterial>>,
|
|
textures: Res<Assets<Texture>>,
|
|
mut query: Query<(&mut Sprite, &Handle<ColorMaterial>), Without<OutsideFrustum>>,
|
|
) {
|
|
for (mut sprite, handle) in query.iter_mut() {
|
|
match sprite.resize_mode {
|
|
SpriteResizeMode::Manual => continue,
|
|
SpriteResizeMode::Automatic => {
|
|
let material = materials.get(handle).unwrap();
|
|
if let Some(ref texture_handle) = material.texture {
|
|
if let Some(texture) = textures.get(texture_handle) {
|
|
let texture_size = texture.size.as_vec3().truncate();
|
|
// only set sprite size if it has changed (this check prevents change
|
|
// detection from triggering)
|
|
if sprite.size != texture_size {
|
|
sprite.size = texture_size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|