mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
sprite sheets are fully operational
This commit is contained in:
parent
8c196139d4
commit
5927bad382
22 changed files with 193 additions and 200 deletions
19
.vscode/launch.json
vendored
19
.vscode/launch.json
vendored
|
@ -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",
|
||||||
|
|
|
@ -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)
|
BIN
assets/textures/character_run.png
Normal file
BIN
assets/textures/character_run.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
|
@ -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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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] {
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue