use crate::dma;
use crate::format::tim::TIM;
use crate::gpu::colors::WHITE;
use crate::gpu::primitives::Sprt8;
use crate::gpu::{Clut, Color, DMAMode, Depth, DispEnv, DrawEnv, Packet, TexColor, TexCoord,
TexPage, Vertex, VertexError, VideoMode, GPU_BUFFER_SIZE};
use crate::hw::gpu::{GP0Command, GP0, GP1};
use crate::hw::irq::IRQ;
use crate::hw::{gpu, irq, Register};
use crate::include_tim;
use core::fmt;
use core::mem::size_of;
fn draw_sync() {
let mut gpu_stat = gpu::Status::new();
while !gpu_stat.cmd_ready() || !gpu_stat.dma_ready() {
gpu_stat.load();
}
}
pub struct Framebuffer {
pub gp0: GP0,
pub gp1: GP1,
pub gpu_status: gpu::Status,
pub irq_mask: irq::Mask,
pub irq_status: irq::Status,
disp_envs: [DispEnv; 2],
draw_envs: [Packet<DrawEnv>; 2],
swapped: bool,
}
impl Default for Framebuffer {
fn default() -> Self {
unsafe { Self::new((0, 0), (0, 240), (320, 240), VideoMode::NTSC, None).unwrap_unchecked() }
}
}
impl Framebuffer {
pub fn new(
buf0: (i16, i16), buf1: (i16, i16), res: (i16, i16), video_mode: VideoMode,
bg_color: Option<Color>,
) -> Result<Self, VertexError> {
let mut fb = Framebuffer {
gp0: GP0::skip_load(),
gp1: GP1::skip_load(),
gpu_status: gpu::Status::new(),
irq_status: irq::Status::skip_load(),
irq_mask: irq::Mask::new(),
disp_envs: [
DispEnv::new(buf0, res, video_mode)?,
DispEnv::new(buf1, res, video_mode)?,
],
draw_envs: [
Packet::new(DrawEnv::new(buf1, res, bg_color)?),
Packet::new(DrawEnv::new(buf0, res, bg_color)?),
],
swapped: false,
};
let interlace = match res.1 {
480 | 512 => true,
_ => false
};
GP1::skip_load()
.reset_gpu()
.dma_mode(Some(DMAMode::GP0))
.display_mode(res, video_mode, Depth::Bits15, interlace)?
.enable_display(true);
fb.irq_mask.enable_irq(IRQ::Vblank).store();
Ok(fb)
}
pub fn set_bg_color(&mut self, color: Color) {
for packet_env in &mut self.draw_envs {
packet_env.contents.bg_color = color;
}
}
pub fn swap(&mut self) {
self.swapped = !self.swapped;
let idx = self.swapped as usize;
self.gp1.set_display_env(&self.disp_envs[idx]);
self.gp0.send_command(&self.draw_envs[idx].contents);
}
pub fn dma_swap(&mut self, gpu_dma: &mut dma::GPU) {
self.swapped = !self.swapped;
let idx = self.swapped as usize;
self.gp1.set_display_env(&self.disp_envs[idx]);
gpu_dma.send_list(&self.draw_envs[idx]);
}
pub fn load_tim<const N: usize, const M: usize>(&mut self, tim: TIM<N, M>) -> LoadedTIM {
struct CopyToVRAM<'a>(&'a [u32]);
impl GP0Command for CopyToVRAM<'_> {
fn data(&self) -> &[u32] {
self.0
}
}
self.draw_sync();
self.gp0.send_command(&CopyToVRAM(&tim.bmp.data));
let clut = if M != 0 {
self.draw_sync();
self.gp0.send_command(&CopyToVRAM(&tim.clut.data));
Some(tim.clut.offset)
} else {
None
};
LoadedTIM {
tex_page: tim.bmp.offset,
clut,
}
}
pub fn load_default_font(&mut self) -> LoadedTIM {
let font = include_tim!("../font.tim");
self.load_tim(font)
}
pub fn draw_sync(&mut self) {
self.gpu_status.load();
while !self.gpu_status.cmd_ready() || !self.gpu_status.dma_ready() {
self.gpu_status.load();
}
}
pub fn wait_vblank(&mut self) {
self.irq_status.ack(IRQ::Vblank).store().wait(IRQ::Vblank);
}
}
impl fmt::Write for TextBox {
fn write_str(&mut self, msg: &str) -> fmt::Result {
draw_sync();
for c in msg.chars() {
if c.is_ascii() {
self.print_char(c as u8);
} else {
self.print_char(b'?');
}
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct LoadedTIM {
pub tex_page: TexPage,
pub clut: Option<Clut>,
}
const TEXT_BOX_BUFFER: usize = GPU_BUFFER_SIZE / size_of::<Sprt8>();
pub struct TextBox {
color: TexColor,
initial: Vertex,
cursor: Vertex,
size: Vertex,
idx: usize,
buffer: [Sprt8; TEXT_BOX_BUFFER],
}
impl LoadedTIM {
pub fn new_text_box(&self, offset: (i16, i16), size: (i16, i16)) -> TextBox {
let offset = Vertex::new(offset);
let size = Vertex::new(size);
let color = TexColor::from(WHITE);
let mut buffer = [Sprt8::new(); TEXT_BOX_BUFFER];
for letter in &mut buffer {
if let Some(clut) = self.clut {
letter.set_clut(clut);
}
letter.set_color(color);
}
TextBox {
color,
initial: offset,
cursor: offset,
size,
idx: 0,
buffer,
}
}
}
const FONT_SIZE: u8 = 8;
impl TextBox {
pub fn newline(&mut self) {
self.cursor = Vertex(self.initial.0, self.cursor.1 + FONT_SIZE as i16);
}
pub fn reset(&mut self) {
self.cursor = self.initial;
}
pub fn move_up(&mut self, n: usize) {
for _ in 0..n {
self.cursor.1 -= FONT_SIZE as i16;
}
}
pub fn move_down(&mut self, n: usize) {
for _ in 0..n {
self.cursor.1 += FONT_SIZE as i16;
}
}
pub fn move_left(&mut self, n: usize) {
for _ in 0..n {
self.cursor.0 -= FONT_SIZE as i16;
}
}
pub fn move_right(&mut self, n: usize) {
for _ in 0..n {
self.cursor.0 += FONT_SIZE as i16;
}
}
pub fn change_color(&mut self, color: Color) {
let color = TexColor::from(color);
if color != self.color {
self.color = color;
for letter in &mut self.buffer {
letter.set_color(color);
}
}
}
pub fn print_char(&mut self, ascii: u8) {
if ascii == b'\n' {
self.newline();
self.cursor.0 = self.initial.0;
} else {
let ascii_per_row = 128 / FONT_SIZE;
let ascii = if ascii < (2 * ascii_per_row) {
b'?'
} else {
ascii - (2 * ascii_per_row)
};
let x = (ascii % ascii_per_row) * FONT_SIZE;
let y = (ascii / ascii_per_row) * FONT_SIZE;
if self.idx == 0 {
draw_sync();
}
self.buffer[self.idx]
.set_offset(self.cursor)
.set_tex_coord(TexCoord { x, y });
GP0::skip_load().send_command(&self.buffer[self.idx]);
self.idx += 1;
if self.idx == TEXT_BOX_BUFFER {
self.idx = 0;
}
self.cursor.0 += FONT_SIZE as i16;
if self.cursor.0 == self.initial.0 + self.size.0 {
self.newline();
self.cursor.0 = self.initial.0;
}
if self.cursor.1 == self.initial.1 + self.size.1 {
self.cursor = self.initial;
}
}
}
}
#[macro_export]
macro_rules! dprint {
($box:expr, $($args:tt)*) => {
{
use core::fmt::Write;
$box.write_fmt(format_args!($($args)*)).ok()
}
};
}
#[macro_export]
macro_rules! dprintln {
($box:expr, $($args:tt)*) => {
$crate::dprint!($box, $($args)*);
$box.print_char(b'\n');
};
}