From 9aa7d0bcd1c91d14bae9b736f6604f2ef07c59fe Mon Sep 17 00:00:00 2001 From: mosure Date: Sun, 18 Feb 2024 11:38:57 -0600 Subject: [PATCH 1/9] feat: bevy 0.13 & planar texture outline --- Cargo.toml | 5 +- README.md | 1 + crates/bevy_interleave_interface/Cargo.toml | 2 +- crates/bevy_interleave_interface/src/lib.rs | 27 ++- .../bevy_interleave_interface/src/texture.rs | 34 ++++ crates/bevy_interleave_macros/Cargo.toml | 4 +- .../src/bindings/storage.rs | 18 +- .../src/bindings/texture.rs | 192 ++++++++++++++++++ crates/bevy_interleave_macros/src/lib.rs | 15 ++ crates/bevy_interleave_macros/src/planar.rs | 26 --- examples/minimal.rs | 1 + src/prelude.rs | 10 +- 12 files changed, 289 insertions(+), 46 deletions(-) create mode 100644 crates/bevy_interleave_interface/src/texture.rs diff --git a/Cargo.toml b/Cargo.toml index d07b763..e63648d 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.1.1" +version = "0.2.0" edition = "2021" authors = ["mosure "] license = "MIT" @@ -35,8 +35,9 @@ bytemuck = "1.14" serde = "1.0" [dependencies.bevy] -version = "0.12" +version = "0.13" default-features = false +features = ["bevy_asset", "bevy_render"] [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/README.md b/README.md index fe1d103..0112034 100644 --- a/README.md +++ b/README.md @@ -47,4 +47,5 @@ fn main() { | `bevy_interleave` | `bevy` | | :-- | :-- | +| `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 1e1a054..835a713 100644 --- a/crates/bevy_interleave_interface/Cargo.toml +++ b/crates/bevy_interleave_interface/Cargo.toml @@ -13,6 +13,6 @@ keywords = [ [dependencies.bevy] -version = "0.12" +version = "0.13" 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 b37a702..483eeac 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,5 +1,30 @@ +pub mod texture; -pub trait GpuStoragePlanar { + +pub trait PlanarStorage { + 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 prepare( + render_device: &bevy::render::renderer::RenderDevice, + planar: &Self::PlanarType, + ) -> Self; +} + + +pub trait PlanarTexture { type PackedType; type PlanarType; diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs new file mode 100644 index 0000000..dea36e6 --- /dev/null +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -0,0 +1,34 @@ +use std::marker::PhantomData; + +use bevy::{ + prelude::*, + reflect::GetTypeRegistration, + render::extract_component::{ + ExtractComponent, + ExtractComponentPlugin, + }, +}; + + +pub struct PlanarTexturePlugin { + phantom: PhantomData R>, +} +impl Default for PlanarTexturePlugin { + fn default() -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl Plugin for PlanarTexturePlugin { + fn build(&self, app: &mut App) { + app.register_type::(); + + app.add_plugins(ExtractComponentPlugin::::default()); + + // TODO: add queuing system registration /w binding? (seems to require too much custom logic for derive e.g. SH texture planes) + // ^ can use MinBindingSize to determine the number of planes + // will require the system to be generated in the macro (bound here) + } +} diff --git a/crates/bevy_interleave_macros/Cargo.toml b/crates/bevy_interleave_macros/Cargo.toml index b0e36cb..04a265b 100644 --- a/crates/bevy_interleave_macros/Cargo.toml +++ b/crates/bevy_interleave_macros/Cargo.toml @@ -22,9 +22,9 @@ sha1 = "0.10" syn = "2.0" [dependencies.bevy] -version = "0.12" +version = "0.13" default-features = false -features = ["bevy_render"] +features = ["bevy_asset", "bevy_render"] [lib] diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs index 013a9cb..82b9191 100644 --- a/crates/bevy_interleave_macros/src/bindings/storage.rs +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -18,7 +18,7 @@ pub fn storage_bindings(input: &DeriveInput) -> Result Result Result 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_name = format!("storage_{}_bind_group", struct_name_snake); let bind_group_entries = fields_named.named .iter() @@ -101,7 +101,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!("{}_bind_group_layout", struct_name_snake); + let bind_group_layout_name = format!("storage_{}_bind_group_layout", struct_name_snake); let bind_group_layout_entries = fields_named.named .iter() @@ -128,12 +128,10 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie 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)* - ], - } + Some(#bind_group_layout_name), + &[ + #(#bind_group_layout_entries)* + ], ) } } diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index e69de29..b655bbe 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -0,0 +1,192 @@ +use convert_case::{ + Case, + Casing, +}; +use quote::quote; +use syn::{ + Data, + DeriveInput, + Error, + Fields, + FieldsNamed, + Ident, + Result, +}; + + +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 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::asset::Handle } + }); + + 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 expanded = quote! { + #[derive(bevy::prelude::Component, Clone, Debug, bevy::reflect::Reflect)] + pub struct #gpu_planar_name { + #(pub #field_names: #field_types,)* + } + + impl bevy::render::extract_component::ExtractComponent for #gpu_planar_name { + type Query = &'static Self; + + type Filter = (); + type Out = Self; + + fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::Query>) -> Option { + texture_buffers.clone().into() + } + } + + impl PlanarTexture for #gpu_planar_name { + type PackedType = #name; + type PlanarType = #planar_name; + + #bind_group + #bind_group_layout + #prepare + } + }; + + 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!("storage_{}_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!("storage_{}_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); + + 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/lib.rs b/crates/bevy_interleave_macros/src/lib.rs index 7bf4c44..68d9903 100644 --- a/crates/bevy_interleave_macros/src/lib.rs +++ b/crates/bevy_interleave_macros/src/lib.rs @@ -53,3 +53,18 @@ pub fn storage_bindings_macro_derive(input: TokenStream) -> TokenStream { TokenStream::from(output) } + + +use bindings::texture::texture_bindings; + +#[proc_macro_derive(TextureBindings)] +pub fn texture_bindings_macro_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let output = match texture_bindings(&input) { + Ok(quote) => quote, + Err(e) => return e.to_compile_error().into(), + }; + + TokenStream::from(output) +} diff --git a/crates/bevy_interleave_macros/src/planar.rs b/crates/bevy_interleave_macros/src/planar.rs index 1f9c649..785eb81 100644 --- a/crates/bevy_interleave_macros/src/planar.rs +++ b/crates/bevy_interleave_macros/src/planar.rs @@ -14,10 +14,6 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result fields, @@ -44,11 +40,9 @@ pub fn generate_planar_struct(input: &DeriveInput) -> Result 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: &str) -> 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 b85b320..0dc96b9 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -6,6 +6,7 @@ use bevy_interleave::prelude::*; Planar, ReflectInterleaved, StorageBindings, + // TextureBindings, )] pub struct MyStruct { pub field: i32, diff --git a/src/prelude.rs b/src/prelude.rs index 1f747ad..32effd5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,11 +1,13 @@ pub use crate::interface::{ - GpuStoragePlanar, - ReflectInterleaved, Planar, + PlanarStorage, + PlanarTexture, + ReflectInterleaved, }; pub use crate::macros::{ - StorageBindings, - ReflectInterleaved, Planar, + ReflectInterleaved, + StorageBindings, + TextureBindings, }; From 39b2093a198a03b3fa5825349a5ba52e610eb670 Mon Sep 17 00:00:00 2001 From: mosure Date: Sun, 18 Feb 2024 21:09:14 -0600 Subject: [PATCH 2/9] feat: planar texture bind group and prepare --- crates/bevy_interleave_interface/src/lib.rs | 5 +- .../bevy_interleave_interface/src/texture.rs | 72 +++++++++++++++- .../src/bindings/storage.rs | 1 - .../src/bindings/texture.rs | 85 +++++++++++++------ crates/bevy_interleave_macros/src/lib.rs | 2 +- examples/minimal.rs | 9 +- src/prelude.rs | 3 + 7 files changed, 141 insertions(+), 36 deletions(-) diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index 483eeac..e4114be 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -12,7 +12,6 @@ pub trait PlanarStorage { ) -> bevy::render::render_resource::BindGroup; fn bind_group_layout( - &self, render_device: &bevy::render::renderer::RenderDevice, read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout; @@ -31,17 +30,17 @@ pub trait PlanarTexture { fn bind_group( &self, render_device: &bevy::render::renderer::RenderDevice, + gpu_images: &bevy::render::render_asset::RenderAssets, 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 prepare( - render_device: &bevy::render::renderer::RenderDevice, + images: &mut bevy::asset::Assets, planar: &Self::PlanarType, ) -> Self; } diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index dea36e6..e4e1e43 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -9,6 +9,11 @@ use bevy::{ }, }; +use crate::{ + PlanarTexture, + ReflectInterleaved, +}; + pub struct PlanarTexturePlugin { phantom: PhantomData R>, @@ -21,14 +26,73 @@ impl Default for PlanarTexturePlugin { } } -impl Plugin for PlanarTexturePlugin { +impl Plugin for PlanarTexturePlugin +where + R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::PlanarType: Asset, +{ fn build(&self, app: &mut App) { app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); - // TODO: add queuing system registration /w binding? (seems to require too much custom logic for derive e.g. SH texture planes) - // ^ can use MinBindingSize to determine the number of planes - // will require the system to be generated in the macro (bound here) + app.add_systems(Startup, prepare_textures::); + + let render_app = app.sub_app_mut(bevy::render::RenderApp); + render_app.add_systems( + bevy::render::Render, + queue_gpu_texture_buffers::.in_set(bevy::render::RenderSet::PrepareAssets), + ); + + app.init_resource::>(); } } + + +#[derive(bevy::prelude::Resource, Default)] +pub struct PlanarTextureLayouts { + pub bind_group_layout: Option, + pub phantom: PhantomData R>, +} + + +fn prepare_textures( + // mut commands: Commands, + asset_server: Res, + cloud_res: Res>, + // mut images: ResMut>, + // mut bind_group_layout: ResMut>, + clouds: Query< + ( + Entity, + &Handle, + ), + Without, + >, +) +where + R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::PlanarType: Asset, +{ + for (_entity, cloud_handle) in clouds.iter() { + if Some(bevy::asset::LoadState::Loading) == asset_server.get_load_state(cloud_handle){ + continue; + } + + if cloud_res.get(cloud_handle).is_none() { + continue; + } + + let _cloud = cloud_res.get(cloud_handle).unwrap(); + } +} + +fn queue_gpu_texture_buffers( + // mut commands: Commands, +) +where + R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::PlanarType: Asset, +{ + +} diff --git a/crates/bevy_interleave_macros/src/bindings/storage.rs b/crates/bevy_interleave_macros/src/bindings/storage.rs index 82b9191..ae1dd91 100644 --- a/crates/bevy_interleave_macros/src/bindings/storage.rs +++ b/crates/bevy_interleave_macros/src/bindings/storage.rs @@ -123,7 +123,6 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie quote! { fn bind_group_layout( - &self, render_device: &bevy::render::renderer::RenderDevice, read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout { diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index b655bbe..48d629f 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -2,14 +2,21 @@ use convert_case::{ Case, Casing, }; +use proc_macro2::TokenStream; use quote::quote; use syn::{ + Attribute, Data, DeriveInput, Error, Fields, FieldsNamed, Ident, + parse::{ + Parse, + ParseStream, + }, + Path, Result, }; @@ -45,12 +52,12 @@ pub fn texture_bindings(input: &DeriveInput) -> Result) -> Option { + fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::QueryData>) -> Option { texture_buffers.clone().into() } } @@ -71,7 +78,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!("storage_{}_bind_group", struct_name_snake); + let bind_group_name = format!("texture_{}_bind_group", struct_name_snake); let bind_group_entries = fields_named.named .iter() @@ -81,12 +88,8 @@ pub fn generate_bind_group_method(struct_name: &Ident, fields_named: &FieldsName 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()), - } + resource: bevy::render::render_resource::BindingResource::TextureView( + &gpu_images.get(&self.#name).unwrap().texture_view ), }, } @@ -96,6 +99,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, layout: &bevy::render::render_resource::BindGroupLayout, ) -> bevy::render::render_resource::BindGroup { render_device.create_bind_group( @@ -134,17 +138,14 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie 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)* - ], - } + Some(#bind_group_layout_name), + &[ + #(#bind_group_layout_entries)* + ], ) } } @@ -156,17 +157,24 @@ 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); + let format = extract_texture_format(&field.attrs); 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 square = (planar.#name.len() as f32).sqrt().ceil() as u32; + let depth = 1; + + let mut #name = bevy::render::texture::Image::new( + bevy::render::render_resource::Extent3d { + width: square, + height: square, + depth_or_array_layers: depth, + }, + bevy::render::render_resource::TextureDimension::D2, + bytemuck::cast_slice(planar.#name.as_slice()).to_vec(), + #format, + bevy::render::render_asset::RenderAssetUsages::default(), // TODO: if there are no CPU image derived features, set to render only ); + let #name = images.add(#name); } }); @@ -179,7 +187,7 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: quote! { fn prepare( - render_device: &bevy::render::renderer::RenderDevice, + images: &mut bevy::asset::Assets, planar: &Self::PlanarType, ) -> Self { #(#buffers)* @@ -190,3 +198,28 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: } } } + + +struct TextureFormatAttr(Path); + +impl Parse for TextureFormatAttr { + fn parse(input: ParseStream) -> Result { + let format: Path = input.parse()?; + Ok(TextureFormatAttr(format)) + } +} + +fn extract_texture_format(attributes: &[Attribute]) -> TokenStream { + for attr in attributes { + if attr.path().is_ident("texture_format") { + if let Ok(parsed) = attr.parse_args::() { + let TextureFormatAttr(format) = parsed; + return quote! { #format }; + } else { + panic!("error parsing texture_format attribute"); + } + } + } + + panic!("no texture_format attribute found, add `#[texture_format(Ident)]` to the field declarations"); +} diff --git a/crates/bevy_interleave_macros/src/lib.rs b/crates/bevy_interleave_macros/src/lib.rs index 68d9903..00c920e 100644 --- a/crates/bevy_interleave_macros/src/lib.rs +++ b/crates/bevy_interleave_macros/src/lib.rs @@ -57,7 +57,7 @@ pub fn storage_bindings_macro_derive(input: TokenStream) -> TokenStream { use bindings::texture::texture_bindings; -#[proc_macro_derive(TextureBindings)] +#[proc_macro_derive(TextureBindings, attributes(texture_format))] pub fn texture_bindings_macro_derive(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/examples/minimal.rs b/examples/minimal.rs index 0dc96b9..6a88d96 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -6,12 +6,19 @@ use bevy_interleave::prelude::*; Planar, ReflectInterleaved, StorageBindings, - // TextureBindings, + 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], } diff --git a/src/prelude.rs b/src/prelude.rs index 32effd5..d41c75f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,3 +1,6 @@ +pub use bevy::render::render_resource::TextureFormat; + + pub use crate::interface::{ Planar, PlanarStorage, From e497e98238e3bf87f12aca7f89df0a587f804c8b Mon Sep 17 00:00:00 2001 From: mosure Date: Sun, 18 Feb 2024 22:17:04 -0600 Subject: [PATCH 3/9] feat: bind_group_layout scaffold --- crates/bevy_interleave_interface/src/lib.rs | 1 - .../src/bindings/texture.rs | 44 ++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index e4114be..39da701 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -36,7 +36,6 @@ pub trait PlanarTexture { fn bind_group_layout( render_device: &bevy::render::renderer::RenderDevice, - read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout; fn prepare( diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index 48d629f..b8cf1e6 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -116,35 +116,57 @@ 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!("texture_{}_bind_group_layout", struct_name_snake); let bind_group_layout_entries = fields_named.named .iter() .enumerate() - .map(|(idx, _)| { + .map(|(idx, field)| { + let name = field.ident.as_ref().unwrap(); + quote! { - bevy::render::render_resource::BindGroupLayoutEntry { + // TODO: select based on texture format + let sample_type = bevy::render::render_resource::TextureSampleType::Float { + filterable: false, + }; + + let depth = 1; // TODO: variable depth based on type size and format + let view_dimension = if depth == 1 { + bevy::render::render_resource::TextureViewDimension::D2 + } else { + bevy::render::render_resource::TextureViewDimension::D2Array + }; + + let #name = 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), + ty: bevy::render::render_resource::BindingType::Texture { + view_dimension, + sample_type, + multisampled: false, }, count: None, - }, + }; } }); + let layout_names = fields_named.named + .iter() + .map(|field| { + let name = field.ident.as_ref().unwrap(); + quote! { #name } + }); + quote! { fn bind_group_layout( render_device: &bevy::render::renderer::RenderDevice, - read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout { + #(#bind_group_layout_entries)* + render_device.create_bind_group_layout( Some(#bind_group_layout_name), &[ - #(#bind_group_layout_entries)* + #(#layout_names),* ], ) } @@ -161,7 +183,7 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: quote! { let square = (planar.#name.len() as f32).sqrt().ceil() as u32; - let depth = 1; + let depth = 1; // TODO: variable depth based on type size and format let mut #name = bevy::render::texture::Image::new( bevy::render::render_resource::Extent3d { From a724d3b0160c54a673d56c559c33c99ba047ef8a Mon Sep 17 00:00:00 2001 From: mosure Date: Sun, 18 Feb 2024 23:52:21 -0600 Subject: [PATCH 4/9] feat: functional planar texture bindings --- README.md | 37 ++++++++++++++++--- .../src/bindings/texture.rs | 20 +++++++--- src/prelude.rs | 5 ++- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0112034..50dc1e8 100644 --- a/README.md +++ b/README.md @@ -13,28 +13,55 @@ bevy support for e2e packed to planar bind groups ```rust use bevy_interleave::prelude::*; -#[derive(Planar)] +#[derive( + Debug, + Planar, + ReflectInterleaved, + StorageBindings, + TextureBindings, +)] pub struct MyStruct { + #[texture_format(TextureFormat::R32Sint)] pub field: i32, - pub field2: i32, + + #[texture_format(TextureFormat::R32Uint)] + pub field2: u32, + + #[texture_format(TextureFormat::R8Unorm)] + pub bool_field: bool, + + #[texture_format(TextureFormat::Rgba32Uint)] + pub array: [u32; 4], } 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, 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 = PlanarMyStruct::from_interleaved(interleaved); println!("{:?}", planar.field); println!("{:?}", planar.field2); + println!("{:?}", planar.bool_field); + println!("{:?}", planar.array); // Prints: // [0, 2, 4] // [1, 3, 5] + // [true, false, true] + // [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]] + + println!("\n\n{:?}", MyStruct::min_binding_sizes()); + println!("{:?}", MyStruct::ordered_field_names()); + + // Prints: + // [4, 4, 1, 16] + // ["field", "field2", "bool_field", "array"] } + ``` diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index b8cf1e6..8320eb9 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -123,14 +123,17 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie .enumerate() .map(|(idx, field)| { let name = field.ident.as_ref().unwrap(); + let format = extract_texture_format(&field.attrs); + + let field_type = &field.ty; quote! { - // TODO: select based on texture format - let sample_type = bevy::render::render_resource::TextureSampleType::Float { - filterable: false, - }; + let sample_type = #format.sample_type(None, None).unwrap(); + + 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 depth = 1; // TODO: variable depth based on type size and format let view_dimension = if depth == 1 { bevy::render::render_resource::TextureViewDimension::D2 } else { @@ -181,9 +184,14 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: let name = field.ident.as_ref().unwrap(); let format = extract_texture_format(&field.attrs); + let field_type = &field.ty; + quote! { let square = (planar.#name.len() as f32).sqrt().ceil() as u32; - let depth = 1; // TODO: variable depth based on type size and format + + 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 #name = bevy::render::texture::Image::new( bevy::render::render_resource::Extent3d { diff --git a/src/prelude.rs b/src/prelude.rs index d41c75f..41cd04f 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,4 +1,7 @@ -pub use bevy::render::render_resource::TextureFormat; +pub use bevy::render::{ + render_resource::TextureFormat, + texture::TextureFormatPixelInfo, +}; pub use crate::interface::{ From 325227bdce5f803bdfa1319a1fea0f81a6c0ea99 Mon Sep 17 00:00:00 2001 From: mosure Date: Mon, 19 Feb 2024 10:02:34 -0600 Subject: [PATCH 5/9] docs: 3d texture support --- crates/bevy_interleave_macros/src/bindings/texture.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index 8320eb9..e034e75 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -135,7 +135,7 @@ pub fn generate_bind_group_layout_method(struct_name: &Ident, fields_named: &Fie let depth = (size as f32 / format_bpp as f32).ceil() as u32; let view_dimension = if depth == 1 { - bevy::render::render_resource::TextureViewDimension::D2 + bevy::render::render_resource::TextureViewDimension::D2 // TODO: support 3D texture sampling } else { bevy::render::render_resource::TextureViewDimension::D2Array }; From 56032ae58d81e98068114fa7033d3eded2ff037b Mon Sep 17 00:00:00 2001 From: mosure Date: Mon, 19 Feb 2024 11:28:24 -0600 Subject: [PATCH 6/9] feat: planar and texture plugins /w example app --- crates/bevy_interleave_interface/src/lib.rs | 1 + .../bevy_interleave_interface/src/planar.rs | 31 +++++++ .../bevy_interleave_interface/src/texture.rs | 84 +++++++++++++++---- .../src/bindings/texture.rs | 2 +- crates/bevy_interleave_macros/src/planar.rs | 1 + examples/app.rs | 43 ++++++++++ src/prelude.rs | 2 + 7 files changed, 147 insertions(+), 17 deletions(-) create mode 100644 crates/bevy_interleave_interface/src/planar.rs create mode 100644 examples/app.rs diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index 39da701..c589487 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,3 +1,4 @@ +pub mod planar; pub mod texture; diff --git a/crates/bevy_interleave_interface/src/planar.rs b/crates/bevy_interleave_interface/src/planar.rs new file mode 100644 index 0000000..f3f1c45 --- /dev/null +++ b/crates/bevy_interleave_interface/src/planar.rs @@ -0,0 +1,31 @@ +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/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index e4e1e43..2b985ff 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -9,10 +9,7 @@ use bevy::{ }, }; -use crate::{ - PlanarTexture, - ReflectInterleaved, -}; +use crate::PlanarTexture; pub struct PlanarTexturePlugin { @@ -28,7 +25,7 @@ impl Default for PlanarTexturePlugin { impl Plugin for PlanarTexturePlugin where - R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, { fn build(&self, app: &mut App) { @@ -44,24 +41,41 @@ where queue_gpu_texture_buffers::.in_set(bevy::render::RenderSet::PrepareAssets), ); - app.init_resource::>(); + render_app.init_resource::>(); + render_app.add_systems(bevy::render::ExtractSchedule, setup_planar_texture_layouts::); } } #[derive(bevy::prelude::Resource, Default)] -pub struct PlanarTextureLayouts { +pub struct PlanarTextureLayouts { pub bind_group_layout: Option, pub phantom: PhantomData R>, } +fn setup_planar_texture_layouts( + mut layouts: ResMut>, + render_device: ResMut, +) +where + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, +{ + if layouts.bind_group_layout.is_none() { + let layout = R::bind_group_layout( + &render_device, + ); + + layouts.bind_group_layout = Some(layout); + } +} + + fn prepare_textures( - // mut commands: Commands, + mut commands: Commands, asset_server: Res, cloud_res: Res>, - // mut images: ResMut>, - // mut bind_group_layout: ResMut>, + mut images: ResMut>, clouds: Query< ( Entity, @@ -71,10 +85,10 @@ fn prepare_textures( >, ) where - R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, { - for (_entity, cloud_handle) in clouds.iter() { + for (entity, cloud_handle) in clouds.iter() { if Some(bevy::asset::LoadState::Loading) == asset_server.get_load_state(cloud_handle){ continue; } @@ -83,16 +97,54 @@ where continue; } - let _cloud = cloud_res.get(cloud_handle).unwrap(); + let cloud = cloud_res.get(cloud_handle).unwrap(); + + let buffers = R::prepare( + &mut images, + &cloud, + ); + + commands.entity(entity).insert(buffers); } } + +#[derive(bevy::prelude::Component, Clone, Debug)] +pub struct PlanarTextureBindGroup { + pub bind_group: bevy::render::render_resource::BindGroup, + pub phantom: PhantomData R>, +} + + fn queue_gpu_texture_buffers( - // mut commands: Commands, + mut commands: Commands, + render_device: ResMut, + gpu_images: Res>, + bind_group_layout: Res>, + clouds: Query<( + Entity, + &R, + )>, ) where - R: PlanarTexture + ReflectInterleaved + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, { - + if bind_group_layout.bind_group_layout.is_none() { + println!("bind_group_layout is none"); + return; + } + + for (entity, texture_buffers,) in clouds.iter() { + let bind_group = texture_buffers.bind_group( + &render_device, + &gpu_images, + bind_group_layout.bind_group_layout.as_ref().unwrap() + ); + + commands.entity(entity).insert(PlanarTextureBindGroup:: { + bind_group, + phantom: PhantomData, + }); + } } diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index e034e75..ef44657 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -46,7 +46,7 @@ pub fn texture_bindings(input: &DeriveInput) -> Result Result::default(), + PlanarTexturePlugin::::default() + )); + + app.run(); +} + + +// TODO: insert test PlanarMyStruct +// TODO: verify bind group is added +// TODO: write as unit test mod in lib.rs diff --git a/src/prelude.rs b/src/prelude.rs index 41cd04f..7d1d72a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -8,6 +8,8 @@ pub use crate::interface::{ Planar, PlanarStorage, PlanarTexture, + planar::PlanarPlugin, + texture::PlanarTexturePlugin, ReflectInterleaved, }; From 5172bd38bbc0ee01d979636658c778897c62e1a4 Mon Sep 17 00:00:00 2001 From: mosure Date: Mon, 19 Feb 2024 14:46:02 -0600 Subject: [PATCH 7/9] feat: e2e planar texture bind group --- .github/workflows/test.yml | 5 +- .../bevy_interleave_interface/src/texture.rs | 7 +- .../src/bindings/texture.rs | 7 +- examples/app.rs | 8 +- src/lib.rs | 107 ++++++++++++++++++ src/prelude.rs | 6 +- 6 files changed, 128 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9b99f0..705363a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,10 @@ jobs: run: cargo clippy -- -Dwarnings - name: build - run: cargo build --example=minimal + run: cargo build + + - name: test + run: cargo test # - name: build (web) # run: cargo build --example=minimal --target wasm32-unknown-unknown --release diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index 2b985ff..968be68 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -33,7 +33,7 @@ where app.add_plugins(ExtractComponentPlugin::::default()); - app.add_systems(Startup, prepare_textures::); + app.add_systems(Update, prepare_textures::); let render_app = app.sub_app_mut(bevy::render::RenderApp); render_app.add_systems( @@ -53,6 +53,8 @@ pub struct PlanarTextureLayouts R>, } + +// TODO: this resource needs a FromWorld implementation see: `BlitPipeline` fn setup_planar_texture_layouts( mut layouts: ResMut>, render_device: ResMut, @@ -70,7 +72,6 @@ where } - fn prepare_textures( mut commands: Commands, asset_server: Res, @@ -101,7 +102,7 @@ where let buffers = R::prepare( &mut images, - &cloud, + cloud, ); commands.entity(entity).insert(buffers); diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index ef44657..2b07497 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -193,6 +193,11 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: 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 padded_size = (square * square * depth * format_bpp as u32) as usize; + data.resize(padded_size, 0); + let mut #name = bevy::render::texture::Image::new( bevy::render::render_resource::Extent3d { width: square, @@ -200,7 +205,7 @@ pub fn generate_prepare_method(fields_named: &FieldsNamed) -> quote::__private:: depth_or_array_layers: depth, }, bevy::render::render_resource::TextureDimension::D2, - bytemuck::cast_slice(planar.#name.as_slice()).to_vec(), + data, #format, bevy::render::render_asset::RenderAssetUsages::default(), // TODO: if there are no CPU image derived features, set to render only ); diff --git a/examples/app.rs b/examples/app.rs index bf6af9f..7edf4b0 100644 --- a/examples/app.rs +++ b/examples/app.rs @@ -31,13 +31,9 @@ fn main() { app.add_plugins(( DefaultPlugins, PlanarPlugin::::default(), - PlanarTexturePlugin::::default() + PlanarTexturePlugin::::default(), + // TODO: PlanarStoragePlugin::::default(), )); app.run(); } - - -// TODO: insert test PlanarMyStruct -// TODO: verify bind group is added -// TODO: write as unit test mod in lib.rs diff --git a/src/lib.rs b/src/lib.rs index 5817e50..f6290f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,110 @@ 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 > 10 { + 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(), + // TODO: PlanarStoragePlugin::::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 7d1d72a..34bab6e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,7 +9,11 @@ pub use crate::interface::{ PlanarStorage, PlanarTexture, planar::PlanarPlugin, - texture::PlanarTexturePlugin, + texture::{ + PlanarTextureBindGroup, + PlanarTextureLayouts, + PlanarTexturePlugin, + }, ReflectInterleaved, }; From d067fe578d861f87e16de804e0cafa96a6b576a7 Mon Sep 17 00:00:00 2001 From: mosure Date: Mon, 19 Feb 2024 15:49:40 -0600 Subject: [PATCH 8/9] feat: e2e test --- crates/bevy_interleave_interface/src/lib.rs | 2 + .../bevy_interleave_interface/src/texture.rs | 61 +++++++++++-------- .../src/bindings/texture.rs | 20 ++++++ src/lib.rs | 3 +- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/crates/bevy_interleave_interface/src/lib.rs b/crates/bevy_interleave_interface/src/lib.rs index c589487..02565cd 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -43,6 +43,8 @@ pub trait PlanarTexture { images: &mut bevy::asset::Assets, planar: &Self::PlanarType, ) -> Self; + + fn get_asset_handles(&self) -> Vec>; } diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index 968be68..c15c96c 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -40,38 +40,38 @@ where bevy::render::Render, queue_gpu_texture_buffers::.in_set(bevy::render::RenderSet::PrepareAssets), ); + } - render_app.init_resource::>(); - render_app.add_systems(bevy::render::ExtractSchedule, setup_planar_texture_layouts::); + fn finish(&self, app: &mut App) { + if let Ok(render_app) = app.get_sub_app_mut(bevy::render::RenderApp) { + render_app.init_resource::>(); + } } } -#[derive(bevy::prelude::Resource, Default)] +#[derive(bevy::prelude::Resource)] pub struct PlanarTextureLayouts { - pub bind_group_layout: Option, + pub bind_group_layout: bevy::render::render_resource::BindGroupLayout, pub phantom: PhantomData R>, } +impl +FromWorld for PlanarTextureLayouts { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); -// TODO: this resource needs a FromWorld implementation see: `BlitPipeline` -fn setup_planar_texture_layouts( - mut layouts: ResMut>, - render_device: ResMut, -) -where - R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, -{ - if layouts.bind_group_layout.is_none() { - let layout = R::bind_group_layout( - &render_device, + let bind_group_layout = R::bind_group_layout( + render_device, ); - layouts.bind_group_layout = Some(layout); + PlanarTextureLayouts:: { + bind_group_layout, + phantom: PhantomData, + } } } - fn prepare_textures( mut commands: Commands, asset_server: Res, @@ -122,25 +122,34 @@ fn queue_gpu_texture_buffers( render_device: ResMut, gpu_images: Res>, bind_group_layout: Res>, - clouds: Query<( - Entity, - &R, - )>, + clouds: Query< + ( + Entity, + &R, + ), + Without>, + >, ) where R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, R::PlanarType: Asset, { - if bind_group_layout.bind_group_layout.is_none() { - println!("bind_group_layout is none"); - return; - } + let layout = &bind_group_layout.bind_group_layout; for (entity, texture_buffers,) in clouds.iter() { + let not_ready = texture_buffers.get_asset_handles().iter() + .map(|handle| gpu_images.get(handle).is_none()) + .reduce(|a, b| a || b) + .unwrap_or(true); + + if not_ready { + continue; + } + let bind_group = texture_buffers.bind_group( &render_device, &gpu_images, - bind_group_layout.bind_group_layout.as_ref().unwrap() + &layout, ); commands.entity(entity).insert(PlanarTextureBindGroup:: { diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index 2b07497..e95e6ea 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -44,6 +44,7 @@ pub fn texture_bindings(input: &DeriveInput) -> Result Result quote::__private:: } +pub fn generate_get_asset_handles_method(fields_named: &FieldsNamed) -> quote::__private::TokenStream { + let buffer_names = fields_named.named + .iter() + .map(|field| { + let name = field.ident.as_ref().unwrap(); + quote! { self.#name.clone() } + }); + + quote! { + fn get_asset_handles(&self) -> Vec> { + vec![ + #(#buffer_names),* + ] + } + } +} + + struct TextureFormatAttr(Path); impl Parse for TextureFormatAttr { diff --git a/src/lib.rs b/src/lib.rs index f6290f0..229dd01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ mod tests { ) { *frame_count += 1; - if *frame_count > 10 { + if *frame_count > 5 { exit.send(bevy::app::AppExit); } } @@ -91,7 +91,6 @@ mod tests { PlanarPlugin::::default(), PlanarTexturePlugin::::default(), - // TODO: PlanarStoragePlugin::::default(), )); app.add_systems(Startup, setup_planar); From ec89147c4cbdd3694b2401ac5c76b3367d59015d Mon Sep 17 00:00:00 2001 From: mosure Date: Mon, 19 Feb 2024 16:03:18 -0600 Subject: [PATCH 9/9] fix: lint --- crates/bevy_interleave_interface/src/texture.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_interleave_interface/src/texture.rs b/crates/bevy_interleave_interface/src/texture.rs index c15c96c..2b5299b 100644 --- a/crates/bevy_interleave_interface/src/texture.rs +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -149,7 +149,7 @@ where let bind_group = texture_buffers.bind_group( &render_device, &gpu_images, - &layout, + layout, ); commands.entity(entity).insert(PlanarTextureBindGroup:: {