use std::{mem::offset_of, rc::Rc};
use glam::{Vec2, Vec4, u16vec2, vec2};
use glow::HasContext;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
TextureHandle,
common::{create_shader_program, pop_debug_group, push_debug_group, slice_as_bytes},
};
const SRC_VERT_HYPERQUAD: &str = include_str!("./shaders/hyperquad.vert");
const SRC_FRAG_HYPERQUAD: &str = include_str!("./shaders/hyperquad.frag");
pub struct HyperQuadBatchRenderer {
gl: Rc<glow::Context>,
shader: glow::Program,
u_viewport: glow::UniformLocation,
vao: glow::VertexArray,
vbo: glow::Buffer,
ebo: glow::Buffer,
quads: Vec<HyperQuad>,
vertices: Vec<[HyperQuadVertex; 4]>,
indices: Vec<[u32; 6]>,
textures_to_batch: FxHashSet<TextureHandle>,
textures_to_batch_index_map: FxHashMap<TextureHandle, u16>,
prev_vertices_count: usize,
gl_idx_buffer_count: i32,
gl_idx_buffer_capacity: i32,
}
impl HyperQuadBatchRenderer {
pub fn new(gl: Rc<glow::Context>, viewport: Vec2, scale_factor: f32) -> Self {
unsafe {
push_debug_group(&gl, "HyperQuadBatchRenderer: init");
let shader = create_shader_program(&gl, SRC_VERT_HYPERQUAD, SRC_FRAG_HYPERQUAD);
let u_viewport = gl.get_uniform_location(shader, "u_viewport").unwrap();
gl.uniform_2_f32(Some(&u_viewport), viewport.x, viewport.y);
let u_smooth_delta = gl.get_uniform_location(shader, "u_smooth_delta").unwrap();
gl.uniform_1_f32(Some(&u_smooth_delta), 0.5 / scale_factor);
let u_images = gl.get_uniform_location(shader, "u_images").unwrap();
gl.uniform_1_i32_slice(Some(&u_images), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
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::<HyperQuadVertex>() as i32;
#[rustfmt::skip]
{
let a_pos = gl.get_attrib_location(shader, "pos" ).unwrap();
let a_uv = gl.get_attrib_location(shader, "uv" ).unwrap();
let a_size = gl.get_attrib_location(shader, "size" ).unwrap();
let a_color = gl.get_attrib_location(shader, "color" ).unwrap();
let a_control = gl.get_attrib_location(shader, "control" ).unwrap();
let a_box_blur = gl.get_attrib_location(shader, "box_blur").unwrap();
let a_border = gl.get_attrib_location(shader, "border").unwrap();
let a_radius = gl.get_attrib_location(shader, "radius").unwrap();
gl.vertex_attrib_pointer_f32(a_pos, 2, glow::FLOAT, false, size_vertex, offset_of!(HyperQuadVertex, pos) as i32);
gl.vertex_attrib_pointer_f32(a_uv, 2, glow::FLOAT, false, size_vertex, offset_of!(HyperQuadVertex, uv) as i32);
gl.vertex_attrib_pointer_f32(a_size, 2, glow::FLOAT, false, size_vertex, offset_of!(HyperQuadVertex, size) as i32);
gl.vertex_attrib_pointer_i32(a_color, 1, glow::INT, size_vertex, offset_of!(HyperQuadVertex, color) as i32);
gl.vertex_attrib_pointer_i32(a_control, 1, glow::SHORT, size_vertex, offset_of!(HyperQuadVertex, control) as i32);
gl.vertex_attrib_pointer_i32(a_box_blur, 1, glow::SHORT, size_vertex, offset_of!(HyperQuadVertex, box_blur) as i32);
gl.vertex_attrib_pointer_f32(a_border, 4, glow::FLOAT, false, size_vertex, offset_of!(HyperQuadVertex, border) as i32);
gl.vertex_attrib_pointer_f32(a_radius, 4, glow::FLOAT, false, size_vertex, offset_of!(HyperQuadVertex, radius) as i32);
gl.enable_vertex_attrib_array(a_pos);
gl.enable_vertex_attrib_array(a_uv);
gl.enable_vertex_attrib_array(a_size);
gl.enable_vertex_attrib_array(a_color);
gl.enable_vertex_attrib_array(a_control);
gl.enable_vertex_attrib_array(a_box_blur);
gl.enable_vertex_attrib_array(a_border);
gl.enable_vertex_attrib_array(a_radius);
};
pop_debug_group(&gl);
Self {
gl,
shader,
u_viewport,
vao,
vbo,
ebo,
quads: Vec::new(),
vertices: Vec::new(),
indices: Vec::new(),
textures_to_batch: FxHashSet::default(),
textures_to_batch_index_map: FxHashMap::default(),
prev_vertices_count: 0,
gl_idx_buffer_count: 0,
gl_idx_buffer_capacity: 0,
}
}
}
pub fn push_quad(&mut self, quad: HyperQuad) {
self.quads.push(quad);
}
pub fn draw(&mut self) -> usize {
let mut draw_call_count = 0;
let gl = &self.gl;
unsafe {
push_debug_group(gl, "HyperQuadBatchRenderer: draw");
let mut quad_idx = 0;
while quad_idx < self.quads.len() {
self.textures_to_batch.clear();
self.textures_to_batch_index_map.clear();
self.vertices.clear();
let quad_start = quad_idx;
while quad_idx < self.quads.len() {
if self.textures_to_batch.len() == 16 {
break;
}
let quad = self.quads[quad_idx];
if let Some(texture) = quad.texture {
self.textures_to_batch.insert(texture);
}
quad_idx += 1;
}
let quad_end = quad_idx;
let mut texture_bindings = [None; 16];
for (i, texture) in self.textures_to_batch.drain().enumerate() {
self.textures_to_batch_index_map.insert(texture, i as u16);
texture_bindings[i] = Some(texture.0);
}
for quad in &self.quads[quad_start..quad_end] {
let texture_index = (quad.texture)
.map(|texture| self.textures_to_batch_index_map[&texture])
.unwrap_or_default();
self.vertices.push(quad.vertices(texture_index));
}
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::STATIC_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));
}
self.gl_idx_buffer_count = (self.vertices.len() * 6) as i32;
self.gl_idx_buffer_capacity = self.gl_idx_buffer_capacity.max(self.gl_idx_buffer_count);
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 (i, texture_binding) in texture_bindings.into_iter().enumerate() {
gl.active_texture(glow::TEXTURE0 + i as u32);
gl.bind_texture(glow::TEXTURE_2D, texture_binding);
}
gl.draw_elements(glow::TRIANGLES, self.gl_idx_buffer_count, glow::UNSIGNED_INT, 0);
draw_call_count += 1;
}
self.quads.clear();
pop_debug_group(gl);
}
draw_call_count
}
pub fn resize(&mut self, viewport: Vec2) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "HyperQuadBatchRenderer: resize");
gl.use_program(Some(self.shader));
gl.uniform_2_f32(Some(&self.u_viewport), viewport.x, viewport.y);
pop_debug_group(gl);
}
}
}
impl Drop for HyperQuadBatchRenderer {
fn drop(&mut self) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "HyperQuadBatchRenderer: drop");
gl.delete_program(self.shader);
gl.delete_vertex_array(self.vao);
gl.delete_buffer(self.vbo);
gl.delete_buffer(self.ebo);
pop_debug_group(gl);
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct HyperQuad {
pub pos: Vec2,
pub size: Vec2,
pub uv_pos: Vec2,
pub uv_size: Vec2,
pub color: u32,
pub texture: Option<TextureHandle>,
pub has_borders: bool,
pub box_blur: f32,
pub border: Vec4,
pub radius: Vec4,
pub rotation: f32,
pub origin: Vec2,
}
impl HyperQuad {
fn vertices(self, texture_index: u16) -> [HyperQuadVertex; 4] {
let r = Vec2::from_angle(self.rotation);
let (pmin, pmax) = (Vec2::ZERO - self.origin, Vec2::ONE - self.origin);
let pos_uvs = [
(
(vec2(pmin.x, pmin.y) * self.size).rotate(r) + self.pos,
self.uv_pos + vec2(0.0, 0.0) * self.uv_size,
u16vec2(0, 0),
),
(
(vec2(pmin.x, pmax.y) * self.size).rotate(r) + self.pos,
self.uv_pos + vec2(0.0, 1.0) * self.uv_size,
u16vec2(0, 1),
),
(
(vec2(pmax.x, pmax.y) * self.size).rotate(r) + self.pos,
self.uv_pos + vec2(1.0, 1.0) * self.uv_size,
u16vec2(1, 1),
),
(
(vec2(pmax.x, pmin.y) * self.size).rotate(r) + self.pos,
self.uv_pos + vec2(1.0, 0.0) * self.uv_size,
u16vec2(1, 0),
),
];
let control = texture_index & 0b1111;
let control = control | ((self.texture.is_some() as u16) << 4);
let control = control | ((self.has_borders as u16) << 5);
let size = self.size;
let color = self.color;
let border = self.border.clamp(Vec4::ZERO, Vec4::MAX);
let radius = self.radius.clamp(Vec4::ZERO, Vec4::MAX);
let box_blur = (self.box_blur * 4.0).round().clamp(i16::MIN as f32, i16::MAX as f32) as i16;
pos_uvs.map(|(pos, uv, local_uv)| HyperQuadVertex {
pos,
uv,
size,
color,
control: control | (local_uv.x << 6) | (local_uv.y << 7),
border,
radius,
box_blur,
})
}
}
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 HyperQuadVertex {
pub pos: Vec2,
pub uv: Vec2,
pub size: Vec2,
pub color: u32,
pub control: u16,
pub box_blur: i16,
pub border: Vec4,
pub radius: Vec4,
}