sprite sheets are fully operational

This commit is contained in:
Carter Anderson 2020-06-03 19:00:19 -07:00
parent 8c196139d4
commit 5927bad382
22 changed files with 193 additions and 200 deletions

19
.vscode/launch.json vendored
View file

@ -1416,6 +1416,25 @@
"args": [], "args": [],
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
}, },
{
"type": "lldb",
"request": "launch",
"name": "Debug example 'sprite_sheet'",
"env": { "CARGO_MANIFEST_DIR": "${workspaceFolder}" },
"cargo": {
"args": [
"build",
"--example=sprite_sheet",
"--package=bevy"
],
"filter": {
"name": "sprite_sheet",
"kind": "example"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",

View file

@ -9,4 +9,8 @@
## Inspiration ## Inspiration
* amethyst * amethyst
* coffee * coffee
## Assets
* Generic RPG Pack (CC0 license) by [Bakudas](https://twitter.com/bakudas) and [Gabe Fern](https://twitter.com/_Gabrielfer)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,11 +1,10 @@
use crate::modules::{get_modules, get_path};
use darling::FromMeta; use darling::FromMeta;
use inflector::Inflector; use inflector::Inflector;
use crate::modules::{get_modules, get_path};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Field, Fields, Path}; use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Field, Fields, Path};
// TODO: ensure shader_def and instance/vertex are mutually exclusive
#[derive(FromMeta, Debug, Default)] #[derive(FromMeta, Debug, Default)]
struct UniformAttributeArgs { struct UniformAttributeArgs {
#[darling(default)] #[darling(default)]
@ -88,6 +87,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
let mut texture_and_sampler_name_strings = Vec::new(); let mut texture_and_sampler_name_strings = Vec::new();
let mut texture_and_sampler_name_idents = Vec::new(); let mut texture_and_sampler_name_idents = Vec::new();
let mut field_infos = Vec::new(); let mut field_infos = Vec::new();
let mut get_field_bind_types = Vec::new();
let mut vertex_buffer_field_names_pascal = Vec::new(); let mut vertex_buffer_field_names_pascal = Vec::new();
let mut vertex_buffer_field_types = Vec::new(); let mut vertex_buffer_field_types = Vec::new();
@ -98,6 +98,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
for (f, attrs) in field_attributes.iter() { for (f, attrs) in field_attributes.iter() {
let field_name = f.ident.as_ref().unwrap().to_string(); let field_name = f.ident.as_ref().unwrap().to_string();
if !attrs.ignore { if !attrs.ignore {
let active_uniform_field_name = &f.ident;
active_uniform_field_names.push(&f.ident); active_uniform_field_names.push(&f.ident);
active_uniform_field_name_strings.push(field_name.clone()); active_uniform_field_name_strings.push(field_name.clone());
let uniform = format!("{}_{}", struct_name, field_name); let uniform = format!("{}_{}", struct_name, field_name);
@ -117,6 +118,19 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
is_instanceable: #is_instanceable, is_instanceable: #is_instanceable,
})); }));
if attrs.buffer {
get_field_bind_types.push(quote!({
let bind_type = self.#active_uniform_field_name.get_bind_type();
let size = if let Some(#bevy_render_path::shader::FieldBindType::Uniform { size }) = bind_type {
size
} else {
panic!("Uniform field was labeled as a 'buffer', but it does not have a compatible type.")
};
Some(#bevy_render_path::shader::FieldBindType::Buffer { size })
}))
} else {
get_field_bind_types.push(quote!(self.#active_uniform_field_name.get_bind_type()))
}
} }
if attrs.shader_def { if attrs.shader_def {
@ -192,7 +206,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
fn get_field_bind_type(&self, name: &str) -> Option<#bevy_render_path::shader::FieldBindType> { fn get_field_bind_type(&self, name: &str) -> Option<#bevy_render_path::shader::FieldBindType> {
use #bevy_render_path::shader::GetFieldBindType; use #bevy_render_path::shader::GetFieldBindType;
match name { match name {
#(#active_uniform_field_name_strings => self.#active_uniform_field_names.get_bind_type(),)* #(#active_uniform_field_name_strings => #get_field_bind_types,)*
_ => None, _ => None,
} }
} }
@ -314,4 +328,4 @@ pub fn derive_uniform(input: TokenStream) -> TokenStream {
} }
} }
}) })
} }

View file

@ -15,7 +15,7 @@ pub enum BindType {
dynamic: bool, dynamic: bool,
properties: Vec<UniformProperty>, properties: Vec<UniformProperty>,
}, },
Buffer { StorageBuffer {
dynamic: bool, dynamic: bool,
readonly: bool, readonly: bool,
}, },

View file

@ -54,7 +54,7 @@ impl Node for TextureCopyNode {
render_context.copy_buffer_to_texture( render_context.copy_buffer_to_texture(
texture_buffer, texture_buffer,
0, 0,
(4 * texture.width) as u32, 4 * texture.size.x() as u32,
texture_resource, texture_resource,
[0, 0, 0], [0, 0, 0],
0, 0,

View file

@ -83,7 +83,7 @@ where
fn increment_uniform_counts(&mut self, uniforms: &T) { fn increment_uniform_counts(&mut self, uniforms: &T) {
for (i, field_info) in T::get_field_infos().iter().enumerate() { for (i, field_info) in T::get_field_infos().iter().enumerate() {
if let Some(FieldBindType::Uniform { size }) = if let Some(FieldBindType::Uniform { size }) | Some(FieldBindType::Buffer { size }) =
uniforms.get_field_bind_type(&field_info.name) uniforms.get_field_bind_type(&field_info.name)
{ {
if let Some((ref _name, ref mut buffer_array_status)) = self.uniform_arrays[i] { if let Some((ref _name, ref mut buffer_array_status)) = self.uniform_arrays[i] {
@ -186,8 +186,9 @@ where
for (i, field_info) in T::get_field_infos().iter().enumerate() { for (i, field_info) in T::get_field_infos().iter().enumerate() {
let bind_type = uniforms.get_field_bind_type(&field_info.name); let bind_type = uniforms.get_field_bind_type(&field_info.name);
match bind_type { match bind_type {
Some(FieldBindType::Uniform { size }) => { Some(FieldBindType::Uniform { size }) | Some(FieldBindType::Buffer { size }) => {
let (_name, uniform_buffer_status) = self.uniform_arrays[i].as_mut().unwrap(); let (_name, uniform_buffer_status) = self.uniform_arrays[i].as_mut().unwrap();
let range = 0..size as u64;
let (target_buffer, target_offset) = if dynamic_uniforms { let (target_buffer, target_offset) = if dynamic_uniforms {
let buffer = uniform_buffer_status.buffer.unwrap(); let buffer = uniform_buffer_status.buffer.unwrap();
let index = uniform_buffer_status let index = uniform_buffer_status
@ -199,31 +200,38 @@ where
dynamic_index: Some( dynamic_index: Some(
(index * uniform_buffer_status.aligned_size) as u32, (index * uniform_buffer_status.aligned_size) as u32,
), ),
range: 0..size as u64, range,
}, },
); );
(buffer, index * uniform_buffer_status.aligned_size) (buffer, index * uniform_buffer_status.aligned_size)
} else { } else {
let resource = let resource = match render_resource_assignments
match render_resource_assignments.get(field_info.uniform_name) { .get(field_info.uniform_name)
Some(assignment) => assignment.get_resource(), {
None => { Some(assignment) => assignment.get_resource(),
let resource = render_resources.create_buffer(BufferInfo { None => {
size, let usage = if let Some(FieldBindType::Buffer { .. }) = bind_type {
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, BufferUsage::STORAGE
..Default::default() } else {
}); BufferUsage::UNIFORM
render_resource_assignments.set( };
&field_info.uniform_name, let resource = render_resources.create_buffer(BufferInfo {
RenderResourceAssignment::Buffer { size,
resource, buffer_usage: BufferUsage::COPY_DST | usage,
range: 0..size as u64, ..Default::default()
dynamic_index: None, });
},
); render_resource_assignments.set(
resource &field_info.uniform_name,
} RenderResourceAssignment::Buffer {
}; resource,
range,
dynamic_index: None,
},
);
resource
}
};
(resource, 0) (resource, 0)
}; };
@ -232,14 +240,16 @@ where
+ (uniform_buffer_status.queued_buffer_writes.len() + (uniform_buffer_status.queued_buffer_writes.len()
* uniform_buffer_status.item_size); * uniform_buffer_status.item_size);
let uniform_byte_len = uniforms.uniform_byte_len(&field_info.uniform_name); let uniform_byte_len = uniforms.uniform_byte_len(&field_info.uniform_name);
if uniform_byte_len > 0 if uniform_byte_len > 0 {
{
if size != uniform_byte_len { if size != uniform_byte_len {
panic!("The number of bytes produced for {} do not match the expected count. Actual: {}. Expected: {}.", field_info.uniform_name, uniform_byte_len, size); panic!("The number of bytes produced for {} do not match the expected count. Actual: {}. Expected: {}.", field_info.uniform_name, uniform_byte_len, size);
} }
uniforms.write_uniform_bytes(&field_info.uniform_name, &mut staging_buffer uniforms.write_uniform_bytes(
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)]); &field_info.uniform_name,
&mut staging_buffer
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)],
);
} else { } else {
panic!( panic!(
"failed to get data from uniform: {}", "failed to get data from uniform: {}",
@ -254,7 +264,8 @@ where
offset: target_offset, offset: target_offset,
}); });
} }
_ => {} Some(FieldBindType::Texture) => { /* ignore textures */ }
None => { /* ignore None */ }
} }
} }
} }
@ -680,7 +691,7 @@ fn setup_uniform_texture_resources<T>(
entities_waiting_for_assets: &EntitiesWaitingForAssets, entities_waiting_for_assets: &EntitiesWaitingForAssets,
render_resource_assignments: &mut RenderResourceAssignments, render_resource_assignments: &mut RenderResourceAssignments,
) where ) where
T: AsUniforms, T: AsUniforms,
{ {
for field_info in T::get_field_infos().iter() { for field_info in T::get_field_infos().iter() {
let bind_type = uniforms.get_field_bind_type(&field_info.name); let bind_type = uniforms.get_field_bind_type(&field_info.name);

View file

@ -1,6 +1,7 @@
bitflags::bitflags! { bitflags::bitflags! {
#[repr(transparent)] #[repr(transparent)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "trace", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct BufferUsage: u32 { pub struct BufferUsage: u32 {
const MAP_READ = 1; const MAP_READ = 1;
const MAP_WRITE = 2; const MAP_WRITE = 2;
@ -11,17 +12,5 @@ bitflags::bitflags! {
const UNIFORM = 64; const UNIFORM = 64;
const STORAGE = 128; const STORAGE = 128;
const INDIRECT = 256; const INDIRECT = 256;
const STORAGE_READ = 512;
const NONE = 0;
/// The combination of all read-only usages.
const READ_ALL = Self::MAP_READ.bits | Self::COPY_SRC.bits |
Self::INDEX.bits | Self::VERTEX.bits | Self::UNIFORM.bits |
Self::STORAGE_READ.bits | Self::INDIRECT.bits;
/// The combination of all write-only and read-write usages.
const WRITE_ALL = Self::MAP_WRITE.bits | Self::COPY_DST.bits | Self::STORAGE.bits;
/// The combination of all usages that the are guaranteed to be be ordered by the hardware.
/// If a usage is not ordered, then even if it doesn't change between draw calls, there
/// still need to be pipeline barriers inserted for synchronization.
const ORDERED = Self::READ_ALL.bits;
} }
} }

View file

@ -10,7 +10,7 @@ impl Default for BufferInfo {
fn default() -> Self { fn default() -> Self {
BufferInfo { BufferInfo {
size: 0, size: 0,
buffer_usage: BufferUsage::NONE, buffer_usage: BufferUsage::empty(),
} }
} }
} }

View file

@ -198,8 +198,8 @@ fn reflect_binding(binding: &ReflectDescriptorBinding) -> BindingDescriptor {
}, },
), ),
ReflectDescriptorType::StorageBuffer => ( ReflectDescriptorType::StorageBuffer => (
&binding.name, &type_description.type_name,
BindType::Buffer { BindType::StorageBuffer {
dynamic: false, dynamic: false,
readonly: true, readonly: true,
}, },

View file

@ -70,8 +70,9 @@ impl ShaderDefSuffixProvider for bool {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum FieldBindType { pub enum FieldBindType {
// TODO: maybe change this to Buffer
Uniform { size: usize }, Uniform { size: usize },
Buffer, Buffer { size: usize },
Texture, Texture,
} }

View file

@ -2,6 +2,7 @@ use super::Texture;
use anyhow::Result; use anyhow::Result;
use bevy_asset::AssetLoader; use bevy_asset::AssetLoader;
use std::path::Path; use std::path::Path;
use glam::Vec2;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct PngTextureLoader; pub struct PngTextureLoader;
@ -14,8 +15,7 @@ impl AssetLoader<Texture> for PngTextureLoader {
reader.next_frame(&mut data)?; reader.next_frame(&mut data)?;
Ok(Texture { Ok(Texture {
data, data,
width: info.width as usize, size: Vec2::new(info.width as f32, info.height as f32),
height: info.height as usize,
}) })
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {

View file

@ -8,6 +8,7 @@ use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_derive::FromResources; use bevy_derive::FromResources;
use legion::prelude::*; use legion::prelude::*;
use std::{collections::HashSet, fs::File}; use std::{collections::HashSet, fs::File};
use glam::Vec2;
pub const TEXTURE_ASSET_INDEX: usize = 0; pub const TEXTURE_ASSET_INDEX: usize = 0;
pub const SAMPLER_ASSET_INDEX: usize = 1; pub const SAMPLER_ASSET_INDEX: usize = 1;
@ -18,13 +19,12 @@ pub enum TextureType {
pub struct Texture { pub struct Texture {
pub data: Vec<u8>, pub data: Vec<u8>,
pub width: usize, pub size: Vec2,
pub height: usize,
} }
impl Texture { impl Texture {
pub fn aspect(&self) -> f32 { pub fn aspect(&self) -> f32 {
self.height as f32 / self.width as f32 self.size.y() / self.size.x()
} }
pub fn load(descriptor: TextureType) -> Self { pub fn load(descriptor: TextureType) -> Self {
@ -41,8 +41,7 @@ impl Texture {
Texture { Texture {
data, data,
width, size: Vec2::new(width as f32, height as f32)
height,
} }
} }

View file

@ -14,8 +14,8 @@ impl From<&Texture> for TextureDescriptor {
fn from(texture: &Texture) -> Self { fn from(texture: &Texture) -> Self {
TextureDescriptor { TextureDescriptor {
size: Extent3d { size: Extent3d {
height: texture.height as u32, width: texture.size.x() as u32,
width: texture.width as u32, height: texture.size.y() as u32,
depth: 1, depth: 1,
}, },
mip_level_count: 1, mip_level_count: 1,

View file

@ -37,10 +37,6 @@ impl AppPlugin for SpritePlugin {
.add_system_to_stage( .add_system_to_stage(
stage::POST_UPDATE, stage::POST_UPDATE,
asset_shader_def_system::<ColorMaterial>.system(), asset_shader_def_system::<ColorMaterial>.system(),
)
.init_system_to_stage(
bevy_render::stage::RENDER_RESOURCE,
sprite_sheet_resource_provider_system,
); );
let resources = app.resources(); let resources = app.resources();

View file

@ -135,7 +135,7 @@ impl SpriteRenderGraphBuilder for RenderGraph {
self.add_system_node( self.add_system_node(
node::SPRITE_SHEET, node::SPRITE_SHEET,
AssetUniformNode::<SpriteSheet>::new(true), AssetUniformNode::<SpriteSheet>::new(false),
); );
self.add_system_node( self.add_system_node(

View file

@ -4,11 +4,11 @@ layout(location = 0) in vec2 v_Uv;
layout(location = 0) out vec4 o_Target; layout(location = 0) out vec4 o_Target;
layout(set = 1, binding = 1) uniform texture2D SpriteSheet_texture; layout(set = 1, binding = 2) uniform texture2D SpriteSheet_texture;
layout(set = 1, binding = 2) uniform sampler SpriteSheet_texture_sampler; layout(set = 1, binding = 3) uniform sampler SpriteSheet_texture_sampler;
void main() { void main() {
o_Target = texture( o_Target = texture(
sampler2D(SpriteSheet_texture, SpriteSheet_texture_sampler), sampler2D(SpriteSheet_texture, SpriteSheet_texture_sampler),
v_Uv);; v_Uv);
} }

View file

@ -4,18 +4,9 @@ layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Normal; layout(location = 1) in vec3 Vertex_Normal;
layout(location = 2) in vec2 Vertex_Uv; layout(location = 2) in vec2 Vertex_Uv;
// TODO: consider swapping explicit mesh binding for this const
// const vec2 positions[4] = vec2[](
// vec2(0.5, -0.5),
// vec2(-0.5, -0.5),
// vec2(0.5, 0.5),
// vec2(-0.5, 0.5)
// );
// TODO: uncomment when instancing is implemented // TODO: uncomment when instancing is implemented
// sprite // sprite
// layout(location = 0) in vec3 Sprite_Position; // layout(location = 0) in vec3 Sprite_Position;
// // this is a vec2 instead of an int due to WebGPU limitations
// layout(location = 1) in int Sprite_Index; // layout(location = 1) in int Sprite_Index;
layout(location = 0) out vec2 v_Uv; layout(location = 0) out vec2 v_Uv;
@ -24,30 +15,37 @@ layout(set = 0, binding = 0) uniform Camera2d {
mat4 ViewProj; 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 {
vec2 Dimensions;
};
struct Rect { struct Rect {
vec2 begin; vec2 begin;
vec2 end; vec2 end;
}; };
layout(set = 1, binding = 0) buffer SpriteSheet_sprites { layout(set = 1, binding = 1) buffer SpriteSheet_sprites {
Rect[] Sprites; Rect[] Sprites;
}; };
layout(set = 2, binding = 0) uniform SpriteSheetSprite { layout(set = 2, binding = 0) uniform SpriteSheetSprite {
vec3 SpriteSheetSprite_position; vec3 SpriteSheetSprite_position;
float SpriteSheetSprite_scale;
uint SpriteSheetSprite_index; uint SpriteSheetSprite_index;
}; };
void main() { void main() {
Rect sprite_rect = Sprites[SpriteSheetSprite_index]; Rect sprite_rect = Sprites[SpriteSheetSprite_index];
vec2 dimensions = sprite_rect.end - sprite_rect.begin; vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
vec2 vertex_position = Vertex_Position.xy * dimensions; vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * SpriteSheetSprite_scale, 0.0) + SpriteSheetSprite_position;
vec2 uvs[4] = vec2[]( vec2 uvs[4] = vec2[](
vec2(sprite_rect.end.x, sprite_rect.begin.y), vec2(sprite_rect.begin.x, sprite_rect.end.y),
sprite_rect.begin, sprite_rect.begin,
sprite_rect.end, vec2(sprite_rect.end.x, sprite_rect.begin.y),
vec2(sprite_rect.begin.x, sprite_rect.end.y) sprite_rect.end
); );
v_Uv = uvs[gl_VertexIndex]; v_Uv = uvs[gl_VertexIndex] / Dimensions;
gl_Position = ViewProj * vec4(vec3(vertex_position, 0.0) + SpriteSheetSprite_position, 1.0); gl_Position = ViewProj * vec4(vertex_position, 1.0);
} }

View file

@ -27,7 +27,7 @@ pub fn sprite_system() -> Box<dyn Schedulable> {
if let Some(texture_handle) = material.texture { if let Some(texture_handle) = material.texture {
if let Some(texture) = textures.get(&texture_handle) { if let Some(texture) = textures.get(&texture_handle) {
let aspect = texture.aspect(); let aspect = texture.aspect();
*rect.size.x_mut() = texture.width as f32 * sprite.scale; *rect.size.x_mut() = texture.size.x() * sprite.scale;
*rect.size.y_mut() = rect.size.x() * aspect; *rect.size.y_mut() = rect.size.x() * aspect;
} }
} }

View file

@ -1,113 +1,52 @@
use crate::Rect; use crate::Rect;
use bevy_app::{Events, GetEventReader}; use bevy_asset::Handle;
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_core::bytes::AsBytes;
use bevy_derive::{Bytes, Uniform, Uniforms}; use bevy_derive::{Bytes, Uniform, Uniforms};
use bevy_render::{ use bevy_render::texture::Texture;
render_resource::{BufferInfo, BufferUsage, RenderResourceAssignment, ResourceInfo}, use glam::{Vec2, Vec3};
renderer::{RenderResourceContext, RenderResources},
texture::Texture,
Renderable,
};
use glam::{Vec3, Vec4};
use legion::prelude::*;
use std::collections::HashSet;
#[derive(Uniforms)] #[derive(Uniforms)]
pub struct SpriteSheet { pub struct SpriteSheet {
pub texture: Handle<Texture>, 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 sprites: Vec<Rect>,
} }
// 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 // 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. https://github.com/bitshifter/glam-rs/issues/36 // Bytes for Byteable as a micro-optimization. https://github.com/bitshifter/glam-rs/issues/36
#[derive(Uniform, Bytes, Default)] #[derive(Uniform, Bytes, Default)]
pub struct SpriteSheetSprite { pub struct SpriteSheetSprite {
pub position: Vec3, pub position: Vec3,
pub scale: f32,
pub index: u32, pub index: u32,
} }
pub const SPRITE_SHEET_BUFFER_ASSET_INDEX: usize = 0; impl SpriteSheet {
pub fn from_grid(
fn remove_sprite_sheet_resource( texture: Handle<Texture>,
render_resources: &dyn RenderResourceContext, size: Vec2,
handle: Handle<SpriteSheet>, columns: usize,
) { rows: usize,
if let Some(resource) = ) -> SpriteSheet {
render_resources.get_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX) let sprite_width = size.x() / columns as f32;
{ let sprite_height = size.y() / rows as f32;
render_resources.remove_buffer(resource); let mut sprites = Vec::new();
render_resources.remove_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX); 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),
max: Vec2::new(
(x + 1) as f32 * sprite_width,
(y + 1) as f32 * sprite_height,
),
})
}
}
SpriteSheet {
dimensions: size,
sprites,
texture,
}
} }
} }
pub fn sprite_sheet_resource_provider_system(resources: &mut Resources) -> Box<dyn Schedulable> {
let mut sprite_sheet_event_reader = resources.get_event_reader::<AssetEvent<SpriteSheet>>();
(move |world: &mut SubWorld,
render_resources: Res<RenderResources>,
sprite_sheets: Res<Assets<SpriteSheet>>,
sprite_sheet_events: Res<Events<AssetEvent<SpriteSheet>>>,
query: &mut Query<(Read<Handle<SpriteSheet>>, Write<Renderable>)>| {
let render_resources = &*render_resources.context;
let mut changed_sprite_sheets = HashSet::new();
for event in sprite_sheet_event_reader.iter(&sprite_sheet_events) {
match event {
AssetEvent::Created { handle } => {
changed_sprite_sheets.insert(*handle);
}
AssetEvent::Modified { handle } => {
changed_sprite_sheets.insert(*handle);
remove_sprite_sheet_resource(render_resources, *handle);
}
AssetEvent::Removed { handle } => {
remove_sprite_sheet_resource(render_resources, *handle);
// if sprite sheet was modified and removed in the same update, ignore the modification
// events are ordered so future modification events are ok
changed_sprite_sheets.remove(handle);
}
}
}
for changed_sprite_sheet_handle in changed_sprite_sheets.iter() {
if let Some(sprite_sheet) = sprite_sheets.get(changed_sprite_sheet_handle) {
let sprite_sheet_bytes = sprite_sheet.sprites.as_slice().as_bytes();
let sprite_sheet_buffer = render_resources.create_buffer_with_data(
BufferInfo {
buffer_usage: BufferUsage::STORAGE,
..Default::default()
},
&sprite_sheet_bytes,
);
render_resources.set_asset_resource(
*changed_sprite_sheet_handle,
sprite_sheet_buffer,
SPRITE_SHEET_BUFFER_ASSET_INDEX,
);
}
}
// TODO: remove this when batching is implemented
for (handle, mut renderable) in query.iter_mut(world) {
if let Some(sprite_sheet_buffer) =
render_resources.get_asset_resource(*handle, SPRITE_SHEET_BUFFER_ASSET_INDEX)
{
let mut buffer_size = None;
render_resources.get_resource_info(sprite_sheet_buffer, &mut |info| {
if let Some(ResourceInfo::Buffer(BufferInfo { size, .. })) = info {
buffer_size = Some(*size as u64)
}
});
renderable.render_resource_assignments.set(
"SpriteSheet",
RenderResourceAssignment::Buffer {
dynamic_index: None,
range: 0..buffer_size.unwrap(),
resource: sprite_sheet_buffer,
},
)
}
}
})
.system()
}

View file

@ -165,7 +165,7 @@ impl WgpuFrom<&BindType> for wgpu::BindingType {
dynamic, dynamic,
properties: _, properties: _,
} => wgpu::BindingType::UniformBuffer { dynamic: *dynamic }, } => wgpu::BindingType::UniformBuffer { dynamic: *dynamic },
BindType::Buffer { dynamic, readonly } => wgpu::BindingType::StorageBuffer { BindType::StorageBuffer { dynamic, readonly } => wgpu::BindingType::StorageBuffer {
dynamic: *dynamic, dynamic: *dynamic,
readonly: *readonly, readonly: *readonly,
}, },

View file

@ -1,36 +1,59 @@
use bevy::prelude::*; use bevy::{input::system::exit_on_esc_system, prelude::*};
use bevy::input::system::exit_on_esc_system; use bevy_sprite::{SpriteSheet, SpriteSheetSprite};
use bevy_sprite::{Rect, SpriteSheet};
fn main() { fn main() {
App::build() App::build()
.init_resource::<State>()
.add_default_plugins() .add_default_plugins()
.add_startup_system(setup.system()) .add_startup_system(setup.system())
.init_system(exit_on_esc_system) .init_system(exit_on_esc_system)
.add_system(animate_sprite_system.system())
.run(); .run();
} }
#[derive(Default)]
struct State {
elapsed: f32,
}
fn animate_sprite_system(
mut state: ResMut<State>,
time: Res<Time>,
sprite_sheets: Res<Assets<SpriteSheet>>,
mut sprite: ComMut<SpriteSheetSprite>,
sprite_sheet_handle: Com<Handle<SpriteSheet>>,
) {
state.elapsed += time.delta_seconds;
if state.elapsed > 0.1 {
state.elapsed = 0.0;
let sprite_sheet = sprite_sheets.get(&sprite_sheet_handle).unwrap();
sprite.index = ((sprite.index as usize + 1) % sprite_sheet.sprites.len()) as u32;
}
}
fn setup( fn setup(
command_buffer: &mut CommandBuffer, command_buffer: &mut CommandBuffer,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut textures: ResMut<Assets<Texture>>,
mut sprite_sheets: ResMut<Assets<SpriteSheet>>, mut sprite_sheets: ResMut<Assets<SpriteSheet>>,
) { ) {
let texture_handle = asset_server.load("assets/branding/icon.png").unwrap(); env_logger::init();
let sprite_sheet = SpriteSheet { let texture_handle = asset_server
texture: texture_handle, .load_sync(&mut textures, "assets/textures/character_run.png")
sprites: vec![ .unwrap();
Rect { let texture = textures.get(&texture_handle).unwrap();
min: Vec2::new(0.0, 0.0), let sprite_sheet = SpriteSheet::from_grid(texture_handle, texture.size, 7, 1);
max: Vec2::new(1.0, 1.0),
}
]
};
let sprite_sheet_handle = sprite_sheets.add(sprite_sheet); let sprite_sheet_handle = sprite_sheets.add(sprite_sheet);
command_buffer command_buffer
.build() .build()
.add_entity(OrthographicCameraEntity::default()) .add_entity(OrthographicCameraEntity::default())
.add_entity(SpriteSheetEntity { .add_entity(SpriteSheetEntity {
sprite_sheet: sprite_sheet_handle, sprite_sheet: sprite_sheet_handle,
sprite: SpriteSheetSprite {
index: 0,
scale: 6.0,
position: Vec3::new(0.0, 0.0, -0.5),
},
..Default::default() ..Default::default()
}); });
} }