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": [],
"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",
"request": "launch",

View file

@ -10,3 +10,7 @@
* amethyst
* 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 inflector::Inflector;
use crate::modules::{get_modules, get_path};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
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)]
struct UniformAttributeArgs {
#[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_idents = 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_types = Vec::new();
@ -98,6 +98,7 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
for (f, attrs) in field_attributes.iter() {
let field_name = f.ident.as_ref().unwrap().to_string();
if !attrs.ignore {
let active_uniform_field_name = &f.ident;
active_uniform_field_names.push(&f.ident);
active_uniform_field_name_strings.push(field_name.clone());
let uniform = format!("{}_{}", struct_name, field_name);
@ -117,6 +118,19 @@ pub fn derive_uniforms(input: TokenStream) -> TokenStream {
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 {
@ -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> {
use #bevy_render_path::shader::GetFieldBindType;
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,
}
}

View file

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

View file

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

View file

@ -83,7 +83,7 @@ where
fn increment_uniform_counts(&mut self, uniforms: &T) {
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)
{
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() {
let bind_type = uniforms.get_field_bind_type(&field_info.name);
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 range = 0..size as u64;
let (target_buffer, target_offset) = if dynamic_uniforms {
let buffer = uniform_buffer_status.buffer.unwrap();
let index = uniform_buffer_status
@ -199,25 +200,32 @@ where
dynamic_index: Some(
(index * uniform_buffer_status.aligned_size) as u32,
),
range: 0..size as u64,
range,
},
);
(buffer, index * uniform_buffer_status.aligned_size)
} else {
let resource =
match render_resource_assignments.get(field_info.uniform_name) {
let resource = match render_resource_assignments
.get(field_info.uniform_name)
{
Some(assignment) => assignment.get_resource(),
None => {
let usage = if let Some(FieldBindType::Buffer { .. }) = bind_type {
BufferUsage::STORAGE
} else {
BufferUsage::UNIFORM
};
let resource = render_resources.create_buffer(BufferInfo {
size,
buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM,
buffer_usage: BufferUsage::COPY_DST | usage,
..Default::default()
});
render_resource_assignments.set(
&field_info.uniform_name,
RenderResourceAssignment::Buffer {
resource,
range: 0..size as u64,
range,
dynamic_index: None,
},
);
@ -232,14 +240,16 @@ where
+ (uniform_buffer_status.queued_buffer_writes.len()
* uniform_buffer_status.item_size);
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 {
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
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)]);
uniforms.write_uniform_bytes(
&field_info.uniform_name,
&mut staging_buffer
[staging_buffer_start..(staging_buffer_start + uniform_byte_len)],
);
} else {
panic!(
"failed to get data from uniform: {}",
@ -254,7 +264,8 @@ where
offset: target_offset,
});
}
_ => {}
Some(FieldBindType::Texture) => { /* ignore textures */ }
None => { /* ignore None */ }
}
}
}

View file

@ -1,6 +1,7 @@
bitflags::bitflags! {
#[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 {
const MAP_READ = 1;
const MAP_WRITE = 2;
@ -11,17 +12,5 @@ bitflags::bitflags! {
const UNIFORM = 64;
const STORAGE = 128;
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 {
BufferInfo {
size: 0,
buffer_usage: BufferUsage::NONE,
buffer_usage: BufferUsage::empty(),
}
}
}

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_derive::FromResources;
use legion::prelude::*;
use std::{collections::HashSet, fs::File};
use glam::Vec2;
pub const TEXTURE_ASSET_INDEX: usize = 0;
pub const SAMPLER_ASSET_INDEX: usize = 1;
@ -18,13 +19,12 @@ pub enum TextureType {
pub struct Texture {
pub data: Vec<u8>,
pub width: usize,
pub height: usize,
pub size: Vec2,
}
impl Texture {
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 {
@ -41,8 +41,7 @@ impl Texture {
Texture {
data,
width,
height,
size: Vec2::new(width as f32, height as f32)
}
}

View file

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

View file

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

View file

@ -135,7 +135,7 @@ impl SpriteRenderGraphBuilder for RenderGraph {
self.add_system_node(
node::SPRITE_SHEET,
AssetUniformNode::<SpriteSheet>::new(true),
AssetUniformNode::<SpriteSheet>::new(false),
);
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(set = 1, binding = 1) uniform texture2D SpriteSheet_texture;
layout(set = 1, binding = 2) uniform sampler SpriteSheet_texture_sampler;
layout(set = 1, binding = 2) uniform texture2D SpriteSheet_texture;
layout(set = 1, binding = 3) uniform sampler SpriteSheet_texture_sampler;
void main() {
o_Target = texture(
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 = 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
// sprite
// 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 = 0) out vec2 v_Uv;
@ -24,30 +15,37 @@ 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 {
vec2 Dimensions;
};
struct Rect {
vec2 begin;
vec2 end;
};
layout(set = 1, binding = 0) buffer SpriteSheet_sprites {
layout(set = 1, binding = 1) buffer SpriteSheet_sprites {
Rect[] Sprites;
};
layout(set = 2, binding = 0) uniform SpriteSheetSprite {
vec3 SpriteSheetSprite_position;
float SpriteSheetSprite_scale;
uint SpriteSheetSprite_index;
};
void main() {
Rect sprite_rect = Sprites[SpriteSheetSprite_index];
vec2 dimensions = sprite_rect.end - sprite_rect.begin;
vec2 vertex_position = Vertex_Position.xy * dimensions;
vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin;
vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions * SpriteSheetSprite_scale, 0.0) + SpriteSheetSprite_position;
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.end,
vec2(sprite_rect.begin.x, sprite_rect.end.y)
vec2(sprite_rect.end.x, sprite_rect.begin.y),
sprite_rect.end
);
v_Uv = uvs[gl_VertexIndex];
gl_Position = ViewProj * vec4(vec3(vertex_position, 0.0) + SpriteSheetSprite_position, 1.0);
v_Uv = uvs[gl_VertexIndex] / Dimensions;
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) = textures.get(&texture_handle) {
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;
}
}

View file

@ -1,113 +1,52 @@
use crate::Rect;
use bevy_app::{Events, GetEventReader};
use bevy_asset::{AssetEvent, Assets, Handle};
use bevy_core::bytes::AsBytes;
use bevy_asset::Handle;
use bevy_derive::{Bytes, Uniform, Uniforms};
use bevy_render::{
render_resource::{BufferInfo, BufferUsage, RenderResourceAssignment, ResourceInfo},
renderer::{RenderResourceContext, RenderResources},
texture::Texture,
Renderable,
};
use glam::{Vec3, Vec4};
use legion::prelude::*;
use std::collections::HashSet;
use bevy_render::texture::Texture;
use glam::{Vec2, Vec3};
#[derive(Uniforms)]
pub struct SpriteSheet {
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>,
}
// 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
// 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 position: Vec3,
pub scale: f32,
pub index: u32,
}
pub const SPRITE_SHEET_BUFFER_ASSET_INDEX: usize = 0;
fn remove_sprite_sheet_resource(
render_resources: &dyn RenderResourceContext,
handle: Handle<SpriteSheet>,
) {
if let Some(resource) =
render_resources.get_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX)
{
render_resources.remove_buffer(resource);
render_resources.remove_asset_resource(handle, SPRITE_SHEET_BUFFER_ASSET_INDEX);
}
}
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,
},
)
}
}
impl SpriteSheet {
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;
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),
max: Vec2::new(
(x + 1) as f32 * sprite_width,
(y + 1) as f32 * sprite_height,
),
})
.system()
}
}
SpriteSheet {
dimensions: size,
sprites,
texture,
}
}
}

View file

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

View file

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