Files
lemur/src/window/vip/framebuffer.rs
T

278 lines
8.9 KiB
Rust

use egui::{
Align, CentralPanel, Color32, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui,
ViewportBuilder,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use serde::{Deserialize, Serialize};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView},
window::{
AppWindow,
utils::{NumberEdit, UiData, UiExt as _},
},
};
use super::utils;
#[derive(Clone, PartialEq, Serialize, Deserialize)]
struct State {
index: usize,
left: bool,
right: bool,
generic_palette: bool,
scale: f32,
}
impl Default for State {
fn default() -> Self {
Self {
index: 0,
left: true,
right: true,
generic_palette: false,
scale: 2.0,
}
}
}
pub struct FrameBufferWindow {
sim_id: SimId,
params: ImageParams<FrameBufferParams>,
state: UiData<State>,
}
impl FrameBufferWindow {
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self {
let state: UiData<State> = UiData::new();
let renderer = FrameBufferRenderer::new(sim_id, memory);
let params = images.add(
sim_id,
renderer,
FrameBufferParams {
index: state.index,
left: state.left,
right: state.right,
generic_palette: state.generic_palette,
left_color: Color32::from_rgb(0xff, 0x00, 0x00),
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
},
);
Self {
sim_id,
params,
state,
}
}
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.state.index).range(0..2));
});
});
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label("Left");
});
row.col(|ui| {
let address = self.state.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.state.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.state.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.state.left, "Left");
});
row.col(|ui| {
ui.checkbox(&mut self.state.right, "Right");
});
});
});
ui.checkbox(&mut self.state.generic_palette, "Generic colors");
});
});
self.params.write(FrameBufferParams {
index: self.state.index,
left: self.state.left,
right: self.state.right,
generic_palette: self.state.generic_palette,
..*self.params
});
}
fn show_buffers(&mut self, ui: &mut Ui) {
let image = Image::new(self.image_url("buffer"))
.fit_to_original_size(self.state.scale)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
}
}
impl AppWindow for FrameBufferWindow {
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 show(&mut self, ui: &mut Ui) {
self.state.load(ui);
CentralPanel::default().show_inside(ui, |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));
});
});
});
});
self.state.save(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 names(&self) -> [&str; 1] {
["buffer"]
}
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].borrow();
let right_buffer = self.buffers[2 + params.index].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::<u8>(0, 0x6000);
let right_cols = right_buffer.range::<u8>(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);
}
}
}
}