Refactor AsBindGroup to use a associated SystemParam. (#14909)

# Objective

Adding more features to `AsBindGroup` proc macro means making the trait
arguments uglier. Downstream implementors of the trait without the proc
macro might want to do different things than our default arguments.

## Solution

Make `AsBindGroup` take an associated `Param` type.

## Migration Guide

`AsBindGroup` now allows the user to specify a `SystemParam` to be used
for creating bind groups.
This commit is contained in:
charlotte 2024-08-25 13:16:34 -07:00 committed by GitHub
parent 3892adcb47
commit 1caa64d948
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 44 additions and 65 deletions

View file

@ -1,14 +1,13 @@
use bevy_asset::{Asset, Handle}; use bevy_asset::{Asset, Handle};
use bevy_ecs::system::SystemParamItem;
use bevy_reflect::{impl_type_path, Reflect}; use bevy_reflect::{impl_type_path, Reflect};
use bevy_render::{ use bevy_render::{
mesh::MeshVertexBufferLayoutRef, mesh::MeshVertexBufferLayoutRef,
render_asset::RenderAssets,
render_resource::{ render_resource::{
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader, AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup, ShaderRef, SpecializedMeshPipelineError, UnpreparedBindGroup,
}, },
renderer::RenderDevice, renderer::RenderDevice,
texture::{FallbackImage, GpuImage},
}; };
use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey}; use crate::{Material, MaterialPipeline, MaterialPipelineKey, MeshPipeline, MeshPipelineKey};
@ -147,26 +146,21 @@ impl_type_path!((in bevy_pbr::extended_material) ExtendedMaterial<B: Material, E
impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> { impl<B: Material, E: MaterialExtension> AsBindGroup for ExtendedMaterial<B, E> {
type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data); type Data = (<B as AsBindGroup>::Data, <E as AsBindGroup>::Data);
type Param = (<B as AsBindGroup>::Param, <E as AsBindGroup>::Param);
fn unprepared_bind_group( fn unprepared_bind_group(
&self, &self,
layout: &BindGroupLayout, layout: &BindGroupLayout,
render_device: &RenderDevice, render_device: &RenderDevice,
images: &RenderAssets<GpuImage>, (base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>,
fallback_image: &FallbackImage,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> { ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
// add together the bindings of the base material and the user material // add together the bindings of the base material and the user material
let UnpreparedBindGroup { let UnpreparedBindGroup {
mut bindings, mut bindings,
data: base_data, data: base_data,
} = B::unprepared_bind_group(&self.base, layout, render_device, images, fallback_image)?; } = B::unprepared_bind_group(&self.base, layout, render_device, base_param)?;
let extended_bindgroup = E::unprepared_bind_group( let extended_bindgroup =
&self.extension, E::unprepared_bind_group(&self.extension, layout, render_device, extended_param)?;
layout,
render_device,
images,
fallback_image,
)?;
bindings.extend(extended_bindgroup.bindings); bindings.extend(extended_bindgroup.bindings);

View file

@ -30,7 +30,6 @@ use bevy_render::{
render_phase::*, render_phase::*,
render_resource::*, render_resource::*,
renderer::RenderDevice, renderer::RenderDevice,
texture::FallbackImage,
view::{ExtractedView, Msaa, RenderVisibilityRanges, VisibleEntities, WithMesh}, view::{ExtractedView, Msaa, RenderVisibilityRanges, VisibleEntities, WithMesh},
}; };
use bevy_utils::tracing::error; use bevy_utils::tracing::error;
@ -908,22 +907,16 @@ impl<M: Material> RenderAsset for PreparedMaterial<M> {
type Param = ( type Param = (
SRes<RenderDevice>, SRes<RenderDevice>,
SRes<RenderAssets<GpuImage>>,
SRes<FallbackImage>,
SRes<MaterialPipeline<M>>, SRes<MaterialPipeline<M>>,
SRes<DefaultOpaqueRendererMethod>, SRes<DefaultOpaqueRendererMethod>,
M::Param,
); );
fn prepare_asset( fn prepare_asset(
material: Self::SourceAsset, material: Self::SourceAsset,
(render_device, images, fallback_image, pipeline, default_opaque_render_method): &mut SystemParamItem<Self::Param>, (render_device, pipeline, default_opaque_render_method, ref mut material_param): &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> { ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
match material.as_bind_group( match material.as_bind_group(&pipeline.material_layout, render_device, material_param) {
&pipeline.material_layout,
render_device,
images,
fallback_image,
) {
Ok(prepared) => { Ok(prepared) => {
let method = match material.opaque_render_method() { let method = match material.opaque_render_method() {
OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward, OpaqueRendererMethod::Forward => OpaqueRendererMethod::Forward,

View file

@ -42,6 +42,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
let manifest = BevyManifest::default(); let manifest = BevyManifest::default();
let render_path = manifest.get_path("bevy_render"); let render_path = manifest.get_path("bevy_render");
let asset_path = manifest.get_path("bevy_asset"); let asset_path = manifest.get_path("bevy_asset");
let ecs_path = manifest.get_path("bevy_ecs");
let mut binding_states: Vec<BindingState> = Vec::new(); let mut binding_states: Vec<BindingState> = Vec::new();
let mut binding_impls = Vec::new(); let mut binding_impls = Vec::new();
@ -62,7 +63,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
binding_impls.push(quote! {{ binding_impls.push(quote! {{
use #render_path::render_resource::AsBindGroupShaderType; use #render_path::render_resource::AsBindGroupShaderType;
let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new()); let mut buffer = #render_path::render_resource::encase::UniformBuffer::new(Vec::new());
let converted: #converted_shader_type = self.as_bind_group_shader_type(images); let converted: #converted_shader_type = self.as_bind_group_shader_type(&images);
buffer.write(&converted).unwrap(); buffer.write(&converted).unwrap();
( (
#binding_index, #binding_index,
@ -523,6 +524,11 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause { impl #impl_generics #render_path::render_resource::AsBindGroup for #struct_name #ty_generics #where_clause {
type Data = #prepared_data; type Data = #prepared_data;
type Param = (
#ecs_path::system::lifetimeless::SRes<#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>>,
#ecs_path::system::lifetimeless::SRes<#render_path::texture::FallbackImage>,
);
fn label() -> Option<&'static str> { fn label() -> Option<&'static str> {
Some(#struct_name_literal) Some(#struct_name_literal)
} }
@ -531,8 +537,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
&self, &self,
layout: &#render_path::render_resource::BindGroupLayout, layout: &#render_path::render_resource::BindGroupLayout,
render_device: &#render_path::renderer::RenderDevice, render_device: &#render_path::renderer::RenderDevice,
images: &#render_path::render_asset::RenderAssets<#render_path::texture::GpuImage>, (images, fallback_image): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>,
fallback_image: &#render_path::texture::FallbackImage,
) -> Result<#render_path::render_resource::UnpreparedBindGroup<Self::Data>, #render_path::render_resource::AsBindGroupError> { ) -> Result<#render_path::render_resource::UnpreparedBindGroup<Self::Data>, #render_path::render_resource::AsBindGroupError> {
let bindings = vec![#(#binding_impls,)*]; let bindings = vec![#(#binding_impls,)*];

View file

@ -3,8 +3,9 @@ use crate::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_resource::{resource_macros::*, BindGroupLayout, Buffer, Sampler, TextureView}, render_resource::{resource_macros::*, BindGroupLayout, Buffer, Sampler, TextureView},
renderer::RenderDevice, renderer::RenderDevice,
texture::{FallbackImage, GpuImage}, texture::GpuImage,
}; };
use bevy_ecs::system::{SystemParam, SystemParamItem};
pub use bevy_render_macros::AsBindGroup; pub use bevy_render_macros::AsBindGroup;
use encase::ShaderType; use encase::ShaderType;
use std::ops::Deref; use std::ops::Deref;
@ -57,7 +58,7 @@ impl Deref for BindGroup {
/// ///
/// This is an opinionated trait that is intended to make it easy to generically /// This is an opinionated trait that is intended to make it easy to generically
/// convert a type into a [`BindGroup`]. It provides access to specific render resources, /// convert a type into a [`BindGroup`]. It provides access to specific render resources,
/// such as [`RenderAssets<GpuImage>`] and [`FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle), /// such as [`RenderAssets<GpuImage>`] and [`crate::texture::FallbackImage`]. If a type has a [`Handle<Image>`](bevy_asset::Handle),
/// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource. /// these can be used to retrieve the corresponding [`Texture`](crate::render_resource::Texture) resource.
/// ///
/// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally /// [`AsBindGroup::as_bind_group`] is intended to be called once, then the result cached somewhere. It is generally
@ -115,7 +116,7 @@ impl Deref for BindGroup {
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture) /// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
/// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice, /// GPU resource, which will be bound as a texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is /// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute /// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `sampler` binding attribute
/// (with a different binding index) if a binding of the sampler for the [`Image`](crate::texture::Image) is also required. /// (with a different binding index) if a binding of the sampler for the [`Image`](crate::texture::Image) is also required.
/// ///
/// | Arguments | Values | Default | /// | Arguments | Values | Default |
@ -130,7 +131,7 @@ impl Deref for BindGroup {
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture) /// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Texture`](crate::render_resource::Texture)
/// GPU resource, which will be bound as a storage texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice, /// GPU resource, which will be bound as a storage texture in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is /// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
/// [`None`], the [`FallbackImage`] resource will be used instead. /// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead.
/// ///
/// | Arguments | Values | Default | /// | Arguments | Values | Default |
/// |------------------------|--------------------------------------------------------------------------------------------|---------------| /// |------------------------|--------------------------------------------------------------------------------------------|---------------|
@ -143,7 +144,7 @@ impl Deref for BindGroup {
/// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`] GPU /// * This field's [`Handle<Image>`](bevy_asset::Handle) will be used to look up the matching [`Sampler`] GPU
/// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice, /// resource, which will be bound as a sampler in shaders. The field will be assumed to implement [`Into<Option<Handle<Image>>>`]. In practice,
/// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is /// most fields should be a [`Handle<Image>`](bevy_asset::Handle) or [`Option<Handle<Image>>`]. If the value of an [`Option<Handle<Image>>`] is
/// [`None`], the [`FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute /// [`None`], the [`crate::texture::FallbackImage`] resource will be used instead. This attribute can be used in conjunction with a `texture` binding attribute
/// (with a different binding index) if a binding of the texture for the [`Image`](crate::texture::Image) is also required. /// (with a different binding index) if a binding of the texture for the [`Image`](crate::texture::Image) is also required.
/// ///
/// | Arguments | Values | Default | /// | Arguments | Values | Default |
@ -187,7 +188,7 @@ impl Deref for BindGroup {
/// color_texture: Option<Handle<Image>>, /// color_texture: Option<Handle<Image>>,
/// } /// }
/// ``` /// ```
/// This is useful if you want a texture to be optional. When the value is [`None`], the [`FallbackImage`] will be used for the binding instead, which defaults /// This is useful if you want a texture to be optional. When the value is [`None`], the [`crate::texture::FallbackImage`] will be used for the binding instead, which defaults
/// to "pure white". /// to "pure white".
/// ///
/// Field uniforms with the same index will be combined into a single binding: /// Field uniforms with the same index will be combined into a single binding:
@ -284,6 +285,8 @@ pub trait AsBindGroup {
/// Data that will be stored alongside the "prepared" bind group. /// Data that will be stored alongside the "prepared" bind group.
type Data: Send + Sync; type Data: Send + Sync;
type Param: SystemParam + 'static;
/// label /// label
fn label() -> Option<&'static str> { fn label() -> Option<&'static str> {
None None
@ -294,11 +297,10 @@ pub trait AsBindGroup {
&self, &self,
layout: &BindGroupLayout, layout: &BindGroupLayout,
render_device: &RenderDevice, render_device: &RenderDevice,
images: &RenderAssets<GpuImage>, param: &mut SystemParamItem<'_, '_, Self::Param>,
fallback_image: &FallbackImage,
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> { ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
let UnpreparedBindGroup { bindings, data } = let UnpreparedBindGroup { bindings, data } =
Self::unprepared_bind_group(self, layout, render_device, images, fallback_image)?; Self::unprepared_bind_group(self, layout, render_device, param)?;
let entries = bindings let entries = bindings
.iter() .iter()
@ -325,8 +327,7 @@ pub trait AsBindGroup {
&self, &self,
layout: &BindGroupLayout, layout: &BindGroupLayout,
render_device: &RenderDevice, render_device: &RenderDevice,
images: &RenderAssets<GpuImage>, param: &mut SystemParamItem<'_, '_, Self::Param>,
fallback_image: &FallbackImage,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>; ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError>;
/// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`] /// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`]

View file

@ -28,7 +28,6 @@ use bevy_render::{
SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines,
}, },
renderer::RenderDevice, renderer::RenderDevice,
texture::{FallbackImage, GpuImage},
view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibleEntities}, view::{ExtractedView, InheritedVisibility, Msaa, ViewVisibility, Visibility, VisibleEntities},
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
}; };
@ -581,23 +580,13 @@ impl<T: Material2d> PreparedMaterial2d<T> {
impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> { impl<M: Material2d> RenderAsset for PreparedMaterial2d<M> {
type SourceAsset = M; type SourceAsset = M;
type Param = ( type Param = (SRes<RenderDevice>, SRes<Material2dPipeline<M>>, M::Param);
SRes<RenderDevice>,
SRes<RenderAssets<GpuImage>>,
SRes<FallbackImage>,
SRes<Material2dPipeline<M>>,
);
fn prepare_asset( fn prepare_asset(
material: Self::SourceAsset, material: Self::SourceAsset,
(render_device, images, fallback_image, pipeline): &mut SystemParamItem<Self::Param>, (render_device, pipeline, material_param): &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> { ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
match material.as_bind_group( match material.as_bind_group(&pipeline.material2d_layout, render_device, material_param) {
&pipeline.material2d_layout,
render_device,
images,
fallback_image,
) {
Ok(prepared) => { Ok(prepared) => {
let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty(); let mut mesh_pipeline_key_bits = Mesh2dPipelineKey::empty();
mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode())); mesh_pipeline_key_bits.insert(alpha_mode_pipeline_key(material.alpha_mode()));

View file

@ -16,7 +16,7 @@ use bevy_render::{
render_phase::*, render_phase::*,
render_resource::{binding_types::uniform_buffer, *}, render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderDevice, RenderQueue}, renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, FallbackImage, GpuImage}, texture::BevyDefault,
view::*, view::*,
Extract, ExtractSchedule, Render, RenderSet, Extract, ExtractSchedule, Render, RenderSet,
}; };
@ -604,18 +604,13 @@ pub struct PreparedUiMaterial<T: UiMaterial> {
impl<M: UiMaterial> RenderAsset for PreparedUiMaterial<M> { impl<M: UiMaterial> RenderAsset for PreparedUiMaterial<M> {
type SourceAsset = M; type SourceAsset = M;
type Param = ( type Param = (SRes<RenderDevice>, SRes<UiMaterialPipeline<M>>, M::Param);
SRes<RenderDevice>,
SRes<RenderAssets<GpuImage>>,
SRes<FallbackImage>,
SRes<UiMaterialPipeline<M>>,
);
fn prepare_asset( fn prepare_asset(
material: Self::SourceAsset, material: Self::SourceAsset,
(render_device, images, fallback_image, pipeline): &mut SystemParamItem<Self::Param>, (render_device, pipeline, ref mut material_param): &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> { ) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
match material.as_bind_group(&pipeline.ui_layout, render_device, images, fallback_image) { match material.as_bind_group(&pipeline.ui_layout, render_device, material_param) {
Ok(prepared) => Ok(PreparedUiMaterial { Ok(prepared) => Ok(PreparedUiMaterial {
bindings: prepared.bindings, bindings: prepared.bindings,
bind_group: prepared.bind_group, bind_group: prepared.bind_group,

View file

@ -1,6 +1,8 @@
//! A shader that binds several textures onto one //! A shader that binds several textures onto one
//! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly. //! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.
use bevy::ecs::system::lifetimeless::SRes;
use bevy::ecs::system::SystemParamItem;
use bevy::{ use bevy::{
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
@ -97,12 +99,13 @@ struct BindlessMaterial {
impl AsBindGroup for BindlessMaterial { impl AsBindGroup for BindlessMaterial {
type Data = (); type Data = ();
type Param = (SRes<RenderAssets<GpuImage>>, SRes<FallbackImage>);
fn as_bind_group( fn as_bind_group(
&self, &self,
layout: &BindGroupLayout, layout: &BindGroupLayout,
render_device: &RenderDevice, render_device: &RenderDevice,
image_assets: &RenderAssets<GpuImage>, (image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,
fallback_image: &FallbackImage,
) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> { ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
// retrieve the render resources from handles // retrieve the render resources from handles
let mut images = vec![]; let mut images = vec![];
@ -140,10 +143,9 @@ impl AsBindGroup for BindlessMaterial {
fn unprepared_bind_group( fn unprepared_bind_group(
&self, &self,
_: &BindGroupLayout, _layout: &BindGroupLayout,
_: &RenderDevice, _render_device: &RenderDevice,
_: &RenderAssets<GpuImage>, _param: &mut SystemParamItem<'_, '_, Self::Param>,
_: &FallbackImage,
) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> { ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
// we implement as_bind_group directly because // we implement as_bind_group directly because
panic!("bindless texture arrays can't be owned") panic!("bindless texture arrays can't be owned")