Path: blob/main/crates/bevy_sprite_render/src/mesh2d/mesh.rs
6849 views
use bevy_app::Plugin;1use bevy_asset::{embedded_asset, load_embedded_asset, AssetId, AssetServer, Handle};2use bevy_camera::{visibility::ViewVisibility, Camera2d};3use bevy_render::RenderStartup;4use bevy_shader::{load_shader_library, Shader, ShaderDefVal, ShaderSettings};56use crate::{tonemapping_pipeline_key, Material2dBindGroupId};7use bevy_core_pipeline::{8core_2d::{AlphaMask2d, Opaque2d, Transparent2d, CORE_2D_DEPTH_FORMAT},9tonemapping::{10get_lut_bind_group_layout_entries, get_lut_bindings, DebandDither, Tonemapping,11TonemappingLuts,12},13};14use bevy_derive::{Deref, DerefMut};15use bevy_ecs::component::Tick;16use bevy_ecs::system::SystemChangeTick;17use bevy_ecs::{18prelude::*,19query::ROQueryItem,20system::{lifetimeless::*, SystemParamItem},21};22use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};23use bevy_math::{Affine3, Vec4};24use bevy_mesh::{Mesh, Mesh2d, MeshTag, MeshVertexBufferLayoutRef};25use bevy_render::prelude::Msaa;26use bevy_render::RenderSystems::PrepareAssets;27use bevy_render::{28batching::{29gpu_preprocessing::IndirectParametersCpuMetadata,30no_gpu_preprocessing::{31self, batch_and_prepare_binned_render_phase, batch_and_prepare_sorted_render_phase,32write_batched_instance_buffer, BatchedInstanceBuffer,33},34GetBatchData, GetFullBatchData, NoAutomaticBatching,35},36globals::{GlobalsBuffer, GlobalsUniform},37mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo},38render_asset::RenderAssets,39render_phase::{40sweep_old_entities, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,41TrackedRenderPass,42},43render_resource::{binding_types::uniform_buffer, *},44renderer::{RenderDevice, RenderQueue},45sync_world::{MainEntity, MainEntityHashMap},46texture::{DefaultImageSampler, FallbackImage, GpuImage},47view::{ExtractedView, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},48Extract, ExtractSchedule, Render, RenderApp, RenderSystems,49};50use bevy_transform::components::GlobalTransform;51use bevy_utils::default;52use nonmax::NonMaxU32;53use tracing::error;5455#[derive(Default)]56pub struct Mesh2dRenderPlugin;5758impl Plugin for Mesh2dRenderPlugin {59fn build(&self, app: &mut bevy_app::App) {60load_shader_library!(app, "mesh2d_vertex_output.wgsl");61load_shader_library!(app, "mesh2d_view_types.wgsl");62load_shader_library!(app, "mesh2d_view_bindings.wgsl");63load_shader_library!(app, "mesh2d_types.wgsl");64load_shader_library!(app, "mesh2d_functions.wgsl");6566embedded_asset!(app, "mesh2d.wgsl");6768// These bindings should be loaded as a shader library, but it depends on runtime69// information, so we will load it in a system.70embedded_asset!(app, "mesh2d_bindings.wgsl");7172if let Some(render_app) = app.get_sub_app_mut(RenderApp) {73render_app74.init_resource::<ViewKeyCache>()75.init_resource::<RenderMesh2dInstances>()76.init_resource::<SpecializedMeshPipelines<Mesh2dPipeline>>()77.init_resource::<ViewSpecializationTicks>()78.add_systems(79RenderStartup,80(81init_mesh_2d_pipeline,82init_batched_instance_buffer,83load_mesh2d_bindings,84),85)86.add_systems(ExtractSchedule, extract_mesh2d)87.add_systems(88Render,89(90check_views_need_specialization.in_set(PrepareAssets),91(92sweep_old_entities::<Opaque2d>,93sweep_old_entities::<AlphaMask2d>,94)95.in_set(RenderSystems::QueueSweep),96batch_and_prepare_binned_render_phase::<Opaque2d, Mesh2dPipeline>97.in_set(RenderSystems::PrepareResources),98batch_and_prepare_binned_render_phase::<AlphaMask2d, Mesh2dPipeline>99.in_set(RenderSystems::PrepareResources),100batch_and_prepare_sorted_render_phase::<Transparent2d, Mesh2dPipeline>101.in_set(RenderSystems::PrepareResources),102write_batched_instance_buffer::<Mesh2dPipeline>103.in_set(RenderSystems::PrepareResourcesFlush),104prepare_mesh2d_bind_group.in_set(RenderSystems::PrepareBindGroups),105prepare_mesh2d_view_bind_groups.in_set(RenderSystems::PrepareBindGroups),106no_gpu_preprocessing::clear_batched_cpu_instance_buffers::<Mesh2dPipeline>107.in_set(RenderSystems::Cleanup)108.after(RenderSystems::Render),109),110);111}112}113}114115#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]116pub struct ViewKeyCache(MainEntityHashMap<Mesh2dPipelineKey>);117118#[derive(Resource, Deref, DerefMut, Default, Debug, Clone)]119pub struct ViewSpecializationTicks(MainEntityHashMap<Tick>);120121pub fn check_views_need_specialization(122mut view_key_cache: ResMut<ViewKeyCache>,123mut view_specialization_ticks: ResMut<ViewSpecializationTicks>,124views: Query<(125&MainEntity,126&ExtractedView,127&Msaa,128Option<&Tonemapping>,129Option<&DebandDither>,130)>,131ticks: SystemChangeTick,132) {133for (view_entity, view, msaa, tonemapping, dither) in &views {134let mut view_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())135| Mesh2dPipelineKey::from_hdr(view.hdr);136137if !view.hdr {138if let Some(tonemapping) = tonemapping {139view_key |= Mesh2dPipelineKey::TONEMAP_IN_SHADER;140view_key |= tonemapping_pipeline_key(*tonemapping);141}142if let Some(DebandDither::Enabled) = dither {143view_key |= Mesh2dPipelineKey::DEBAND_DITHER;144}145}146147if !view_key_cache148.get_mut(view_entity)149.is_some_and(|current_key| *current_key == view_key)150{151view_key_cache.insert(*view_entity, view_key);152view_specialization_ticks.insert(*view_entity, ticks.this_run());153}154}155}156157pub fn init_batched_instance_buffer(mut commands: Commands, render_device: Res<RenderDevice>) {158commands.insert_resource(BatchedInstanceBuffer::<Mesh2dUniform>::new(&render_device));159}160161fn load_mesh2d_bindings(render_device: Res<RenderDevice>, asset_server: Res<AssetServer>) {162let mut mesh_bindings_shader_defs = Vec::with_capacity(1);163164if let Some(per_object_buffer_batch_size) =165GpuArrayBuffer::<Mesh2dUniform>::batch_size(&render_device)166{167mesh_bindings_shader_defs.push(ShaderDefVal::UInt(168"PER_OBJECT_BUFFER_BATCH_SIZE".into(),169per_object_buffer_batch_size,170));171}172173// Load the mesh_bindings shader module here as it depends on runtime information about174// whether storage buffers are supported, or the maximum uniform buffer binding size.175let handle: Handle<Shader> = load_embedded_asset!(176asset_server.as_ref(),177"mesh2d_bindings.wgsl",178move |settings| {179*settings = ShaderSettings {180shader_defs: mesh_bindings_shader_defs.clone(),181}182}183);184// Forget the handle so we don't have to store it anywhere, and we keep the embedded asset185// loaded. Note: This is what happens in `load_shader_library` internally.186core::mem::forget(handle);187}188189#[derive(Component)]190pub struct Mesh2dTransforms {191pub world_from_local: Affine3,192pub flags: u32,193}194195#[derive(ShaderType, Clone, Copy)]196pub struct Mesh2dUniform {197// Affine 4x3 matrix transposed to 3x4198pub world_from_local: [Vec4; 3],199// 3x3 matrix packed in mat2x4 and f32 as:200// [0].xyz, [1].x,201// [1].yz, [2].xy202// [2].z203pub local_from_world_transpose_a: [Vec4; 2],204pub local_from_world_transpose_b: f32,205pub flags: u32,206pub tag: u32,207}208209impl Mesh2dUniform {210fn from_components(mesh_transforms: &Mesh2dTransforms, tag: u32) -> Self {211let (local_from_world_transpose_a, local_from_world_transpose_b) =212mesh_transforms.world_from_local.inverse_transpose_3x3();213Self {214world_from_local: mesh_transforms.world_from_local.to_transpose(),215local_from_world_transpose_a,216local_from_world_transpose_b,217flags: mesh_transforms.flags,218tag,219}220}221}222223// NOTE: These must match the bit flags in bevy_sprite_render/src/mesh2d/mesh2d.wgsl!224bitflags::bitflags! {225#[repr(transparent)]226pub struct MeshFlags: u32 {227const NONE = 0;228const UNINITIALIZED = 0xFFFF;229}230}231232pub struct RenderMesh2dInstance {233pub transforms: Mesh2dTransforms,234pub mesh_asset_id: AssetId<Mesh>,235pub material_bind_group_id: Material2dBindGroupId,236pub automatic_batching: bool,237pub tag: u32,238}239240#[derive(Default, Resource, Deref, DerefMut)]241pub struct RenderMesh2dInstances(MainEntityHashMap<RenderMesh2dInstance>);242243#[derive(Component, Default)]244pub struct Mesh2dMarker;245246pub fn extract_mesh2d(247mut render_mesh_instances: ResMut<RenderMesh2dInstances>,248query: Extract<249Query<(250Entity,251&ViewVisibility,252&GlobalTransform,253&Mesh2d,254Option<&MeshTag>,255Has<NoAutomaticBatching>,256)>,257>,258) {259render_mesh_instances.clear();260261for (entity, view_visibility, transform, handle, tag, no_automatic_batching) in &query {262if !view_visibility.get() {263continue;264}265render_mesh_instances.insert(266entity.into(),267RenderMesh2dInstance {268transforms: Mesh2dTransforms {269world_from_local: (&transform.affine()).into(),270flags: MeshFlags::empty().bits(),271},272mesh_asset_id: handle.0.id(),273material_bind_group_id: Material2dBindGroupId::default(),274automatic_batching: !no_automatic_batching,275tag: tag.map_or(0, |i| **i),276},277);278}279}280281#[derive(Resource, Clone)]282pub struct Mesh2dPipeline {283pub view_layout: BindGroupLayout,284pub mesh_layout: BindGroupLayout,285pub shader: Handle<Shader>,286// This dummy white texture is to be used in place of optional textures287pub dummy_white_gpu_image: GpuImage,288pub per_object_buffer_batch_size: Option<u32>,289}290291pub fn init_mesh_2d_pipeline(292mut commands: Commands,293render_device: Res<RenderDevice>,294render_queue: Res<RenderQueue>,295default_sampler: Res<DefaultImageSampler>,296asset_server: Res<AssetServer>,297) {298let tonemapping_lut_entries = get_lut_bind_group_layout_entries();299let view_layout = render_device.create_bind_group_layout(300"mesh2d_view_layout",301&BindGroupLayoutEntries::sequential(302ShaderStages::VERTEX_FRAGMENT,303(304uniform_buffer::<ViewUniform>(true),305uniform_buffer::<GlobalsUniform>(false),306tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT),307tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT),308),309),310);311312let mesh_layout = render_device.create_bind_group_layout(313"mesh2d_layout",314&BindGroupLayoutEntries::single(315ShaderStages::VERTEX_FRAGMENT,316GpuArrayBuffer::<Mesh2dUniform>::binding_layout(&render_device),317),318);319// A 1x1x1 'all 1.0' texture to use as a dummy texture to use in place of optional StandardMaterial textures320let dummy_white_gpu_image = {321let image = Image::default();322let texture = render_device.create_texture(&image.texture_descriptor);323let sampler = match image.sampler {324ImageSampler::Default => (**default_sampler).clone(),325ImageSampler::Descriptor(ref descriptor) => {326render_device.create_sampler(&descriptor.as_wgpu())327}328};329330if let Ok(format_size) = image.texture_descriptor.format.pixel_size() {331render_queue.write_texture(332texture.as_image_copy(),333image.data.as_ref().expect("Image has no data"),334TexelCopyBufferLayout {335offset: 0,336bytes_per_row: Some(image.width() * format_size as u32),337rows_per_image: None,338},339image.texture_descriptor.size,340);341}342343let texture_view = texture.create_view(&TextureViewDescriptor::default());344GpuImage {345texture,346texture_view,347texture_format: image.texture_descriptor.format,348sampler,349size: image.texture_descriptor.size,350mip_level_count: image.texture_descriptor.mip_level_count,351}352};353commands.insert_resource(Mesh2dPipeline {354view_layout,355mesh_layout,356dummy_white_gpu_image,357per_object_buffer_batch_size: GpuArrayBuffer::<Mesh2dUniform>::batch_size(&render_device),358shader: load_embedded_asset!(asset_server.as_ref(), "mesh2d.wgsl"),359});360}361362impl Mesh2dPipeline {363pub fn get_image_texture<'a>(364&'a self,365gpu_images: &'a RenderAssets<GpuImage>,366handle_option: &Option<Handle<Image>>,367) -> Option<(&'a TextureView, &'a Sampler)> {368if let Some(handle) = handle_option {369let gpu_image = gpu_images.get(handle)?;370Some((&gpu_image.texture_view, &gpu_image.sampler))371} else {372Some((373&self.dummy_white_gpu_image.texture_view,374&self.dummy_white_gpu_image.sampler,375))376}377}378}379380impl GetBatchData for Mesh2dPipeline {381type Param = (382SRes<RenderMesh2dInstances>,383SRes<RenderAssets<RenderMesh>>,384SRes<MeshAllocator>,385);386type CompareData = (Material2dBindGroupId, AssetId<Mesh>);387type BufferData = Mesh2dUniform;388389fn get_batch_data(390(mesh_instances, _, _): &SystemParamItem<Self::Param>,391(_entity, main_entity): (Entity, MainEntity),392) -> Option<(Self::BufferData, Option<Self::CompareData>)> {393let mesh_instance = mesh_instances.get(&main_entity)?;394Some((395Mesh2dUniform::from_components(&mesh_instance.transforms, mesh_instance.tag),396mesh_instance.automatic_batching.then_some((397mesh_instance.material_bind_group_id,398mesh_instance.mesh_asset_id,399)),400))401}402}403404impl GetFullBatchData for Mesh2dPipeline {405type BufferInputData = ();406407fn get_binned_batch_data(408(mesh_instances, _, _): &SystemParamItem<Self::Param>,409main_entity: MainEntity,410) -> Option<Self::BufferData> {411let mesh_instance = mesh_instances.get(&main_entity)?;412Some(Mesh2dUniform::from_components(413&mesh_instance.transforms,414mesh_instance.tag,415))416}417418fn get_index_and_compare_data(419_: &SystemParamItem<Self::Param>,420_query_item: MainEntity,421) -> Option<(NonMaxU32, Option<Self::CompareData>)> {422error!(423"`get_index_and_compare_data` is only intended for GPU mesh uniform building, \424but this is not yet implemented for 2d meshes"425);426None427}428429fn get_binned_index(430_: &SystemParamItem<Self::Param>,431_query_item: MainEntity,432) -> Option<NonMaxU32> {433error!(434"`get_binned_index` is only intended for GPU mesh uniform building, \435but this is not yet implemented for 2d meshes"436);437None438}439440fn write_batch_indirect_parameters_metadata(441indexed: bool,442base_output_index: u32,443batch_set_index: Option<NonMaxU32>,444indirect_parameters_buffer: &mut bevy_render::batching::gpu_preprocessing::UntypedPhaseIndirectParametersBuffers,445indirect_parameters_offset: u32,446) {447// Note that `IndirectParameters` covers both of these structures, even448// though they actually have distinct layouts. See the comment above that449// type for more information.450let indirect_parameters = IndirectParametersCpuMetadata {451base_output_index,452batch_set_index: match batch_set_index {453None => !0,454Some(batch_set_index) => u32::from(batch_set_index),455},456};457458if indexed {459indirect_parameters_buffer460.indexed461.set(indirect_parameters_offset, indirect_parameters);462} else {463indirect_parameters_buffer464.non_indexed465.set(indirect_parameters_offset, indirect_parameters);466}467}468}469470bitflags::bitflags! {471#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]472#[repr(transparent)]473// NOTE: Apparently quadro drivers support up to 64x MSAA.474// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.475// FIXME: make normals optional?476pub struct Mesh2dPipelineKey: u32 {477const NONE = 0;478const HDR = 1 << 0;479const TONEMAP_IN_SHADER = 1 << 1;480const DEBAND_DITHER = 1 << 2;481const BLEND_ALPHA = 1 << 3;482const MAY_DISCARD = 1 << 4;483const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;484const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS;485const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;486const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS;487const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS;488const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS;489const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;490const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;491const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;492const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;493const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;494}495}496497impl Mesh2dPipelineKey {498const MSAA_MASK_BITS: u32 = 0b111;499const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();500const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;501const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3;502const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;503const TONEMAP_METHOD_SHIFT_BITS: u32 =504Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();505506pub fn from_msaa_samples(msaa_samples: u32) -> Self {507let msaa_bits =508(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;509Self::from_bits_retain(msaa_bits)510}511512pub fn from_hdr(hdr: bool) -> Self {513if hdr {514Mesh2dPipelineKey::HDR515} else {516Mesh2dPipelineKey::NONE517}518}519520pub fn msaa_samples(&self) -> u32 {5211 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)522}523524pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self {525let primitive_topology_bits = ((primitive_topology as u32)526& Self::PRIMITIVE_TOPOLOGY_MASK_BITS)527<< Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS;528Self::from_bits_retain(primitive_topology_bits)529}530531pub fn primitive_topology(&self) -> PrimitiveTopology {532let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS)533& Self::PRIMITIVE_TOPOLOGY_MASK_BITS;534match primitive_topology_bits {535x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList,536x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList,537x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip,538x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList,539x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip,540_ => PrimitiveTopology::default(),541}542}543}544545impl SpecializedMeshPipeline for Mesh2dPipeline {546type Key = Mesh2dPipelineKey;547548fn specialize(549&self,550key: Self::Key,551layout: &MeshVertexBufferLayoutRef,552) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {553let mut shader_defs = Vec::new();554let mut vertex_attributes = Vec::new();555556if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {557shader_defs.push("VERTEX_POSITIONS".into());558vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));559}560561if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {562shader_defs.push("VERTEX_NORMALS".into());563vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1));564}565566if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {567shader_defs.push("VERTEX_UVS".into());568vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2));569}570571if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {572shader_defs.push("VERTEX_TANGENTS".into());573vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3));574}575576if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {577shader_defs.push("VERTEX_COLORS".into());578vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(4));579}580581if key.contains(Mesh2dPipelineKey::TONEMAP_IN_SHADER) {582shader_defs.push("TONEMAP_IN_SHADER".into());583shader_defs.push(ShaderDefVal::UInt(584"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),5852,586));587shader_defs.push(ShaderDefVal::UInt(588"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),5893,590));591592let method = key.intersection(Mesh2dPipelineKey::TONEMAP_METHOD_RESERVED_BITS);593594match method {595Mesh2dPipelineKey::TONEMAP_METHOD_NONE => {596shader_defs.push("TONEMAP_METHOD_NONE".into());597}598Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD => {599shader_defs.push("TONEMAP_METHOD_REINHARD".into());600}601Mesh2dPipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE => {602shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());603}604Mesh2dPipelineKey::TONEMAP_METHOD_ACES_FITTED => {605shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());606}607Mesh2dPipelineKey::TONEMAP_METHOD_AGX => {608shader_defs.push("TONEMAP_METHOD_AGX".into());609}610Mesh2dPipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM => {611shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());612}613Mesh2dPipelineKey::TONEMAP_METHOD_BLENDER_FILMIC => {614shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());615}616Mesh2dPipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE => {617shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());618}619_ => {}620}621// Debanding is tied to tonemapping in the shader, cannot run without it.622if key.contains(Mesh2dPipelineKey::DEBAND_DITHER) {623shader_defs.push("DEBAND_DITHER".into());624}625}626627if key.contains(Mesh2dPipelineKey::MAY_DISCARD) {628shader_defs.push("MAY_DISCARD".into());629}630631let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;632633let format = match key.contains(Mesh2dPipelineKey::HDR) {634true => ViewTarget::TEXTURE_FORMAT_HDR,635false => TextureFormat::bevy_default(),636};637638let (depth_write_enabled, label, blend);639if key.contains(Mesh2dPipelineKey::BLEND_ALPHA) {640label = "transparent_mesh2d_pipeline";641blend = Some(BlendState::ALPHA_BLENDING);642depth_write_enabled = false;643} else {644label = "opaque_mesh2d_pipeline";645blend = None;646depth_write_enabled = true;647}648649Ok(RenderPipelineDescriptor {650vertex: VertexState {651shader: self.shader.clone(),652shader_defs: shader_defs.clone(),653buffers: vec![vertex_buffer_layout],654..default()655},656fragment: Some(FragmentState {657shader: self.shader.clone(),658shader_defs,659targets: vec![Some(ColorTargetState {660format,661blend,662write_mask: ColorWrites::ALL,663})],664..default()665}),666layout: vec![self.view_layout.clone(), self.mesh_layout.clone()],667primitive: PrimitiveState {668front_face: FrontFace::Ccw,669cull_mode: None,670unclipped_depth: false,671polygon_mode: PolygonMode::Fill,672conservative: false,673topology: key.primitive_topology(),674strip_index_format: None,675},676depth_stencil: Some(DepthStencilState {677format: CORE_2D_DEPTH_FORMAT,678depth_write_enabled,679depth_compare: CompareFunction::GreaterEqual,680stencil: StencilState {681front: StencilFaceState::IGNORE,682back: StencilFaceState::IGNORE,683read_mask: 0,684write_mask: 0,685},686bias: DepthBiasState {687constant: 0,688slope_scale: 0.0,689clamp: 0.0,690},691}),692multisample: MultisampleState {693count: key.msaa_samples(),694mask: !0,695alpha_to_coverage_enabled: false,696},697label: Some(label.into()),698..default()699})700}701}702703#[derive(Resource)]704pub struct Mesh2dBindGroup {705pub value: BindGroup,706}707708pub fn prepare_mesh2d_bind_group(709mut commands: Commands,710mesh2d_pipeline: Res<Mesh2dPipeline>,711render_device: Res<RenderDevice>,712mesh2d_uniforms: Res<BatchedInstanceBuffer<Mesh2dUniform>>,713) {714if let Some(binding) = mesh2d_uniforms.instance_data_binding() {715commands.insert_resource(Mesh2dBindGroup {716value: render_device.create_bind_group(717"mesh2d_bind_group",718&mesh2d_pipeline.mesh_layout,719&BindGroupEntries::single(binding),720),721});722}723}724725#[derive(Component)]726pub struct Mesh2dViewBindGroup {727pub value: BindGroup,728}729730pub fn prepare_mesh2d_view_bind_groups(731mut commands: Commands,732render_device: Res<RenderDevice>,733mesh2d_pipeline: Res<Mesh2dPipeline>,734view_uniforms: Res<ViewUniforms>,735views: Query<(Entity, &Tonemapping), (With<ExtractedView>, With<Camera2d>)>,736globals_buffer: Res<GlobalsBuffer>,737tonemapping_luts: Res<TonemappingLuts>,738images: Res<RenderAssets<GpuImage>>,739fallback_image: Res<FallbackImage>,740) {741let (Some(view_binding), Some(globals)) = (742view_uniforms.uniforms.binding(),743globals_buffer.buffer.binding(),744) else {745return;746};747748for (entity, tonemapping) in &views {749let lut_bindings =750get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);751let view_bind_group = render_device.create_bind_group(752"mesh2d_view_bind_group",753&mesh2d_pipeline.view_layout,754&BindGroupEntries::sequential((755view_binding.clone(),756globals.clone(),757lut_bindings.0,758lut_bindings.1,759)),760);761762commands.entity(entity).insert(Mesh2dViewBindGroup {763value: view_bind_group,764});765}766}767768pub struct SetMesh2dViewBindGroup<const I: usize>;769impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMesh2dViewBindGroup<I> {770type Param = ();771type ViewQuery = (Read<ViewUniformOffset>, Read<Mesh2dViewBindGroup>);772type ItemQuery = ();773774#[inline]775fn render<'w>(776_item: &P,777(view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>,778_view: Option<()>,779_param: SystemParamItem<'w, '_, Self::Param>,780pass: &mut TrackedRenderPass<'w>,781) -> RenderCommandResult {782pass.set_bind_group(I, &mesh2d_view_bind_group.value, &[view_uniform.offset]);783784RenderCommandResult::Success785}786}787788pub struct SetMesh2dBindGroup<const I: usize>;789impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMesh2dBindGroup<I> {790type Param = SRes<Mesh2dBindGroup>;791type ViewQuery = ();792type ItemQuery = ();793794#[inline]795fn render<'w>(796item: &P,797_view: (),798_item_query: Option<()>,799mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>,800pass: &mut TrackedRenderPass<'w>,801) -> RenderCommandResult {802let mut dynamic_offsets: [u32; 1] = Default::default();803let mut offset_count = 0;804if let PhaseItemExtraIndex::DynamicOffset(dynamic_offset) = item.extra_index() {805dynamic_offsets[offset_count] = dynamic_offset;806offset_count += 1;807}808pass.set_bind_group(809I,810&mesh2d_bind_group.into_inner().value,811&dynamic_offsets[..offset_count],812);813RenderCommandResult::Success814}815}816817pub struct DrawMesh2d;818impl<P: PhaseItem> RenderCommand<P> for DrawMesh2d {819type Param = (820SRes<RenderAssets<RenderMesh>>,821SRes<RenderMesh2dInstances>,822SRes<MeshAllocator>,823);824type ViewQuery = ();825type ItemQuery = ();826827#[inline]828fn render<'w>(829item: &P,830_view: (),831_item_query: Option<()>,832(meshes, render_mesh2d_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>,833pass: &mut TrackedRenderPass<'w>,834) -> RenderCommandResult {835let meshes = meshes.into_inner();836let render_mesh2d_instances = render_mesh2d_instances.into_inner();837let mesh_allocator = mesh_allocator.into_inner();838839let Some(RenderMesh2dInstance { mesh_asset_id, .. }) =840render_mesh2d_instances.get(&item.main_entity())841else {842return RenderCommandResult::Skip;843};844let Some(gpu_mesh) = meshes.get(*mesh_asset_id) else {845return RenderCommandResult::Skip;846};847let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(mesh_asset_id) else {848return RenderCommandResult::Skip;849};850851pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));852853let batch_range = item.batch_range();854match &gpu_mesh.buffer_info {855RenderMeshBufferInfo::Indexed {856index_format,857count,858} => {859let Some(index_buffer_slice) = mesh_allocator.mesh_index_slice(mesh_asset_id)860else {861return RenderCommandResult::Skip;862};863864pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format);865866pass.draw_indexed(867index_buffer_slice.range.start..(index_buffer_slice.range.start + count),868vertex_buffer_slice.range.start as i32,869batch_range.clone(),870);871}872RenderMeshBufferInfo::NonIndexed => {873pass.draw(vertex_buffer_slice.range, batch_range.clone());874}875}876RenderCommandResult::Success877}878}879880881