/* LICENSE BEGIN This file is part of the SixtyFPS Project -- https://sixtyfps.io Copyright (c) 2020 Olivier Goffart Copyright (c) 2020 Simon Hausmann SPDX-License-Identifier: GPL-3.0-only This file is also available under commercial licensing terms. Please contact info@sixtyfps.io for more information. LICENSE END */ use super::{GLContext, Vertex}; use glow::HasContext; use pathfinder_geometry::{rect::RectI, vector::Vector2I}; use std::{cell::RefCell, rc::Rc}; pub struct GLTexture { texture_id: ::Texture, context: Rc, width: i32, height: i32, } impl PartialEq for GLTexture { fn eq(&self, other: &Self) -> bool { self.texture_id == other.texture_id && Rc::ptr_eq(&self.context, &other.context) } } impl GLTexture { fn new_with_size_and_data( gl: &Rc, width: i32, height: i32, data: Option<&[u8]>, ) -> Self { let texture_id = unsafe { gl.create_texture().unwrap() }; unsafe { gl.bind_texture(glow::TEXTURE_2D, Some(texture_id)); 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.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); gl.tex_image_2d( glow::TEXTURE_2D, 0, glow::RGBA as i32, width, height, 0, glow::RGBA, glow::UNSIGNED_BYTE, data, ) } Self { texture_id, context: gl.clone(), width, height } } #[cfg(target_arch = "wasm32")] pub fn new_from_canvas(gl: &Rc, canvas: &web_sys::HtmlCanvasElement) -> Self { let texture_id = unsafe { gl.create_texture().unwrap() }; unsafe { gl.bind_texture(glow::TEXTURE_2D, Some(texture_id)); 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.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); gl.tex_image_2d_with_html_canvas( glow::TEXTURE_2D, 0, glow::RGBA as i32, glow::RGBA, glow::UNSIGNED_BYTE, canvas, ) } Self { texture_id, context: gl.clone(), width: canvas.width() as _, height: canvas.height() as _, } } fn set_sub_image>( &self, x: i32, y: i32, image: image::ImageBuffer, Container>, ) { unsafe { self.context.bind_texture(glow::TEXTURE_2D, Some(self.texture_id)); self.context.tex_sub_image_2d( glow::TEXTURE_2D, 0, x, y, image.width() as i32, image.height() as i32, glow::RGBA, glow::UNSIGNED_BYTE, glow::PixelUnpackData::Slice(&image.into_raw()), ); } } pub fn bind_to_location( &self, texture_location: &::UniformLocation, ) { unsafe { self.context.active_texture(glow::TEXTURE0); self.context.bind_texture(glow::TEXTURE_2D, Some(self.texture_id)); self.context.uniform_1_i32(Some(&texture_location), 0); } } } impl Drop for GLTexture { fn drop(&mut self) { unsafe { self.context.delete_texture(self.texture_id); } } } pub(crate) struct GLAtlasTexture { pub(crate) texture: Rc, allocator: RefCell, } pub struct AtlasAllocation { pub texture_coordinates: RectI, // excludes padding allocation_id: guillotiere::AllocId, pub(crate) atlas: Rc, } impl Drop for AtlasAllocation { fn drop(&mut self) { self.atlas.allocator.borrow_mut().deallocate(self.allocation_id) } } impl AtlasAllocation { pub(crate) fn normalized_texture_coordinates(&self) -> [Vertex; 6] { let atlas_width = self.atlas.texture.width as f32; let atlas_height = self.atlas.texture.height as f32; let origin = self.texture_coordinates.origin(); let size = self.texture_coordinates.size(); let texture_coordinates = RectI::new(origin, size); let tex_left = (texture_coordinates.min_x() as f32) / atlas_width; let tex_top = (texture_coordinates.min_y() as f32) / atlas_height; let tex_right = (texture_coordinates.max_x() as f32) / atlas_width; let tex_bottom = (texture_coordinates.max_y() as f32) / atlas_height; let tex_vertex1 = Vertex { _pos: [tex_left, tex_top] }; let tex_vertex2 = Vertex { _pos: [tex_right, tex_top] }; let tex_vertex3 = Vertex { _pos: [tex_right, tex_bottom] }; let tex_vertex4 = Vertex { _pos: [tex_left, tex_bottom] }; [tex_vertex1, tex_vertex2, tex_vertex3, tex_vertex1, tex_vertex3, tex_vertex4] } } impl GLAtlasTexture { fn new(gl: &Rc) -> Self { let allocator = guillotiere::AtlasAllocator::new(guillotiere::Size::new(2048, 2048)); let texture = Rc::new(GLTexture::new_with_size_and_data( gl, allocator.size().width, allocator.size().height, None, )); Self { texture, allocator: RefCell::new(allocator) } } fn allocate( self: Rc, requested_width: i32, requested_height: i32, ) -> Option { self.allocator .borrow_mut() .allocate(guillotiere::Size::new(requested_width, requested_height)) .map(|guillotiere_alloc| { let min = guillotiere_alloc.rectangle.min; let size = guillotiere_alloc.rectangle.max - guillotiere_alloc.rectangle.min; let origin = Vector2I::new(min.x, min.y); let size = Vector2I::new(size.x, size.y); let texture_coordinates = RectI::new(origin, size); AtlasAllocation { texture_coordinates, allocation_id: guillotiere_alloc.id, atlas: self.clone(), } }) } } pub struct TextureAtlas { atlases: Vec>, } impl TextureAtlas { pub fn new() -> Self { Self { atlases: vec![] } } fn allocate_region( &mut self, gl: &Rc, requested_width: i32, requested_height: i32, ) -> AtlasAllocation { self.atlases .iter() .find_map(|atlas| atlas.clone().allocate(requested_width, requested_height)) .unwrap_or_else(|| { let new_atlas = Rc::new(GLAtlasTexture::new(&gl)); let atlas_allocation = new_atlas.clone().allocate(requested_width, requested_height).unwrap(); self.atlases.push(new_atlas); atlas_allocation }) } pub fn allocate_image_in_atlas( &mut self, gl: &Rc, image: image::ImageBuffer, &[u8]>, ) -> AtlasAllocation { use image::GenericImage; use image::GenericImageView; // To avoid pixels leaking from adjacent textures in the atlas when scaling, add a one-pixel // padding. let requested_width = image.width() + 2; let requested_height = image.height() + 2; let mut padded_image = image::ImageBuffer::new(requested_width, requested_height); let mut blit = |target_x, target_y, source_x, source_y, source_width, source_height| { padded_image .copy_from( &image.view(source_x, source_y, source_width, source_height), target_x, target_y, ) .ok() .unwrap(); }; // First the main image itself blit(1, 1, 0, 0, image.width(), image.height()); // duplicate the top edge blit(1, 0, 0, 0, image.width(), 1); // duplicate the bottom edge blit(1, requested_height - 1, 0, image.height() - 1, image.width(), 1); // duplicate the left edge blit(0, 1, 0, 0, 1, image.height()); // duplicate the right edge blit(requested_width - 1, 1, image.width() - 1, 0, 1, image.height()); // duplicate the top-left corner pixel blit(0, 0, 0, 0, 1, 1); // duplicate the bottom-left corner pixel blit(0, requested_height - 1, 0, image.height() - 1, 1, 1); // duplicate the top-right corner pixel blit(requested_width - 1, 0, image.width() - 1, 0, 1, 1); // duplicate the bottom-right corner pixel blit( requested_width - 1, requested_height - 1, image.width() - 1, image.height() - 1, 1, 1, ); let mut allocation = self.allocate_region(gl, requested_width as _, requested_height as _); allocation.atlas.texture.set_sub_image( allocation.texture_coordinates.origin_x(), allocation.texture_coordinates.origin_y(), padded_image, ); // Remove the padding from the coordinates we use for sampling allocation.texture_coordinates = allocation.texture_coordinates.contract(Vector2I::new(1, 1)); allocation } }