use bevy_app::Plugin; use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ core_2d::{AlphaMask2d, Camera2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT}, tonemapping::{ get_lut_bind_group_layout_entries, get_lut_bindings, Tonemapping, TonemappingLuts, }, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ entity::EntityHashMap, prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3, Vec4}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::batching::gpu_preprocessing::IndirectParameters; use bevy_render::batching::no_gpu_preprocessing::batch_and_prepare_binned_render_phase; use bevy_render::batching::no_gpu_preprocessing::{ self, batch_and_prepare_sorted_render_phase, write_batched_instance_buffer, BatchedInstanceBuffer, }; use bevy_render::batching::GetFullBatchData; use bevy_render::mesh::allocator::MeshAllocator; use bevy_render::mesh::{MeshVertexBufferLayoutRef, RenderMesh}; use bevy_render::texture::FallbackImage; use bevy_render::{ batching::{GetBatchData, NoAutomaticBatching}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{Mesh, RenderMeshBufferInfo}, 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 bevy_utils::tracing::error; use nonmax::NonMaxU32; 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_binned_render_phase:: .in_set(RenderSet::PrepareResources), batch_and_prepare_binned_render_phase:: .in_set(RenderSet::PrepareResources), 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 world_from_local: Affine3, pub flags: u32, } #[derive(ShaderType, Clone, Copy)] pub struct Mesh2dUniform { // Affine 4x3 matrix transposed to 3x4 pub world_from_local: [Vec4; 3], // 3x3 matrix packed in mat2x4 and f32 as: // [0].xyz, [1].x, // [1].yz, [2].xy // [2].z pub local_from_world_transpose_a: [Vec4; 2], pub local_from_world_transpose_b: f32, pub flags: u32, } impl From<&Mesh2dTransforms> for Mesh2dUniform { fn from(mesh_transforms: &Mesh2dTransforms) -> Self { let (local_from_world_transpose_a, local_from_world_transpose_b) = mesh_transforms.world_from_local.inverse_transpose_3x3(); Self { world_from_local: mesh_transforms.world_from_local.to_transpose(), local_from_world_transpose_a, local_from_world_transpose_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 { world_from_local: (&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 tonemapping_lut_entries = get_lut_bind_group_layout_entries(); let view_layout = render_device.create_bind_group_layout( "mesh2d_view_layout", &BindGroupLayoutEntries::with_indices( ShaderStages::VERTEX_FRAGMENT, ( (0, uniform_buffer::(true)), (1, uniform_buffer::(false)), ( 2, tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT), ), ( 3, tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT), ), ), ), ); 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, SRes>, 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, )), )) } } impl GetFullBatchData for Mesh2dPipeline { type BufferInputData = (); fn get_binned_batch_data( (mesh_instances, _, _): &SystemParamItem, entity: Entity, ) -> Option { let mesh_instance = mesh_instances.get(&entity)?; Some((&mesh_instance.transforms).into()) } fn get_index_and_compare_data( _: &SystemParamItem, _query_item: Entity, ) -> Option<(NonMaxU32, Option)> { error!( "`get_index_and_compare_data` is only intended for GPU mesh uniform building, \ but this is not yet implemented for 2d meshes" ); None } fn get_binned_index( _: &SystemParamItem, _query_item: Entity, ) -> Option { error!( "`get_binned_index` is only intended for GPU mesh uniform building, \ but this is not yet implemented for 2d meshes" ); None } fn get_batch_indirect_parameters_index( (mesh_instances, meshes, mesh_allocator): &SystemParamItem, indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::IndirectParametersBuffer, entity: Entity, instance_index: u32, ) -> Option { let mesh_instance = mesh_instances.get(&entity)?; let mesh = meshes.get(mesh_instance.mesh_asset_id)?; let vertex_buffer_slice = mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)?; // Note that `IndirectParameters` covers both of these structures, even // though they actually have distinct layouts. See the comment above that // type for more information. let indirect_parameters = match mesh.buffer_info { RenderMeshBufferInfo::Indexed { count: index_count, .. } => { let index_buffer_slice = mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id)?; IndirectParameters { vertex_or_index_count: index_count, instance_count: 0, first_vertex_or_first_index: index_buffer_slice.range.start, base_vertex_or_first_instance: vertex_buffer_slice.range.start, first_instance: instance_index, } } RenderMeshBufferInfo::NonIndexed => IndirectParameters { vertex_or_index_count: mesh.vertex_count, instance_count: 0, first_vertex_or_first_index: vertex_buffer_slice.range.start, base_vertex_or_first_instance: instance_index, first_instance: instance_index, }, }; (indirect_parameters_buffer.push(indirect_parameters) as u32) .try_into() .ok() } } 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 BLEND_ALPHA = 1 << 3; const MAY_DISCARD = 1 << 4; 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()); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(), 2, )); shader_defs.push(ShaderDefVal::UInt( "TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(), 3, )); 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()); } } if key.contains(Mesh2dPipelineKey::MAY_DISCARD) { shader_defs.push("MAY_DISCARD".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(), }; let (depth_write_enabled, label, blend); if key.contains(Mesh2dPipelineKey::BLEND_ALPHA) { label = "transparent_mesh2d_pipeline"; blend = Some(BlendState::ALPHA_BLENDING); depth_write_enabled = false; } else { label = "opaque_mesh2d_pipeline"; blend = None; depth_write_enabled = true; } 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, 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: Some(DepthStencilState { format: CORE_2D_DEPTH_FORMAT, depth_write_enabled, depth_compare: CompareFunction::GreaterEqual, stencil: StencilState { front: StencilFaceState::IGNORE, back: StencilFaceState::IGNORE, read_mask: 0, write_mask: 0, }, bias: DepthBiasState { constant: 0, slope_scale: 0.0, clamp: 0.0, }, }), multisample: MultisampleState { count: key.msaa_samples(), mask: !0, alpha_to_coverage_enabled: false, }, label: Some(label.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, } #[allow(clippy::too_many_arguments)] pub fn prepare_mesh2d_view_bind_groups( mut commands: Commands, render_device: Res, mesh2d_pipeline: Res, view_uniforms: Res, views: Query<(Entity, &Tonemapping), (With, With)>, globals_buffer: Res, tonemapping_luts: Res, images: Res>, fallback_image: Res, ) { let (Some(view_binding), Some(globals)) = ( view_uniforms.uniforms.binding(), globals_buffer.buffer.binding(), ) else { return; }; for (entity, tonemapping) in &views { let lut_bindings = get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image); let view_bind_group = render_device.create_bind_group( "mesh2d_view_bind_group", &mesh2d_pipeline.view_layout, &BindGroupEntries::with_indices(( (0, view_binding.clone()), (1, globals.clone()), (2, lut_bindings.0), (3, lut_bindings.1), )), ); 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.extra_index().as_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, SRes, ); type ViewQuery = (); type ItemQuery = (); #[inline] fn render<'w>( item: &P, _view: (), _item_query: Option<()>, (meshes, render_mesh2d_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let meshes = meshes.into_inner(); let render_mesh2d_instances = render_mesh2d_instances.into_inner(); let mesh_allocator = mesh_allocator.into_inner(); let Some(RenderMesh2dInstance { mesh_asset_id, .. }) = render_mesh2d_instances.get(&item.entity()) else { return RenderCommandResult::Skip; }; let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else { return RenderCommandResult::Skip; }; let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(mesh_asset_id) else { return RenderCommandResult::Skip; }; pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..)); let batch_range = item.batch_range(); match &gpu_mesh.buffer_info { RenderMeshBufferInfo::Indexed { index_format, count, } => { let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(mesh_asset_id) else { return RenderCommandResult::Skip; }; pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format); pass.draw_indexed( index_buffer_slice.range.start..(index_buffer_slice.range.start + count), vertex_buffer_slice.range.start as i32, batch_range.clone(), ); } RenderMeshBufferInfo::NonIndexed => { pass.draw(0..gpu_mesh.vertex_count, batch_range.clone()); } } RenderCommandResult::Success } }