From 5c8f1ede5836b3285ffef9f1bcd038781a981880 Mon Sep 17 00:00:00 2001 From: mosure Date: Sat, 17 Feb 2024 15:58:20 -0600 Subject: [PATCH 1/3] feat: derived planar storage buffer bind groups --- Cargo.toml | 8 +- crates/bevy_interleave_interface/Cargo.toml | 6 + crates/bevy_interleave_interface/src/lib.rs | 44 +++- crates/bevy_interleave_macros/Cargo.toml | 12 +- .../src/bindings/mod.rs | 2 + .../src/bindings/storage.rs | 202 ++++++++++++++++++ .../src/bindings/texture.rs | 1 + crates/bevy_interleave_macros/src/lib.rs | 34 ++- crates/bevy_interleave_macros/src/packed.rs | 53 +++++ crates/bevy_interleave_macros/src/planar.rs | 45 +++- examples/minimal.rs | 14 +- src/prelude.rs | 13 +- 12 files changed, 412 insertions(+), 22 deletions(-) create mode 100644 crates/bevy_interleave_macros/src/bindings/mod.rs create mode 100644 crates/bevy_interleave_macros/src/bindings/storage.rs create mode 100644 crates/bevy_interleave_macros/src/bindings/texture.rs create mode 100644 crates/bevy_interleave_macros/src/packed.rs diff --git a/Cargo.toml b/Cargo.toml index 70372a7..d07b763 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,10 +31,12 @@ members = [ [dependencies] bevy_interleave_interface = { path = "crates/bevy_interleave_interface", version = "0.1.0" } bevy_interleave_macros = { path = "crates/bevy_interleave_macros", version = "0.1.0" } +bytemuck = "1.14" +serde = "1.0" -# [dependencies.bevy] -# version = "0.12" -# default-features = false +[dependencies.bevy] +version = "0.12" +default-features = false [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/bevy_interleave_interface/Cargo.toml b/crates/bevy_interleave_interface/Cargo.toml index b0319ee..1e1a054 100644 --- a/crates/bevy_interleave_interface/Cargo.toml +++ b/crates/bevy_interleave_interface/Cargo.toml @@ -10,3 +10,9 @@ keywords = [ "bevy", "shader-types", ] + + +[dependencies.bevy] +version = "0.12" +default-features = false +features = ["bevy_render"] diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index 1074b0a..4a904c2 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,10 +1,44 @@ -pub trait Planar { - fn get(&self, index: usize) -> T; +pub trait GpuStoragePlanar { + type PackedType; + type PlanarType; + + fn bind_group( + &self, + render_device: &bevy::render::renderer::RenderDevice, + layout: &bevy::render::render_resource::BindGroupLayout, + ) -> bevy::render::render_resource::BindGroup; + + fn bind_group_layout( + &self, + render_device: &bevy::render::renderer::RenderDevice, + read_only: bool, + ) -> bevy::render::render_resource::BindGroupLayout; + + fn ordered_field_names(&self) -> &'static [&'static str]; + + fn prepare( + render_device: &bevy::render::renderer::RenderDevice, + planar: &Self::PlanarType, + ) -> Self; +} + + +pub trait MinBindingSize { + type PackedType; + + fn min_binding_sizes() -> &'static [usize]; +} + + +pub trait Planar { + type PackedType; + + fn get(&self, index: usize) -> Self::PackedType; fn is_empty(&self) -> bool; fn len(&self) -> usize; - fn set(&mut self, index: usize, value: T); - fn to_interleaved(&self) -> Vec; + fn set(&mut self, index: usize, value: Self::PackedType); + fn to_interleaved(&self) -> Vec; - fn from_interleaved(packed: Vec) -> Self where Self: Sized; + fn from_interleaved(packed: Vec) -> Self where Self: Sized; } diff --git a/crates/bevy_interleave_macros/Cargo.toml b/crates/bevy_interleave_macros/Cargo.toml index b65a8e3..b0e36cb 100644 --- a/crates/bevy_interleave_macros/Cargo.toml +++ b/crates/bevy_interleave_macros/Cargo.toml @@ -14,9 +14,17 @@ keywords = [ [dependencies] bevy_interleave_interface = { path = "../bevy_interleave_interface", version = "0.1.0" } -syn = "2.0" -quote = "1.0" +bytemuck = "1.14" +convert_case = "0.6" proc-macro2 = "1.0" +quote = "1.0" +sha1 = "0.10" +syn = "2.0" + +[dependencies.bevy] +version = "0.12" +default-features = false +features = ["bevy_render"] [lib] diff --git a/crates/bevy_interleave_macros/src/bindings/mod.rs b/crates/bevy_interleave_macros/src/bindings/mod.rs new file mode 100644 index 0000000..03394c1 --- /dev/null +++ b/crates/bevy_interleave_macros/src/bindings/mod.rs @@ -0,0 +1,2 @@ +pub mod storage; +pub mod texture; diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs new file mode 100644 index 0000000..ed0e4ef --- /dev/null +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -0,0 +1,202 @@ +use convert_case::{ + Case, + Casing, +}; +use quote::quote; +use syn::{ + Data, + DeriveInput, + Error, + Fields, + FieldsNamed, + Ident, + Result, +}; + + +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!("GpuPlanar{}", name), name.span()); + + let fields_struct = if let Data::Struct(ref data_struct) = input.data { + match data_struct.fields { + Fields::Named(ref fields) => fields, + _ => return Err(Error::new_spanned(input, "Unsupported struct type")), + } + } else { + return Err(Error::new_spanned(input, "Planar macro only supports structs")); + }; + + let field_names = fields_struct.named.iter().map(|f| f.ident.as_ref().unwrap()); + let field_types = fields_struct.named.iter().map(|_| { + quote! { bevy::render::render_resource::Buffer } + }); + + let bind_group = generate_bind_group_method(name, fields_struct); + let bind_group_layout = generate_bind_group_layout_method(name, fields_struct); + let prepare = generate_prepare_method(fields_struct); + let ordered_field_names = generate_ordered_field_names_method(fields_struct); + + let expanded = quote! { + #[derive(Debug, Clone)] + pub struct #gpu_planar_name { + #(pub #field_names: #field_types,)* + } + + impl GpuStoragePlanar for #gpu_planar_name { + type PackedType = #name; + type PlanarType = #planar_name; + + #bind_group + #bind_group_layout + #prepare + #ordered_field_names + } + }; + + Ok(expanded) +} + + +pub fn generate_bind_group_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_name = format!("{}_bind_group", struct_name_snake); + + let bind_group_entries = fields_named.named + .iter() + .enumerate() + .map(|(idx, field)| { + let name = field.ident.as_ref().unwrap(); + quote! { + bevy::render::render_resource::BindGroupEntry { + binding: #idx as u32, + resource: bevy::render::render_resource::BindingResource::Buffer( + bevy::render::render_resource::BufferBinding { + buffer: &self.#name, + offset: 0, + size: bevy::render::render_resource::BufferSize::new(self.#name.size()), + } + ), + }, + } + }); + + quote! { + fn bind_group( + &self, + render_device: &bevy::render::renderer::RenderDevice, + layout: &bevy::render::render_resource::BindGroupLayout, + ) -> bevy::render::render_resource::BindGroup { + render_device.create_bind_group( + #bind_group_name, + &layout, + &[ + #(#bind_group_entries)* + ] + ) + } + } +} + + +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!("{}_bind_group_layout", struct_name_snake); + + let bind_group_layout_entries = fields_named.named + .iter() + .enumerate() + .map(|(idx, _)| { + quote! { + bevy::render::render_resource::BindGroupLayoutEntry { + binding: #idx as u32, + visibility: bevy::render::render_resource::ShaderStages::all(), + ty: bevy::render::render_resource::BindingType::Buffer { + ty: bevy::render::render_resource::BufferBindingType::Storage { read_only }, + has_dynamic_offset: false, + min_binding_size: bevy::render::render_resource::BufferSize::new(Self::PackedType::min_binding_sizes()[#idx] as u64), + }, + count: None, + }, + } + }); + + quote! { + fn bind_group_layout( + &self, + render_device: &bevy::render::renderer::RenderDevice, + read_only: bool, + ) -> bevy::render::render_resource::BindGroupLayout { + render_device.create_bind_group_layout( + &bevy::render::render_resource::BindGroupLayoutDescriptor { + label: Some(#bind_group_layout_name), + entries: &[ + #(#bind_group_layout_entries)* + ], + } + ) + } + } +} + + +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.to_string()); + + 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),* + } + } + } +} + + +pub fn generate_ordered_field_names_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream { + let string_field_names = fields_named.named + .iter() + .map(|field| { + let name = field.ident.as_ref().unwrap(); + let name_str = name.to_string(); + quote! { #name_str } + }); + + quote! { + fn ordered_field_names(&self) -> &'static [&'static str] { + &[ + #(#string_field_names),* + ] + } + } +} diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs new file mode 100644 index 0000000..b8d7be1 --- /dev/null +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -0,0 +1 @@ +extern crate proc_macro; diff --git a/crates/bevy_interleave_macros/src/lib.rs b/crates/bevy_interleave_macros/src/lib.rs index 37a33ae..9925bf7 100644 --- a/crates/bevy_interleave_macros/src/lib.rs +++ b/crates/bevy_interleave_macros/src/lib.rs @@ -6,10 +6,10 @@ use syn::{ parse_macro_input, }; + mod planar; use planar::generate_planar_struct; - #[proc_macro_derive(Planar)] pub fn planar_macro_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -21,3 +21,35 @@ pub fn planar_macro_derive(input: TokenStream) -> TokenStream { TokenStream::from(output) } + + +mod packed; +use packed::generate_min_binding_sizes; + +#[proc_macro_derive(MinBindingSize)] +pub fn min_binding_size_macro_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let output = match generate_min_binding_sizes(&input) { + Ok(quote) => quote, + Err(e) => return e.to_compile_error().into(), + }; + + TokenStream::from(output) +} + + +mod bindings; +use bindings::storage::storage_bindings; + +#[proc_macro_derive(StorageBindings)] +pub fn storage_bindings_macro_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let output = match storage_bindings(&input) { + Ok(quote) => quote, + Err(e) => return e.to_compile_error().into(), + }; + + TokenStream::from(output) +} diff --git a/crates/bevy_interleave_macros/src/packed.rs b/crates/bevy_interleave_macros/src/packed.rs new file mode 100644 index 0000000..441ced5 --- /dev/null +++ b/crates/bevy_interleave_macros/src/packed.rs @@ -0,0 +1,53 @@ +use quote::quote; +use syn::{ + Data, + DeriveInput, + Error, + Fields, + FieldsNamed, + Result, +}; + + +pub fn generate_min_binding_sizes(input: &DeriveInput) -> Result { + let name = &input.ident; + + let fields_struct = if let Data::Struct(ref data_struct) = input.data { + match data_struct.fields { + Fields::Named(ref fields) => fields, + _ => return Err(Error::new_spanned(input, "Unsupported struct type")), + } + } else { + return Err(Error::new_spanned(input, "Planar macro only supports structs")); + }; + + let min_binding_size_method = generate_min_binding_size_method(fields_struct); + + let expanded = quote! { + impl MinBindingSize for #name { + type PackedType = #name; + + #min_binding_size_method + } + }; + + Ok(expanded) +} + + +pub fn generate_min_binding_size_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream { + let min_binding_sizes = fields_named.named + .iter() + .map(|f| { + let field_type = &f.ty; + quote! { + std::mem::size_of::<#field_type>() + } + }); + + quote! { + fn min_binding_sizes() -> &'static [usize] { + &[#(#min_binding_sizes),*] + } + } +} diff --git a/crates/bevy_interleave_macros/src/planar.rs b/crates/bevy_interleave_macros/src/planar.rs index b6f01a2..93e4224 100644 --- a/crates/bevy_interleave_macros/src/planar.rs +++ b/crates/bevy_interleave_macros/src/planar.rs @@ -1,5 +1,3 @@ -extern crate proc_macro; - use quote::quote; use syn::{ Data, @@ -16,6 +14,10 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result fields, @@ -26,18 +28,34 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result }); + let field_types = fields_struct.named.iter().map(|og| { + let ty = &og.ty; + quote! { Vec<#ty> } + }); let conversion_methods = generate_conversion_methods(name, fields_struct); let get_set_methods = generate_accessor_setter_methods(name, fields_struct); let len_method = generate_len_method(fields_struct); let expanded = quote! { + #[derive( + Clone, + Debug, + Default, + PartialEq, + bevy::reflect::Reflect, + bevy::reflect::TypeUuid, + serde::Serialize, + serde::Deserialize, + )] + #[uuid = #uuid] pub struct #planar_name { #(pub #field_names: #field_types,)* } - impl Planar<#name> for #planar_name { + impl Planar for #planar_name { + type PackedType = #name; + #conversion_methods #get_set_methods #len_method @@ -138,3 +156,22 @@ pub fn generate_conversion_methods(struct_name: &Ident, fields_named: &FieldsNam conversion_methods } + +fn create_unique_identifier(name: &str) -> String { + use sha1::{Sha1, Digest}; + let mut hasher = Sha1::new(); + hasher.update(name.as_bytes()); + let result = hasher.finalize(); + format!("{:x}", result) +} + +fn format_unique_identifier_as_uuid(identifier: &String) -> String { + format!( + "{}-{}-{}-{}-{}", + &identifier[0..8], + &identifier[8..12], + &identifier[12..16], + &identifier[16..20], + &identifier[20..32] + ) +} diff --git a/examples/minimal.rs b/examples/minimal.rs index d8adf80..8775423 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -1,18 +1,22 @@ use bevy_interleave::prelude::*; -#[derive(Planar)] +#[derive( + Planar, + MinBindingSize, + StorageBindings, +)] pub struct MyStruct { pub field: i32, - pub field2: i32, + pub field2: u32, } fn main() { let interleaved = vec![ - MyStruct { field: 0, field2: 1 }, - MyStruct { field: 2, field2: 3 }, - MyStruct { field: 4, field2: 5 }, + MyStruct { field: 0, field2: 1_u32 }, + MyStruct { field: 2, field2: 3_u32 }, + MyStruct { field: 4, field2: 5_u32 }, ]; let planar = PlanarMyStruct::from_interleaved(interleaved); diff --git a/src/prelude.rs b/src/prelude.rs index 6c63883..778af84 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,2 +1,11 @@ -pub use crate::interface::Planar; -pub use crate::macros::Planar; +pub use crate::interface::{ + GpuStoragePlanar, + MinBindingSize, + Planar, +}; + +pub use crate::macros::{ + StorageBindings, + MinBindingSize, + Planar, +}; From 99b942ec9f093f406be0a1e75fcd818482456d3e Mon Sep 17 00:00:00 2001 From: mosure Date: Sat, 17 Feb 2024 15:59:10 -0600 Subject: [PATCH 2/3] chore: clean texture bind group scaffolding --- crates/bevy_interleave_macros/src/bindings/texture.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index b8d7be1..e69de29 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -1 +0,0 @@ -extern crate proc_macro; From 4d66c7883ec2f6d6fda9684a27bf0dca6e56d79c Mon Sep 17 00:00:00 2001 From: mosure Date: Sat, 17 Feb 2024 16:09:50 -0600 Subject: [PATCH 3/3] fix: lint --- crates/bevy_interleave_macros/src/bindings/storage.rs | 2 +- crates/bevy_interleave_macros/src/planar.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs index ed0e4ef..2b52c29 100644 --- a/crates/bevy_interleave_macros/src/bindings/storage.rs +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -147,7 +147,7 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: .iter() .map(|field| { let name = field.ident.as_ref().unwrap(); - let buffer_name_string = format!("{}_buffer", name.to_string()); + let buffer_name_string = format!("{}_buffer", name); quote! { let #name = render_device.create_buffer_with_data( diff --git a/crates/bevy_interleave_macros/src/planar.rs b/crates/bevy_interleave_macros/src/planar.rs index 93e4224..1f9c649 100644 --- a/crates/bevy_interleave_macros/src/planar.rs +++ b/crates/bevy_interleave_macros/src/planar.rs @@ -165,7 +165,7 @@ fn create_unique_identifier(name: &str) -> String { format!("{:x}", result) } -fn format_unique_identifier_as_uuid(identifier: &String) -> String { +fn format_unique_identifier_as_uuid(identifier: &str) -> String { format!( "{}-{}-{}-{}-{}", &identifier[0..8],