From 600148c781c394011479cee3a2de5c0575ea7dc3 Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 8 Feb 2025 15:39:12 -0500 Subject: [PATCH] Rerender VRAM graphics every frame (slow in debug) --- src/vram.rs | 81 ++++++++++++++++++++++++++++--------- src/window/vram/bgmap.rs | 66 ++++++++++++++++-------------- src/window/vram/chardata.rs | 68 ++++++++++++++++++------------- 3 files changed, 138 insertions(+), 77 deletions(-) diff --git a/src/vram.rs b/src/vram.rs index 3bf9e07..2394617 100644 --- a/src/vram.rs +++ b/src/vram.rs @@ -1,13 +1,13 @@ use std::{ collections::{hash_map::Entry, HashMap}, hash::Hash, - sync::Mutex, + sync::{Arc, Mutex}, }; use egui::{ epaint::ImageDelta, load::{LoadError, SizedTexture, TextureLoader, TexturePoll}, - ColorImage, Context, TextureHandle, TextureOptions, + Color32, ColorImage, Context, TextureHandle, TextureOptions, }; use serde::{Deserialize, Serialize}; @@ -27,21 +27,60 @@ impl Deserialize<'a> + PartialEq + Eq + Hash> VramResourc } } +pub enum VramImage { + Unchanged(Arc), + 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> { + 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 { type Resource: VramResource; fn id(&self) -> &str; - fn add(&self, resource: &Self::Resource) -> Option; + fn add(&self, resource: &Self::Resource) -> Option; fn update<'a>( &'a self, - resources: impl Iterator, - ) -> Vec<(&'a Self::Resource, ColorImage)>; + resources: impl Iterator, + ); } pub struct VramTextureLoader { id: String, loader: Mutex, - cache: Mutex>, + cache: Mutex>, } impl VramTextureLoader { @@ -76,26 +115,30 @@ impl TextureLoader for VramTextureLoader { } let loader = self.loader.lock().unwrap(); let mut cache = self.cache.lock().unwrap(); - for (resource, updated_image) in loader.update(cache.keys()) { - if let Some(handle) = cache.get(resource) { - let delta = ImageDelta::full(updated_image, TextureOptions::NEAREST); + let resources = cache.iter_mut().map(|(k, v)| (k, &mut v.1)); + loader.update(resources); + 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); } } match cache.entry(resource) { Entry::Occupied(entry) => { - let texture = SizedTexture::from_handle(entry.get()); + let texture = SizedTexture::from_handle(&entry.get().0); Ok(TexturePoll::Ready { texture }) } Entry::Vacant(entry) => { - if let Some(image) = loader.add(entry.key()) { - let handle = - entry.insert(ctx.load_texture(uri, image, TextureOptions::NEAREST)); - let texture = SizedTexture::from_handle(handle); - Ok(TexturePoll::Ready { texture }) - } else { - Err(LoadError::Loading("could not load texture".into())) - } + let Some(mut image) = loader.add(entry.key()) else { + return Err(LoadError::Loading("could not load texture".into())); + }; + let Some(data) = image.take() else { + return 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 TextureLoader for VramTextureLoader { .lock() .unwrap() .values() - .map(|h| h.byte_size()) + .map(|h| h.0.byte_size()) .sum() } } diff --git a/src/window/vram/bgmap.rs b/src/window/vram/bgmap.rs index 2a4962d..32ed66f 100644 --- a/src/window/vram/bgmap.rs +++ b/src/window/vram/bgmap.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use egui::{ - Align, CentralPanel, Checkbox, Color32, ColorImage, Context, Image, ScrollArea, Slider, - TextEdit, TextureOptions, Ui, ViewportBuilder, ViewportId, + Align, CentralPanel, Checkbox, Color32, Context, Image, ScrollArea, Slider, TextEdit, + TextureOptions, Ui, ViewportBuilder, ViewportId, }; use egui_extras::{Column, Size, StripBuilder, TableBuilder}; use serde::{Deserialize, Serialize}; @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, memory::{MemoryMonitor, MemoryView}, - vram::{VramImageLoader, VramResource as _, VramTextureLoader}, + vram::{VramImage, VramImageLoader, VramResource as _, VramTextureLoader}, window::{utils::UiExt, AppWindow}, }; @@ -242,7 +242,7 @@ impl BgMapLoader { } } - fn load_bgmap(&self, bgmap_index: usize, generic_palette: bool) -> Option { + fn update_bgmap(&self, image: &mut VramImage, bgmap_index: usize, generic_palette: bool) { let chardata = self.chardata.borrow(); let bgmaps = self.bgmaps.borrow(); let brightness = self.brightness.borrow(); @@ -265,7 +265,6 @@ impl BgMapLoader { ] }; - let mut data = vec![0u8; 512 * 512]; for (i, cell) in bgmaps .range::(bgmap_index * 4096, 4096) .iter() @@ -275,21 +274,17 @@ impl BgMapLoader { let char = chardata.range::(char_index * 8, 8); let palette = &colors[palette_index]; - let mut target_idx = (i % 64) * 8 + (i / 64) * 8 * 512; for row in 0..8 { - let dests = &mut data[target_idx..target_idx + 8]; - let pixels = self.read_char_row(char, hflip, vflip, row); - for (dest, pixel) in dests.iter_mut().zip(pixels) { - *dest = palette[pixel as usize]; + let y = row + (i / 64) * 8; + for (col, pixel) in self.read_char_row(char, hflip, vflip, row).enumerate() { + let x = col + (i % 64) * 8; + 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 { + fn update_bgmap_cell(&self, image: &mut VramImage, index: usize, generic_palette: bool) { let chardata = self.chardata.borrow(); let bgmaps = self.bgmaps.borrow(); let brightness = self.brightness.borrow(); @@ -297,7 +292,6 @@ impl BgMapLoader { let brts = brightness.range::(0, 8); - let mut data = vec![0u8; 8 * 8]; let cell = bgmaps.read::(index); let (char_index, vflip, hflip, palette_index) = parse_cell(cell); @@ -308,17 +302,11 @@ impl BgMapLoader { parse_palette(palettes.read(palette_index * 2), brts) }; - let mut target_idx = 0; for row in 0..8 { - let dests = &mut data[target_idx..target_idx + 8]; - let pixels = self.read_char_row(char, hflip, vflip, row); - for (dest, pixel) in dests.iter_mut().zip(pixels) { - *dest = palette[pixel as usize]; + for (col, pixel) in self.read_char_row(char, hflip, vflip, row).enumerate() { + image.write((col, row), palette[pixel as usize]); } - target_idx += 8; } - - Some(ColorImage::from_gray([8, 8], &data)) } fn read_char_row( @@ -343,24 +331,42 @@ impl VramImageLoader for BgMapLoader { concat!(module_path!(), "::BgMapLoader") } - fn add(&self, resource: &Self::Resource) -> Option { + fn add(&self, resource: &Self::Resource) -> Option { match resource { BgMapResource::BgMap { index, 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 { index, 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>( &'a self, - resources: impl Iterator, - ) -> Vec<(&'a Self::Resource, ColorImage)> { - let _ = resources; - vec![] + resources: impl Iterator, + ) { + for (resource, image) in resources { + 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), + } + } } } diff --git a/src/window/vram/chardata.rs b/src/window/vram/chardata.rs index a8eb3f7..504a6eb 100644 --- a/src/window/vram/chardata.rs +++ b/src/window/vram/chardata.rs @@ -1,8 +1,8 @@ use std::{fmt::Display, sync::Arc}; use egui::{ - Align, CentralPanel, Color32, ColorImage, ComboBox, Context, Image, ScrollArea, Slider, - TextEdit, TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId, + 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}; @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::{ emulator::SimId, memory::{MemoryMonitor, MemoryView}, - vram::{VramImageLoader, VramResource as _, VramTextureLoader}, + vram::{VramImage, VramImageLoader, VramResource as _, VramTextureLoader}, window::{utils::UiExt as _, AppWindow}, }; @@ -294,38 +294,34 @@ impl CharDataLoader { } } - fn load_character(&self, palette: VramPalette, index: usize) -> Option { + fn update_character(&self, image: &mut VramImage, palette: VramPalette, index: usize) { if index >= 2048 { - return None; + return; } let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); let character = chardata.range::(index * 8, 8); - let mut buffer = Vec::with_capacity(8 * 8); - for row in character { - for offset in (0..16).step_by(2) { - let char = (row >> offset) & 0x3; - buffer.push(palette[char as usize]); + 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]); } } - Some(ColorImage::from_gray([8, 8], &buffer)) } - fn load_character_data(&self, palette: VramPalette) -> Option { + fn update_character_data(&self, image: &mut VramImage, palette: VramPalette) { let palette = self.load_palette(palette); let chardata = self.chardata.borrow(); - let mut buffer = vec![0; 8 * 8 * 2048]; - for (i, row) in chardata.range::(0, 8 * 2048).iter().enumerate() { - let bytes = - [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; + 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; - let write_index = (y * 16 * 8) + x; - buffer[write_index..write_index + 8].copy_from_slice(&bytes); + for col in 0..8 { + 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] { @@ -346,18 +342,34 @@ impl VramImageLoader for CharDataLoader { concat!(module_path!(), "::CharDataLoader") } - fn add(&self, resource: &Self::Resource) -> Option { + fn add(&self, resource: &Self::Resource) -> Option { match resource { - CharDataResource::Character { palette, index } => self.load_character(*palette, *index), - CharDataResource::CharacterData { palette } => self.load_character_data(*palette), + CharDataResource::Character { palette, index } => { + 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>( &'a self, - resources: impl Iterator, - ) -> Vec<(&'a Self::Resource, ColorImage)> { - let _ = resources; - vec![] + resources: impl Iterator, + ) { + for (resource, image) in resources { + match resource { + CharDataResource::Character { palette, index } => { + self.update_character(image, *palette, *index) + } + CharDataResource::CharacterData { palette } => { + self.update_character_data(image, *palette) + } + } + } } }