//! A [`SpriteBatch`](struct.SpriteBatch.html) is a way to //! efficiently draw a large number of copies of the same image, or part //! of the same image. It's useful for implementing tiled maps, //! spritesheets, particles, and other such things. //! //! Essentially this uses a technique called "instancing" to queue up //! a large amount of location/position data in a buffer, then feed it //! to the graphics card all in one go. //! //! Also it's super slow in `rustc`'s default debug mode, because //! `rustc` adds a lot of checking to the vector accesses and math. //! If you use it, it's recommended to crank up the `opt-level` for //! debug mode in your game's `Cargo.toml`. use crate::ggez::context::Context; use crate::ggez::error; use crate::ggez::error::GameResult; use crate::ggez::graphics::shader::BlendMode; use crate::ggez::graphics::types::FilterMode; use crate::ggez::graphics::{self, transform_rect, BackendSpec, DrawParam, DrawTransform, Rect}; use gfx; use gfx::Factory; /// A `SpriteBatch` draws a number of copies of the same image, using a single draw call. /// /// This is generally faster than drawing the same sprite with many /// invocations of [`draw()`](../fn.draw.html), though it has a bit of /// overhead to set up the batch. This overhead makes it run very /// slowly in `debug` mode because it spends a lot of time on array /// bounds checking and un-optimized math; you need to build with /// optimizations enabled to really get the speed boost. #[derive(Debug, Clone, PartialEq)] pub struct SpriteBatch { image: graphics::Image, sprites: Vec, blend_mode: Option, } /// An index of a particular sprite in a `SpriteBatch`. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SpriteIdx(usize); impl SpriteBatch { /// Creates a new `SpriteBatch`, drawing with the given image. /// /// Takes ownership of the `Image`, but cloning an `Image` is /// cheap since they have an internal `Arc` containing the actual /// image data. pub fn new(image: graphics::Image) -> Self { Self { image, sprites: vec![], blend_mode: None, } } /// Adds a new sprite to the sprite batch. /// /// Returns a handle with which to modify the sprite using /// [`set()`](#method.set) pub fn add

(&mut self, param: P) -> SpriteIdx where P: Into, { self.sprites.push(param.into()); SpriteIdx(self.sprites.len() - 1) } /// Alters a sprite in the batch to use the given draw params pub fn set

(&mut self, handle: SpriteIdx, param: P) -> GameResult where P: Into, { if handle.0 < self.sprites.len() { self.sprites[handle.0] = param.into(); Ok(()) } else { Err(error::GameError::RenderError(String::from( "Provided index is out of bounds.", ))) } } /// Immediately sends all data in the batch to the graphics card. /// /// Generally just calling [`graphics::draw()`](../fn.draw.html) on the `SpriteBatch` /// will do this automatically. fn flush(&self, ctx: &mut Context, image: &graphics::Image) -> GameResult { // This is a little awkward but this is the right place // to do whatever transformations need to happen to DrawParam's. // We have a Context, and *everything* must pass through this // function to be drawn, so. // Though we do awkwardly have to allocate a new vector. // ...though upon benchmarking, the actual allocation is basically nothing, // the cost in debug mode is alllll math. let new_sprites = self .sprites .iter() .map(|param| { // Copy old params let mut new_param = *param; let src_width = param.src.w; let src_height = param.src.h; let real_scale = graphics::Vector2::new( src_width * param.scale.x * f32::from(image.width), src_height * param.scale.y * f32::from(image.height), ); new_param.scale = real_scale.into(); new_param.color = new_param.color; let primitive_param = graphics::DrawTransform::from(new_param); primitive_param.to_instance_properties(ctx.gfx_context.is_srgb()) }) .collect::>(); let gfx = &mut ctx.gfx_context; if gfx.data.rect_instance_properties.len() < self.sprites.len() { gfx.data.rect_instance_properties = gfx.factory.create_buffer( self.sprites.len(), gfx::buffer::Role::Vertex, gfx::memory::Usage::Dynamic, gfx::memory::Bind::TRANSFER_DST, )?; } gfx.encoder .update_buffer(&gfx.data.rect_instance_properties, &new_sprites[..], 0)?; Ok(()) } /// Removes all data from the sprite batch. pub fn clear(&mut self) { self.sprites.clear(); } /// Unwraps and returns the contained `Image` pub fn into_inner(self) -> graphics::Image { self.image } /// Replaces the contained `Image`, returning the old one. pub fn set_image(&mut self, image: graphics::Image) -> graphics::Image { use std::mem; mem::replace(&mut self.image, image) } /// Get the filter mode for the SpriteBatch. pub fn filter(&self) -> FilterMode { self.image.filter() } /// Set the filter mode for the SpriteBatch. pub fn set_filter(&mut self, mode: FilterMode) { self.image.set_filter(mode); } } impl graphics::Drawable for SpriteBatch { fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult { // Awkwardly we must update values on all sprites and such. // Also awkwardly we have this chain of colors with differing priorities. self.flush(ctx, &self.image)?; let gfx = &mut ctx.gfx_context; let sampler = gfx .samplers .get_or_insert(self.image.sampler_info, gfx.factory.as_mut()); gfx.data.vbuf = gfx.quad_vertex_buffer.clone(); let typed_thingy = gfx .backend_spec .raw_to_typed_shader_resource(self.image.texture.clone()); gfx.data.tex = (typed_thingy, sampler); let mut slice = gfx.quad_slice.clone(); slice.instances = Some((self.sprites.len() as u32, 0)); let curr_transform = gfx.transform(); let m: DrawTransform = param.into(); gfx.push_transform(m.matrix * curr_transform); gfx.calculate_transform_matrix(); gfx.update_globals()?; let previous_mode: Option = if let Some(mode) = self.blend_mode { let current_mode = gfx.blend_mode(); if current_mode != mode { gfx.set_blend_mode(mode)?; Some(current_mode) } else { None } } else { None }; gfx.draw(Some(&slice))?; if let Some(mode) = previous_mode { gfx.set_blend_mode(mode)?; } gfx.pop_transform(); gfx.calculate_transform_matrix(); gfx.update_globals()?; Ok(()) } fn dimensions(&self, _ctx: &mut Context) -> Option { if self.sprites.is_empty() { return None; } let dimensions = self.image.dimensions(); self.sprites .iter() .map(|¶m| transform_rect(dimensions, param)) .fold(None, |acc: Option, rect| { Some(if let Some(acc) = acc { acc.combine_with(rect) } else { rect }) }) } fn set_blend_mode(&mut self, mode: Option) { self.blend_mode = mode; } fn blend_mode(&self) -> Option { self.blend_mode } }