 17edf4f7c7
			
		
	
	
		17edf4f7c7
		
			
		
	
	
	
	
		
			
			# Objective The `AssetServer` and `AssetProcessor` do a lot of `AssetPath` cloning (across many threads). To store the path on the handle, to store paths in dependency lists, to pass an owned path to the offloaded thread, to pass a path to the LoadContext, etc , etc. Cloning multiple string allocations multiple times like this will add up. It is worth optimizing this. Referenced in #9714 ## Solution Added a new `CowArc<T>` type to `bevy_util`, which behaves a lot like `Cow<T>`, but the Owned variant is an `Arc<T>`. Use this in place of `Cow<str>` and `Cow<Path>` on `AssetPath`. --- ## Changelog - `AssetPath` now internally uses `CowArc`, making clone operations much cheaper - `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` ## Migration Guide ```rust // Old AssetPath::new("logo.png", None); // New AssetPath::new("logo.png"); // Old AssetPath::new("scene.gltf", Some("Mesh0"); // New AssetPath::new("scene.gltf").with_label("Mesh0"); ``` `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com>
		
			
				
	
	
		
			182 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! A shader that binds several textures onto one
 | |
| //! `binding_array<texture<f32>>` shader binding slot and sample non-uniformly.
 | |
| 
 | |
| use bevy::{
 | |
|     prelude::*,
 | |
|     reflect::TypePath,
 | |
|     render::{
 | |
|         render_asset::RenderAssets,
 | |
|         render_resource::{AsBindGroupError, PreparedBindGroup, *},
 | |
|         renderer::RenderDevice,
 | |
|         texture::FallbackImage,
 | |
|         RenderApp,
 | |
|     },
 | |
| };
 | |
| use std::{num::NonZeroU32, process::exit};
 | |
| 
 | |
| 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 Ok(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::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(MaterialMeshBundle {
 | |
|         mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
 | |
|         material: materials.add(BindlessMaterial { textures }),
 | |
|         ..Default::default()
 | |
|     });
 | |
| }
 | |
| 
 | |
| #[derive(Asset, TypePath, Debug, Clone)]
 | |
| struct BindlessMaterial {
 | |
|     textures: Vec<Handle<Image>>,
 | |
| }
 | |
| 
 | |
| impl AsBindGroup for BindlessMaterial {
 | |
|     type Data = ();
 | |
| 
 | |
|     fn as_bind_group(
 | |
|         &self,
 | |
|         layout: &BindGroupLayout,
 | |
|         render_device: &RenderDevice,
 | |
|         image_assets: &RenderAssets<Image>,
 | |
|         fallback_image: &FallbackImage,
 | |
|     ) -> 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(&BindGroupDescriptor {
 | |
|             label: "bindless_material_bind_group".into(),
 | |
|             layout,
 | |
|             entries: &[
 | |
|                 BindGroupEntry {
 | |
|                     binding: 0,
 | |
|                     resource: BindingResource::TextureViewArray(&textures[..]),
 | |
|                 },
 | |
|                 BindGroupEntry {
 | |
|                     binding: 1,
 | |
|                     resource: BindingResource::Sampler(&fallback_image.sampler),
 | |
|                 },
 | |
|             ],
 | |
|         });
 | |
| 
 | |
|         Ok(PreparedBindGroup {
 | |
|             bindings: vec![],
 | |
|             bind_group,
 | |
|             data: (),
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout
 | |
|     where
 | |
|         Self: Sized,
 | |
|     {
 | |
|         render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
 | |
|             label: "bindless_material_layout".into(),
 | |
|             entries: &[
 | |
|                 // @group(1) @binding(0) var textures: binding_array<texture_2d<f32>>;
 | |
|                 BindGroupLayoutEntry {
 | |
|                     binding: 0,
 | |
|                     visibility: ShaderStages::FRAGMENT,
 | |
|                     ty: BindingType::Texture {
 | |
|                         sample_type: TextureSampleType::Float { filterable: true },
 | |
|                         view_dimension: TextureViewDimension::D2,
 | |
|                         multisampled: false,
 | |
|                     },
 | |
|                     count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
 | |
|                 },
 | |
|                 // @group(1) @binding(1) var nearest_sampler: sampler;
 | |
|                 BindGroupLayoutEntry {
 | |
|                     binding: 1,
 | |
|                     visibility: ShaderStages::FRAGMENT,
 | |
|                     ty: BindingType::Sampler(SamplerBindingType::Filtering),
 | |
|                     count: None,
 | |
|                     // Note: as textures, multiple samplers can also be bound onto one binding slot.
 | |
|                     // One may need to pay attention to the limit of sampler binding amount on some platforms.
 | |
|                     // count: NonZeroU32::new(MAX_TEXTURE_COUNT as u32),
 | |
|                 },
 | |
|             ],
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Material for BindlessMaterial {
 | |
|     fn fragment_shader() -> ShaderRef {
 | |
|         "shaders/texture_binding_array.wgsl".into()
 | |
|     }
 | |
| }
 |