Path: blob/main/crates/bevy_sprite_render/src/render/mod.rs
9359 views
use core::ops::Range;12use crate::ComputedTextureSlices;3use bevy_asset::{load_embedded_asset, AssetEvent, AssetId, AssetServer, Assets, Handle};4use bevy_camera::visibility::ViewVisibility;5use bevy_color::{ColorToComponents, LinearRgba};6use bevy_core_pipeline::{7core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},8tonemapping::{9get_lut_bind_group_layout_entries, get_lut_bindings, DebandDither, Tonemapping,10TonemappingLuts,11},12};13use bevy_derive::{Deref, DerefMut};14use bevy_ecs::{15prelude::*,16query::ROQueryItem,17system::{lifetimeless::*, SystemParamItem},18};19use bevy_image::{BevyDefault, Image, TextureAtlasLayout};20use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};21use bevy_mesh::VertexBufferLayout;22use bevy_platform::collections::HashMap;23use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity};24use bevy_render::{25render_asset::RenderAssets,26render_phase::{27DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,28SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases,29},30render_resource::{31binding_types::{sampler, texture_2d, uniform_buffer},32*,33},34renderer::{RenderDevice, RenderQueue},35sync_world::RenderEntity,36texture::{FallbackImage, GpuImage},37view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},38Extract,39};40use bevy_shader::{Shader, ShaderDefVal};41use bevy_sprite::{Anchor, Sprite, SpriteScalingMode};42use bevy_transform::components::GlobalTransform;43use bevy_utils::default;44use bytemuck::{Pod, Zeroable};45use fixedbitset::FixedBitSet;4647#[derive(Resource)]48pub struct SpritePipeline {49view_layout: BindGroupLayoutDescriptor,50material_layout: BindGroupLayoutDescriptor,51shader: Handle<Shader>,52}5354pub fn init_sprite_pipeline(mut commands: Commands, asset_server: Res<AssetServer>) {55let tonemapping_lut_entries = get_lut_bind_group_layout_entries();56let view_layout = BindGroupLayoutDescriptor::new(57"sprite_view_layout",58&BindGroupLayoutEntries::sequential(59ShaderStages::VERTEX_FRAGMENT,60(61uniform_buffer::<ViewUniform>(true),62tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT),63tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT),64),65),66);6768let material_layout = BindGroupLayoutDescriptor::new(69"sprite_material_layout",70&BindGroupLayoutEntries::sequential(71ShaderStages::FRAGMENT,72(73texture_2d(TextureSampleType::Float { filterable: true }),74sampler(SamplerBindingType::Filtering),75),76),77);7879commands.insert_resource(SpritePipeline {80view_layout,81material_layout,82shader: load_embedded_asset!(asset_server.as_ref(), "sprite.wgsl"),83});84}8586bitflags::bitflags! {87#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]88#[repr(transparent)]89// NOTE: Apparently quadro drivers support up to 64x MSAA.90// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.91pub struct SpritePipelineKey: u32 {92const NONE = 0;93const HDR = 1 << 0;94const TONEMAP_IN_SHADER = 1 << 1;95const DEBAND_DITHER = 1 << 2;96const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;97const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;98const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS;99const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS;100const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS;101const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;102const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;103const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;104const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;105const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;106}107}108109impl SpritePipelineKey {110const MSAA_MASK_BITS: u32 = 0b111;111const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();112const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;113const TONEMAP_METHOD_SHIFT_BITS: u32 =114Self::MSAA_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();115116#[inline]117pub const fn from_msaa_samples(msaa_samples: u32) -> Self {118let msaa_bits =119(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;120Self::from_bits_retain(msaa_bits)121}122123#[inline]124pub const fn msaa_samples(&self) -> u32 {1251 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)126}127128#[inline]129pub const fn from_hdr(hdr: bool) -> Self {130if hdr {131SpritePipelineKey::HDR132} else {133SpritePipelineKey::NONE134}135}136}137138impl SpecializedRenderPipeline for SpritePipeline {139type Key = SpritePipelineKey;140141fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {142let mut shader_defs = Vec::new();143if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) {144shader_defs.push("TONEMAP_IN_SHADER".into());145shader_defs.push(ShaderDefVal::UInt(146"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),1471,148));149shader_defs.push(ShaderDefVal::UInt(150"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),1512,152));153154let method = key.intersection(SpritePipelineKey::TONEMAP_METHOD_RESERVED_BITS);155156if method == SpritePipelineKey::TONEMAP_METHOD_NONE {157shader_defs.push("TONEMAP_METHOD_NONE".into());158} else if method == SpritePipelineKey::TONEMAP_METHOD_REINHARD {159shader_defs.push("TONEMAP_METHOD_REINHARD".into());160} else if method == SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {161shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());162} else if method == SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED {163shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());164} else if method == SpritePipelineKey::TONEMAP_METHOD_AGX {165shader_defs.push("TONEMAP_METHOD_AGX".into());166} else if method == SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM167{168shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());169} else if method == SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {170shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());171} else if method == SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {172shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());173}174175// Debanding is tied to tonemapping in the shader, cannot run without it.176if key.contains(SpritePipelineKey::DEBAND_DITHER) {177shader_defs.push("DEBAND_DITHER".into());178}179}180181let format = match key.contains(SpritePipelineKey::HDR) {182true => ViewTarget::TEXTURE_FORMAT_HDR,183false => TextureFormat::bevy_default(),184};185186let instance_rate_vertex_buffer_layout = VertexBufferLayout {187array_stride: 80,188step_mode: VertexStepMode::Instance,189attributes: vec![190// @location(0) i_model_transpose_col0: vec4<f32>,191VertexAttribute {192format: VertexFormat::Float32x4,193offset: 0,194shader_location: 0,195},196// @location(1) i_model_transpose_col1: vec4<f32>,197VertexAttribute {198format: VertexFormat::Float32x4,199offset: 16,200shader_location: 1,201},202// @location(2) i_model_transpose_col2: vec4<f32>,203VertexAttribute {204format: VertexFormat::Float32x4,205offset: 32,206shader_location: 2,207},208// @location(3) i_color: vec4<f32>,209VertexAttribute {210format: VertexFormat::Float32x4,211offset: 48,212shader_location: 3,213},214// @location(4) i_uv_offset_scale: vec4<f32>,215VertexAttribute {216format: VertexFormat::Float32x4,217offset: 64,218shader_location: 4,219},220],221};222223RenderPipelineDescriptor {224vertex: VertexState {225shader: self.shader.clone(),226shader_defs: shader_defs.clone(),227buffers: vec![instance_rate_vertex_buffer_layout],228..default()229},230fragment: Some(FragmentState {231shader: self.shader.clone(),232shader_defs,233targets: vec![Some(ColorTargetState {234format,235blend: Some(BlendState::ALPHA_BLENDING),236write_mask: ColorWrites::ALL,237})],238..default()239}),240layout: vec![self.view_layout.clone(), self.material_layout.clone()],241// Sprites are always alpha blended so they never need to write to depth.242// They just need to read it in case an opaque mesh2d243// that wrote to depth is present.244depth_stencil: Some(DepthStencilState {245format: CORE_2D_DEPTH_FORMAT,246depth_write_enabled: false,247depth_compare: CompareFunction::GreaterEqual,248stencil: StencilState {249front: StencilFaceState::IGNORE,250back: StencilFaceState::IGNORE,251read_mask: 0,252write_mask: 0,253},254bias: DepthBiasState {255constant: 0,256slope_scale: 0.0,257clamp: 0.0,258},259}),260multisample: MultisampleState {261count: key.msaa_samples(),262mask: !0,263alpha_to_coverage_enabled: false,264},265label: Some("sprite_pipeline".into()),266..default()267}268}269}270271pub struct ExtractedSlice {272pub offset: Vec2,273pub rect: Rect,274pub size: Vec2,275}276277pub struct ExtractedSprite {278pub main_entity: Entity,279pub render_entity: Entity,280pub transform: GlobalTransform,281pub color: LinearRgba,282/// Change the on-screen size of the sprite283/// Asset ID of the [`Image`] of this sprite284/// PERF: storing an `AssetId` instead of `Handle<Image>` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped)285pub image_handle_id: AssetId<Image>,286pub flip_x: bool,287pub flip_y: bool,288pub kind: ExtractedSpriteKind,289}290291pub enum ExtractedSpriteKind {292/// A single sprite with custom sizing and scaling options293Single {294anchor: Vec2,295rect: Option<Rect>,296scaling_mode: Option<SpriteScalingMode>,297custom_size: Option<Vec2>,298},299/// Indexes into the list of [`ExtractedSlice`]s stored in the [`ExtractedSlices`] resource300/// Used for elements composed from multiple sprites such as text or nine-patched borders301Slices { indices: Range<usize> },302}303304#[derive(Resource, Default)]305pub struct ExtractedSprites {306pub sprites: Vec<ExtractedSprite>,307}308309#[derive(Resource, Default)]310pub struct ExtractedSlices {311pub slices: Vec<ExtractedSlice>,312}313314#[derive(Resource, Default)]315pub struct SpriteAssetEvents {316pub images: Vec<AssetEvent<Image>>,317}318319pub fn extract_sprite_events(320mut events: ResMut<SpriteAssetEvents>,321mut image_events: Extract<MessageReader<AssetEvent<Image>>>,322) {323let SpriteAssetEvents { ref mut images } = *events;324images.clear();325326for event in image_events.read() {327images.push(*event);328}329}330331pub fn extract_sprites(332mut extracted_sprites: ResMut<ExtractedSprites>,333mut extracted_slices: ResMut<ExtractedSlices>,334texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,335sprite_query: Extract<336Query<(337Entity,338RenderEntity,339&ViewVisibility,340&Sprite,341&GlobalTransform,342&Anchor,343Option<&ComputedTextureSlices>,344)>,345>,346) {347extracted_sprites.sprites.clear();348extracted_slices.slices.clear();349for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in350sprite_query.iter()351{352if !view_visibility.get() {353continue;354}355356if let Some(slices) = slices {357let start = extracted_slices.slices.len();358extracted_slices359.slices360.extend(slices.extract_slices(sprite, anchor.as_vec()));361let end = extracted_slices.slices.len();362extracted_sprites.sprites.push(ExtractedSprite {363main_entity,364render_entity,365color: sprite.color.into(),366transform: *transform,367flip_x: sprite.flip_x,368flip_y: sprite.flip_y,369image_handle_id: sprite.image.id(),370kind: ExtractedSpriteKind::Slices {371indices: start..end,372},373});374} else {375let atlas_rect = sprite376.texture_atlas377.as_ref()378.and_then(|s| s.texture_rect(&texture_atlases).map(|r| r.as_rect()));379let rect = match (atlas_rect, sprite.rect) {380(None, None) => None,381(None, Some(sprite_rect)) => Some(sprite_rect),382(Some(atlas_rect), None) => Some(atlas_rect),383(Some(atlas_rect), Some(mut sprite_rect)) => {384sprite_rect.min += atlas_rect.min;385sprite_rect.max += atlas_rect.min;386Some(sprite_rect)387}388};389390// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive391extracted_sprites.sprites.push(ExtractedSprite {392main_entity,393render_entity,394color: sprite.color.into(),395transform: *transform,396flip_x: sprite.flip_x,397flip_y: sprite.flip_y,398image_handle_id: sprite.image.id(),399kind: ExtractedSpriteKind::Single {400anchor: anchor.as_vec(),401rect,402scaling_mode: sprite.image_mode.scale(),403// Pass the custom size404custom_size: sprite.custom_size,405},406});407}408}409}410411#[repr(C)]412#[derive(Copy, Clone, Pod, Zeroable)]413struct SpriteInstance {414// Affine 4x3 transposed to 3x4415pub i_model_transpose: [Vec4; 3],416pub i_color: [f32; 4],417pub i_uv_offset_scale: [f32; 4],418}419420impl SpriteInstance {421#[inline]422fn from(transform: &Affine3A, color: &LinearRgba, uv_offset_scale: &Vec4) -> Self {423let transpose_model_3x3 = transform.matrix3.transpose();424Self {425i_model_transpose: [426transpose_model_3x3.x_axis.extend(transform.translation.x),427transpose_model_3x3.y_axis.extend(transform.translation.y),428transpose_model_3x3.z_axis.extend(transform.translation.z),429],430i_color: color.to_f32_array(),431i_uv_offset_scale: uv_offset_scale.to_array(),432}433}434}435436#[derive(Resource)]437pub struct SpriteMeta {438sprite_index_buffer: RawBufferVec<u32>,439sprite_instance_buffer: RawBufferVec<SpriteInstance>,440}441442impl Default for SpriteMeta {443fn default() -> Self {444Self {445sprite_index_buffer: RawBufferVec::<u32>::new(BufferUsages::INDEX),446sprite_instance_buffer: RawBufferVec::<SpriteInstance>::new(BufferUsages::VERTEX),447}448}449}450451#[derive(Component)]452pub struct SpriteViewBindGroup {453pub value: BindGroup,454}455456#[derive(Resource, Deref, DerefMut, Default)]457pub struct SpriteBatches(HashMap<(RetainedViewEntity, Entity), SpriteBatch>);458459#[derive(PartialEq, Eq, Clone, Debug)]460pub struct SpriteBatch {461image_handle_id: AssetId<Image>,462range: Range<u32>,463}464465#[derive(Resource, Default)]466pub struct ImageBindGroups {467values: HashMap<AssetId<Image>, BindGroup>,468}469470pub fn queue_sprites(471mut view_entities: Local<FixedBitSet>,472draw_functions: Res<DrawFunctions<Transparent2d>>,473sprite_pipeline: Res<SpritePipeline>,474mut pipelines: ResMut<SpecializedRenderPipelines<SpritePipeline>>,475pipeline_cache: Res<PipelineCache>,476extracted_sprites: Res<ExtractedSprites>,477mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,478mut views: Query<(479&RenderVisibleEntities,480&ExtractedView,481&Msaa,482Option<&Tonemapping>,483Option<&DebandDither>,484)>,485) {486let draw_sprite_function = draw_functions.read().id::<DrawSprite>();487488for (visible_entities, view, msaa, tonemapping, dither) in &mut views {489let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)490else {491continue;492};493494let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples());495let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key;496497if !view.hdr {498if let Some(tonemapping) = tonemapping {499view_key |= SpritePipelineKey::TONEMAP_IN_SHADER;500view_key |= match tonemapping {501Tonemapping::None => SpritePipelineKey::TONEMAP_METHOD_NONE,502Tonemapping::Reinhard => SpritePipelineKey::TONEMAP_METHOD_REINHARD,503Tonemapping::ReinhardLuminance => {504SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE505}506Tonemapping::AcesFitted => SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED,507Tonemapping::AgX => SpritePipelineKey::TONEMAP_METHOD_AGX,508Tonemapping::SomewhatBoringDisplayTransform => {509SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM510}511Tonemapping::TonyMcMapface => SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,512Tonemapping::BlenderFilmic => SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,513};514}515if let Some(DebandDither::Enabled) = dither {516view_key |= SpritePipelineKey::DEBAND_DITHER;517}518}519520let pipeline = pipelines.specialize(&pipeline_cache, &sprite_pipeline, view_key);521522view_entities.clear();523view_entities.extend(524visible_entities525.iter::<Sprite>()526.map(|(_, e)| e.index_u32() as usize),527);528529transparent_phase530.items531.reserve(extracted_sprites.sprites.len());532533for (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {534let view_index = extracted_sprite.main_entity.index_u32();535536if !view_entities.contains(view_index as usize) {537continue;538}539540// These items will be sorted by depth with other phase items541let sort_key = FloatOrd(extracted_sprite.transform.translation().z);542543// Add the item to the render phase544transparent_phase.add(Transparent2d {545draw_function: draw_sprite_function,546pipeline,547entity: (548extracted_sprite.render_entity,549extracted_sprite.main_entity.into(),550),551sort_key,552// `batch_range` is calculated in `prepare_sprite_image_bind_groups`553batch_range: 0..0,554extra_index: PhaseItemExtraIndex::None,555extracted_index: index,556indexed: true,557});558}559}560}561562pub fn prepare_sprite_view_bind_groups(563mut commands: Commands,564render_device: Res<RenderDevice>,565pipeline_cache: Res<PipelineCache>,566sprite_pipeline: Res<SpritePipeline>,567view_uniforms: Res<ViewUniforms>,568views: Query<(Entity, &Tonemapping), With<ExtractedView>>,569tonemapping_luts: Res<TonemappingLuts>,570images: Res<RenderAssets<GpuImage>>,571fallback_image: Res<FallbackImage>,572) {573let Some(view_binding) = view_uniforms.uniforms.binding() else {574return;575};576577for (entity, tonemapping) in &views {578let lut_bindings =579get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);580let view_bind_group = render_device.create_bind_group(581"mesh2d_view_bind_group",582&pipeline_cache.get_bind_group_layout(&sprite_pipeline.view_layout),583&BindGroupEntries::sequential((view_binding.clone(), lut_bindings.0, lut_bindings.1)),584);585586commands.entity(entity).insert(SpriteViewBindGroup {587value: view_bind_group,588});589}590}591592pub fn prepare_sprite_image_bind_groups(593render_device: Res<RenderDevice>,594render_queue: Res<RenderQueue>,595pipeline_cache: Res<PipelineCache>,596mut sprite_meta: ResMut<SpriteMeta>,597sprite_pipeline: Res<SpritePipeline>,598mut image_bind_groups: ResMut<ImageBindGroups>,599gpu_images: Res<RenderAssets<GpuImage>>,600extracted_sprites: Res<ExtractedSprites>,601extracted_slices: Res<ExtractedSlices>,602mut phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,603events: Res<SpriteAssetEvents>,604mut batches: ResMut<SpriteBatches>,605) {606// If an image has changed, the GpuImage has (probably) changed607for event in &events.images {608match event {609AssetEvent::Added { .. } |610// Images don't have dependencies611AssetEvent::LoadedWithDependencies { .. } => {}612AssetEvent::Unused { id } | AssetEvent::Modified { id } | AssetEvent::Removed { id } => {613image_bind_groups.values.remove(id);614}615};616}617618batches.clear();619620// Clear the sprite instances621sprite_meta.sprite_instance_buffer.clear();622623// Index buffer indices624let mut index = 0;625626let image_bind_groups = &mut *image_bind_groups;627628for (retained_view, transparent_phase) in phases.iter_mut() {629let mut current_batch = None;630let mut batch_item_index = 0;631let mut batch_image_size = Vec2::ZERO;632let mut batch_image_handle = AssetId::invalid();633634// Iterate through the phase items and detect when successive sprites that can be batched.635// Spawn an entity with a `SpriteBatch` component for each possible batch.636// Compatible items share the same entity.637for item_index in 0..transparent_phase.items.len() {638let item = &transparent_phase.items[item_index];639640let Some(extracted_sprite) = extracted_sprites641.sprites642.get(item.extracted_index)643.filter(|extracted_sprite| extracted_sprite.render_entity == item.entity())644else {645// If there is a phase item that is not a sprite, then we must start a new646// batch to draw the other phase item(s) and to respect draw order. This can be647// done by invalidating the batch_image_handle648batch_image_handle = AssetId::invalid();649continue;650};651652if batch_image_handle != extracted_sprite.image_handle_id {653let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else {654continue;655};656657batch_image_size = gpu_image.size_2d().as_vec2();658batch_image_handle = extracted_sprite.image_handle_id;659image_bind_groups660.values661.entry(batch_image_handle)662.or_insert_with(|| {663render_device.create_bind_group(664"sprite_material_bind_group",665&pipeline_cache.get_bind_group_layout(&sprite_pipeline.material_layout),666&BindGroupEntries::sequential((667&gpu_image.texture_view,668&gpu_image.sampler,669)),670)671});672673batch_item_index = item_index;674current_batch = Some(batches.entry((*retained_view, item.entity())).insert(675SpriteBatch {676image_handle_id: batch_image_handle,677range: index..index,678},679));680}681match extracted_sprite.kind {682ExtractedSpriteKind::Single {683anchor,684rect,685scaling_mode,686custom_size,687} => {688// By default, the size of the quad is the size of the texture689let mut quad_size = batch_image_size;690let mut texture_size = batch_image_size;691692// Calculate vertex data for this item693// If a rect is specified, adjust UVs and the size of the quad694let mut uv_offset_scale = if let Some(rect) = rect {695let rect_size = rect.size();696quad_size = rect_size;697// Update texture size to the rect size698// It will help scale properly only portion of the image699texture_size = rect_size;700Vec4::new(701rect.min.x / batch_image_size.x,702rect.max.y / batch_image_size.y,703rect_size.x / batch_image_size.x,704-rect_size.y / batch_image_size.y,705)706} else {707Vec4::new(0.0, 1.0, 1.0, -1.0)708};709710if extracted_sprite.flip_x {711uv_offset_scale.x += uv_offset_scale.z;712uv_offset_scale.z *= -1.0;713}714if extracted_sprite.flip_y {715uv_offset_scale.y += uv_offset_scale.w;716uv_offset_scale.w *= -1.0;717}718719// Override the size if a custom one is specified720quad_size = custom_size.unwrap_or(quad_size);721722// Used for translation of the quad if `TextureScale::Fit...` is specified.723let mut quad_translation = Vec2::ZERO;724725// Scales the texture based on the `texture_scale` field.726if let Some(scaling_mode) = scaling_mode {727apply_scaling(728scaling_mode,729texture_size,730&mut quad_size,731&mut quad_translation,732&mut uv_offset_scale,733);734}735736let transform = extracted_sprite.transform.affine()737* Affine3A::from_scale_rotation_translation(738quad_size.extend(1.0),739Quat::IDENTITY,740((quad_size + quad_translation) * (-anchor - Vec2::splat(0.5)))741.extend(0.0),742);743744// Store the vertex data and add the item to the render phase745sprite_meta746.sprite_instance_buffer747.push(SpriteInstance::from(748&transform,749&extracted_sprite.color,750&uv_offset_scale,751));752753current_batch.as_mut().unwrap().get_mut().range.end += 1;754index += 1;755}756ExtractedSpriteKind::Slices { ref indices } => {757for i in indices.clone() {758let slice = &extracted_slices.slices[i];759let rect = slice.rect;760let rect_size = rect.size();761762// Calculate vertex data for this item763let mut uv_offset_scale: Vec4;764765// If a rect is specified, adjust UVs and the size of the quad766uv_offset_scale = Vec4::new(767rect.min.x / batch_image_size.x,768rect.max.y / batch_image_size.y,769rect_size.x / batch_image_size.x,770-rect_size.y / batch_image_size.y,771);772773if extracted_sprite.flip_x {774uv_offset_scale.x += uv_offset_scale.z;775uv_offset_scale.z *= -1.0;776}777if extracted_sprite.flip_y {778uv_offset_scale.y += uv_offset_scale.w;779uv_offset_scale.w *= -1.0;780}781782let transform = extracted_sprite.transform.affine()783* Affine3A::from_scale_rotation_translation(784slice.size.extend(1.0),785Quat::IDENTITY,786(slice.size * -Vec2::splat(0.5) + slice.offset).extend(0.0),787);788789// Store the vertex data and add the item to the render phase790sprite_meta791.sprite_instance_buffer792.push(SpriteInstance::from(793&transform,794&extracted_sprite.color,795&uv_offset_scale,796));797798current_batch.as_mut().unwrap().get_mut().range.end += 1;799index += 1;800}801}802}803transparent_phase.items[batch_item_index]804.batch_range_mut()805.end += 1;806}807sprite_meta808.sprite_instance_buffer809.write_buffer(&render_device, &render_queue);810811if sprite_meta.sprite_index_buffer.len() != 6 {812sprite_meta.sprite_index_buffer.clear();813814// NOTE: This code is creating 6 indices pointing to 4 vertices.815// The vertices form the corners of a quad based on their two least significant bits.816// 10 11817//818// 00 01819// The sprite shader can then use the two least significant bits as the vertex index.820// The rest of the properties to transform the vertex positions and UVs (which are821// implicit) are baked into the instance transform, and UV offset and scale.822// See bevy_sprite_render/src/render/sprite.wgsl for the details.823sprite_meta.sprite_index_buffer.push(2);824sprite_meta.sprite_index_buffer.push(0);825sprite_meta.sprite_index_buffer.push(1);826sprite_meta.sprite_index_buffer.push(1);827sprite_meta.sprite_index_buffer.push(3);828sprite_meta.sprite_index_buffer.push(2);829830sprite_meta831.sprite_index_buffer832.write_buffer(&render_device, &render_queue);833}834}835}836/// [`RenderCommand`] for sprite rendering.837pub type DrawSprite = (838SetItemPipeline,839SetSpriteViewBindGroup<0>,840SetSpriteTextureBindGroup<1>,841DrawSpriteBatch,842);843844pub struct SetSpriteViewBindGroup<const I: usize>;845impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteViewBindGroup<I> {846type Param = ();847type ViewQuery = (Read<ViewUniformOffset>, Read<SpriteViewBindGroup>);848type ItemQuery = ();849850fn render<'w>(851_item: &P,852(view_uniform, sprite_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>,853_entity: Option<()>,854_param: SystemParamItem<'w, '_, Self::Param>,855pass: &mut TrackedRenderPass<'w>,856) -> RenderCommandResult {857pass.set_bind_group(I, &sprite_view_bind_group.value, &[view_uniform.offset]);858RenderCommandResult::Success859}860}861pub struct SetSpriteTextureBindGroup<const I: usize>;862impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteTextureBindGroup<I> {863type Param = (SRes<ImageBindGroups>, SRes<SpriteBatches>);864type ViewQuery = Read<ExtractedView>;865type ItemQuery = ();866867fn render<'w>(868item: &P,869view: ROQueryItem<'w, '_, Self::ViewQuery>,870_entity: Option<()>,871(image_bind_groups, batches): SystemParamItem<'w, '_, Self::Param>,872pass: &mut TrackedRenderPass<'w>,873) -> RenderCommandResult {874let image_bind_groups = image_bind_groups.into_inner();875let Some(batch) = batches.get(&(view.retained_view_entity, item.entity())) else {876return RenderCommandResult::Skip;877};878879pass.set_bind_group(880I,881image_bind_groups882.values883.get(&batch.image_handle_id)884.unwrap(),885&[],886);887RenderCommandResult::Success888}889}890891pub struct DrawSpriteBatch;892impl<P: PhaseItem> RenderCommand<P> for DrawSpriteBatch {893type Param = (SRes<SpriteMeta>, SRes<SpriteBatches>);894type ViewQuery = Read<ExtractedView>;895type ItemQuery = ();896897fn render<'w>(898item: &P,899view: ROQueryItem<'w, '_, Self::ViewQuery>,900_entity: Option<()>,901(sprite_meta, batches): SystemParamItem<'w, '_, Self::Param>,902pass: &mut TrackedRenderPass<'w>,903) -> RenderCommandResult {904let sprite_meta = sprite_meta.into_inner();905let Some(batch) = batches.get(&(view.retained_view_entity, item.entity())) else {906return RenderCommandResult::Skip;907};908909pass.set_index_buffer(910sprite_meta.sprite_index_buffer.buffer().unwrap().slice(..),911IndexFormat::Uint32,912);913pass.set_vertex_buffer(9140,915sprite_meta916.sprite_instance_buffer917.buffer()918.unwrap()919.slice(..),920);921pass.draw_indexed(0..6, 0, batch.range.clone());922RenderCommandResult::Success923}924}925926/// Scales a texture to fit within a given quad size with keeping the aspect ratio.927fn apply_scaling(928scaling_mode: SpriteScalingMode,929texture_size: Vec2,930quad_size: &mut Vec2,931quad_translation: &mut Vec2,932uv_offset_scale: &mut Vec4,933) {934let quad_ratio = quad_size.x / quad_size.y;935let texture_ratio = texture_size.x / texture_size.y;936let tex_quad_scale = texture_ratio / quad_ratio;937let quad_tex_scale = quad_ratio / texture_ratio;938939match scaling_mode {940SpriteScalingMode::FillCenter => {941if quad_ratio > texture_ratio {942// offset texture to center by y coordinate943uv_offset_scale.y += (uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale) * 0.5;944// sum up scales945uv_offset_scale.w *= tex_quad_scale;946} else {947// offset texture to center by x coordinate948uv_offset_scale.x += (uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale) * 0.5;949uv_offset_scale.z *= quad_tex_scale;950};951}952SpriteScalingMode::FillStart => {953if quad_ratio > texture_ratio {954uv_offset_scale.y += uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale;955uv_offset_scale.w *= tex_quad_scale;956} else {957uv_offset_scale.z *= quad_tex_scale;958}959}960SpriteScalingMode::FillEnd => {961if quad_ratio > texture_ratio {962uv_offset_scale.w *= tex_quad_scale;963} else {964uv_offset_scale.x += uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale;965uv_offset_scale.z *= quad_tex_scale;966}967}968SpriteScalingMode::FitCenter => {969if texture_ratio > quad_ratio {970// Scale based on width971quad_size.y *= quad_tex_scale;972} else {973// Scale based on height974quad_size.x *= tex_quad_scale;975}976}977SpriteScalingMode::FitStart => {978if texture_ratio > quad_ratio {979// The quad is scaled to match the image ratio, and the quad translation is adjusted980// to start of the quad within the original quad size.981let scale = Vec2::new(1.0, quad_tex_scale);982let new_quad = *quad_size * scale;983let offset = *quad_size - new_quad;984*quad_translation = Vec2::new(0.0, -offset.y);985*quad_size = new_quad;986} else {987let scale = Vec2::new(tex_quad_scale, 1.0);988let new_quad = *quad_size * scale;989let offset = *quad_size - new_quad;990*quad_translation = Vec2::new(offset.x, 0.0);991*quad_size = new_quad;992}993}994SpriteScalingMode::FitEnd => {995if texture_ratio > quad_ratio {996let scale = Vec2::new(1.0, quad_tex_scale);997let new_quad = *quad_size * scale;998let offset = *quad_size - new_quad;999*quad_translation = Vec2::new(0.0, offset.y);1000*quad_size = new_quad;1001} else {1002let scale = Vec2::new(tex_quad_scale, 1.0);1003let new_quad = *quad_size * scale;1004let offset = *quad_size - new_quad;1005*quad_translation = Vec2::new(-offset.x, 0.0);1006*quad_size = new_quad;1007}1008}1009}1010}101110121013