use std::{fmt::Display, sync::Arc}; use egui::{ Align, CentralPanel, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, memory::{MemoryMonitor, MemoryView}, vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader}, window::{utils::UiExt as _, AppWindow}, }; use super::utils::{self, CharacterGrid}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum VramPalette { #[default] Generic, Bg0, Bg1, Bg2, Bg3, Obj0, Obj1, Obj2, Obj3, } impl VramPalette { pub const fn values() -> [VramPalette; 9] { [ Self::Generic, Self::Bg0, Self::Bg1, Self::Bg2, Self::Bg3, Self::Obj0, Self::Obj1, Self::Obj2, Self::Obj3, ] } pub const fn offset(self) -> Option { match self { Self::Generic => None, Self::Bg0 => Some(0), Self::Bg1 => Some(2), Self::Bg2 => Some(4), Self::Bg3 => Some(6), Self::Obj0 => Some(8), Self::Obj1 => Some(10), Self::Obj2 => Some(12), Self::Obj3 => Some(14), } } } impl Display for VramPalette { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Generic => f.write_str("Generic"), Self::Bg0 => f.write_str("BG 0"), Self::Bg1 => f.write_str("BG 1"), Self::Bg2 => f.write_str("BG 2"), Self::Bg3 => f.write_str("BG 3"), Self::Obj0 => f.write_str("OBJ 0"), Self::Obj1 => f.write_str("OBJ 1"), Self::Obj2 => f.write_str("OBJ 2"), Self::Obj3 => f.write_str("OBJ 3"), } } } pub struct CharacterDataWindow { sim_id: SimId, loader: Arc, brightness: MemoryView, palettes: MemoryView, palette: VramPalette, index: usize, index_str: String, params: VramParams, scale: f32, show_grid: bool, } impl CharacterDataWindow { pub fn new(sim_id: SimId, memory: &mut MemoryMonitor, vram: &mut VramProcessor) -> Self { let renderer = CharDataRenderer::new(sim_id, memory); let ([char, chardata], params) = vram.add(renderer); let loader = VramTextureLoader::new([ ("vram://char".into(), char), ("vram://chardata".into(), chardata), ]); Self { sim_id, loader: Arc::new(loader), brightness: memory.view(sim_id, 0x0005f824, 8), palettes: memory.view(sim_id, 0x0005f860, 16), palette: params.palette, index: params.index, index_str: params.index.to_string(), params, scale: 4.0, show_grid: true, } } fn show_form(&mut self, ui: &mut Ui) { let row_height = ui.spacing().interact_size.y; ui.vertical(|ui| { TableBuilder::new(ui) .column(Column::auto()) .column(Column::remainder()) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.label("Index"); }); row.col(|ui| { ui.number_picker(&mut self.index_str, &mut self.index, 0..2048); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Address"); }); row.col(|ui| { let address = match self.index { 0x000..0x200 => 0x00060000 + self.index * 16, 0x200..0x400 => 0x000e0000 + (self.index - 0x200) * 16, 0x400..0x600 => 0x00160000 + (self.index - 0x400) * 16, 0x600..0x800 => 0x001e0000 + (self.index - 0x600) * 16, _ => unreachable!("can't happen"), }; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut address_str).horizontal_align(Align::Max), ); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Mirror"); }); row.col(|ui| { let mirror = 0x00078000 + (self.index * 16); let mut mirror_str = format!("{mirror:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut mirror_str).horizontal_align(Align::Max), ); }); }); }); let image = Image::new("vram://char") .maintain_aspect_ratio(true) .tint(Color32::RED) .texture_options(TextureOptions::NEAREST); ui.add(image); ui.section("Colors", |ui| { ui.horizontal(|ui| { ui.label("Palette"); ComboBox::from_id_salt("palette") .selected_text(self.palette.to_string()) .width(ui.available_width()) .show_ui(ui, |ui| { for palette in VramPalette::values() { ui.selectable_value( &mut self.palette, palette, palette.to_string(), ); } }); }); TableBuilder::new(ui) .columns(Column::remainder(), 4) .body(|mut body| { let palette = self.load_palette_colors(); body.row(30.0, |mut row| { for color in palette { row.col(|ui| { let rect = ui.available_rect_before_wrap(); let scale = rect.height() / rect.width(); let rect = rect.scale_from_center2(Vec2::new(scale, 1.0)); ui.painter().rect_filled( rect, 0.0, Color32::RED * Color32::from_gray(color), ); }); } }); }); }); ui.section("Display", |ui| { ui.horizontal(|ui| { ui.label("Scale"); ui.spacing_mut().slider_width = ui.available_width(); let slider = Slider::new(&mut self.scale, 1.0..=10.0) .step_by(1.0) .show_value(false); ui.add(slider); }); ui.checkbox(&mut self.show_grid, "Show grid"); }); }); self.params.write(CharDataParams { palette: self.palette, index: self.index, }); } fn load_palette_colors(&self) -> [u8; 4] { let Some(offset) = self.palette.offset() else { return utils::GENERIC_PALETTE; }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.range(0, 8); utils::parse_palette(palette, brts) } fn show_chardata(&mut self, ui: &mut Ui) { let grid = CharacterGrid::new("vram://chardata") .with_scale(self.scale) .with_grid(self.show_grid) .with_selected(self.index); if let Some(selected) = grid.show(ui) { self.index = selected; self.index_str = selected.to_string(); } } } impl AppWindow for CharacterDataWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("chardata-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("Character Data ({})", self.sim_id)) .with_inner_size((640.0, 480.0)) } fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) { ctx.add_texture_loader(self.loader.clone()); } fn show(&mut self, ctx: &Context) { CentralPanel::default().show(ctx, |ui| { ui.horizontal_top(|ui| { StripBuilder::new(ui) .size(Size::relative(0.3)) .size(Size::remainder()) .horizontal(|mut strip| { strip.cell(|ui| { ScrollArea::vertical().show(ui, |ui| self.show_form(ui)); }); strip.cell(|ui| { ScrollArea::both().show(ui, |ui| self.show_chardata(ui)); }); }); }); }); } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] enum CharDataResource { Character { palette: VramPalette, index: usize }, CharacterData { palette: VramPalette }, } #[derive(Clone, Default, PartialEq, Eq)] struct CharDataParams { palette: VramPalette, index: usize, } struct CharDataRenderer { chardata: MemoryView, brightness: MemoryView, palettes: MemoryView, } impl VramRenderer<2> for CharDataRenderer { type Params = CharDataParams; fn sizes(&self) -> [[usize; 2]; 2] { [[8, 8], [16 * 8, 128 * 8]] } fn render(&mut self, params: &Self::Params, image: &mut [VramImage; 2]) { self.render_character(&mut image[0], params.palette, params.index); self.render_character_data(&mut image[1], params.palette); } } impl CharDataRenderer { pub fn new(sim_id: SimId, memory: &mut MemoryMonitor) -> Self { Self { chardata: memory.view(sim_id, 0x00078000, 0x8000), brightness: memory.view(sim_id, 0x0005f824, 8), palettes: memory.view(sim_id, 0x0005f860, 16), } } fn render_character(&self, image: &mut VramImage, palette: VramPalette, index: usize) { if index >= 2048 { return; } let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); let character = chardata.range::(index * 8, 8); for (row, pixels) in character.iter().enumerate() { for col in 0..8 { let char = (pixels >> (col * 2)) & 0x03; image.write((col, row), palette[char as usize]); } } } fn render_character_data(&self, image: &mut VramImage, palette: VramPalette) { let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); for (row, pixels) in chardata.range::(0, 8 * 2048).iter().enumerate() { let char_index = row / 8; let row_index = row % 8; let x = (char_index % 16) * 8; let y = (char_index / 16) * 8 + row_index; for col in 0..8 { let char = (pixels >> (col * 2)) & 0x03; image.write((x + col, y), palette[char as usize]); } } } fn load_palette(&self, palette: VramPalette) -> [u8; 4] { let Some(offset) = palette.offset() else { return utils::GENERIC_PALETTE; }; let palette = self.palettes.borrow().read(offset); let brightnesses = self.brightness.borrow(); let brts = brightnesses.range(0, 8); utils::parse_palette(palette, brts) } }