check sampler type in as_bind_group derives (#12637)

# Objective

currently if we use an image with the wrong sampler type in a material,
wgpu panics with an invalid texture format. turn this into a warning and
fail more gracefully.

## Solution

the expected sampler type is specified in the AsBindGroup derive, so we
can just check the image sampler is what it should be.

i am not totally sure about the mapping of image sampler type to
#[sampler(type)], i assumed:

```
    "filtering" => [ TextureSampleType::Float { filterable: true } ],
    "non_filtering" => [
        TextureSampleType::Float { filterable: false },
        TextureSampleType::Sint,
        TextureSampleType::Uint,
    ],
    "comparison" => [ TextureSampleType::Depth ],
```
This commit is contained in:
robtfm 2024-08-21 02:41:31 +01:00 committed by GitHub
parent f59a6a971a
commit 6e2f96f222
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 60 additions and 3 deletions

View file

@ -953,6 +953,7 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
Err(AsBindGroupError::RetryNextUpdate) => { Err(AsBindGroupError::RetryNextUpdate) => {
Err(PrepareAssetError::RetryNextUpdate(material)) Err(PrepareAssetError::RetryNextUpdate(material))
} }
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
} }
} }
} }

View file

@ -355,6 +355,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
let fallback_image = get_fallback_image(&render_path, *dimension); let fallback_image = get_fallback_image(&render_path, *dimension);
let expected_samplers = match sampler_binding_type {
SamplerBindingType::Filtering => {
quote!( [#render_path::render_resource::TextureSampleType::Float { filterable: true }] )
}
SamplerBindingType::NonFiltering => quote!([
#render_path::render_resource::TextureSampleType::Float { filterable: false },
#render_path::render_resource::TextureSampleType::Sint,
#render_path::render_resource::TextureSampleType::Uint,
]),
SamplerBindingType::Comparison => {
quote!( [#render_path::render_resource::TextureSampleType::Depth] )
}
};
// insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers // insert fallible texture-based entries at 0 so that if we fail here, we exit before allocating any buffers
binding_impls.insert(0, quote! { binding_impls.insert(0, quote! {
( (
@ -362,7 +376,26 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
#render_path::render_resource::OwnedBindingResource::Sampler({ #render_path::render_resource::OwnedBindingResource::Sampler({
let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into(); let handle: Option<&#asset_path::Handle<#render_path::texture::Image>> = (&self.#field_name).into();
if let Some(handle) = handle { if let Some(handle) = handle {
images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?.sampler.clone() let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?;
let Some(sample_type) = image.texture_format.sample_type(None, None) else {
return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(
#binding_index,
"None".to_string(),
format!("{:?}", #expected_samplers),
));
};
let valid = #expected_samplers.contains(&sample_type);
if !valid {
return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType(
#binding_index,
format!("{:?}", sample_type),
format!("{:?}", #expected_samplers),
));
}
image.sampler.clone()
} else { } else {
#fallback_image.sampler.clone() #fallback_image.sampler.clone()
} }

View file

@ -1,4 +1,6 @@
use crate::{ExtractSchedule, MainWorld, Render, RenderApp, RenderSet}; use crate::{
render_resource::AsBindGroupError, ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
};
use bevy_app::{App, Plugin, SubApp}; use bevy_app::{App, Plugin, SubApp};
use bevy_asset::{Asset, AssetEvent, AssetId, Assets}; use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
use bevy_ecs::{ use bevy_ecs::{
@ -9,7 +11,10 @@ use bevy_ecs::{
}; };
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render_macros::ExtractResource; use bevy_render_macros::ExtractResource;
use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_utils::{
tracing::{debug, error},
HashMap, HashSet,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::marker::PhantomData; use std::marker::PhantomData;
use thiserror::Error; use thiserror::Error;
@ -18,6 +23,8 @@ use thiserror::Error;
pub enum PrepareAssetError<E: Send + Sync + 'static> { pub enum PrepareAssetError<E: Send + Sync + 'static> {
#[error("Failed to prepare asset")] #[error("Failed to prepare asset")]
RetryNextUpdate(E), RetryNextUpdate(E),
#[error("Failed to build bind group: {0}")]
AsBindGroupError(AsBindGroupError),
} }
/// Describes how an asset gets extracted and prepared for rendering. /// Describes how an asset gets extracted and prepared for rendering.
@ -359,6 +366,12 @@ pub fn prepare_assets<A: RenderAsset>(
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((id, extracted_asset)); prepare_next_frame.assets.push((id, extracted_asset));
} }
Err(PrepareAssetError::AsBindGroupError(e)) => {
error!(
"{} Bind group construction failed: {e}",
std::any::type_name::<A>()
);
}
} }
} }
@ -391,6 +404,12 @@ pub fn prepare_assets<A: RenderAsset>(
Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
prepare_next_frame.assets.push((id, extracted_asset)); prepare_next_frame.assets.push((id, extracted_asset));
} }
Err(PrepareAssetError::AsBindGroupError(e)) => {
error!(
"{} Bind group construction failed: {e}",
std::any::type_name::<A>()
);
}
} }
} }

View file

@ -352,6 +352,8 @@ pub enum AsBindGroupError {
/// The bind group could not be generated. Try again next frame. /// The bind group could not be generated. Try again next frame.
#[error("The bind group could not be generated")] #[error("The bind group could not be generated")]
RetryNextUpdate, RetryNextUpdate,
#[error("At binding index{0}, the provided image sampler `{1}` does not match the required sampler type(s) `{2}`.")]
InvalidSamplerType(u32, String, String),
} }
/// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`]. /// A prepared bind group returned as a result of [`AsBindGroup::as_bind_group`].

View file

@ -615,6 +615,7 @@ impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
Err(AsBindGroupError::RetryNextUpdate) => { Err(AsBindGroupError::RetryNextUpdate) => {
Err(PrepareAssetError::RetryNextUpdate(material)) Err(PrepareAssetError::RetryNextUpdate(material))
} }
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
} }
} }
} }

View file

@ -624,6 +624,7 @@ impl<M: UiMaterial> RenderAsset for PreparedUiMaterial<M> {
Err(AsBindGroupError::RetryNextUpdate) => { Err(AsBindGroupError::RetryNextUpdate) => {
Err(PrepareAssetError::RetryNextUpdate(material)) Err(PrepareAssetError::RetryNextUpdate(material))
} }
Err(other) => Err(PrepareAssetError::AsBindGroupError(other)),
} }
} }
} }