Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_sprite_render/src/render/mod.rs
6604 views
1
use core::ops::Range;
2
3
use crate::ComputedTextureSlices;
4
use bevy_asset::{load_embedded_asset, AssetEvent, AssetId, AssetServer, Assets, Handle};
5
use bevy_camera::visibility::ViewVisibility;
6
use bevy_color::{ColorToComponents, LinearRgba};
7
use bevy_core_pipeline::{
8
core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT},
9
tonemapping::{
10
get_lut_bind_group_layout_entries, get_lut_bindings, DebandDither, Tonemapping,
11
TonemappingLuts,
12
},
13
};
14
use bevy_derive::{Deref, DerefMut};
15
use bevy_ecs::{
16
prelude::*,
17
query::ROQueryItem,
18
system::{lifetimeless::*, SystemParamItem},
19
};
20
use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo};
21
use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
22
use bevy_mesh::VertexBufferLayout;
23
use bevy_platform::collections::HashMap;
24
use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity};
25
use bevy_render::{
26
render_asset::RenderAssets,
27
render_phase::{
28
DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand, RenderCommandResult,
29
SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases,
30
},
31
render_resource::{
32
binding_types::{sampler, texture_2d, uniform_buffer},
33
*,
34
},
35
renderer::{RenderDevice, RenderQueue},
36
sync_world::RenderEntity,
37
texture::{DefaultImageSampler, FallbackImage, GpuImage},
38
view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
39
Extract,
40
};
41
use bevy_shader::{Shader, ShaderDefVal};
42
use bevy_sprite::{Anchor, ScalingMode, Sprite};
43
use bevy_transform::components::GlobalTransform;
44
use bevy_utils::default;
45
use bytemuck::{Pod, Zeroable};
46
use fixedbitset::FixedBitSet;
47
48
#[derive(Resource)]
49
pub struct SpritePipeline {
50
view_layout: BindGroupLayout,
51
material_layout: BindGroupLayout,
52
shader: Handle<Shader>,
53
pub dummy_white_gpu_image: GpuImage,
54
}
55
56
pub fn init_sprite_pipeline(
57
mut commands: Commands,
58
render_device: Res<RenderDevice>,
59
default_sampler: Res<DefaultImageSampler>,
60
render_queue: Res<RenderQueue>,
61
asset_server: Res<AssetServer>,
62
) {
63
let tonemapping_lut_entries = get_lut_bind_group_layout_entries();
64
let view_layout = render_device.create_bind_group_layout(
65
"sprite_view_layout",
66
&BindGroupLayoutEntries::sequential(
67
ShaderStages::VERTEX_FRAGMENT,
68
(
69
uniform_buffer::<ViewUniform>(true),
70
tonemapping_lut_entries[0].visibility(ShaderStages::FRAGMENT),
71
tonemapping_lut_entries[1].visibility(ShaderStages::FRAGMENT),
72
),
73
),
74
);
75
76
let material_layout = render_device.create_bind_group_layout(
77
"sprite_material_layout",
78
&BindGroupLayoutEntries::sequential(
79
ShaderStages::FRAGMENT,
80
(
81
texture_2d(TextureSampleType::Float { filterable: true }),
82
sampler(SamplerBindingType::Filtering),
83
),
84
),
85
);
86
let dummy_white_gpu_image = {
87
let image = Image::default();
88
let texture = render_device.create_texture(&image.texture_descriptor);
89
let sampler = match image.sampler {
90
ImageSampler::Default => (**default_sampler).clone(),
91
ImageSampler::Descriptor(ref descriptor) => {
92
render_device.create_sampler(&descriptor.as_wgpu())
93
}
94
};
95
96
if let Ok(format_size) = image.texture_descriptor.format.pixel_size() {
97
render_queue.write_texture(
98
texture.as_image_copy(),
99
image.data.as_ref().expect("Image has no data"),
100
TexelCopyBufferLayout {
101
offset: 0,
102
bytes_per_row: Some(image.width() * format_size as u32),
103
rows_per_image: None,
104
},
105
image.texture_descriptor.size,
106
);
107
}
108
let texture_view = texture.create_view(&TextureViewDescriptor::default());
109
GpuImage {
110
texture,
111
texture_view,
112
texture_format: image.texture_descriptor.format,
113
sampler,
114
size: image.texture_descriptor.size,
115
mip_level_count: image.texture_descriptor.mip_level_count,
116
}
117
};
118
119
commands.insert_resource(SpritePipeline {
120
view_layout,
121
material_layout,
122
dummy_white_gpu_image,
123
shader: load_embedded_asset!(asset_server.as_ref(), "sprite.wgsl"),
124
});
125
}
126
127
bitflags::bitflags! {
128
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
129
#[repr(transparent)]
130
// NOTE: Apparently quadro drivers support up to 64x MSAA.
131
// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA.
132
pub struct SpritePipelineKey: u32 {
133
const NONE = 0;
134
const HDR = 1 << 0;
135
const TONEMAP_IN_SHADER = 1 << 1;
136
const DEBAND_DITHER = 1 << 2;
137
const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS;
138
const TONEMAP_METHOD_RESERVED_BITS = Self::TONEMAP_METHOD_MASK_BITS << Self::TONEMAP_METHOD_SHIFT_BITS;
139
const TONEMAP_METHOD_NONE = 0 << Self::TONEMAP_METHOD_SHIFT_BITS;
140
const TONEMAP_METHOD_REINHARD = 1 << Self::TONEMAP_METHOD_SHIFT_BITS;
141
const TONEMAP_METHOD_REINHARD_LUMINANCE = 2 << Self::TONEMAP_METHOD_SHIFT_BITS;
142
const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;
143
const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;
144
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
145
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
146
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
147
}
148
}
149
150
impl SpritePipelineKey {
151
const MSAA_MASK_BITS: u32 = 0b111;
152
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
153
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
154
const TONEMAP_METHOD_SHIFT_BITS: u32 =
155
Self::MSAA_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
156
157
#[inline]
158
pub const fn from_msaa_samples(msaa_samples: u32) -> Self {
159
let msaa_bits =
160
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
161
Self::from_bits_retain(msaa_bits)
162
}
163
164
#[inline]
165
pub const fn msaa_samples(&self) -> u32 {
166
1 << ((self.bits() >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS)
167
}
168
169
#[inline]
170
pub const fn from_hdr(hdr: bool) -> Self {
171
if hdr {
172
SpritePipelineKey::HDR
173
} else {
174
SpritePipelineKey::NONE
175
}
176
}
177
}
178
179
impl SpecializedRenderPipeline for SpritePipeline {
180
type Key = SpritePipelineKey;
181
182
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
183
let mut shader_defs = Vec::new();
184
if key.contains(SpritePipelineKey::TONEMAP_IN_SHADER) {
185
shader_defs.push("TONEMAP_IN_SHADER".into());
186
shader_defs.push(ShaderDefVal::UInt(
187
"TONEMAPPING_LUT_TEXTURE_BINDING_INDEX".into(),
188
1,
189
));
190
shader_defs.push(ShaderDefVal::UInt(
191
"TONEMAPPING_LUT_SAMPLER_BINDING_INDEX".into(),
192
2,
193
));
194
195
let method = key.intersection(SpritePipelineKey::TONEMAP_METHOD_RESERVED_BITS);
196
197
if method == SpritePipelineKey::TONEMAP_METHOD_NONE {
198
shader_defs.push("TONEMAP_METHOD_NONE".into());
199
} else if method == SpritePipelineKey::TONEMAP_METHOD_REINHARD {
200
shader_defs.push("TONEMAP_METHOD_REINHARD".into());
201
} else if method == SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE {
202
shader_defs.push("TONEMAP_METHOD_REINHARD_LUMINANCE".into());
203
} else if method == SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED {
204
shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into());
205
} else if method == SpritePipelineKey::TONEMAP_METHOD_AGX {
206
shader_defs.push("TONEMAP_METHOD_AGX".into());
207
} else if method == SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
208
{
209
shader_defs.push("TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM".into());
210
} else if method == SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC {
211
shader_defs.push("TONEMAP_METHOD_BLENDER_FILMIC".into());
212
} else if method == SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE {
213
shader_defs.push("TONEMAP_METHOD_TONY_MC_MAPFACE".into());
214
}
215
216
// Debanding is tied to tonemapping in the shader, cannot run without it.
217
if key.contains(SpritePipelineKey::DEBAND_DITHER) {
218
shader_defs.push("DEBAND_DITHER".into());
219
}
220
}
221
222
let format = match key.contains(SpritePipelineKey::HDR) {
223
true => ViewTarget::TEXTURE_FORMAT_HDR,
224
false => TextureFormat::bevy_default(),
225
};
226
227
let instance_rate_vertex_buffer_layout = VertexBufferLayout {
228
array_stride: 80,
229
step_mode: VertexStepMode::Instance,
230
attributes: vec![
231
// @location(0) i_model_transpose_col0: vec4<f32>,
232
VertexAttribute {
233
format: VertexFormat::Float32x4,
234
offset: 0,
235
shader_location: 0,
236
},
237
// @location(1) i_model_transpose_col1: vec4<f32>,
238
VertexAttribute {
239
format: VertexFormat::Float32x4,
240
offset: 16,
241
shader_location: 1,
242
},
243
// @location(2) i_model_transpose_col2: vec4<f32>,
244
VertexAttribute {
245
format: VertexFormat::Float32x4,
246
offset: 32,
247
shader_location: 2,
248
},
249
// @location(3) i_color: vec4<f32>,
250
VertexAttribute {
251
format: VertexFormat::Float32x4,
252
offset: 48,
253
shader_location: 3,
254
},
255
// @location(4) i_uv_offset_scale: vec4<f32>,
256
VertexAttribute {
257
format: VertexFormat::Float32x4,
258
offset: 64,
259
shader_location: 4,
260
},
261
],
262
};
263
264
RenderPipelineDescriptor {
265
vertex: VertexState {
266
shader: self.shader.clone(),
267
shader_defs: shader_defs.clone(),
268
buffers: vec![instance_rate_vertex_buffer_layout],
269
..default()
270
},
271
fragment: Some(FragmentState {
272
shader: self.shader.clone(),
273
shader_defs,
274
targets: vec![Some(ColorTargetState {
275
format,
276
blend: Some(BlendState::ALPHA_BLENDING),
277
write_mask: ColorWrites::ALL,
278
})],
279
..default()
280
}),
281
layout: vec![self.view_layout.clone(), self.material_layout.clone()],
282
// Sprites are always alpha blended so they never need to write to depth.
283
// They just need to read it in case an opaque mesh2d
284
// that wrote to depth is present.
285
depth_stencil: Some(DepthStencilState {
286
format: CORE_2D_DEPTH_FORMAT,
287
depth_write_enabled: false,
288
depth_compare: CompareFunction::GreaterEqual,
289
stencil: StencilState {
290
front: StencilFaceState::IGNORE,
291
back: StencilFaceState::IGNORE,
292
read_mask: 0,
293
write_mask: 0,
294
},
295
bias: DepthBiasState {
296
constant: 0,
297
slope_scale: 0.0,
298
clamp: 0.0,
299
},
300
}),
301
multisample: MultisampleState {
302
count: key.msaa_samples(),
303
mask: !0,
304
alpha_to_coverage_enabled: false,
305
},
306
label: Some("sprite_pipeline".into()),
307
..default()
308
}
309
}
310
}
311
312
pub struct ExtractedSlice {
313
pub offset: Vec2,
314
pub rect: Rect,
315
pub size: Vec2,
316
}
317
318
pub struct ExtractedSprite {
319
pub main_entity: Entity,
320
pub render_entity: Entity,
321
pub transform: GlobalTransform,
322
pub color: LinearRgba,
323
/// Change the on-screen size of the sprite
324
/// Asset ID of the [`Image`] of this sprite
325
/// PERF: storing an `AssetId` instead of `Handle<Image>` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped)
326
pub image_handle_id: AssetId<Image>,
327
pub flip_x: bool,
328
pub flip_y: bool,
329
pub kind: ExtractedSpriteKind,
330
}
331
332
pub enum ExtractedSpriteKind {
333
/// A single sprite with custom sizing and scaling options
334
Single {
335
anchor: Vec2,
336
rect: Option<Rect>,
337
scaling_mode: Option<ScalingMode>,
338
custom_size: Option<Vec2>,
339
},
340
/// Indexes into the list of [`ExtractedSlice`]s stored in the [`ExtractedSlices`] resource
341
/// Used for elements composed from multiple sprites such as text or nine-patched borders
342
Slices { indices: Range<usize> },
343
}
344
345
#[derive(Resource, Default)]
346
pub struct ExtractedSprites {
347
pub sprites: Vec<ExtractedSprite>,
348
}
349
350
#[derive(Resource, Default)]
351
pub struct ExtractedSlices {
352
pub slices: Vec<ExtractedSlice>,
353
}
354
355
#[derive(Resource, Default)]
356
pub struct SpriteAssetEvents {
357
pub images: Vec<AssetEvent<Image>>,
358
}
359
360
pub fn extract_sprite_events(
361
mut events: ResMut<SpriteAssetEvents>,
362
mut image_events: Extract<EventReader<AssetEvent<Image>>>,
363
) {
364
let SpriteAssetEvents { ref mut images } = *events;
365
images.clear();
366
367
for event in image_events.read() {
368
images.push(*event);
369
}
370
}
371
372
pub fn extract_sprites(
373
mut extracted_sprites: ResMut<ExtractedSprites>,
374
mut extracted_slices: ResMut<ExtractedSlices>,
375
texture_atlases: Extract<Res<Assets<TextureAtlasLayout>>>,
376
sprite_query: Extract<
377
Query<(
378
Entity,
379
RenderEntity,
380
&ViewVisibility,
381
&Sprite,
382
&GlobalTransform,
383
&Anchor,
384
Option<&ComputedTextureSlices>,
385
)>,
386
>,
387
) {
388
extracted_sprites.sprites.clear();
389
extracted_slices.slices.clear();
390
for (main_entity, render_entity, view_visibility, sprite, transform, anchor, slices) in
391
sprite_query.iter()
392
{
393
if !view_visibility.get() {
394
continue;
395
}
396
397
if let Some(slices) = slices {
398
let start = extracted_slices.slices.len();
399
extracted_slices
400
.slices
401
.extend(slices.extract_slices(sprite, anchor.as_vec()));
402
let end = extracted_slices.slices.len();
403
extracted_sprites.sprites.push(ExtractedSprite {
404
main_entity,
405
render_entity,
406
color: sprite.color.into(),
407
transform: *transform,
408
flip_x: sprite.flip_x,
409
flip_y: sprite.flip_y,
410
image_handle_id: sprite.image.id(),
411
kind: ExtractedSpriteKind::Slices {
412
indices: start..end,
413
},
414
});
415
} else {
416
let atlas_rect = sprite
417
.texture_atlas
418
.as_ref()
419
.and_then(|s| s.texture_rect(&texture_atlases).map(|r| r.as_rect()));
420
let rect = match (atlas_rect, sprite.rect) {
421
(None, None) => None,
422
(None, Some(sprite_rect)) => Some(sprite_rect),
423
(Some(atlas_rect), None) => Some(atlas_rect),
424
(Some(atlas_rect), Some(mut sprite_rect)) => {
425
sprite_rect.min += atlas_rect.min;
426
sprite_rect.max += atlas_rect.min;
427
Some(sprite_rect)
428
}
429
};
430
431
// 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 expensive
432
extracted_sprites.sprites.push(ExtractedSprite {
433
main_entity,
434
render_entity,
435
color: sprite.color.into(),
436
transform: *transform,
437
flip_x: sprite.flip_x,
438
flip_y: sprite.flip_y,
439
image_handle_id: sprite.image.id(),
440
kind: ExtractedSpriteKind::Single {
441
anchor: anchor.as_vec(),
442
rect,
443
scaling_mode: sprite.image_mode.scale(),
444
// Pass the custom size
445
custom_size: sprite.custom_size,
446
},
447
});
448
}
449
}
450
}
451
452
#[repr(C)]
453
#[derive(Copy, Clone, Pod, Zeroable)]
454
struct SpriteInstance {
455
// Affine 4x3 transposed to 3x4
456
pub i_model_transpose: [Vec4; 3],
457
pub i_color: [f32; 4],
458
pub i_uv_offset_scale: [f32; 4],
459
}
460
461
impl SpriteInstance {
462
#[inline]
463
fn from(transform: &Affine3A, color: &LinearRgba, uv_offset_scale: &Vec4) -> Self {
464
let transpose_model_3x3 = transform.matrix3.transpose();
465
Self {
466
i_model_transpose: [
467
transpose_model_3x3.x_axis.extend(transform.translation.x),
468
transpose_model_3x3.y_axis.extend(transform.translation.y),
469
transpose_model_3x3.z_axis.extend(transform.translation.z),
470
],
471
i_color: color.to_f32_array(),
472
i_uv_offset_scale: uv_offset_scale.to_array(),
473
}
474
}
475
}
476
477
#[derive(Resource)]
478
pub struct SpriteMeta {
479
sprite_index_buffer: RawBufferVec<u32>,
480
sprite_instance_buffer: RawBufferVec<SpriteInstance>,
481
}
482
483
impl Default for SpriteMeta {
484
fn default() -> Self {
485
Self {
486
sprite_index_buffer: RawBufferVec::<u32>::new(BufferUsages::INDEX),
487
sprite_instance_buffer: RawBufferVec::<SpriteInstance>::new(BufferUsages::VERTEX),
488
}
489
}
490
}
491
492
#[derive(Component)]
493
pub struct SpriteViewBindGroup {
494
pub value: BindGroup,
495
}
496
497
#[derive(Resource, Deref, DerefMut, Default)]
498
pub struct SpriteBatches(HashMap<(RetainedViewEntity, Entity), SpriteBatch>);
499
500
#[derive(PartialEq, Eq, Clone, Debug)]
501
pub struct SpriteBatch {
502
image_handle_id: AssetId<Image>,
503
range: Range<u32>,
504
}
505
506
#[derive(Resource, Default)]
507
pub struct ImageBindGroups {
508
values: HashMap<AssetId<Image>, BindGroup>,
509
}
510
511
pub fn queue_sprites(
512
mut view_entities: Local<FixedBitSet>,
513
draw_functions: Res<DrawFunctions<Transparent2d>>,
514
sprite_pipeline: Res<SpritePipeline>,
515
mut pipelines: ResMut<SpecializedRenderPipelines<SpritePipeline>>,
516
pipeline_cache: Res<PipelineCache>,
517
extracted_sprites: Res<ExtractedSprites>,
518
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
519
mut views: Query<(
520
&RenderVisibleEntities,
521
&ExtractedView,
522
&Msaa,
523
Option<&Tonemapping>,
524
Option<&DebandDither>,
525
)>,
526
) {
527
let draw_sprite_function = draw_functions.read().id::<DrawSprite>();
528
529
for (visible_entities, view, msaa, tonemapping, dither) in &mut views {
530
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
531
else {
532
continue;
533
};
534
535
let msaa_key = SpritePipelineKey::from_msaa_samples(msaa.samples());
536
let mut view_key = SpritePipelineKey::from_hdr(view.hdr) | msaa_key;
537
538
if !view.hdr {
539
if let Some(tonemapping) = tonemapping {
540
view_key |= SpritePipelineKey::TONEMAP_IN_SHADER;
541
view_key |= match tonemapping {
542
Tonemapping::None => SpritePipelineKey::TONEMAP_METHOD_NONE,
543
Tonemapping::Reinhard => SpritePipelineKey::TONEMAP_METHOD_REINHARD,
544
Tonemapping::ReinhardLuminance => {
545
SpritePipelineKey::TONEMAP_METHOD_REINHARD_LUMINANCE
546
}
547
Tonemapping::AcesFitted => SpritePipelineKey::TONEMAP_METHOD_ACES_FITTED,
548
Tonemapping::AgX => SpritePipelineKey::TONEMAP_METHOD_AGX,
549
Tonemapping::SomewhatBoringDisplayTransform => {
550
SpritePipelineKey::TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM
551
}
552
Tonemapping::TonyMcMapface => SpritePipelineKey::TONEMAP_METHOD_TONY_MC_MAPFACE,
553
Tonemapping::BlenderFilmic => SpritePipelineKey::TONEMAP_METHOD_BLENDER_FILMIC,
554
};
555
}
556
if let Some(DebandDither::Enabled) = dither {
557
view_key |= SpritePipelineKey::DEBAND_DITHER;
558
}
559
}
560
561
let pipeline = pipelines.specialize(&pipeline_cache, &sprite_pipeline, view_key);
562
563
view_entities.clear();
564
view_entities.extend(
565
visible_entities
566
.iter::<Sprite>()
567
.map(|(_, e)| e.index() as usize),
568
);
569
570
transparent_phase
571
.items
572
.reserve(extracted_sprites.sprites.len());
573
574
for (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
575
let view_index = extracted_sprite.main_entity.index();
576
577
if !view_entities.contains(view_index as usize) {
578
continue;
579
}
580
581
// These items will be sorted by depth with other phase items
582
let sort_key = FloatOrd(extracted_sprite.transform.translation().z);
583
584
// Add the item to the render phase
585
transparent_phase.add(Transparent2d {
586
draw_function: draw_sprite_function,
587
pipeline,
588
entity: (
589
extracted_sprite.render_entity,
590
extracted_sprite.main_entity.into(),
591
),
592
sort_key,
593
// `batch_range` is calculated in `prepare_sprite_image_bind_groups`
594
batch_range: 0..0,
595
extra_index: PhaseItemExtraIndex::None,
596
extracted_index: index,
597
indexed: true,
598
});
599
}
600
}
601
}
602
603
pub fn prepare_sprite_view_bind_groups(
604
mut commands: Commands,
605
render_device: Res<RenderDevice>,
606
sprite_pipeline: Res<SpritePipeline>,
607
view_uniforms: Res<ViewUniforms>,
608
views: Query<(Entity, &Tonemapping), With<ExtractedView>>,
609
tonemapping_luts: Res<TonemappingLuts>,
610
images: Res<RenderAssets<GpuImage>>,
611
fallback_image: Res<FallbackImage>,
612
) {
613
let Some(view_binding) = view_uniforms.uniforms.binding() else {
614
return;
615
};
616
617
for (entity, tonemapping) in &views {
618
let lut_bindings =
619
get_lut_bindings(&images, &tonemapping_luts, tonemapping, &fallback_image);
620
let view_bind_group = render_device.create_bind_group(
621
"mesh2d_view_bind_group",
622
&sprite_pipeline.view_layout,
623
&BindGroupEntries::sequential((view_binding.clone(), lut_bindings.0, lut_bindings.1)),
624
);
625
626
commands.entity(entity).insert(SpriteViewBindGroup {
627
value: view_bind_group,
628
});
629
}
630
}
631
632
pub fn prepare_sprite_image_bind_groups(
633
render_device: Res<RenderDevice>,
634
render_queue: Res<RenderQueue>,
635
mut sprite_meta: ResMut<SpriteMeta>,
636
sprite_pipeline: Res<SpritePipeline>,
637
mut image_bind_groups: ResMut<ImageBindGroups>,
638
gpu_images: Res<RenderAssets<GpuImage>>,
639
extracted_sprites: Res<ExtractedSprites>,
640
extracted_slices: Res<ExtractedSlices>,
641
mut phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
642
events: Res<SpriteAssetEvents>,
643
mut batches: ResMut<SpriteBatches>,
644
) {
645
// If an image has changed, the GpuImage has (probably) changed
646
for event in &events.images {
647
match event {
648
AssetEvent::Added { .. } |
649
// Images don't have dependencies
650
AssetEvent::LoadedWithDependencies { .. } => {}
651
AssetEvent::Unused { id } | AssetEvent::Modified { id } | AssetEvent::Removed { id } => {
652
image_bind_groups.values.remove(id);
653
}
654
};
655
}
656
657
batches.clear();
658
659
// Clear the sprite instances
660
sprite_meta.sprite_instance_buffer.clear();
661
662
// Index buffer indices
663
let mut index = 0;
664
665
let image_bind_groups = &mut *image_bind_groups;
666
667
for (retained_view, transparent_phase) in phases.iter_mut() {
668
let mut current_batch = None;
669
let mut batch_item_index = 0;
670
let mut batch_image_size = Vec2::ZERO;
671
let mut batch_image_handle = AssetId::invalid();
672
673
// Iterate through the phase items and detect when successive sprites that can be batched.
674
// Spawn an entity with a `SpriteBatch` component for each possible batch.
675
// Compatible items share the same entity.
676
for item_index in 0..transparent_phase.items.len() {
677
let item = &transparent_phase.items[item_index];
678
679
let Some(extracted_sprite) = extracted_sprites
680
.sprites
681
.get(item.extracted_index)
682
.filter(|extracted_sprite| extracted_sprite.render_entity == item.entity())
683
else {
684
// If there is a phase item that is not a sprite, then we must start a new
685
// batch to draw the other phase item(s) and to respect draw order. This can be
686
// done by invalidating the batch_image_handle
687
batch_image_handle = AssetId::invalid();
688
continue;
689
};
690
691
if batch_image_handle != extracted_sprite.image_handle_id {
692
let Some(gpu_image) = gpu_images.get(extracted_sprite.image_handle_id) else {
693
continue;
694
};
695
696
batch_image_size = gpu_image.size_2d().as_vec2();
697
batch_image_handle = extracted_sprite.image_handle_id;
698
image_bind_groups
699
.values
700
.entry(batch_image_handle)
701
.or_insert_with(|| {
702
render_device.create_bind_group(
703
"sprite_material_bind_group",
704
&sprite_pipeline.material_layout,
705
&BindGroupEntries::sequential((
706
&gpu_image.texture_view,
707
&gpu_image.sampler,
708
)),
709
)
710
});
711
712
batch_item_index = item_index;
713
current_batch = Some(batches.entry((*retained_view, item.entity())).insert(
714
SpriteBatch {
715
image_handle_id: batch_image_handle,
716
range: index..index,
717
},
718
));
719
}
720
match extracted_sprite.kind {
721
ExtractedSpriteKind::Single {
722
anchor,
723
rect,
724
scaling_mode,
725
custom_size,
726
} => {
727
// By default, the size of the quad is the size of the texture
728
let mut quad_size = batch_image_size;
729
let mut texture_size = batch_image_size;
730
731
// Calculate vertex data for this item
732
// If a rect is specified, adjust UVs and the size of the quad
733
let mut uv_offset_scale = if let Some(rect) = rect {
734
let rect_size = rect.size();
735
quad_size = rect_size;
736
// Update texture size to the rect size
737
// It will help scale properly only portion of the image
738
texture_size = rect_size;
739
Vec4::new(
740
rect.min.x / batch_image_size.x,
741
rect.max.y / batch_image_size.y,
742
rect_size.x / batch_image_size.x,
743
-rect_size.y / batch_image_size.y,
744
)
745
} else {
746
Vec4::new(0.0, 1.0, 1.0, -1.0)
747
};
748
749
if extracted_sprite.flip_x {
750
uv_offset_scale.x += uv_offset_scale.z;
751
uv_offset_scale.z *= -1.0;
752
}
753
if extracted_sprite.flip_y {
754
uv_offset_scale.y += uv_offset_scale.w;
755
uv_offset_scale.w *= -1.0;
756
}
757
758
// Override the size if a custom one is specified
759
quad_size = custom_size.unwrap_or(quad_size);
760
761
// Used for translation of the quad if `TextureScale::Fit...` is specified.
762
let mut quad_translation = Vec2::ZERO;
763
764
// Scales the texture based on the `texture_scale` field.
765
if let Some(scaling_mode) = scaling_mode {
766
apply_scaling(
767
scaling_mode,
768
texture_size,
769
&mut quad_size,
770
&mut quad_translation,
771
&mut uv_offset_scale,
772
);
773
}
774
775
let transform = extracted_sprite.transform.affine()
776
* Affine3A::from_scale_rotation_translation(
777
quad_size.extend(1.0),
778
Quat::IDENTITY,
779
((quad_size + quad_translation) * (-anchor - Vec2::splat(0.5)))
780
.extend(0.0),
781
);
782
783
// Store the vertex data and add the item to the render phase
784
sprite_meta
785
.sprite_instance_buffer
786
.push(SpriteInstance::from(
787
&transform,
788
&extracted_sprite.color,
789
&uv_offset_scale,
790
));
791
792
current_batch.as_mut().unwrap().get_mut().range.end += 1;
793
index += 1;
794
}
795
ExtractedSpriteKind::Slices { ref indices } => {
796
for i in indices.clone() {
797
let slice = &extracted_slices.slices[i];
798
let rect = slice.rect;
799
let rect_size = rect.size();
800
801
// Calculate vertex data for this item
802
let mut uv_offset_scale: Vec4;
803
804
// If a rect is specified, adjust UVs and the size of the quad
805
uv_offset_scale = Vec4::new(
806
rect.min.x / batch_image_size.x,
807
rect.max.y / batch_image_size.y,
808
rect_size.x / batch_image_size.x,
809
-rect_size.y / batch_image_size.y,
810
);
811
812
if extracted_sprite.flip_x {
813
uv_offset_scale.x += uv_offset_scale.z;
814
uv_offset_scale.z *= -1.0;
815
}
816
if extracted_sprite.flip_y {
817
uv_offset_scale.y += uv_offset_scale.w;
818
uv_offset_scale.w *= -1.0;
819
}
820
821
let transform = extracted_sprite.transform.affine()
822
* Affine3A::from_scale_rotation_translation(
823
slice.size.extend(1.0),
824
Quat::IDENTITY,
825
(slice.size * -Vec2::splat(0.5) + slice.offset).extend(0.0),
826
);
827
828
// Store the vertex data and add the item to the render phase
829
sprite_meta
830
.sprite_instance_buffer
831
.push(SpriteInstance::from(
832
&transform,
833
&extracted_sprite.color,
834
&uv_offset_scale,
835
));
836
837
current_batch.as_mut().unwrap().get_mut().range.end += 1;
838
index += 1;
839
}
840
}
841
}
842
transparent_phase.items[batch_item_index]
843
.batch_range_mut()
844
.end += 1;
845
}
846
sprite_meta
847
.sprite_instance_buffer
848
.write_buffer(&render_device, &render_queue);
849
850
if sprite_meta.sprite_index_buffer.len() != 6 {
851
sprite_meta.sprite_index_buffer.clear();
852
853
// NOTE: This code is creating 6 indices pointing to 4 vertices.
854
// The vertices form the corners of a quad based on their two least significant bits.
855
// 10 11
856
//
857
// 00 01
858
// The sprite shader can then use the two least significant bits as the vertex index.
859
// The rest of the properties to transform the vertex positions and UVs (which are
860
// implicit) are baked into the instance transform, and UV offset and scale.
861
// See bevy_sprite_render/src/render/sprite.wgsl for the details.
862
sprite_meta.sprite_index_buffer.push(2);
863
sprite_meta.sprite_index_buffer.push(0);
864
sprite_meta.sprite_index_buffer.push(1);
865
sprite_meta.sprite_index_buffer.push(1);
866
sprite_meta.sprite_index_buffer.push(3);
867
sprite_meta.sprite_index_buffer.push(2);
868
869
sprite_meta
870
.sprite_index_buffer
871
.write_buffer(&render_device, &render_queue);
872
}
873
}
874
}
875
/// [`RenderCommand`] for sprite rendering.
876
pub type DrawSprite = (
877
SetItemPipeline,
878
SetSpriteViewBindGroup<0>,
879
SetSpriteTextureBindGroup<1>,
880
DrawSpriteBatch,
881
);
882
883
pub struct SetSpriteViewBindGroup<const I: usize>;
884
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteViewBindGroup<I> {
885
type Param = ();
886
type ViewQuery = (Read<ViewUniformOffset>, Read<SpriteViewBindGroup>);
887
type ItemQuery = ();
888
889
fn render<'w>(
890
_item: &P,
891
(view_uniform, sprite_view_bind_group): ROQueryItem<'w, '_, Self::ViewQuery>,
892
_entity: Option<()>,
893
_param: SystemParamItem<'w, '_, Self::Param>,
894
pass: &mut TrackedRenderPass<'w>,
895
) -> RenderCommandResult {
896
pass.set_bind_group(I, &sprite_view_bind_group.value, &[view_uniform.offset]);
897
RenderCommandResult::Success
898
}
899
}
900
pub struct SetSpriteTextureBindGroup<const I: usize>;
901
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetSpriteTextureBindGroup<I> {
902
type Param = (SRes<ImageBindGroups>, SRes<SpriteBatches>);
903
type ViewQuery = Read<ExtractedView>;
904
type ItemQuery = ();
905
906
fn render<'w>(
907
item: &P,
908
view: ROQueryItem<'w, '_, Self::ViewQuery>,
909
_entity: Option<()>,
910
(image_bind_groups, batches): SystemParamItem<'w, '_, Self::Param>,
911
pass: &mut TrackedRenderPass<'w>,
912
) -> RenderCommandResult {
913
let image_bind_groups = image_bind_groups.into_inner();
914
let Some(batch) = batches.get(&(view.retained_view_entity, item.entity())) else {
915
return RenderCommandResult::Skip;
916
};
917
918
pass.set_bind_group(
919
I,
920
image_bind_groups
921
.values
922
.get(&batch.image_handle_id)
923
.unwrap(),
924
&[],
925
);
926
RenderCommandResult::Success
927
}
928
}
929
930
pub struct DrawSpriteBatch;
931
impl<P: PhaseItem> RenderCommand<P> for DrawSpriteBatch {
932
type Param = (SRes<SpriteMeta>, SRes<SpriteBatches>);
933
type ViewQuery = Read<ExtractedView>;
934
type ItemQuery = ();
935
936
fn render<'w>(
937
item: &P,
938
view: ROQueryItem<'w, '_, Self::ViewQuery>,
939
_entity: Option<()>,
940
(sprite_meta, batches): SystemParamItem<'w, '_, Self::Param>,
941
pass: &mut TrackedRenderPass<'w>,
942
) -> RenderCommandResult {
943
let sprite_meta = sprite_meta.into_inner();
944
let Some(batch) = batches.get(&(view.retained_view_entity, item.entity())) else {
945
return RenderCommandResult::Skip;
946
};
947
948
pass.set_index_buffer(
949
sprite_meta.sprite_index_buffer.buffer().unwrap().slice(..),
950
0,
951
IndexFormat::Uint32,
952
);
953
pass.set_vertex_buffer(
954
0,
955
sprite_meta
956
.sprite_instance_buffer
957
.buffer()
958
.unwrap()
959
.slice(..),
960
);
961
pass.draw_indexed(0..6, 0, batch.range.clone());
962
RenderCommandResult::Success
963
}
964
}
965
966
/// Scales a texture to fit within a given quad size with keeping the aspect ratio.
967
fn apply_scaling(
968
scaling_mode: ScalingMode,
969
texture_size: Vec2,
970
quad_size: &mut Vec2,
971
quad_translation: &mut Vec2,
972
uv_offset_scale: &mut Vec4,
973
) {
974
let quad_ratio = quad_size.x / quad_size.y;
975
let texture_ratio = texture_size.x / texture_size.y;
976
let tex_quad_scale = texture_ratio / quad_ratio;
977
let quad_tex_scale = quad_ratio / texture_ratio;
978
979
match scaling_mode {
980
ScalingMode::FillCenter => {
981
if quad_ratio > texture_ratio {
982
// offset texture to center by y coordinate
983
uv_offset_scale.y += (uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale) * 0.5;
984
// sum up scales
985
uv_offset_scale.w *= tex_quad_scale;
986
} else {
987
// offset texture to center by x coordinate
988
uv_offset_scale.x += (uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale) * 0.5;
989
uv_offset_scale.z *= quad_tex_scale;
990
};
991
}
992
ScalingMode::FillStart => {
993
if quad_ratio > texture_ratio {
994
uv_offset_scale.y += uv_offset_scale.w - uv_offset_scale.w * tex_quad_scale;
995
uv_offset_scale.w *= tex_quad_scale;
996
} else {
997
uv_offset_scale.z *= quad_tex_scale;
998
}
999
}
1000
ScalingMode::FillEnd => {
1001
if quad_ratio > texture_ratio {
1002
uv_offset_scale.w *= tex_quad_scale;
1003
} else {
1004
uv_offset_scale.x += uv_offset_scale.z - uv_offset_scale.z * quad_tex_scale;
1005
uv_offset_scale.z *= quad_tex_scale;
1006
}
1007
}
1008
ScalingMode::FitCenter => {
1009
if texture_ratio > quad_ratio {
1010
// Scale based on width
1011
quad_size.y *= quad_tex_scale;
1012
} else {
1013
// Scale based on height
1014
quad_size.x *= tex_quad_scale;
1015
}
1016
}
1017
ScalingMode::FitStart => {
1018
if texture_ratio > quad_ratio {
1019
// The quad is scaled to match the image ratio, and the quad translation is adjusted
1020
// to start of the quad within the original quad size.
1021
let scale = Vec2::new(1.0, quad_tex_scale);
1022
let new_quad = *quad_size * scale;
1023
let offset = *quad_size - new_quad;
1024
*quad_translation = Vec2::new(0.0, -offset.y);
1025
*quad_size = new_quad;
1026
} else {
1027
let scale = Vec2::new(tex_quad_scale, 1.0);
1028
let new_quad = *quad_size * scale;
1029
let offset = *quad_size - new_quad;
1030
*quad_translation = Vec2::new(offset.x, 0.0);
1031
*quad_size = new_quad;
1032
}
1033
}
1034
ScalingMode::FitEnd => {
1035
if texture_ratio > quad_ratio {
1036
let scale = Vec2::new(1.0, quad_tex_scale);
1037
let new_quad = *quad_size * scale;
1038
let offset = *quad_size - new_quad;
1039
*quad_translation = Vec2::new(0.0, offset.y);
1040
*quad_size = new_quad;
1041
} else {
1042
let scale = Vec2::new(tex_quad_scale, 1.0);
1043
let new_quad = *quad_size * scale;
1044
let offset = *quad_size - new_quad;
1045
*quad_translation = Vec2::new(-offset.x, 0.0);
1046
*quad_size = new_quad;
1047
}
1048
}
1049
}
1050
}
1051
1052