some object based text rendering

This commit is contained in:
Corwin 2023-06-25 12:34:27 +01:00
parent aa38a03ac9
commit b99fff7c8e
No known key found for this signature in database
5 changed files with 322 additions and 8 deletions

View file

@ -0,0 +1,78 @@
#![no_std]
#![no_main]
use agb::{
display::{
object::{
font::{Configuration, WordRender},
PaletteVram, Size,
},
palette16::Palette16,
Font,
},
fixnum::num,
include_font,
timer::Divider,
};
use agb_fixnum::Num;
use core::fmt::Write;
const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
#[agb::entry]
fn entry(gba: agb::Gba) -> ! {
main(gba);
}
fn main(mut gba: agb::Gba) -> ! {
let (mut unmanaged, mut sprites) = gba.display.object.get_unmanaged();
let mut palette = [0x0; 16];
palette[1] = 0xFF_FF;
let palette = Palette16::new(palette);
let palette = PaletteVram::new(&palette).unwrap();
let config = Configuration::new(Size::S32x16, palette);
let mut wr = WordRender::new(&FONT, config);
let mut number: Num<i32, 8> = num!(1.25235);
let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new();
let timer = gba.timers.timers();
let mut timer = timer.timer2;
timer.set_enabled(true);
timer.set_divider(agb::timer::Divider::Divider64);
loop {
vblank.wait_for_vblank();
input.update();
number += num!(0.01) * input.y_tri() as i32;
let start = timer.value();
let _ = writeln!(wr, "abcdefgh ijklmnopq rstuvwxyz");
let line = wr.get_line();
let rasterised = timer.value();
let oam_frmae = &mut unmanaged.iter();
line.unwrap().draw(oam_frmae);
let drawn = timer.value();
let start_to_end = to_ms(drawn.wrapping_sub(start));
let raster = to_ms(rasterised.wrapping_sub(start));
let object = to_ms(drawn.wrapping_sub(rasterised));
agb::println!("Start: {start_to_end:.3}");
agb::println!("Raster: {raster:.3}");
agb::println!("Object: {object:.3}");
}
}
fn to_ms(time: u16) -> Num<i32, 8> {
Num::new(time as i32) * num!(3.815) / 1000
}

View file

@ -10,12 +10,12 @@ use super::tiled::{DynamicTile, RegularMap, TileSetting, VRamManager};
/// Does not support any unicode features. /// Does not support any unicode features.
/// For usage see the `text_render.rs` example /// For usage see the `text_render.rs` example
pub struct FontLetter { pub struct FontLetter {
width: u8, pub(crate) width: u8,
height: u8, pub(crate) height: u8,
data: &'static [u8], pub(crate) data: &'static [u8],
xmin: i8, pub(crate) xmin: i8,
ymin: i8, pub(crate) ymin: i8,
advance_width: u8, pub(crate) advance_width: u8,
} }
impl FontLetter { impl FontLetter {
@ -37,6 +37,13 @@ impl FontLetter {
advance_width, advance_width,
} }
} }
pub(crate) const fn bit_absolute(&self, x: usize, y: usize) -> bool {
let position = x + y * self.width as usize;
let byte = self.data[position / 8];
let bit = position % 8;
((byte >> bit) & 1) != 0
}
} }
pub struct Font { pub struct Font {
@ -55,9 +62,13 @@ impl Font {
} }
} }
fn letter(&self, letter: char) -> &'static FontLetter { pub(crate) fn letter(&self, letter: char) -> &'static FontLetter {
&self.letters[letter as usize] &self.letters[letter as usize]
} }
pub(crate) fn ascent(&self) -> i32 {
self.ascent
}
} }
impl Font { impl Font {

View file

@ -9,6 +9,7 @@
//! harder to integrate into your games depending on how they are architectured. //! harder to integrate into your games depending on how they are architectured.
mod affine; mod affine;
pub mod font;
mod managed; mod managed;
mod sprites; mod sprites;
mod unmanaged; mod unmanaged;

View file

@ -0,0 +1,221 @@
use core::fmt::Write;
use agb_fixnum::Vector2D;
use alloc::{collections::VecDeque, vec::Vec};
use crate::display::{object::ObjectUnmanaged, Font};
use super::{DynamicSprite, OamIterator, PaletteVram, Size, SpriteVram};
struct LetterGroup {
sprite: SpriteVram,
/// x offset from the *start* of the *word*
offset: i32,
}
struct Word {
start_index: usize,
end_index: usize,
size: i32,
}
impl Word {
fn number_of_letter_groups(&self) -> usize {
self.end_index - self.start_index
}
}
pub struct MetaWords {
letters: Vec<LetterGroup>,
words: Vec<Word>,
}
impl MetaWords {
const fn new_empty() -> Self {
Self {
letters: Vec::new(),
words: Vec::new(),
}
}
fn word_iter(&self) -> impl Iterator<Item = (i32, &[LetterGroup])> {
self.words
.iter()
.map(|x| (x.size, &self.letters[x.start_index..x.end_index]))
}
pub fn draw(&self, oam: &mut OamIterator) {
fn inner_draw(mw: &MetaWords, oam: &mut OamIterator) -> Option<()> {
let mut word_offset = 0;
for (size, word) in mw.word_iter() {
for letter_group in word.iter() {
let mut object = ObjectUnmanaged::new(letter_group.sprite.clone());
object.set_position((word_offset + letter_group.offset, 0).into());
object.show();
oam.next()?.set(&object);
}
word_offset += size + 10;
}
Some(())
}
let _ = inner_draw(self, oam);
}
}
struct WorkingLetter {
dynamic: DynamicSprite,
// the x offset of the current letter with respect to the start of the current letter group
x_position: i32,
// where to render the letter from x_min to x_max
x_offset: usize,
}
impl WorkingLetter {
fn new(size: Size) -> Self {
Self {
dynamic: DynamicSprite::new(size),
x_position: 0,
x_offset: 0,
}
}
}
pub struct Configuration {
sprite_size: Size,
palette: PaletteVram,
}
impl Configuration {
#[must_use]
pub fn new(sprite_size: Size, palette: PaletteVram) -> Self {
Self {
sprite_size,
palette,
}
}
}
pub struct WordRender<'font> {
font: &'font Font,
working: Working,
finalised_metas: VecDeque<MetaWords>,
config: Configuration,
}
struct Working {
letter: WorkingLetter,
meta: MetaWords,
word_offset: i32,
}
impl<'font> Write for WordRender<'font> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for c in s.chars() {
self.write_char(c);
}
Ok(())
}
}
impl<'font> WordRender<'font> {
#[must_use]
pub fn new(font: &'font Font, config: Configuration) -> Self {
WordRender {
font,
working: Working {
letter: WorkingLetter::new(config.sprite_size),
meta: MetaWords::new_empty(),
word_offset: 0,
},
finalised_metas: VecDeque::new(),
config,
}
}
}
impl WordRender<'_> {
pub fn get_line(&mut self) -> Option<MetaWords> {
self.finalised_metas.pop_front()
}
fn write_char(&mut self, c: char) {
if c == '\n' {
self.finalise_line();
} else if c == ' ' {
self.finalise_word();
} else {
self.render_char(c);
}
}
fn finalise_line(&mut self) {
self.finalise_word();
let mut final_meta = MetaWords::new_empty();
core::mem::swap(&mut self.working.meta, &mut final_meta);
self.finalised_metas.push_back(final_meta);
}
fn finalise_word(&mut self) {
self.finalise_letter();
let start_index = self.working.meta.words.last().map_or(0, |x| x.end_index);
let end_index = self.working.meta.letters.len();
let word = Word {
start_index,
end_index,
size: self.working.word_offset,
};
self.working.meta.words.push(word);
self.working.word_offset = 0;
}
fn finalise_letter(&mut self) {
let mut final_letter = WorkingLetter::new(self.config.sprite_size);
core::mem::swap(&mut final_letter, &mut self.working.letter);
let sprite = final_letter.dynamic.to_vram(self.config.palette.clone());
self.working.meta.letters.push(LetterGroup {
sprite,
offset: self.working.word_offset,
});
self.working.word_offset += final_letter.x_position;
}
fn render_char(&mut self, c: char) {
let font_letter = self.font.letter(c);
// uses more than the sprite can hold
if self.working.letter.x_offset + font_letter.width as usize
> self.config.sprite_size.to_width_height().0
{
self.finalise_letter();
}
self.working.letter.x_position += font_letter.xmin as i32;
let y_position = self.font.ascent() - font_letter.height as i32 - font_letter.ymin as i32;
for y in 0..font_letter.height as usize {
for x in 0..font_letter.width as usize {
let rendered = font_letter.bit_absolute(x, y);
if rendered {
self.working.letter.dynamic.set_pixel(
x + self.working.letter.x_offset,
(y_position + y as i32) as usize,
1,
);
}
}
}
self.working.letter.x_position += font_letter.advance_width as i32;
self.working.letter.x_offset += font_letter.advance_width as usize;
}
}

View file

@ -333,7 +333,10 @@ impl<A: Allocator> DynamicSprite<A> {
let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x; let tile_number_to_modify = adjust_tile_x + adjust_tile_y * sprite_tile_x;
let byte_to_modify_in_tile = x / 2 + y * 4; let (x_in_tile, y_in_tile) = (x % 8, y % 8);
let byte_to_modify_in_tile = x_in_tile / 2 + y_in_tile * 4;
let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP + byte_to_modify_in_tile; let byte_to_modify = tile_number_to_modify * BYTES_PER_TILE_4BPP + byte_to_modify_in_tile;
let mut byte = self.data[byte_to_modify]; let mut byte = self.data[byte_to_modify];
let parity = (x & 0b1) * 4; let parity = (x & 0b1) * 4;