use std::ops::Range;
use std::rc::Rc;
use glam::{Vec2, vec2};
use glow::HasContext;
use crate::common::{create_shader_program, pop_debug_group, push_debug_group, slice_as_bytes};
use super::{SRC_FRAG_GLYPH_TEXTURE, SRC_VERT_GLYPH_QUAD};
pub struct GlyphQuadBatchRenderer {
gl: Rc<glow::Context>,
shader: glow::Program,
u_viewport: glow::UniformLocation,
vao: glow::VertexArray,
vbo: glow::Buffer,
ebo: glow::Buffer,
batch_slices: Vec<(glow::Texture, Range<usize>)>,
quads: Vec<GlyphQuad>,
vertices: Vec<[GlyphQuadVertex; 4]>,
indices: Vec<[u32; 6]>,
prev_vertices_count: usize,
}
impl GlyphQuadBatchRenderer {
pub fn new(gl: Rc<glow::Context>, viewport: Vec2) -> Self {
unsafe {
push_debug_group(&gl, "GlyphQuadBatchRenderer: init");
let shader = create_shader_program(&gl, SRC_VERT_GLYPH_QUAD, SRC_FRAG_GLYPH_TEXTURE);
let u_viewport = gl.get_uniform_location(shader, "u_viewport").unwrap();
gl.uniform_2_f32(Some(&u_viewport), viewport.x, viewport.y);
let vao = gl.create_vertex_array().unwrap();
gl.bind_vertex_array(Some(vao));
let vbo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(vbo));
let ebo = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(ebo));
let size_vertex = size_of::<GlyphQuadVertex>() as i32;
let size_f32 = size_of::<f32>() as i32;
#[rustfmt::skip]
{
let a_position = gl.get_attrib_location(shader, "position").unwrap();
let a_uv = gl.get_attrib_location(shader, "uv" ).unwrap();
let a_color = gl.get_attrib_location(shader, "color" ).unwrap();
gl.vertex_attrib_pointer_f32(a_position, 2, glow::FLOAT, false, size_vertex, 0);
gl.vertex_attrib_pointer_f32(a_uv, 2, glow::FLOAT, false, size_vertex, 2 * size_f32);
gl.vertex_attrib_pointer_i32(a_color, 1, glow::INT, size_vertex, 4 * size_f32);
gl.enable_vertex_attrib_array(a_position);
gl.enable_vertex_attrib_array(a_uv);
gl.enable_vertex_attrib_array(a_color);
};
pop_debug_group(&gl);
Self {
gl,
shader,
u_viewport,
vao,
vbo,
ebo,
batch_slices: Vec::new(),
quads: Vec::new(),
vertices: Vec::new(),
indices: Vec::new(),
prev_vertices_count: 0,
}
}
}
pub fn clear_quads(&mut self) {
self.quads.clear();
self.batch_slices.clear();
self.vertices.clear();
}
pub fn push_quad(&mut self, quad: GlyphQuad, texture: glow::Texture) {
if let Some((last_texture, last_batch_slice)) = self.batch_slices.last_mut() {
if texture == *last_texture {
last_batch_slice.end += 1;
} else {
let slice = self.quads.len()..(self.quads.len() + 1);
self.batch_slices.push((texture, slice));
}
} else {
let slice = 0..(self.quads.len() + 1);
self.batch_slices.push((texture, slice));
}
self.quads.push(quad);
}
pub fn upload_quads(&mut self) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "GlyphQuadBatchRenderer: upload quads");
self.vertices.clear();
for quad in &self.quads {
self.vertices.push(quad.vertices());
}
if self.vertices.len() > self.prev_vertices_count {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, slice_as_bytes(&self.vertices), glow::DYNAMIC_DRAW);
for i in self.prev_vertices_count..self.vertices.len() {
self.indices.push(quad_indices(i as u32));
}
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo));
gl.buffer_data_u8_slice(
glow::ELEMENT_ARRAY_BUFFER,
slice_as_bytes(&self.indices),
glow::DYNAMIC_DRAW,
);
self.prev_vertices_count = self.vertices.len();
} else {
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, slice_as_bytes(&self.vertices));
}
pop_debug_group(gl);
}
}
pub fn draw(&self) -> usize {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "GlyphQuadBatchRenderer: draw");
gl.bind_vertex_array(Some(self.vao));
gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vbo));
gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.ebo));
gl.use_program(Some(self.shader));
for (texture, batch_slice) in &self.batch_slices {
gl.bind_texture(glow::TEXTURE_2D, Some(*texture));
gl.draw_elements(
glow::TRIANGLES,
(batch_slice.end - batch_slice.start) as i32 * 6,
glow::UNSIGNED_INT,
(batch_slice.start * 6 * size_of::<u32>()) as i32,
);
}
pop_debug_group(gl);
self.batch_slices.len()
}
}
pub fn resize(&mut self, viewport: Vec2) {
unsafe {
push_debug_group(&self.gl, "GlyphQuadBatchRenderer: resize");
self.gl.use_program(Some(self.shader));
self.gl.uniform_2_f32(Some(&self.u_viewport), viewport.x, viewport.y);
pop_debug_group(&self.gl);
}
}
}
impl Drop for GlyphQuadBatchRenderer {
fn drop(&mut self) {
unsafe {
push_debug_group(&self.gl, "GlyphQuadBatchRenderer: drop");
self.gl.delete_program(self.shader);
self.gl.delete_vertex_array(self.vao);
self.gl.delete_buffer(self.vbo);
self.gl.delete_buffer(self.ebo);
pop_debug_group(&self.gl);
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct GlyphQuad {
pub pos: Vec2,
pub size: Vec2,
pub uv_pos: Vec2,
pub uv_size: Vec2,
pub color: u32,
}
impl GlyphQuad {
fn vertices(self) -> [GlyphQuadVertex; 4] {
[vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0), vec2(1.0, 0.0)].map(|uv| GlyphQuadVertex {
pos: uv * self.size + self.pos,
uv: self.uv_pos + uv * self.uv_size,
color: self.color,
})
}
}
fn quad_indices(index: u32) -> [u32; 6] {
let i = index * 4;
[i, 1 + i, 2 + i, i, 2 + i, 3 + i]
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
struct GlyphQuadVertex {
pub pos: Vec2,
pub uv: Vec2,
pub color: u32,
}