Skip to main content

niko/graphics/
sprite_batch.rs

1use crate::{
2    Error,
3    Rectangle,
4    Context,
5    Image,
6    Color,
7    graphics::{
8        Buffer,
9        BufferType,
10        BufferUsage,
11    },
12};
13use glow::HasContext;
14
15struct DynamicBuffer {
16    vertex_data: Vec<f32>,
17    vertices: u16,
18    indices: Vec<u16>,
19}
20
21impl DynamicBuffer {
22    pub fn new() -> Self {
23        Self {
24            vertex_data: Vec::new(),
25            vertices: 0,
26            indices: Vec::new(),
27        }
28    }
29
30    pub fn push_vertex(&mut self, x: f32, y: f32, u: f32, v: f32) -> u16 {
31        let index = self.vertices;
32        self.vertices += 1;
33
34        self.vertex_data.push(x);
35        self.vertex_data.push(y);
36        self.vertex_data.push(u);
37        self.vertex_data.push(v);
38
39        index
40    }
41
42    pub fn push_quad(&mut self, a: u16, b: u16, c: u16, d: u16) {
43        self.indices.push(a);
44        self.indices.push(b);
45        self.indices.push(c);
46        
47        self.indices.push(a);
48        self.indices.push(c);
49        self.indices.push(d);
50    }
51
52    pub fn build(self, gl: &glow::Context) -> Result<(i32, Buffer, Buffer), Error> {
53        use std::mem::size_of;
54        use std::slice::from_raw_parts;
55
56        let vertex_buffer = unsafe {
57            let byte_len = self.vertex_data.len() * size_of::<f32>();
58            let byte_data = from_raw_parts(self.vertex_data.as_ptr() as *const u8, byte_len);
59            Buffer::create(gl, BufferType::VertexBuffer, BufferUsage::DynamicDraw, &byte_data)?
60        };
61
62        let index_buffer = unsafe {
63            let byte_len = self.indices.len() * size_of::<u16>();
64            let byte_data = from_raw_parts(self.indices.as_ptr() as *const u8, byte_len);
65            Buffer::create(gl, BufferType::IndexBuffer, BufferUsage::DynamicDraw, &byte_data)?
66        };
67
68        let count = self.indices.len() as i32;
69
70        Ok((count, vertex_buffer, index_buffer))
71    }
72}
73
74struct SpriteInstance {
75    sprite: Image,
76    source: Rectangle,
77    target: Rectangle,
78    color: Color,
79}
80
81pub struct SpriteBatch {
82    instances: Vec<SpriteInstance>,
83    brute_force: bool,
84}
85
86impl SpriteBatch {
87    pub fn new() -> Self {
88        Self {
89            instances: Vec::new(),
90            brute_force: false,
91        }
92    }
93
94    pub fn draw_sprite(&mut self, sprite: Image, source: Rectangle, target: Rectangle, color: Color) {
95        // TODO expose color
96        self.instances.push(SpriteInstance {
97            sprite,
98            source,
99            target,
100            color,
101        });
102    }
103
104    pub fn draw(self, context: &mut Context) -> Result<(), Error> {
105        if self.instances.len() < 1 {
106            return Ok(());
107        }
108
109        let gl = &context.gl;
110        let shader = &context.sprite_shader;
111
112        let canvas_size = Rectangle::new(0, 0, 1280, 720);
113
114        let mut dynamic_buffer = DynamicBuffer::new();
115        for instance in &self.instances {
116
117            // TODO propper ignore unloaded sprites
118            let image_size = if let Some((width, height)) = context.images.find_size(instance.sprite) {
119                Rectangle::new(0, 0, width as i32, height as i32)
120            } else {
121                Rectangle::new(0, 0, 1, 1)
122            };
123
124            let (source_left, source_right, source_top, source_bottom) = instance.source.to_rendering_position(&image_size);
125            let (target_left, target_right, target_top, target_bottom) = instance.target.to_rendering_position(&canvas_size);
126
127            let a = dynamic_buffer.push_vertex((target_left - 0.5) * 2.0, (target_bottom - 0.5) * 2.0, source_left, source_top);
128            let b = dynamic_buffer.push_vertex((target_right - 0.5) * 2.0, (target_bottom - 0.5) * 2.0, source_right, source_top);
129            let c = dynamic_buffer.push_vertex((target_right - 0.5) * 2.0,  (target_top - 0.5) * 2.0, source_right, source_bottom);
130            let d = dynamic_buffer.push_vertex((target_left - 0.5) * 2.0,  (target_top - 0.5) * 2.0, source_left, source_bottom);
131
132            dynamic_buffer.push_quad(a, b, c, d);
133
134        }
135        let (_count, vertex_buffer, index_buffer) = dynamic_buffer.build(gl)?;
136        
137        unsafe {
138            gl.enable(glow::BLEND);
139            gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
140
141            gl.use_program(Some(shader.get_inner()));
142            
143            let position_attribute = shader.get_attribute_location("position")
144                .ok_or(super::ShaderError::AttributeNotFound("position".to_string()))?;
145            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer.get_inner()));
146            gl.vertex_attrib_pointer_f32(position_attribute, 4, glow::FLOAT, false, 0, 0);
147            gl.enable_vertex_attrib_array(position_attribute);
148            
149            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buffer.get_inner()));
150            
151            let color_location = shader.get_uniform_location("color")
152                .ok_or(super::ShaderError::UniformNotFound("color".to_string()))?;
153            let sprite_location = shader.get_uniform_location("sprite")
154                .ok_or(super::ShaderError::UniformNotFound("sprite".to_string()))?;
155
156            let mut draw_calls = 0;
157            let mut skipped = 0;
158
159            if self.brute_force {
160                // draw brute-force
161                let mut offset = 0;
162                for instance in &self.instances {
163                    if let Some(sprite) = context.images.find_texture(instance.sprite) {
164                        gl.active_texture(glow::TEXTURE0);
165                        gl.bind_texture(glow::TEXTURE_2D, Some(sprite));
166                        gl.uniform_1_i32(Some(sprite_location), 0);
167                        let (r, g, b, a) = instance.color.into_normalized();
168                        gl.uniform_4_f32(Some(color_location), r, g, b, a);
169                        gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_SHORT, offset);
170                        offset += 6 * std::mem::size_of::<u16>() as i32;
171
172                        draw_calls += 1;
173                    } else {
174                        skipped += 1;
175                    }
176                }
177            } else {
178                // draw batched
179
180                // TODO ignore sprites outside of view rectangle
181
182                // begin first batch
183                let mut batch_offset = 0;
184                let mut current_offset = 0;
185                let mut draw_count = 0;
186                let mut last_color = self.instances[0].color;
187                let mut last_sprite = self.instances[0].sprite;
188
189                for instance in &self.instances {
190                    // check if we have to finish current batch and start next batch
191                    if last_color != instance.color || last_sprite != instance.sprite {
192                        // draw current batch
193                        if let Some(sprite) = context.images.find_texture(last_sprite) {
194                            gl.active_texture(glow::TEXTURE0);
195                            gl.bind_texture(glow::TEXTURE_2D, Some(sprite));
196                            gl.uniform_1_i32(Some(sprite_location), 0);
197                            let (r, g, b, a) = last_color.into_normalized();
198                            gl.uniform_4_f32(Some(color_location), r, g, b, a);
199                            gl.draw_elements(glow::TRIANGLES, draw_count, glow::UNSIGNED_SHORT, batch_offset);
200
201                            draw_calls += 1;
202                        } else {
203                            skipped += 1;
204                        }
205    
206                        //begin new batch
207                        draw_count = 0;
208                        batch_offset = current_offset;
209                        last_color = instance.color;
210                        last_sprite = instance.sprite;
211                    }
212
213                    // continue batching
214                    current_offset += 6 * std::mem::size_of::<u16>() as i32;
215                    draw_count += 6;
216                }
217    
218                // draw last batch
219                if let Some(sprite) = context.images.find_texture(last_sprite) {
220                    gl.active_texture(glow::TEXTURE0);
221                    gl.bind_texture(glow::TEXTURE_2D, Some(sprite));
222                    gl.uniform_1_i32(Some(sprite_location), 0);
223                    let (r, g, b, a) = last_color.into_normalized();
224                    gl.uniform_4_f32(Some(color_location), r, g, b, a);
225                    gl.draw_elements(glow::TRIANGLES, draw_count, glow::UNSIGNED_SHORT, batch_offset);
226                    draw_calls += 1;
227                } else {
228                    skipped += 1;
229                }
230            }
231
232            gl.delete_buffer(vertex_buffer.get_inner());
233            gl.delete_buffer(index_buffer.get_inner());
234
235            context.metrics.add_draw_calls(draw_calls);
236            context.metrics.add_sprites_drawn(self.instances.len() - skipped);
237            context.metrics.add_sprites_skipped(skipped);
238        }
239
240        Ok(())
241    }
242}