Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand FallbackImage to include a GpuImage for each possible TextureViewDimension #6974

Merged
merged 4 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
39 changes: 39 additions & 0 deletions assets/shaders/fallback_image_test.wgsl
Original file line number Diff line number Diff line change
@@ -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<f32>;
@group(1) @binding(1)
var test_texture_1d_sampler: sampler;

@group(1) @binding(2)
var test_texture_2d: texture_2d<f32>;
@group(1) @binding(3)
var test_texture_2d_sampler: sampler;

@group(1) @binding(4)
var test_texture_2d_array: texture_2d_array<f32>;
@group(1) @binding(5)
var test_texture_2d_array_sampler: sampler;

@group(1) @binding(6)
var test_texture_cube: texture_cube<f32>;
@group(1) @binding(7)
var test_texture_cube_sampler: sampler;

@group(1) @binding(8)
var test_texture_cube_array: texture_cube_array<f32>;
@group(1) @binding(9)
var test_texture_cube_array_sampler: sampler;

@group(1) @binding(10)
var test_texture_3d: texture_3d<f32>;
@group(1) @binding(11)
var test_texture_3d_sampler: sampler;

struct FragmentInput {
#import bevy_pbr::mesh_vertex_output
};

@fragment
fn fragment(in: FragmentInput) {}
45 changes: 41 additions & 4 deletions crates/bevy_render/macros/src/as_bind_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {

// 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;
Expand Down Expand Up @@ -255,18 +268,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
sample_type,
multisampled,
visibility,
} = get_texture_attrs(nested_meta_items)?;
} = tex_attrs.as_ref().unwrap();

let visibility =
visibility.hygenic_quote(&quote! { #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()
}
})
});
Expand All @@ -288,18 +303,24 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
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(&quote! { #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()
}
})
});
Expand Down Expand Up @@ -457,6 +478,22 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
}))
}

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
Expand Down Expand Up @@ -637,7 +674,7 @@ fn get_visibility_flag_value(meta: Meta) -> Result<ShaderStageVisibility> {
Ok(ShaderStageVisibility::Flags(visibility))
}

#[derive(Default)]
#[derive(Clone, Copy, Default)]
enum BindingTextureDimension {
D1,
#[default]
Expand Down
94 changes: 79 additions & 15 deletions crates/bevy_render/src/texture/fallback_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,8 +20,21 @@ 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 {
/// 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,
}

/// 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
Expand Down Expand Up @@ -54,14 +67,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 {
Expand Down Expand Up @@ -97,15 +114,62 @@ impl FromWorld for FallbackImage {
let render_device = world.resource::<RenderDevice>();
let render_queue = world.resource::<RenderQueue>();
let default_sampler = world.resource::<DefaultImageSampler>();
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,
),
}
}
}

Expand Down
77 changes: 77 additions & 0 deletions examples/shader/fallback_image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//! 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},
render::render_resource::{AsBindGroup, ShaderRef},
};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MaterialPlugin::<FallbackTestMaterial>::default())
.add_systems(Startup, setup)
.run();
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<FallbackTestMaterial>>,
) {
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<Handle<Image>>,

#[texture(2, dimension = "2d")]
#[sampler(3)]
image_2d: Option<Handle<Image>>,

#[texture(4, dimension = "2d_array")]
#[sampler(5)]
image_2d_array: Option<Handle<Image>>,

#[texture(6, dimension = "cube")]
#[sampler(7)]
image_cube: Option<Handle<Image>>,

#[texture(8, dimension = "cube_array")]
#[sampler(9)]
image_cube_array: Option<Handle<Image>>,

#[texture(10, dimension = "3d")]
#[sampler(11)]
image_3d: Option<Handle<Image>>,
}

impl Material for FallbackTestMaterial {
fn fragment_shader() -> ShaderRef {
"shaders/fallback_image_test.wgsl".into()
}
}
2 changes: 2 additions & 0 deletions examples/shader/texture_binding_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down