use std::mem::offset_of;
use std::rc::Rc;
use glam::{Vec2, Vec4, vec2};
use glow::HasContext;
use crate::{RRect, RotatingRRect};
use crate::common::{create_shader_program, pop_debug_group, push_debug_group, slice_as_bytes};
use super::{SRC_FRAG_RRECT, SRC_VERT_RRECT};
pub struct RRectBatchRenderer {
gl: Rc<glow::Context>,
shader: glow::Program,
u_viewport: glow::UniformLocation,
vao: glow::VertexArray,
vbo: glow::Buffer,
ebo: glow::Buffer,
vertices: Vec<[RRectVertex; 4]>,
indices: Vec<[u32; 6]>,
prev_vertices_count: usize,
gl_idx_buffer_count: i32,
gl_idx_buffer_capacity: i32,
}
impl RRectBatchRenderer {
pub fn new(gl: Rc<glow::Context>, viewport: Vec2, scale_factor: f32) -> Self {
unsafe {
push_debug_group(&gl, "RRectBatchRenderer: init");
let shader = create_shader_program(&gl, SRC_VERT_RRECT, SRC_FRAG_RRECT);
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 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::<RRectVertex>() as i32;
#[rustfmt::skip]
{
let a_position = gl.get_attrib_location(shader, "position" ).unwrap();
let a_size = gl.get_attrib_location(shader, "size" ).unwrap();
let a_border_radius = gl.get_attrib_location(shader, "border_radius").unwrap();
let a_border_width = gl.get_attrib_location(shader, "border_width" ).unwrap();
let a_fill_color = gl.get_attrib_location(shader, "fill_color" ).unwrap();
let a_stroke_color = gl.get_attrib_location(shader, "stroke_color" ).unwrap();
let a_box_blur = gl.get_attrib_location(shader, "box_blur" ).unwrap();
gl.vertex_attrib_pointer_f32(a_position, 2, glow::FLOAT, false, size_vertex, offset_of!(RRectVertex, pos) as _);
gl.vertex_attrib_pointer_f32(a_size, 2, glow::FLOAT, false, size_vertex, offset_of!(RRectVertex, size) as _);
gl.vertex_attrib_pointer_f32(a_border_radius, 4, glow::FLOAT, false, size_vertex, offset_of!(RRectVertex, border_radius) as _);
gl.vertex_attrib_pointer_f32(a_border_width, 4, glow::FLOAT, false, size_vertex, offset_of!(RRectVertex, border_width) as _);
gl.vertex_attrib_pointer_i32(a_fill_color, 1, glow::INT, size_vertex, offset_of!(RRectVertex, fill_color) as _);
gl.vertex_attrib_pointer_i32(a_stroke_color, 1, glow::INT, size_vertex, offset_of!(RRectVertex, stroke_color) as _);
gl.vertex_attrib_pointer_f32(a_box_blur, 1, glow::FLOAT, false, size_vertex, offset_of!(RRectVertex, box_blur) as _);
gl.enable_vertex_attrib_array(a_position);
gl.enable_vertex_attrib_array(a_size);
gl.enable_vertex_attrib_array(a_border_radius);
gl.enable_vertex_attrib_array(a_border_width);
gl.enable_vertex_attrib_array(a_fill_color);
gl.enable_vertex_attrib_array(a_stroke_color);
gl.enable_vertex_attrib_array(a_box_blur);
};
pop_debug_group(&gl);
Self {
gl,
shader,
u_viewport,
vao,
vbo,
ebo,
vertices: Vec::new(),
indices: Vec::new(),
prev_vertices_count: 0,
gl_idx_buffer_count: 0,
gl_idx_buffer_capacity: 0,
}
}
}
pub fn clear_rrects(&mut self) {
self.vertices.clear();
}
pub fn push_rrect(&mut self, rrect: RRect) {
self.vertices.push(rrect.vertices());
}
pub fn push_rotating_rrect(&mut self, rotating_rrect: RotatingRRect) {
self.vertices.push(rotating_rrect.vertices());
}
pub fn upload_rrects(&mut self) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "RRectBatchRenderer: upload rrects");
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);
pop_debug_group(gl);
}
}
pub fn draw(&self) -> usize {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "RRectBatchRenderer: 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));
gl.draw_elements(glow::TRIANGLES, self.gl_idx_buffer_count, glow::UNSIGNED_INT, 0);
pop_debug_group(gl);
1
}
}
pub fn resize(&mut self, viewport: Vec2) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "RRectBatchRenderer: 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 RRectBatchRenderer {
fn drop(&mut self) {
let gl = &self.gl;
unsafe {
push_debug_group(gl, "RRectBatchRenderer: 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);
}
}
}
impl RotatingRRect {
fn vertices(self) -> [RRectVertex; 4] {
let Self {
pos: position,
size,
origin,
rotation,
border_radius,
border_width,
fill_color,
stroke_color,
box_blur,
} = self;
let r = Vec2::from_angle(rotation);
let (pmin, pmax) = (Vec2::ZERO - origin, Vec2::ONE - origin);
let pos_dims = [
(vec2(pmin.x, pmin.y) * size).rotate(r) + position,
(vec2(pmin.x, pmax.y) * size).rotate(r) + position,
(vec2(pmax.x, pmax.y) * size).rotate(r) + position,
(vec2(pmax.x, pmin.y) * size).rotate(r) + position,
];
pos_dims.map(|position| RRectVertex {
pos: position,
size,
fill_color: i32::from_ne_bytes(fill_color.to_le_bytes()),
stroke_color: i32::from_ne_bytes(stroke_color.to_le_bytes()),
border_radius,
border_width,
box_blur,
})
}
}
impl RRect {
fn vertices(self) -> [RRectVertex; 4] {
let Self {
pos,
size,
border_radius,
border_width,
fill_color,
stroke_color,
box_blur,
} = self;
let pos_dims = [
vec2(0.0, 0.0) * size + pos,
vec2(0.0, 1.0) * size + pos,
vec2(1.0, 1.0) * size + pos,
vec2(1.0, 0.0) * size + pos,
];
pos_dims.map(|pos| RRectVertex {
pos,
size,
border_radius,
border_width,
fill_color: i32::from_ne_bytes(fill_color.to_le_bytes()),
stroke_color: i32::from_ne_bytes(stroke_color.to_le_bytes()),
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 RRectVertex {
pub pos: Vec2,
pub size: Vec2,
pub border_radius: Vec4,
pub border_width: Vec4,
pub fill_color: i32,
pub stroke_color: i32,
pub box_blur: f32,
}