| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2025 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- use crate::gfx::{DebugTag, ManagedTexturePtr, Rectangle, RenderApi};
- use super::{
- ft::{Sprite, SpritePtr},
- Glyph,
- };
- /// Prevents render artifacts from aliasing.
- /// Even with aliasing turned off, some bleed still appears possibly
- /// due to UV coord calcs. Adding a gap perfectly fixes this.
- const ATLAS_GAP: usize = 2;
- /// Convenience wrapper fn. Use if rendering a single line of glyphs.
- pub fn make_texture_atlas(
- render_api: &RenderApi,
- tag: DebugTag,
- glyphs: &Vec<Glyph>,
- ) -> RenderedAtlas {
- let mut atlas = Atlas::new(render_api, tag);
- atlas.push(&glyphs);
- atlas.make()
- }
- /// Responsible for aggregating glyphs, and then producing a single software
- /// blitted texture usable in a single draw call.
- /// This makes OpenGL batch precomputation of meshes efficient.
- ///
- /// ```rust
- /// let mut atlas = Atlas::new(&render_api);
- /// atlas.push(&glyphs); // repeat as needed for shaped lines
- /// let atlas = atlas.make().unwrap();
- /// let uv = atlas.fetch_uv(glyph_id).unwrap();
- /// let atlas_texture_id = atlas.texture_id;
- /// ```
- #[derive(Clone)]
- pub struct Atlas<'a> {
- glyph_ids: Vec<u32>,
- sprites: Vec<SpritePtr>,
- // LHS x pos of glyph
- x_pos: Vec<usize>,
- width: usize,
- height: usize,
- render_api: &'a RenderApi,
- tag: DebugTag,
- }
- impl<'a> Atlas<'a> {
- pub fn new(render_api: &'a RenderApi, tag: DebugTag) -> Self {
- Self {
- glyph_ids: vec![],
- sprites: vec![],
- x_pos: vec![],
- width: ATLAS_GAP,
- // Not really important to set a value here since it will
- // get overwritten.
- // FYI glyphs have a gap on all sides (top and bottom here).
- height: 2 * ATLAS_GAP,
- render_api,
- tag,
- }
- }
- fn push_glyph(&mut self, glyph: &Glyph) {
- if self.glyph_ids.contains(&glyph.glyph_id) {
- return
- }
- self.glyph_ids.push(glyph.glyph_id);
- self.sprites.push(glyph.sprite.clone());
- let sprite = &glyph.sprite;
- self.x_pos.push(self.width);
- // Gap on the top and bottom
- let height = ATLAS_GAP + sprite.bmp_height + ATLAS_GAP;
- self.height = std::cmp::max(height, self.height);
- // Gap between glyphs and on both sides
- self.width += sprite.bmp_width + ATLAS_GAP;
- }
- /// Push a line of shaped text represented as `Vec<Glyph>`
- /// to this atlas.
- pub fn push(&mut self, glyphs: &Vec<Glyph>) {
- for glyph in glyphs {
- self.push_glyph(glyph);
- }
- }
- fn render(&self) -> Vec<u8> {
- let mut atlas = vec![0; 4 * self.width * self.height];
- // For drawing debug lines we want a single white pixel.
- // This is very useful to have in our texture for debugging.
- atlas[0] = 255;
- atlas[1] = 255;
- atlas[2] = 255;
- atlas[3] = 255;
- let y = ATLAS_GAP;
- // Copy all the sprites to our atlas.
- // They should have ATLAS_GAP spacing on all sides to avoid bleeding.
- for (sprite, x) in self.sprites.iter().zip(self.x_pos.iter()) {
- copy_image(sprite, *x, y, &mut atlas, self.width);
- }
- atlas
- }
- fn compute_uvs(&self) -> Vec<Rectangle> {
- // UV coords are in the range [0, 1]
- let mut uvs = vec![];
- let (self_w, self_h) = (self.width as f32, self.height as f32);
- let y = ATLAS_GAP as f32;
- for (sprite, x) in self.sprites.iter().zip(self.x_pos.iter()) {
- let x = *x as f32;
- let sprite_w = sprite.bmp_width as f32;
- let sprite_h = sprite.bmp_height as f32;
- let uv = Rectangle {
- x: x / self_w,
- y: y / self_h,
- w: sprite_w / self_w,
- h: sprite_h / self_h,
- };
- uvs.push(uv);
- }
- uvs
- }
- /// Invalidate this atlas and produce the finalized result.
- /// Each glyph is given a sub-rect within the texture, accessible by calling
- /// `rendered_atlas.fetch_uv(my_glyph_id)`.
- /// The texture ID is a struct member: `rendered_atlas.texture_id`.
- pub fn make(self) -> RenderedAtlas {
- //if self.glyph_ids.is_empty() {
- // return Err(Error::AtlasIsEmpty)
- //}
- assert_eq!(self.glyph_ids.len(), self.sprites.len());
- assert_eq!(self.glyph_ids.len(), self.x_pos.len());
- let atlas = self.render();
- let texture =
- self.render_api.new_texture(self.width as u16, self.height as u16, atlas, self.tag);
- let uv_rects = self.compute_uvs();
- let glyph_ids = self.glyph_ids;
- RenderedAtlas { glyph_ids, uv_rects, texture }
- }
- }
- /// Copy a sprite to (x, y) position within the atlas texture.
- /// Both image formats are RGBA flat vecs.
- fn copy_image(sprite: &Sprite, x: usize, y: usize, atlas: &mut Vec<u8>, atlas_width: usize) {
- for i in 0..sprite.bmp_height {
- for j in 0..sprite.bmp_width {
- let src_y = i * sprite.bmp_width;
- let off_src = 4 * (src_y + j);
- let dest_y = (y + i) * atlas_width;
- let off_dest = 4 * (dest_y + j + x);
- atlas[off_dest] = sprite.bmp[off_src];
- atlas[off_dest + 1] = sprite.bmp[off_src + 1];
- atlas[off_dest + 2] = sprite.bmp[off_src + 2];
- atlas[off_dest + 3] = sprite.bmp[off_src + 3];
- }
- }
- }
- /// Final result computed from `Atlas::make()`.
- #[derive(Clone)]
- pub struct RenderedAtlas {
- glyph_ids: Vec<u32>,
- /// UV rectangle within the texture.
- uv_rects: Vec<Rectangle>,
- /// Allocated atlas texture.
- pub texture: ManagedTexturePtr,
- }
- impl RenderedAtlas {
- /// Get UV coords for a glyph within the rendered atlas.
- pub fn fetch_uv(&self, glyph_id: u32) -> Option<&Rectangle> {
- let glyphs_len = self.glyph_ids.len();
- assert_eq!(glyphs_len, self.uv_rects.len());
- for i in 0..glyphs_len {
- if self.glyph_ids[i] == glyph_id {
- return Some(&self.uv_rects[i])
- }
- }
- None
- }
- }
|