Compare commits

..
12 Commits
9 changed files with 124 additions and 33 deletions
Generated
+1 -1
View File
@@ -1763,7 +1763,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "lemur" name = "lemur"
version = "0.2.7" version = "0.3.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"atoi", "atoi",
+1 -1
View File
@@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
repository = "https://git.virtual-boy.com/PVB/lemur" repository = "https://git.virtual-boy.com/PVB/lemur"
publish = false publish = false
license = "MIT" license = "MIT"
version = "0.2.7" version = "0.3.2"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
+64 -12
View File
@@ -35,6 +35,7 @@ fn load_icon() -> anyhow::Result<IconData> {
pub struct Application { pub struct Application {
icon: Option<Arc<IconData>>, icon: Option<Arc<IconData>>,
wgpu: WgpuState,
client: EmulatorClient, client: EmulatorClient,
proxy: EventLoopProxy<UserEvent>, proxy: EventLoopProxy<UserEvent>,
mappings: MappingProvider, mappings: MappingProvider,
@@ -51,6 +52,7 @@ impl Application {
proxy: EventLoopProxy<UserEvent>, proxy: EventLoopProxy<UserEvent>,
debug_port: Option<u16>, debug_port: Option<u16>,
) -> Self { ) -> Self {
let wgpu = WgpuState::new();
let icon = load_icon().ok().map(Arc::new); let icon = load_icon().ok().map(Arc::new);
let persistence = Persistence::new(); let persistence = Persistence::new();
let mappings = MappingProvider::new(persistence.clone()); let mappings = MappingProvider::new(persistence.clone());
@@ -62,6 +64,7 @@ impl Application {
} }
Self { Self {
icon, icon,
wgpu,
client, client,
proxy, proxy,
mappings, mappings,
@@ -80,7 +83,7 @@ impl Application {
} }
self.viewports.insert( self.viewports.insert(
viewport_id, viewport_id,
Viewport::new(event_loop, self.icon.clone(), window), Viewport::new(event_loop, &self.wgpu, self.icon.clone(), window),
); );
} }
} }
@@ -238,6 +241,58 @@ impl ApplicationHandler<UserEvent> for Application {
} }
} }
struct WgpuState {
instance: Arc<wgpu::Instance>,
adapter: Arc<wgpu::Adapter>,
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
}
impl WgpuState {
fn new() -> Self {
let egui_wgpu::WgpuConfiguration {
wgpu_setup:
egui_wgpu::WgpuSetup::CreateNew {
#[allow(unused)]
supported_backends,
device_descriptor,
..
},
..
} = egui_wgpu::WgpuConfiguration::default()
else {
panic!("required fields not found")
};
#[cfg(windows)]
let supported_backends = wgpu::util::backend_bits_from_env()
.unwrap_or((wgpu::Backends::PRIMARY | wgpu::Backends::GL) - wgpu::Backends::VULKAN);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: supported_backends,
..wgpu::InstanceDescriptor::default()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
}))
.expect("could not create adapter");
let trace_path = std::env::var("WGPU_TRACE");
let (device, queue) = pollster::block_on(adapter.request_device(
&(*device_descriptor)(&adapter),
trace_path.ok().as_ref().map(std::path::Path::new),
))
.expect("could not request device");
Self {
instance: Arc::new(instance),
adapter: Arc::new(adapter),
device: Arc::new(device),
queue: Arc::new(queue),
}
}
}
struct Viewport { struct Viewport {
painter: egui_wgpu::winit::Painter, painter: egui_wgpu::winit::Painter,
ctx: Context, ctx: Context,
@@ -251,6 +306,7 @@ struct Viewport {
impl Viewport { impl Viewport {
pub fn new( pub fn new(
event_loop: &ActiveEventLoop, event_loop: &ActiveEventLoop,
wgpu: &WgpuState,
icon: Option<Arc<IconData>>, icon: Option<Arc<IconData>>,
mut app: Box<dyn AppWindow>, mut app: Box<dyn AppWindow>,
) -> Self { ) -> Self {
@@ -274,20 +330,16 @@ impl Viewport {
}); });
egui_extras::install_image_loaders(&ctx); egui_extras::install_image_loaders(&ctx);
#[allow(unused_mut)] let wgpu_config = egui_wgpu::WgpuConfiguration {
let mut wgpu_config = egui_wgpu::WgpuConfiguration {
present_mode: wgpu::PresentMode::AutoNoVsync, present_mode: wgpu::PresentMode::AutoNoVsync,
wgpu_setup: egui_wgpu::WgpuSetup::Existing {
instance: wgpu.instance.clone(),
adapter: wgpu.adapter.clone(),
device: wgpu.device.clone(),
queue: wgpu.queue.clone(),
},
..egui_wgpu::WgpuConfiguration::default() ..egui_wgpu::WgpuConfiguration::default()
}; };
#[cfg(windows)]
{
if let egui_wgpu::WgpuSetup::CreateNew {
supported_backends, ..
} = &mut wgpu_config.wgpu_setup
{
*supported_backends -= wgpu::Backends::VULKAN;
}
}
let mut painter = let mut painter =
egui_wgpu::winit::Painter::new(ctx.clone(), wgpu_config, 1, None, false, true); egui_wgpu::winit::Painter::new(ctx.clone(), wgpu_config, 1, None, false, true);
+3 -3
View File
@@ -311,7 +311,7 @@ impl Emulator {
} }
let debug = DebugInfo { let debug = DebugInfo {
sender, sender,
stop_reason: Some(DebugStopReason::Trapped), stop_reason: Some(DebugStopReason::Paused),
}; };
self.debuggers.insert(sim_id, debug); self.debuggers.insert(sim_id, debug);
self.state self.state
@@ -334,7 +334,7 @@ impl Emulator {
} }
fn debug_interrupt(&mut self, sim_id: SimId) { fn debug_interrupt(&mut self, sim_id: SimId) {
self.debug_stop(sim_id, DebugStopReason::Trapped); self.debug_stop(sim_id, DebugStopReason::Paused);
} }
fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) { fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) {
@@ -686,7 +686,7 @@ pub enum DebugStopReason {
// We hit a watchpoint // We hit a watchpoint
Watchpoint(VBWatchpointType, u32), Watchpoint(VBWatchpointType, u32),
// The debugger told us to pause // The debugger told us to pause
Trapped, Paused,
} }
struct DebugInfo { struct DebugInfo {
+41 -2
View File
@@ -1,11 +1,11 @@
use std::{ use std::{
collections::{BTreeMap, HashSet}, collections::{BTreeMap, BTreeSet},
ops::Bound, ops::Bound,
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct AddressSet { pub struct AddressSet {
ranges: HashSet<(u32, usize)>, ranges: BTreeSet<(u32, usize)>,
bounds: BTreeMap<u32, usize>, bounds: BTreeMap<u32, usize>,
} }
@@ -109,6 +109,22 @@ impl AddressSet {
.next_back() .next_back()
.is_some_and(|(_, &val)| val > 0) .is_some_and(|(_, &val)| val > 0)
} }
pub fn start_of_range_containing(&self, address: u32) -> Option<u32> {
if !self.contains(address) {
return None;
}
self.ranges
.range(..=(address, usize::MAX))
.rev()
.find_map(|&(start, length)| {
let contains = start <= address
&& (start as usize)
.checked_add(length)
.is_none_or(|end| end > address as usize);
contains.then_some(start)
})
}
} }
#[cfg(test)] #[cfg(test)]
@@ -120,6 +136,7 @@ mod tests {
let set = AddressSet::new(); let set = AddressSet::new();
assert!(set.is_empty()); assert!(set.is_empty());
assert!(!set.contains(0x13374200)); assert!(!set.contains(0x13374200));
assert_eq!(set.start_of_range_containing(0x13374200), None);
} }
#[test] #[test]
@@ -127,6 +144,7 @@ mod tests {
let mut set = AddressSet::new(); let mut set = AddressSet::new();
set.add(0x00000000, 0x100000000); set.add(0x00000000, 0x100000000);
assert!(set.contains(0x13374200)); assert!(set.contains(0x13374200));
assert_eq!(set.start_of_range_containing(0x13374200), Some(0x00000000));
} }
#[test] #[test]
@@ -228,4 +246,25 @@ mod tests {
assert!(!set.contains(address)); assert!(!set.contains(address));
} }
} }
#[test]
fn should_find_start_of_range() {
let mut set = AddressSet::new();
set.add(0x13374200, 4);
assert_eq!(set.start_of_range_containing(0x133741ff), None);
for address in 0x13374200..0x13374204 {
assert_eq!(set.start_of_range_containing(address), Some(0x13374200));
}
assert_eq!(set.start_of_range_containing(0x13374204), None);
}
#[test]
fn should_ignore_ranges_not_containing_address() {
let mut set = AddressSet::new();
set.add(0x10000000, 1024);
set.add(0x30000000, 1024);
assert!(!set.contains(0x20000000));
assert_eq!(set.start_of_range_containing(0x20000000), None);
}
} }
+4 -4
View File
@@ -215,13 +215,13 @@ extern "C" fn on_read(
// There is no way for the userdata to be null or otherwise invalid. // There is no way for the userdata to be null or otherwise invalid.
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
if data.read_watchpoints.contains(address) { if let Some(start) = data.read_watchpoints.start_of_range_containing(address) {
let watch = if data.write_watchpoints.contains(address) { let watch = if data.write_watchpoints.contains(address) {
VBWatchpointType::Access VBWatchpointType::Access
} else { } else {
VBWatchpointType::Read VBWatchpointType::Read
}; };
data.stop_reason = Some(StopReason::Watchpoint(watch, address)); data.stop_reason = Some(StopReason::Watchpoint(watch, start));
} }
// Don't stop here, the debugger expects us to break after the memory access. // Don't stop here, the debugger expects us to break after the memory access.
@@ -242,13 +242,13 @@ extern "C" fn on_write(
// There is no way for the userdata to be null or otherwise invalid. // There is no way for the userdata to be null or otherwise invalid.
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() }; let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
if data.write_watchpoints.contains(address) { if let Some(start) = data.write_watchpoints.start_of_range_containing(address) {
let watch = if data.read_watchpoints.contains(address) { let watch = if data.read_watchpoints.contains(address) {
VBWatchpointType::Access VBWatchpointType::Access
} else { } else {
VBWatchpointType::Write VBWatchpointType::Write
}; };
data.stop_reason = Some(StopReason::Watchpoint(watch, address)); data.stop_reason = Some(StopReason::Watchpoint(watch, start));
} }
// Don't stop here, the debugger expects us to break after the memory access. // Don't stop here, the debugger expects us to break after the memory access.
+6 -2
View File
@@ -521,7 +521,11 @@ fn parse_memory_range(req: &mut Request<'_>) -> Option<(u32, usize)> {
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String { fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
let mut result = String::new(); let mut result = String::new();
result += if reason.is_some() { "T05;" } else { "T00;" }; result += if reason.is_some_and(|r| r != DebugStopReason::Paused) {
"T05;"
} else {
"T00;"
};
if let Some(DebugStopReason::Breakpoint) = reason { if let Some(DebugStopReason::Breakpoint) = reason {
result += "swbreak;"; result += "swbreak;";
} }
@@ -542,7 +546,7 @@ fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
DebugStopReason::Trace => "trace;", DebugStopReason::Trace => "trace;",
DebugStopReason::Breakpoint => "breakpoint;", DebugStopReason::Breakpoint => "breakpoint;",
DebugStopReason::Watchpoint(_, _) => "watchpoint;", DebugStopReason::Watchpoint(_, _) => "watchpoint;",
DebugStopReason::Trapped => "trap;", DebugStopReason::Paused => "trap;",
}; };
} }
+3 -7
View File
@@ -142,8 +142,7 @@ impl GameWindow {
fn show_options_menu(&mut self, ctx: &Context, ui: &mut Ui) { fn show_options_menu(&mut self, ctx: &Context, ui: &mut Ui) {
ui.menu_button("Video", |ui| { ui.menu_button("Video", |ui| {
ui.menu_button("Screen Size", |ui| { ui.menu_button("Screen Size", |ui| {
let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap()); let current_dims = self.config.dimensions;
let current_dims = current_dims.max - current_dims.min;
for scale in 1..=4 { for scale in 1..=4 {
let label = format!("x{scale}"); let label = format!("x{scale}");
@@ -183,10 +182,7 @@ impl GameWindow {
return; return;
} }
let current_dims = { let current_dims = self.config.dimensions;
let viewport = ctx.input(|i| i.viewport().inner_rect.unwrap());
viewport.max - viewport.min
};
let new_proportions = display_mode.proportions(); let new_proportions = display_mode.proportions();
let scale = new_proportions / old_proportions; let scale = new_proportions / old_proportions;
if scale != Vec2::new(1.0, 1.0) { if scale != Vec2::new(1.0, 1.0) {
@@ -330,7 +326,7 @@ impl AppWindow for GameWindow {
fn show(&mut self, ctx: &Context) { fn show(&mut self, ctx: &Context) {
let dimensions = { let dimensions = {
let bounds = ctx.input(|i| i.viewport().inner_rect.unwrap()); let bounds = ctx.available_rect();
bounds.max - bounds.min bounds.max - bounds.min
}; };
self.update_config(|c| c.dimensions = dimensions); self.update_config(|c| c.dimensions = dimensions);