From 06065a1c7661eabc5ae20878bc01f1b455aa2f1a Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 19 Mar 2023 19:36:52 -0700 Subject: [PATCH 1/4] add integration test example of FallbackImage --- Cargo.toml | 7 +++ assets/shaders/fallback_image_test.wgsl | 39 ++++++++++++++ examples/shader/fallback_image.rs | 72 +++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 assets/shaders/fallback_image_test.wgsl create mode 100644 examples/shader/fallback_image.rs diff --git a/Cargo.toml b/Cargo.toml index 4168bcbb78083..36b28ffd7b547 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2012,6 +2012,13 @@ hidden = true name = "window_resizing" path = "examples/window/window_resizing.rs" +[[example]] +name = "fallback_image" +path = "examples/shader/fallback_image.rs" + +[package.metadata.example.fallback_image] +hidden = true + [package.metadata.example.window_resizing] name = "Window Resizing" description = "Demonstrates resizing and responding to resizing a window" diff --git a/assets/shaders/fallback_image_test.wgsl b/assets/shaders/fallback_image_test.wgsl new file mode 100644 index 0000000000000..1ececdbfdf70e --- /dev/null +++ b/assets/shaders/fallback_image_test.wgsl @@ -0,0 +1,39 @@ +#import bevy_pbr::mesh_view_bindings +#import bevy_pbr::mesh_bindings + +@group(1) @binding(0) +var test_texture_1d: texture_1d; +@group(1) @binding(1) +var test_texture_1d_sampler: sampler; + +@group(1) @binding(2) +var test_texture_2d: texture_2d; +@group(1) @binding(3) +var test_texture_2d_sampler: sampler; + +@group(1) @binding(4) +var test_texture_2d_array: texture_2d_array; +@group(1) @binding(5) +var test_texture_2d_array_sampler: sampler; + +@group(1) @binding(6) +var test_texture_cube: texture_cube; +@group(1) @binding(7) +var test_texture_cube_sampler: sampler; + +@group(1) @binding(8) +var test_texture_cube_array: texture_cube_array; +@group(1) @binding(9) +var test_texture_cube_array_sampler: sampler; + +@group(1) @binding(10) +var test_texture_3d: texture_3d; +@group(1) @binding(11) +var test_texture_3d_sampler: sampler; + +struct FragmentInput { + #import bevy_pbr::mesh_vertex_output +}; + +@fragment +fn fragment(in: FragmentInput) {} diff --git a/examples/shader/fallback_image.rs b/examples/shader/fallback_image.rs new file mode 100644 index 0000000000000..746853a2158ae --- /dev/null +++ b/examples/shader/fallback_image.rs @@ -0,0 +1,72 @@ +use bevy::{ + prelude::*, + reflect::{TypePath, TypeUuid}, + render::render_resource::{AsBindGroup, ShaderRef}, +}; + +/// This example tests that all texture dimensions are supported by +/// `FallbackImage`. +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_plugin(MaterialPlugin::::default()) + .add_systems(Startup, setup) + .run(); +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + commands.spawn(MaterialMeshBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(FallbackTestMaterial { + image_1d: None, + image_2d: None, + image_2d_array: None, + image_cube: None, + image_cube_array: None, + image_3d: None, + }), + ..Default::default() + }); + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::new(1.5, 0.0, 0.0), Vec3::Y), + ..Default::default() + }); +} + +#[derive(AsBindGroup, Debug, Clone, TypePath, TypeUuid)] +#[uuid = "d4890167-0e16-4bfc-b812-434717f20409"] +struct FallbackTestMaterial { + #[texture(0, dimension = "1d")] + #[sampler(1)] + image_1d: Option>, + + #[texture(2, dimension = "2d")] + #[sampler(3)] + image_2d: Option>, + + #[texture(4, dimension = "2d_array")] + #[sampler(5)] + image_2d_array: Option>, + + #[texture(6, dimension = "cube")] + #[sampler(7)] + image_cube: Option>, + + #[texture(8, dimension = "cube_array")] + #[sampler(9)] + image_cube_array: Option>, + + #[texture(10, dimension = "3d")] + #[sampler(11)] + image_3d: Option>, +} + +impl Material for FallbackTestMaterial { + fn fragment_shader() -> ShaderRef { + "shaders/fallback_image_test.wgsl".into() + } +} From 6ebc0784cbc371865a237bf10cc5f2d69f4f80cf Mon Sep 17 00:00:00 2001 From: Duncan Fairbanks Date: Sun, 19 Mar 2023 15:06:45 -0700 Subject: [PATCH 2/4] expand FallbackImage into one image per TextureViewDimension variant --- .../bevy_render/macros/src/as_bind_group.rs | 45 +++++++++- .../bevy_render/src/texture/fallback_image.rs | 88 +++++++++++++++---- examples/shader/texture_binding_array.rs | 2 + 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 9970e0b93676d..5c24c608ea05b 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -117,6 +117,19 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { // Read field-level attributes for field in fields.iter() { + // Search ahead for texture attributes so we can use them with any + // corresponding sampler attribute. + let mut tex_attrs = None; + for attr in &field.attrs { + let Some(attr_ident) = attr.path().get_ident() else { + continue; + }; + if attr_ident == TEXTURE_ATTRIBUTE_NAME { + let (_binding_index, nested_meta_items) = get_binding_nested_attr(attr)?; + tex_attrs = Some(get_texture_attrs(nested_meta_items)?); + } + } + for attr in &field.attrs { let Some(attr_ident) = attr.path().get_ident() else { continue; @@ -255,18 +268,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { sample_type, multisampled, visibility, - } = get_texture_attrs(nested_meta_items)?; + } = tex_attrs.as_ref().unwrap(); let visibility = visibility.hygenic_quote("e! { #render_path::render_resource }); + let fallback_image = get_fallback_image(&render_path, *dimension); + binding_impls.push(quote! { #render_path::render_resource::OwnedBindingResource::TextureView({ let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.texture_view.clone() } else { - fallback_image.texture_view.clone() + #fallback_image.texture_view.clone() } }) }); @@ -288,18 +303,24 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let SamplerAttrs { sampler_binding_type, visibility, + .. } = get_sampler_attrs(nested_meta_items)?; + let TextureAttrs { dimension, .. } = tex_attrs + .as_ref() + .expect("sampler attribute must have matching texture attribute"); let visibility = visibility.hygenic_quote("e! { #render_path::render_resource }); + let fallback_image = get_fallback_image(&render_path, *dimension); + binding_impls.push(quote! { #render_path::render_resource::OwnedBindingResource::Sampler({ let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into(); if let Some(handle) = handle { images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.sampler.clone() } else { - fallback_image.sampler.clone() + #fallback_image.sampler.clone() } }) }); @@ -457,6 +478,22 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { })) } +fn get_fallback_image( + render_path: &syn::Path, + dimension: BindingTextureDimension, +) -> proc_macro2::TokenStream { + quote! { + match #render_path::render_resource::#dimension { + #render_path::render_resource::TextureViewDimension::D1 => &fallback_image.d1, + #render_path::render_resource::TextureViewDimension::D2 => &fallback_image.d2, + #render_path::render_resource::TextureViewDimension::D2Array => &fallback_image.d2_array, + #render_path::render_resource::TextureViewDimension::Cube => &fallback_image.cube, + #render_path::render_resource::TextureViewDimension::CubeArray => &fallback_image.cube_array, + #render_path::render_resource::TextureViewDimension::D3 => &fallback_image.d3, + } + } +} + /// Represents the arguments for the `uniform` binding attribute. /// /// If parsed, represents an attribute @@ -637,7 +674,7 @@ fn get_visibility_flag_value(meta: Meta) -> Result { Ok(ShaderStageVisibility::Flags(visibility)) } -#[derive(Default)] +#[derive(Clone, Copy, Default)] enum BindingTextureDimension { D1, #[default] diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index dedb266343570..5641d99f33e71 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ }; use bevy_math::Vec2; use bevy_utils::HashMap; -use wgpu::{Extent3d, TextureDimension, TextureFormat}; +use wgpu::{Extent3d, TextureFormat}; use crate::{ prelude::Image, @@ -20,8 +20,15 @@ use crate::{ /// /// Defaults to a 1x1 fully opaque white texture, (1.0, 1.0, 1.0, 1.0) which makes multiplying /// it with other colors a no-op. -#[derive(Resource, Deref)] -pub struct FallbackImage(GpuImage); +#[derive(Resource)] +pub struct FallbackImage { + pub d1: GpuImage, + pub d2: GpuImage, + pub d2_array: GpuImage, + pub cube: GpuImage, + pub cube_array: GpuImage, + pub d3: GpuImage, +} /// A [`RenderApp`](crate::RenderApp) resource that contains a _zero-filled_ "fallback image", /// which can be used in place of [`FallbackImage`], when a fully transparent or black fallback @@ -54,14 +61,18 @@ fn fallback_image_new( width: 1, height: 1, depth_or_array_layers: match dimension { - TextureViewDimension::Cube => 6, + TextureViewDimension::Cube | TextureViewDimension::CubeArray => 6, _ => 1, }, }; - let mut image = Image::new_fill(extents, TextureDimension::D2, &data, format); + let image_dimension = dimension.compatible_texture_dimension(); + + let mut image = Image::new_fill(extents, image_dimension, &data, format); image.texture_descriptor.sample_count = samples; - image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; + if image_dimension == TextureDimension::D2 { + image.texture_descriptor.usage |= TextureUsages::RENDER_ATTACHMENT; + } // We can't create textures with data when it's a depth texture or when using multiple samples let texture = if format.is_depth_stencil_format() || samples > 1 { @@ -97,15 +108,62 @@ impl FromWorld for FallbackImage { let render_device = world.resource::(); let render_queue = world.resource::(); let default_sampler = world.resource::(); - Self(fallback_image_new( - render_device, - render_queue, - default_sampler, - TextureFormat::bevy_default(), - TextureViewDimension::D2, - 1, - 255, - )) + Self { + d1: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::D1, + 1, + 255, + ), + d2: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::D2, + 1, + 255, + ), + d2_array: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::D2Array, + 1, + 255, + ), + cube: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::Cube, + 1, + 255, + ), + cube_array: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::CubeArray, + 1, + 255, + ), + d3: fallback_image_new( + render_device, + render_queue, + default_sampler, + TextureFormat::bevy_default(), + TextureViewDimension::D3, + 1, + 255, + ), + } } } diff --git a/examples/shader/texture_binding_array.rs b/examples/shader/texture_binding_array.rs index 9bc7d6efb0d1d..76c1c13e483eb 100644 --- a/examples/shader/texture_binding_array.rs +++ b/examples/shader/texture_binding_array.rs @@ -110,6 +110,8 @@ impl AsBindGroup for BindlessMaterial { } } + let fallback_image = &fallback_image.d2; + let textures = vec![&fallback_image.texture_view; MAX_TEXTURE_COUNT]; // convert bevy's resource types to WGPU's references From bdc3a51d07f9e275de73f9cc38dd34ced18f1786 Mon Sep 17 00:00:00 2001 From: Duncan Date: Sun, 11 Jun 2023 16:44:48 -0700 Subject: [PATCH 3/4] review feedback --- crates/bevy_render/src/texture/fallback_image.rs | 6 ++++++ examples/shader/fallback_image.rs | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 5641d99f33e71..24e91bc224dcc 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -22,11 +22,17 @@ use crate::{ /// it with other colors a no-op. #[derive(Resource)] pub struct FallbackImage { + /// Fallback image for [`TextureViewDimension::D1`]. pub d1: GpuImage, + /// Fallback image for [`TextureViewDimension::D2`]. pub d2: GpuImage, + /// Fallback image for [`TextureViewDimension::D2Array`]. pub d2_array: GpuImage, + /// Fallback image for [`TextureViewDimension::Cube`]. pub cube: GpuImage, + /// Fallback image for [`TextureViewDimension::CubeArray`]. pub cube_array: GpuImage, + /// Fallback image for [`TextureViewDimension::D3`]. pub d3: GpuImage, } diff --git a/examples/shader/fallback_image.rs b/examples/shader/fallback_image.rs index 746853a2158ae..1f928b9f1c0f6 100644 --- a/examples/shader/fallback_image.rs +++ b/examples/shader/fallback_image.rs @@ -1,11 +1,11 @@ +//! This example tests that all texture dimensions are supported by +//! `FallbackImage`. use bevy::{ prelude::*, reflect::{TypePath, TypeUuid}, render::render_resource::{AsBindGroup, ShaderRef}, }; -/// This example tests that all texture dimensions are supported by -/// `FallbackImage`. fn main() { App::new() .add_plugins(DefaultPlugins) From adf18d2943c1485cb5551263ae3d26ca1a7d4a89 Mon Sep 17 00:00:00 2001 From: Duncan Date: Mon, 19 Jun 2023 14:49:16 -0700 Subject: [PATCH 4/4] Add description of expected example behavior. --- examples/shader/fallback_image.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/shader/fallback_image.rs b/examples/shader/fallback_image.rs index 1f928b9f1c0f6..a3762b9fd0f00 100644 --- a/examples/shader/fallback_image.rs +++ b/examples/shader/fallback_image.rs @@ -1,5 +1,10 @@ //! This example tests that all texture dimensions are supported by //! `FallbackImage`. +//! +//! When running this example, you should expect to see a window that only draws +//! the clear color. The test material does not shade any geometry; this example +//! only tests that the images are initialized and bound so that the app does +//! not panic. use bevy::{ prelude::*, reflect::{TypePath, TypeUuid},