use crate::{
error::GameResult,
graphics::{
self, apply_uniforms, transform_rect, BlendMode, DrawParam, FilterMode, InstanceAttributes,
Rect,
},
Context,
};
use std::cell::RefCell;
use miniquad::{Buffer, BufferType, PassAction};
#[derive(Debug)]
pub struct SpriteBatch {
image: RefCell<graphics::Image>,
sprites: Vec<DrawParam>,
gpu_sprites: RefCell<Vec<InstanceAttributes>>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SpriteIdx(usize);
impl SpriteBatch {
pub fn new(image: graphics::Image) -> Self {
Self {
image: RefCell::new(image),
sprites: vec![],
gpu_sprites: RefCell::new(vec![]),
}
}
pub fn add<P>(&mut self, param: P) -> SpriteIdx
where
P: Into<graphics::DrawParam>,
{
let param = param.into();
self.sprites.push(param);
SpriteIdx(self.sprites.len() - 1)
}
pub fn set<P>(&mut self, handle: SpriteIdx, param: P) -> GameResult
where
P: Into<graphics::DrawParam>,
{
if handle.0 < self.sprites.len() {
self.sprites[handle.0] = param.into();
Ok(())
} else {
Err(crate::error::GameError::RenderError(String::from(
"Provided index is out of bounds.",
)))
}
}
pub fn get_sprites(&self) -> &[DrawParam] {
&self.sprites
}
pub fn get_sprites_mut(&mut self) -> &mut [DrawParam] {
&mut self.sprites
}
pub fn clear(&mut self) {
self.sprites.clear();
}
pub fn into_inner(self) -> graphics::Image {
self.image.into_inner()
}
pub fn set_image(&mut self, image: graphics::Image) -> graphics::Image {
use std::mem;
self.gpu_sprites = RefCell::new(vec![]);
let mut self_image = self.image.borrow_mut();
mem::replace(&mut *self_image, image)
}
pub fn set_filter(&mut self, mode: FilterMode) {
self.image.borrow_mut().set_filter(mode);
}
}
impl graphics::Drawable for SpriteBatch {
fn draw(
&self,
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: DrawParam,
) -> GameResult {
{
let mut image = self.image.borrow_mut();
let mut gpu_sprites = self.gpu_sprites.borrow_mut();
if self.sprites.len() > gpu_sprites.len() {
gpu_sprites.resize(self.sprites.len(), InstanceAttributes::default());
let buffer = Buffer::stream(
quad_ctx,
BufferType::VertexBuffer,
std::mem::size_of::<InstanceAttributes>() * self.sprites.len(),
);
image.bindings.vertex_buffers[1].delete();
image.bindings.vertex_buffers[1] = buffer;
}
for (n, param) in self.sprites.iter().enumerate() {
let mut new_param = *param;
let src_width = param.src.w;
let src_height = param.src.h;
let scale_x = src_width * f32::from(image.width);
let scale_y = src_height * f32::from(image.height);
use crate::graphics::Transform;
new_param = match new_param.trans {
Transform::Values { scale, .. } => new_param.scale(mint::Vector2 {
x: scale.x * scale_x,
y: scale.y * scale_y,
}),
Transform::Matrix(m) => new_param.transform(
cgmath::Matrix4::from(m)
* cgmath::Matrix4::from_nonuniform_scale(scale_x, scale_y, 1.0),
),
};
let instance = InstanceAttributes::from(&new_param);
gpu_sprites[n] = instance;
}
image.bindings.vertex_buffers[1].update(quad_ctx, &gpu_sprites[0..self.sprites.len()]);
let pass = ctx.framebuffer();
quad_ctx.begin_pass(pass, PassAction::Nothing);
quad_ctx.apply_bindings(&image.bindings);
}
let shader_id = *ctx.gfx_context.current_shader.borrow();
let current_shader = &mut ctx.gfx_context.shaders[shader_id];
quad_ctx.apply_pipeline(¤t_shader.pipeline);
apply_uniforms(
ctx,
quad_ctx,
shader_id,
Some(cgmath::Matrix4::from(param.trans.to_bare_matrix())),
);
let mut custom_blend = false;
if let Some(blend_mode) = self.blend_mode() {
custom_blend = true;
crate::graphics::set_current_blend_mode(quad_ctx, blend_mode)
}
quad_ctx.draw(0, 6, self.sprites.len() as i32);
if custom_blend {
crate::graphics::restore_blend_mode(ctx, quad_ctx);
}
quad_ctx.end_render_pass();
Ok(())
}
fn dimensions(&self, _ctx: &mut Context) -> Option<Rect> {
if self.sprites.is_empty() {
return None;
}
let dimensions = self.image.borrow().dimensions();
self.sprites
.iter()
.map(|¶m| transform_rect(dimensions, param))
.fold(None, |acc: Option<Rect>, rect| {
Some(if let Some(acc) = acc {
acc.combine_with(rect)
} else {
rect
})
})
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.image.get_mut().set_blend_mode(mode);
}
fn blend_mode(&self) -> Option<BlendMode> {
self.image.borrow().blend_mode()
}
}