use bevy_asset::AssetId;1use bevy_ecs::{2resource::Resource,3world::{FromWorld, World},4};5use bevy_log::error;6use bevy_mesh::{7morph::{MorphAttributes, MorphBuildError, MAX_MORPH_WEIGHTS, MAX_TEXTURE_WIDTH},8Mesh,9};10use bevy_platform::collections::HashMap;11use wgpu::{12Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,13TextureViewDescriptor,14};15use wgpu_types::TextureDataOrder;1617use crate::{18render_resource::{Buffer, Texture, TextureView},19renderer::{RenderDevice, RenderQueue},20};2122/// An image formatted for use with [`bevy_mesh::morph::MorphWeights`] for23/// rendering the morph target, containing the vertex displacements.24///25/// We only use these if storage buffers aren't supported on the current26/// platform. Otherwise, we store the mesh displacements in a storage buffer,27/// managed by the mesh allocator.28#[derive(Clone, Debug)]29pub struct MorphTargetImage {30/// The texture containing the vertex displacements.31pub texture: Texture,32/// A view into the texture, suitable for attaching to the vertex shader.33pub texture_view: TextureView,34}3536impl MorphTargetImage {37/// Generate textures for each morph target.38///39/// This accepts an "iterator of [`MorphAttributes`] iterators". Each item40/// iterated in the top level iterator corresponds "the attributes of a41/// specific morph target".42///43/// Each pixel of the texture is a component of morph target animated44/// attributes. So a set of 9 pixels is this morph's displacement for45/// position, normal and tangents of a single vertex (each taking 3 pixels).46pub fn new(47render_device: &RenderDevice,48render_queue: &RenderQueue,49targets: &[MorphAttributes],50vertex_count: usize,51) -> Result<Self, MorphBuildError> {52let max = MAX_TEXTURE_WIDTH;53let target_count = targets.len() / vertex_count;54if target_count > MAX_MORPH_WEIGHTS {55return Err(MorphBuildError::TooManyTargets { target_count });56}57let component_count = (vertex_count * MorphAttributes::COMPONENT_COUNT) as u32;58let Some((Rect(width, height), padding)) = lowest_2d(component_count, max) else {59return Err(MorphBuildError::TooManyAttributes {60vertex_count,61component_count,62});63};64let data: Vec<u8> = targets65.chunks(vertex_count)66.flat_map(|attributes| {67let layer_byte_count = (padding + component_count) as usize * size_of::<f32>();68let mut buffer = Vec::with_capacity(layer_byte_count);69for to_add in attributes {70buffer.extend_from_slice(bytemuck::bytes_of(&[71to_add.position,72to_add.normal,73to_add.tangent,74]));75}76// Pad each layer so that they fit width * height77buffer.extend(core::iter::repeat_n(0, padding as usize * size_of::<f32>()));78debug_assert_eq!(buffer.len(), layer_byte_count);79buffer80})81.collect();82let extents = Extent3d {83width,84height,85depth_or_array_layers: target_count as u32,86};87let texture = render_device.create_texture_with_data(88render_queue,89&TextureDescriptor {90label: Some("morph target image"),91size: extents,92mip_level_count: 1,93sample_count: 1,94dimension: TextureDimension::D3,95format: TextureFormat::R32Float,96usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,97view_formats: &[],98},99TextureDataOrder::LayerMajor,100&data,101);102let texture_view = texture.create_view(&TextureViewDescriptor {103label: Some("morph target texture view"),104..TextureViewDescriptor::default()105});106Ok(MorphTargetImage {107texture,108texture_view,109})110}111}112113/// Stores the images for all morph target displacement data, if the current114/// platform doesn't support storage buffers.115///116/// If the current platform does support storage buffers, the mesh allocator117/// stores displacement data instead.118#[derive(Resource)]119pub enum RenderMorphTargetAllocator {120/// The variant used when the current platform doesn't support storage121/// buffers.122Image {123/// Maps the ID of each mesh to the image containing its morph target124/// displacements.125mesh_id_to_image: HashMap<AssetId<Mesh>, MorphTargetImage>,126},127/// The variant used when the current platform does support storage buffers.128///129/// In this case, this resource is empty, because the mesh allocator stores130/// displacements instead.131Storage,132}133134impl FromWorld for RenderMorphTargetAllocator {135fn from_world(world: &mut World) -> RenderMorphTargetAllocator {136let render_device = world.resource::<RenderDevice>();137if bevy_render::storage_buffers_are_unsupported(&render_device.limits()) {138RenderMorphTargetAllocator::Image {139mesh_id_to_image: HashMap::default(),140}141} else {142RenderMorphTargetAllocator::Storage143}144}145}146147/// A reference to the resource in which morph displacements for a mesh are148/// stored.149#[derive(Clone, Copy)]150pub enum MorphTargetsResource<'a> {151/// The [`MorphTargetImage`].152///153/// This variant is used when storage buffers aren't supported on the154/// current platform.155Texture(&'a TextureView),156157/// The slab containing the morph target displacements.158///159/// This variant is used when storage buffers are supported on the current160/// platform.161Storage(&'a Buffer),162}163164impl RenderMorphTargetAllocator {165/// Allocates morph target displacements for the given mesh.166///167/// If storage buffers aren't supported on the current platform, this method168/// creates a new [`MorphTargetImage`] and stores it inside the allocator.169///170/// If storage buffers are supported on the current platform, this method171/// does nothing, as morph target displacements are instead managed by the172/// mesh allocator.173pub fn allocate(174&mut self,175render_device: &RenderDevice,176render_queue: &RenderQueue,177mesh_id: AssetId<Mesh>,178targets: &[MorphAttributes],179vertex_count: usize,180) {181match *self {182RenderMorphTargetAllocator::Image {183ref mut mesh_id_to_image,184} => {185if let Ok(morph_target_image) =186MorphTargetImage::new(render_device, render_queue, targets, vertex_count)187{188mesh_id_to_image.insert(mesh_id, morph_target_image);189}190}191192RenderMorphTargetAllocator::Storage => {193// Do nothing. Morph target displacements are managed by the194// mesh allocator in this case.195}196}197}198199/// Frees the storage associated with morph target displacements for the200/// mesh with the given ID.201///202/// If the current platform doesn't support storage buffers, this drops the203/// reference to the [`MorphTargetImage`] that stores the data for the204/// mesh's morph target displacements. If the current platform does support205/// storage buffers, this method does nothing, as morph target displacements206/// are managed by the mesh allocator in this case.207///208/// If passed a mesh without morph targets, this method does nothing.209pub fn free(&mut self, mesh_id: AssetId<Mesh>) {210match *self {211RenderMorphTargetAllocator::Image {212ref mut mesh_id_to_image,213} => {214if mesh_id_to_image.remove(&mesh_id).is_none() {215error!(216"Attempted to free a morph target allocation that wasn't allocated: {:?}",217mesh_id218);219}220}221RenderMorphTargetAllocator::Storage => {222// Do nothing. Morph target displacements are managed by the223// mesh allocator in this case.224}225}226}227228/// Returns the [`MorphTargetImage`] containing the packed morph target229/// displacements for the mesh with the given ID, if that image is present.230///231/// A [`MorphTargetImage`] is only available if storage buffers aren't232/// supported on the given platform. If storage buffers are supported, this233/// method returns `None`, as the mesh allocator stores the morph target234/// displacements in that case.235pub fn get_image(&self, mesh_id: AssetId<Mesh>) -> Option<MorphTargetImage> {236match *self {237RenderMorphTargetAllocator::Image {238ref mesh_id_to_image,239} => mesh_id_to_image.get(&mesh_id).cloned(),240RenderMorphTargetAllocator::Storage => None,241}242}243}244245struct Rect(u32, u32);246247/// Find the smallest rectangle of maximum edge size `max_edge` that contains248/// at least `min_includes` cells. `u32` is how many extra cells the rectangle249/// has.250///251/// The following rectangle contains 27 cells, and its longest edge is 9:252/// ```text253/// ----------------------------254/// |1 |2 |3 |4 |5 |6 |7 |8 |9 |255/// ----------------------------256/// |2 | | | | | | | | |257/// ----------------------------258/// |3 | | | | | | | | |259/// ----------------------------260/// ```261///262/// Returns `None` if `max_edge` is too small to build a rectangle263/// containing `min_includes` cells.264fn lowest_2d(min_includes: u32, max_edge: u32) -> Option<(Rect, u32)> {265(1..=max_edge)266.filter_map(|a| {267let b = min_includes.div_ceil(a);268let diff = (a * b).checked_sub(min_includes)?;269Some((Rect(a, b), diff))270})271.filter_map(|(rect, diff)| (rect.1 <= max_edge).then_some((rect, diff)))272.min_by_key(|(_, diff)| *diff)273}274275276