use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle}; use bevy_ecs::{ prelude::*, query::ROQueryItem, system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Mat4, Vec2}; use bevy_reflect::Reflect; use bevy_render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, globals::{GlobalsBuffer, GlobalsUniform}, mesh::{GpuBufferInfo, Mesh, MeshVertexBufferLayout}, render_asset::RenderAssets, render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass}, render_resource::*, 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; /// 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)] #[reflect(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_BINDINGS_HANDLE, "mesh2d_bindings.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); app.add_plugins(UniformComponentPlugin::::default()); if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .add_systems(ExtractSchedule, extract_mesh2d) .add_systems( Render, ( prepare_mesh2d_bind_group.in_set(RenderSet::PrepareBindGroups), prepare_mesh2d_view_bind_groups.in_set(RenderSet::PrepareBindGroups), ), ); } } fn finish(&self, app: &mut bevy_app::App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.init_resource::(); } } } #[derive(Component, ShaderType, Clone)] pub struct Mesh2dUniform { pub transform: Mat4, pub inverse_transpose_model: Mat4, pub flags: u32, } // NOTE: These must match the bit flags in bevy_sprite/src/mesh2d/mesh2d.wgsl! bitflags::bitflags! { #[repr(transparent)] struct MeshFlags: u32 { const NONE = 0; const UNINITIALIZED = 0xFFFF; } } pub fn extract_mesh2d( mut commands: Commands, mut previous_len: Local, query: Extract>, ) { let mut values = Vec::with_capacity(*previous_len); for (entity, view_visibility, transform, handle) in &query { if !view_visibility.get() { continue; } let transform = transform.compute_matrix(); values.push(( entity, ( Mesh2dHandle(handle.0.clone_weak()), Mesh2dUniform { flags: MeshFlags::empty().bits(), transform, inverse_transpose_model: transform.inverse().transpose(), }, ), )); } *previous_len = values.len(); commands.insert_or_spawn_batch(values); } #[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, } impl FromWorld for Mesh2dPipeline { fn from_world(world: &mut World) -> Self { let mut system_state: SystemState<(Res, Res)> = SystemState::new(world); let (render_device, default_sampler) = system_state.get_mut(world); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[ // View BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(ViewUniform::min_size()), }, count: None, }, BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::VERTEX_FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: Some(GlobalsUniform::min_size()), }, count: None, }, ], label: Some("mesh2d_view_layout"), }); let mesh_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: Some(Mesh2dUniform::min_size()), }, count: None, }], label: Some("mesh2d_layout"), }); // 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_descriptor { ImageSampler::Default => (**default_sampler).clone(), ImageSampler::Descriptor(descriptor) => render_device.create_sampler(&descriptor), }; let format_size = image.texture_descriptor.format.pixel_size(); let render_queue = world.resource_mut::(); render_queue.write_texture( ImageCopyTexture { texture: &texture, mip_level: 0, origin: Origin3d::ZERO, aspect: TextureAspect::All, }, &image.data, ImageDataLayout { offset: 0, bytes_per_row: Some(image.texture_descriptor.size.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: Vec2::new( image.texture_descriptor.size.width as f32, image.texture_descriptor.size.height as f32, ), mip_level_count: image.texture_descriptor.mip_level_count, } }; Mesh2dPipeline { view_layout, mesh_layout, dummy_white_gpu_image, } } } 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, )) } } } 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: &MeshVertexBufferLayout, ) -> Result { let mut shader_defs = Vec::new(); let mut vertex_attributes = Vec::new(); if layout.contains(Mesh::ATTRIBUTE_POSITION) { shader_defs.push("VERTEX_POSITIONS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if layout.contains(Mesh::ATTRIBUTE_NORMAL) { shader_defs.push("VERTEX_NORMALS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1)); } if layout.contains(Mesh::ATTRIBUTE_UV_0) { shader_defs.push("VERTEX_UVS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2)); } if layout.contains(Mesh::ATTRIBUTE_TANGENT) { shader_defs.push("VERTEX_TANGENTS".into()); vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } if layout.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.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::new(), 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.uniforms().binding() { commands.insert_resource(Mesh2dBindGroup { value: render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { binding: 0, resource: binding, }], label: Some("mesh2d_bind_group"), layout: &mesh2d_pipeline.mesh_layout, }), }); } } #[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(&BindGroupDescriptor { entries: &[ BindGroupEntry { binding: 0, resource: view_binding.clone(), }, BindGroupEntry { binding: 1, resource: globals.clone(), }, ], label: Some("mesh2d_view_bind_group"), layout: &mesh2d_pipeline.view_layout, }); commands.entity(entity).insert(Mesh2dViewBindGroup { value: view_bind_group, }); } } } pub struct SetMesh2dViewBindGroup; impl RenderCommand

for SetMesh2dViewBindGroup { type Param = (); type ViewWorldQuery = (Read, Read); type ItemWorldQuery = (); #[inline] fn render<'w>( _item: &P, (view_uniform, mesh2d_view_bind_group): ROQueryItem<'w, Self::ViewWorldQuery>, _view: (), _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 ViewWorldQuery = (); type ItemWorldQuery = Read>; #[inline] fn render<'w>( _item: &P, _view: (), mesh2d_index: &'_ DynamicUniformIndex, mesh2d_bind_group: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { pass.set_bind_group( I, &mesh2d_bind_group.into_inner().value, &[mesh2d_index.index()], ); RenderCommandResult::Success } } pub struct DrawMesh2d; impl RenderCommand

for DrawMesh2d { type Param = SRes>; type ViewWorldQuery = (); type ItemWorldQuery = Read; #[inline] fn render<'w>( _item: &P, _view: (), mesh_handle: ROQueryItem<'w, Self::ItemWorldQuery>, meshes: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { if let Some(gpu_mesh) = meshes.into_inner().get(&mesh_handle.0) { pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); 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, 0..1); } GpuBufferInfo::NonIndexed => { pass.draw(0..gpu_mesh.vertex_count, 0..1); } } RenderCommandResult::Success } else { RenderCommandResult::Failure } } }