implement texture atlases for sprites, allow storage of assets not from a loader

This commit is contained in:
SeanOMik 2024-11-20 17:29:52 -05:00 committed by SeanOMik
parent b78101718e
commit e3b0b1de8f
20 changed files with 582 additions and 90 deletions

5
Cargo.lock generated
View File

@ -515,9 +515,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.18.0" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
@ -1893,6 +1893,7 @@ dependencies = [
name = "lyra-math" name = "lyra-math"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bytemuck",
"glam", "glam",
] ]

View File

@ -21,7 +21,7 @@ pub use render_target::*;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tracing::{debug_span, instrument, trace, warn}; use tracing::{debug_span, instrument, trace, warn};
use wgpu::CommandEncoder; use wgpu::{util::DeviceExt, BufferUsages, CommandEncoder};
use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader}; use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader};
@ -543,6 +543,15 @@ impl RenderGraph {
shader.wait_for_load()?; shader.wait_for_load()?;
Ok(shader) Ok(shader)
} }
/// Create a buffer with a single item inside of it
pub fn create_buffer_with_data<T: bytemuck::NoUninit>(&self, label: Option<&'static str>, usage: BufferUsages, data: &T) -> wgpu::Buffer {
self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label,
usage,
contents: bytemuck::bytes_of(data),
})
}
} }
pub struct SubGraphNode { pub struct SubGraphNode {

View File

@ -20,7 +20,7 @@ use tracing::{debug, warn};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, TextureAtlas graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, PackedTextureAtlas
}; };
use super::{MeshBufferStorage, RenderAssets, RenderMeshes}; use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
@ -98,7 +98,7 @@ impl ShadowMapsPass {
}), }),
); );
let atlas = TextureAtlas::new( let atlas = PackedTextureAtlas::new(
device, device,
wgpu::TextureFormat::Depth32Float, wgpu::TextureFormat::Depth32Float,
wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
@ -1062,14 +1062,14 @@ impl LightShadowMapId {
/// An ecs resource storing the [`TextureAtlas`] of shadow maps. /// An ecs resource storing the [`TextureAtlas`] of shadow maps.
#[derive(Clone)] #[derive(Clone)]
pub struct LightShadowMapAtlas(Arc<RwLock<TextureAtlas>>); pub struct LightShadowMapAtlas(Arc<RwLock<PackedTextureAtlas>>);
impl LightShadowMapAtlas { impl LightShadowMapAtlas {
pub fn get(&self) -> RwLockReadGuard<TextureAtlas> { pub fn get(&self) -> RwLockReadGuard<PackedTextureAtlas> {
self.0.read().unwrap() self.0.read().unwrap()
} }
pub fn get_mut(&self) -> RwLockWriteGuard<TextureAtlas> { pub fn get_mut(&self) -> RwLockWriteGuard<PackedTextureAtlas> {
self.0.write().unwrap() self.0.write().unwrap()
} }
} }

View File

@ -1,35 +1,39 @@
use std::{ use std::{collections::VecDeque, sync::Arc};
collections::VecDeque,
sync::Arc,
};
use glam::{Vec2, Vec3}; use glam::{UVec2, Vec2, Vec3};
use image::GenericImageView; use image::GenericImageView;
use lyra_ecs::{ use lyra_ecs::{
query::{Entities, ResMut}, AtomicRef, ResourceData query::{filter::Or, Entities, ResMut, TickOf}, AtomicRef, Entity, ResourceData
}; };
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use lyra_math::URect;
use lyra_resource::Image; use lyra_resource::Image;
use tracing::{info, instrument, warn}; use rustc_hash::FxHashMap;
use tracing::{debug, instrument, warn};
use uuid::Uuid; use uuid::Uuid;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use crate::{ use crate::{
render::{ render::{
graph::{Node, NodeDesc, NodeType, SlotAttribute}, graph::{Node, NodeDesc, NodeType, SlotAttribute},
render_job::RenderJob, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
resource::{
FragmentState, RenderPipeline, RenderPipelineDescriptor,
VertexState,
},
transform_buffer_storage::{TransformBuffers, TransformIndex}, transform_buffer_storage::{TransformBuffers, TransformIndex},
vertex::Vertex2D, vertex::Vertex2D,
}, },
sprite::Sprite, sprite::{AtlasSprite, Sprite},
}; };
use super::{BasePassSlots, RenderAssets}; use super::{BasePassSlots, RenderAssets};
#[derive(Clone)]
pub struct RenderJob {
pub entity: Entity,
pub shader_id: u64,
pub asset_uuid: uuid::Uuid,
pub transform_id: TransformIndex,
pub atlas_frame_id: u64,
}
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct SpritePassLabel; pub struct SpritePassLabel;
@ -48,7 +52,9 @@ struct SpriteTexture {
#[allow(dead_code)] #[allow(dead_code)]
sampler: wgpu::Sampler, sampler: wgpu::Sampler,
texture_bg: Arc<wgpu::BindGroup>, texture_bg: Arc<wgpu::BindGroup>,
}
struct SpriteBuffers {
vertex_buffers: wgpu::Buffer, vertex_buffers: wgpu::Buffer,
index_buffers: wgpu::Buffer, index_buffers: wgpu::Buffer,
} }
@ -58,9 +64,13 @@ pub struct SpritePass {
pipeline: Option<RenderPipeline>, pipeline: Option<RenderPipeline>,
texture_bgl: Option<Arc<wgpu::BindGroupLayout>>, texture_bgl: Option<Arc<wgpu::BindGroupLayout>>,
jobs: VecDeque<RenderJob>, jobs: VecDeque<RenderJob>,
/// Buffer that stores a `Rect` with `min` and `max` set to zero.
/// This can be used for sprites that are not from an atlas.
atlas_frames_buf: Option<Arc<wgpu::Buffer>>,
transform_buffers: Option<ResourceData>, transform_buffers: Option<ResourceData>,
sprite_textures: Option<ResourceData>, texture_store: Option<ResourceData>,
buffer_store: Option<ResourceData>,
} }
impl SpritePass { impl SpritePass {
@ -68,32 +78,22 @@ impl SpritePass {
Self::default() Self::default()
} }
#[instrument(skip(self, device, sprite))] #[instrument(skip(self, device))]
fn create_vertex_index_buffers( fn create_vertex_index_buffers(
&mut self, &mut self,
device: &wgpu::Device, device: &wgpu::Device,
sprite: &Sprite, dimensions: UVec2,
) -> (wgpu::Buffer, wgpu::Buffer) { ) -> (wgpu::Buffer, wgpu::Buffer) {
let tex_dims = sprite
.texture
.data_ref()
.map(|t| t.dimensions());
if tex_dims.is_none() {
info!("Sprite texture is not loaded, not rendering it until it is!");
todo!("Wait until texture is loaded");
}
let tex_dims = tex_dims.unwrap();
let vertices = vec![ let vertices = vec![
// top left // top left
Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)), Vertex2D::new(Vec3::new(0.0, 0.0, 0.0), Vec2::new(0.0, 1.0)),
// bottom left // bottom left
Vertex2D::new(Vec3::new(0.0, tex_dims.1 as f32, 0.0), Vec2::new(0.0, 0.0)), Vertex2D::new(Vec3::new(0.0, dimensions.y as f32, 0.0), Vec2::new(0.0, 0.0)),
// top right // top right
Vertex2D::new(Vec3::new(tex_dims.0 as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)), Vertex2D::new(Vec3::new(dimensions.x as f32, 0.0, 0.0), Vec2::new(1.0, 1.0)),
// bottom right // bottom right
Vertex2D::new( Vertex2D::new(
Vec3::new(tex_dims.0 as f32, tex_dims.1 as f32, 0.0), Vec3::new(dimensions.x as f32, dimensions.y as f32, 0.0),
Vec2::new(1.0, 0.0), Vec2::new(1.0, 0.0),
), ),
]; ];
@ -109,7 +109,7 @@ impl SpritePass {
//0, 2, 3 //0, 2, 3
3, 1, 0, // second tri 3, 1, 0, // second tri
0, 2, 3, // first tri 0, 2, 3, // first tri
//0, 2, 3, // second tri //0, 2, 3, // second tri
]; ];
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"), label: Some("Index Buffer"),
@ -175,6 +175,7 @@ impl SpritePass {
}, },
); );
let frames = self.atlas_frames_buf.as_ref().unwrap();
let bgl = self.texture_bgl.as_ref().unwrap(); let bgl = self.texture_bgl.as_ref().unwrap();
let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor { let tex_bg = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("sprite_texture_bg_{}", uuid_str)), label: Some(&format!("sprite_texture_bg_{}", uuid_str)),
@ -188,6 +189,14 @@ impl SpritePass {
binding: 1, binding: 1,
resource: wgpu::BindingResource::Sampler(&sampler), resource: wgpu::BindingResource::Sampler(&sampler),
}, },
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &frames,
offset: 0,
size: None,
}),
},
], ],
}); });
@ -202,6 +211,17 @@ impl Node for SpritePass {
) -> crate::render::graph::NodeDesc { ) -> crate::render::graph::NodeDesc {
let device = &graph.device; let device = &graph.device;
let atlas_frames = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("default_sprite_atlas_frame"),
size: std::mem::size_of::<URect>() as u64 * 1000,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
// write the rect for sprites that aren't part of the texture atlas.
graph
.queue
.write_buffer(&atlas_frames, 0, bytemuck::bytes_of(&URect::ZERO));
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("bgl_sprite_main"), label: Some("bgl_sprite_main"),
entries: &[ entries: &[
@ -221,9 +241,20 @@ impl Node for SpritePass {
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None, count: None,
}, },
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
], ],
}); });
self.texture_bgl = Some(Arc::new(bgl)); self.texture_bgl = Some(Arc::new(bgl));
self.atlas_frames_buf = Some(Arc::new(atlas_frames));
let mut desc = NodeDesc::new(NodeType::Render, None, vec![]); let mut desc = NodeDesc::new(NodeType::Render, None, vec![]);
@ -242,7 +273,11 @@ impl Node for SpritePass {
let vt = graph.view_target(); let vt = graph.view_target();
if self.pipeline.is_none() { if self.pipeline.is_none() {
let shader = graph.load_shader_str("sprite_shader", include_str!("../../shaders/2d/sprite_main.wgsl")) let shader = graph
.load_shader_str(
"sprite_shader",
include_str!("../../shaders/2d/sprite_main.wgsl"),
)
.expect("failed to load wgsl shader from manager"); .expect("failed to load wgsl shader from manager");
let diffuse_bgl = self.texture_bgl.clone().unwrap(); let diffuse_bgl = self.texture_bgl.clone().unwrap();
@ -284,10 +319,14 @@ impl Node for SpritePass {
drop(transforms); drop(transforms);
world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>(); world.add_resource_default_if_absent::<RenderAssets<SpriteTexture>>();
let sprite_textures = world let texture_store = world
.get_resource_data::<RenderAssets<SpriteTexture>>() .get_resource_data::<RenderAssets<SpriteTexture>>().unwrap();
.expect("Missing sprite texture store"); self.texture_store = Some(texture_store.clone());
self.sprite_textures = Some(sprite_textures.clone());
world.add_resource_default_if_absent::<FxHashMap<Entity, SpriteBuffers>>();
let buffer_store = world
.get_resource_data::<FxHashMap<Entity, SpriteBuffers>>().unwrap();
self.buffer_store = Some(buffer_store.clone());
let transforms = world let transforms = world
.get_resource_data::<TransformBuffers>() .get_resource_data::<TransformBuffers>()
@ -295,46 +334,88 @@ impl Node for SpritePass {
self.transform_buffers = Some(transforms.clone()); self.transform_buffers = Some(transforms.clone());
} }
let mut v = Vec::with_capacity(500);
let world_tick = world.current_tick();
let queue = &graph.queue; let queue = &graph.queue;
for (entity, sprite, transform_idx, mut sprite_store) in world for (entity, (sprite, atlas_sprite), transform_idx, mut texture_store, mut buffer_store) in world
.view::<( .view::<(
Entities, Entities,
&Sprite, Or<&Sprite, (&AtlasSprite, TickOf<AtlasSprite>)>,
&TransformIndex, &TransformIndex,
ResMut<RenderAssets<SpriteTexture>>, ResMut<RenderAssets<SpriteTexture>>,
ResMut<FxHashMap<Entity, SpriteBuffers>>,
)>() )>()
.iter() .iter()
{ {
if let Some(image) = sprite.texture.data_ref() { let tex = if let Some(sprite) = &sprite {
let texture_uuid = sprite.texture.uuid(); sprite.texture.clone()//.data_ref()
if !sprite_store.contains_key(&texture_uuid) { } else if let Some((a, _)) = &atlas_sprite {
a.atlas.data_ref()
.unwrap().texture.clone()
} else { continue; };
let rect = atlas_sprite.as_ref().map(|(a, _)| a.sprite);
if let Some(image) = tex.data_ref() {
let texture_uuid = tex.uuid();
if !texture_store.contains_key(&texture_uuid) {
// returns `None` if the Texture image is not loaded. // returns `None` if the Texture image is not loaded.
if let Some((texture, sampler, tex_bg)) = if let Some((texture, sampler, tex_bg)) =
self.load_sprite_texture(device, queue, &texture_uuid, &image) self.load_sprite_texture(device, queue, &texture_uuid, &image)
{ {
let (vertex, index) = self.create_vertex_index_buffers(device, &sprite); texture_store.insert(
sprite_store.insert(
texture_uuid, texture_uuid,
SpriteTexture { SpriteTexture {
texture, texture,
sampler, sampler,
texture_bg: Arc::new(tex_bg), texture_bg: Arc::new(tex_bg),
vertex_buffers: vertex,
index_buffers: index,
}, },
); );
} }
} }
if !buffer_store.contains_key(&entity) {
let dim = rect.unwrap_or_else(|| {
let i = image.dimensions();
URect::new(0, 0, i.0, i.1)
});
debug!("storing rect: {dim:?}");
let dim = dim.dimensions();
let (vertex, index) = self.create_vertex_index_buffers(device, dim);
buffer_store.insert(entity, SpriteBuffers { vertex_buffers: vertex, index_buffers: index });
} else if let Some((ats, tick)) = &atlas_sprite {
// detect a change for the vertex and index buffers of the sprite
if tick.checked_sub(1).unwrap_or(0) >= *world_tick {
debug!("Updating buffer for entity after change detected in atlas sprite");
let dim = ats.sprite.dimensions();
let (vertex, index) = self.create_vertex_index_buffers(device, dim);
buffer_store.insert(entity, SpriteBuffers { vertex_buffers: vertex, index_buffers: index });
}
}
let frame_id = match rect {
Some(r) => {
v.push(r);
// No -1 here since the gpu buffer is already offset by 1
// to store the default rect at the front
v.len() as u64
}
None => 0
};
self.jobs.push_back(RenderJob { self.jobs.push_back(RenderJob {
entity, entity,
shader_id: 0, shader_id: 0,
asset_uuid: texture_uuid, asset_uuid: texture_uuid,
transform_id: *transform_idx, transform_id: *transform_idx,
atlas_frame_id: frame_id,
}); });
} };
} }
let buf = self.atlas_frames_buf.as_ref().unwrap();
// skip default rect
queue.write_buffer(buf, std::mem::size_of::<URect>() as _, bytemuck::cast_slice(&v));
} }
fn execute( fn execute(
@ -345,8 +426,10 @@ impl Node for SpritePass {
) { ) {
let pipeline = self.pipeline.as_ref().unwrap(); let pipeline = self.pipeline.as_ref().unwrap();
let sprite_store = self.sprite_textures.clone().unwrap(); let texture_store = self.texture_store.clone().unwrap();
let sprite_store: AtomicRef<RenderAssets<SpriteTexture>> = sprite_store.get(); let texture_store: AtomicRef<RenderAssets<SpriteTexture>> = texture_store.get();
let buffer_store = self.buffer_store.clone().unwrap();
let buffer_store: AtomicRef<FxHashMap<Entity, SpriteBuffers>> = buffer_store.get();
let transforms = self.transform_buffers.clone().unwrap(); let transforms = self.transform_buffers.clone().unwrap();
let transforms: AtomicRef<TransformBuffers> = transforms.get(); let transforms: AtomicRef<TransformBuffers> = transforms.get();
@ -379,10 +462,11 @@ impl Node for SpritePass {
pass.set_pipeline(pipeline); pass.set_pipeline(pipeline);
while let Some(job) = self.jobs.pop_front() { while let Some(job) = self.jobs.pop_front() {
let sprite = sprite_store.get(&job.asset_uuid) // bind texture
let tex = texture_store
.get(&job.asset_uuid)
.expect("failed to find SpriteTexture for job asset_uuid"); .expect("failed to find SpriteTexture for job asset_uuid");
pass.set_bind_group(0, &tex.texture_bg, &[]);
pass.set_bind_group(0, &sprite.texture_bg, &[]);
// Get the bindgroup for job's transform and bind to it using an offset. // Get the bindgroup for job's transform and bind to it using an offset.
let bindgroup = transforms.bind_group(job.transform_id); let bindgroup = transforms.bind_group(job.transform_id);
@ -391,12 +475,16 @@ impl Node for SpritePass {
pass.set_bind_group(2, camera_bg, &[]); pass.set_bind_group(2, camera_bg, &[]);
pass.set_vertex_buffer( // set vertex and index buffers
0, let bufs = buffer_store
sprite.vertex_buffers.slice(..), .get(&job.entity)
); .expect("failed to find buffers for job entity");
pass.set_index_buffer(sprite.index_buffers.slice(..), wgpu::IndexFormat::Uint32); pass.set_vertex_buffer(0, bufs.vertex_buffers.slice(..));
pass.draw_indexed(0..6, 0, 0..1); pass.set_index_buffer(bufs.index_buffers.slice(..), wgpu::IndexFormat::Uint32);
// use the atlas frame id as the instance
let inst = job.atlas_frame_id as u32;
pass.draw_indexed(0..6, 0, inst..inst + 1);
} }
} }
} }

View File

@ -3,12 +3,14 @@ const ALPHA_CUTOFF = 0.1;
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>, @location(1) tex_coords: vec2<f32>,
@builtin(instance_index) instance_index: u32,
} }
struct VertexOutput { struct VertexOutput {
@builtin(position) clip_position: vec4<f32>, @builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>, @location(0) tex_coords: vec2<f32>,
@location(1) world_position: vec3<f32>, @location(1) world_position: vec3<f32>,
@location(2) instance_index: u32
} }
struct TransformData { struct TransformData {
@ -16,6 +18,11 @@ struct TransformData {
normal_matrix: mat4x4<f32>, normal_matrix: mat4x4<f32>,
} }
struct URect {
min: vec2<u32>,
max: vec2<u32>,
}
struct CameraUniform { struct CameraUniform {
view: mat4x4<f32>, view: mat4x4<f32>,
inverse_projection: mat4x4<f32>, inverse_projection: mat4x4<f32>,
@ -40,6 +47,7 @@ fn vs_main(
out.world_position = world_position.xyz; out.world_position = world_position.xyz;
out.tex_coords = in.tex_coords; out.tex_coords = in.tex_coords;
out.clip_position = u_camera.view_projection * world_position; out.clip_position = u_camera.view_projection * world_position;
out.instance_index = in.instance_index;
return out; return out;
} }
@ -49,9 +57,26 @@ var t_diffuse: texture_2d<f32>;
@group(0) @binding(1) @group(0) @binding(1)
var s_diffuse: sampler; var s_diffuse: sampler;
@group(0) @binding(2)
var<storage, read> u_sprite_atlas_frames: array<URect>;
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords); let frame = u_sprite_atlas_frames[in.instance_index];
var region_coords = in.tex_coords;
if (frame.min.x != 0 || frame.min.y != 0 || frame.max.x != 0 || frame.max.y != 0) {
let dim = vec2<f32>(textureDimensions(t_diffuse));
// convert tex coords to frame
region_coords = vec2<f32>(
mix(f32(frame.min.x), f32(frame.max.x), in.tex_coords.x),
mix(f32(frame.min.y), f32(frame.max.y), in.tex_coords.y)
);
// convert frame coords to texture coords
region_coords /= dim;
}
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, region_coords);
if (object_color.a < ALPHA_CUTOFF) { if (object_color.a < ALPHA_CUTOFF) {
discard; discard;
} }

View File

@ -28,7 +28,7 @@ impl AtlasFrame {
} }
} }
pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> { pub struct PackedTextureAtlas<P: AtlasPacker = SkylinePacker> {
atlas_size: UVec2, atlas_size: UVec2,
texture_format: wgpu::TextureFormat, texture_format: wgpu::TextureFormat,
@ -38,7 +38,7 @@ pub struct TextureAtlas<P: AtlasPacker = SkylinePacker> {
packer: P, packer: P,
} }
impl<P: AtlasPacker> TextureAtlas<P> { impl<P: AtlasPacker> PackedTextureAtlas<P> {
pub fn new( pub fn new(
device: &wgpu::Device, device: &wgpu::Device,
format: wgpu::TextureFormat, format: wgpu::TextureFormat,

View File

@ -3,6 +3,9 @@ use lyra_reflect::Reflect;
use lyra_resource::ResHandle; use lyra_resource::ResHandle;
use lyra_math::{Vec3, Vec2}; use lyra_math::{Vec3, Vec2};
mod texture_atlas;
pub use texture_atlas::*;
/// How the sprite is positioned and rotated relative to its [`Transform`]. /// How the sprite is positioned and rotated relative to its [`Transform`].
/// ///
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites. /// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.

View File

@ -0,0 +1,105 @@
use glam::{UVec2, Vec3};
use lyra_ecs::Component;
use lyra_math::URect;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle;
use super::Pivot;
/// A texture atlas of multiple sprites.
#[derive(Clone, Component, Reflect)]
pub struct TextureAtlas {
pub texture: ResHandle<lyra_resource::Image>,
/// The coordinates in the texture where the grid starts.
pub grid_offset: UVec2,
/// The size of the grid in cells.
pub grid_size: UVec2,
/// The size of each cell.
pub cell_size: UVec2,
pub sprite_color: Vec3,
pub pivot: Pivot,
}
impl TextureAtlas {
/// The cell x and y in the grid of a specific index.
pub fn index_cell(&self, i: u32) -> UVec2 {
let x = i % self.grid_size.x;
let y = i / self.grid_size.x;
UVec2 { x, y }
}
/// The coords of the cell at x and y in the grid.
///
/// The indices are different then the image coords, this is the position in the grid.
/// So if you have a 9x7 grid, and wanted to get the 1nd cell on the 2nd row, you'd
/// use the values `x = 0, y = 1` (indices start at zero like arrays).
#[inline(always)]
pub fn cell_coords(&self, x: u32, y: u32) -> UVec2 {
UVec2 {
x: x * self.cell_size.x,
y: y * self.cell_size.y,
}
}
/// The coords of the cell at an index.
#[inline(always)]
pub fn index_coords(&self, i: u32) -> UVec2 {
let cell = self.index_cell(i);
self.cell_coords(cell.x, cell.y)
}
/// The rectangle of the cell at the x and y indices in the grid.
///
/// The indices are different then the image coords, this is the position in the grid.
/// So if you have a 9x7 grid, and wanted to get the 1nd cell on the 2nd row, you'd
/// use the values `x = 0, y = 1` (indices start at zero like arrays).
#[inline(always)]
pub fn cell_rect(&self, x: u32, y: u32) -> URect {
let start = self.cell_coords(x, y);
let end = start + self.cell_size;
URect { min: start, max: end }
}
/// The rectangle of the cell at an index.
#[inline(always)]
pub fn index_rect(&self, i: u32) -> URect {
let cell = self.index_cell(i);
self.cell_rect(cell.x, cell.y)
}
}
impl lyra_resource::ResourceData for TextureAtlas {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn dependencies(&self) -> Vec<lyra_resource::UntypedResHandle> {
vec![self.texture.untyped_clone()]
}
}
/// A sprite from a texture atlas.
#[derive(Clone, Component, Reflect)]
pub struct AtlasSprite {
pub atlas: ResHandle<TextureAtlas>,
pub sprite: URect,
}
impl AtlasSprite {
#[inline(always)]
pub fn from_atlas_index(atlas: ResHandle<TextureAtlas>, i: u32) -> Self {
let a = atlas.data_ref().unwrap();
let rect = a.index_rect(i);
Self {
atlas: atlas.clone(),
sprite: rect,
}
}
}

View File

@ -6,4 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
glam = { version = "0.29.0" } bytemuck = "1.19.0"
glam = { version = "0.29.0", features = ["bytemuck"] }

View File

@ -0,0 +1,2 @@
mod rect;
pub use rect::*;

View File

@ -0,0 +1,90 @@
use glam::IVec2;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct IRect {
pub min: IVec2,
pub max: IVec2,
}
impl IRect {
pub const ZERO: IRect = IRect { min: IVec2::ZERO, max: IVec2::ZERO };
pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
Self {
min: IVec2::new(x1, y1),
max: IVec2::new(x2, y2),
}
}
pub fn from_vec(min: IVec2, max: IVec2) -> Self {
Self {
min,
max
}
}
pub fn dimensions(&self) -> IVec2 {
self.max - self.min
}
}
impl std::ops::Add for IRect {
type Output = IRect;
fn add(self, rhs: Self) -> Self::Output {
IRect::from_vec(self.min + rhs.min, self.max + rhs.max)
}
}
impl std::ops::AddAssign for IRect {
fn add_assign(&mut self, rhs: Self) {
self.min += rhs.min;
self.max += rhs.max;
}
}
impl std::ops::Sub for IRect {
type Output = IRect;
fn sub(self, rhs: Self) -> Self::Output {
IRect::from_vec(self.min - rhs.min, self.max - rhs.max)
}
}
impl std::ops::SubAssign for IRect {
fn sub_assign(&mut self, rhs: Self) {
self.min -= rhs.min;
self.max -= rhs.max;
}
}
impl std::ops::Mul for IRect {
type Output = IRect;
fn mul(self, rhs: Self) -> Self::Output {
IRect::from_vec(self.min * rhs.min, self.max * rhs.max)
}
}
impl std::ops::MulAssign for IRect {
fn mul_assign(&mut self, rhs: Self) {
self.min *= rhs.min;
self.max *= rhs.max;
}
}
impl std::ops::Div for IRect {
type Output = IRect;
fn div(self, rhs: Self) -> Self::Output {
IRect::from_vec(self.min / rhs.min, self.max / rhs.max)
}
}
impl std::ops::DivAssign for IRect {
fn div_assign(&mut self, rhs: Self) {
self.min /= rhs.min;
self.max /= rhs.max;
}
}

View File

@ -10,6 +10,14 @@ pub use area::*;
mod rect; mod rect;
pub use rect::*; pub use rect::*;
#[allow(hidden_glob_reexports)]
mod u32;
pub use u32::*;
#[allow(hidden_glob_reexports)]
mod i32;
pub use i32::*;
pub mod transform; pub mod transform;
pub use transform::*; pub use transform::*;

View File

@ -1,12 +1,15 @@
use glam::Vec2; use glam::Vec2;
#[derive(Debug, Clone, Copy, Default, PartialEq)] #[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Rect { pub struct Rect {
pub min: Vec2, pub min: Vec2,
pub max: Vec2, pub max: Vec2,
} }
impl Rect { impl Rect {
pub const ZERO: Rect = Rect { min: Vec2::ZERO, max: Vec2::ZERO };
pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self { pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
Self { Self {
min: Vec2::new(x1, y1), min: Vec2::new(x1, y1),
@ -20,6 +23,10 @@ impl Rect {
max max
} }
} }
pub fn dimensions(&self) -> Vec2 {
(self.max - self.min).abs()
}
} }
impl std::ops::Add for Rect { impl std::ops::Add for Rect {

View File

@ -0,0 +1,2 @@
mod rect;
pub use rect::*;

View File

@ -0,0 +1,90 @@
use glam::UVec2;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
pub struct URect {
pub min: UVec2,
pub max: UVec2,
}
impl URect {
pub const ZERO: URect = URect { min: UVec2::ZERO, max: UVec2::ZERO };
pub fn new(x1: u32, y1: u32, x2: u32, y2: u32) -> Self {
Self {
min: UVec2::new(x1, y1),
max: UVec2::new(x2, y2),
}
}
pub fn from_vec(min: UVec2, max: UVec2) -> Self {
Self {
min,
max
}
}
pub fn dimensions(&self) -> UVec2 {
self.max - self.min
}
}
impl std::ops::Add for URect {
type Output = URect;
fn add(self, rhs: Self) -> Self::Output {
URect::from_vec(self.min + rhs.min, self.max + rhs.max)
}
}
impl std::ops::AddAssign for URect {
fn add_assign(&mut self, rhs: Self) {
self.min += rhs.min;
self.max += rhs.max;
}
}
impl std::ops::Sub for URect {
type Output = URect;
fn sub(self, rhs: Self) -> Self::Output {
URect::from_vec(self.min - rhs.min, self.max - rhs.max)
}
}
impl std::ops::SubAssign for URect {
fn sub_assign(&mut self, rhs: Self) {
self.min -= rhs.min;
self.max -= rhs.max;
}
}
impl std::ops::Mul for URect {
type Output = URect;
fn mul(self, rhs: Self) -> Self::Output {
URect::from_vec(self.min * rhs.min, self.max * rhs.max)
}
}
impl std::ops::MulAssign for URect {
fn mul_assign(&mut self, rhs: Self) {
self.min *= rhs.min;
self.max *= rhs.max;
}
}
impl std::ops::Div for URect {
type Output = URect;
fn div(self, rhs: Self) -> Self::Output {
URect::from_vec(self.min / rhs.min, self.max / rhs.max)
}
}
impl std::ops::DivAssign for URect {
fn div_assign(&mut self, rhs: Self) {
self.min /= rhs.min;
self.max /= rhs.max;
}
}

View File

@ -6,7 +6,17 @@ use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef};
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32)); impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32)); impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32)); impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(lyra_math::UVec2, fields(x = u32, y = u32));
impl_reflect_simple_struct!(lyra_math::UVec3, fields(x = u32, y = u32, z = u32));
impl_reflect_simple_struct!(lyra_math::UVec4, fields(x = u32, y = u32, z = u32, w = u32));
impl_reflect_simple_struct!(lyra_math::IVec2, fields(x = i32, y = i32));
impl_reflect_simple_struct!(lyra_math::IVec3, fields(x = i32, y = i32, z = i32));
impl_reflect_simple_struct!(lyra_math::IVec4, fields(x = i32, y = i32, z = i32, w = i32));
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32)); impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(lyra_math::Rect, fields(min = lyra_math::Vec2, max = lyra_math::Vec2));
impl_reflect_simple_struct!(lyra_math::URect, fields(min = lyra_math::UVec2, max = lyra_math::UVec2));
impl_reflect_simple_struct!(lyra_math::IRect, fields(min = lyra_math::IVec2, max = lyra_math::IVec2));
impl_reflect_simple_struct!( impl_reflect_simple_struct!(
lyra_math::Transform, lyra_math::Transform,

View File

@ -184,6 +184,14 @@ impl ResourceManager {
} }
} }
/// Store a new resource, returning its handle.
pub fn store_new<T: ResourceData>(&self, data: T) -> ResHandle<T> {
let handle = ResHandle::new_ready(None, data);
let mut state = self.state_mut();
state.resources.insert(handle.uuid().to_string(), Arc::new(handle.clone()));
handle
}
/// Store a resource using its uuid. /// Store a resource using its uuid.
/// ///
/// The resource cannot be requested with [`ResourceManager::request`], it can only be /// The resource cannot be requested with [`ResourceManager::request`], it can only be

View File

@ -1,18 +1,12 @@
use lyra_engine::{ use lyra_engine::{
assets::{Image, ResourceManager}, assets::{Image, ResourceManager}, ecs::query::{Res, ResMut, View}, game::App, gltf::Gltf, input::{
game::App,
gltf::Gltf,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, InputActionPlugin, KeyCode, LayoutId,
}, }, math::{self, Rect, Transform, URect, UVec2, Vec2, Vec3}, render::light::directional::DirectionalLight, scene::{
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
system_update_world_transforms, Camera2dBundle, CameraProjection, OrthographicProjection, ScaleMode, TopDown2dCamera, TopDown2dCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN system_update_world_transforms, Camera2dBundle, CameraProjection, OrthographicProjection, ScaleMode, TopDown2dCamera, TopDown2dCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN
}, }, sprite::{self, AtlasSprite, Pivot, Sprite, TextureAtlas}, DeltaTime
sprite::{self, Sprite},
}; };
use tracing::debug;
#[async_std::main] #[async_std::main]
async fn main() { async fn main() {
@ -100,6 +94,9 @@ async fn main() {
} }
fn setup_scene_plugin(app: &mut App) { fn setup_scene_plugin(app: &mut App) {
app.add_resource(Timer(0.0));
app.with_system("sprite_change", sprite_change, &[]);
let world = &mut app.world; let world = &mut app.world;
let resman = world.get_resource_mut::<ResourceManager>().unwrap(); let resman = world.get_resource_mut::<ResourceManager>().unwrap();
@ -127,19 +124,28 @@ fn setup_scene_plugin(app: &mut App) {
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap(); let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
image.wait_recurse_dependencies_load().unwrap(); image.wait_recurse_dependencies_load().unwrap();
let soldier = resman.request::<Image>("../assets/tiny_rpg_characters/Characters(100x100)/Soldier/Soldier/Soldier.png").unwrap();
soldier.wait_recurse_dependencies_load().unwrap();
let atlas = resman.store_new(TextureAtlas {
texture: soldier,
grid_offset: UVec2::ZERO,
grid_size: UVec2::new(9, 7),
cell_size: UVec2::new(100, 100),
sprite_color: Vec3::ONE,
pivot: Pivot::default(),
});
let sprite = AtlasSprite::from_atlas_index(atlas, 9);
drop(resman); drop(resman);
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -2.0),
));
world.spawn(( world.spawn((
Sprite { /* Sprite {
texture: image, texture: sprite,
color: Vec3::ONE, color: Vec3::ONE,
pivot: sprite::Pivot::Center, pivot: sprite::Pivot::Center,
}, }, */
sprite,
WorldTransform::default(), WorldTransform::default(),
Transform::from_xyz(0.0, 0.0, -10.0), Transform::from_xyz(0.0, 0.0, -10.0),
)); ));
@ -169,8 +175,41 @@ fn setup_scene_plugin(app: &mut App) {
}, },
Transform::from_xyz(0.0, 0.0, 0.0), Transform::from_xyz(0.0, 0.0, 0.0),
TopDown2dCamera { TopDown2dCamera {
zoom_speed: Some(0.1), zoom_speed: Some(0.2),
speed: 14.0,
..Default::default() ..Default::default()
} }
)); ));
} }
#[derive(Clone, Copy, Debug)]
struct Timer(f32);
fn sprite_change(mut timer: ResMut<Timer>, dt: Res<DeltaTime>, view: View<&mut AtlasSprite>) -> anyhow::Result<()> {
timer.0 += **dt;
const TIME: f32 = 0.1;
if timer.0 >= TIME {
//println!("{t} seconds timer triggered, moving sprite");
timer.0 = 0.0;
for mut a in view.iter() {
//println!("a.sprite: {:?}", a.sprite);
if a.sprite.max.x >= 800 {
a.sprite = URect {
min: UVec2::new(0, 100),
max: UVec2::new(100, 200),
};
//println!("restart!");
} else {
a.sprite += URect {
min: UVec2::new(100, 0),
max: UVec2::new(100, 0),
};
}
}
}
Ok(())
}

View File

@ -0,0 +1,3 @@
*
!.gitignore
!source.txt

View File

@ -0,0 +1 @@
https://zerie.itch.io/tiny-rpg-character-asset-pack