use std::sync::Arc; use egui::{ Align, CentralPanel, Color32, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use crate::{ emulator::SimId, images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader}, memory::{MemoryClient, MemoryView}, window::{ utils::{NumberEdit, UiExt as _}, AppWindow, }, }; use super::utils; pub struct FrameBufferWindow { sim_id: SimId, loader: Arc, index: usize, left: bool, right: bool, generic_palette: bool, params: ImageParams, scale: f32, } impl FrameBufferWindow { pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self { let initial_params = FrameBufferParams { index: 0, left: true, right: true, generic_palette: false, left_color: Color32::from_rgb(0xff, 0x00, 0x00), right_color: Color32::from_rgb(0x00, 0xc6, 0xf0), }; let renderer = FrameBufferRenderer::new(sim_id, memory); let ([buffer], params) = images.add(renderer, initial_params); let loader = ImageTextureLoader::new([("vip://buffer".into(), buffer)]); Self { sim_id, loader: Arc::new(loader), index: params.index, left: params.left, right: params.right, generic_palette: params.generic_palette, params, scale: 2.0, } } 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.add(NumberEdit::new(&mut self.index).range(0..2)); }); }); body.row(row_height, |mut row| { row.col(|ui| { ui.label("Left"); }); row.col(|ui| { let address = self.index * 0x00008000; 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("Right"); }); row.col(|ui| { let address = self.index * 0x00008000 + 0x00010000; let mut address_str = format!("{address:08x}"); ui.add_enabled( false, TextEdit::singleline(&mut address_str).horizontal_align(Align::Max), ); }); }); }); 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); }); TableBuilder::new(ui) .columns(Column::remainder(), 2) .body(|mut body| { body.row(row_height, |mut row| { row.col(|ui| { ui.checkbox(&mut self.left, "Left"); }); row.col(|ui| { ui.checkbox(&mut self.right, "Right"); }); }); }); ui.checkbox(&mut self.generic_palette, "Generic colors"); }); }); self.params.write(FrameBufferParams { index: self.index, left: self.left, right: self.right, generic_palette: self.generic_palette, ..*self.params }); } fn show_buffers(&mut self, ui: &mut Ui) { let image = Image::new("vip://buffer") .fit_to_original_size(self.scale) .texture_options(TextureOptions::NEAREST); ui.add(image); } } impl AppWindow for FrameBufferWindow { fn viewport_id(&self) -> ViewportId { ViewportId::from_hash_of(format!("framebuffer-{}", self.sim_id)) } fn sim_id(&self) -> SimId { self.sim_id } fn initial_viewport(&self) -> ViewportBuilder { ViewportBuilder::default() .with_title(format!("Frame Buffers ({})", 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).at_most(200.0)) .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_buffers(ui)); }); }); }); }); } } #[derive(Clone, PartialEq, Eq)] struct FrameBufferParams { index: usize, left: bool, right: bool, generic_palette: bool, left_color: Color32, right_color: Color32, } struct FrameBufferRenderer { buffers: [MemoryView; 4], brightness: MemoryView, } impl FrameBufferRenderer { fn new(sim_id: SimId, memory: &MemoryClient) -> Self { Self { buffers: [ memory.watch(sim_id, 0x00000000, 0x6000), memory.watch(sim_id, 0x00008000, 0x6000), memory.watch(sim_id, 0x00010000, 0x6000), memory.watch(sim_id, 0x00018000, 0x6000), ], brightness: memory.watch(sim_id, 0x0005f824, 8), } } } impl ImageRenderer<1> for FrameBufferRenderer { type Params = FrameBufferParams; fn sizes(&self) -> [[usize; 2]; 1] { [[384, 224]] } fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) { let image = &mut images[0]; let left_buffer = self.buffers[params.index * 2].borrow(); let right_buffer = self.buffers[params.index * 2 + 1].borrow(); let colors = if params.generic_palette { [ utils::generic_palette(params.left_color), utils::generic_palette(params.right_color), ] } else { let brts = self.brightness.borrow().read::<[u8; 8]>(0); let shades = utils::parse_shades(&brts); [ shades.map(|s| utils::shade(s, params.left_color)), shades.map(|s| utils::shade(s, params.right_color)), ] }; let left_cols = left_buffer.range::(0, 0x6000); let right_cols = right_buffer.range::(0, 0x6000); for (index, (left, right)) in left_cols.zip(right_cols).enumerate() { let top = (index % 64) * 4; if top >= 224 { continue; } let pixels = [0, 2, 4, 6].map(|i| { let left = if params.left { colors[0][(left >> i) as usize & 0x3] } else { Color32::BLACK }; let right = if params.right { colors[1][(right >> i) as usize & 0x3] } else { Color32::BLACK }; Color32::from_rgb( left.r() + right.r(), left.g() + right.g(), left.b() + right.b(), ) }); let x = index / 64; for (i, pixel) in pixels.into_iter().enumerate() { let y = top + i; image.write((x, y), pixel); } } } }