use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::{ Arc, Weak };
use glow::HasContext;
use super::GlesVersion;
pub( super ) const PAGE_SIZE: u32 = 512;
const MAX_GLYPH_SIDE: u32 = 2048;
const MAX_BYTES: usize = 8 * 1024 * 1024;
const PAD: u32 = 1;
pub( super ) struct GlyphEntry
{
pub( super ) metrics: crate::font_face::Metrics,
pub( super ) tex_w: i32,
pub( super ) tex_h: i32,
pub( super ) page: usize,
pub( super ) atlas_x: u32,
pub( super ) atlas_y: u32,
}
pub( super ) type GlyphAtlasKey = ( u16, u32, usize );
struct Page
{
texture: glow::Texture,
width: u32,
height: u32,
cursor_x: u32,
cursor_y: u32,
row_height: u32,
}
impl Page
{
fn new( gl: &glow::Context, internal: i32, format: u32, width: u32, height: u32 ) -> Self
{
let texture = unsafe
{
let tex = gl.create_texture().unwrap();
gl.bind_texture( glow::TEXTURE_2D, Some( tex ) );
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 1 );
gl.tex_image_2d(
glow::TEXTURE_2D, 0, internal,
width as i32, height as i32, 0,
format, glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice( None ),
);
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32 );
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32 );
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32 );
gl.tex_parameter_i32( glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32 );
gl.bind_texture( glow::TEXTURE_2D, None );
tex
};
Self { texture, width, height, cursor_x: 0, cursor_y: 0, row_height: 0 }
}
fn bytes( &self ) -> usize
{
( self.width as usize ) * ( self.height as usize )
}
fn alloc( &mut self, w: u32, h: u32 ) -> Option<( u32, u32 )>
{
if w > self.width || h > self.height { return None; }
if self.cursor_x + w > self.width
{
self.cursor_x = 0;
self.cursor_y += self.row_height;
self.row_height = 0;
}
if self.cursor_y + h > self.height { return None; }
let pos = ( self.cursor_x, self.cursor_y );
self.cursor_x += w;
if h > self.row_height { self.row_height = h; }
Some( pos )
}
fn rewind( &mut self )
{
self.cursor_x = 0;
self.cursor_y = 0;
self.row_height = 0;
}
}
pub( super ) struct GlyphAtlas
{
internal: i32,
format: u32,
pages: Vec<Page>,
current: usize,
bytes: usize,
pub( super ) cache: HashMap<GlyphAtlasKey, GlyphEntry>,
}
impl GlyphAtlas
{
fn new( version: GlesVersion ) -> Self
{
let ( internal, format ) = match version
{
GlesVersion::V3 => ( glow::R8 as i32, glow::RED ),
GlesVersion::V2 => ( glow::LUMINANCE as i32, glow::LUMINANCE ),
};
Self { internal, format, pages: Vec::new(), current: 0, bytes: 0, cache: HashMap::new() }
}
pub( super ) fn page_texture( &self, page: usize ) -> glow::Texture
{
self.pages[ page ].texture
}
pub( super ) fn page_size( &self, page: usize ) -> ( f32, f32 )
{
let p = &self.pages[ page ];
( p.width as f32, p.height as f32 )
}
pub( super ) fn alloc( &mut self, gl: &glow::Context, w: u32, h: u32 ) -> Option<( usize, u32, u32 )>
{
let ( cw, ch ) = ( w + PAD, h + PAD );
if cw > MAX_GLYPH_SIDE || ch > MAX_GLYPH_SIDE { return None; }
if cw > PAGE_SIZE || ch > PAGE_SIZE
{
if self.bytes + ( cw as usize ) * ( ch as usize ) > MAX_BYTES { return None; }
let mut page = Page::new( gl, self.internal, self.format, cw, ch );
page.alloc( cw, ch );
self.bytes += page.bytes();
self.pages.push( page );
return Some( ( self.pages.len() - 1, 0, 0 ) );
}
loop
{
if self.current == self.pages.len()
{
let page_bytes = ( PAGE_SIZE as usize ) * ( PAGE_SIZE as usize );
if self.bytes + page_bytes > MAX_BYTES { return None; }
self.pages.push( Page::new( gl, self.internal, self.format, PAGE_SIZE, PAGE_SIZE ) );
self.bytes += page_bytes;
}
if let Some( ( x, y ) ) = self.pages[ self.current ].alloc( cw, ch )
{
return Some( ( self.current, x, y ) );
}
self.current += 1;
}
}
#[ allow( clippy::too_many_arguments ) ]
pub( super ) fn upload( &self, gl: &glow::Context, page: usize, x: u32, y: u32, w: u32, h: u32, bitmap: &[u8] )
{
unsafe
{
gl.bind_texture( glow::TEXTURE_2D, Some( self.pages[ page ].texture ) );
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 1 );
gl.tex_sub_image_2d(
glow::TEXTURE_2D, 0,
x as i32, y as i32,
w as i32, h as i32,
self.format, glow::UNSIGNED_BYTE,
glow::PixelUnpackData::Slice( Some( bitmap ) ),
);
gl.pixel_store_i32( glow::UNPACK_ALIGNMENT, 4 );
}
}
pub( super ) fn reset( &mut self, gl: &glow::Context )
{
self.cache.clear();
let mut bytes = 0;
self.pages.retain_mut( |p|
{
if p.width == PAGE_SIZE && p.height == PAGE_SIZE
{
p.rewind();
bytes += p.bytes();
return true;
}
unsafe { gl.delete_texture( p.texture ); }
false
} );
self.bytes = bytes;
self.current = 0;
}
}
thread_local!
{
static SHARED_ATLASES: RefCell<Vec<( Weak<glow::Context>, Rc<RefCell<GlyphAtlas>> )>> =
const { RefCell::new( Vec::new() ) };
}
pub( super ) fn shared_glyph_atlas( gl: &Arc<glow::Context>, version: GlesVersion ) -> Rc<RefCell<GlyphAtlas>>
{
SHARED_ATLASES.with( |atlases|
{
let mut atlases = atlases.borrow_mut();
atlases.retain( |( ctx, _ )| ctx.strong_count() > 0 );
if let Some( ( _, atlas ) ) = atlases.iter().find( |( ctx, _ )| std::ptr::eq( ctx.as_ptr(), Arc::as_ptr( gl ) ) )
{
return Rc::clone( atlas );
}
let atlas = Rc::new( RefCell::new( GlyphAtlas::new( version ) ) );
atlases.push( ( Arc::downgrade( gl ), Rc::clone( &atlas ) ) );
atlas
} )
}