use std::ffi::{CStr, c_void};
use std::rc::Rc;
#[cfg(feature = "text")]
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use batch::quad::TextureQuadBatchRenderer;
use batch::rrect::RRectBatchRenderer;
use common::{TextureWrapping, pop_debug_group, push_debug_group};
use glam::{Vec2, Vec4};
use glow::HasContext;
mod batch;
mod common;
mod texas;
mod white_rect;
#[cfg(feature = "text")]
mod quadlas;
#[cfg(feature = "text")]
mod text;
use rustc_hash::FxHashMap;
#[cfg(feature = "text")]
pub use text::TextLayout;
#[cfg(feature = "text")]
use crate::batch::glyph_quad::GlyphQuadBatchRenderer;
use crate::common::{TextureInterpolation, create_shader_program, slice_as_bytes, upload_texture};
use crate::white_rect::WhiteRectRenderer;
pub type Color = u32;
pub static OPENGL_DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
pub fn is_opengl_debug_enabled() -> bool {
OPENGL_DEBUG_ENABLED.load(Ordering::Relaxed)
}
fn debug_message_callback(src: u32, ty: u32, _id: u32, severity: u32, msg: &str) {
let ty = match ty {
glow::DEBUG_TYPE_ERROR => "Error: ",
glow::DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behavior: ",
glow::DEBUG_TYPE_MARKER => "Marker: ",
glow::DEBUG_TYPE_OTHER => "",
glow::DEBUG_TYPE_POP_GROUP => "Pop Group: ",
glow::DEBUG_TYPE_PORTABILITY => "Portability: ",
glow::DEBUG_TYPE_PUSH_GROUP => "Push Group: ",
glow::DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behavior: ",
glow::DEBUG_TYPE_PERFORMANCE => "Performance: ",
ty => unreachable!("unknown debug type {ty}"),
};
match severity {
glow::DEBUG_SEVERITY_NOTIFICATION => {
if src != glow::DEBUG_SOURCE_APPLICATION {
tracing::debug!(target: "opengl", "{ty}{msg}")
}
}
glow::DEBUG_SEVERITY_LOW => tracing::info!(target: "opengl", "{ty}{msg}"),
glow::DEBUG_SEVERITY_MEDIUM => tracing::warn!(target: "opengl", "{ty}{msg}"),
glow::DEBUG_SEVERITY_HIGH => tracing::error!(target: "opengl", "{ty}{msg}"),
sevr => unreachable!("unknown debug severity {sevr}"),
};
}
unsafe fn init_opengl(gl: &mut glow::Context, debug_gl: bool) {
unsafe {
let renderer = gl.get_parameter_string(glow::RENDERER);
tracing::info!("Renderer: {}", renderer);
let version = gl.get_parameter_string(glow::VERSION);
tracing::info!("OpenGL ver: {}", version);
let shaders_version = gl.get_parameter_string(glow::SHADING_LANGUAGE_VERSION);
tracing::info!("Shaders ver: {}", shaders_version);
if debug_gl {
let extensions = gl.supported_extensions();
if extensions.contains("GL_KHR_debug") {
tracing::info!("Debug ext: supported");
gl.enable(glow::DEBUG_OUTPUT);
gl.enable(glow::DEBUG_OUTPUT_SYNCHRONOUS);
gl.debug_message_callback(debug_message_callback);
OPENGL_DEBUG_ENABLED.store(true, Ordering::Relaxed);
} else {
tracing::info!("Debug ext: unsupported");
}
}
tracing::info!("");
let max_texture_size = gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE);
tracing::info!("Max texture size: {}", max_texture_size);
let max_3d_texture_size = gl.get_parameter_i32(glow::MAX_3D_TEXTURE_SIZE);
tracing::info!("Max 3D texture size: {}", max_3d_texture_size);
let max_rectangle_texture_size = gl.get_parameter_i32(glow::MAX_RECTANGLE_TEXTURE_SIZE);
tracing::info!("Max rectangle texture size: {}", max_rectangle_texture_size);
if max_texture_size == 0 {
tracing::warn!("Max texture size is a mystery. Weird GPU detected.");
} else if max_texture_size < 2048 {
tracing::warn!(
"Max texture size is {} (less than 2048), device might be very old. Performance might be suboptimal.",
max_texture_size
);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum BatchKind {
None,
RRect,
Quad,
#[cfg(feature = "text")]
GlyphQuad,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RRect {
pub pos: Vec2,
pub size: Vec2,
pub border_radius: Vec4,
pub border_width: Vec4,
pub fill_color: u32,
pub stroke_color: u32,
pub box_blur: f32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RotatingRRect {
pub pos: Vec2,
pub size: Vec2,
pub border_radius: Vec4,
pub border_width: Vec4,
pub fill_color: u32,
pub stroke_color: u32,
pub box_blur: f32,
pub rotation: f32,
pub origin: Vec2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Quad {
pub pos: Vec2,
pub size: Vec2,
pub origin: Vec2,
pub uv_pos: Vec2,
pub uv_size: Vec2,
pub rotation: f32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct DebugStrip {
pub kind: DebugStripKind,
pub id: DebugStripId,
pub color: Color,
pub offset: Vec2,
pub scale: Vec2,
}
#[derive(Clone)]
pub enum DrawCommand {
RRect(RRect),
RotatingRRect(RotatingRRect),
TextureQuad(Quad, TextureHandle),
#[cfg(feature = "text")]
GlyphQuad(batch::glyph_quad::GlyphQuad, glow::Texture),
DebugStrip(DebugStrip),
MaskStart {
draw_mask: bool,
},
MaskContent,
MaskEnd,
}
#[derive(Clone, Copy)]
pub struct RawImage<'a> {
pub width: u32,
pub height: u32,
pub pixels: &'a [u8],
}
struct BatchRenderers {
rrect: RRectBatchRenderer,
texture_quad: TextureQuadBatchRenderer,
#[cfg(feature = "text")]
glyph_quad: batch::glyph_quad::GlyphQuadBatchRenderer,
}
impl BatchRenderers {
fn draw_batch(&mut self, batch_kind: BatchKind) -> usize {
match batch_kind {
BatchKind::None => 0,
BatchKind::RRect => {
self.rrect.upload_rrects();
let draw_calls = self.rrect.draw();
self.rrect.clear_rrects();
draw_calls
}
BatchKind::Quad => {
self.texture_quad.upload_quads();
let draw_calls = self.texture_quad.draw();
self.texture_quad.clear_quads();
draw_calls
}
#[cfg(feature = "text")]
BatchKind::GlyphQuad => {
self.glyph_quad.upload_quads();
let draw_calls = self.glyph_quad.draw();
self.glyph_quad.clear_quads();
draw_calls
}
}
}
}
#[cfg(feature = "text")]
pub struct TextContext {
font_context: parley::FontContext,
layout_context: parley::LayoutContext<()>,
scale_factor: f32,
}
#[cfg(feature = "text")]
impl TextContext {
pub fn new(scale_factor: f32, font_context: parley::FontContext) -> Self {
let layout_context = parley::LayoutContext::new();
Self {
font_context,
layout_context,
scale_factor,
}
}
pub fn register_font_ttf(&mut self, ttf: Arc<dyn AsRef<[u8]> + Send + Sync>) {
use parley::fontique::Blob;
self.font_context.collection.register_fonts(Blob::new(ttf), None);
}
pub fn build_text(
&mut self,
alignment: parley::Alignment,
color: Color,
text: &str,
f: impl FnOnce(&mut parley::RangedBuilder<'_, ()>),
) -> TextLayout {
let mut builder = (self.layout_context).ranged_builder(&mut self.font_context, text, self.scale_factor, true);
(f)(&mut builder);
TextLayout::new(
builder.build(text),
text.to_owned(),
alignment,
color,
self.scale_factor,
)
}
pub fn contexts(&self) -> (&parley::FontContext, &parley::LayoutContext<()>) {
(&self.font_context, &self.layout_context)
}
pub fn contexts_mut(&mut self) -> (&mut parley::FontContext, &mut parley::LayoutContext<()>) {
(&mut self.font_context, &mut self.layout_context)
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
}
#[cfg(feature = "text")]
struct TextRendererState {
quadlas: quadlas::Quadlas,
quadlas_texture: glow::Texture,
scale_cx: swash::scale::ScaleContext,
scale_render: swash::scale::Render<'static>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextureHandle(glow::Texture);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DebugStripId(glow::Buffer);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DebugStripKind {
Points,
Lines,
}
pub struct Renderer {
gl: Rc<glow::Context>,
viewport: Vec2,
clear_color: [f32; 4],
textures: Vec<glow::Texture>,
batch_renderers: BatchRenderers,
white_rect_renderer: WhiteRectRenderer,
debug_strip_shader: glow::Program,
debug_strip_vao: glow::VertexArray,
u_debug_strip_viewport: glow::UniformLocation,
u_debug_strip_offset: glow::UniformLocation,
u_debug_strip_scale: glow::UniformLocation,
u_debug_strip_color: glow::UniformLocation,
debug_strip_lengths: FxHashMap<DebugStripId, usize>,
#[cfg(feature = "text")]
text: TextRendererState,
command_buffer: Vec<DrawCommand>,
current_frame: u64,
draw_calls_in_prev_frame: usize,
}
impl Renderer {
pub fn new_with_str_loader<F>(viewport: Vec2, scale_factor: f32, debug_logs: bool, loader_function: F) -> Self
where
F: FnMut(&str) -> *const c_void,
{
let gl = unsafe { glow::Context::from_loader_function(loader_function) };
Self::new_inner(gl, viewport, scale_factor, debug_logs)
}
pub fn new_with_cstr_loader<F>(viewport: Vec2, scale_factor: f32, debug_logs: bool, loader_function: F) -> Self
where
F: FnMut(&CStr) -> *const c_void,
{
let gl = unsafe { glow::Context::from_loader_function_cstr(loader_function) };
Self::new_inner(gl, viewport, scale_factor, debug_logs)
}
fn new_inner(mut gl: glow::Context, viewport: Vec2, scale_factor: f32, debug_logs: bool) -> Self {
unsafe {
init_opengl(&mut gl, debug_logs);
let gl = Rc::new(gl);
push_debug_group(&gl, "Initialization");
gl.enable(glow::BLEND);
gl.blend_equation(glow::FUNC_ADD);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
gl.enable(glow::PROGRAM_POINT_SIZE);
let rrect = RRectBatchRenderer::new(Rc::clone(&gl), viewport, scale_factor);
let texture_quad = TextureQuadBatchRenderer::new(Rc::clone(&gl), viewport);
#[cfg(feature = "text")]
let glyph_quad = GlyphQuadBatchRenderer::new(Rc::clone(&gl), viewport);
let white_rect_renderer = WhiteRectRenderer::new(Rc::clone(&gl));
#[cfg(feature = "text")]
let text = {
let atlas_size = suitable_atlas_size(viewport.x as u32, viewport.y as u32);
let quadlas_texture = gl.create_texture().unwrap();
let max_texture_size = gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE).clamp(1024, 8192) as u32;
let quadlas = quadlas::Quadlas::new(max_texture_size, atlas_size);
let mut scale_render = swash::scale::Render::new(&[
swash::scale::Source::ColorOutline(0),
swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
swash::scale::Source::Outline,
]);
scale_render.format(swash::zeno::Format::Alpha);
TextRendererState {
quadlas,
quadlas_texture,
scale_cx: swash::scale::ScaleContext::new(),
scale_render,
}
};
const SRC_VERT_DEBUG_STRIP: &str = include_str!("./shaders/debug_strip.vert");
const SRC_FRAG_DEBUG_STRIP: &str = include_str!("./shaders/debug_strip.frag");
dbg!(SRC_VERT_DEBUG_STRIP);
let debug_strip_shader = create_shader_program(&gl, SRC_VERT_DEBUG_STRIP, SRC_FRAG_DEBUG_STRIP);
let u_debug_strip_viewport = gl.get_uniform_location(debug_strip_shader, "u_viewport").unwrap();
let u_debug_strip_offset = gl.get_uniform_location(debug_strip_shader, "u_offset").unwrap();
let u_debug_strip_scale = gl.get_uniform_location(debug_strip_shader, "u_scale").unwrap();
let u_debug_strip_color = gl.get_uniform_location(debug_strip_shader, "u_color").unwrap();
gl.uniform_2_f32(Some(&u_debug_strip_viewport), viewport.x, viewport.y);
let debug_strip_vao = gl.create_vertex_array().unwrap();
pop_debug_group(&gl);
let mut renderer = Self {
gl,
viewport,
clear_color: [0.0, 0.0, 0.0, 1.0],
textures: Vec::new(),
batch_renderers: BatchRenderers {
rrect,
texture_quad,
#[cfg(feature = "text")]
glyph_quad,
},
white_rect_renderer,
debug_strip_shader,
debug_strip_vao,
u_debug_strip_viewport,
u_debug_strip_offset,
u_debug_strip_scale,
u_debug_strip_color,
debug_strip_lengths: FxHashMap::default(),
#[cfg(feature = "text")]
text,
command_buffer: Vec::new(),
current_frame: 0,
draw_calls_in_prev_frame: 0,
};
#[cfg(feature = "text")]
renderer.reupload_glyph_atlas_texture();
renderer
}
}
pub fn clear_textures(&mut self) {
unsafe {
push_debug_group(&self.gl, "Clear textures");
for texture in &self.textures {
self.gl.delete_texture(*texture);
}
pop_debug_group(&self.gl);
}
self.textures.clear();
}
pub fn upload_texture(&self, image: RawImage) -> TextureHandle {
self.upload_texture_with_interp(image, TextureInterpolation::Linear)
}
pub fn upload_texture_with_interp(&self, image: RawImage, interpolation: TextureInterpolation) -> TextureHandle {
unsafe {
push_debug_group(&self.gl, "Upload texture");
let texture = self.gl.create_texture().unwrap();
common::upload_texture(
&self.gl,
texture,
image.width,
image.height,
Some(image.pixels),
TextureWrapping::ClampToBorder,
interpolation,
);
pop_debug_group(&self.gl);
TextureHandle(texture)
}
}
pub fn delete_texture(&self, texture: TextureHandle) {
unsafe {
push_debug_group(&self.gl, "Delete texture");
self.gl.delete_texture(texture.0);
pop_debug_group(&self.gl);
}
}
#[cfg(feature = "text")]
pub fn glyph_atlas_debug_view(&mut self, pos_offset: Vec2, quad_size: Vec2) {
(self.text.quadlas).debug_view(&mut self.command_buffer, pos_offset, quad_size);
}
#[cfg(feature = "text")]
pub fn glyph_atlas_debug_glyph_priorities(&mut self, pos_offset: Vec2, quad_size: Vec2) {
(self.text.quadlas).debug_glyph_priorities(&mut self.command_buffer, self.current_frame, pos_offset, quad_size);
}
#[cfg(feature = "text")]
pub fn reupload_glyph_atlas_texture(&mut self) {
let gl = &self.gl;
unsafe {
gl.delete_texture(self.text.quadlas_texture);
self.text.quadlas_texture = gl.create_texture().unwrap();
upload_texture(
gl,
self.text.quadlas_texture,
self.text.quadlas.size(),
self.text.quadlas.size(),
None,
TextureWrapping::ClampToBorder,
TextureInterpolation::Nearest,
);
}
}
#[cfg(feature = "text")]
pub fn glyph_atlas_texture(&self) -> (TextureHandle, u32) {
(TextureHandle(self.text.quadlas_texture), self.text.quadlas.size())
}
#[cfg(feature = "text")]
pub fn clear_glyph_atlas(&mut self) {
self.text.quadlas.clear_with_size(self.text.quadlas.size());
self.reupload_glyph_atlas_texture();
}
#[cfg(feature = "text")]
pub fn resize_glyph_atlas_using_window_size(&mut self, width: u32, height: u32) {
self.text.quadlas.clear_with_size(suitable_atlas_size(width, height));
self.reupload_glyph_atlas_texture();
}
pub fn create_debug_strip(&mut self, points: &[Vec2]) -> DebugStripId {
let id = unsafe {
let gl = &mut self.gl;
let debug_strip_buffer = gl.create_buffer().unwrap();
gl.bind_buffer(glow::ARRAY_BUFFER, Some(debug_strip_buffer));
gl.buffer_data_u8_slice(glow::ARRAY_BUFFER, slice_as_bytes(points), glow::DYNAMIC_DRAW);
DebugStripId(debug_strip_buffer)
};
self.debug_strip_lengths.insert(id, points.len());
id
}
pub fn update_debug_strip(&mut self, id: DebugStripId, points: &[Vec2]) {
unsafe {
let gl = &mut self.gl;
push_debug_group(gl, "debug strip upload");
gl.bind_buffer(glow::ARRAY_BUFFER, Some(id.0));
gl.buffer_sub_data_u8_slice(glow::ARRAY_BUFFER, 0, slice_as_bytes(points));
self.debug_strip_lengths.insert(id, points.len());
pop_debug_group(gl);
}
}
pub fn delete_debug_strip(&mut self, debug_strip_id: DebugStripId) {
if self.debug_strip_lengths.remove(&debug_strip_id).is_some() {
unsafe {
self.gl.delete_buffer(debug_strip_id.0);
}
}
}
pub fn push_draw_command(&mut self, cmd: DrawCommand) {
self.command_buffer.push(cmd);
}
pub fn push_draw_commands(&mut self, cmds: &[DrawCommand]) {
self.command_buffer.extend_from_slice(cmds);
}
pub fn draw_command_count(&self) -> usize {
self.command_buffer.len()
}
pub fn draw(&mut self) {
unsafe {
push_debug_group(&self.gl, "Draw");
self.gl.bind_framebuffer(glow::FRAMEBUFFER, None);
let [r, g, b, a] = self.clear_color;
self.gl.clear_color(r, g, b, a);
self.gl.clear(glow::COLOR_BUFFER_BIT);
}
self.batch_renderers.rrect.clear_rrects();
self.batch_renderers.texture_quad.clear_quads();
#[cfg(feature = "text")]
self.batch_renderers.glyph_quad.clear_quads();
let mut prev_batch_kind = BatchKind::None;
#[derive(Debug, Clone, Copy)]
enum StencilStackVal {
MaskStart(bool),
MaskContent,
}
let mut stencil_depth = 0;
let mut stencil_stack: Vec<StencilStackVal> = Vec::new();
self.draw_calls_in_prev_frame = 0;
for cmd in self.command_buffer.drain(..) {
let batch_kind = match cmd {
DrawCommand::RRect(rrect) => {
self.batch_renderers.rrect.push_rrect(rrect);
BatchKind::RRect
}
DrawCommand::RotatingRRect(rotating_rrect) => {
self.batch_renderers.rrect.push_rotating_rrect(rotating_rrect);
BatchKind::RRect
}
DrawCommand::TextureQuad(quad, texture) => {
self.batch_renderers.texture_quad.push_quad(quad, texture.0);
BatchKind::Quad
}
#[cfg(feature = "text")]
DrawCommand::GlyphQuad(glyph_quad, texture) => {
self.batch_renderers.glyph_quad.push_quad(glyph_quad, texture);
BatchKind::GlyphQuad
}
_ => BatchKind::None,
};
if batch_kind != prev_batch_kind {
self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
prev_batch_kind = batch_kind;
}
match cmd {
DrawCommand::MaskStart { draw_mask } => {
self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
prev_batch_kind = BatchKind::None;
if stencil_depth >= 255 {
panic!("Stencil mask stack overflow (too many masks stacked on top of each other)");
}
unsafe {
if stencil_depth == 0 {
self.gl.enable(glow::STENCIL_TEST);
self.gl.clear_stencil(0);
self.gl.clear(glow::STENCIL_BUFFER_BIT);
}
push_debug_group(&self.gl, "Stencil Mask");
self.gl.color_mask(draw_mask, draw_mask, draw_mask, draw_mask);
self.gl.stencil_op(glow::KEEP, glow::INCR, glow::INCR);
self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
}
stencil_depth += 1;
stencil_stack.push(StencilStackVal::MaskStart(draw_mask));
}
DrawCommand::MaskContent => {
self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
prev_batch_kind = BatchKind::None;
stencil_stack.pop();
unsafe {
pop_debug_group(&self.gl);
push_debug_group(&self.gl, "Stencil Content");
self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
self.gl.color_mask(true, true, true, true);
}
stencil_stack.push(StencilStackVal::MaskContent);
}
DrawCommand::MaskEnd => {
self.draw_calls_in_prev_frame += self.batch_renderers.draw_batch(prev_batch_kind);
prev_batch_kind = BatchKind::None;
stencil_depth -= 1;
let stencil_stack_val = stencil_stack.pop();
unsafe {
pop_debug_group(&self.gl);
push_debug_group(&self.gl, "Stencil Layer Clear");
self.gl.color_mask(false, false, false, false);
self.gl.stencil_op(glow::KEEP, glow::DECR, glow::DECR);
self.gl.stencil_func(glow::EQUAL, stencil_depth + 1, 0xff);
self.draw_calls_in_prev_frame += self.white_rect_renderer.draw();
pop_debug_group(&self.gl);
match stencil_stack_val {
Some(StencilStackVal::MaskStart(draw_mask)) => {
self.gl.color_mask(draw_mask, draw_mask, draw_mask, draw_mask);
self.gl.stencil_op(glow::KEEP, glow::INCR, glow::INCR);
self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
}
Some(StencilStackVal::MaskContent) => {
self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP);
self.gl.stencil_func(glow::EQUAL, stencil_depth, 0xff);
self.gl.color_mask(true, true, true, true);
}
None => (),
}
if stencil_depth == 0 {
self.gl.disable(glow::STENCIL_TEST);
}
}
}
DrawCommand::DebugStrip(debug_strip) => {
let length = self.debug_strip_lengths[&debug_strip.id];
let mode = match debug_strip.kind {
DebugStripKind::Points => glow::POINTS,
DebugStripKind::Lines => glow::LINE_STRIP,
};
unsafe {
let gl = &self.gl;
push_debug_group(gl, "Debug strip");
gl.bind_buffer(glow::ARRAY_BUFFER, Some(debug_strip.id.0));
gl.bind_vertex_array(Some(self.debug_strip_vao));
gl.bind_vertex_array(Some(self.debug_strip_vao));
gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, size_of::<Vec2>() as i32, 0 as _);
gl.enable_vertex_attrib_array(0);
gl.use_program(Some(self.debug_strip_shader));
gl.uniform_1_i32(
Some(&self.u_debug_strip_color),
i32::from_ne_bytes(debug_strip.color.to_ne_bytes()),
);
gl.uniform_2_f32_slice(Some(&self.u_debug_strip_offset), &debug_strip.offset.to_array());
gl.uniform_2_f32_slice(Some(&self.u_debug_strip_scale), &debug_strip.scale.to_array());
gl.draw_arrays(mode, 0, length as i32);
pop_debug_group(gl);
}
}
_ => (),
}
}
self.batch_renderers.draw_batch(prev_batch_kind);
self.current_frame += 1;
unsafe {
pop_debug_group(&self.gl);
}
}
pub fn resize(&mut self, width: i32, height: i32, scale_factor: f32) {
unsafe {
self.gl.viewport(0, 0, width, height);
self.viewport = Vec2::new(width as f32, height as f32) / scale_factor;
self.batch_renderers.rrect.resize(self.viewport);
self.batch_renderers.texture_quad.resize(self.viewport);
#[cfg(feature = "text")]
self.batch_renderers.glyph_quad.resize(self.viewport);
self.gl.use_program(Some(self.debug_strip_shader));
(self.gl).uniform_2_f32(Some(&self.u_debug_strip_viewport), self.viewport.x, self.viewport.y);
}
}
pub fn draw_calls_in_prev_frame(&self) -> usize {
self.draw_calls_in_prev_frame
}
pub fn set_clear_color(&mut self, r: f32, g: f32, b: f32, a: f32) {
self.clear_color = [r, g, b, a];
}
}
impl Drop for Renderer {
fn drop(&mut self) {
self.clear_textures();
}
}
fn suitable_atlas_size(width: u32, height: u32) -> u32 {
2_u32.pow((width.max(height) as f32).log2().ceil() as u32) / 8
}