From 778aa8657d75834d1938778a71186b85aeef6945 Mon Sep 17 00:00:00 2001 From: mosure Date: Fri, 27 Dec 2024 22:48:44 -0600 Subject: [PATCH] feat: storage bindings --- Cargo.toml | 9 +- README.md | 3 +- crates/bevy_interleave_interface/Cargo.toml | 2 +- crates/bevy_interleave_interface/src/lib.rs | 39 +++-- .../bevy_interleave_interface/src/planar.rs | 31 ---- .../bevy_interleave_interface/src/storage.rs | 135 ++++++++++++++++++ .../bevy_interleave_interface/src/texture.rs | 3 +- crates/bevy_interleave_macros/Cargo.toml | 5 +- .../src/bindings/storage.rs | 114 +++++++++------ crates/bevy_interleave_macros/src/planar.rs | 41 +++++- examples/app.rs | 23 +-- src/lib.rs | 46 ++++-- src/prelude.rs | 7 +- 13 files changed, 328 insertions(+), 130 deletions(-) delete mode 100644 crates/bevy_interleave_interface/src/planar.rs create mode 100644 crates/bevy_interleave_interface/src/storage.rs diff --git a/Cargo.toml b/Cargo.toml index 55722a3..6f0b7c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy_interleave" description = "bevy support for e2e packed to planar bind groups" -version = "0.3.0" +version = "0.4.0" edition = "2021" authors = ["mosure "] license = "MIT" @@ -29,10 +29,11 @@ members = [ [dependencies] -bevy_interleave_interface = { path = "crates/bevy_interleave_interface", version = "0.3" } -bevy_interleave_macros = { path = "crates/bevy_interleave_macros", version = "0.3" } -bytemuck = "1.20" +bevy_interleave_interface = { path = "crates/bevy_interleave_interface", version = "0.4" } +bevy_interleave_macros = { path = "crates/bevy_interleave_macros", version = "0.4" } +bytemuck = "1.21" serde = "1.0" +wgpu = "23.0.1" [dependencies.bevy] version = "0.15" diff --git a/README.md b/README.md index a44aff9..177acaf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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 support for e2e packed to planar bind groups (e.g. statically typed meshes) ## minimal example @@ -18,7 +18,6 @@ use bevy_interleave::prelude::*; Planar, ReflectInterleaved, StorageBindings, - TextureBindings, )] pub struct MyStruct { #[texture_format(TextureFormat::R32Sint)] diff --git a/crates/bevy_interleave_interface/Cargo.toml b/crates/bevy_interleave_interface/Cargo.toml index dcf28d2..85fd2b5 100644 --- a/crates/bevy_interleave_interface/Cargo.toml +++ b/crates/bevy_interleave_interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_interleave_interface" -version = "0.3.0" +version = "0.4.0" edition = "2021" description = "interface for e2e packed to planar bind groups" homepage = "https://github.com/mosure/bevy_interleave" diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index 99973f5..fe9235a 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,11 +1,19 @@ -pub mod planar; +pub mod storage; pub mod texture; -pub trait PlanarStorage { +// TODO: this needs to be refactored and better structured +pub trait PlanarHandle +where + Self: bevy::ecs::component::Component, + Self: bevy::render::extract_component::ExtractComponent, + T: bevy::asset::Asset, +{ + fn handle(&self) -> &bevy::asset::Handle; +} + +pub trait GpuPlanarStorage { type PackedType; - type PlanarType; - type PlanarTypeHandle: bevy::ecs::component::Component; fn bind_group( &self, @@ -17,22 +25,23 @@ 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 PlanarTextureHandle: bevy::ecs::component::Component { - fn handle(&self) -> &bevy::asset::Handle; +pub trait PlanarStorage { + type PackedType; // Self + type PlanarType: bevy::asset::Asset + bevy::reflect::GetTypeRegistration + bevy::reflect::FromReflect; + type PlanarTypeHandle: PlanarHandle; + type GpuPlanarType: GpuPlanarStorage + bevy::render::render_asset::RenderAsset; } + +// TODO: refactor planar texture to be more like planar storage pub trait PlanarTexture { - type PackedType; + type PackedType; // Self type PlanarType: bevy::asset::Asset; - type PlanarTypeHandle: PlanarTextureHandle; + type PlanarTypeHandle: PlanarHandle; + + // note: planar texture's gpu type utilizes bevy's image render asset fn bind_group( &self, @@ -72,4 +81,6 @@ pub trait Planar { 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..4a7cd0f --- /dev/null +++ b/crates/bevy_interleave_interface/src/storage.rs @@ -0,0 +1,135 @@ +use std::marker::PhantomData; + +use bevy::{ + prelude::*, + reflect::GetTypeRegistration, + render::extract_component::ExtractComponentPlugin, +}; + +use crate::{ + GpuPlanarStorage, + PlanarHandle, + PlanarStorage, +}; + + +pub struct PlanarStoragePlugin { + phantom: PhantomData R>, +} +impl Default for PlanarStoragePlugin { + fn default() -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl Plugin for PlanarStoragePlugin +where + R: PlanarStorage + Default + GetTypeRegistration + Clone + Reflect, +{ + fn build(&self, app: &mut App) { + 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(ExtractComponentPlugin::::default()); + + let render_app = app.sub_app_mut(bevy::render::RenderApp); + render_app.add_systems( + bevy::render::Render, + queue_gpu_storage_buffers::.in_set(bevy::render::RenderSet::PrepareBindGroups), + ); + } + + fn finish(&self, app: &mut App) { + if let Some(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) { + render_app.init_resource::>(); + } + } +} + + +#[derive(bevy::prelude::Resource)] +pub struct PlanarStorageLayouts { + pub bind_group_layout: bevy::render::render_resource::BindGroupLayout, + pub phantom: PhantomData R>, +} + +impl +FromWorld for PlanarStorageLayouts { + 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 queue_gpu_storage_buffers( + mut commands: Commands, + asset_server: Res, + render_device: ResMut, + gpu_planars: Res>, + bind_group_layout: Res>, + clouds: Query< + ( + Entity, + &R::PlanarTypeHandle, + ), + Without>, + >, +) +where + R: PlanarStorage + Default + Clone + Reflect, + R::PlanarType: Asset, +{ + let layout = &bind_group_layout.bind_group_layout; + + info!("queue_gpu_storage_buffers"); + + for (entity, planar_handle,) in clouds.iter() { + info!("handle {:?}", planar_handle.handle()); + + if let Some(load_state) = asset_server.get_load_state(planar_handle.handle()) { + if load_state.is_loading() { + info!("loading"); + continue; + } + } + + if gpu_planars.get(planar_handle.handle()).is_none() { + info!("no gpu planar"); + 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 4615530..389f26c 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -10,8 +10,8 @@ use bevy::{ }; use crate::{ + PlanarHandle, PlanarTexture, - PlanarTextureHandle, }; @@ -75,6 +75,7 @@ FromWorld for PlanarTextureLayouts { } } + fn prepare_textures( mut commands: Commands, asset_server: Res, diff --git a/crates/bevy_interleave_macros/Cargo.toml b/crates/bevy_interleave_macros/Cargo.toml index 22fe26b..74d78e1 100644 --- a/crates/bevy_interleave_macros/Cargo.toml +++ b/crates/bevy_interleave_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_interleave_macros" -version = "0.3.0" +version = "0.4.0" edition = "2021" description = "macros for e2e packed to planar bind groups" homepage = "https://github.com/mosure/bevy_interleave" @@ -13,13 +13,14 @@ keywords = [ [dependencies] -bevy_interleave_interface = { path = "../bevy_interleave_interface", version = "0.3.0" } +bevy_interleave_interface = { path = "../bevy_interleave_interface", version = "0.4.0" } bytemuck = "1.14" convert_case = "0.6" proc-macro2 = "1.0" quote = "1.0" sha1 = "0.10" syn = "2.0" +wgpu = "23.0.1" [dependencies.bevy] version = "0.15" diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs index 869a44e..a0f20ff 100644 --- a/crates/bevy_interleave_macros/src/bindings/storage.rs +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -37,22 +37,85 @@ pub fn storage_bindings(input: &DeriveInput) -> Result; + + fn prepare_asset( + source: Self::SourceAsset, + render_device: &mut bevy::ecs::system::SystemParamItem, + ) -> 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),* + }) + } + } + + impl GpuPlanarStorage for #gpu_planar_name { type PackedType = #name; - type PlanarType = #planar_name; - type PlanarTypeHandle = #planar_handle_name; #bind_group #bind_group_layout - #prepare + } + + impl PlanarStorage for #name { + type PackedType = #name; + type PlanarType = #planar_name; + type PlanarTypeHandle = #planar_handle_name; + type GpuPlanarType = #gpu_planar_name; } }; @@ -137,44 +200,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/planar.rs b/crates/bevy_interleave_macros/src/planar.rs index 73a2c72..636cf28 100644 --- a/crates/bevy_interleave_macros/src/planar.rs +++ b/crates/bevy_interleave_macros/src/planar.rs @@ -33,6 +33,7 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result Result); - impl bevy_interleave_interface::PlanarTextureHandle<#planar_name> for #planar_handle_name { + impl bevy_interleave_interface::PlanarHandle<#planar_name> for #planar_handle_name { fn handle(&self) -> &bevy::asset::Handle<#planar_name> { &self.0 } @@ -160,3 +162,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 bbe7f0a..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(), - // PlanarStoragePlugin::::default(), - )); + app.add_plugins(DefaultPlugins); + app.add_plugins( + PlanarStoragePlugin::::default(), + ); app.run(); } diff --git a/src/lib.rs b/src/lib.rs index fd79ce6..7d63ace 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod macros { +#[allow(dead_code)] mod tests { use std::sync::{Arc, Mutex}; @@ -17,30 +18,32 @@ mod tests { use crate::prelude::*; #[derive( + Clone, Debug, + Default, + Reflect, Planar, 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], } #[derive(Resource, Default)] struct TestSuccess(Arc>); - #[allow(dead_code)] fn setup_planar( mut commands: Commands, mut gaussian_assets: ResMut>, @@ -56,9 +59,18 @@ mod tests { commands.spawn(PlanarMyStructHandle(planar_handle)); } - #[allow(dead_code)] - fn check_bind_group( - bind_group: Query<&PlanarTextureBindGroup::>, + // 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 { @@ -66,7 +78,6 @@ mod tests { } } - #[allow(dead_code)] fn test_timeout( mut exit: EventWriter, mut frame_count: Local, @@ -83,21 +94,26 @@ mod tests { fn texture_bind_group() { let mut app = App::new(); - app.add_plugins(( + app.add_plugins( DefaultPlugins .set(bevy::app::ScheduleRunnerPlugin::run_loop( std::time::Duration::from_millis(50) )), - PlanarPlugin::::default(), - PlanarTexturePlugin::::default(), - )); + ); + 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_bind_group.in_set(bevy::render::RenderSet::QueueMeshes), + ( + check_storage_bind_group.in_set(bevy::render::RenderSet::QueueMeshes), + // check_texture_bind_group.in_set(bevy::render::RenderSet::QueueMeshes) + ), ); let success = TestSuccess(Arc::new(Mutex::new(false))); diff --git a/src/prelude.rs b/src/prelude.rs index ee4f8ca..ddb98bc 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,10 +2,15 @@ pub use bevy::render::render_resource::TextureFormat; pub use bevy::image::TextureFormatPixelInfo; pub use crate::interface::{ + GpuPlanarStorage, Planar, PlanarStorage, PlanarTexture, - planar::PlanarPlugin, + storage::{ + PlanarStorageBindGroup, + PlanarStorageLayouts, + PlanarStoragePlugin, + }, texture::{ PlanarTextureBindGroup, PlanarTextureLayouts,