VIP inspection tooling #4
			
				
			
		
		
		
	| 
						 | 
					@ -7,7 +7,7 @@ use std::{
 | 
				
			||||||
    sync::{
 | 
					    sync::{
 | 
				
			||||||
        atomic::{AtomicBool, Ordering},
 | 
					        atomic::{AtomicBool, Ordering},
 | 
				
			||||||
        mpsc::{self, RecvError, TryRecvError},
 | 
					        mpsc::{self, RecvError, TryRecvError},
 | 
				
			||||||
        Arc,
 | 
					        Arc, Weak,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,11 @@ use bytemuck::NoUninit;
 | 
				
			||||||
use egui_toast::{Toast, ToastKind, ToastOptions};
 | 
					use egui_toast::{Toast, ToastKind, ToastOptions};
 | 
				
			||||||
use tracing::{error, warn};
 | 
					use tracing::{error, warn};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{audio::Audio, graphics::TextureSink};
 | 
					use crate::{
 | 
				
			||||||
 | 
					    audio::Audio,
 | 
				
			||||||
 | 
					    graphics::TextureSink,
 | 
				
			||||||
 | 
					    memory::{MemoryRange, MemoryRegion},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE};
 | 
					use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE};
 | 
				
			||||||
pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
 | 
					pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,8 +169,10 @@ pub struct Emulator {
 | 
				
			||||||
    renderers: HashMap<SimId, TextureSink>,
 | 
					    renderers: HashMap<SimId, TextureSink>,
 | 
				
			||||||
    messages: HashMap<SimId, mpsc::Sender<Toast>>,
 | 
					    messages: HashMap<SimId, mpsc::Sender<Toast>>,
 | 
				
			||||||
    debuggers: HashMap<SimId, DebugInfo>,
 | 
					    debuggers: HashMap<SimId, DebugInfo>,
 | 
				
			||||||
 | 
					    watched_regions: HashMap<MemoryRange, Weak<MemoryRegion>>,
 | 
				
			||||||
    eye_contents: Vec<u8>,
 | 
					    eye_contents: Vec<u8>,
 | 
				
			||||||
    audio_samples: Vec<f32>,
 | 
					    audio_samples: Vec<f32>,
 | 
				
			||||||
 | 
					    buffer: Vec<u8>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Emulator {
 | 
					impl Emulator {
 | 
				
			||||||
| 
						 | 
					@ -189,8 +195,10 @@ impl Emulator {
 | 
				
			||||||
            renderers: HashMap::new(),
 | 
					            renderers: HashMap::new(),
 | 
				
			||||||
            messages: HashMap::new(),
 | 
					            messages: HashMap::new(),
 | 
				
			||||||
            debuggers: HashMap::new(),
 | 
					            debuggers: HashMap::new(),
 | 
				
			||||||
 | 
					            watched_regions: HashMap::new(),
 | 
				
			||||||
            eye_contents: vec![0u8; 384 * 224 * 2],
 | 
					            eye_contents: vec![0u8; 384 * 224 * 2],
 | 
				
			||||||
            audio_samples: Vec::with_capacity(EXPECTED_FRAME_SIZE),
 | 
					            audio_samples: Vec::with_capacity(EXPECTED_FRAME_SIZE),
 | 
				
			||||||
 | 
					            buffer: vec![],
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -367,6 +375,10 @@ impl Emulator {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn watch_memory(&mut self, range: MemoryRange, region: Weak<MemoryRegion>) {
 | 
				
			||||||
 | 
					        self.watched_regions.insert(range, region);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn run(&mut self) {
 | 
					    pub fn run(&mut self) {
 | 
				
			||||||
        loop {
 | 
					        loop {
 | 
				
			||||||
            let idle = self.tick();
 | 
					            let idle = self.tick();
 | 
				
			||||||
| 
						 | 
					@ -391,6 +403,18 @@ impl Emulator {
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            self.watched_regions.retain(|range, region| {
 | 
				
			||||||
 | 
					                let Some(region) = region.upgrade() else {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                let Some(sim) = self.sims.get_mut(range.sim.to_index()) else {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                self.buffer.clear();
 | 
				
			||||||
 | 
					                sim.read_memory(range.start, range.length, &mut self.buffer);
 | 
				
			||||||
 | 
					                region.update(&self.buffer);
 | 
				
			||||||
 | 
					                true
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -557,6 +581,9 @@ impl Emulator {
 | 
				
			||||||
                sim.write_memory(start, &buffer);
 | 
					                sim.write_memory(start, &buffer);
 | 
				
			||||||
                let _ = done.send(buffer);
 | 
					                let _ = done.send(buffer);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            EmulatorCommand::WatchMemory(range, region) => {
 | 
				
			||||||
 | 
					                self.watch_memory(range, region);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            EmulatorCommand::AddBreakpoint(sim_id, address) => {
 | 
					            EmulatorCommand::AddBreakpoint(sim_id, address) => {
 | 
				
			||||||
                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
					                let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
| 
						 | 
					@ -647,6 +674,7 @@ pub enum EmulatorCommand {
 | 
				
			||||||
    WriteRegister(SimId, VBRegister, u32),
 | 
					    WriteRegister(SimId, VBRegister, u32),
 | 
				
			||||||
    ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
 | 
					    ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
 | 
				
			||||||
    WriteMemory(SimId, u32, Vec<u8>, oneshot::Sender<Vec<u8>>),
 | 
					    WriteMemory(SimId, u32, Vec<u8>, oneshot::Sender<Vec<u8>>),
 | 
				
			||||||
 | 
					    WatchMemory(MemoryRange, Weak<MemoryRegion>),
 | 
				
			||||||
    AddBreakpoint(SimId, u32),
 | 
					    AddBreakpoint(SimId, u32),
 | 
				
			||||||
    RemoveBreakpoint(SimId, u32),
 | 
					    RemoveBreakpoint(SimId, u32),
 | 
				
			||||||
    AddWatchpoint(SimId, u32, usize, VBWatchpointType),
 | 
					    AddWatchpoint(SimId, u32, usize, VBWatchpointType),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										143
									
								
								src/memory.rs
								
								
								
								
							
							
						
						
									
										143
									
								
								src/memory.rs
								
								
								
								
							| 
						 | 
					@ -1,15 +1,18 @@
 | 
				
			||||||
use std::{
 | 
					use std::{
 | 
				
			||||||
    collections::HashMap,
 | 
					    collections::HashMap,
 | 
				
			||||||
    sync::{Arc, Mutex, MutexGuard},
 | 
					    fmt::Debug,
 | 
				
			||||||
 | 
					    sync::{atomic::AtomicU64, Arc, RwLock, RwLockReadGuard, TryLockError, Weak},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use bytemuck::BoxBytes;
 | 
					use bytemuck::BoxBytes;
 | 
				
			||||||
 | 
					use itertools::Itertools;
 | 
				
			||||||
 | 
					use tracing::warn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
 | 
					use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct MemoryMonitor {
 | 
					pub struct MemoryMonitor {
 | 
				
			||||||
    client: EmulatorClient,
 | 
					    client: EmulatorClient,
 | 
				
			||||||
    regions: HashMap<MemoryRegion, Arc<Mutex<BoxBytes>>>,
 | 
					    regions: HashMap<MemoryRange, Weak<MemoryRegion>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MemoryMonitor {
 | 
					impl MemoryMonitor {
 | 
				
			||||||
| 
						 | 
					@ -21,20 +24,19 @@ impl MemoryMonitor {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn view(&mut self, sim: SimId, start: u32, length: usize) -> MemoryView {
 | 
					    pub fn view(&mut self, sim: SimId, start: u32, length: usize) -> MemoryView {
 | 
				
			||||||
        let region = MemoryRegion { sim, start, length };
 | 
					        let range = MemoryRange { sim, start, length };
 | 
				
			||||||
        let memory = self.regions.entry(region).or_insert_with(|| {
 | 
					        let region = self
 | 
				
			||||||
            let mut buf = aligned_memory(start, length);
 | 
					            .regions
 | 
				
			||||||
            let (tx, rx) = oneshot::channel();
 | 
					            .get(&range)
 | 
				
			||||||
 | 
					            .and_then(|r| r.upgrade())
 | 
				
			||||||
 | 
					            .unwrap_or_else(|| {
 | 
				
			||||||
 | 
					                let region = Arc::new(MemoryRegion::new(start, length));
 | 
				
			||||||
 | 
					                self.regions.insert(range, Arc::downgrade(®ion));
 | 
				
			||||||
                self.client
 | 
					                self.client
 | 
				
			||||||
                .send_command(EmulatorCommand::ReadMemory(sim, start, length, vec![], tx));
 | 
					                    .send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
 | 
				
			||||||
            let bytes = pollster::block_on(rx).unwrap();
 | 
					                region
 | 
				
			||||||
            buf.copy_from_slice(&bytes);
 | 
					 | 
				
			||||||
            #[expect(clippy::arc_with_non_send_sync)] // TODO: remove after bytemuck upgrade
 | 
					 | 
				
			||||||
            Arc::new(Mutex::new(buf))
 | 
					 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        MemoryView {
 | 
					        MemoryView { region }
 | 
				
			||||||
            memory: memory.clone(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,21 +54,17 @@ fn aligned_memory(start: u32, length: usize) -> BoxBytes {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct MemoryView {
 | 
					pub struct MemoryView {
 | 
				
			||||||
    memory: Arc<Mutex<BoxBytes>>,
 | 
					    region: Arc<MemoryRegion>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
// SAFETY: BoxBytes is supposed to be Send+Sync, will be in a new version
 | 
					 | 
				
			||||||
unsafe impl Send for MemoryView {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MemoryView {
 | 
					impl MemoryView {
 | 
				
			||||||
    pub fn borrow(&self) -> MemoryRef<'_> {
 | 
					    pub fn borrow(&self) -> MemoryRef<'_> {
 | 
				
			||||||
        MemoryRef {
 | 
					        self.region.borrow()
 | 
				
			||||||
            inner: self.memory.lock().unwrap(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct MemoryRef<'a> {
 | 
					pub struct MemoryRef<'a> {
 | 
				
			||||||
    inner: MutexGuard<'a, BoxBytes>,
 | 
					    inner: RwLockReadGuard<'a, BoxBytes>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl MemoryRef<'_> {
 | 
					impl MemoryRef<'_> {
 | 
				
			||||||
| 
						 | 
					@ -83,9 +81,102 @@ impl MemoryRef<'_> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
 | 
					#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 | 
				
			||||||
struct MemoryRegion {
 | 
					pub struct MemoryRange {
 | 
				
			||||||
    sim: SimId,
 | 
					    pub sim: SimId,
 | 
				
			||||||
    start: u32,
 | 
					    pub start: u32,
 | 
				
			||||||
    length: usize,
 | 
					    pub length: usize,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BUFFERS: usize = 4;
 | 
				
			||||||
 | 
					pub struct MemoryRegion {
 | 
				
			||||||
 | 
					    gens: [AtomicU64; BUFFERS],
 | 
				
			||||||
 | 
					    bufs: [RwLock<BoxBytes>; BUFFERS],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					impl Debug for MemoryRegion {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        f.debug_struct("MemoryRegion")
 | 
				
			||||||
 | 
					            .field("gens", &self.gens)
 | 
				
			||||||
 | 
					            .finish_non_exhaustive()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
 | 
				
			||||||
 | 
					unsafe impl Send for MemoryRegion {}
 | 
				
			||||||
 | 
					// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
 | 
				
			||||||
 | 
					unsafe impl Sync for MemoryRegion {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MemoryRegion {
 | 
				
			||||||
 | 
					    fn new(start: u32, length: usize) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            gens: std::array::from_fn(|i| AtomicU64::new(i as u64)),
 | 
				
			||||||
 | 
					            bufs: std::array::from_fn(|_| RwLock::new(aligned_memory(start, length))),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn borrow(&self) -> MemoryRef<'_> {
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * When reading memory, a thread will grab the newest buffer (with the highest gen)
 | 
				
			||||||
 | 
					         * It will only fail to grab the lock if the writer already has it,
 | 
				
			||||||
 | 
					         * but the writer prioritizes older buffers (with lower gens).
 | 
				
			||||||
 | 
					         * So this method will only block if the writer produces three full buffers
 | 
				
			||||||
 | 
					         * in the time it takes the reader to do four atomic reads and grab a lock.
 | 
				
			||||||
 | 
					         * In the unlikely event this happens... just try again.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            let newest_index = self
 | 
				
			||||||
 | 
					                .gens
 | 
				
			||||||
 | 
					                .iter()
 | 
				
			||||||
 | 
					                .map(|i| i.load(std::sync::atomic::Ordering::Acquire))
 | 
				
			||||||
 | 
					                .enumerate()
 | 
				
			||||||
 | 
					                .max_by_key(|(_, gen)| *gen)
 | 
				
			||||||
 | 
					                .map(|(i, _)| i)
 | 
				
			||||||
 | 
					                .unwrap();
 | 
				
			||||||
 | 
					            let inner = match self.bufs[newest_index].try_read() {
 | 
				
			||||||
 | 
					                Ok(inner) => inner,
 | 
				
			||||||
 | 
					                Err(TryLockError::Poisoned(e)) => e.into_inner(),
 | 
				
			||||||
 | 
					                Err(TryLockError::WouldBlock) => {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            break MemoryRef { inner };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn update(&self, data: &[u8]) {
 | 
				
			||||||
 | 
					        let gens: Vec<u64> = self
 | 
				
			||||||
 | 
					            .gens
 | 
				
			||||||
 | 
					            .iter()
 | 
				
			||||||
 | 
					            .map(|i| i.load(std::sync::atomic::Ordering::Acquire))
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					        let next_gen = gens.iter().max().unwrap() + 1;
 | 
				
			||||||
 | 
					        let indices = gens
 | 
				
			||||||
 | 
					            .into_iter()
 | 
				
			||||||
 | 
					            .enumerate()
 | 
				
			||||||
 | 
					            .sorted_by_key(|(_, val)| *val)
 | 
				
			||||||
 | 
					            .map(|(i, _)| i);
 | 
				
			||||||
 | 
					        for index in indices {
 | 
				
			||||||
 | 
					            let mut lock = match self.bufs[index].try_write() {
 | 
				
			||||||
 | 
					                Ok(inner) => inner,
 | 
				
			||||||
 | 
					                Err(TryLockError::Poisoned(e)) => e.into_inner(),
 | 
				
			||||||
 | 
					                Err(TryLockError::WouldBlock) => {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            lock.copy_from_slice(data);
 | 
				
			||||||
 | 
					            self.gens[index].store(next_gen, std::sync::atomic::Ordering::Release);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * We have four buffers, and (at time of writing) only three threads interacting with memory:
 | 
				
			||||||
 | 
					         * - The UI thread, reading small regions of memory
 | 
				
			||||||
 | 
					         * - The "vram renderer" thread, reading large regions of memory
 | 
				
			||||||
 | 
					         * - The emulation thread, writing memory every so often
 | 
				
			||||||
 | 
					         * So it should be impossible for all four buffers to have a read lock at the same time,
 | 
				
			||||||
 | 
					         *  and (because readers always read the newest buffer) at least one of the oldest three
 | 
				
			||||||
 | 
					         *  buffers will be free the entire time we're in this method.
 | 
				
			||||||
 | 
					         * TL;DR this should never happen.
 | 
				
			||||||
 | 
					         * But if it does, do nothing. This isn't medical software, better to show stale data than crash.
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        warn!("all buffers were locked by a reader at the same time")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -359,100 +359,3 @@ impl CharDataRenderer {
 | 
				
			||||||
        utils::parse_palette(palette, brts)
 | 
					        utils::parse_palette(palette, brts)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
struct CharDataLoader {
 | 
					 | 
				
			||||||
    chardata: MemoryView,
 | 
					 | 
				
			||||||
    brightness: MemoryView,
 | 
					 | 
				
			||||||
    palettes: MemoryView,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl CharDataLoader {
 | 
					 | 
				
			||||||
    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 update_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::<u16>(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 update_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::<u16>(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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl VramImageLoader for CharDataLoader {
 | 
					 | 
				
			||||||
    type Resource = CharDataResource;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn id(&self) -> &str {
 | 
					 | 
				
			||||||
        concat!(module_path!(), "::CharDataLoader")
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn add(&self, resource: &Self::Resource) -> Option<VramImage> {
 | 
					 | 
				
			||||||
        match resource {
 | 
					 | 
				
			||||||
            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<Item = (&'a Self::Resource, &'a mut VramImage)>,
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        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)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue