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/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..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"] } + ``` @@ -47,4 +74,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..02565cd 100644 --- a/crates/bevy_interleave_interface/src/lib.rs +++ b/crates/bevy_interleave_interface/src/lib.rs @@ -1,5 +1,8 @@ +pub mod planar; +pub mod texture; -pub trait GpuStoragePlanar { + +pub trait PlanarStorage { type PackedType; type PlanarType; @@ -10,7 +13,6 @@ pub trait GpuStoragePlanar { ) -> bevy::render::render_resource::BindGroup; fn bind_group_layout( - &self, render_device: &bevy::render::renderer::RenderDevice, read_only: bool, ) -> bevy::render::render_resource::BindGroupLayout; @@ -22,6 +24,30 @@ pub trait GpuStoragePlanar { } +pub trait PlanarTexture { + type PackedType; + type PlanarType; + + 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( + render_device: &bevy::render::renderer::RenderDevice, + ) -> bevy::render::render_resource::BindGroupLayout; + + fn prepare( + images: &mut bevy::asset::Assets, + planar: &Self::PlanarType, + ) -> Self; + + fn get_asset_handles(&self) -> Vec>; +} + + pub trait ReflectInterleaved { type PackedType; 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 new file mode 100644 index 0000000..2b5299b --- /dev/null +++ b/crates/bevy_interleave_interface/src/texture.rs @@ -0,0 +1,160 @@ +use std::marker::PhantomData; + +use bevy::{ + prelude::*, + reflect::GetTypeRegistration, + render::extract_component::{ + ExtractComponent, + ExtractComponentPlugin, + }, +}; + +use crate::PlanarTexture; + + +pub struct PlanarTexturePlugin { + phantom: PhantomData R>, +} +impl Default for PlanarTexturePlugin { + fn default() -> Self { + Self { + phantom: PhantomData, + } + } +} + +impl Plugin for PlanarTexturePlugin +where + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::PlanarType: Asset, +{ + fn build(&self, app: &mut App) { + app.register_type::(); + + app.add_plugins(ExtractComponentPlugin::::default()); + + app.add_systems(Update, 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), + ); + } + + 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)] +pub struct PlanarTextureLayouts { + 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::(); + + let bind_group_layout = R::bind_group_layout( + render_device, + ); + + PlanarTextureLayouts:: { + bind_group_layout, + phantom: PhantomData, + } + } +} + +fn prepare_textures( + mut commands: Commands, + asset_server: Res, + cloud_res: Res>, + mut images: ResMut>, + clouds: Query< + ( + Entity, + &Handle, + ), + Without, + >, +) +where + R: PlanarTexture + 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(); + + 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, + render_device: ResMut, + gpu_images: Res>, + bind_group_layout: Res>, + clouds: Query< + ( + Entity, + &R, + ), + Without>, + >, +) +where + R: PlanarTexture + Default + Component + ExtractComponent + GetTypeRegistration + Clone + Reflect, + R::PlanarType: Asset, +{ + 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, + layout, + ); + + commands.entity(entity).insert(PlanarTextureBindGroup:: { + bind_group, + phantom: PhantomData, + }); + } +} 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..ae1dd91 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() @@ -123,17 +123,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)* + ], ) } } diff --git a/crates/bevy_interleave_macros/src/bindings/texture.rs b/crates/bevy_interleave_macros/src/bindings/texture.rs index e69de29..e95e6ea 100644 --- a/crates/bevy_interleave_macros/src/bindings/texture.rs +++ b/crates/bevy_interleave_macros/src/bindings/texture.rs @@ -0,0 +1,280 @@ +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, +}; + + +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 get_asset_handles = generate_get_asset_handles_method(fields_struct); + + let expanded = quote! { + #[derive(bevy::prelude::Component, Default, 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 QueryData = &'static Self; + + type QueryFilter = (); + type Out = Self; + + fn extract_component(texture_buffers: bevy::ecs::query::QueryItem<'_, Self::QueryData>) -> Option { + texture_buffers.clone().into() + } + } + + impl PlanarTexture for #gpu_planar_name { + type PackedType = #name; + type PlanarType = #planar_name; + + #bind_group + #bind_group_layout + #prepare + #get_asset_handles + } + }; + + 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!("texture_{}_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::TextureView( + &gpu_images.get(&self.#name).unwrap().texture_view + ), + }, + } + }); + + quote! { + 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( + #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!("texture_{}_bind_group_layout", struct_name_snake); + + let bind_group_layout_entries = fields_named.named + .iter() + .enumerate() + .map(|(idx, field)| { + let name = field.ident.as_ref().unwrap(); + let format = extract_texture_format(&field.attrs); + + let field_type = &field.ty; + + quote! { + 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 view_dimension = if depth == 1 { + bevy::render::render_resource::TextureViewDimension::D2 // TODO: support 3D texture sampling + } 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::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, + ) -> bevy::render::render_resource::BindGroupLayout { + #(#bind_group_layout_entries)* + + render_device.create_bind_group_layout( + Some(#bind_group_layout_name), + &[ + #(#layout_names),* + ], + ) + } + } +} + + +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 format = extract_texture_format(&field.attrs); + + let field_type = &field.ty; + + quote! { + let square = (planar.#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 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, + height: square, + depth_or_array_layers: depth, + }, + bevy::render::render_resource::TextureDimension::D2, + data, + #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); + } + }); + + let buffer_names = fields_named.named + .iter() + .map(|field| { + let name = field.ident.as_ref().unwrap(); + quote! { #name } + }); + + quote! { + fn prepare( + images: &mut bevy::asset::Assets, + planar: &Self::PlanarType, + ) -> Self { + #(#buffers)* + + Self { + #(#buffer_names),* + } + } + } +} + + +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 { + 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 7bf4c44..00c920e 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, attributes(texture_format))] +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..96c85d9 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, @@ -39,16 +35,15 @@ 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/app.rs b/examples/app.rs new file mode 100644 index 0000000..7edf4b0 --- /dev/null +++ b/examples/app.rs @@ -0,0 +1,39 @@ +use bevy::prelude::*; + +use bevy_interleave::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], +} + + +fn main() { + let mut app = App::new(); + + app.add_plugins(( + DefaultPlugins, + PlanarPlugin::::default(), + PlanarTexturePlugin::::default(), + // TODO: PlanarStoragePlugin::::default(), + )); + + app.run(); +} diff --git a/examples/minimal.rs b/examples/minimal.rs index b85b320..6a88d96 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -6,11 +6,19 @@ use bevy_interleave::prelude::*; 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], } diff --git a/src/lib.rs b/src/lib.rs index 5817e50..229dd01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,3 +7,109 @@ 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 1f747ad..34bab6e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,11 +1,25 @@ +pub use bevy::render::{ + render_resource::TextureFormat, + texture::TextureFormatPixelInfo, +}; + + pub use crate::interface::{ - GpuStoragePlanar, - ReflectInterleaved, Planar, + PlanarStorage, + PlanarTexture, + planar::PlanarPlugin, + texture::{ + PlanarTextureBindGroup, + PlanarTextureLayouts, + PlanarTexturePlugin, + }, + ReflectInterleaved, }; pub use crate::macros::{ - StorageBindings, - ReflectInterleaved, Planar, + ReflectInterleaved, + StorageBindings, + TextureBindings, };