mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-22 15:16:40 +11:00
Textrender rework (#352)
This reworks the text rendering system so the tiles in some rendered text can be persisted across frames. This is important for performance in situations where you want to incrementally write to a text box for example in a Pokemon/Phoenix Wright style dialogue system. Previously the only way to do this was to render all the chars in the text every frame, which is currently prohibitively slow (though this could be potentially sped up). In addition with this implementation it is easy to individually colour each character, so I added the ability to do that to. I expanded the tests to test keeping the renderer instance (which contains the tiles) over multiple frames, and also added in some coloured text. If you are happy with this implementation then I will change the examples which use text, and also add some more documentation. Thanks for taking a look.
This commit is contained in:
commit
99ce2f73d5
6 changed files with 130 additions and 58 deletions
|
@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- Text renderer can now be re-used which is useful for rpg style character/word at a time text boxes.
|
||||
|
||||
## [0.12.2] - 2022/10/22
|
||||
|
||||
This is a minor release to fix an alignment issue with background tiles.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -27,7 +27,8 @@ fn main(mut gba: Gba) -> ! {
|
|||
|
||||
init_background(&mut bg, &mut vram);
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 3u16).into(), 1, 0, &mut bg, &mut vram);
|
||||
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
|
||||
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||
|
||||
writeln!(&mut writer, "Crazy Glue by Josh Woodward").unwrap();
|
||||
|
||||
|
@ -51,6 +52,7 @@ fn main(mut gba: Gba) -> ! {
|
|||
let mut frame_counter = 0i32;
|
||||
let mut has_written_frame_time = false;
|
||||
|
||||
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
|
||||
loop {
|
||||
vblank_provider.wait_for_vblank();
|
||||
bg.commit(&mut vram);
|
||||
|
@ -66,7 +68,9 @@ fn main(mut gba: Gba) -> ! {
|
|||
|
||||
let percent = (total_cycles * 100) / 280896;
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 6u16).into(), 2, 0, &mut bg, &mut vram);
|
||||
stats_renderer.clear(&mut vram);
|
||||
|
||||
let mut writer = stats_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||
writeln!(&mut writer, "{total_cycles} cycles").unwrap();
|
||||
writeln!(&mut writer, "{percent} percent").unwrap();
|
||||
|
||||
|
|
|
@ -27,10 +27,12 @@ fn main(mut gba: Gba) -> ! {
|
|||
|
||||
init_background(&mut bg, &mut vram);
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 3u16).into(), 1, 0, &mut bg, &mut vram);
|
||||
let mut title_renderer = FONT.render_text((0u16, 3u16).into());
|
||||
let mut writer = title_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||
|
||||
writeln!(&mut writer, "Let it in by Josh Woodward").unwrap();
|
||||
|
||||
|
||||
writer.commit();
|
||||
|
||||
bg.commit(&mut vram);
|
||||
|
@ -49,6 +51,8 @@ fn main(mut gba: Gba) -> ! {
|
|||
|
||||
let mut frame_counter = 0i32;
|
||||
let mut has_written_frame_time = false;
|
||||
|
||||
let mut stats_renderer = FONT.render_text((0u16, 6u16).into());
|
||||
loop {
|
||||
vblank_provider.wait_for_vblank();
|
||||
bg.commit(&mut vram);
|
||||
|
@ -65,7 +69,7 @@ fn main(mut gba: Gba) -> ! {
|
|||
|
||||
let percent = (total_cycles * 100) / 280896;
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 6u16).into(), 2, 0, &mut bg, &mut vram);
|
||||
let mut writer = stats_renderer.writer(1, 0, &mut bg, &mut vram);
|
||||
writeln!(&mut writer, "{total_cycles} cycles").unwrap();
|
||||
writeln!(&mut writer, "{percent} percent").unwrap();
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
|
||||
vram.remove_dynamic_tile(background_tile);
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 3u16).into(), 1, 2, &mut bg, &mut vram);
|
||||
let mut renderer = FONT.render_text((0u16, 3u16).into());
|
||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||
|
||||
writeln!(&mut writer, "Hello, World!").unwrap();
|
||||
writeln!(&mut writer, "This is a font rendering example").unwrap();
|
||||
|
@ -53,7 +54,8 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
let mut frame = 0;
|
||||
|
||||
loop {
|
||||
let mut writer = FONT.render_text((4u16, 0u16).into(), 1, 2, &mut bg, &mut vram);
|
||||
let mut renderer = FONT.render_text((4u16, 0u16).into());
|
||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||
|
||||
writeln!(&mut writer, "Frame {}", frame).unwrap();
|
||||
writer.commit();
|
||||
|
@ -62,5 +64,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
|||
|
||||
vblank.wait_for_vblank();
|
||||
bg.commit(&mut vram);
|
||||
|
||||
renderer.clear(&mut vram);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ use crate::hash_map::HashMap;
|
|||
|
||||
use super::tiled::{DynamicTile, RegularMap, TileSetting, VRamManager};
|
||||
|
||||
/// The text renderer renders a variable width fixed size
|
||||
/// bitmap font using dynamic tiles as a rendering surface.
|
||||
/// Does not support any unicode features.
|
||||
/// For usage see the `text_render.rs` example
|
||||
pub struct FontLetter {
|
||||
width: u8,
|
||||
height: u8,
|
||||
|
@ -57,69 +61,91 @@ impl Font {
|
|||
}
|
||||
|
||||
impl Font {
|
||||
pub fn render_text<'a>(
|
||||
&'a self,
|
||||
tile_pos: Vector2D<u16>,
|
||||
foreground_colour: u8,
|
||||
background_colour: u8,
|
||||
bg: &'a mut RegularMap,
|
||||
vram_manager: &'a mut VRamManager,
|
||||
) -> TextRenderer<'a> {
|
||||
#[must_use]
|
||||
/// Create renderer starting at the given tile co-ordinates.
|
||||
pub fn render_text(&self, tile_pos: Vector2D<u16>) -> TextRenderer<'_> {
|
||||
TextRenderer {
|
||||
current_x_pos: 0,
|
||||
current_y_pos: 0,
|
||||
font: self,
|
||||
tile_pos,
|
||||
vram_manager,
|
||||
bg,
|
||||
background_colour,
|
||||
foreground_colour,
|
||||
tiles: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keeps track of the cursor and manages rendered tiles.
|
||||
pub struct TextRenderer<'a> {
|
||||
current_x_pos: i32,
|
||||
current_y_pos: i32,
|
||||
font: &'a Font,
|
||||
tile_pos: Vector2D<u16>,
|
||||
vram_manager: &'a mut VRamManager,
|
||||
bg: &'a mut RegularMap,
|
||||
background_colour: u8,
|
||||
foreground_colour: u8,
|
||||
tiles: HashMap<(i32, i32), DynamicTile<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Write for TextRenderer<'a> {
|
||||
fn write_str(&mut self, text: &str) -> Result<(), Error> {
|
||||
for c in text.chars() {
|
||||
if c == '\n' {
|
||||
self.current_y_pos += self.font.line_height;
|
||||
self.current_x_pos = 0;
|
||||
continue;
|
||||
/// Generated from the renderer for use
|
||||
/// with `Write` trait methods.
|
||||
pub struct TextWriter<'a, 'b> {
|
||||
foreground_colour: u8,
|
||||
background_colour: u8,
|
||||
text_renderer: &'a mut TextRenderer<'b>,
|
||||
vram_manager: &'a mut VRamManager,
|
||||
bg: &'a mut RegularMap,
|
||||
}
|
||||
|
||||
let letter = self.font.letter(c);
|
||||
|
||||
self.render_letter(letter);
|
||||
|
||||
self.current_x_pos += i32::from(letter.advance_width);
|
||||
impl<'a, 'b> Write for TextWriter<'a, 'b> {
|
||||
fn write_str(&mut self, text: &str) -> Result<(), Error> {
|
||||
for c in text.chars() {
|
||||
self.text_renderer.write_char(
|
||||
c,
|
||||
self.vram_manager,
|
||||
self.foreground_colour,
|
||||
self.background_colour,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> TextWriter<'a, 'b> {
|
||||
pub fn commit(self) {
|
||||
self.text_renderer.commit(self.bg, self.vram_manager);
|
||||
}
|
||||
}
|
||||
|
||||
fn div_ceil(quotient: i32, divisor: i32) -> i32 {
|
||||
(quotient + divisor - 1) / divisor
|
||||
}
|
||||
|
||||
impl<'a> TextRenderer<'a> {
|
||||
fn render_letter(&mut self, letter: &FontLetter) {
|
||||
let vram_manager = &mut self.vram_manager;
|
||||
let foreground_colour = self.foreground_colour;
|
||||
let background_colour = self.background_colour;
|
||||
impl<'a, 'b> TextRenderer<'b> {
|
||||
pub fn writer(
|
||||
&'a mut self,
|
||||
foreground_colour: u8,
|
||||
background_colour: u8,
|
||||
bg: &'a mut RegularMap,
|
||||
vram_manager: &'a mut VRamManager,
|
||||
) -> TextWriter<'a, 'b> {
|
||||
TextWriter {
|
||||
text_renderer: self,
|
||||
foreground_colour,
|
||||
background_colour,
|
||||
bg,
|
||||
vram_manager,
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders a single character creating as many dynamic tiles as needed.
|
||||
/// The foreground and background colour are palette indicies.
|
||||
fn render_letter(
|
||||
&mut self,
|
||||
letter: &FontLetter,
|
||||
vram_manager: &mut VRamManager,
|
||||
foreground_colour: u8,
|
||||
background_colour: u8,
|
||||
) {
|
||||
assert!(foreground_colour < 16);
|
||||
assert!(background_colour < 16);
|
||||
|
||||
let x_start = (self.current_x_pos + i32::from(letter.xmin)).max(0);
|
||||
let y_start = self.current_y_pos + self.font.ascent
|
||||
|
@ -182,27 +208,44 @@ impl<'a> TextRenderer<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn commit(mut self) {
|
||||
let tiles = core::mem::take(&mut self.tiles);
|
||||
|
||||
for ((x, y), tile) in tiles.into_iter() {
|
||||
self.bg.set_tile(
|
||||
self.vram_manager,
|
||||
(self.tile_pos.x + x as u16, self.tile_pos.y + y as u16).into(),
|
||||
/// Commit the dynamic tiles that contain the text to the background.
|
||||
pub fn commit(&self, bg: &'a mut RegularMap, vram_manager: &'a mut VRamManager) {
|
||||
for ((x, y), tile) in self.tiles.iter() {
|
||||
bg.set_tile(
|
||||
vram_manager,
|
||||
(self.tile_pos.x + *x as u16, self.tile_pos.y + *y as u16).into(),
|
||||
&tile.tile_set(),
|
||||
TileSetting::from_raw(tile.tile_index()),
|
||||
);
|
||||
self.vram_manager.remove_dynamic_tile(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for TextRenderer<'a> {
|
||||
fn drop(&mut self) {
|
||||
/// Write another char into the text, moving the cursor as appropriate.
|
||||
pub fn write_char(
|
||||
&mut self,
|
||||
c: char,
|
||||
vram_manager: &mut VRamManager,
|
||||
foreground_colour: u8,
|
||||
background_colour: u8,
|
||||
) {
|
||||
if c == '\n' {
|
||||
self.current_y_pos += self.font.line_height;
|
||||
self.current_x_pos = 0;
|
||||
} else {
|
||||
let letter = self.font.letter(c);
|
||||
self.render_letter(letter, vram_manager, foreground_colour, background_colour);
|
||||
self.current_x_pos += i32::from(letter.advance_width);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the text, removing the tiles from vram and resetting the cursor.
|
||||
pub fn clear(&mut self, vram_manager: &mut VRamManager) {
|
||||
self.current_x_pos = 0;
|
||||
self.current_y_pos = 0;
|
||||
let tiles = core::mem::take(&mut self.tiles);
|
||||
|
||||
for (_, tile) in tiles.into_iter() {
|
||||
self.vram_manager.remove_dynamic_tile(tile);
|
||||
vram_manager.remove_dynamic_tile(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,16 +285,30 @@ mod tests {
|
|||
|
||||
vram.remove_dynamic_tile(background_tile);
|
||||
|
||||
let mut writer = FONT.render_text((0u16, 3u16).into(), 1, 2, &mut bg, &mut vram);
|
||||
|
||||
writeln!(&mut writer, "Hello, World!").unwrap();
|
||||
writeln!(&mut writer, "This is a font rendering example").unwrap();
|
||||
let mut renderer = FONT.render_text((0u16, 3u16).into());
|
||||
|
||||
// Test twice to ensure that clearing works
|
||||
for _ in 0..2 {
|
||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||
write!(&mut writer, "Hello, ").unwrap();
|
||||
writer.commit();
|
||||
|
||||
// Test changing color
|
||||
let mut writer = renderer.writer(4, 2, &mut bg, &mut vram);
|
||||
writeln!(&mut writer, "World!").unwrap();
|
||||
writer.commit();
|
||||
bg.commit(&mut vram);
|
||||
bg.show();
|
||||
|
||||
// Test writing with same renderer after showing background
|
||||
let mut writer = renderer.writer(1, 2, &mut bg, &mut vram);
|
||||
writeln!(&mut writer, "This is a font rendering example").unwrap();
|
||||
writer.commit();
|
||||
bg.commit(&mut vram);
|
||||
bg.show();
|
||||
|
||||
crate::test_runner::assert_image_output("examples/font/font-test-output.png");
|
||||
renderer.clear(&mut vram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue