diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index ce55b93d3ec68..a000b19a2c663 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -953,6 +953,7 @@ impl RenderAsset for PreparedMaterial { Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } + Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } } diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 25887495f41bf..8fa69d29f1a06 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -355,6 +355,20 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { 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 binding_impls.insert(0, quote! { ( @@ -362,7 +376,26 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { #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() + 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 { #fallback_image.sampler.clone() } diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 5c9be59de3dd6..6ba181d77d6ba 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -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_asset::{Asset, AssetEvent, AssetId, Assets}; use bevy_ecs::{ @@ -9,7 +11,10 @@ use bevy_ecs::{ }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; 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 std::marker::PhantomData; use thiserror::Error; @@ -18,6 +23,8 @@ use thiserror::Error; pub enum PrepareAssetError { #[error("Failed to prepare asset")] RetryNextUpdate(E), + #[error("Failed to build bind group: {0}")] + AsBindGroupError(AsBindGroupError), } /// Describes how an asset gets extracted and prepared for rendering. @@ -359,6 +366,12 @@ pub fn prepare_assets( Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((id, extracted_asset)); } + Err(PrepareAssetError::AsBindGroupError(e)) => { + error!( + "{} Bind group construction failed: {e}", + std::any::type_name::() + ); + } } } @@ -391,6 +404,12 @@ pub fn prepare_assets( Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((id, extracted_asset)); } + Err(PrepareAssetError::AsBindGroupError(e)) => { + error!( + "{} Bind group construction failed: {e}", + std::any::type_name::() + ); + } } } diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 39704525b780f..6371678e48b3d 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -352,6 +352,8 @@ pub enum AsBindGroupError { /// The bind group could not be generated. Try again next frame. #[error("The bind group could not be generated")] 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`]. diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 58a02ba13048c..849fd980fbc91 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -615,6 +615,7 @@ impl RenderAsset for PreparedMaterial2d { Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } + Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } } diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index e15dc4223e456..2eaa2f583bc4d 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -624,6 +624,7 @@ impl RenderAsset for PreparedUiMaterial { Err(AsBindGroupError::RetryNextUpdate) => { Err(PrepareAssetError::RetryNextUpdate(material)) } + Err(other) => Err(PrepareAssetError::AsBindGroupError(other)), } } }