Skip to content

Instantly share code, notes, and snippets.

@tomaka
Created August 12, 2015 13:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomaka/06d0041f46db357e907e to your computer and use it in GitHub Desktop.
Save tomaka/06d0041f46db357e907e to your computer and use it in GitHub Desktop.
use glium::{self, Surface};
use glium::backend::Context;
use glium::index::PrimitiveType;
use glium::texture::SrgbTexture2dArray;
use std::cell::{RefCell, RefMut};
use std::default::Default;
use std::rc::Rc;
use std::ops::Range;
use cgmath::Matrix;
use cgmath::Matrix4;
use cgmath::Vector4;
/// Maximum number of sprites per batch.
pub const MAX_SPRITES_PER_BATCH: u16 = 1024;
/// A system that holds the program and buffers required to draw sprites.
pub struct SpritesSystem {
vertex_buffer: RefCell<glium::VertexBuffer<SpriteVertex>>,
index_buffer: glium::IndexBuffer<u16>,
program: glium::Program,
}
impl SpritesSystem {
/// Initializes the program and creates the buffer.
pub fn new(context: &Rc<Context>) -> SpritesSystem {
SpritesSystem {
vertex_buffer: RefCell::new(glium::VertexBuffer::empty_persistent(context,
MAX_SPRITES_PER_BATCH as usize * 4).unwrap()),
index_buffer: {
let data = (0 .. MAX_SPRITES_PER_BATCH).flat_map(|sprite| {
vec![sprite * 4 + 0, sprite * 4 + 1, sprite * 4 + 2,
sprite * 4 + 1, sprite * 4 + 3, sprite * 4 + 2].into_iter()
}).collect::<Vec<_>>();
glium::index::IndexBuffer::immutable(context, PrimitiveType::TrianglesList,
&data).unwrap()
},
program: program!(context,
140 => {
vertex: r"
#version 140
in vec3 position;
in vec2 tex_coords;
in uint tex_index;
out vec3 v_tex_coords;
void main() {
gl_Position = vec4(position, 1.0);
v_tex_coords = vec3(tex_coords, float(tex_index));
}
",
fragment: r"
#version 140
uniform sampler2DArray tex;
in vec3 v_tex_coords;
out vec4 color;
void main() {
color = texture(tex, v_tex_coords);
if (color.a < 0.9) {
discard;
}
}
"
}
).unwrap()
}
}
pub fn batch(&self) -> Batch {
Batch {
system: self,
vertex_buffer: self.vertex_buffer.borrow_mut(),
index: 0,
}
}
}
pub struct Batch<'a> {
system: &'a SpritesSystem,
vertex_buffer: RefMut<'a, glium::VertexBuffer<SpriteVertex>>,
index: usize,
}
impl<'a> Batch<'a> {
pub fn add(&mut self) -> Add {
Add {
vertex_buffer: self.vertex_buffer.map(),
index: &mut self.index,
}
}
pub fn draw<S>(&mut self, target: &mut S, texture: &SrgbTexture2dArray) where S: Surface {
debug_assert!(self.index % 4 == 0);
let num_sprites = self.index / 4;
let uniforms = uniform! {
tex: texture
};
let params = glium::DrawParameters {
depth_write: true,
depth_test: glium::DepthTest::IfLessOrEqual,
.. Default::default()
};
target.draw(&*self.vertex_buffer,
self.system.index_buffer.slice(0 .. num_sprites * 6).unwrap(),
&self.system.program, &uniforms, &params).unwrap();
self.index = 0;
}
}
pub struct Add<'a> {
vertex_buffer: glium::buffer::Mapping<'a, [SpriteVertex]>,
index: &'a mut usize,
}
impl<'a> Add<'a> {
pub fn add(&mut self, matrix: &Matrix4<f32>, texture_index: u32) {
let top_left = matrix.mul_v(&Vector4::new(-1.0, 1.0, 0.0, 1.0));
self.vertex_buffer[*self.index + 0].position = [top_left.x / top_left.w,
top_left.y / top_left.w,
top_left.z / top_left.w];
self.vertex_buffer[*self.index + 0].tex_coords = [0.0, 1.0];
self.vertex_buffer[*self.index + 0].tex_index = texture_index;
let top_right = matrix.mul_v(&Vector4::new(1.0, 1.0, 0.0, 1.0));
self.vertex_buffer[*self.index + 1].position = [top_right.x / top_right.w,
top_right.y / top_right.w,
top_right.z / top_right.w];
self.vertex_buffer[*self.index + 1].tex_coords = [1.0, 1.0];
self.vertex_buffer[*self.index + 1].tex_index = texture_index;
let bottom_left = matrix.mul_v(&Vector4::new(-1.0, -1.0, 0.0, 1.0));
self.vertex_buffer[*self.index + 2].position = [bottom_left.x / bottom_left.w,
bottom_left.y / bottom_left.w,
bottom_left.z / bottom_left.w];
self.vertex_buffer[*self.index + 2].tex_coords = [0.0, 0.0];
self.vertex_buffer[*self.index + 2].tex_index = texture_index;
let bottom_right = matrix.mul_v(&Vector4::new(1.0, -1.0, 0.0, 1.0));
self.vertex_buffer[*self.index + 3].position = [bottom_right.x / bottom_right.w,
bottom_right.y / bottom_right.w,
bottom_right.z / bottom_right.w];
self.vertex_buffer[*self.index + 3].tex_coords = [1.0, 0.0];
self.vertex_buffer[*self.index + 3].tex_index = texture_index;
*self.index += 4;
}
}
#[derive(Copy, Clone)]
struct SpriteVertex {
position: [f32; 3],
tex_coords: [f32; 2],
tex_index: u32,
}
implement_vertex!(SpriteVertex, position, tex_coords, tex_index);
use glium;
use glium::Surface;
use glium::backend::{Context, Facade};
use draw::sprites::{self, SpritesSystem};
use resources::{self, ResourceId};
use image;
use cgmath::Vector2;
use cgmath::Vector4;
use cgmath::Matrix;
use cgmath::Matrix4;
use spine::SpineDocument;
use std::collections::hash_map;
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use std::mem;
/// Struct that holds all resources, programs, etc. and allows you to draw things on the screen.
pub struct DrawSystem {
context: Rc<Context>,
sprites_arrays: RefCell<Vec<(glium::texture::SrgbTexture2dArray, Vec<Option<ResourceId>>)>>,
spine_docs: RefCell<HashMap<ResourceId, SpineDocument>>,
sprites_system: SpritesSystem,
}
impl DrawSystem {
/// Builds a new `DrawSystem`.
///
/// Compiles shaders.
pub fn new<F>(facade: &F) -> DrawSystem where F: Facade {
let context = facade.get_context();
DrawSystem {
context: context.clone(),
sprites_arrays: RefCell::new(Vec::new()),
spine_docs: RefCell::new(HashMap::new()),
sprites_system: SpritesSystem::new(&context),
}
}
/// Ensures that a texture is loaded.
pub fn load_texture(&self, texture: &ResourceId) {
self.get_texture(texture);
}
/// Returns the index of the texture atlas and index in the texture atlas of the texture.
fn get_texture(&self, resource_name: &ResourceId) -> (usize, usize) {
let mut arrays = self.sprites_arrays.borrow_mut();
// trying to find an existing entry
for (arr_id, &(_, ref tex_list)) in arrays.iter().enumerate() {
let num = tex_list.iter().position(|t| if let &Some(ref t) = t { t == resource_name } else { false });
if let Some(num) = num {
return (arr_id, num);
}
}
// finding a free slot
let result = arrays.iter().enumerate()
.filter_map(|(arr_id, a)| a.1.iter().position(|t| t.is_none())
.map(|p| (arr_id, p))).next();
let (arr_id, index) = if let Some((arr_id, index)) = result {
(arr_id, index)
} else {
const ARRAY_LAYERS: u32 = 256;
let new_tex = glium::texture::SrgbTexture2dArray::empty_with_mipmaps(&self.context,
glium::texture::MipmapsOption::NoMipmap, 512, 512, ARRAY_LAYERS).unwrap();
let arr_id = arrays.len();
arrays.push((new_tex, (0 .. ARRAY_LAYERS).map(|_| None).collect()));
(arr_id, 0)
};
// creating a texture that contains the sprite
let image = image::load(resources::load(resource_name), image::PNG).unwrap();
let texture = glium::texture::SrgbTexture2d::new(&self.context, image).unwrap();
// blitting to the texture array
{
let src = glium::framebuffer::SimpleFrameBuffer::new(&self.context, &texture);
let dest = glium::framebuffer::SimpleFrameBuffer::new(&self.context, arrays[arr_id].0.layer(index as u32).unwrap().main_level());
src.fill(&dest, glium::uniforms::MagnifySamplerFilter::Linear);
}
// setting the entry
arrays[arr_id].1[index] = Some(resource_name.clone());
(arr_id, index)
}
/// Ensures that a spine document is loaded.
pub fn load_spine_document(&self, document: &ResourceId) {
let mut spine_docs = self.spine_docs.borrow_mut();
spine_docs.entry(document.clone()).or_insert_with(|| {
let main_file = resources::load(document);
let document = SpineDocument::new(main_file).unwrap();
for sprite in document.get_possible_sprites() {
self.load_texture(&sprite.parse().unwrap());
}
document
});
}
pub fn batch(&self) -> Batch {
Batch {
system: self,
sprites_list: Vec::with_capacity(self.sprites_arrays.borrow().len()),
}
}
}
pub struct Batch<'a> {
system: &'a DrawSystem,
sprites_list: Vec<Vec<(Matrix4<f32>, u32)>>,
}
impl<'a> Batch<'a> {
pub fn add_sprite(&mut self, matrix: &Matrix4<f32>, resource: &ResourceId) {
let (tex_index, in_index) = self.system.get_texture(resource);
if self.sprites_list.len() <= tex_index {
for _ in (self.sprites_list.len() .. tex_index + 1) { self.sprites_list.push(vec![]); }
}
self.sprites_list[tex_index].push((matrix.clone(), in_index as u32));
}
pub fn add_spine(&mut self, matrix: &Matrix4<f32>, document: &ResourceId,
animation: Option<(&str, f32)>, skin: &str)
{
self.system.load_spine_document(document);
// skipping drawing if the doc is not visible
{
let pt = matrix.mul_v(&Vector4::new(0.0, 0.0, 0.0, 1.0));
let pt = Vector2::new(pt.x / pt.w, pt.y / pt.w);
if pt.x < -2.0 || pt.x > 2.0 || pt.y < -2.0 || pt.y > 2.0 {
return;
}
}
// getting the spine document
let spine_docs = self.system.spine_docs.borrow_mut();
let spine_doc = spine_docs.get(document).unwrap();
// calculating animation
let (animation, time) = match animation {
Some((name, timer)) if spine_doc.has_animation(name) => (Some(name), timer),
_ => (None, 0.0),
};
let results = match spine_doc.calculate(skin, animation, time) {
Ok(r) => r,
Err(e) => {
println!("Error while drawing spine: {:?}", e);
return;
}
};
// this matrix is used to move elements on the top a bit forward and avoid z fighting
let mut z_offset = Matrix4::new(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
for (sprite, inner_matrix, _color) in results.sprites.into_iter() {
self.add_sprite(&((*matrix) * z_offset * inner_matrix), &sprite.parse().unwrap());
z_offset[3][2] += 0.1;
}
}
pub fn draw<S>(&mut self, target: &mut S) where S: Surface {
let sprites_arrays = self.system.sprites_arrays.borrow();
let sprites_list = mem::replace(&mut self.sprites_list, Vec::new());
for (tex_index, list) in sprites_list.into_iter().enumerate() {
let mut batch = self.system.sprites_system.batch();
for chunk in list.chunks(sprites::MAX_SPRITES_PER_BATCH as usize) {
{
let mut add = batch.add();
for &(ref matrix, ref id) in chunk.iter() {
add.add(matrix, id.clone());
}
}
batch.draw(target, &sprites_arrays[tex_index].0);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment