use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::core_2d::Transparent2d; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::{ prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::batching::no_gpu_preprocessing::{ self, batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, BatchedInstanceBuffer, }; use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef}; use bevy_render::{ batching::{GetBatchData, NoAutomaticBatching}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh}, render_asset::RenderAssets, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::{binding_types::uniform_buffer, *}, renderer::{RenderDevice, RenderQueue}, texture::{ BevyDefault, DefaultImageSampler, GpuImage, Image, ImageSampler, TextureFormatPixelInfo, }, view::{ ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, ViewVisibility, }, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; use crate::Material2dBindGroupId; /// Component for rendering with meshes in the 2d pipeline, usually with a [2d material](crate::Material2d) such as [`ColorMaterial`](crate::ColorMaterial). /// /// It wraps a [`Handle`] to differentiate from the 3d pipelines which use the handles directly as components #[derive(Default, Clone, Component, Debug, Reflect, PartialEq, Eq, Deref, DerefMut)] #[reflect(Default, Component)] pub struct Mesh2dHandle(pub Handle); impl From> for Mesh2dHandle { fn from(handle: Handle) -> Self { Self(handle) } } #[derive(Default)] pub struct Mesh2dRenderPlugin; pub const MESH2D_VERTEX_OUTPUT: Handle = Handle::weak_from_u128(7646632476603252194); pub const MESH2D_VIEW_TYPES_HANDLE: Handle = Handle::weak_from_u128(12677582416765805110); pub const MESH2D_VIEW_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(6901431444735842434); pub const MESH2D_TYPES_HANDLE: Handle = Handle::weak_from_u128(8994673400261890424); pub const MESH2D_BINDINGS_HANDLE: Handle = Handle::weak_from_u128(8983617858458862856); pub const MESH2D_FUNCTIONS_HANDLE: Handle = Handle::weak_from_u128(4976379308250389413); pub const MESH2D_SHADER_HANDLE: Handle = Handle::weak_from_u128(2971387252468633715); impl Plugin for Mesh2dRenderPlugin { fn build(&self, app: &mut bevy_app::App) { load_internal_asset!( app, MESH2D_VERTEX_OUTPUT, "mesh2d_vertex_output.wgsl", Shader::from_wgsl ); load_internal_asset!( app, MESH2D_VIEW_TYPES_HANDLE, "mesh2d_view_types.wgsl", Shader::from_wgsl ); load_internal_asset!( app, MESH2D_VIEW_BINDINGS_HANDLE, "mesh2d_view_bindings.wgsl", Shader::from_wgsl ); load_internal_asset!( app, MESH2D_TYPES_HANDLE, "mesh2d_types.wgsl", Shader::from_wgsl ); load_internal_asset!( app, MESH2D_FUNCTIONS_HANDLE, "mesh2d_functions.wgsl", Shader::from_wgsl ); load_internal_asset!(app, MESH2D_SHADER_HANDLE, "mesh2d.wgsl", Shader::from_wgsl); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() .init_resource::>() .add_systems(ExtractSchedule, extract_mesh2d) .add_systems( Render, ( batch_and_prepare_sorted_render_phase:: .in_set(RenderSet::PrepareResources), write_batched_instance_buffer:: .in_set(RenderSet::PrepareResourcesFlush), prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), no_gpu_preprocessing::clear_batched_cpu_instance_buffers:: .in_set(RenderSet::Cleanup) .after(RenderSet::Render), ), ); } } fn finish(&self, app: &mut bevy_app::App) { let mut mesh_bindings_shader_defs = Vec::with_capacity(1); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { let render_device = render_app.world().resource::(); let batched_instance_buffer = BatchedInstanceBuffer::::new(render_device); if let Some(per_object_buffer_batch_size) = GpuArrayBuffer::::batch_size(render_device) { mesh_bindings_shader_defs.push(ShaderDefVal::UInt( "PER_OBJECT_BUFFER_BATCH_SIZE".into(), per_object_buffer_batch_size, )); } render_app .insert_resource(batched_instance_buffer) .init_resource::(); } // Load the mesh_bindings shader module here as it depends on runtime information about // whether storage buffers are supported, or the maximum uniform buffer binding size. load_internal_asset!( app, MESH2D_BINDINGS_HANDLE, "mesh2d_bindings.wgsl", Shader::from_wgsl_with_defs, mesh_bindings_shader_defs ); } } #[derive(Component)] pub struct Mesh2dTransforms { pub transform: Affine3, pub flags: u32, } #[derive(ShaderType, Clone)] pub struct Mesh2dUniform { // Affine 4x3 matrix transposed to 3x4 pub transform: [Vec4; 3], // 3x3 matrix packed in mat2x4 and f32 as: // [0].xyz, [1].x, // [1].yz, [2].xy // [2].z pub inverse_transpose_model_a: [Vec4; 2], pub inverse_transpose_model_b: f32, pub flags: u32, } impl From<&Mesh2dTransforms> for Mesh2dUniform { fn from(mesh_transforms: &Mesh2dTransforms) -> Self { let (inverse_transpose_model_a, inverse_transpose_model_b) = mesh_transforms.transform.inverse_transpose_3x3(); Self { transform: mesh_transforms.transform.to_transpose(), inverse_transpose_model_a, inverse_transpose_model_b, flags: mesh_transforms.flags, } } } // NOTE: These must match the bit flags in bevy_sprite/src/mesh2d/mesh2d.wgsl! bitflags::bitflags! { #[repr(transparent)] pub struct MeshFlags: u32 { const NONE = 0; const UNINITIALIZED = 0xFFFF; } } pub struct RenderMesh2dInstance { pub transforms: Mesh2dTransforms, pub mesh_asset_id: AssetId, pub material_bind_group_id: Material2dBindGroupId, pub automatic_batching: bool, } #[derive(Default, Resource, Deref, DerefMut)] pub struct RenderMesh2dInstances(EntityHashMap); #[derive(Component)] pub struct Mesh2d; pub fn extract_mesh2d( mut commands: Commands, mut previous_len: Local, mut render_mesh_instances: ResMut, query: Extract< Query<( Entity, &ViewVisibility, &GlobalTransform, &Mesh2dHandle, Has, )>, >, ) { render_mesh_instances.clear(); let mut entities = Vec::with_capacity(*previous_len); for (entity, view_visibility, transform, handle, no_automatic_batching) in &query { if !view_visibility.get() { continue; } // FIXME: Remove this - it is just a workaround to enable rendering to work as // render commands require an entity to exist at the moment. entities.push((entity, Mesh2d)); render_mesh_instances.insert( entity, RenderMesh2dInstance { transforms: Mesh2dTransforms { transform: (&transform.affine()).into(), flags: MeshFlags::empty().bits(), }, mesh_asset_id: handle.0.id(), material_bind_group_id: Material2dBindGroupId::default(), automatic_batching: !no_automatic_batching, }, ); } *previous_len = entities.len(); commands.insert_or_spawn_batch(entities); } #[derive(Resource, Clone)] pub struct Mesh2dPipeline { pub view_layout: BindGroupLayout, pub mesh_layout: BindGroupLayout, // This dummy white texture is to be used in place of optional textures pub dummy_white_gpu_image: GpuImage, pub per_object_buffer_batch_size: Option, } impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { let mut system_state: SystemState<( Res, Res, Res, )> = SystemState::new(world); let (render_device, render_queue, default_sampler) = system_state.get_mut(world); let render_device = render_device.into_inner(); let view_layout = render_device.create_bind_group_layout( "mesh2d_view_layout", &BindGroupLayoutEntries::sequential( ShaderStages::VERTEX_FRAGMENT, ( // View uniform_buffer::(true), uniform_buffer::(false), ), ), ); let mesh_layout = render_device.create_bind_group_layout( "mesh2d_layout", &BindGroupLayoutEntries::single( ShaderStages::VERTEX_FRAGMENT, GpuArrayBuffer::::binding_layout(render_device), ), ); // A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures let dummy_white_gpu_image = { let image = Image::default(); let texture = render_device.create_texture(&image.texture_descriptor); let sampler = match image.sampler { ImageSampler::Default => (**default_sampler).clone(), ImageSampler::Descriptor(ref descriptor) => { render_device.create_sampler(&descriptor.as_wgpu()) } }; let format_size = image.texture_descriptor.format.pixel_size(); render_queue.write_texture( texture.as_image_copy(), &image.data, ImageDataLayout { offset: 0, bytes_per_row: Some(image.width() * format_size as u32), rows_per_image: None, }, image.texture_descriptor.size, ); let texture_view = texture.create_view(&TextureViewDescriptor::default()); GpuImage { texture, texture_view, texture_format: image.texture_descriptor.format, sampler, size: image.size(), mip_level_count: image.texture_descriptor.mip_level_count, } }; Mesh2dPipeline { view_layout, mesh_layout, dummy_white_gpu_image, per_object_buffer_batch_size: GpuArrayBuffer::::batch_size( render_device, ), } } } impl Mesh2dPipeline { pub fn get_image_texture<'a>( &'a self, gpu_images: &'a RenderAssets, handle_option: &Option>, ) -> Option<(&'a TextureView, &'a Sampler)> { if let Some(handle) = handle_option { let gpu_image = gpu_images.get(handle)?; Some((&gpu_image.texture_view, &gpu_image.sampler)) } else { Some(( &self.dummy_white_gpu_image.texture_view, &self.dummy_white_gpu_image.sampler, )) } } } impl GetBatchData for Mesh2dPipeline { type Param = SRes; type CompareData = (Material2dBindGroupId, AssetId); type BufferData = Mesh2dUniform; fn get_batch_data( mesh_instances: &SystemParamItem, entity: Entity, ) -> Option<(Self::BufferData, Option)> { let mesh_instance = mesh_instances.get(&entity)?; Some(( (&mesh_instance.transforms).into(), mesh_instance.automatic_batching.then_some(( mesh_instance.material_bind_group_id, mesh_instance.mesh_asset_id, )), )) } } bitflags::bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[repr(transparent)] // NOTE: Apparently quadro drivers support up to 64x MSAA. // MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. // FIXME: make normals optional? pub struct Mesh2dPipelineKey: u32 { const NONE = 0; const HDR = 1 << 0; const TONEMAP_IN_SHADER = 1 << 1; const DEBAND_DITHER = 1 << 2; const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS; const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS; } } impl Mesh2dPipelineKey { const MSAA_MASK_BITS: u32 = 0b111; const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3; const TONEMAP_METHOD_MASK_BITS: u32 = 0b111; const TONEMAP_METHOD_SHIFT_BITS: u32 = Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones(); pub fn from_msaa_samples(msaa_samples: u32) -> Self { let msaa_bits = (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; Self::from_bits_retain(msaa_bits) } pub fn from_hdr(hdr: bool) -> Self { if hdr { Mesh2dPipelineKey::HDR } else { Mesh2dPipelineKey::NONE } } pub fn msaa_samples(&self) -> u32 { 1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { let primitive_topology_bits = ((primitive_topology as u32) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; Self::from_bits_retain(primitive_topology_bits) } pub fn primitive_topology(&self) -> PrimitiveTopology { let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; match primitive_topology_bits { x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList, x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList, x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip, x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList, x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip, _ => PrimitiveTopology::default(), } } } impl SpecializedMeshPipeline for Mesh2dPipeline { type Key = Mesh2dPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); if layout.0.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) { shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); } if layout.0.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); } if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } if layout.0.contains(Mesh::ATTRIBUTE_COLOR) { shader_defs.push("VERTEX_COLORS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4)); } if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) { shader_defs.push("TONEMAP_IN_SHADER".into()); let method = key.intersection(Mesh2dPipelineKey::TONEMAP_METHOD_RESERVED_BITS); match method { Mesh2dPipelineKey::TONEMAP_METHOD_NONE => { shader_defs.push("TONEMAP_METHOD_NONE".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD => { shader_defs.push("TONEMAP_METHOD_REINHARD".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE => { shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED => { shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_AGX => { shader_defs.push("TONEMAP_METHOD_AGX".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM => { shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC => { shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into()); } Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE => { shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into()); } _ => {} } // Debanding is tied to tonemapping in the shader, cannot run without it. if key.contains(Mesh2dPipelineKey::DEBAND_DITHER) { shader_defs.push("DEBAND_DITHER".into()); } } let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?; let format = match key.contains(Mesh2dPipelineKey::HDR) { true => ViewTarget::TEXTURE_FORMAT_HDR, false => TextureFormat::bevy_default(), }; Ok(RenderPipelineDescriptor { vertex: VertexState { shader: MESH2D_SHADER_HANDLE, entry_point: "vertex".into(), shader_defs: shader_defs.clone(), buffers: vec![vertex_buffer_layout], }, fragment: Some(FragmentState { shader: MESH2D_SHADER_HANDLE, shader_defs, entry_point: "fragment".into(), targets: vec![Some(ColorTargetState { format, blend: Some(BlendState::ALPHA_BLENDING), write_mask: ColorWrites::ALL, })], }), layout: vec![self.view_layout.clone(), self.mesh_layout.clone()], push_constant_ranges: vec![], primitive: PrimitiveState { front_face: FrontFace::Ccw, cull_mode: None, unclipped_depth: false, polygon_mode: PolygonMode::Fill, conservative: false, topology: key.primitive_topology(), strip_index_format: None, }, depth_stencil: None, multisample: MultisampleState { count: key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, label: Some("transparent_mesh2d_pipeline".into()), }) } } #[derive(Resource)] pub struct Mesh2dBindGroup { pub value: BindGroup, } pub fn prepare_mesh2d_bind_group( mut commands: Commands, mesh2d_pipeline: Res, render_device: Res, mesh2d_uniforms: Res>, ) { if let Some(binding) = mesh2d_uniforms.instance_data_binding() { commands.insert_resource(Mesh2dBindGroup { value: render_device.create_bind_group( "mesh2d_bind_group", &mesh2d_pipeline.mesh_layout, &BindGroupEntries::single(binding), ), }); } } #[derive(Component)] pub struct Mesh2dViewBindGroup { pub value: BindGroup, } pub fn prepare_mesh2d_view_bind_groups( mut commands: Commands, render_device: Res, mesh2d_pipeline: Res, view_uniforms: Res, views: Query>, globals_buffer: Res, ) { if let (Some(view_binding), Some(globals)) = ( view_uniforms.uniforms.binding(), globals_buffer.buffer.binding(), ) { for entity in &views { let view_bind_group = render_device.create_bind_group( "mesh2d_view_bind_group", &mesh2d_pipeline.view_layout, &BindGroupEntries::sequential((view_binding.clone(), globals.clone())), ); commands.entity(entity).insert(Mesh2dViewBindGroup { value: view_bind_group, }); } } } pub struct SetMesh2dViewBindGroup; impl RenderCommand

for SetMesh2dViewBindGroup { type Param = (); type ViewQuery = (Read, Read); type ItemQuery = (); #[inline] fn render<'w>( _item: &P, (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewQuery>, _view: Option<()>, _param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { pass.set_bind_group(I, &mesh2d_view_bind_group.value, &[view_uniform.offset]); RenderCommandResult::Success } } pub struct SetMesh2dBindGroup; impl RenderCommand

for SetMesh2dBindGroup { type Param = SRes; type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let mut dynamic_offsets: [u32; 1] = Default::default(); let mut offset_count = 0; if let Some(dynamic_offset) = item.dynamic_offset() { dynamic_offsets[offset_count] = dynamic_offset.get(); offset_count += 1; } pass.set_bind_group( I, &mesh2d_bind_group.into_inner().value, &dynamic_offsets[..offset_count], ); RenderCommandResult::Success } } pub struct DrawMesh2d; impl RenderCommand

for DrawMesh2d { type Param = (SRes>, SRes); type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, (meshes, render_mesh2d_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let meshes = meshes.into_inner(); let render_mesh2d_instances = render_mesh2d_instances.into_inner(); let Some(RenderMesh2dInstance { mesh_asset_id, .. }) = render_mesh2d_instances.get(&item.entity()) else { return RenderCommandResult::Failure; }; let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else { return RenderCommandResult::Failure; }; pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); let batch_range = item.batch_range(); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, index_format, count, } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); pass.draw_indexed(0..*count, 0, batch_range.clone()); } GpuBufferInfo::NonIndexed => { pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success } }