Created
August 12, 2015 13:10
-
-
Save tomaka/06d0041f46db357e907e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¶ms).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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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