diff --git a/.vscode/bevy_gaussian_splatting.code-workspace b/.vscode/bevy_gaussian_splatting.code-workspace deleted file mode 100644 index fbf610a..0000000 --- a/.vscode/bevy_gaussian_splatting.code-workspace +++ /dev/null @@ -1,13 +0,0 @@ -{ - "folders": [ - { - "path": "../" - }, - { - "path": "../../bevy" - } - ], - "settings": { - "liveServer.settings.multiRootWorkspaceName": "bevy_gaussian_splatting" - } -} \ No newline at end of file diff --git a/.vscode/bevy_interleave.code-workspace b/.vscode/bevy_interleave.code-workspace new file mode 100644 index 0000000..5101809 --- /dev/null +++ b/.vscode/bevy_interleave.code-workspace @@ -0,0 +1,18 @@ +{ + "folders": [ + { + "path": ".." + }, + { + "path": "../crates/bevy_interleave_interface" + }, + { + "path": "../crates/bevy_interleave_macros" + } + ], + "settings": { + "rust-analyzer.linkedProjects": [ + "./Cargo.toml" + ] + } +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 753b8c3..9aa80dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "bevy_interleave" description = "bevy support for e2e packed to planar bind groups" -version = "0.2.1" -edition = "2021" +version = "0.8.0" +edition = "2024" authors = ["mosure "] license = "MIT" keywords = [ @@ -29,15 +29,29 @@ members = [ [dependencies] -bevy_interleave_interface = { path = "crates/bevy_interleave_interface", version = "0.2.1" } -bevy_interleave_macros = { path = "crates/bevy_interleave_macros", version = "0.2.1" } -bytemuck = "1.14" +bevy_interleave_interface = { path = "crates/bevy_interleave_interface", version = "0.8" } +bevy_interleave_macros = { path = "crates/bevy_interleave_macros", version = "0.8" } +bytemuck = "1.23" serde = "1.0" +wgpu = "26" [dependencies.bevy] -version = "0.13" +version = "0.17" default-features = false -features = ["bevy_asset", "bevy_render"] +features = ["bevy_asset", "bevy_render", "png", "reflect_documentation", "reflect_functions"] + + +[dev-dependencies.bevy] +version = "0.17" +default-features = false +features = [ + "bevy_asset", + "bevy_render", + "bevy_winit", + "png", + "reflect_documentation", + "reflect_functions", +] [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -77,3 +91,7 @@ codegen-units = 1 [lib] path = "src/lib.rs" + +[[test]] +name = "lib" +path = "test/lib.rs" diff --git a/README.md b/README.md index 24b0ba0..75fb4dc 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,29 @@ [![GitHub Issues](https://img.shields.io/github/issues/mosure/bevy_interleave)](https://github.com/mosure/bevy_interleave/issues) [![crates.io](https://img.shields.io/crates/v/bevy_interleave.svg)](https://crates.io/crates/bevy_interleave) -bevy support for e2e packed to planar bind groups +bevy static bind group api (e.g. statically typed meshes) +## features + +- [x] storage/texture bind group automation +- [x] packed -> planar main world representation /w serialization +- [x] packed -> planar storage/texture GPU representation +- [x] derive macro automation + ## minimal example ```rust +use bevy::prelude::*; use bevy_interleave::prelude::*; + #[derive( + Clone, Debug, + Default, Planar, + Reflect, ReflectInterleaved, StorageBindings, TextureBindings, @@ -34,16 +46,15 @@ pub struct MyStruct { pub array: [u32; 4], } -fn interleaved() -> Vec { - vec![ + +fn main() { + let interleaved = vec![ MyStruct { field: 0, field2: 1_u32, bool_field: true, array: [0, 1, 2, 3] }, MyStruct { field: 2, field2: 3_u32, bool_field: false, array: [4, 5, 6, 7] }, MyStruct { field: 4, field2: 5_u32, bool_field: true, array: [8, 9, 10, 11] }, ]; -} -fn main() { - let planar = PlanarMyStruct::from_interleaved(interleaved()); + let planar = PlanarMyStruct::from_interleaved(interleaved); println!("{:?}", planar.field); println!("{:?}", planar.field2); @@ -62,46 +73,10 @@ fn main() { // Prints: // [4, 4, 1, 16] // ["field", "field2", "bool_field", "array"] - - - let mut app = App::new() - .add_plugins(( - DefaultPlugins, - PlanarPlugin::::default(), - PlanarTexturePlugin::::default(), - )); - - app.sub_app_mut(bevy::render::RenderApp) - .add_systems( - bevy::render::Render, - check_bind_group.in_set(bevy::render::RenderSet::QueueMeshes), - ); - - app.run(); } -fn setup_planar_asset( - mut commands: Commands, - mut planar_assets: ResMut>, -) { - let planar = PlanarMyStruct::from_interleaved(interleaved()); - - commands.spawn(planar_assets.add(planar)); -} - -fn check_bind_group( - bind_group: Query<&PlanarTextureBindGroup::>, -) { - // attach bind group to render pipeline - // size: - // 2 x 2 x bpp - // format: - // binding: 0 - texture - R32Sint - depth 1 - // binding: 1 - texture - R32Uint - depth 1 - // binding: 2 - texture - R8Unorm - depth 1 - // binding: 3 - texture - Rgba32Uint - depth 1 -} +// TODO: gpu node binding example, see bevy_gaussian_splatting ``` @@ -114,5 +89,8 @@ fn check_bind_group( | `bevy_interleave` | `bevy` | | :-- | :-- | +| `0.8` | `0.17` | +| `0.7` | `0.16` | +| `0.3` | `0.15` | | `0.2` | `0.13` | | `0.1` | `0.12` | diff --git a/crates/bevy_interleave_interface/Cargo.toml b/crates/bevy_interleave_interface/Cargo.toml index 40f57a7..8fa6a7e 100644 --- a/crates/bevy_interleave_interface/Cargo.toml +++ b/crates/bevy_interleave_interface/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_interleave_interface" -version = "0.2.1" -edition = "2021" +version = "0.8.0" +edition = "2024" description = "interface for e2e packed to planar bind groups" homepage = "https://github.com/mosure/bevy_interleave" repository = "https://github.com/mosure/bevy_interleave" @@ -13,6 +13,6 @@ keywords = [ [dependencies.bevy] -version = "0.13" +version = "0.17" default-features = false -features = ["bevy_asset", "bevy_render"] +features = ["bevy_asset", "bevy_render", "png", "reflect_documentation", "reflect_functions"] diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index 02565cd..6ae84aa 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,11 +1,63 @@ -pub mod planar; -pub mod texture; +pub mod storage; +// pub mod texture; + + +pub trait PlanarHandle +where + Self: bevy::ecs::component::Component, + Self: Clone, + Self: Default, + Self: bevy::reflect::FromReflect, + Self: bevy::reflect::GetTypeRegistration, + Self: bevy::reflect::Reflect, + T: bevy::asset::Asset, +{ + fn handle(&self) -> &bevy::asset::Handle; +} + +// TODO: migrate to PlanarSync +pub trait PlanarSync +where + Self: Default, + Self: Send, + Self: Sync, + Self: bevy::reflect::Reflect, + Self: 'static, +{ + type PackedType; // Self + type PlanarType: Planar; + type PlanarTypeHandle: PlanarHandle; + type GpuPlanarType: GpuPlanar< + PackedType = Self::PackedType, + PlanarType = Self::PlanarType, + >; +} -pub trait PlanarStorage { + +pub trait GpuPlanar +where + Self: bevy::render::render_asset::RenderAsset, +{ type PackedType; type PlanarType; + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn len(&self) -> usize; +} + +// #[cfg(feature = "debug_gpu")] +// pub debug_gpu: PlanarType, +// TODO: when `debug_gpu` feature is enabled, add a function to access the main -> render world copied asset (for ease of test writing) +pub trait GpuPlanarStorage +where + Self: GpuPlanar, + Self: bevy::render::render_asset::RenderAsset, +{ + fn draw_indirect_buffer(&self) -> &bevy::render::render_resource::Buffer; + fn bind_group( &self, render_device: &bevy::render::renderer::RenderDevice, @@ -16,22 +68,19 @@ pub trait PlanarStorage { render_device: &bevy::render::renderer::RenderDevice, read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout; - - fn prepare( - render_device: &bevy::render::renderer::RenderDevice, - planar: &Self::PlanarType, - ) -> Self; } -pub trait PlanarTexture { - type PackedType; - type PlanarType; +pub trait GpuPlanarTexture +where + Self: GpuPlanar, + Self: bevy::render::render_asset::RenderAsset, +{ fn bind_group( &self, render_device: &bevy::render::renderer::RenderDevice, - gpu_images: &bevy::render::render_asset::RenderAssets, + gpu_images: &bevy::render::render_asset::RenderAssets, layout: &bevy::render::render_resource::BindGroupLayout, ) -> bevy::render::render_resource::BindGroup; @@ -39,15 +88,26 @@ pub trait PlanarTexture { render_device: &bevy::render::renderer::RenderDevice, ) -> bevy::render::render_resource::BindGroupLayout; + fn get_asset_handles(&self) -> Vec>; +} + + +// TODO: find a better name, PlanarTexture is implemented on the packed type +pub trait PlanarTexture +where + Self: PlanarSync, +{ + // note: planar texture's gpu type utilizes bevy's image render asset fn prepare( - images: &mut bevy::asset::Assets, + images: &mut bevy::asset::Assets, planar: &Self::PlanarType, - ) -> Self; + ) -> Self::GpuPlanarType; - fn get_asset_handles(&self) -> Vec>; + fn get_asset_handles(&self) -> Vec>; } + pub trait ReflectInterleaved { type PackedType; @@ -56,14 +116,23 @@ pub trait ReflectInterleaved { } -pub trait Planar { +pub trait Planar +where + Self: bevy::asset::Asset, + Self: bevy::reflect::GetTypeRegistration, + Self: bevy::reflect::FromReflect, +{ type PackedType; fn get(&self, index: usize) -> Self::PackedType; - fn is_empty(&self) -> bool; + fn is_empty(&self) -> bool { + self.len() == 0 + } fn len(&self) -> usize; fn set(&mut self, index: usize, value: Self::PackedType); fn to_interleaved(&self) -> Vec; fn from_interleaved(packed: Vec) -> Self where Self: Sized; + + fn subset(&self, indices: &[usize]) -> Self; } diff --git a/crates/bevy_interleave_interface/src/planar.rs b/crates/bevy_interleave_interface/src/planar.rs deleted file mode 100644 index f3f1c45..0000000 --- a/crates/bevy_interleave_interface/src/planar.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::marker::PhantomData; - -use bevy::{ - prelude::*, - reflect::GetTypeRegistration, -}; - -use crate::Planar; - - -pub struct PlanarPlugin { - phantom: PhantomData R>, -} -impl Default for PlanarPlugin { - fn default() -> Self { - Self { - phantom: PhantomData, - } - } -} - -impl Plugin for PlanarPlugin -where - R: Planar + Default + Asset + GetTypeRegistration + Clone + Reflect + FromReflect, -{ - fn build(&self, app: &mut App) { - app.register_type::(); - app.init_asset::(); - app.register_asset_reflect::(); - } -} diff --git a/crates/bevy_interleave_interface/src/storage.rs b/crates/bevy_interleave_interface/src/storage.rs new file mode 100644 index 0000000..2487078 --- /dev/null +++ b/crates/bevy_interleave_interface/src/storage.rs @@ -0,0 +1,168 @@ +use std::marker::PhantomData; + +use bevy::{ + prelude::*, + reflect::GetTypeRegistration, +}; + +use crate::{ + GpuPlanarStorage, + PlanarHandle, + PlanarSync, +}; + + +pub struct PlanarStoragePlugin { + phantom: PhantomData R>, +} +impl Default for PlanarStoragePlugin { + fn default() -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl Plugin for PlanarStoragePlugin +where + R: PlanarSync + Default + GetTypeRegistration + Clone + Reflect, + R::GpuPlanarType: GpuPlanarStorage, +{ + fn build(&self, app: &mut App) { + app.register_type::(); + + app.register_type::(); + app.register_type::(); + app.init_asset::(); + app.register_asset_reflect::(); + + app.add_plugins(bevy::render::render_asset::RenderAssetPlugin::::default()); + app.add_plugins(bevy::render::sync_component::SyncComponentPlugin::::default()); + + let render_app = app.sub_app_mut(bevy::render::RenderApp); + render_app.add_systems( + bevy::render::ExtractSchedule, + extract_planar_handles::, + ); + render_app.add_systems( + bevy::render::Render, + queue_gpu_storage_buffers::.in_set(bevy::render::RenderSystems::PrepareBindGroups), + ); + } + + fn finish(&self, app: &mut App) { + if let Some(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) { + render_app.init_resource::>(); + } + } +} + + +// TODO: migrate to PlanarLayouts +#[derive(bevy::prelude::Resource)] +pub struct PlanarStorageLayouts +where + R::GpuPlanarType: GpuPlanarStorage, +{ + pub bind_group_layout: bevy::render::render_resource::BindGroupLayout, + pub phantom: PhantomData R>, +} + +impl +FromWorld for PlanarStorageLayouts +where + R::GpuPlanarType: GpuPlanarStorage, +{ + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + + let read_only = true; + let bind_group_layout = R::GpuPlanarType::bind_group_layout( + render_device, + read_only, + ); + + Self { + bind_group_layout, + phantom: PhantomData, + } + } +} + +#[derive(bevy::prelude::Component, Clone, Debug)] +pub struct PlanarStorageBindGroup { + pub bind_group: bevy::render::render_resource::BindGroup, + pub phantom: PhantomData R>, +} + + +fn extract_planar_handles( + mut commands: Commands, + mut main_world: ResMut, +) +where + R: PlanarSync + Default + Clone + Reflect, + R::PlanarType: Asset, + R::GpuPlanarType: GpuPlanarStorage, +{ + let mut planar_handles_query = main_world.query::<( + bevy::render::sync_world::RenderEntity, + &R::PlanarTypeHandle, + )>(); + + for ( + entity, + planar_handle + ) in planar_handles_query.iter(&main_world) { + if let Ok(mut entity_commands) = commands.get_entity(entity) { + entity_commands.insert(planar_handle.clone()); + } + } +} + + +fn queue_gpu_storage_buffers( + mut commands: Commands, + asset_server: Res, + render_device: ResMut, + gpu_planars: Res>, + bind_group_layout: Res>, + clouds: Query< + ( + bevy::prelude::Entity, + &R::PlanarTypeHandle, + ), + Without>, + >, +) +where + R: PlanarSync + Default + Clone + Reflect, + R::PlanarType: Asset, + R::GpuPlanarType: GpuPlanarStorage, +{ + let layout = &bind_group_layout.bind_group_layout; + + for (entity, planar_handle,) in clouds.iter() { + + if let Some(load_state) = asset_server.get_load_state(planar_handle.handle()) + && load_state.is_loading() + { + continue; + } + + if gpu_planars.get(planar_handle.handle()).is_none() { + continue; + } + + let gpu_planar: &::GpuPlanarType = gpu_planars.get(planar_handle.handle()).unwrap(); + let bind_group = gpu_planar.bind_group( + &render_device, + layout, + ); + + commands.entity(entity).insert(PlanarStorageBindGroup:: { + bind_group, + phantom: PhantomData, + }); + } +} diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index 2b5299b..c839def 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -9,7 +9,9 @@ use bevy::{ }, }; -use crate::PlanarTexture; +use crate::{ + GpuPlanarTexture, PlanarHandle, PlanarTexture +}; pub struct PlanarTexturePlugin { @@ -27,6 +29,7 @@ impl Plugin for PlanarTexturePlugin where R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, + R::GpuPlanarType: GpuPlanarTexture, { fn build(&self, app: &mut App) { app.register_type::(); @@ -43,7 +46,7 @@ where } fn finish(&self, app: &mut App) { - if let Ok(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) { + if let Some(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) { render_app.init_resource::>(); } } @@ -51,17 +54,24 @@ where #[derive(bevy::prelude::Resource)] -pub struct PlanarTextureLayouts { +pub struct PlanarTextureLayouts +where + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::GpuPlanarType: GpuPlanarTexture, +{ pub bind_group_layout: bevy::render::render_resource::BindGroupLayout, pub phantom: PhantomData R>, } -impl -FromWorld for PlanarTextureLayouts { +impl FromWorld for PlanarTextureLayouts +where + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::GpuPlanarType: GpuPlanarTexture, +{ fn from_world(world: &mut World) -> Self { let render_device = world.resource::(); - let bind_group_layout = R::bind_group_layout( + let bind_group_layout = R::GpuPlanarType::bind_group_layout( render_device, ); @@ -72,6 +82,8 @@ FromWorld for PlanarTextureLayouts { } } + +// TODO: utilize asset prepare workflow fn prepare_textures( mut commands: Commands, asset_server: Res, @@ -80,7 +92,7 @@ fn prepare_textures( clouds: Query< ( Entity, - &Handle, + &R::PlanarTypeHandle, ), Without, >, @@ -88,23 +100,26 @@ fn prepare_textures( where R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, + R::GpuPlanarType: GpuPlanarTexture, { for (entity, cloud_handle) in clouds.iter() { - if Some(bevy::asset::LoadState::Loading) == asset_server.get_load_state(cloud_handle){ - continue; + if let Some(load_state) = asset_server.get_load_state(cloud_handle.handle()) { + if load_state.is_loading() { + continue; + } } - if cloud_res.get(cloud_handle).is_none() { + if cloud_res.get(cloud_handle.handle()).is_none() { continue; } - let cloud = cloud_res.get(cloud_handle).unwrap(); - - let buffers = R::prepare( - &mut images, - cloud, - ); + let cloud = cloud_res.get(cloud_handle.handle()).unwrap(); + let buffers = R::prepare(&mut images, cloud); + // TODO: the flow is: + // 1. given a planar asset handle, prepare main world image buffers insert handles into main world + // 2. copy the main world struct directly to gpu world via extract component + // 3. utilize gpu_planar_type to handle both main and gpu world handles commands.entity(entity).insert(buffers); } } @@ -120,7 +135,7 @@ pub struct PlanarTextureBindGroup( mut commands: Commands, render_device: ResMut, - gpu_images: Res>, + gpu_images: Res>, bind_group_layout: Res>, clouds: Query< ( @@ -133,6 +148,7 @@ fn queue_gpu_texture_buffers( where R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, + R::GpuPlanarType: GpuPlanarTexture, { let layout = &bind_group_layout.bind_group_layout; diff --git a/crates/bevy_interleave_macros/Cargo.toml b/crates/bevy_interleave_macros/Cargo.toml index f1b0350..8034092 100644 --- a/crates/bevy_interleave_macros/Cargo.toml +++ b/crates/bevy_interleave_macros/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_interleave_macros" -version = "0.2.1" -edition = "2021" +version = "0.8.0" +edition = "2024" description = "macros for e2e packed to planar bind groups" homepage = "https://github.com/mosure/bevy_interleave" repository = "https://github.com/mosure/bevy_interleave" @@ -13,18 +13,19 @@ keywords = [ [dependencies] -bevy_interleave_interface = { path = "../bevy_interleave_interface", version = "0.2.1" } +bevy_interleave_interface = { path = "../bevy_interleave_interface", version = "0.8.0" } bytemuck = "1.14" -convert_case = "0.6" +convert_case = "0.8" proc-macro2 = "1.0" quote = "1.0" sha1 = "0.10" syn = "2.0" +wgpu = "26" [dependencies.bevy] -version = "0.13" +version = "0.17" default-features = false -features = ["bevy_asset", "bevy_render"] +features = ["bevy_asset", "bevy_render", "png", "reflect_documentation", "reflect_functions"] [lib] diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs index ae1dd91..b271e0f 100644 --- a/crates/bevy_interleave_macros/src/bindings/storage.rs +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -17,8 +17,9 @@ use syn::{ pub fn storage_bindings(input: &DeriveInput) -> Result { let name = &input.ident; - let planar_name = Ident::new(&format!("Planar{}", name), name.span()); - let gpu_planar_name = Ident::new(&format!("PlanarStorage{}", name), name.span()); + let planar_name = Ident::new(&format!("Planar{name}"), name.span()); + let gpu_planar_name = Ident::new(&format!("PlanarStorage{name}"), name.span()); + let planar_handle_name = Ident::new(&format!("Planar{name}Handle"), name.span()); let fields_struct = if let Data::Struct(ref data_struct) = input.data { match data_struct.fields { @@ -36,21 +37,102 @@ pub fn storage_bindings(input: &DeriveInput) -> Result; + + fn prepare_asset( + source: Self::SourceAsset, + _: AssetId, + render_device: &mut bevy::ecs::system::SystemParamItem, + _: Option<&Self>, + ) -> Result> { + let count = source.len(); + + let draw_indirect_buffer = render_device.create_buffer_with_data(&bevy::render::render_resource::BufferInitDescriptor { + label: Some("draw indirect buffer"), + contents: wgpu::util::DrawIndirectArgs { // TODO: reexport this type + vertex_count: 4, + instance_count: count as u32, + first_vertex: 0, + first_instance: 0, + }.as_bytes(), + usage: bevy::render::render_resource::BufferUsages::INDIRECT + | bevy::render::render_resource::BufferUsages::COPY_DST + | bevy::render::render_resource::BufferUsages::STORAGE + | bevy::render::render_resource::BufferUsages::COPY_SRC, + }); + + #(#buffers)* + + Ok(Self { + count, + draw_indirect_buffer, + + #(#buffer_names),* + }) + } + + fn asset_usage(_: &Self::SourceAsset) -> bevy::asset::RenderAssetUsages { + bevy::asset::RenderAssetUsages::default() + } + } + + impl GpuPlanar for #gpu_planar_name { type PackedType = #name; type PlanarType = #planar_name; + fn len(&self) -> usize { + self.count + } + } + + impl GpuPlanarStorage for #gpu_planar_name { + fn draw_indirect_buffer(&self) -> &bevy::render::render_resource::Buffer { + return &self.draw_indirect_buffer; + } + #bind_group #bind_group_layout - #prepare + } + + impl PlanarSync for #name { + type PackedType = #name; + type PlanarType = #planar_name; + type PlanarTypeHandle = #planar_handle_name; + type GpuPlanarType = #gpu_planar_name; } }; @@ -60,7 +142,7 @@ pub fn storage_bindings(input: &DeriveInput) -> Result quote::__private::TokenStream { let struct_name_snake = struct_name.to_string().to_case(Case::Snake); - let bind_group_name = format!("storage_{}_bind_group", struct_name_snake); + let bind_group_name = format!("storage_{struct_name_snake}_bind_group"); let bind_group_entries = fields_named.named .iter() @@ -101,7 +183,7 @@ pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsName pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream { let struct_name_snake = struct_name.to_string().to_case(Case::Snake); - let bind_group_layout_name = format!("storage_{}_bind_group_layout", struct_name_snake); + let bind_group_layout_name = format!("storage_{struct_name_snake}_bind_group_layout"); let bind_group_layout_entries = fields_named.named .iter() @@ -135,44 +217,3 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie } } } - - -pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream { - let buffers = fields_named.named - .iter() - .map(|field| { - let name = field.ident.as_ref().unwrap(); - let buffer_name_string = format!("{}_buffer", name); - - quote! { - let #name = render_device.create_buffer_with_data( - &bevy::render::render_resource::BufferInitDescriptor { - label: Some(#buffer_name_string), - contents: bytemuck::cast_slice(planar.#name.as_slice()), - usage: bevy::render::render_resource::BufferUsages::COPY_DST - | bevy::render::render_resource::BufferUsages::STORAGE, - } - ); - } - }); - - let buffer_names = fields_named.named - .iter() - .map(|field| { - let name = field.ident.as_ref().unwrap(); - quote! { #name } - }); - - quote! { - fn prepare( - render_device: &bevy::render::renderer::RenderDevice, - planar: &Self::PlanarType, - ) -> Self { - #(#buffers)* - - Self { - #(#buffer_names),* - } - } - } -} diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index e95e6ea..6868372 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -24,8 +24,9 @@ use syn::{ pub fn texture_bindings(input: &DeriveInput) -> Result { let name = &input.ident; - let planar_name = Ident::new(&format!("Planar{}", name), name.span()); - let gpu_planar_name = Ident::new(&format!("PlanarTexture{}", name), name.span()); + let planar_name = Ident::new(&format!("Planar{name}"), name.span()); + let gpu_planar_name = Ident::new(&format!("PlanarTexture{name}"), name.span()); + let planar_handle_name = Ident::new(&format!("Planar{name}Handle"), name.span()); let fields_struct = if let Data::Struct(ref data_struct) = input.data { match data_struct.fields { @@ -38,7 +39,7 @@ pub fn texture_bindings(input: &DeriveInput) -> Result } + quote! { bevy::asset::Handle } }); let bind_group = generate_bind_group_method(name, fields_struct); @@ -46,32 +47,63 @@ pub fn texture_bindings(input: &DeriveInput) -> Result, + _: &mut bevy::ecs::system::SystemParamItem, + _: Option<&Self>, + ) -> Result> { + let count = source.len(); - type QueryFilter = (); - type Out = Self; + Ok(Self { + count, + + #(#handle_clones),* + }) + } - fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::QueryData>) -> Option { - texture_buffers.clone().into() + fn asset_usage(_: &Self::SourceAsset) -> bevy::asset::RenderAssetUsages { + bevy::asset::RenderAssetUsages::default() } } - impl PlanarTexture for #gpu_planar_name { + impl GpuPlanarTexture for #gpu_planar_name { type PackedType = #name; - type PlanarType = #planar_name; + + fn len(&self) -> usize { + self.count + } #bind_group #bind_group_layout - #prepare #get_asset_handles } + + impl PlanarTexture for #name { + type PackedType = #name; + type PlanarType = #planar_name; + type PlanarTypeHandle = #planar_handle_name; + type GpuPlanarType = #gpu_planar_name; + + #get_asset_handles + #prepare + } }; Ok(expanded) @@ -80,7 +112,7 @@ pub fn texture_bindings(input: &DeriveInput) -> Result quote::__private::TokenStream { let struct_name_snake = struct_name.to_string().to_case(Case::Snake); - let bind_group_name = format!("texture_{}_bind_group", struct_name_snake); + let bind_group_name = format!("texture_{struct_name_snake}_bind_group"); let bind_group_entries = fields_named.named .iter() @@ -101,7 +133,7 @@ pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsName fn bind_group( &self, render_device: &bevy::render::renderer::RenderDevice, - gpu_images: &bevy::render::render_asset::RenderAssets, + gpu_images: &bevy::render::render_asset::RenderAssets, layout: &bevy::render::render_resource::BindGroupLayout, ) -> bevy::render::render_resource::BindGroup { render_device.create_bind_group( @@ -118,7 +150,7 @@ pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsName pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &FieldsNamed) -> quote::__private::TokenStream { let struct_name_snake = struct_name.to_string().to_case(Case::Snake); - let bind_group_layout_name = format!("texture_{}_bind_group_layout", struct_name_snake); + let bind_group_layout_name = format!("texture_{struct_name_snake}_bind_group_layout"); let bind_group_layout_entries = fields_named.named .iter() @@ -189,18 +221,18 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: let field_type = &field.ty; quote! { - let square = (planar.#name.len() as f32).sqrt().ceil() as u32; + let square = (self.#name.len() as f32).sqrt().ceil() as u32; let size = std::mem::size_of::<#field_type>(); let format_bpp = #format.pixel_size(); let depth = (size as f32 / format_bpp as f32).ceil() as u32; - let mut data = bytemuck::cast_slice(planar.#name.as_slice()).to_vec(); + let mut data = bytemuck::cast_slice(self.#name.as_slice()).to_vec(); let padded_size = (square * square * depth * format_bpp as u32) as usize; data.resize(padded_size, 0); - let mut #name = bevy::render::texture::Image::new( + let mut #name = bevy::image::Image::new( bevy::render::render_resource::Extent3d { width: square, height: square, @@ -224,8 +256,8 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: quote! { fn prepare( - images: &mut bevy::asset::Assets, - planar: &Self::PlanarType, + &self, + images: &mut bevy::asset::Assets, ) -> Self { #(#buffers)* @@ -246,7 +278,7 @@ pub fn generate_get_asset_handles_method(fields_named: &FieldsNamed) -> quote::_ }); quote! { - fn get_asset_handles(&self) -> Vec> { + fn get_asset_handles(&self) -> Vec> { vec![ #(#buffer_names),* ] diff --git a/crates/bevy_interleave_macros/src/planar.rs b/crates/bevy_interleave_macros/src/planar.rs index 96c85d9..7ff0326 100644 --- a/crates/bevy_interleave_macros/src/planar.rs +++ b/crates/bevy_interleave_macros/src/planar.rs @@ -12,7 +12,8 @@ use syn::{ pub fn generate_planar_struct(input: &DeriveInput) -> Result { let name = &input.ident; - let planar_name = Ident::new(&format!("Planar{}", name), name.span()); + let planar_name = Ident::new(&format!("Planar{name}"), name.span()); + let planar_handle_name = Ident::new(&format!("Planar{name}Handle"), name.span()); let fields_struct = if let Data::Struct(ref data_struct) = input.data { match data_struct.fields { @@ -32,6 +33,7 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result Result); + + impl bevy_interleave::interface::PlanarHandle<#planar_name> for #planar_handle_name { + fn handle(&self) -> &bevy::asset::Handle<#planar_name> { + &self.0 + } } }; @@ -150,3 +163,38 @@ pub fn generate_conversion_methods(struct_name: &Ident, fields_named: &FieldsNam conversion_methods } + + +pub fn generate_subset_method(fields_named: &FieldsNamed) -> proc_macro2::TokenStream { + let mut new_planes_fields = Vec::new(); + let mut push_self_index = Vec::new(); + let mut planes = Vec::new(); + + for field in &fields_named.named { + let name = field.ident.as_ref().unwrap(); + + new_planes_fields.push(quote! { + let mut #name = Vec::with_capacity(indices.len()); + }); + push_self_index.push(quote! { + #name.push(self.#name[index]); + }); + planes.push(quote! { + #name + }); + } + + quote! { + fn subset(&self, indices: &[usize]) -> Self { + #(#new_planes_fields)* + + for &index in indices { + #(#push_self_index)* + } + + Self { + #(#planes),* + } + } + } +} diff --git a/examples/app.rs b/examples/app.rs index 7edf4b0..d8ed190 100644 --- a/examples/app.rs +++ b/examples/app.rs @@ -4,23 +4,26 @@ use bevy_interleave::prelude::*; #[derive( + Clone, Debug, + Default, Planar, + Reflect, ReflectInterleaved, StorageBindings, - TextureBindings, + // TextureBindings, )] pub struct MyStruct { - #[texture_format(TextureFormat::R32Sint)] + // #[texture_format(TextureFormat::R32Sint)] pub field: i32, - #[texture_format(TextureFormat::R32Uint)] + // #[texture_format(TextureFormat::R32Uint)] pub field2: u32, - #[texture_format(TextureFormat::R8Unorm)] + // #[texture_format(TextureFormat::R8Unorm)] pub bool_field: bool, - #[texture_format(TextureFormat::Rgba32Uint)] + // #[texture_format(TextureFormat::Rgba32Uint)] pub array: [u32; 4], } @@ -28,12 +31,10 @@ pub struct MyStruct { fn main() { let mut app = App::new(); - app.add_plugins(( - DefaultPlugins, - PlanarPlugin::::default(), - PlanarTexturePlugin::::default(), - // TODO: PlanarStoragePlugin::::default(), - )); + app.add_plugins(DefaultPlugins); + app.add_plugins( + PlanarStoragePlugin::::default(), + ); app.run(); } diff --git a/examples/minimal.rs b/examples/minimal.rs index 6a88d96..7a70872 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,24 +1,28 @@ +use bevy::prelude::*; use bevy_interleave::prelude::*; #[derive( + Clone, Debug, + Default, Planar, + Reflect, ReflectInterleaved, StorageBindings, - TextureBindings, + // TextureBindings, )] pub struct MyStruct { - #[texture_format(TextureFormat::R32Sint)] + // #[texture_format(TextureFormat::R32Sint)] pub field: i32, - #[texture_format(TextureFormat::R32Uint)] + // #[texture_format(TextureFormat::R32Uint)] pub field2: u32, - #[texture_format(TextureFormat::R8Unorm)] + // #[texture_format(TextureFormat::R8Unorm)] pub bool_field: bool, - #[texture_format(TextureFormat::Rgba32Uint)] + // #[texture_format(TextureFormat::Rgba32Uint)] pub array: [u32; 4], } diff --git a/src/lib.rs b/src/lib.rs index 229dd01..5817e50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,109 +7,3 @@ pub mod interface { pub mod macros { pub use bevy_interleave_macros::*; } - - - -mod tests { - use std::sync::{Arc, Mutex}; - - use bevy::prelude::*; - use crate::prelude::*; - - #[derive( - Debug, - Planar, - ReflectInterleaved, - StorageBindings, - TextureBindings, - )] - pub struct MyStruct { - #[texture_format(TextureFormat::R32Sint)] - pub field: i32, - - #[texture_format(TextureFormat::R32Uint)] - pub field2: u32, - - #[texture_format(TextureFormat::R8Unorm)] - pub bool_field: bool, - - #[texture_format(TextureFormat::Rgba32Uint)] - pub array: [u32; 4], - } - - #[derive(Resource, Default)] - struct TestSuccess(Arc>); - - #[allow(dead_code)] - fn setup_planar( - mut commands: Commands, - mut gaussian_assets: ResMut>, - ) { - let planar = PlanarMyStruct::from_interleaved(vec![ - MyStruct { field: 0, field2: 1_u32, bool_field: true, array: [0, 1, 2, 3] }, - MyStruct { field: 2, field2: 3_u32, bool_field: false, array: [4, 5, 6, 7] }, - MyStruct { field: 4, field2: 5_u32, bool_field: true, array: [8, 9, 10, 11] }, - ]); - - let planar_handle = gaussian_assets.add(planar); - - commands.spawn(planar_handle); - } - - #[allow(dead_code)] - fn check_bind_group( - bind_group: Query<&PlanarTextureBindGroup::>, - success: Res, - ) { - if bind_group.iter().count() > 0 { - *success.0.lock().unwrap() = true; - } - } - - #[allow(dead_code)] - fn test_timeout( - mut exit: EventWriter, - mut frame_count: Local, - ) { - *frame_count += 1; - - if *frame_count > 5 { - exit.send(bevy::app::AppExit); - } - } - - - #[test] - fn texture_bind_group() { - let mut app = App::new(); - - app.add_plugins(( - DefaultPlugins, - bevy::app::ScheduleRunnerPlugin::run_loop( - std::time::Duration::from_millis(50) - ), - - PlanarPlugin::::default(), - PlanarTexturePlugin::::default(), - )); - - app.add_systems(Startup, setup_planar); - - let render_app = app.sub_app_mut(bevy::render::RenderApp); - render_app.add_systems( - bevy::render::Render, - check_bind_group.in_set(bevy::render::RenderSet::QueueMeshes), - ); - - render_app.init_resource::(); - let success = render_app.world.resource::(); - let success = success.0.clone(); - - app.add_systems(Update, test_timeout); - app.run(); - - if !*success.lock().unwrap() { - panic!("app exit without success flag set - bind group was not found"); - } - } -} diff --git a/src/prelude.rs b/src/prelude.rs index 34bab6e..1ee2a8f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,19 +1,23 @@ -pub use bevy::render::{ - render_resource::TextureFormat, - texture::TextureFormatPixelInfo, -}; - +pub use bevy::render::render_resource::TextureFormat; +pub use bevy::image::TextureFormatPixelInfo; pub use crate::interface::{ + GpuPlanar, + GpuPlanarStorage, Planar, - PlanarStorage, + PlanarHandle, + PlanarSync, PlanarTexture, - planar::PlanarPlugin, - texture::{ - PlanarTextureBindGroup, - PlanarTextureLayouts, - PlanarTexturePlugin, + storage::{ + PlanarStorageBindGroup, + PlanarStorageLayouts, + PlanarStoragePlugin, }, + // texture::{ + // PlanarTextureBindGroup, + // PlanarTextureLayouts, + // PlanarTexturePlugin, + // }, ReflectInterleaved, }; diff --git a/test/lib.rs b/test/lib.rs new file mode 100644 index 0000000..2e747f3 --- /dev/null +++ b/test/lib.rs @@ -0,0 +1,126 @@ +use std::sync::{Arc, Mutex}; + +use bevy::{ + prelude::*, + winit::{WakeUp, WinitPlugin}, +}; +use bevy_interleave::prelude::*; + + +#[derive( + Clone, + Debug, + Default, + Reflect, + Planar, + ReflectInterleaved, + StorageBindings, + // TextureBindings, +)] +pub struct MyStruct { + // #[texture_format(TextureFormat::R32Sint)] + pub field: i32, + + // #[texture_format(TextureFormat::R32Uint)] + pub field2: u32, + + // #[texture_format(TextureFormat::R8Unorm)] + pub bool_field: bool, + + // #[texture_format(TextureFormat::Rgba32Uint)] + pub array: [u32; 4], +} + +#[derive(Resource, Default)] +struct TestSuccess(Arc>); + + +fn setup_planar( + mut commands: Commands, + mut gaussian_assets: ResMut>, +) { + let planar = PlanarMyStruct::from_interleaved(vec![ + MyStruct { field: 0, field2: 1_u32, bool_field: true, array: [0, 1, 2, 3] }, + MyStruct { field: 2, field2: 3_u32, bool_field: false, array: [4, 5, 6, 7] }, + MyStruct { field: 4, field2: 5_u32, bool_field: true, array: [8, 9, 10, 11] }, + ]); + + let planar_handle = gaussian_assets.add(planar); + + println!("spawned planar with handle: {:?}", planar_handle.clone()); + commands.spawn(PlanarMyStructHandle(planar_handle)); +} + +// TODO: require both texture and storage bind groups +// fn check_texture_bind_group( +// bind_group: Query<&PlanarTextureBindGroup::>, +// success: Res, +// ) { +// if bind_group.iter().count() > 0 { +// *success.0.lock().unwrap() = true; +// } +// } + +fn check_storage_bind_group( + bind_group: Query<&PlanarStorageBindGroup::>, + success: Res, +) { + if bind_group.iter().count() > 0 { + *success.0.lock().unwrap() = true; + } +} + +fn test_timeout( + mut exit: MessageWriter, + mut frame_count: Local, +) { + *frame_count += 1; + + if *frame_count > 5 { + exit.write(bevy::app::AppExit::Success); + } +} + + +#[test] +#[cfg_attr(target_os = "macos", ignore = "WinitPlugin cannot run on non-main thread on macOS")] +fn texture_bind_group() { + let mut app = App::new(); + + let mut winit_plugin = WinitPlugin::::default(); + winit_plugin.run_on_any_thread = true; + + app.add_plugins(( + DefaultPlugins + .set(winit_plugin), + bevy::app::ScheduleRunnerPlugin::run_loop( + std::time::Duration::from_millis(50) + ), + )); + app.add_plugins( + PlanarStoragePlugin::::default(), + // PlanarTexturePlugin::::default(), + ); + + app.add_systems(Startup, setup_planar); + + let render_app = app.sub_app_mut(bevy::render::RenderApp); + render_app.add_systems( + bevy::render::Render, + ( + check_storage_bind_group.in_set(bevy::render::RenderSystems::QueueMeshes), + // check_texture_bind_group.in_set(bevy::render::RenderSet::QueueMeshes) + ), + ); + + let success = TestSuccess(Arc::new(Mutex::new(false))); + let success_arc = success.0.clone(); + render_app.insert_resource(success); + + app.add_systems(Update, test_timeout); + app.run(); + + if !*success_arc.lock().unwrap() { + panic!("app exit without success flag set - bind group was not found"); + } +}