Skip to content

Commit

Permalink
add texture atlases
Browse files Browse the repository at this point in the history
  • Loading branch information
cart committed Jun 6, 2020
1 parent ffc4246 commit 2705e5c
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 57 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ path = "examples/2d/sprite.rs"
name = "sprite_sheet"
path = "examples/2d/sprite_sheet.rs"

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

[[example]]
name = "load_model"
path = "examples/3d/load_model.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_gltf/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
}
}

// TODO: this should return a scene
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
let gltf = gltf::Gltf::from_slice(&bytes)?;
let buffer_data = load_buffers(gltf.buffers(), asset_path)?;
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_render/src/camera/projection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl Default for PerspectiveProjection {
}
}

// TODO: make this a component instead of a property
#[derive(Debug, Clone, Property, Serialize, Deserialize)]
pub enum WindowOrigin {
Center,
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ impl AppPlugin for RenderPlugin {
.init_resource::<EntityRenderResourceAssignments>()
.init_resource::<EntitiesWaitingForAssets>()
.init_resource::<TextureResourceSystemState>()
.add_system(entity_render_resource_assignments_system())
.add_system_to_stage(
bevy_app::stage::POST_UPDATE,
entity_render_resource_assignments_system(),
)
.init_system_to_stage(
bevy_app::stage::POST_UPDATE,
camera::camera_system::<OrthographicProjection>,
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_sprite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ bevy_type_registry = { path = "../bevy_type_registry" }
bevy_derive = { path = "../bevy_derive" }
bevy_render = { path = "../bevy_render" }
bevy_transform = { path = "../bevy_transform" }

legion = { path = "../bevy_legion", features = ["serialize"] }
glam = "0.8.7"
legion = { path = "../bevy_legion", features = ["serialize"] }
guillotiere = "0.5.2"
10 changes: 5 additions & 5 deletions crates/bevy_sprite/src/entity.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
render::SPRITE_PIPELINE_HANDLE, sprite::Sprite, ColorMaterial, Quad, SpriteSheet,
SpriteSheetSprite, QUAD_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE,
render::SPRITE_PIPELINE_HANDLE, sprite::Sprite, ColorMaterial, Quad, TextureAtlas,
TextureAtlasSprite, QUAD_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE,
};
use bevy_asset::Handle;
use bevy_derive::EntityArchetype;
Expand Down Expand Up @@ -32,8 +32,8 @@ impl Default for SpriteEntity {

#[derive(EntityArchetype)]
pub struct SpriteSheetEntity {
pub sprite: SpriteSheetSprite,
pub sprite_sheet: Handle<SpriteSheet>,
pub sprite: TextureAtlasSprite,
pub texture_atlas: Handle<TextureAtlas>,
pub renderable: Renderable,
pub mesh: Handle<Mesh>, // TODO: maybe abstract this out
// pub local_to_world: LocalToWorld,
Expand All @@ -46,7 +46,7 @@ impl Default for SpriteSheetEntity {
fn default() -> Self {
Self {
sprite: Default::default(),
sprite_sheet: Default::default(),
texture_atlas: Default::default(),
renderable: Renderable {
pipelines: vec![SPRITE_SHEET_PIPELINE_HANDLE],
..Default::default()
Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ mod quad;
mod rect;
mod render;
mod sprite;
mod sprite_sheet;
mod texture_atlas;
mod texture_atlas_builder;

pub use color_material::*;
pub use quad::*;
pub use rect::*;
pub use render::*;
pub use sprite::*;
pub use sprite_sheet::*;
pub use texture_atlas::*;
pub use texture_atlas_builder::*;

use bevy_app::{stage, AppBuilder, AppPlugin};
use bevy_asset::{AddAsset, Assets, Handle};
Expand All @@ -32,7 +34,7 @@ pub const QUAD_HANDLE: Handle<Mesh> = Handle::from_u128(142404619811301375266013
impl AppPlugin for SpritePlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_asset::<ColorMaterial>()
.add_asset::<SpriteSheet>()
.add_asset::<TextureAtlas>()
.add_system_to_stage(stage::POST_UPDATE, sprite_system())
.add_system_to_stage(
stage::POST_UPDATE,
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_sprite/src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{ColorMaterial, Quad, SpriteSheet, SpriteSheetSprite};
use crate::{ColorMaterial, Quad, TextureAtlas, TextureAtlasSprite};
use bevy_asset::{Assets, Handle};
use bevy_render::{
base_render_graph,
Expand Down Expand Up @@ -135,12 +135,12 @@ impl SpriteRenderGraphBuilder for RenderGraph {

self.add_system_node(
node::SPRITE_SHEET,
AssetUniformNode::<SpriteSheet>::new(false),
AssetUniformNode::<TextureAtlas>::new(false),
);

self.add_system_node(
node::SPRITE_SHEET_SPRITE,
UniformNode::<SpriteSheetSprite>::new(true),
UniformNode::<TextureAtlasSprite>::new(true),
);

let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_sprite/src/render/sprite_sheet.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ layout(location = 0) in vec2 v_Uv;

layout(location = 0) out vec4 o_Target;

layout(set = 1, binding = 2) uniform texture2D SpriteSheet_texture;
layout(set = 1, binding = 3) uniform sampler SpriteSheet_texture_sampler;
layout(set = 1, binding = 2) uniform texture2D TextureAtlas_texture;
layout(set = 1, binding = 3) uniform sampler TextureAtlas_texture_sampler;

void main() {
o_Target = texture(
sampler2D(SpriteSheet_texture, SpriteSheet_texture_sampler),
sampler2D(TextureAtlas_texture, TextureAtlas_texture_sampler),
v_Uv);
}
23 changes: 9 additions & 14 deletions crates/bevy_sprite/src/render/sprite_sheet.vert
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@ layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Normal;
layout(location = 2) in vec2 Vertex_Uv;

// TODO: uncomment when instancing is implemented
// sprite
// layout(location = 0) in vec3 Sprite_Position;
// layout(location = 1) in int Sprite_Index;

layout(location = 0) out vec2 v_Uv;

layout(set = 0, binding = 0) uniform Camera2d {
mat4 ViewProj;
};

// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction
layout(set = 1, binding = 0) uniform SpriteSheet_dimensions {
layout(set = 1, binding = 0) uniform TextureAtlas_dimensions {
vec2 Dimensions;
};

Expand All @@ -25,21 +20,21 @@ struct Rect {
vec2 end;
};

layout(set = 1, binding = 1) buffer SpriteSheet_sprites {
Rect[] Sprites;
layout(set = 1, binding = 1) buffer TextureAtlas_textures {
Rect[] Textures;
};


layout(set = 2, binding = 0) uniform SpriteSheetSprite {
vec3 SpriteSheetSprite_position;
float SpriteSheetSprite_scale;
uint SpriteSheetSprite_index;
layout(set = 2, binding = 0) uniform TextureAtlasSprite {
vec3 TextureAtlasSprite_position;
float TextureAtlasSprite_scale;
uint TextureAtlasSprite_index;
};

void main() {
Rect sprite_rect = Sprites[SpriteSheetSprite_index];
Rect sprite_rect = Textures[TextureAtlasSprite_index];
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * SpriteSheetSprite_scale, 0.0) + SpriteSheetSprite_position;
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * TextureAtlasSprite_scale, 0.0) + TextureAtlasSprite_position;
vec2 uvs[4] = vec2[](
vec2(sprite_rect.begin.x, sprite_rect.end.y),
sprite_rect.begin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,60 @@ use bevy_asset::Handle;
use bevy_derive::{Bytes, Uniform, Uniforms};
use bevy_render::texture::Texture;
use glam::{Vec2, Vec3};
use std::collections::HashMap;

#[derive(Uniforms)]
pub struct SpriteSheet {
pub struct TextureAtlas {
pub texture: Handle<Texture>,
// TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer
pub dimensions: Vec2,
#[uniform(buffer)]
pub sprites: Vec<Rect>,
pub textures: Vec<Rect>,
#[uniform(ignore)]
pub texture_handles: Option<HashMap<Handle<Texture>, usize>>,
}

// NOTE: cannot do `unsafe impl Byteable` here because Vec3 takes up the space of a Vec4. If/when glam changes this we can swap out
// Bytes for Byteable as a micro-optimization. https://github.com/bitshifter/glam-rs/issues/36
#[derive(Uniform, Bytes, Default)]
pub struct SpriteSheetSprite {
pub struct TextureAtlasSprite {
pub position: Vec3,
pub scale: f32,
pub index: u32,
}

impl SpriteSheet {
impl TextureAtlas {
pub fn from_grid(
texture: Handle<Texture>,
size: Vec2,
columns: usize,
rows: usize,
) -> SpriteSheet {
let sprite_width = size.x() / columns as f32;
let sprite_height = size.y() / rows as f32;
) -> TextureAtlas {
let texture_width = size.x() / columns as f32;
let texture_height = size.y() / rows as f32;
let mut sprites = Vec::new();
for y in 0..rows {
for x in 0..columns {
sprites.push(Rect {
min: Vec2::new(x as f32 * sprite_width, y as f32 * sprite_height),
min: Vec2::new(x as f32 * texture_width, y as f32 * texture_height),
max: Vec2::new(
(x + 1) as f32 * sprite_width,
(y + 1) as f32 * sprite_height,
(x + 1) as f32 * texture_width,
(y + 1) as f32 * texture_height,
),
})
}
}
SpriteSheet {
TextureAtlas {
dimensions: size,
sprites,
textures: sprites,
texture,
texture_handles: None,
}
}

pub fn get_texture_index(&self, texture: Handle<Texture>) -> Option<usize> {
self.texture_handles
.as_ref()
.and_then(|texture_handles| texture_handles.get(&texture).cloned())
}
}
88 changes: 88 additions & 0 deletions crates/bevy_sprite/src/texture_atlas_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::{TextureAtlas, Rect};
use bevy_asset::{Assets, Handle};
use bevy_render::texture::Texture;
use glam::Vec2;
use guillotiere::{size2, Allocation, AtlasAllocator};
use std::collections::HashMap;

pub struct TextureAtlasBuilder {
pub texture_allocations: Vec<(Handle<Texture>, Allocation)>,
pub atlas_allocator: AtlasAllocator,
pub texture: Texture,
}

impl Default for TextureAtlasBuilder {
fn default() -> Self {
Self::new(Vec2::new(256., 256.))
}
}

const FORMAT_SIZE: usize = 4; // TODO: get this from an actual format type
impl TextureAtlasBuilder {
pub fn new(initial_size: Vec2) -> Self {
let width = initial_size.x() as usize;
let height = initial_size.y() as usize;
Self {
texture_allocations: Default::default(),
atlas_allocator: AtlasAllocator::new(size2(width as i32, height as i32)),
texture: Texture::new(vec![0; width * height * FORMAT_SIZE], initial_size),
}
}

pub fn add_texture(&mut self, texture_handle: Handle<Texture>, texture: &Texture) {
// TODO: resize if allocation fails
let allocation = self
.atlas_allocator
.allocate(size2(texture.size.x() as i32, texture.size.y() as i32))
.unwrap();
let rect = allocation.rectangle;
let atlas_width = self.texture.size.x() as usize;
let rect_width = rect.width() as usize;

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;
self.texture.data[begin..end]
.copy_from_slice(&texture.data[texture_begin..texture_end]);
}
self.texture_allocations.push((texture_handle, allocation));
}

pub fn remove_texture(&mut self, texture_handle: Handle<Texture>) {
if let Some(position) = self.texture_allocations.iter().position(|(handle, _)| *handle == texture_handle) {
let (_, allocation) = self.texture_allocations.remove(position);
self.atlas_allocator.deallocate(allocation.id);
}
}

pub fn finish(self, textures: &mut Assets<Texture>) -> TextureAtlas {
let mut texture_rects = Vec::with_capacity(self.texture_allocations.len());
let mut texture_handles = HashMap::with_capacity(self.texture_allocations.len());
for (index, (handle, allocation)) in self.texture_allocations.iter().enumerate() {
texture_rects.push(allocation.rectangle.into());
texture_handles.insert(*handle, index);
}
TextureAtlas {
dimensions: to_vec2(self.atlas_allocator.size()),
texture: textures.add(self.texture),
textures: texture_rects,
texture_handles: Some(texture_handles),
}
}
}

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_vec2(size: guillotiere::Size) -> Vec2 {
Vec2::new(size.width as f32, size.height as f32)
}

Loading

0 comments on commit 2705e5c

Please sign in to comment.