Rerender VRAM graphics every frame (slow in debug)

This commit is contained in:
Simon Gellis 2025-02-08 15:39:12 -05:00
parent 4601f1b52f
commit 600148c781
3 changed files with 138 additions and 77 deletions

View File

@ -1,13 +1,13 @@
use std::{ use std::{
collections::{hash_map::Entry, HashMap}, collections::{hash_map::Entry, HashMap},
hash::Hash, hash::Hash,
sync::Mutex, sync::{Arc, Mutex},
}; };
use egui::{ use egui::{
epaint::ImageDelta, epaint::ImageDelta,
load::{LoadError, SizedTexture, TextureLoader, TexturePoll}, load::{LoadError, SizedTexture, TextureLoader, TexturePoll},
ColorImage, Context, TextureHandle, TextureOptions, Color32, ColorImage, Context, TextureHandle, TextureOptions,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -27,21 +27,60 @@ impl<T: Serialize + for<'a> Deserialize<'a> + PartialEq + Eq + Hash> VramResourc
} }
} }
pub enum VramImage {
Unchanged(Arc<ColorImage>),
Changed(ColorImage),
}
impl VramImage {
pub fn new(width: usize, height: usize) -> Self {
Self::Changed(ColorImage::new([width, height], Color32::BLACK))
}
pub fn write(&mut self, coords: (usize, usize), shade: u8) {
match self {
Self::Unchanged(image) => {
let value = image[coords];
if value.r() == shade {
return;
};
let mut new_image = ColorImage::clone(image);
new_image[coords] = Color32::from_gray(shade);
*self = Self::Changed(new_image);
}
Self::Changed(image) => {
image[coords] = Color32::from_gray(shade);
}
}
}
pub fn take(&mut self) -> Option<Arc<ColorImage>> {
match self {
Self::Unchanged(_) => None,
Self::Changed(image) => {
let arced = Arc::new(std::mem::take(image));
*self = Self::Unchanged(arced.clone());
Some(arced)
}
}
}
}
pub trait VramImageLoader { pub trait VramImageLoader {
type Resource: VramResource; type Resource: VramResource;
fn id(&self) -> &str; fn id(&self) -> &str;
fn add(&self, resource: &Self::Resource) -> Option<ColorImage>; fn add(&self, resource: &Self::Resource) -> Option<VramImage>;
fn update<'a>( fn update<'a>(
&'a self, &'a self,
resources: impl Iterator<Item = &'a Self::Resource>, resources: impl Iterator<Item = (&'a Self::Resource, &'a mut VramImage)>,
) -> Vec<(&'a Self::Resource, ColorImage)>; );
} }
pub struct VramTextureLoader<T: VramImageLoader> { pub struct VramTextureLoader<T: VramImageLoader> {
id: String, id: String,
loader: Mutex<T>, loader: Mutex<T>,
cache: Mutex<HashMap<T::Resource, TextureHandle>>, cache: Mutex<HashMap<T::Resource, (TextureHandle, VramImage)>>,
} }
impl<T: VramImageLoader> VramTextureLoader<T> { impl<T: VramImageLoader> VramTextureLoader<T> {
@ -76,26 +115,30 @@ impl<T: VramImageLoader> TextureLoader for VramTextureLoader<T> {
} }
let loader = self.loader.lock().unwrap(); let loader = self.loader.lock().unwrap();
let mut cache = self.cache.lock().unwrap(); let mut cache = self.cache.lock().unwrap();
for (resource, updated_image) in loader.update(cache.keys()) { let resources = cache.iter_mut().map(|(k, v)| (k, &mut v.1));
if let Some(handle) = cache.get(resource) { loader.update(resources);
let delta = ImageDelta::full(updated_image, TextureOptions::NEAREST); for (handle, image) in cache.values_mut() {
if let Some(data) = image.take() {
let delta = ImageDelta::full(data, TextureOptions::NEAREST);
ctx.tex_manager().write().set(handle.id(), delta); ctx.tex_manager().write().set(handle.id(), delta);
} }
} }
match cache.entry(resource) { match cache.entry(resource) {
Entry::Occupied(entry) => { Entry::Occupied(entry) => {
let texture = SizedTexture::from_handle(entry.get()); let texture = SizedTexture::from_handle(&entry.get().0);
Ok(TexturePoll::Ready { texture }) Ok(TexturePoll::Ready { texture })
} }
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
if let Some(image) = loader.add(entry.key()) { let Some(mut image) = loader.add(entry.key()) else {
let handle = return Err(LoadError::Loading("could not load texture".into()));
entry.insert(ctx.load_texture(uri, image, TextureOptions::NEAREST)); };
let texture = SizedTexture::from_handle(handle); let Some(data) = image.take() else {
Ok(TexturePoll::Ready { texture }) return Err(LoadError::Loading("could not load texture".into()));
} else { };
Err(LoadError::Loading("could not load texture".into())) let handle = ctx.load_texture(uri, data, TextureOptions::NEAREST);
} let (handle, _) = entry.insert((handle, image));
let texture = SizedTexture::from_handle(handle);
Ok(TexturePoll::Ready { texture })
} }
} }
} }
@ -115,7 +158,7 @@ impl<T: VramImageLoader> TextureLoader for VramTextureLoader<T> {
.lock() .lock()
.unwrap() .unwrap()
.values() .values()
.map(|h| h.byte_size()) .map(|h| h.0.byte_size())
.sum() .sum()
} }
} }

View File

@ -1,8 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use egui::{ use egui::{
Align, CentralPanel, Checkbox, Color32, ColorImage, Context, Image, ScrollArea, Slider, Align, CentralPanel, Checkbox, Color32, Context, Image, ScrollArea, Slider, TextEdit,
TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, TextureOptions, Ui, ViewportBuilder, ViewportId,
}; };
use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
emulator::SimId, emulator::SimId,
memory::{MemoryMonitor, MemoryView}, memory::{MemoryMonitor, MemoryView},
vram::{VramImageLoader, VramResource as _, VramTextureLoader}, vram::{VramImage, VramImageLoader, VramResource as _, VramTextureLoader},
window::{utils::UiExt, AppWindow}, window::{utils::UiExt, AppWindow},
}; };
@ -242,7 +242,7 @@ impl BgMapLoader {
} }
} }
fn load_bgmap(&self, bgmap_index: usize, generic_palette: bool) -> Option<ColorImage> { fn update_bgmap(&self, image: &mut VramImage, bgmap_index: usize, generic_palette: bool) {
let chardata = self.chardata.borrow(); let chardata = self.chardata.borrow();
let bgmaps = self.bgmaps.borrow(); let bgmaps = self.bgmaps.borrow();
let brightness = self.brightness.borrow(); let brightness = self.brightness.borrow();
@ -265,7 +265,6 @@ impl BgMapLoader {
] ]
}; };
let mut data = vec![0u8; 512 * 512];
for (i, cell) in bgmaps for (i, cell) in bgmaps
.range::<u16>(bgmap_index * 4096, 4096) .range::<u16>(bgmap_index * 4096, 4096)
.iter() .iter()
@ -275,21 +274,17 @@ impl BgMapLoader {
let char = chardata.range::<u16>(char_index * 8, 8); let char = chardata.range::<u16>(char_index * 8, 8);
let palette = &colors[palette_index]; let palette = &colors[palette_index];
let mut target_idx = (i % 64) * 8 + (i / 64) * 8 * 512;
for row in 0..8 { for row in 0..8 {
let dests = &mut data[target_idx..target_idx + 8]; let y = row + (i / 64) * 8;
let pixels = self.read_char_row(char, hflip, vflip, row); for (col, pixel) in self.read_char_row(char, hflip, vflip, row).enumerate() {
for (dest, pixel) in dests.iter_mut().zip(pixels) { let x = col + (i % 64) * 8;
*dest = palette[pixel as usize]; image.write((x, y), palette[pixel as usize]);
} }
target_idx += 512;
} }
} }
Some(ColorImage::from_gray([512, 512], &data))
} }
fn load_bgmap_cell(&self, index: usize, generic_palette: bool) -> Option<ColorImage> { fn update_bgmap_cell(&self, image: &mut VramImage, index: usize, generic_palette: bool) {
let chardata = self.chardata.borrow(); let chardata = self.chardata.borrow();
let bgmaps = self.bgmaps.borrow(); let bgmaps = self.bgmaps.borrow();
let brightness = self.brightness.borrow(); let brightness = self.brightness.borrow();
@ -297,7 +292,6 @@ impl BgMapLoader {
let brts = brightness.range::<u8>(0, 8); let brts = brightness.range::<u8>(0, 8);
let mut data = vec![0u8; 8 * 8];
let cell = bgmaps.read::<u16>(index); let cell = bgmaps.read::<u16>(index);
let (char_index, vflip, hflip, palette_index) = parse_cell(cell); let (char_index, vflip, hflip, palette_index) = parse_cell(cell);
@ -308,17 +302,11 @@ impl BgMapLoader {
parse_palette(palettes.read(palette_index * 2), brts) parse_palette(palettes.read(palette_index * 2), brts)
}; };
let mut target_idx = 0;
for row in 0..8 { for row in 0..8 {
let dests = &mut data[target_idx..target_idx + 8]; for (col, pixel) in self.read_char_row(char, hflip, vflip, row).enumerate() {
let pixels = self.read_char_row(char, hflip, vflip, row); image.write((col, row), palette[pixel as usize]);
for (dest, pixel) in dests.iter_mut().zip(pixels) {
*dest = palette[pixel as usize];
} }
target_idx += 8;
} }
Some(ColorImage::from_gray([8, 8], &data))
} }
fn read_char_row( fn read_char_row(
@ -343,24 +331,42 @@ impl VramImageLoader for BgMapLoader {
concat!(module_path!(), "::BgMapLoader") concat!(module_path!(), "::BgMapLoader")
} }
fn add(&self, resource: &Self::Resource) -> Option<ColorImage> { fn add(&self, resource: &Self::Resource) -> Option<VramImage> {
match resource { match resource {
BgMapResource::BgMap { BgMapResource::BgMap {
index, index,
generic_palette, generic_palette,
} => self.load_bgmap(*index, *generic_palette), } => {
let mut image = VramImage::new(64 * 8, 64 * 8);
self.update_bgmap(&mut image, *index, *generic_palette);
Some(image)
}
BgMapResource::Cell { BgMapResource::Cell {
index, index,
generic_palette, generic_palette,
} => self.load_bgmap_cell(*index, *generic_palette), } => {
let mut image = VramImage::new(8, 8);
self.update_bgmap_cell(&mut image, *index, *generic_palette);
Some(image)
}
} }
} }
fn update<'a>( fn update<'a>(
&'a self, &'a self,
resources: impl Iterator<Item = &'a Self::Resource>, resources: impl Iterator<Item = (&'a Self::Resource, &'a mut VramImage)>,
) -> Vec<(&'a Self::Resource, ColorImage)> { ) {
let _ = resources; for (resource, image) in resources {
vec![] match resource {
BgMapResource::BgMap {
index,
generic_palette,
} => self.update_bgmap(image, *index, *generic_palette),
BgMapResource::Cell {
index,
generic_palette,
} => self.update_bgmap_cell(image, *index, *generic_palette),
}
}
} }
} }

View File

@ -1,8 +1,8 @@
use std::{fmt::Display, sync::Arc}; use std::{fmt::Display, sync::Arc};
use egui::{ use egui::{
Align, CentralPanel, Color32, ColorImage, ComboBox, Context, Image, ScrollArea, Slider, Align, CentralPanel, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
TextEdit, TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId, TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId,
}; };
use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
emulator::SimId, emulator::SimId,
memory::{MemoryMonitor, MemoryView}, memory::{MemoryMonitor, MemoryView},
vram::{VramImageLoader, VramResource as _, VramTextureLoader}, vram::{VramImage, VramImageLoader, VramResource as _, VramTextureLoader},
window::{utils::UiExt as _, AppWindow}, window::{utils::UiExt as _, AppWindow},
}; };
@ -294,38 +294,34 @@ impl CharDataLoader {
} }
} }
fn load_character(&self, palette: VramPalette, index: usize) -> Option<ColorImage> { fn update_character(&self, image: &mut VramImage, palette: VramPalette, index: usize) {
if index >= 2048 { if index >= 2048 {
return None; return;
} }
let palette = self.load_palette(palette); let palette = self.load_palette(palette);
let chardata = self.chardata.borrow(); let chardata = self.chardata.borrow();
let character = chardata.range::<u16>(index * 8, 8); let character = chardata.range::<u16>(index * 8, 8);
let mut buffer = Vec::with_capacity(8 * 8); for (row, pixels) in character.iter().enumerate() {
for row in character { for col in 0..8 {
for offset in (0..16).step_by(2) { let char = (pixels >> (col * 2)) & 0x03;
let char = (row >> offset) & 0x3; image.write((col, row), palette[char as usize]);
buffer.push(palette[char as usize]);
} }
} }
Some(ColorImage::from_gray([8, 8], &buffer))
} }
fn load_character_data(&self, palette: VramPalette) -> Option<ColorImage> { fn update_character_data(&self, image: &mut VramImage, palette: VramPalette) {
let palette = self.load_palette(palette); let palette = self.load_palette(palette);
let chardata = self.chardata.borrow(); let chardata = self.chardata.borrow();
let mut buffer = vec![0; 8 * 8 * 2048]; for (row, pixels) in chardata.range::<u16>(0, 8 * 2048).iter().enumerate() {
for (i, row) in chardata.range::<u16>(0, 8 * 2048).iter().enumerate() { let char_index = row / 8;
let bytes = let row_index = row % 8;
[0, 2, 4, 6, 8, 10, 12, 14].map(|off| palette[(*row as usize >> off) & 0x3]);
let char_index = i / 8;
let row_index = i % 8;
let x = (char_index % 16) * 8; let x = (char_index % 16) * 8;
let y = (char_index / 16) * 8 + row_index; let y = (char_index / 16) * 8 + row_index;
let write_index = (y * 16 * 8) + x; for col in 0..8 {
buffer[write_index..write_index + 8].copy_from_slice(&bytes); let char = (pixels >> (col * 2)) & 0x03;
image.write((x + col, y), palette[char as usize]);
}
} }
Some(ColorImage::from_gray([8 * 16, 8 * 128], &buffer))
} }
fn load_palette(&self, palette: VramPalette) -> [u8; 4] { fn load_palette(&self, palette: VramPalette) -> [u8; 4] {
@ -346,18 +342,34 @@ impl VramImageLoader for CharDataLoader {
concat!(module_path!(), "::CharDataLoader") concat!(module_path!(), "::CharDataLoader")
} }
fn add(&self, resource: &Self::Resource) -> Option<ColorImage> { fn add(&self, resource: &Self::Resource) -> Option<VramImage> {
match resource { match resource {
CharDataResource::Character { palette, index } => self.load_character(*palette, *index), CharDataResource::Character { palette, index } => {
CharDataResource::CharacterData { palette } => self.load_character_data(*palette), let mut image = VramImage::new(8, 8);
self.update_character(&mut image, *palette, *index);
Some(image)
}
CharDataResource::CharacterData { palette } => {
let mut image = VramImage::new(8 * 16, 8 * 128);
self.update_character_data(&mut image, *palette);
Some(image)
}
} }
} }
fn update<'a>( fn update<'a>(
&'a self, &'a self,
resources: impl Iterator<Item = &'a Self::Resource>, resources: impl Iterator<Item = (&'a Self::Resource, &'a mut VramImage)>,
) -> Vec<(&'a Self::Resource, ColorImage)> { ) {
let _ = resources; for (resource, image) in resources {
vec![] match resource {
CharDataResource::Character { palette, index } => {
self.update_character(image, *palette, *index)
}
CharDataResource::CharacterData { palette } => {
self.update_character_data(image, *palette)
}
}
}
} }
} }