bevy/crates/bevy_sprite/src/sprite.rs
Aaron Winter b65ec82d46 Frustum Culling (for Sprites) (#1492)
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>
2021-03-24 21:29:53 +00:00

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;
}
}
}
}
}
}
}