diff --git a/crates/bevy_tiles/src/chunks.rs b/crates/bevy_tiles/src/chunks.rs index e46a4dd..1c1ca42 100644 --- a/crates/bevy_tiles/src/chunks.rs +++ b/crates/bevy_tiles/src/chunks.rs @@ -1,7 +1,10 @@ use std::ops::Deref; use aery::prelude::{CleanupPolicy, Relation}; -use bevy::ecs::{component::Component, entity::Entity}; +use bevy::{ + ecs::{component::Component, entity::Entity}, + math::Vec2, +}; mod chunk_query; @@ -17,7 +20,7 @@ pub struct InMap; /// Right now, changes to this coordinate don't automatically update any information. /// If you wish to move a chunk, add, or remove a chunk, please do so via commands. /// Use this if you wish to track changes or other information. -#[derive(Component, Debug, PartialEq, Eq, Hash)] +#[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct ChunkCoord([isize; N]); impl From<[isize; N]> for ChunkCoord { @@ -34,6 +37,12 @@ impl Deref for ChunkCoord { } } +impl From<&ChunkCoord<2>> for Vec2 { + fn from(value: &ChunkCoord<2>) -> Self { + Vec2::new(value.0[0] as f32, value.0[1] as f32) + } +} + /// Holds handles to all the tiles in a chunk. /// # Note /// Manually updating this value, adding it, or removing it from an entity may @@ -54,4 +63,9 @@ impl Chunk { pub fn total_size(&self) -> usize { self.tiles.len() } + + /// Gets a readonly reference to the underlying chunk data. + pub fn get_tiles(&self) -> &Vec> { + &self.tiles + } } diff --git a/crates/bevy_tiles/src/chunks/chunk_query.rs b/crates/bevy_tiles/src/chunks/chunk_query.rs index 1bf2d84..497ce2a 100644 --- a/crates/bevy_tiles/src/chunks/chunk_query.rs +++ b/crates/bevy_tiles/src/chunks/chunk_query.rs @@ -12,7 +12,7 @@ use bevy::{ use crate::{ chunks::ChunkCoord, - maps::{MapLabel, TileMapLabel, Tilemap}, + maps::{MapLabel, TileMap, TileMapLabel}, prelude::{calculate_chunk_coordinate, Chunk, CoordIterator, InMap}, }; @@ -27,7 +27,7 @@ where F: ReadOnlyWorldQuery + 'static, { chunk_q: Query<'w, 's, Q, (F, Relations, With, With>)>, - map_q: Query<'w, 's, &'static Tilemap, With>>, + map_q: Query<'w, 's, &'static TileMap, With>>, } impl<'w, 's, L, Q, F, const N: usize> Deref for ChunkQuery<'w, 's, L, Q, F, N> diff --git a/crates/bevy_tiles/src/commands.rs b/crates/bevy_tiles/src/commands.rs index a9f237b..23929a6 100644 --- a/crates/bevy_tiles/src/commands.rs +++ b/crates/bevy_tiles/src/commands.rs @@ -8,8 +8,8 @@ use std::{ use crate::{ maps::MapLabel, prelude::{ - calculate_chunk_coordinate, calculate_tile_index, Chunk, ChunkCoord, InMap, TileMapLabel, - Tilemap, + calculate_chunk_coordinate, calculate_tile_index, Chunk, ChunkCoord, InMap, TileMap, + TileMapLabel, }, tiles::{InChunk, TileCoord, TileIndex}, }; @@ -228,6 +228,19 @@ where }); } + /// Spawn a new map, overwriting any old maps found. + pub fn spawn_map(&mut self, bundle: T) -> EntityCommands<'w, 's, '_> + where + T: Bundle + 'static, + { + let map_id = self.spawn(bundle).id(); + self.add(SpawnMap:: { + map_id, + label: PhantomData, + }); + self.entity(map_id) + } + /// Recursively despawns a map and all it's chunks and tiles. pub fn despawn_map(&mut self) -> &mut Self { self.add(DespawnMap:: { label: PhantomData }); @@ -240,7 +253,7 @@ where #[inline] fn spawn_or_remove_chunk( world: &mut World, - map: &mut Tilemap, + map: &mut TileMap, map_id: Entity, chunk_c: [isize; N], ) -> (Entity, Chunk) @@ -261,7 +274,7 @@ where #[inline] fn remove_chunk( world: &mut World, - map: &mut Tilemap, + map: &mut TileMap, chunk_c: [isize; N], ) -> Option<(Entity, Chunk)> { map.chunks @@ -273,7 +286,7 @@ fn remove_chunk( /// Takes the map out of the world or spawns a new one and returns the entity id to return the map to. #[inline] -fn spawn_or_remove_map(world: &mut World) -> (Entity, Tilemap) +fn spawn_or_remove_map(world: &mut World) -> (Entity, TileMap) where L: TileMapLabel + Send + 'static, { @@ -283,19 +296,19 @@ where } else { ( world.spawn(MapLabel::::default()).id(), - Tilemap::::default(), + TileMap::::with_chunk_size(L::CHUNK_SIZE), ) } } /// Takes the map out of the world if it exists. #[inline] -fn remove_map(world: &mut World) -> Option<(Entity, Tilemap)> +fn remove_map(world: &mut World) -> Option<(Entity, TileMap)> where L: TileMapLabel + Send + 'static, { world - .query_filtered::>, With>)>() + .query_filtered::>, With>)>() .get_single_mut(world) .ok() .map(|map_id| { @@ -304,7 +317,7 @@ where world .get_entity_mut(map_id) .unwrap() - .take::>() + .take::>() .unwrap(), ) }) @@ -685,6 +698,24 @@ where chunk_ids } +/// Insert the given entity and have it be treated as the given map. +/// # Note +/// This will despawn any existing map with this label. +pub fn insert_map(world: &mut World, map_id: Entity) +where + L: TileMapLabel + Send + 'static, +{ + let map_info = remove_map::(world); + if let Some(map_info) = map_info { + CheckedDespawn(map_info.0).apply(world); + } else { + world.entity_mut(map_id).insert(( + MapLabel::::default(), + TileMap::::with_chunk_size(L::CHUNK_SIZE), + )); + } +} + trait GroupBy: Iterator { fn group_by( self, diff --git a/crates/bevy_tiles/src/commands/map.rs b/crates/bevy_tiles/src/commands/map.rs index 9343b26..bfa6f3a 100644 --- a/crates/bevy_tiles/src/commands/map.rs +++ b/crates/bevy_tiles/src/commands/map.rs @@ -2,10 +2,26 @@ use aery::edges::CheckedDespawn; use bevy::ecs::{entity::Entity, query::With, system::Command, world::World}; use crate::{ - maps::{MapLabel, Tilemap}, + maps::{MapLabel, TileMap}, prelude::TileMapLabel, }; +use super::insert_map; + +pub struct SpawnMap { + pub map_id: Entity, + pub label: std::marker::PhantomData, +} + +impl Command for SpawnMap +where + L: TileMapLabel + 'static, +{ + fn apply(self, world: &mut World) { + insert_map::(world, self.map_id) + } +} + pub struct DespawnMap { pub label: std::marker::PhantomData, } @@ -16,7 +32,7 @@ where { fn apply(self, world: &mut World) { if let Ok(map_id) = world - .query_filtered::>, With>)>() + .query_filtered::>, With>)>() .get_single(world) { CheckedDespawn(map_id).apply(world); diff --git a/crates/bevy_tiles/src/maps.rs b/crates/bevy_tiles/src/maps.rs index a63f754..779518a 100644 --- a/crates/bevy_tiles/src/maps.rs +++ b/crates/bevy_tiles/src/maps.rs @@ -31,14 +31,17 @@ impl Default for MapLabel { /// Manually updating this value, adding it, or removing it from an entity may /// cause issues, please only mutate map information via commands. #[derive(Component)] -pub struct Tilemap { +pub struct TileMap { pub(crate) chunks: HashMap, Entity>, + /// The size of a chunk in one direction. + pub chunk_size: usize, } -impl Default for Tilemap { - fn default() -> Self { +impl TileMap { + pub(crate) fn with_chunk_size(chunk_size: usize) -> Self { Self { chunks: Default::default(), + chunk_size, } } } diff --git a/crates/bevy_tiles/src/tiles/tile_query.rs b/crates/bevy_tiles/src/tiles/tile_query.rs index 896274d..663cc05 100644 --- a/crates/bevy_tiles/src/tiles/tile_query.rs +++ b/crates/bevy_tiles/src/tiles/tile_query.rs @@ -11,7 +11,7 @@ use bevy::{ use crate::{ chunks::{Chunk, ChunkCoord, InMap}, - maps::{MapLabel, TileMapLabel, Tilemap}, + maps::{MapLabel, TileMap, TileMapLabel}, prelude::{ calculate_chunk_coordinate, calculate_tile_coordinate, calculate_tile_index, max_tile_index, CoordIterator, @@ -32,7 +32,7 @@ where { tile_q: Query<'w, 's, Q, (F, Relations, With>)>, chunk_q: Query<'w, 's, &'static Chunk, (Relations, With>)>, - map_q: Query<'w, 's, &'static Tilemap, With>>, + map_q: Query<'w, 's, &'static TileMap, With>>, } impl<'w, 's, L, Q, F, const N: usize> Deref for TileQuery<'w, 's, L, Q, F, N> diff --git a/crates/bevy_tiles_render/examples/hello_tile.rs b/crates/bevy_tiles_render/examples/hello_tile.rs index 80bba00..9fea8f7 100644 --- a/crates/bevy_tiles_render/examples/hello_tile.rs +++ b/crates/bevy_tiles_render/examples/hello_tile.rs @@ -6,8 +6,11 @@ use bevy::{ transform::{components::Transform, TransformBundle}, DefaultPlugins, }; -use bevy_tiles::TilesPlugin; -use bevy_tiles_render::{settings::ChunkRenderSettings, tiles::TriThing, TilesRenderPlugin}; +use bevy_tiles::{commands::TileCommandExt, maps::TileMapLabel, TilesPlugin}; +use bevy_tiles_render::{ + maps::{TileMapRenderer, TileMapRenderingBundle}, + TilesRenderPlugin, +}; fn main() { App::new() @@ -17,19 +20,23 @@ fn main() { .run(); } +struct GameLayer; + +impl TileMapLabel for GameLayer { + const CHUNK_SIZE: usize = 16; +} + fn spawn(mut commands: Commands) { - for i in -5..5 { - commands.spawn(( - TransformBundle { - local: Transform { - translation: Vec3::new(0.0, 64.0 * i as f32, 0.0), - ..Default::default() - }, - ..Default::default() - }, - TriThing, - )); + let mut tile_commands = commands.tiles::(); + tile_commands.spawn_map(TileMapRenderingBundle::default()); + + for i in -10..10 { + tile_commands.spawn_tile([0, 10 * i], ()); + } + for i in -10..10 { + tile_commands.spawn_tile([10 * i, 0], ()); } + commands.spawn(Camera2dBundle { ..Default::default() }); diff --git a/crates/bevy_tiles_render/src/bindings.rs b/crates/bevy_tiles_render/src/bindings.rs index 280af79..e6aa5e9 100644 --- a/crates/bevy_tiles_render/src/bindings.rs +++ b/crates/bevy_tiles_render/src/bindings.rs @@ -1,17 +1,59 @@ +use std::num::NonZeroU64; + use bevy::{ - ecs::world::FromWorld, - math::{Affine3, Vec4}, + ecs::{component::Component, world::FromWorld}, + math::{Affine3, Vec2, Vec4}, render::{ - render_resource::{BindGroupLayout, BindGroupLayoutDescriptor, ShaderStages, ShaderType}, - renderer::RenderDevice, + render_resource::{ + BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, + BindingType, Buffer, BufferBindingType, BufferInitDescriptor, BufferUsages, ShaderSize, + ShaderStages, ShaderType, UniformBuffer, + }, + renderer::{RenderDevice, RenderQueue}, }, transform::components::GlobalTransform, }; use crate::buffer_helpers::*; +#[derive(Component)] +pub struct ChunkBatchBindGroups { + pub map_bind_group: BindGroup, + pub chunk_bind_group: BindGroup, +} + +#[derive(Component)] +pub struct MapChunkSizeBuffer(pub UniformBuffer); + +impl MapChunkSizeBuffer { + pub fn new(map_chunk_size: &u32) -> Self { + let mut map_chunk_size_buffer = UniformBuffer::::default(); + map_chunk_size_buffer.set(*map_chunk_size); + Self(map_chunk_size_buffer) + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.0.write_buffer(device, queue); + } +} + +#[derive(Component)] +pub struct MapTransformUniformBuffer(pub UniformBuffer); + +impl MapTransformUniformBuffer { + pub fn new(map_transform: &MapTransformUniform) -> Self { + let mut map_transform_buffer = UniformBuffer::::default(); + map_transform_buffer.set(map_transform.clone()); + Self(map_transform_buffer) + } + + pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) { + self.0.write_buffer(device, queue); + } +} + #[derive(ShaderType, Clone, Default)] -pub struct TilesChunkModelUniform { +pub struct MapTransformUniform { // Affine 4x3 matrix transposed to 3x4 pub transform: [Vec4; 3], // 3x3 matrix packed in mat2x4 and f32 as: @@ -22,7 +64,7 @@ pub struct TilesChunkModelUniform { pub inverse_transpose_model_b: f32, } -impl From<&GlobalTransform> for TilesChunkModelUniform { +impl From<&GlobalTransform> for MapTransformUniform { fn from(value: &GlobalTransform) -> Self { let affine = Affine3::from(&value.affine()); let (inverse_transpose_model_a, inverse_transpose_model_b) = affine.inverse_transpose_3x3(); @@ -34,27 +76,54 @@ impl From<&GlobalTransform> for TilesChunkModelUniform { } } -pub struct TilesChunkBindGroupLayouts { - pub model_layout: BindGroupLayout, +pub struct ChunkBatchBindGroupLayouts { + pub map_layouts: BindGroupLayout, + pub chunk_layouts: BindGroupLayout, } -impl FromWorld for TilesChunkBindGroupLayouts { +impl FromWorld for ChunkBatchBindGroupLayouts { fn from_world(world: &mut bevy::prelude::World) -> Self { let device = world .get_resource::() .expect("No render device found!"); - let mesh_layout = device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("bevy_tiles_mesh_bindings"), - entries: &[GpuStorageBuffer::::binding_layout( + let map_layouts = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bevy_tiles_map_bind_group"), + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(MapTransformUniform::SHADER_SIZE), + }, + count: None, + }, + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::VERTEX_FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: Some(u32::SHADER_SIZE), + }, + count: None, + }, + ], + }); + + let chunk_layouts = device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("bevy_tiles_chunk_bind_group"), + entries: &[GpuStorageBuffer::::binding_layout( 0, ShaderStages::VERTEX_FRAGMENT, - device, )], }); Self { - model_layout: mesh_layout, + map_layouts, + chunk_layouts, } } } diff --git a/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs b/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs index 92c83cc..01d91cb 100644 --- a/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs +++ b/crates/bevy_tiles_render/src/buffer_helpers/gpu_storage_buffer.rs @@ -45,11 +45,7 @@ impl GpuStorageBuffer { self.gpu_buffer.write_buffer(device, queue); } - pub fn binding_layout( - binding: u32, - visibility: ShaderStages, - device: &RenderDevice, - ) -> BindGroupLayoutEntry { + pub fn binding_layout(binding: u32, visibility: ShaderStages) -> BindGroupLayoutEntry { BindGroupLayoutEntry { binding, visibility, diff --git a/crates/bevy_tiles_render/src/chunk/internal.rs b/crates/bevy_tiles_render/src/chunk/internal.rs index d1a43b6..d712892 100644 --- a/crates/bevy_tiles_render/src/chunk/internal.rs +++ b/crates/bevy_tiles_render/src/chunk/internal.rs @@ -7,8 +7,13 @@ use bevy::{ pub struct TileInstances(Vec); #[derive(Component, Deref)] -pub struct BatchSize(pub usize); +pub struct BatchSize(pub u32); -#[derive(Component, Deref, Clone)] /// Holds a reference to the batch this chunk is in +#[derive(Component, Deref, Clone)] pub struct ChunkBatch(pub Entity); + +/// Holds a vector the size of the chunk that indicates whether +/// a tile exists at that index in the chunk or not. +#[derive(Component, Deref)] +pub struct ExtractedTileInstances(pub Vec); diff --git a/crates/bevy_tiles_render/src/chunk/mod.rs b/crates/bevy_tiles_render/src/chunk/mod.rs index de803c5..ef77e6c 100644 --- a/crates/bevy_tiles_render/src/chunk/mod.rs +++ b/crates/bevy_tiles_render/src/chunk/mod.rs @@ -1,4 +1,4 @@ -//! Components that can effect chunk rendering +//! Components that can affect chunk rendering /// Components used by rendering pipeline pub(crate) mod internal; diff --git a/crates/bevy_tiles_render/src/draw.rs b/crates/bevy_tiles_render/src/draw.rs index b01ea1a..ef72acb 100644 --- a/crates/bevy_tiles_render/src/draw.rs +++ b/crates/bevy_tiles_render/src/draw.rs @@ -17,15 +17,52 @@ use bevy::{ sprite::SetMesh2dViewBindGroup, }; -use crate::{chunk::internal::BatchSize, queue::ChunkBatchBindGroups}; +use crate::{ + bindings::ChunkBatchBindGroups, chunk::internal::BatchSize, maps::internal::ChunkSize, +}; pub type DrawChunks = ( SetItemPipeline, SetMesh2dViewBindGroup<0>, - SetChunkBindGroup<1>, + SetMapBindGroup<1>, + SetChunkBindGroup<2>, DrawChunkBatch, ); +pub struct SetMapBindGroup; +impl RenderCommand for SetMapBindGroup { + type Param = (); + + type ViewWorldQuery = (); + + type ItemWorldQuery = Read; + + #[inline] + fn render<'w>( + item: &Transparent2d, + _view: (), + bind_groups: ROQueryItem<'w, Self::ItemWorldQuery>, + _: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + debug!("Setting Chunk Map Level Bind Groups"); + + let mut dynamic_offsets: [u32; 1] = Default::default(); + let mut offset_count = 0; + if let Some(dynamic_offset) = item.dynamic_offset() { + dynamic_offsets[offset_count] = dynamic_offset.get(); + offset_count += 1; + } + + pass.set_bind_group( + I, + &bind_groups.map_bind_group, + &dynamic_offsets[..offset_count], + ); + RenderCommandResult::Success + } +} + pub struct SetChunkBindGroup; impl RenderCommand for SetChunkBindGroup { type Param = (); @@ -42,7 +79,7 @@ impl RenderCommand for SetChunkBindGroup { _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - debug!("Setting Chunk Bind Groups"); + debug!("Setting Chunk Level Bind Groups"); let mut dynamic_offsets: [u32; 1] = Default::default(); let mut offset_count = 0; @@ -53,7 +90,7 @@ impl RenderCommand for SetChunkBindGroup { pass.set_bind_group( I, - &bind_groups.model_bind_group, + &bind_groups.chunk_bind_group, &dynamic_offsets[..offset_count], ); RenderCommandResult::Success @@ -66,17 +103,17 @@ impl RenderCommand for DrawChunkBatch { type ViewWorldQuery = (); - type ItemWorldQuery = Read; + type ItemWorldQuery = (Read, Read); #[inline] fn render<'w>( item: &Transparent2d, _view: ROQueryItem<'w, Self::ViewWorldQuery>, - batch_size: ROQueryItem<'w, Self::ItemWorldQuery>, + (chunk_size, batch_size): ROQueryItem<'w, Self::ItemWorldQuery>, _: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - pass.draw(0..102, 0..(**batch_size) as u32); + pass.draw(0..(chunk_size.0 * chunk_size.0 * 6), 0..**batch_size); RenderCommandResult::Success } } diff --git a/crates/bevy_tiles_render/src/extract.rs b/crates/bevy_tiles_render/src/extract.rs index 6f33e75..a7b4117 100644 --- a/crates/bevy_tiles_render/src/extract.rs +++ b/crates/bevy_tiles_render/src/extract.rs @@ -1,22 +1,90 @@ +use aery::edges::{EdgeInfo, Edges, Set}; use bevy::{ ecs::{ entity::Entity, - system::{Commands, Query, ResMut}, + query::With, + system::{Commands, Query}, }, - log::{debug, info}, - prelude::SpatialBundle, + log::debug, render::Extract, - sprite::{Mesh2d, RenderMesh2dInstance, RenderMesh2dInstances}, transform::components::GlobalTransform, }; +use bevy_tiles::{ + chunks::{Chunk, ChunkCoord, InMap}, + maps::TileMap, + tiles::TileIndex, +}; -use crate::tiles::TriThing; +use crate::{ + chunk::internal::ExtractedTileInstances, + maps::{internal::ChunkSize, TileGridSize, TileMapRenderer, TileSize}, +}; pub fn extract_chunks( mut commands: Commands, - tri_things: Extract>, + maps: Extract< + Query<( + Entity, + &TileMap, + &TileMapRenderer, + Option<&GlobalTransform>, + Option<&TileSize>, + Option<&TileGridSize>, + )>, + >, + chunks: Extract, &Chunk, &ChunkCoord)>>, + tiles: Extract>>, ) { - let tri_things = Vec::from_iter(tri_things.iter().map(|(e, gt, tt)| (e, (*gt, *tt)))); - debug!("Extracted {:?} Chunks", tri_things.len()); - commands.insert_or_spawn_batch(tri_things); + let mut extracted_maps = Vec::with_capacity(maps.iter().len()); + for (map_id, map, renderer, transform, tile_size, grid_size) in maps.iter() { + let transform = transform.cloned().unwrap_or_default(); + let tile_size = tile_size.cloned().unwrap_or_default(); + let grid_size = grid_size.cloned().unwrap_or_default(); + extracted_maps.push(( + map_id, + ( + transform, + ChunkSize(map.chunk_size as u32), + renderer.clone(), + tile_size, + grid_size, + ), + )); + } + commands.insert_or_spawn_batch(extracted_maps); + + let mut extracted_chunks = Vec::with_capacity(chunks.iter().len()); + let mut chunk_edges = Vec::with_capacity(chunks.iter().len()); + + for (chunk_id, edges, chunk, chunk_coord) in chunks.iter() { + let [map_id] = edges.targets() else { + panic!("Missing map reference on chunk!"); + }; + + let mut extracted_tile_instances = Vec::with_capacity(chunk.total_size()); + + for tile in chunk.get_tiles() { + if let Some(()) = tile.and_then(|tile_id| tiles.get(tile_id).ok()) { + extracted_tile_instances.push(true); + } else { + extracted_tile_instances.push(false); + } + } + + chunk_edges.push((chunk_id, *map_id)); + + extracted_chunks.push(( + chunk_id, + ( + ExtractedTileInstances(extracted_tile_instances), + *chunk_coord, + ), + )); + } + + commands.insert_or_spawn_batch(extracted_chunks); + + for (chunk_id, map_id) in chunk_edges.into_iter() { + commands.add(Set::::new(chunk_id, map_id)); + } } diff --git a/crates/bevy_tiles_render/src/lib.rs b/crates/bevy_tiles_render/src/lib.rs index c6e7b63..3318cd8 100644 --- a/crates/bevy_tiles_render/src/lib.rs +++ b/crates/bevy_tiles_render/src/lib.rs @@ -1,3 +1,4 @@ +use aery::Aery; use bevy::{ app::Plugin, asset::{load_internal_asset, Handle}, @@ -16,22 +17,21 @@ use prepare::{create_bind_groups, prepare_chunk_batch}; use queue::{create_chunk_batches, queue_chunks}; use crate::{ - bindings::TilesChunkModelUniform, + bindings::MapTransformUniform, buffer_helpers::gpu_storage_buffer::GpuStorageBuffer, draw::{DrawChunkBatch, DrawChunks}, pipeline::TilesChunkPipeline, - settings::ChunkRenderSettings, }; mod bindings; mod buffer_helpers; -mod chunk; +pub mod chunk; mod draw; mod extract; +pub mod maps; mod pipeline; mod prepare; mod queue; -pub mod settings; pub mod tiles; mod tiles_render; @@ -43,6 +43,7 @@ pub struct TilesRenderPlugin; impl Plugin for TilesRenderPlugin { fn build(&self, app: &mut bevy::prelude::App) { let render_app = app.get_sub_app_mut(RenderApp).expect("No RenderApp found!"); + render_app.add_plugins(Aery); // Respawn chunks that we saved from the last frame // Copy over tile data @@ -66,11 +67,8 @@ impl Plugin for TilesRenderPlugin { let render_app = app.get_sub_app_mut(RenderApp).expect("No RenderApp found!"); render_app.add_render_command::(); - render_app.insert_resource(ChunkRenderSettings { - max_chunk_batch_size: 4, - }); render_app - .init_resource::>() + .init_resource::>() .init_resource::() .init_resource::>(); diff --git a/crates/bevy_tiles_render/src/maps/internal.rs b/crates/bevy_tiles_render/src/maps/internal.rs new file mode 100644 index 0000000..fc89607 --- /dev/null +++ b/crates/bevy_tiles_render/src/maps/internal.rs @@ -0,0 +1,4 @@ +use bevy::{ecs::component::Component, prelude::Deref}; + +#[derive(Deref, Clone, Default, Component)] +pub struct ChunkSize(pub u32); diff --git a/crates/bevy_tiles_render/src/maps/mod.rs b/crates/bevy_tiles_render/src/maps/mod.rs new file mode 100644 index 0000000..db302b4 --- /dev/null +++ b/crates/bevy_tiles_render/src/maps/mod.rs @@ -0,0 +1,53 @@ +//! Components that can affect map rendering + +use bevy::{ + ecs::{bundle::Bundle, component::Component}, + prelude::Deref, + transform::components::GlobalTransform, +}; + +pub(crate) mod internal; + +#[derive(Bundle, Default)] +pub struct TileMapRenderingBundle { + pub tile_map_renderer: TileMapRenderer, + pub tile_size: TileSize, + pub grid_size: TileGridSize, + pub transform: GlobalTransform, +} + +/// Marks a tilemap as renderable, without this it cannot be rendered. +#[derive(Clone, Component)] +pub struct TileMapRenderer { + pub batch_size: u32, +} + +impl Default for TileMapRenderer { + fn default() -> Self { + Self { batch_size: 128 } + } +} + +/// The size of a tile in pixels. +#[derive(Clone, Deref, Component)] +pub struct TileSize(pub u32); + +/// Defaults to 16 pixels +impl Default for TileSize { + fn default() -> Self { + Self(16) + } +} +/// The size of a tile grid in pixels. +/// # Example +/// A [`TileSize`] of 16 with a [`GridSize`] of 18 would lead to a 2 pixel gap between tiles. +/// A [`TileSize`] of 16 with a [`GridSize`] of 14 would lead to a 2 pixel overlap between tiles. +#[derive(Clone, Deref, Component)] +pub struct TileGridSize(pub u32); + +/// Defaults to 16 pixels +impl Default for TileGridSize { + fn default() -> Self { + Self(16) + } +} diff --git a/crates/bevy_tiles_render/src/pipeline.rs b/crates/bevy_tiles_render/src/pipeline.rs index d5075fe..30cdb0d 100644 --- a/crates/bevy_tiles_render/src/pipeline.rs +++ b/crates/bevy_tiles_render/src/pipeline.rs @@ -18,21 +18,21 @@ use bevy::{ }; use crate::{ - bindings::{TilesChunkBindGroupLayouts, TilesChunkModelUniform}, + bindings::{ChunkBatchBindGroupLayouts, MapTransformUniform}, TILES_FRAG, TILES_VERT, }; #[derive(Resource)] pub struct TilesChunkPipeline { pub mesh2d_pipeline: Mesh2dPipeline, - pub tiles_bind_groups: TilesChunkBindGroupLayouts, + pub chunk_batch_bind_groups: ChunkBatchBindGroupLayouts, } impl FromWorld for TilesChunkPipeline { fn from_world(world: &mut World) -> Self { Self { mesh2d_pipeline: Mesh2dPipeline::from_world(world), - tiles_bind_groups: TilesChunkBindGroupLayouts::from_world(world), + chunk_batch_bind_groups: ChunkBatchBindGroupLayouts::from_world(world), } } } @@ -74,8 +74,10 @@ impl SpecializedRenderPipeline for TilesChunkPipeline { layout: vec![ // Bind group 0 is the view uniform self.mesh2d_pipeline.view_layout.clone(), - // Bind group 1 is the mesh uniforms for all the chunks - self.tiles_bind_groups.model_layout.clone(), + // Bind group 1 are the map components + self.chunk_batch_bind_groups.map_layouts.clone(), + // Bind group 2 are the chunk components + self.chunk_batch_bind_groups.chunk_layouts.clone(), ], push_constant_ranges: Vec::new(), primitive: PrimitiveState { diff --git a/crates/bevy_tiles_render/src/prepare.rs b/crates/bevy_tiles_render/src/prepare.rs index 12490c5..800d189 100644 --- a/crates/bevy_tiles_render/src/prepare.rs +++ b/crates/bevy_tiles_render/src/prepare.rs @@ -1,68 +1,90 @@ -use std::{ - iter::{once, repeat_with}, - mem::size_of, - num::{NonZeroU32, NonZeroU64}, - sync::atomic::{AtomicUsize, Ordering}, -}; +use std::sync::atomic::{AtomicUsize, Ordering}; use bevy::{ ecs::{ entity::Entity, - system::{Commands, ParallelCommands, Query, Res, Resource}, + system::{Commands, Query, Res}, }, - log::{debug, info}, + log::debug, + math::Vec2, render::{ - render_resource::{BindGroup, BindGroupEntries, GpuArrayBuffer}, + render_resource::{BindGroupEntries, BindGroupEntry}, renderer::{RenderDevice, RenderQueue}, }, transform::components::GlobalTransform, utils::hashbrown::HashMap, }; +use bevy_tiles::chunks::ChunkCoord; use crate::{ - bindings::TilesChunkModelUniform, + bindings::{ + ChunkBatchBindGroups, MapChunkSizeBuffer, MapTransformUniform, MapTransformUniformBuffer, + }, buffer_helpers::GpuStorageBuffer, - chunk::internal::{BatchSize, ChunkBatch, TileInstances}, + chunk::internal::{BatchSize, ChunkBatch, ExtractedTileInstances, TileInstances}, + maps::{internal::ChunkSize, TileGridSize, TileSize}, pipeline::TilesChunkPipeline, - queue::ChunkBatchBindGroups, - settings::ChunkRenderSettings, - tiles::TriThing, }; -pub type ChunkQueryItems<'w> = (&'w GlobalTransform, &'w TriThing); - -pub type ChunkBatchQueryItems<'w> = (&'w BatchSize, &'w GpuStorageBuffer); - +// Consolidate individual chunk information into the batch entities pub fn prepare_chunk_batch( mut commands: Commands, - render_settings: Res, device: Res, queue: Res, - chunks: Query<(&ChunkBatch, ChunkQueryItems)>, - chunk_batches: Query<(Entity, &BatchSize)>, + chunks: Query<(&ChunkBatch, &ChunkCoord)>, + chunk_batches: Query<( + Entity, + &BatchSize, + &ChunkSize, + &GlobalTransform, + &TileSize, + &TileGridSize, + )>, ) { - let mut instance_indices = HashMap::with_capacity(chunk_batches.iter().len()); - let mut transform_buffers = HashMap::with_capacity(chunk_batches.iter().len()); - // Create all our instance buffers before we start iterating over chunks - for (batch_id, batch_size) in chunk_batches.iter() { - debug!("Found batch {:?} with size {:?}", batch_id, **batch_size); + let batch_iter = chunk_batches.iter(); + let batch_count = batch_iter.len(); + + let mut instance_indices = HashMap::with_capacity(batch_count); + let mut transform_buffers = HashMap::with_capacity(batch_count); + let mut global_uniform_buffers = Vec::with_capacity(batch_count); + + if batch_iter.len() == 0 { + return; + } + + for (batch_id, batch_size, chunk_size, batch_transform, tile_size, grid_size) in batch_iter { + debug!( + "Preparing batch {:?} with size {:?}", + batch_id, **batch_size + ); + + // Create all our instance buffers before we start iterating over chunks instance_indices.insert(batch_id, AtomicUsize::new(0)); transform_buffers.insert( batch_id, - GpuStorageBuffer::::with_size(**batch_size), + GpuStorageBuffer::::with_size(**batch_size as usize), ); + + // Create all our global uniforms for the batches + let mut map_transform_buffer = + MapTransformUniformBuffer::new(&MapTransformUniform::from(batch_transform)); + map_transform_buffer.write_buffer(&device, &queue); + + let mut map_chunk_size_buffer = MapChunkSizeBuffer::new(chunk_size); + map_chunk_size_buffer.write_buffer(&device, &queue); + + global_uniform_buffers.push((batch_id, (map_transform_buffer, map_chunk_size_buffer))); } - chunks.par_iter().for_each(|(batch_id, chunk)| { + chunks.par_iter().for_each(|(batch_id, chunk_coord)| { let batch_index = instance_indices[&**batch_id].fetch_add(1, Ordering::Acquire); - prepare_chunk( + add_chunk_to_batch( **batch_id, batch_index, - &render_settings, &device, &queue, &transform_buffers, - chunk, + chunk_coord, ) }); @@ -70,45 +92,59 @@ pub fn prepare_chunk_batch( buffer.write_buffer(&device, &queue) } + commands.insert_or_spawn_batch(global_uniform_buffers); commands.insert_or_spawn_batch(transform_buffers); } #[inline] -fn prepare_chunk( +fn add_chunk_to_batch( batch: Entity, batch_index: usize, - render_settings: &ChunkRenderSettings, device: &RenderDevice, queue: &RenderQueue, - transform_buffers: &HashMap>, - (transform, instances): ChunkQueryItems, + transform_buffers: &HashMap>, + chunk_coord: &ChunkCoord, ) { // SAFETY: Each chunk can only write to one place because math - unsafe { transform_buffers[&batch].raw_insert(batch_index, transform.into()) }; + unsafe { transform_buffers[&batch].raw_insert(batch_index, Vec2::from(chunk_coord)) }; } pub fn create_bind_groups( mut commands: Commands, device: Res, chunk_pipeline: Res, - chunk_batches: Query<(Entity, ChunkBatchQueryItems)>, + chunk_batches: Query<( + Entity, + &MapTransformUniformBuffer, + &MapChunkSizeBuffer, + &GpuStorageBuffer, + )>, ) { // Create bind groups debug!( "Creating bind group for {} batches", chunk_batches.iter().len() ); - for (batch_id, (_, model_buffer)) in chunk_batches.iter() { - let binding = model_buffer.binding().expect("No model buffer found!"); + for (batch_id, map_transform, map_chunk_size_buffer, chunk_offsets) in chunk_batches.iter() { + let map_bind_group = device.create_bind_group( + "batch_map_bind_group", + &chunk_pipeline.chunk_batch_bind_groups.map_layouts, + &BindGroupEntries::with_indices(( + (0, map_transform.0.binding().unwrap()), + (1, map_chunk_size_buffer.0.binding().unwrap()), + )), + ); - let model_bind_group = device.create_bind_group( - "tiles_bind_group", - &chunk_pipeline.tiles_bind_groups.model_layout, - &BindGroupEntries::single(binding), + let chunk_bind_group = device.create_bind_group( + "batch_chunk_bind_group", + &chunk_pipeline.chunk_batch_bind_groups.chunk_layouts, + &BindGroupEntries::single(chunk_offsets.binding().unwrap()), ); - commands - .entity(batch_id) - .insert(ChunkBatchBindGroups { model_bind_group }); + debug!("Adding bind groups to batch {:?}", batch_id); + commands.entity(batch_id).insert(ChunkBatchBindGroups { + map_bind_group, + chunk_bind_group, + }); } } diff --git a/crates/bevy_tiles_render/src/queue.rs b/crates/bevy_tiles_render/src/queue.rs index deb7422..d32ef09 100644 --- a/crates/bevy_tiles_render/src/queue.rs +++ b/crates/bevy_tiles_render/src/queue.rs @@ -1,5 +1,7 @@ -use std::ops::Deref; - +use aery::{ + for_each::JoinForEach, + operations::{utils::Relations, Join}, +}; use bevy::{ core_pipeline::core_2d::Transparent2d, ecs::{ @@ -7,69 +9,104 @@ use bevy::{ entity::Entity, system::{Commands, Query, Res, ResMut}, }, - log::{debug, info}, + log::debug, render::{ render_phase::{DrawFunctions, RenderPhase}, render_resource::{ - BindGroup, BindGroupEntries, DynamicStorageBuffer, PipelineCache, PrimitiveTopology, - SpecializedRenderPipelines, + BindGroup, PipelineCache, PrimitiveTopology, SpecializedRenderPipelines, }, renderer::RenderDevice, view::{ExtractedView, Msaa}, }, sprite::Mesh2dPipelineKey, + transform::components::GlobalTransform, utils::FloatOrd, }; +use bevy_tiles::chunks::InMap; use crate::{ - chunk::internal::{BatchSize, ChunkBatch}, - draw::{DrawChunkBatch, DrawChunks}, + chunk::internal::{BatchSize, ChunkBatch, ExtractedTileInstances}, + draw::DrawChunks, + maps::{internal::ChunkSize, TileGridSize, TileMapRenderer, TileSize}, pipeline::TilesChunkPipeline, - prepare::{ChunkBatchQueryItems, ChunkQueryItems}, - settings::ChunkRenderSettings, }; -#[derive(Component)] -pub struct ChunkBatchBindGroups { - pub model_bind_group: BindGroup, -} - pub fn create_chunk_batches( mut commands: Commands, - render_settings: Res, - chunks: Query<(Entity, ChunkQueryItems)>, + maps: Query<( + ( + Entity, + &TileMapRenderer, + &ChunkSize, + &GlobalTransform, + &TileSize, + &TileGridSize, + ), + Relations, + )>, + chunks: Query<(Entity, &ExtractedTileInstances)>, ) { - let max_batch_size = render_settings.max_chunk_batch_size as usize; - let chunk_count = chunks.iter().len(); - let batch_count = chunk_count / max_batch_size - + if (chunk_count % max_batch_size) > 0 { - 1 - } else { - 0 - }; + for ((map_id, renderer, map_chunk_size, map_transform, tile_size, grid_size), edges) in + maps.iter() + { + let max_batch_size = renderer.batch_size; + let chunk_count = chunks.iter().len(); + let batch_count = chunk_count / max_batch_size as usize + + if (chunk_count % max_batch_size as usize) > 0 { + 1 + } else { + 0 + }; - if batch_count == 0 { - return; - } + if batch_count == 0 { + return; + } + + let mut batches = Vec::with_capacity(batch_count); + let mut batched_chunks = Vec::with_capacity(chunk_count); + + let mut batch_size = 0; + let mut current_batch = ChunkBatch(commands.spawn_empty().id()); - let mut batches = Vec::with_capacity(batch_count); - let mut batched_chunks = Vec::with_capacity(chunk_count); - let mut batch_size = 0; - let mut current_batch = ChunkBatch(commands.spawn_empty().id()); - for (chunk_id, _) in chunks.iter() { - if batch_size == max_batch_size { - batches.push((*current_batch, BatchSize(batch_size))); - batch_size = 0; - current_batch = ChunkBatch(commands.spawn_empty().id()); + edges + .join::(&chunks) + .for_each(|(chunk_id, extracted_tiles)| { + if batch_size == max_batch_size { + batches.push(( + *current_batch, + ( + BatchSize(batch_size), + *map_transform, + map_chunk_size.clone(), + renderer.clone(), + tile_size.clone(), + grid_size.clone(), + ), + )); + batch_size = 0; + current_batch = ChunkBatch(commands.spawn_empty().id()); + } + batched_chunks.push((chunk_id, current_batch.clone())); + batch_size += 1; + }); + + if batch_size > 0 { + batches.push(( + *current_batch, + ( + BatchSize(batch_size), + *map_transform, + map_chunk_size.clone(), + renderer.clone(), + tile_size.clone(), + grid_size.clone(), + ), + )); } - batched_chunks.push((chunk_id, current_batch.clone())); - batch_size += 1; - } - if batch_size > 0 { - batches.push((*current_batch, BatchSize(batch_size))); + + commands.insert_or_spawn_batch(batches); + commands.insert_or_spawn_batch(batched_chunks); } - commands.insert_or_spawn_batch(batches); - commands.insert_or_spawn_batch(batched_chunks); } pub fn queue_chunks( @@ -77,14 +114,18 @@ pub fn queue_chunks( mut pipelines: ResMut>, device: Res, chunk_pipeline: Res, - transparent_draw_functions: Res>, pipeline_cache: Res, msaa: Res, - render_settings: Res, + transparent_draw_functions: Res>, mut views: Query<(&mut RenderPhase, &ExtractedView)>, chunk_batches: Query<(Entity, &BatchSize)>, ) { for (mut transparent_phase, view) in &mut views { + let chunk_batch_iter = chunk_batches.iter(); + if chunk_batch_iter.len() == 0 { + continue; + } + let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples()) | Mesh2dPipelineKey::from_hdr(view.hdr) | Mesh2dPipelineKey::from_primitive_topology(PrimitiveTopology::TriangleList); @@ -92,7 +133,7 @@ pub fn queue_chunks( let draw_chunks = transparent_draw_functions.read().id::(); - for (batch_id, batch_size) in chunk_batches.iter() { + for (batch_id, batch_size) in chunk_batch_iter { debug!("Queuing draw call for batch: {:?}", batch_id); transparent_phase.add(Transparent2d { entity: batch_id, diff --git a/crates/bevy_tiles_render/src/settings.rs b/crates/bevy_tiles_render/src/settings.rs deleted file mode 100644 index 1b35ddd..0000000 --- a/crates/bevy_tiles_render/src/settings.rs +++ /dev/null @@ -1,14 +0,0 @@ -use bevy::ecs::system::Resource; - -#[derive(Resource)] -pub struct ChunkRenderSettings { - pub max_chunk_batch_size: u32, -} - -impl Default for ChunkRenderSettings { - fn default() -> Self { - Self { - max_chunk_batch_size: 128, - } - } -} diff --git a/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl b/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl index 8702a50..a4451d9 100644 --- a/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl +++ b/crates/bevy_tiles_render/src/shaders/tiles_vert.wgsl @@ -11,7 +11,12 @@ @group(0) @binding(1) var globals: Globals; -@group(1) @binding(0) var mesh: array; +// Map level uniforms +@group(1) @binding(0) var mesh: Mesh2d; +@group(1) @binding(1) var chunk_size: u32; + +// Chunk level uniforms +@group(2) @binding(0) var chunk_offsets: array>; // The structure of the vertex buffer is as specified in `specialize()` struct VertIn { @@ -25,6 +30,7 @@ struct VertOut { @location(1) chunk_index: u32, }; +// LUT for quad verts var positions: array, 6> = array, 6>( vec2(0.0, 0.0), vec2(1.0, 0.0), @@ -43,9 +49,10 @@ fn vs_main(v: VertIn) -> VertOut { f32(tile_index % 16u), f32((tile_index / 16u) % 16u) ); - let vertex_position = 16.0 * (positions[v.vertex_index % 6u] + tile_offset); + let chunk_offset = f32(chunk_size) * chunk_offsets[chunk_index]; + let vertex_position = 16.0 * (positions[v.vertex_index % 6u] + tile_offset + chunk_offset); - let model = get_model_matrix(v.instance_index); + let model = affine_to_square(mesh.model); let clip_position = mesh2d_position_local_to_clip(model, vec4(vertex_position, 1.0, 1.0)); var out: VertOut; @@ -62,9 +69,6 @@ struct Mesh2d { flags: u32, }; -fn get_model_matrix(instance_index: u32) -> mat4x4 { - return affine_to_square(mesh[get_instance_index(instance_index)].model); -} fn mesh2d_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { return model * vertex_position; @@ -82,13 +86,6 @@ fn mesh2d_position_local_to_clip(model: mat4x4, vertex_position: vec4) return mesh2d_position_world_to_clip(world_position); } -fn mesh2d_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> vec3 { - return mat2x4_f32_to_mat3x3_unpack( - mesh[get_instance_index(instance_index)].inverse_transpose_model_a, - mesh[get_instance_index(instance_index)].inverse_transpose_model_b, - ) * vertex_normal; -} - fn mesh2d_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { return vec4( mat3x3( diff --git a/crates/bevy_tiles_render/src/tiles.rs b/crates/bevy_tiles_render/src/tiles.rs index 34e0e7b..f14141e 100644 --- a/crates/bevy_tiles_render/src/tiles.rs +++ b/crates/bevy_tiles_render/src/tiles.rs @@ -1,4 +1 @@ use bevy::ecs::component::Component; - -#[derive(Component, Clone, Copy)] -pub struct TriThing;