Skip to content

Commit

Permalink
feat: Font batch rendering, true 0type improvements (#344)
Browse files Browse the repository at this point in the history
* feat: Render fonts using texture batch instead of components

* fix last bugs

* test fix
  • Loading branch information
suspistew authored Jan 18, 2025
1 parent f74933d commit 54d9c92
Show file tree
Hide file tree
Showing 52 changed files with 663 additions and 566 deletions.
2 changes: 1 addition & 1 deletion examples/tetris/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn add_score_ui(data: &mut GameData) -> Entity {

data.push((
button,
Transform::from_xyz(394., 430., 2)
Transform::from_xyz(394., 430., 3)
));

let font2 = Font::Bitmap {
Expand Down
18 changes: 8 additions & 10 deletions src/application.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
use std::path::Path;

use std::sync::{Arc, mpsc};
use std::sync::{mpsc, Arc};
use std::thread;
use std::time::Duration;


use crate::core::application_builder::ScionBuilder;
use crate::config::scion_config::{ScionConfig, ScionConfigReader};
use log::{error, info};
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
use winit::application::ApplicationHandler;
use winit::error::EventLoopError;
use winit::event::{DeviceEvent, DeviceId};
use winit::event_loop::ActiveEventLoop;
use winit::window::{WindowAttributes, WindowId};
use crate::{
config::scion_config::{ScionConfig, ScionConfigReader},
use winit::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
};
use crate::core::application_builder::ScionBuilder;


use crate::core::scene::{SceneMachine};
use crate::core::scene::SceneMachine;
use crate::core::scheduler::Scheduler;
use crate::core::scion_runner::ScionRunner;


use crate::core::world::GameData;
use crate::graphics::rendering::RendererCallbackEvent;
use crate::graphics::rendering::scion2d::window_rendering_manager::ScionWindowRenderingManager;
use crate::graphics::rendering::RendererCallbackEvent;
use crate::graphics::windowing::WindowingEvent;

/// `Scion` is the entry point of any application made with Scion's lib.
Expand Down
2 changes: 1 addition & 1 deletion src/config/window_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use winit::dpi::LogicalSize;
use winit::window::{WindowAttributes};
use winit::window::WindowLevel;
use winit::window::WindowAttributes;

use crate::{config::scion_config::ScionConfig, graphics::components::color::Color};

Expand Down
2 changes: 1 addition & 1 deletion src/core/components/maths/collider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use geo_clipper::Clipper;
use geo_types::{Coord, LineString};
use hecs::Entity;

use crate::core::components::maths::{coordinates::Coordinates, Pivot, transform::Transform};
use crate::core::components::maths::{coordinates::Coordinates, transform::Transform, Pivot};
use crate::utils::maths::{centroid_polygon, rotate_point_around_pivot, Vector};

/// `ColliderMask` will serve as a 'mask' to allow filter while collisions happen
Expand Down
6 changes: 2 additions & 4 deletions src/core/resources/asset_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::{collections::HashMap, marker::PhantomData};

use log::debug;

use crate::graphics::components::{material::Material, tiles::tileset::Tileset};
use crate::graphics::components::ui::font::Font;
use crate::graphics::components::{material::Material, tiles::tileset::Tileset};

/// `AssetManager` is resource that will link assets to an asset ref to allow reusability of assets
#[derive(Default)]
Expand Down Expand Up @@ -133,9 +133,7 @@ pub enum AssetType {

#[cfg(test)]
mod tests {
use crate::core::{
resources::asset_manager::AssetManager,
};
use crate::core::resources::asset_manager::AssetManager;
use crate::graphics::components::color::Color;
use crate::graphics::components::material::Material;
use crate::graphics::components::tiles::tileset::Tileset;
Expand Down
2 changes: 0 additions & 2 deletions src/core/resources/audio.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use std::sync::mpsc;

use rodio::{OutputStream, Sink};

use crate::core::audio_controller;
use crate::core::audio_controller::AudioController;

Expand Down
4 changes: 2 additions & 2 deletions src/core/resources/color_picking.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::{HashMap, VecDeque};
use hecs::Entity;
use crate::graphics::components::color::Color;
use hecs::Entity;
use std::collections::{HashMap, VecDeque};

#[derive(Default)]
pub(crate) struct ColorPickingStorage{
Expand Down
190 changes: 124 additions & 66 deletions src/core/resources/font_atlas.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use std::collections::HashMap;
use std::path::Path;

use ab_glyph::{Font, FontVec, Glyph, Point, point, PxScale, ScaleFont};
use ab_glyph::{point, Font, FontVec, Glyph, Point, PxScale, ScaleFont};
use image::{DynamicImage, Rgba};

use log::info;
use crate::graphics::components::color::Color;
use crate::graphics::components::material::Texture;
use crate::utils::file::read_file;
use crate::utils::file::{app_base_path, read_file};
use crate::utils::ScionError;

const TEXT: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890éèàùç-?!.,:=/+-%&'()";
const TEXT: &str = "a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0 é è à ù ç - ? ! . , : = / + - % & ' ( )";

#[derive(Debug)]
pub(crate) struct CharacterPosition {
Expand All @@ -28,17 +28,26 @@ impl CharacterPosition {
end_y,
}
}

pub fn width(&self) -> f32 {
self.end_x - self.start_x
}

pub fn height(&self) -> f32 {
self.end_y - self.start_y
}
}

pub(crate) struct TrueTypeData {
#[derive(Debug)]
pub(crate) struct FontAtlasEntry {
pub(crate) texture: Option<Texture>,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) min_y: f32,
pub(crate) character_positions: HashMap<char, CharacterPosition>,
}

impl TrueTypeData {
impl FontAtlasEntry {
pub(crate) fn take_texture(&mut self) -> Texture {
if let Some(tex) = self.texture.take(){
return tex;
Expand All @@ -51,98 +60,147 @@ impl TrueTypeData {
}
0.
}
pub(crate) fn min_y(&self) -> f32 {
self.character_positions.iter()
.min_by(|p1, p2| p1.1.start_y.partial_cmp(&p2.1.start_y).unwrap_or(std::cmp::Ordering::Equal))
.map(|p| p.1.start_y).unwrap_or(0.)
}
}

#[derive(Default)]
pub(crate) struct FontAtlas {
atlas: HashMap<String, TrueTypeData>,
atlas: HashMap<String, FontAtlasEntry>,
}

impl FontAtlas {
pub fn get_texture_from_path(&mut self, path: &str) -> Option<&mut TrueTypeData> {
pub fn get_texture_from_path(&mut self, path: &str) -> Option<&mut FontAtlasEntry> {
if self.atlas.contains_key(path) {
return self.atlas.get_mut(path);
}
None
}

pub fn get_texture(&self, font: &str, font_size: usize, font_color: &Color) -> Option<&TrueTypeData> {
let key = format!("{:?}_{:?}_{:?}", font, font_size, font_color.to_string());
pub fn get_texture(&self, font: &str, font_size: usize, font_color: &Color) -> Option<&FontAtlasEntry> {
let key = FontAtlas::true_type_path(font, font_size, font_color);
if self.atlas.contains_key(&key) {
return self.atlas.get(&key);
}
None
}

pub fn add_texture(&mut self, font: String, font_size: usize, font_color: &Color, data: TrueTypeData) {
pub fn true_type_path(font: &str, font_size: usize, font_color: &Color) -> String {
format!("{:?}_{:?}_{:?}", font, font_size, font_color.to_string())
}

pub fn add_true_type(&mut self, font: String, font_size: usize, font_color: &Color, data: FontAtlasEntry) {
let key = format!("{:?}_{:?}_{:?}", font, font_size, font_color.to_string());
self.atlas.insert(key, data);
}

pub fn add_bitmap(&mut self, font: String, data: FontAtlasEntry) {
self.atlas.insert(font, data);
}
}

pub(crate) fn generate_bitmap(font: crate::graphics::components::ui::font::Font, font_size: usize, font_color: &Color) -> Result<TrueTypeData, ScionError> {
if let crate::graphics::components::ui::font::Font::TrueType { font_path } = font {
return match read_file(Path::new(&font_path)) {
Ok(res) => {
let font = FontVec::try_from_vec(res);
if let Ok(font_vec) = font {
let mut glyphs = Vec::<Glyph>::new();
let scale = PxScale::from(font_size as f32);
let scaled_font = font_vec.as_scaled(scale);
layout_paragraph(scaled_font, point(20.0, 20.0), 9999.0, TEXT, &mut glyphs);
let glyphs_height = scaled_font.height().ceil() as u32;
let glyphs_width = {
let min_x = glyphs.first().unwrap().position.x;
let last_glyph = glyphs.last().unwrap();
let max_x = last_glyph.position.x + scaled_font.h_advance(last_glyph.id);
(max_x - min_x).ceil() as u32
};

let mut character_positions = HashMap::<char, CharacterPosition>::new();
let mut min_y = 99999.;
let mut image = DynamicImage::new_rgba8(glyphs_width + 40, glyphs_height + 40).to_rgba8();
for (pos, glyph) in glyphs.drain(0..glyphs.len()).enumerate() {
if let Some(outlined) = scaled_font.outline_glyph(glyph) {
let bounds = outlined.px_bounds();
outlined.draw(|x, y, v| {
let px = image.get_pixel_mut(x + bounds.min.x as u32, y + bounds.min.y as u32);
*px = Rgba([
font_color.red(),
font_color.green(),
font_color.blue(),
px.0[3].saturating_add((v * 255.0) as u8),
]);
});
if min_y > bounds.min.y {
min_y = bounds.min.y;
}
let char_pos = CharacterPosition::new(bounds.min.x, bounds.min.y, bounds.max.x, bounds.max.y);
character_positions.insert(TEXT.to_string().chars().nth(pos).unwrap(), char_pos);
pub(crate) fn convert_true_type(font_path: String, font_size: usize, font_color: &Color) -> Result<FontAtlasEntry, ScionError> {
match read_file(Path::new(&font_path)) {
Ok(res) => {
let font = FontVec::try_from_vec(res);
if let Ok(font_vec) = font {
let mut glyphs = Vec::<Glyph>::new();
let scale = PxScale::from(font_size as f32);
let scaled_font = font_vec.as_scaled(scale);
layout_paragraph(scaled_font, point(20.0, 20.0), 9999.0, TEXT, &mut glyphs);
let glyphs_height = scaled_font.height().ceil() as u32;
let glyphs_width = {
let min_x = glyphs.first().unwrap().position.x;
let last_glyph = glyphs.last().unwrap();
let max_x = last_glyph.position.x + scaled_font.h_advance(last_glyph.id);
(max_x - min_x).ceil() as u32
};

let mut character_positions = HashMap::<char, CharacterPosition>::new();
let mut min_y = 99999.;
let mut image = DynamicImage::new_rgba8(glyphs_width + 40, glyphs_height + 40).to_rgba8();
let mut min_x = f32::MAX;
let mut max_x = f32::MIN;
for (pos, glyph) in glyphs.drain(0..glyphs.len()).enumerate() {
if let Some(outlined) = scaled_font.outline_glyph(glyph) {
let bounds = outlined.px_bounds();
outlined.draw(|x, y, v| {
let px = image.get_pixel_mut(x + bounds.min.x as u32, y + bounds.min.y as u32);
*px = Rgba([
font_color.red(),
font_color.green(),
font_color.blue(),
px.0[3].saturating_add((v * 255.0) as u8),
]);
});
if min_y > bounds.min.y {
min_y = bounds.min.y;
}
let char_pos = CharacterPosition::new(bounds.min.x, bounds.min.y, bounds.max.x, bounds.max.y);
character_positions.insert(TEXT.to_string().chars().nth(pos).unwrap(), char_pos);
}
return Ok(TrueTypeData {
texture: Some(Texture {
bytes: image.to_vec(),
width: glyphs_width + 40,
height: glyphs_height + 40,
}),
}
image.save(app_base_path().join("test_font.png").get()).unwrap();
return Ok(FontAtlasEntry {
texture: Some(Texture {
bytes: image.to_vec(),
width: glyphs_width + 40,
height: glyphs_height + 40,
min_y,
character_positions,
});
}
Err(ScionError::new("Impossible to read font"))
}),
width: glyphs_width + 40,
height: glyphs_height + 40,
min_y,
character_positions,
});
}
Err(_) => Err(ScionError::new("Impossible to find font file"))
};
Err(ScionError::new("Impossible to read font"))
}
Err(_) => Err(ScionError::new("Impossible to find font file"))
}
}

pub(crate) fn convert_bitmap(texture_path: String,
chars: String,
width: f32,
height: f32,
texture_columns: f32,
texture_lines: f32) -> Result<FontAtlasEntry, ScionError> {

match image::open(&texture_path) {
Ok(img) => {
let mut character_positions = HashMap::<char, CharacterPosition>::new();
let img_width = img.width();
let img_height = img.height();

for (pos, character) in chars.chars().enumerate() {
info!("c {}", texture_columns);
let mut cursor_x = (pos % texture_columns as usize) as f32 * width;
let mut cursor_y = (pos / texture_columns as usize) as f32 * height;
let char_pos = CharacterPosition::new(cursor_x, cursor_y, cursor_x + width, cursor_y + height);
character_positions.insert(character, char_pos);
}

Ok(FontAtlasEntry {
texture: Some(Texture {
bytes: img.into_bytes(),
width: img_width,
height: img_height,
}),
width: img_width,
height: img_height,
min_y: 0.,
character_positions,
})
}
Err(err) => {
Err(crate::utils::ScionError::new(""))
}
}
Err(ScionError::new("Wrong type sent to bitmap generation"))
}

/// Simple paragraph layout for glyphs into `target`.
///
/// This is for testing and examples.
pub fn layout_paragraph<F, SF>(
font: SF,
position: Point,
Expand Down
2 changes: 1 addition & 1 deletion src/core/resources/inputs/inputs_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::core::resources::inputs::{
keyboard::Keyboard,
mouse::{Mouse, MouseEvent},
types::{Input, InputState, KeyboardEvent, KeyCode, Shortcut},
types::{Input, InputState, KeyCode, KeyboardEvent, Shortcut},
};

/// A resource updated by `Scion` to keep track of the core.resources.inputs
Expand Down
2 changes: 1 addition & 1 deletion src/core/resources/inputs/keyboard.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::HashSet;

use crate::core::resources::inputs::types::{Input, InputState, KeyboardEvent, KeyCode};
use crate::core::resources::inputs::types::{Input, InputState, KeyCode, KeyboardEvent};

#[derive(Default)]
/// Convenience resource used to keep track of keyboard inputs
Expand Down
2 changes: 1 addition & 1 deletion src/core/resources/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ mod timer {

#[cfg(test)]
mod tests {
use crate::core::resources::time::{Timers, TimerType};
use crate::core::resources::time::{TimerType, Timers};

#[test]
fn add_timer_test() {
Expand Down
Loading

0 comments on commit 54d9c92

Please sign in to comment.