Sprite Batching (#3060)
This implements the following: * **Sprite Batching**: Collects sprites in a vertex buffer to draw many sprites with a single draw call. Sprites are batched by their `Handle<Image>` within a specific z-level. When possible, sprites are opportunistically batched _across_ z-levels (when no sprites with a different texture exist between two sprites with the same texture on different z levels). With these changes, I can now get ~130,000 sprites at 60fps on the `bevymark_pipelined` example. * **Sprite Color Tints**: The `Sprite` type now has a `color` field. Non-white color tints result in a specialized render pipeline that passes the color in as a vertex attribute. I chose to specialize this because passing vertex colors has a measurable price (without colors I get ~130,000 sprites on bevymark, with colors I get ~100,000 sprites). "Colored" sprites cannot be batched with "uncolored" sprites, but I think this is fine because the chance of a "colored" sprite needing to batch with other "colored" sprites is generally probably way higher than an "uncolored" sprite needing to batch with a "colored" sprite. * **Sprite Flipping**: Sprites can be flipped on their x or y axis using `Sprite::flip_x` and `Sprite::flip_y`. This is also true for `TextureAtlasSprite`. * **Simpler BufferVec/UniformVec/DynamicUniformVec Clearing**: improved the clearing interface by removing the need to know the size of the final buffer at the initial clear.  Note that this moves sprites away from entity-driven rendering and back to extracted lists. We _could_ use entities here, but it necessitates that an intermediate list is allocated / populated to collect and sort extracted sprites. This redundant copy, combined with the normal overhead of spawning extracted sprite entities, brings bevymark down to ~80,000 sprites at 60fps. I think making sprites a bit more fixed (by default) is worth it. I view this as acceptable because batching makes normal entity-driven rendering pretty useless anyway (and we would want to batch most custom materials too). We can still support custom shaders with custom bindings, we'll just need to define a specific interface for it.
This commit is contained in:
parent
2f22f5ca21
commit
85487707ef
@ -4,13 +4,13 @@ use bevy::{
|
|||||||
ecs::prelude::*,
|
ecs::prelude::*,
|
||||||
input::Input,
|
input::Input,
|
||||||
math::Vec3,
|
math::Vec3,
|
||||||
prelude::{App, AssetServer, Handle, MouseButton, Transform},
|
prelude::{info, App, AssetServer, Handle, MouseButton, Transform},
|
||||||
render2::{camera::OrthographicCameraBundle, color::Color, texture::Image},
|
render2::{camera::OrthographicCameraBundle, color::Color, texture::Image},
|
||||||
sprite2::PipelinedSpriteBundle,
|
sprite2::{PipelinedSpriteBundle, Sprite},
|
||||||
window::WindowDescriptor,
|
window::WindowDescriptor,
|
||||||
PipelinedDefaultPlugins,
|
PipelinedDefaultPlugins,
|
||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::{random, Rng};
|
||||||
|
|
||||||
const BIRDS_PER_SECOND: u32 = 10000;
|
const BIRDS_PER_SECOND: u32 = 10000;
|
||||||
const _BASE_COLOR: Color = Color::rgb(5.0, 5.0, 5.0);
|
const _BASE_COLOR: Color = Color::rgb(5.0, 5.0, 5.0);
|
||||||
@ -21,6 +21,7 @@ const HALF_BIRD_SIZE: f32 = 256. * BIRD_SCALE * 0.5;
|
|||||||
|
|
||||||
struct BevyCounter {
|
struct BevyCounter {
|
||||||
pub count: u128,
|
pub count: u128,
|
||||||
|
pub color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Bird {
|
struct Bird {
|
||||||
@ -52,7 +53,10 @@ fn main() {
|
|||||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||||
.add_plugin(LogDiagnosticsPlugin::default())
|
.add_plugin(LogDiagnosticsPlugin::default())
|
||||||
// .add_plugin(WgpuResourceDiagnosticsPlugin::default())
|
// .add_plugin(WgpuResourceDiagnosticsPlugin::default())
|
||||||
.insert_resource(BevyCounter { count: 0 })
|
.insert_resource(BevyCounter {
|
||||||
|
count: 0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
})
|
||||||
// .init_resource::<BirdMaterial>()
|
// .init_resource::<BirdMaterial>()
|
||||||
.add_startup_system(setup)
|
.add_startup_system(setup)
|
||||||
.add_system(mouse_handler)
|
.add_system(mouse_handler)
|
||||||
@ -161,6 +165,9 @@ fn mouse_handler(
|
|||||||
// texture: Some(texture_handle),
|
// texture: Some(texture_handle),
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
if mouse_button_input.just_released(MouseButton::Left) {
|
||||||
|
counter.color = Color::rgb(random(), random(), random());
|
||||||
|
}
|
||||||
|
|
||||||
if mouse_button_input.pressed(MouseButton::Left) {
|
if mouse_button_input.pressed(MouseButton::Left) {
|
||||||
let spawn_count = (BIRDS_PER_SECOND as f64 * time.delta_seconds_f64()) as u128;
|
let spawn_count = (BIRDS_PER_SECOND as f64 * time.delta_seconds_f64()) as u128;
|
||||||
@ -194,6 +201,10 @@ fn spawn_birds(
|
|||||||
scale: Vec3::splat(BIRD_SCALE),
|
scale: Vec3::splat(BIRD_SCALE),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
sprite: Sprite {
|
||||||
|
color: counter.color,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
.insert(Bird {
|
.insert(Bird {
|
||||||
@ -255,7 +266,7 @@ fn counter_system(
|
|||||||
counter: Res<BevyCounter>,
|
counter: Res<BevyCounter>,
|
||||||
) {
|
) {
|
||||||
if timer.timer.tick(time.delta()).finished() {
|
if timer.timer.tick(time.delta()).finished() {
|
||||||
println!("counter: {}", counter.count);
|
info!("counter: {}", counter.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ pub use main_pass_3d::*;
|
|||||||
pub use main_pass_driver::*;
|
pub use main_pass_driver::*;
|
||||||
|
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_asset::Handle;
|
|
||||||
use bevy_core::FloatOrd;
|
use bevy_core::FloatOrd;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
@ -23,7 +22,7 @@ use bevy_render2::{
|
|||||||
},
|
},
|
||||||
render_resource::*,
|
render_resource::*,
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
texture::{Image, TextureCache},
|
texture::TextureCache,
|
||||||
view::{ExtractedView, Msaa, ViewDepthTexture},
|
view::{ExtractedView, Msaa, ViewDepthTexture},
|
||||||
RenderApp, RenderStage, RenderWorld,
|
RenderApp, RenderStage, RenderWorld,
|
||||||
};
|
};
|
||||||
@ -131,18 +130,18 @@ impl Plugin for CorePipelinePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Transparent2d {
|
pub struct Transparent2d {
|
||||||
pub sort_key: Handle<Image>,
|
pub sort_key: FloatOrd,
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub pipeline: CachedPipelineId,
|
pub pipeline: CachedPipelineId,
|
||||||
pub draw_function: DrawFunctionId,
|
pub draw_function: DrawFunctionId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhaseItem for Transparent2d {
|
impl PhaseItem for Transparent2d {
|
||||||
type SortKey = Handle<Image>;
|
type SortKey = FloatOrd;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn sort_key(&self) -> Self::SortKey {
|
fn sort_key(&self) -> Self::SortKey {
|
||||||
self.sort_key.clone_weak()
|
self.sort_key
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -383,10 +383,7 @@ pub fn prepare_lights(
|
|||||||
point_lights: Query<&ExtractedPointLight>,
|
point_lights: Query<&ExtractedPointLight>,
|
||||||
directional_lights: Query<&ExtractedDirectionalLight>,
|
directional_lights: Query<&ExtractedDirectionalLight>,
|
||||||
) {
|
) {
|
||||||
// PERF: view.iter().count() could be views.iter().len() if we implemented ExactSizeIterator for archetype-only filters
|
light_meta.view_gpu_lights.clear();
|
||||||
light_meta
|
|
||||||
.view_gpu_lights
|
|
||||||
.reserve_and_clear(views.iter().count(), &render_device);
|
|
||||||
|
|
||||||
let ambient_color = ambient_light.color.as_rgba_linear() * ambient_light.brightness;
|
let ambient_color = ambient_light.color.as_rgba_linear() * ambient_light.brightness;
|
||||||
// set up light data for each view
|
// set up light data for each view
|
||||||
@ -605,7 +602,9 @@ pub fn prepare_lights(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
light_meta.view_gpu_lights.write_buffer(&render_queue);
|
light_meta
|
||||||
|
.view_gpu_lights
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_shadow_view_bind_group(
|
pub fn queue_shadow_view_bind_group(
|
||||||
|
@ -5,6 +5,7 @@ pub trait SrgbColorSpace {
|
|||||||
|
|
||||||
// source: https://entropymine.com/imageworsener/srgbformula/
|
// source: https://entropymine.com/imageworsener/srgbformula/
|
||||||
impl SrgbColorSpace for f32 {
|
impl SrgbColorSpace for f32 {
|
||||||
|
#[inline]
|
||||||
fn linear_to_nonlinear_srgb(self) -> f32 {
|
fn linear_to_nonlinear_srgb(self) -> f32 {
|
||||||
if self <= 0.0 {
|
if self <= 0.0 {
|
||||||
return self;
|
return self;
|
||||||
@ -17,6 +18,7 @@ impl SrgbColorSpace for f32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn nonlinear_to_linear_srgb(self) -> f32 {
|
fn nonlinear_to_linear_srgb(self) -> f32 {
|
||||||
if self <= 0.0 {
|
if self <= 0.0 {
|
||||||
return self;
|
return self;
|
||||||
@ -32,6 +34,7 @@ impl SrgbColorSpace for f32 {
|
|||||||
pub struct HslRepresentation;
|
pub struct HslRepresentation;
|
||||||
impl HslRepresentation {
|
impl HslRepresentation {
|
||||||
/// converts a color in HLS space to sRGB space
|
/// converts a color in HLS space to sRGB space
|
||||||
|
#[inline]
|
||||||
pub fn hsl_to_nonlinear_srgb(hue: f32, saturation: f32, lightness: f32) -> [f32; 3] {
|
pub fn hsl_to_nonlinear_srgb(hue: f32, saturation: f32, lightness: f32) -> [f32; 3] {
|
||||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
||||||
let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
|
let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
|
||||||
@ -60,6 +63,7 @@ impl HslRepresentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// converts a color in sRGB space to HLS space
|
/// converts a color in sRGB space to HLS space
|
||||||
|
#[inline]
|
||||||
pub fn nonlinear_srgb_to_hsl([red, green, blue]: [f32; 3]) -> (f32, f32, f32) {
|
pub fn nonlinear_srgb_to_hsl([red, green, blue]: [f32; 3]) -> (f32, f32, f32) {
|
||||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
||||||
let x_max = red.max(green.max(blue));
|
let x_max = red.max(green.max(blue));
|
||||||
|
@ -416,6 +416,7 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a `Color` to a `[f32; 4]` from linear RBG colorspace
|
/// Converts a `Color` to a `[f32; 4]` from linear RBG colorspace
|
||||||
|
#[inline]
|
||||||
pub fn as_linear_rgba_f32(self: Color) -> [f32; 4] {
|
pub fn as_linear_rgba_f32(self: Color) -> [f32; 4] {
|
||||||
match self {
|
match self {
|
||||||
Color::Rgba {
|
Color::Rgba {
|
||||||
|
@ -92,10 +92,7 @@ fn prepare_uniform_components<C: Component>(
|
|||||||
) where
|
) where
|
||||||
C: AsStd140 + Clone,
|
C: AsStd140 + Clone,
|
||||||
{
|
{
|
||||||
let len = components.iter().len();
|
component_uniforms.uniforms.clear();
|
||||||
component_uniforms
|
|
||||||
.uniforms
|
|
||||||
.reserve_and_clear(len, &render_device);
|
|
||||||
for (entity, component) in components.iter() {
|
for (entity, component) in components.iter() {
|
||||||
commands
|
commands
|
||||||
.get_or_spawn(entity)
|
.get_or_spawn(entity)
|
||||||
@ -105,7 +102,9 @@ fn prepare_uniform_components<C: Component>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
component_uniforms.uniforms.write_buffer(&render_queue);
|
component_uniforms
|
||||||
|
.uniforms
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractComponentPlugin<C, F = ()>(PhantomData<fn() -> (C, F)>);
|
pub struct ExtractComponentPlugin<C, F = ()>(PhantomData<fn() -> (C, F)>);
|
||||||
|
@ -43,17 +43,20 @@ impl<T: Pod> BufferVec<T> {
|
|||||||
self.capacity
|
self.capacity
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> usize {
|
#[inline]
|
||||||
let len = self.values.len();
|
pub fn len(&self) -> usize {
|
||||||
if len < self.capacity {
|
self.values.len()
|
||||||
self.values.push(value);
|
|
||||||
len
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Cannot push value because capacity of {} has been reached",
|
|
||||||
self.capacity
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.values.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, value: T) -> usize {
|
||||||
|
let index = self.values.len();
|
||||||
|
self.values.push(value);
|
||||||
|
index
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) {
|
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) {
|
||||||
@ -69,12 +72,11 @@ impl<T: Pod> BufferVec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve_and_clear(&mut self, capacity: usize, device: &RenderDevice) {
|
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
|
||||||
self.clear();
|
if self.values.is_empty() {
|
||||||
self.reserve(capacity, device);
|
return;
|
||||||
}
|
}
|
||||||
|
self.reserve(self.values.len(), device);
|
||||||
pub fn write_buffer(&mut self, queue: &RenderQueue) {
|
|
||||||
if let Some(buffer) = &self.buffer {
|
if let Some(buffer) = &self.buffer {
|
||||||
let range = 0..self.item_size * self.values.len();
|
let range = 0..self.item_size * self.values.len();
|
||||||
let bytes: &[u8] = cast_slice(&self.values);
|
let bytes: &[u8] = cast_slice(&self.values);
|
||||||
|
@ -58,19 +58,12 @@ impl<T: AsStd140> UniformVec<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> usize {
|
pub fn push(&mut self, value: T) -> usize {
|
||||||
let len = self.values.len();
|
let index = self.values.len();
|
||||||
if len < self.capacity {
|
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
len
|
index
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Cannot push value because capacity of {} has been reached",
|
|
||||||
self.capacity
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) {
|
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
|
||||||
if capacity > self.capacity {
|
if capacity > self.capacity {
|
||||||
self.capacity = capacity;
|
self.capacity = capacity;
|
||||||
let size = self.item_size * capacity;
|
let size = self.item_size * capacity;
|
||||||
@ -81,15 +74,17 @@ impl<T: AsStd140> UniformVec<T> {
|
|||||||
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
|
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
|
||||||
mapped_at_creation: false,
|
mapped_at_creation: false,
|
||||||
}));
|
}));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve_and_clear(&mut self, capacity: usize, device: &RenderDevice) {
|
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
|
||||||
self.clear();
|
if self.values.is_empty() {
|
||||||
self.reserve(capacity, device);
|
return;
|
||||||
}
|
}
|
||||||
|
self.reserve(self.values.len(), device);
|
||||||
pub fn write_buffer(&mut self, queue: &RenderQueue) {
|
|
||||||
if let Some(uniform_buffer) = &self.uniform_buffer {
|
if let Some(uniform_buffer) = &self.uniform_buffer {
|
||||||
let range = 0..self.item_size * self.values.len();
|
let range = 0..self.item_size * self.values.len();
|
||||||
let mut writer = std140::Writer::new(&mut self.scratch[range.clone()]);
|
let mut writer = std140::Writer::new(&mut self.scratch[range.clone()]);
|
||||||
@ -152,13 +147,8 @@ impl<T: AsStd140> DynamicUniformVec<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reserve_and_clear(&mut self, capacity: usize, device: &RenderDevice) {
|
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
|
||||||
self.uniform_vec.reserve_and_clear(capacity, device);
|
self.uniform_vec.write_buffer(device, queue);
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn write_buffer(&mut self, queue: &RenderQueue) {
|
|
||||||
self.uniform_vec.write_buffer(queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -90,11 +90,9 @@ fn prepare_view_uniforms(
|
|||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
render_queue: Res<RenderQueue>,
|
render_queue: Res<RenderQueue>,
|
||||||
mut view_uniforms: ResMut<ViewUniforms>,
|
mut view_uniforms: ResMut<ViewUniforms>,
|
||||||
mut views: Query<(Entity, &ExtractedView)>,
|
views: Query<(Entity, &ExtractedView)>,
|
||||||
) {
|
) {
|
||||||
view_uniforms
|
view_uniforms.uniforms.clear();
|
||||||
.uniforms
|
|
||||||
.reserve_and_clear(views.iter_mut().len(), &render_device);
|
|
||||||
for (entity, camera) in views.iter() {
|
for (entity, camera) in views.iter() {
|
||||||
let projection = camera.projection;
|
let projection = camera.projection;
|
||||||
let view_uniforms = ViewUniformOffset {
|
let view_uniforms = ViewUniformOffset {
|
||||||
@ -108,7 +106,9 @@ fn prepare_view_uniforms(
|
|||||||
commands.entity(entity).insert(view_uniforms);
|
commands.entity(entity).insert(view_uniforms);
|
||||||
}
|
}
|
||||||
|
|
||||||
view_uniforms.uniforms.write_buffer(&render_queue);
|
view_uniforms
|
||||||
|
.uniforms
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_view_targets(
|
fn prepare_view_targets(
|
||||||
|
@ -18,7 +18,11 @@ use bevy_app::prelude::*;
|
|||||||
use bevy_asset::{AddAsset, Assets, HandleUntyped};
|
use bevy_asset::{AddAsset, Assets, HandleUntyped};
|
||||||
use bevy_core_pipeline::Transparent2d;
|
use bevy_core_pipeline::Transparent2d;
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render2::{render_phase::DrawFunctions, render_resource::Shader, RenderApp, RenderStage};
|
use bevy_render2::{
|
||||||
|
render_phase::DrawFunctions,
|
||||||
|
render_resource::{Shader, SpecializedPipelines},
|
||||||
|
RenderApp, RenderStage,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SpritePlugin;
|
pub struct SpritePlugin;
|
||||||
@ -36,8 +40,9 @@ impl Plugin for SpritePlugin {
|
|||||||
render_app
|
render_app
|
||||||
.init_resource::<ImageBindGroups>()
|
.init_resource::<ImageBindGroups>()
|
||||||
.init_resource::<SpritePipeline>()
|
.init_resource::<SpritePipeline>()
|
||||||
|
.init_resource::<SpecializedPipelines<SpritePipeline>>()
|
||||||
.init_resource::<SpriteMeta>()
|
.init_resource::<SpriteMeta>()
|
||||||
.add_system_to_stage(RenderStage::Extract, render::extract_atlases)
|
.init_resource::<ExtractedSprites>()
|
||||||
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
|
.add_system_to_stage(RenderStage::Extract, render::extract_sprites)
|
||||||
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
|
.add_system_to_stage(RenderStage::Prepare, render::prepare_sprites)
|
||||||
.add_system_to_stage(RenderStage::Queue, queue_sprites);
|
.add_system_to_stage(RenderStage::Queue, queue_sprites);
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
|
use std::{cmp::Ordering, ops::Range};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
texture_atlas::{TextureAtlas, TextureAtlasSprite},
|
||||||
Rect, Sprite, SPRITE_SHADER_HANDLE,
|
Rect, Sprite, SPRITE_SHADER_HANDLE,
|
||||||
};
|
};
|
||||||
use bevy_asset::{Assets, Handle};
|
use bevy_asset::{Assets, Handle};
|
||||||
|
use bevy_core::FloatOrd;
|
||||||
use bevy_core_pipeline::Transparent2d;
|
use bevy_core_pipeline::Transparent2d;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{lifetimeless::*, SystemState},
|
system::{lifetimeless::*, SystemState},
|
||||||
};
|
};
|
||||||
use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles};
|
use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles};
|
||||||
use bevy_render2::{
|
use bevy_render2::{
|
||||||
mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues},
|
color::Color,
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
|
render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||||
render_resource::*,
|
render_resource::*,
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
texture::{BevyDefault, Image},
|
texture::{BevyDefault, Image},
|
||||||
view::{ViewUniformOffset, ViewUniforms},
|
view::{ViewUniformOffset, ViewUniforms},
|
||||||
|
RenderWorld,
|
||||||
};
|
};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
@ -25,14 +29,12 @@ use bytemuck::{Pod, Zeroable};
|
|||||||
pub struct SpritePipeline {
|
pub struct SpritePipeline {
|
||||||
view_layout: BindGroupLayout,
|
view_layout: BindGroupLayout,
|
||||||
material_layout: BindGroupLayout,
|
material_layout: BindGroupLayout,
|
||||||
pipeline: CachedPipelineId,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for SpritePipeline {
|
impl FromWorld for SpritePipeline {
|
||||||
fn from_world(world: &mut World) -> Self {
|
fn from_world(world: &mut World) -> Self {
|
||||||
let world = world.cell();
|
let world = world.cell();
|
||||||
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
||||||
let mut pipeline_cache = world.get_resource_mut::<RenderPipelineCache>().unwrap();
|
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[BindGroupLayoutEntry {
|
entries: &[BindGroupLayoutEntry {
|
||||||
@ -75,12 +77,23 @@ impl FromWorld for SpritePipeline {
|
|||||||
label: Some("sprite_material_layout"),
|
label: Some("sprite_material_layout"),
|
||||||
});
|
});
|
||||||
|
|
||||||
let descriptor = RenderPipelineDescriptor {
|
SpritePipeline {
|
||||||
vertex: VertexState {
|
view_layout,
|
||||||
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
|
material_layout,
|
||||||
entry_point: "vertex".into(),
|
}
|
||||||
shader_defs: vec![],
|
}
|
||||||
buffers: vec![VertexBufferLayout {
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
pub struct SpritePipelineKey {
|
||||||
|
colored: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecializedPipeline for SpritePipeline {
|
||||||
|
type Key = SpritePipelineKey;
|
||||||
|
|
||||||
|
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
|
||||||
|
let mut vertex_buffer_layout = VertexBufferLayout {
|
||||||
array_stride: 20,
|
array_stride: 20,
|
||||||
step_mode: VertexStepMode::Vertex,
|
step_mode: VertexStepMode::Vertex,
|
||||||
attributes: vec![
|
attributes: vec![
|
||||||
@ -95,11 +108,28 @@ impl FromWorld for SpritePipeline {
|
|||||||
shader_location: 1,
|
shader_location: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}],
|
};
|
||||||
|
let mut shader_defs = Vec::new();
|
||||||
|
if key.colored {
|
||||||
|
shader_defs.push("COLORED".to_string());
|
||||||
|
vertex_buffer_layout.attributes.push(VertexAttribute {
|
||||||
|
format: VertexFormat::Uint32,
|
||||||
|
offset: 20,
|
||||||
|
shader_location: 2,
|
||||||
|
});
|
||||||
|
vertex_buffer_layout.array_stride += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPipelineDescriptor {
|
||||||
|
vertex: VertexState {
|
||||||
|
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
|
||||||
|
entry_point: "vertex".into(),
|
||||||
|
shader_defs: shader_defs.clone(),
|
||||||
|
buffers: vec![vertex_buffer_layout],
|
||||||
},
|
},
|
||||||
fragment: Some(FragmentState {
|
fragment: Some(FragmentState {
|
||||||
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
|
shader: SPRITE_SHADER_HANDLE.typed::<Shader>(),
|
||||||
shader_defs: vec![],
|
shader_defs,
|
||||||
entry_point: "fragment".into(),
|
entry_point: "fragment".into(),
|
||||||
targets: vec![ColorTargetState {
|
targets: vec![ColorTargetState {
|
||||||
format: TextureFormat::bevy_default(),
|
format: TextureFormat::bevy_default(),
|
||||||
@ -118,7 +148,7 @@ impl FromWorld for SpritePipeline {
|
|||||||
write_mask: ColorWrites::ALL,
|
write_mask: ColorWrites::ALL,
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
layout: Some(vec![view_layout.clone(), material_layout.clone()]),
|
layout: Some(vec![self.view_layout.clone(), self.material_layout.clone()]),
|
||||||
primitive: PrimitiveState {
|
primitive: PrimitiveState {
|
||||||
front_face: FrontFace::Ccw,
|
front_face: FrontFace::Ccw,
|
||||||
cull_mode: None,
|
cull_mode: None,
|
||||||
@ -135,67 +165,41 @@ impl FromWorld for SpritePipeline {
|
|||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
label: Some("sprite_pipeline".into()),
|
label: Some("sprite_pipeline".into()),
|
||||||
};
|
|
||||||
|
|
||||||
SpritePipeline {
|
|
||||||
pipeline: pipeline_cache.queue(descriptor),
|
|
||||||
view_layout,
|
|
||||||
material_layout,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtractedSprite {
|
pub struct ExtractedSprite {
|
||||||
transform: Mat4,
|
transform: Mat4,
|
||||||
|
color: Color,
|
||||||
rect: Rect,
|
rect: Rect,
|
||||||
handle: Handle<Image>,
|
handle: Handle<Image>,
|
||||||
atlas_size: Option<Vec2>,
|
atlas_size: Option<Vec2>,
|
||||||
vertex_index: usize,
|
flip_x: bool,
|
||||||
|
flip_y: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_atlases(
|
#[derive(Default)]
|
||||||
mut commands: Commands,
|
pub struct ExtractedSprites {
|
||||||
texture_atlases: Res<Assets<TextureAtlas>>,
|
sprites: Vec<ExtractedSprite>,
|
||||||
atlas_query: Query<(
|
|
||||||
Entity,
|
|
||||||
&TextureAtlasSprite,
|
|
||||||
&GlobalTransform,
|
|
||||||
&Handle<TextureAtlas>,
|
|
||||||
)>,
|
|
||||||
) {
|
|
||||||
let mut sprites = Vec::new();
|
|
||||||
for (entity, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
|
|
||||||
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
|
|
||||||
let rect = texture_atlas.textures[atlas_sprite.index];
|
|
||||||
sprites.push((
|
|
||||||
entity,
|
|
||||||
(ExtractedSprite {
|
|
||||||
atlas_size: Some(texture_atlas.size),
|
|
||||||
transform: transform.compute_matrix(),
|
|
||||||
rect,
|
|
||||||
handle: texture_atlas.texture.clone_weak(),
|
|
||||||
vertex_index: 0,
|
|
||||||
},),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
commands.insert_or_spawn_batch(sprites);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_sprites(
|
pub fn extract_sprites(
|
||||||
mut commands: Commands,
|
mut render_world: ResMut<RenderWorld>,
|
||||||
images: Res<Assets<Image>>,
|
images: Res<Assets<Image>>,
|
||||||
sprite_query: Query<(Entity, &Sprite, &GlobalTransform, &Handle<Image>)>,
|
texture_atlases: Res<Assets<TextureAtlas>>,
|
||||||
|
sprite_query: Query<(&Sprite, &GlobalTransform, &Handle<Image>)>,
|
||||||
|
atlas_query: Query<(&TextureAtlasSprite, &GlobalTransform, &Handle<TextureAtlas>)>,
|
||||||
) {
|
) {
|
||||||
let mut sprites = Vec::new();
|
let mut extracted_sprites = render_world.get_resource_mut::<ExtractedSprites>().unwrap();
|
||||||
for (entity, sprite, transform, handle) in sprite_query.iter() {
|
extracted_sprites.sprites.clear();
|
||||||
|
for (sprite, transform, handle) in sprite_query.iter() {
|
||||||
if let Some(image) = images.get(handle) {
|
if let Some(image) = images.get(handle) {
|
||||||
let size = image.texture_descriptor.size;
|
let size = image.texture_descriptor.size;
|
||||||
|
|
||||||
sprites.push((
|
extracted_sprites.sprites.push(ExtractedSprite {
|
||||||
entity,
|
|
||||||
(ExtractedSprite {
|
|
||||||
atlas_size: None,
|
atlas_size: None,
|
||||||
|
color: sprite.color,
|
||||||
transform: transform.compute_matrix(),
|
transform: transform.compute_matrix(),
|
||||||
rect: Rect {
|
rect: Rect {
|
||||||
min: Vec2::ZERO,
|
min: Vec2::ZERO,
|
||||||
@ -203,13 +207,26 @@ pub fn extract_sprites(
|
|||||||
.custom_size
|
.custom_size
|
||||||
.unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)),
|
.unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)),
|
||||||
},
|
},
|
||||||
|
flip_x: sprite.flip_x,
|
||||||
|
flip_y: sprite.flip_y,
|
||||||
handle: handle.clone_weak(),
|
handle: handle.clone_weak(),
|
||||||
vertex_index: 0,
|
});
|
||||||
},),
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
commands.insert_or_spawn_batch(sprites);
|
for (atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() {
|
||||||
|
if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) {
|
||||||
|
let rect = texture_atlas.textures[atlas_sprite.index as usize];
|
||||||
|
extracted_sprites.sprites.push(ExtractedSprite {
|
||||||
|
atlas_size: Some(texture_atlas.size),
|
||||||
|
color: atlas_sprite.color,
|
||||||
|
transform: transform.compute_matrix(),
|
||||||
|
rect,
|
||||||
|
flip_x: atlas_sprite.flip_x,
|
||||||
|
flip_y: atlas_sprite.flip_y,
|
||||||
|
handle: texture_atlas.texture.clone_weak(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -219,10 +236,17 @@ struct SpriteVertex {
|
|||||||
pub uv: [f32; 2],
|
pub uv: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||||
|
struct ColoredSpriteVertex {
|
||||||
|
pub position: [f32; 3],
|
||||||
|
pub uv: [f32; 2],
|
||||||
|
pub color: u32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SpriteMeta {
|
pub struct SpriteMeta {
|
||||||
vertices: BufferVec<SpriteVertex>,
|
vertices: BufferVec<SpriteVertex>,
|
||||||
indices: BufferVec<u32>,
|
colored_vertices: BufferVec<ColoredSpriteVertex>,
|
||||||
quad: Mesh,
|
|
||||||
view_bind_group: Option<BindGroup>,
|
view_bind_group: Option<BindGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,88 +254,180 @@ impl Default for SpriteMeta {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vertices: BufferVec::new(BufferUsages::VERTEX),
|
vertices: BufferVec::new(BufferUsages::VERTEX),
|
||||||
indices: BufferVec::new(BufferUsages::INDEX),
|
colored_vertices: BufferVec::new(BufferUsages::VERTEX),
|
||||||
view_bind_group: None,
|
view_bind_group: None,
|
||||||
quad: Quad {
|
|
||||||
size: Vec2::new(1.0, 1.0),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QUAD_VERTEX_POSITIONS: &[Vec3] = &[
|
||||||
|
const_vec3!([-0.5, -0.5, 0.0]),
|
||||||
|
const_vec3!([0.5, 0.5, 0.0]),
|
||||||
|
const_vec3!([-0.5, 0.5, 0.0]),
|
||||||
|
const_vec3!([-0.5, -0.5, 0.0]),
|
||||||
|
const_vec3!([0.5, -0.5, 0.0]),
|
||||||
|
const_vec3!([0.5, 0.5, 0.0]),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct SpriteBatch {
|
||||||
|
range: Range<u32>,
|
||||||
|
handle: Handle<Image>,
|
||||||
|
z: f32,
|
||||||
|
colored: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prepare_sprites(
|
pub fn prepare_sprites(
|
||||||
|
mut commands: Commands,
|
||||||
render_device: Res<RenderDevice>,
|
render_device: Res<RenderDevice>,
|
||||||
render_queue: Res<RenderQueue>,
|
render_queue: Res<RenderQueue>,
|
||||||
mut sprite_meta: ResMut<SpriteMeta>,
|
mut sprite_meta: ResMut<SpriteMeta>,
|
||||||
mut extracted_sprites: Query<&mut ExtractedSprite>,
|
mut extracted_sprites: ResMut<ExtractedSprites>,
|
||||||
) {
|
) {
|
||||||
let extracted_sprite_len = extracted_sprites.iter_mut().len();
|
sprite_meta.vertices.clear();
|
||||||
// dont create buffers when there are no sprites
|
sprite_meta.colored_vertices.clear();
|
||||||
if extracted_sprite_len == 0 {
|
|
||||||
return;
|
// sort first by z and then by handle. this ensures that, when possible, batches span multiple z layers
|
||||||
|
// batches won't span z-layers if there is another batch between them
|
||||||
|
extracted_sprites.sprites.sort_by(|a, b| {
|
||||||
|
match FloatOrd(a.transform.w_axis[2]).cmp(&FloatOrd(b.transform.w_axis[2])) {
|
||||||
|
Ordering::Equal => a.handle.cmp(&b.handle),
|
||||||
|
other => other,
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let quad_vertex_positions = if let VertexAttributeValues::Float32x3(vertex_positions) =
|
let mut start = 0;
|
||||||
sprite_meta
|
let mut end = 0;
|
||||||
.quad
|
let mut colored_start = 0;
|
||||||
.attribute(Mesh::ATTRIBUTE_POSITION)
|
let mut colored_end = 0;
|
||||||
.unwrap()
|
let mut current_batch_handle: Option<Handle<Image>> = None;
|
||||||
.clone()
|
let mut current_batch_colored = false;
|
||||||
|
let mut last_z = 0.0;
|
||||||
|
for extracted_sprite in extracted_sprites.sprites.iter() {
|
||||||
|
let colored = extracted_sprite.color != Color::WHITE;
|
||||||
|
if let Some(current_batch_handle) = ¤t_batch_handle {
|
||||||
|
if *current_batch_handle != extracted_sprite.handle || current_batch_colored != colored
|
||||||
{
|
{
|
||||||
vertex_positions
|
if current_batch_colored {
|
||||||
|
commands.spawn_bundle((SpriteBatch {
|
||||||
|
range: colored_start..colored_end,
|
||||||
|
handle: current_batch_handle.clone_weak(),
|
||||||
|
z: last_z,
|
||||||
|
colored: true,
|
||||||
|
},));
|
||||||
|
colored_start = colored_end;
|
||||||
} else {
|
} else {
|
||||||
panic!("expected vec3");
|
commands.spawn_bundle((SpriteBatch {
|
||||||
};
|
range: start..end,
|
||||||
|
handle: current_batch_handle.clone_weak(),
|
||||||
let quad_indices = if let Indices::U32(indices) = sprite_meta.quad.indices().unwrap() {
|
z: last_z,
|
||||||
indices.clone()
|
colored: false,
|
||||||
} else {
|
},));
|
||||||
panic!("expected u32 indices");
|
start = end;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
sprite_meta.vertices.reserve_and_clear(
|
}
|
||||||
extracted_sprite_len * quad_vertex_positions.len(),
|
current_batch_handle = Some(extracted_sprite.handle.clone_weak());
|
||||||
&render_device,
|
current_batch_colored = colored;
|
||||||
);
|
|
||||||
sprite_meta
|
|
||||||
.indices
|
|
||||||
.reserve_and_clear(extracted_sprite_len * quad_indices.len(), &render_device);
|
|
||||||
|
|
||||||
for (i, mut extracted_sprite) in extracted_sprites.iter_mut().enumerate() {
|
|
||||||
let sprite_rect = extracted_sprite.rect;
|
let sprite_rect = extracted_sprite.rect;
|
||||||
|
|
||||||
// Specify the corners of the sprite
|
// Specify the corners of the sprite
|
||||||
let bottom_left = Vec2::new(sprite_rect.min.x, sprite_rect.max.y);
|
let mut bottom_left = Vec2::new(sprite_rect.min.x, sprite_rect.max.y);
|
||||||
let top_left = sprite_rect.min;
|
let mut top_left = sprite_rect.min;
|
||||||
let top_right = Vec2::new(sprite_rect.max.x, sprite_rect.min.y);
|
let mut top_right = Vec2::new(sprite_rect.max.x, sprite_rect.min.y);
|
||||||
let bottom_right = sprite_rect.max;
|
let mut bottom_right = sprite_rect.max;
|
||||||
|
|
||||||
let atlas_positions: [Vec2; 4] = [bottom_left, top_left, top_right, bottom_right];
|
if extracted_sprite.flip_x {
|
||||||
|
bottom_left.x = sprite_rect.max.x;
|
||||||
|
top_left.x = sprite_rect.max.x;
|
||||||
|
bottom_right.x = sprite_rect.min.x;
|
||||||
|
top_right.x = sprite_rect.min.x;
|
||||||
|
}
|
||||||
|
|
||||||
extracted_sprite.vertex_index = i;
|
if extracted_sprite.flip_y {
|
||||||
for (index, vertex_position) in quad_vertex_positions.iter().enumerate() {
|
bottom_left.y = sprite_rect.min.y;
|
||||||
let mut final_position =
|
bottom_right.y = sprite_rect.min.y;
|
||||||
Vec3::from(*vertex_position) * extracted_sprite.rect.size().extend(1.0);
|
top_left.y = sprite_rect.max.y;
|
||||||
|
top_right.y = sprite_rect.max.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
let atlas_extent = extracted_sprite.atlas_size.unwrap_or(sprite_rect.max);
|
||||||
|
bottom_left /= atlas_extent;
|
||||||
|
bottom_right /= atlas_extent;
|
||||||
|
top_left /= atlas_extent;
|
||||||
|
top_right /= atlas_extent;
|
||||||
|
|
||||||
|
let uvs: [[f32; 2]; 6] = [
|
||||||
|
bottom_left.into(),
|
||||||
|
top_right.into(),
|
||||||
|
top_left.into(),
|
||||||
|
bottom_left.into(),
|
||||||
|
bottom_right.into(),
|
||||||
|
top_right.into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
let rect_size = extracted_sprite.rect.size().extend(1.0);
|
||||||
|
if current_batch_colored {
|
||||||
|
let color = extracted_sprite.color.as_linear_rgba_f32();
|
||||||
|
// encode color as a single u32 to save space
|
||||||
|
let color = (color[0] * 255.0) as u32
|
||||||
|
| ((color[1] * 255.0) as u32) << 8
|
||||||
|
| ((color[2] * 255.0) as u32) << 16
|
||||||
|
| ((color[3] * 255.0) as u32) << 24;
|
||||||
|
for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() {
|
||||||
|
let mut final_position = *vertex_position * rect_size;
|
||||||
|
final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz();
|
||||||
|
sprite_meta.colored_vertices.push(ColoredSpriteVertex {
|
||||||
|
position: final_position.into(),
|
||||||
|
uv: uvs[index],
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() {
|
||||||
|
let mut final_position = *vertex_position * rect_size;
|
||||||
final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz();
|
final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz();
|
||||||
sprite_meta.vertices.push(SpriteVertex {
|
sprite_meta.vertices.push(SpriteVertex {
|
||||||
position: final_position.into(),
|
position: final_position.into(),
|
||||||
uv: (atlas_positions[index]
|
uv: uvs[index],
|
||||||
/ extracted_sprite.atlas_size.unwrap_or(sprite_rect.max))
|
|
||||||
.into(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_z = extracted_sprite.transform.w_axis[2];
|
||||||
|
if current_batch_colored {
|
||||||
|
colored_end += QUAD_VERTEX_POSITIONS.len() as u32;
|
||||||
|
} else {
|
||||||
|
end += QUAD_VERTEX_POSITIONS.len() as u32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if start != end, there is one last batch to process
|
||||||
|
if start != end {
|
||||||
|
if let Some(current_batch_handle) = current_batch_handle {
|
||||||
|
commands.spawn_bundle((SpriteBatch {
|
||||||
|
range: start..end,
|
||||||
|
handle: current_batch_handle,
|
||||||
|
colored: false,
|
||||||
|
z: last_z,
|
||||||
|
},));
|
||||||
|
}
|
||||||
|
} else if colored_start != colored_end {
|
||||||
|
if let Some(current_batch_handle) = current_batch_handle {
|
||||||
|
commands.spawn_bundle((SpriteBatch {
|
||||||
|
range: colored_start..colored_end,
|
||||||
|
handle: current_batch_handle,
|
||||||
|
colored: true,
|
||||||
|
z: last_z,
|
||||||
|
},));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for index in quad_indices.iter() {
|
|
||||||
sprite_meta
|
sprite_meta
|
||||||
.indices
|
.vertices
|
||||||
.push((i * quad_vertex_positions.len()) as u32 + *index);
|
.write_buffer(&render_device, &render_queue);
|
||||||
}
|
sprite_meta
|
||||||
}
|
.colored_vertices
|
||||||
|
.write_buffer(&render_device, &render_queue);
|
||||||
sprite_meta.vertices.write_buffer(&render_queue);
|
|
||||||
sprite_meta.indices.write_buffer(&render_queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -326,9 +442,11 @@ pub fn queue_sprites(
|
|||||||
mut sprite_meta: ResMut<SpriteMeta>,
|
mut sprite_meta: ResMut<SpriteMeta>,
|
||||||
view_uniforms: Res<ViewUniforms>,
|
view_uniforms: Res<ViewUniforms>,
|
||||||
sprite_pipeline: Res<SpritePipeline>,
|
sprite_pipeline: Res<SpritePipeline>,
|
||||||
|
mut pipelines: ResMut<SpecializedPipelines<SpritePipeline>>,
|
||||||
|
mut pipeline_cache: ResMut<RenderPipelineCache>,
|
||||||
mut image_bind_groups: ResMut<ImageBindGroups>,
|
mut image_bind_groups: ResMut<ImageBindGroups>,
|
||||||
gpu_images: Res<RenderAssets<Image>>,
|
gpu_images: Res<RenderAssets<Image>>,
|
||||||
mut extracted_sprites: Query<(Entity, &ExtractedSprite)>,
|
mut sprite_batches: Query<(Entity, &SpriteBatch)>,
|
||||||
mut views: Query<&mut RenderPhase<Transparent2d>>,
|
mut views: Query<&mut RenderPhase<Transparent2d>>,
|
||||||
) {
|
) {
|
||||||
if let Some(view_binding) = view_uniforms.uniforms.binding() {
|
if let Some(view_binding) = view_uniforms.uniforms.binding() {
|
||||||
@ -341,13 +459,23 @@ pub fn queue_sprites(
|
|||||||
layout: &sprite_pipeline.view_layout,
|
layout: &sprite_pipeline.view_layout,
|
||||||
}));
|
}));
|
||||||
let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().unwrap();
|
let draw_sprite_function = draw_functions.read().get_id::<DrawSprite>().unwrap();
|
||||||
|
let pipeline = pipelines.specialize(
|
||||||
|
&mut pipeline_cache,
|
||||||
|
&sprite_pipeline,
|
||||||
|
SpritePipelineKey { colored: false },
|
||||||
|
);
|
||||||
|
let colored_pipeline = pipelines.specialize(
|
||||||
|
&mut pipeline_cache,
|
||||||
|
&sprite_pipeline,
|
||||||
|
SpritePipelineKey { colored: true },
|
||||||
|
);
|
||||||
for mut transparent_phase in views.iter_mut() {
|
for mut transparent_phase in views.iter_mut() {
|
||||||
for (entity, sprite) in extracted_sprites.iter_mut() {
|
for (entity, batch) in sprite_batches.iter_mut() {
|
||||||
image_bind_groups
|
image_bind_groups
|
||||||
.values
|
.values
|
||||||
.entry(sprite.handle.clone_weak())
|
.entry(batch.handle.clone_weak())
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
let gpu_image = gpu_images.get(&sprite.handle).unwrap();
|
let gpu_image = gpu_images.get(&batch.handle).unwrap();
|
||||||
render_device.create_bind_group(&BindGroupDescriptor {
|
render_device.create_bind_group(&BindGroupDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
BindGroupEntry {
|
BindGroupEntry {
|
||||||
@ -365,9 +493,13 @@ pub fn queue_sprites(
|
|||||||
});
|
});
|
||||||
transparent_phase.add(Transparent2d {
|
transparent_phase.add(Transparent2d {
|
||||||
draw_function: draw_sprite_function,
|
draw_function: draw_sprite_function,
|
||||||
pipeline: sprite_pipeline.pipeline,
|
pipeline: if batch.colored {
|
||||||
|
colored_pipeline
|
||||||
|
} else {
|
||||||
|
pipeline
|
||||||
|
},
|
||||||
entity,
|
entity,
|
||||||
sort_key: sprite.handle.clone_weak(),
|
sort_key: FloatOrd(batch.z),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -380,7 +512,7 @@ pub struct DrawSprite {
|
|||||||
SRes<ImageBindGroups>,
|
SRes<ImageBindGroups>,
|
||||||
SRes<RenderPipelineCache>,
|
SRes<RenderPipelineCache>,
|
||||||
SQuery<Read<ViewUniformOffset>>,
|
SQuery<Read<ViewUniformOffset>>,
|
||||||
SQuery<Read<ExtractedSprite>>,
|
SQuery<Read<SpriteBatch>>,
|
||||||
)>,
|
)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,20 +532,18 @@ impl Draw<Transparent2d> for DrawSprite {
|
|||||||
view: Entity,
|
view: Entity,
|
||||||
item: &Transparent2d,
|
item: &Transparent2d,
|
||||||
) {
|
) {
|
||||||
const INDICES: usize = 6;
|
|
||||||
let (sprite_meta, image_bind_groups, pipelines, views, sprites) = self.params.get(world);
|
let (sprite_meta, image_bind_groups, pipelines, views, sprites) = self.params.get(world);
|
||||||
let view_uniform = views.get(view).unwrap();
|
let view_uniform = views.get(view).unwrap();
|
||||||
let sprite_meta = sprite_meta.into_inner();
|
let sprite_meta = sprite_meta.into_inner();
|
||||||
let image_bind_groups = image_bind_groups.into_inner();
|
let image_bind_groups = image_bind_groups.into_inner();
|
||||||
let extracted_sprite = sprites.get(item.entity).unwrap();
|
let sprite_batch = sprites.get(item.entity).unwrap();
|
||||||
if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) {
|
if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) {
|
||||||
pass.set_render_pipeline(pipeline);
|
pass.set_render_pipeline(pipeline);
|
||||||
|
if sprite_batch.colored {
|
||||||
|
pass.set_vertex_buffer(0, sprite_meta.colored_vertices.buffer().unwrap().slice(..));
|
||||||
|
} else {
|
||||||
pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..));
|
pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..));
|
||||||
pass.set_index_buffer(
|
}
|
||||||
sprite_meta.indices.buffer().unwrap().slice(..),
|
|
||||||
0,
|
|
||||||
IndexFormat::Uint32,
|
|
||||||
);
|
|
||||||
pass.set_bind_group(
|
pass.set_bind_group(
|
||||||
0,
|
0,
|
||||||
sprite_meta.view_bind_group.as_ref().unwrap(),
|
sprite_meta.view_bind_group.as_ref().unwrap(),
|
||||||
@ -421,19 +551,11 @@ impl Draw<Transparent2d> for DrawSprite {
|
|||||||
);
|
);
|
||||||
pass.set_bind_group(
|
pass.set_bind_group(
|
||||||
1,
|
1,
|
||||||
image_bind_groups
|
image_bind_groups.values.get(&sprite_batch.handle).unwrap(),
|
||||||
.values
|
|
||||||
.get(&extracted_sprite.handle)
|
|
||||||
.unwrap(),
|
|
||||||
&[],
|
&[],
|
||||||
);
|
);
|
||||||
|
|
||||||
pass.draw_indexed(
|
pass.draw(sprite_batch.range.clone(), 0..1);
|
||||||
(extracted_sprite.vertex_index * INDICES) as u32
|
|
||||||
..(extracted_sprite.vertex_index * INDICES + INDICES) as u32,
|
|
||||||
0,
|
|
||||||
0..1,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,17 +8,26 @@ var<uniform> view: View;
|
|||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
[[location(0)]] uv: vec2<f32>;
|
[[location(0)]] uv: vec2<f32>;
|
||||||
|
#ifdef COLORED
|
||||||
|
[[location(1)]] color: vec4<f32>;
|
||||||
|
#endif
|
||||||
[[builtin(position)]] position: vec4<f32>;
|
[[builtin(position)]] position: vec4<f32>;
|
||||||
};
|
};
|
||||||
|
|
||||||
[[stage(vertex)]]
|
[[stage(vertex)]]
|
||||||
fn vertex(
|
fn vertex(
|
||||||
[[location(0)]] vertex_position: vec3<f32>,
|
[[location(0)]] vertex_position: vec3<f32>,
|
||||||
[[location(1)]] vertex_uv: vec2<f32>
|
[[location(1)]] vertex_uv: vec2<f32>,
|
||||||
|
#ifdef COLORED
|
||||||
|
[[location(2)]] vertex_color: u32,
|
||||||
|
#endif
|
||||||
) -> VertexOutput {
|
) -> VertexOutput {
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.uv = vertex_uv;
|
out.uv = vertex_uv;
|
||||||
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
|
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
|
||||||
|
#ifdef COLORED
|
||||||
|
out.color = vec4<f32>((vec4<u32>(vertex_color) >> vec4<u32>(0u, 8u, 16u, 24u)) & vec4<u32>(255u)) / 255.0;
|
||||||
|
#endif
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,5 +38,9 @@ var sprite_sampler: sampler;
|
|||||||
|
|
||||||
[[stage(fragment)]]
|
[[stage(fragment)]]
|
||||||
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||||
return textureSample(sprite_texture, sprite_sampler, in.uv);
|
var color = textureSample(sprite_texture, sprite_sampler, in.uv);
|
||||||
|
#ifdef COLORED
|
||||||
|
color = in.color * color;
|
||||||
|
#endif
|
||||||
|
return color;
|
||||||
}
|
}
|
@ -1,10 +1,13 @@
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_reflect::{Reflect, TypeUuid};
|
use bevy_reflect::{Reflect, TypeUuid};
|
||||||
|
use bevy_render2::color::Color;
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, TypeUuid, Reflect)]
|
#[derive(Debug, Default, Clone, TypeUuid, Reflect)]
|
||||||
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
|
#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Sprite {
|
pub struct Sprite {
|
||||||
|
/// The sprite's color tint
|
||||||
|
pub color: Color,
|
||||||
/// Flip the sprite along the X axis
|
/// Flip the sprite along the X axis
|
||||||
pub flip_x: bool,
|
pub flip_x: bool,
|
||||||
/// Flip the sprite along the Y axis
|
/// Flip the sprite along the Y axis
|
||||||
|
Loading…
Reference in New Issue
Block a user