Skip to content

Commit

Permalink
Add sprite atlases into the new renderer. (#2560)
Browse files Browse the repository at this point in the history
# Objective
Restore the functionality of sprite atlases in the new renderer.

### **Note:** This PR relies on #2555 

## Solution
Mostly just a copy paste of the existing sprite atlas implementation, however I unified the rendering between sprites and atlases.

Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
StarArawn and cart committed Aug 4, 2021
1 parent ae4f809 commit 115b170
Show file tree
Hide file tree
Showing 11 changed files with 708 additions and 34 deletions.
18 changes: 14 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@ default = [
dynamic = ["bevy_dylib"]

# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend)
render = ["bevy_internal/bevy_pbr", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", "bevy_internal/bevy_ui"]
render = [
"bevy_internal/bevy_pbr",
"bevy_internal/bevy_render",
"bevy_internal/bevy_sprite",
"bevy_internal/bevy_text",
"bevy_internal/bevy_ui",
]

# Optional bevy crates
bevy_audio = ["bevy_internal/bevy_audio"]
Expand Down Expand Up @@ -92,14 +98,14 @@ subpixel_glyph_atlas = ["bevy_internal/subpixel_glyph_atlas"]
bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]

[dependencies]
bevy_dylib = {path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true}
bevy_internal = {path = "crates/bevy_internal", version = "0.5.0", default-features = false}
bevy_dylib = { path = "crates/bevy_dylib", version = "0.5.0", default-features = false, optional = true }
bevy_internal = { path = "crates/bevy_internal", version = "0.5.0", default-features = false }

[dev-dependencies]
anyhow = "1.0.4"
rand = "0.8.0"
ron = "0.6.2"
serde = {version = "1", features = ["derive"]}
serde = { version = "1", features = ["derive"] }
# Needed to poll Task examples
futures-lite = "1.11.3"

Expand Down Expand Up @@ -140,6 +146,10 @@ path = "examples/2d/text2d.rs"
name = "texture_atlas"
path = "examples/2d/texture_atlas.rs"

[[example]]
name = "pipelined_texture_atlas"
path = "examples/2d/pipelined_texture_atlas.rs"

# 3D Rendering
[[example]]
name = "3d_scene"
Expand Down
98 changes: 98 additions & 0 deletions examples/2d/pipelined_texture_atlas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use bevy::{
asset::LoadState,
math::{Vec2, Vec3},
prelude::{
App, AssetServer, Assets, Commands, HandleUntyped, IntoSystem, Res, ResMut, State,
SystemSet, Transform,
},
render2::{camera::OrthographicCameraBundle, texture::Image},
sprite2::{
PipelinedSpriteBundle, PipelinedSpriteSheetBundle, Sprite, TextureAtlas,
TextureAtlasBuilder, TextureAtlasSprite,
},
PipelinedDefaultPlugins,
};

/// In this example we generate a new texture atlas (sprite sheet) from a folder containing
/// individual sprites
fn main() {
App::new()
.init_resource::<RpgSpriteHandles>()
.add_plugins(PipelinedDefaultPlugins)
.add_state(AppState::Setup)
.add_system_set(SystemSet::on_enter(AppState::Setup).with_system(load_textures.system()))
.add_system_set(SystemSet::on_update(AppState::Setup).with_system(check_textures.system()))
.add_system_set(SystemSet::on_enter(AppState::Finished).with_system(setup.system()))
.run();
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum AppState {
Setup,
Finished,
}

#[derive(Default)]
struct RpgSpriteHandles {
handles: Vec<HandleUntyped>,
}

fn load_textures(mut rpg_sprite_handles: ResMut<RpgSpriteHandles>, asset_server: Res<AssetServer>) {
rpg_sprite_handles.handles = asset_server.load_folder("textures/rpg").unwrap();
}

fn check_textures(
mut state: ResMut<State<AppState>>,
rpg_sprite_handles: ResMut<RpgSpriteHandles>,
asset_server: Res<AssetServer>,
) {
if let LoadState::Loaded =
asset_server.get_group_load_state(rpg_sprite_handles.handles.iter().map(|handle| handle.id))
{
state.set(AppState::Finished).unwrap();
}
}

fn setup(
mut commands: Commands,
rpg_sprite_handles: Res<RpgSpriteHandles>,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut textures: ResMut<Assets<Image>>,
) {
let mut texture_atlas_builder = TextureAtlasBuilder::default();
for handle in rpg_sprite_handles.handles.iter() {
let texture = textures.get(handle).unwrap();
texture_atlas_builder.add_texture(handle.clone_weak().typed::<Image>(), texture);
}

let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap();
let texture_atlas_texture = texture_atlas.texture.clone();
let vendor_handle = asset_server.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png");
let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap();
let atlas_handle = texture_atlases.add(texture_atlas);

// set up a scene to display our texture atlas
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
// draw a sprite from the atlas
commands.spawn_bundle(PipelinedSpriteSheetBundle {
transform: Transform {
translation: Vec3::new(150.0, 0.0, 0.0),
scale: Vec3::splat(4.0),
..Default::default()
},
sprite: TextureAtlasSprite::new(vendor_index as u32),
texture_atlas: atlas_handle,
..Default::default()
});
// draw the atlas itself
commands.spawn_bundle(PipelinedSpriteBundle {
sprite: Sprite {
size: Vec2::new(512.0, 512.0),
..Default::default()
},
texture: texture_atlas_texture,
transform: Transform::from_xyz(-300.0, 0.0, 0.0),
..Default::default()
});
}
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Example | File | Description
`contributors` | [`2d/contributors.rs`](./2d/contributors.rs) | Displays each contributor as a bouncy bevy-ball!
`many_sprites` | [`2d/many_sprites.rs`](./2d/many_sprites.rs) | Displays many sprites in a grid arragement! Used for performance testing.
`mesh` | [`2d/mesh.rs`](./2d/mesh.rs) | Renders a custom mesh
`pipelined_texture_atlas` | [`2d/pipelined_texture_atlas.rs`](./2d/pipelined_texture_atlas.rs) | Generates a texture atlas (sprite sheet) from individual sprites
`sprite` | [`2d/sprite.rs`](./2d/sprite.rs) | Renders a sprite
`sprite_sheet` | [`2d/sprite_sheet.rs`](./2d/sprite_sheet.rs) | Renders an animated sprite
`text2d` | [`2d/text2d.rs`](./2d/text2d.rs) | Generates text in 2d
Expand Down
8 changes: 6 additions & 2 deletions pipelined/bevy_sprite2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" }
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
bevy_log = { path = "../../crates/bevy_log", version = "0.5.0" }
bevy_math = { path = "../../crates/bevy_math", version = "0.5.0" }
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = ["bevy"] }
bevy_reflect = { path = "../../crates/bevy_reflect", version = "0.5.0", features = [
"bevy",
] }
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
bevy_transform = { path = "../../crates/bevy_transform", version = "0.5.0" }
bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" }

# other
bytemuck = "1.5"
guillotiere = "0.6.0"
thiserror = "1.0"
rectangle-pack = "0.4"
serde = { version = "1", features = ["derive"] }
bytemuck = "1.5"
29 changes: 28 additions & 1 deletion pipelined/bevy_sprite2/src/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::Sprite;
use crate::{
texture_atlas::{TextureAtlas, TextureAtlasSprite},
Sprite,
};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
use bevy_render2::texture::Image;
Expand All @@ -22,3 +25,27 @@ impl Default for PipelinedSpriteBundle {
}
}
}

/// A Bundle of components for drawing a single sprite from a sprite sheet (also referred
/// to as a `TextureAtlas`)
#[derive(Bundle, Clone)]
pub struct PipelinedSpriteSheetBundle {
/// The specific sprite from the texture atlas to be drawn
pub sprite: TextureAtlasSprite,
/// A handle to the texture atlas that holds the sprite images
pub texture_atlas: Handle<TextureAtlas>,
/// Data pertaining to how the sprite is drawn on the screen
pub transform: Transform,
pub global_transform: GlobalTransform,
}

impl Default for PipelinedSpriteSheetBundle {
fn default() -> Self {
Self {
sprite: Default::default(),
texture_atlas: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
}
}
}
101 changes: 101 additions & 0 deletions pipelined/bevy_sprite2/src/dynamic_texture_atlas_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::{Rect, TextureAtlas};
use bevy_asset::Assets;
use bevy_math::Vec2;
use bevy_render2::texture::{Image, TextureFormatPixelInfo};
use guillotiere::{size2, Allocation, AtlasAllocator};

pub struct DynamicTextureAtlasBuilder {
pub atlas_allocator: AtlasAllocator,
pub padding: i32,
}

impl DynamicTextureAtlasBuilder {
pub fn new(size: Vec2, padding: i32) -> Self {
Self {
atlas_allocator: AtlasAllocator::new(to_size2(size)),
padding,
}
}

pub fn add_texture(
&mut self,
texture_atlas: &mut TextureAtlas,
textures: &mut Assets<Image>,
texture: &Image,
) -> Option<u32> {
let allocation = self.atlas_allocator.allocate(size2(
texture.texture_descriptor.size.width as i32 + self.padding,
texture.texture_descriptor.size.height as i32 + self.padding,
));
if let Some(allocation) = allocation {
let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap();
self.place_texture(atlas_texture, allocation, texture);
let mut rect: Rect = allocation.rectangle.into();
rect.max.x -= self.padding as f32;
rect.max.y -= self.padding as f32;
texture_atlas.add_texture(rect);
Some((texture_atlas.len() - 1) as u32)
} else {
None
}
}

// fn resize(
// &mut self,
// texture_atlas: &mut TextureAtlas,
// textures: &mut Assets<Texture>,
// size: Vec2,
// ) {
// let new_size2 = to_size2(new_size);
// self.atlas_texture = Texture::new_fill(new_size, &[0,0,0,0]);
// let change_list = self.atlas_allocator.resize_and_rearrange(new_size2);

// for change in change_list.changes {
// if let Some(changed_texture_handle) = self.allocation_textures.remove(&change.old.id)
// { let changed_texture = textures.get(&changed_texture_handle).unwrap();
// self.place_texture(change.new, changed_texture_handle, changed_texture);
// }
// }

// for failure in change_list.failures {
// let failed_texture = self.allocation_textures.remove(&failure.id).unwrap();
// queued_textures.push(failed_texture);
// }
// }

fn place_texture(
&mut self,
atlas_texture: &mut Image,
allocation: Allocation,
texture: &Image,
) {
let mut rect = allocation.rectangle;
rect.max.x -= self.padding;
rect.max.y -= self.padding;
let atlas_width = atlas_texture.texture_descriptor.size.width as usize;
let rect_width = rect.width() as usize;
let format_size = atlas_texture.texture_descriptor.format.pixel_size();

for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() {
let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size;
let end = begin + rect_width * format_size;
let texture_begin = texture_y * rect_width * format_size;
let texture_end = texture_begin + rect_width * format_size;
atlas_texture.data[begin..end]
.copy_from_slice(&texture.data[texture_begin..texture_end]);
}
}
}

impl From<guillotiere::Rectangle> for Rect {
fn from(rectangle: guillotiere::Rectangle) -> Self {
Rect {
min: Vec2::new(rectangle.min.x as f32, rectangle.min.y as f32),
max: Vec2::new(rectangle.max.x as f32, rectangle.max.y as f32),
}
}
}

fn to_size2(vec2: Vec2) -> guillotiere::Size {
guillotiere::Size::new(vec2.x as i32, vec2.y as i32)
}
11 changes: 10 additions & 1 deletion pipelined/bevy_sprite2/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
mod bundle;
mod dynamic_texture_atlas_builder;
mod rect;
mod render;
mod sprite;
mod texture_atlas;
mod texture_atlas_builder;

use bevy_asset::AddAsset;
pub use bundle::*;
pub use dynamic_texture_atlas_builder::*;
pub use rect::*;
pub use render::*;
pub use sprite::*;
pub use texture_atlas::*;
pub use texture_atlas_builder::*;

use bevy_app::prelude::*;
use bevy_render2::{render_graph::RenderGraph, render_phase::DrawFunctions, RenderStage};
Expand All @@ -16,9 +23,11 @@ pub struct SpritePlugin;

impl Plugin for SpritePlugin {
fn build(&self, app: &mut App) {
app.register_type::<Sprite>();
app.add_asset::<TextureAtlas>().register_type::<Sprite>();
let render_app = app.sub_app_mut(0);
render_app
.init_resource::<ExtractedSprites>()
.add_system_to_stage(RenderStage::Extract, render::extract_atlases)
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
.add_system_to_stage(RenderStage::Queue, queue_sprites)
Expand Down
4 changes: 4 additions & 0 deletions pipelined/bevy_sprite2/src/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ impl Rect {
pub fn height(&self) -> f32 {
self.max.y - self.min.y
}

pub fn size(&self) -> Vec2 {
Vec2::new(self.width(), self.height())
}
}
Loading

0 comments on commit 115b170

Please sign in to comment.