 54006b107b
			
		
	
	
		54006b107b
		
			
		
	
	
	
	
		
			
			# Objective A big step in the migration to required components: meshes and materials! ## Solution As per the [selected proposal](https://hackmd.io/@bevy/required_components/%2Fj9-PnF-2QKK0on1KQ29UWQ): - Deprecate `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle`. - Add `Mesh2d` and `Mesh3d` components, which wrap a `Handle<Mesh>`. - Add `MeshMaterial2d<M: Material2d>` and `MeshMaterial3d<M: Material>`, which wrap a `Handle<M>`. - Meshes *without* a mesh material should be rendered with a default material. The existence of a material is determined by `HasMaterial2d`/`HasMaterial3d`, which is required by `MeshMaterial2d`/`MeshMaterial3d`. This gets around problems with the generics. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, previously nothing was rendered. Now, it renders a white default `ColorMaterial` in 2D and a `StandardMaterial` in 3D (this can be overridden). Below, only every other entity has a material:   Why white? This is still open for discussion, but I think white makes sense for a *default* material, while *invalid* asset handles pointing to nothing should have something like a pink material to indicate that something is broken (I don't handle that in this PR yet). This is kind of a mix of Godot and Unity: Godot just renders a white material for non-existent materials, while Unity renders nothing when no materials exist, but renders pink for invalid materials. I can also change the default material to pink if that is preferable though. ## Testing I ran some 2D and 3D examples to test if anything changed visually. I have not tested all examples or features yet however. If anyone wants to test more extensively, it would be appreciated! ## Implementation Notes - The relationship between `bevy_render` and `bevy_pbr` is weird here. `bevy_render` needs `Mesh3d` for its own systems, but `bevy_pbr` has all of the material logic, and `bevy_render` doesn't depend on it. I feel like the two crates should be refactored in some way, but I think that's out of scope for this PR. - I didn't migrate meshlets to required components yet. That can probably be done in a follow-up, as this is already a huge PR. - It is becoming increasingly clear to me that we really, *really* want to disallow raw asset handles as components. They caused me a *ton* of headache here already, and it took me a long time to find every place that queried for them or inserted them directly on entities, since there were no compiler errors for it. If we don't remove the `Component` derive, I expect raw asset handles to be a *huge* footgun for users as we transition to wrapper components, especially as handles as components have been the norm so far. I personally consider this to be a blocker for 0.15: we need to migrate to wrapper components for asset handles everywhere, and remove the `Component` derive. Also see https://github.com/bevyengine/bevy/issues/14124. --- ## Migration Guide Asset handles for meshes and mesh materials must now be wrapped in the `Mesh2d` and `MeshMaterial2d` or `Mesh3d` and `MeshMaterial3d` components for 2D and 3D respectively. Raw handles as components no longer render meshes. Additionally, `MaterialMesh2dBundle`, `MaterialMeshBundle`, and `PbrBundle` have been deprecated. Instead, use the mesh and material components directly. Previously: ```rust commands.spawn(MaterialMesh2dBundle { mesh: meshes.add(Circle::new(100.0)).into(), material: materials.add(Color::srgb(7.5, 0.0, 7.5)), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), ..default() }); ``` Now: ```rust commands.spawn(( Mesh2d(meshes.add(Circle::new(100.0))), MeshMaterial2d(materials.add(Color::srgb(7.5, 0.0, 7.5))), Transform::from_translation(Vec3::new(-200., 0., 0.)), )); ``` If the mesh material is missing, a white default material is now used. Previously, nothing was rendered if the material was missing. The `WithMesh2d` and `WithMesh3d` query filter type aliases have also been removed. Simply use `With<Mesh2d>` or `With<Mesh3d>`. --------- Co-authored-by: Tim Blackbird <justthecooldude@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
		
			
				
	
	
		
			195 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			195 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! A shader that binds several textures onto one
 | |
| //! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.
 | |
| 
 | |
| use bevy::{
 | |
|     ecs::system::{lifetimeless::SRes, SystemParamItem},
 | |
|     prelude::*,
 | |
|     reflect::TypePath,
 | |
|     render::{
 | |
|         render_asset::RenderAssets,
 | |
|         render_resource::{
 | |
|             binding_types::{sampler, texture_2d},
 | |
|             *,
 | |
|         },
 | |
|         renderer::RenderDevice,
 | |
|         texture::{FallbackImage, GpuImage},
 | |
|         RenderApp,
 | |
|     },
 | |
| };
 | |
| use std::{num::NonZero, process::exit};
 | |
| 
 | |
| /// This example uses a shader source file from the assets subdirectory
 | |
| const SHADER_ASSET_PATH: &str = "shaders/texture_binding_array.wgsl";
 | |
| 
 | |
| fn main() {
 | |
|     let mut app = App::new();
 | |
|     app.add_plugins((
 | |
|         DefaultPlugins.set(ImagePlugin::default_nearest()),
 | |
|         GpuFeatureSupportChecker,
 | |
|         MaterialPlugin::<BindlessMaterial>::default(),
 | |
|     ))
 | |
|     .add_systems(Startup, setup)
 | |
|     .run();
 | |
| }
 | |
| 
 | |
| const MAX_TEXTURE_COUNT: usize = 16;
 | |
| const TILE_ID: [usize; 16] = [
 | |
|     19, 23, 4, 33, 12, 69, 30, 48, 10, 65, 40, 47, 57, 41, 44, 46,
 | |
| ];
 | |
| 
 | |
| struct GpuFeatureSupportChecker;
 | |
| 
 | |
| impl Plugin for GpuFeatureSupportChecker {
 | |
|     fn build(&self, _app: &mut App) {}
 | |
| 
 | |
|     fn finish(&self, app: &mut App) {
 | |
|         let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
 | |
|             return;
 | |
|         };
 | |
| 
 | |
|         let render_device = render_app.world().resource::<RenderDevice>();
 | |
| 
 | |
|         // Check if the device support the required feature. If not, exit the example.
 | |
|         // In a real application, you should setup a fallback for the missing feature
 | |
|         if !render_device
 | |
|             .features()
 | |
|             .contains(WgpuFeatures::SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING)
 | |
|         {
 | |
|             error!(
 | |
|                 "Render device doesn't support feature \
 | |
| SAMPLED_TEXTURE_AND_STORAGE_BUFFER_ARRAY_NON_UNIFORM_INDEXING, \
 | |
| which is required for texture binding arrays"
 | |
|             );
 | |
|             exit(1);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn setup(
 | |
|     mut commands: Commands,
 | |
|     mut meshes: ResMut<Assets<Mesh>>,
 | |
|     mut materials: ResMut<Assets<BindlessMaterial>>,
 | |
|     asset_server: Res<AssetServer>,
 | |
| ) {
 | |
|     commands.spawn(Camera3dBundle {
 | |
|         transform: Transform::from_xyz(2.0, 2.0, 2.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
 | |
|         ..default()
 | |
|     });
 | |
| 
 | |
|     // load 16 textures
 | |
|     let textures: Vec<_> = TILE_ID
 | |
|         .iter()
 | |
|         .map(|id| asset_server.load(format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png")))
 | |
|         .collect();
 | |
| 
 | |
|     // a cube with multiple textures
 | |
|     commands.spawn((
 | |
|         Mesh3d(meshes.add(Cuboid::default())),
 | |
|         MeshMaterial3d(materials.add(BindlessMaterial { textures })),
 | |
|     ));
 | |
| }
 | |
| 
 | |
| #[derive(Asset, TypePath, Debug, Clone)]
 | |
| struct BindlessMaterial {
 | |
|     textures: Vec<Handle<Image>>,
 | |
| }
 | |
| 
 | |
| impl AsBindGroup for BindlessMaterial {
 | |
|     type Data = ();
 | |
| 
 | |
|     type Param = (SRes<RenderAssets<GpuImage>>, SRes<FallbackImage>);
 | |
| 
 | |
|     fn as_bind_group(
 | |
|         &self,
 | |
|         layout: &BindGroupLayout,
 | |
|         render_device: &RenderDevice,
 | |
|         (image_assets, fallback_image): &mut SystemParamItem<'_, '_, Self::Param>,
 | |
|     ) -> Result<PreparedBindGroup<Self::Data>, AsBindGroupError> {
 | |
|         // retrieve the render resources from handles
 | |
|         let mut images = vec![];
 | |
|         for handle in self.textures.iter().take(MAX_TEXTURE_COUNT) {
 | |
|             match image_assets.get(handle) {
 | |
|                 Some(image) => images.push(image),
 | |
|                 None => return Err(AsBindGroupError::RetryNextUpdate),
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         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
 | |
|         let mut textures: Vec<_> = textures.into_iter().map(|texture| &**texture).collect();
 | |
| 
 | |
|         // fill in up to the first `MAX_TEXTURE_COUNT` textures and samplers to the arrays
 | |
|         for (id, image) in images.into_iter().enumerate() {
 | |
|             textures[id] = &*image.texture_view;
 | |
|         }
 | |
| 
 | |
|         let bind_group = render_device.create_bind_group(
 | |
|             "bindless_material_bind_group",
 | |
|             layout,
 | |
|             &BindGroupEntries::sequential((&textures[..], &fallback_image.sampler)),
 | |
|         );
 | |
| 
 | |
|         Ok(PreparedBindGroup {
 | |
|             bindings: vec![],
 | |
|             bind_group,
 | |
|             data: (),
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     fn unprepared_bind_group(
 | |
|         &self,
 | |
|         _layout: &BindGroupLayout,
 | |
|         _render_device: &RenderDevice,
 | |
|         _param: &mut SystemParamItem<'_, '_, Self::Param>,
 | |
|     ) -> Result<UnpreparedBindGroup<Self::Data>, AsBindGroupError> {
 | |
|         // we implement as_bind_group directly because
 | |
|         panic!("bindless texture arrays can't be owned")
 | |
|         // or rather, they can be owned, but then you can't make a `&'a [&'a TextureView]` from a vec of them in get_binding().
 | |
|     }
 | |
| 
 | |
|     fn bind_group_layout_entries(_: &RenderDevice) -> Vec<BindGroupLayoutEntry>
 | |
|     where
 | |
|         Self: Sized,
 | |
|     {
 | |
|         BindGroupLayoutEntries::with_indices(
 | |
|             // The layout entries will only be visible in the fragment stage
 | |
|             ShaderStages::FRAGMENT,
 | |
|             (
 | |
|                 // Screen texture
 | |
|                 //
 | |
|                 // @group(2) @binding(0) var textures: binding_array<texture_2d<f32>>;
 | |
|                 (
 | |
|                     0,
 | |
|                     texture_2d(TextureSampleType::Float { filterable: true })
 | |
|                         .count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),
 | |
|                 ),
 | |
|                 // Sampler
 | |
|                 //
 | |
|                 // @group(2) @binding(1) var nearest_sampler: sampler;
 | |
|                 //
 | |
|                 // Note: as with textures, multiple samplers can also be bound
 | |
|                 // onto one binding slot:
 | |
|                 //
 | |
|                 // ```
 | |
|                 // sampler(SamplerBindingType::Filtering)
 | |
|                 //     .count(NonZero::<u32>::new(MAX_TEXTURE_COUNT as u32).unwrap()),
 | |
|                 // ```
 | |
|                 //
 | |
|                 // One may need to pay attention to the limit of sampler binding
 | |
|                 // amount on some platforms.
 | |
|                 (1, sampler(SamplerBindingType::Filtering)),
 | |
|             ),
 | |
|         )
 | |
|         .to_vec()
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Material for BindlessMaterial {
 | |
|     fn fragment_shader() -> ShaderRef {
 | |
|         SHADER_ASSET_PATH.into()
 | |
|     }
 | |
| }
 |