Compare commits
12
Commits
9d37d22a22
..
v0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db36332307 | ||
|
|
8df05d923d | ||
|
|
0086b9c9ce | ||
|
|
a87fce102d | ||
|
|
1cae0c8e54 | ||
|
|
707dd3a200 | ||
|
|
16a2aaee92 | ||
|
|
d59da1009a | ||
|
|
b79f35ac61 | ||
|
|
89128b8207 | ||
|
|
b0404a5da2 | ||
|
|
16cf54e5cf |
Generated
+1
-1
@@ -1763,7 +1763,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lemur"
|
||||
version = "0.2.7"
|
||||
version = "0.3.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atoi",
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ description = "An emulator for the Virtual Boy."
|
||||
repository = "https://git.virtual-boy.com/PVB/lemur"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
version = "0.2.7"
|
||||
version = "0.3.2"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
+1
-1
Submodule shrooms-vb-core updated: 57dcd8370a...155a3aa678
+64
-12
@@ -35,6 +35,7 @@ fn load_icon() -> anyhow::Result<IconData> {
|
||||
|
||||
pub struct Application {
|
||||
icon: Option<Arc<IconData>>,
|
||||
wgpu: WgpuState,
|
||||
client: EmulatorClient,
|
||||
proxy: EventLoopProxy<UserEvent>,
|
||||
mappings: MappingProvider,
|
||||
@@ -51,6 +52,7 @@ impl Application {
|
||||
proxy: EventLoopProxy<UserEvent>,
|
||||
debug_port: Option<u16>,
|
||||
) -> Self {
|
||||
let wgpu = WgpuState::new();
|
||||
let icon = load_icon().ok().map(Arc::new);
|
||||
let persistence = Persistence::new();
|
||||
let mappings = MappingProvider::new(persistence.clone());
|
||||
@@ -62,6 +64,7 @@ impl Application {
|
||||
}
|
||||
Self {
|
||||
icon,
|
||||
wgpu,
|
||||
client,
|
||||
proxy,
|
||||
mappings,
|
||||
@@ -80,7 +83,7 @@ impl Application {
|
||||
}
|
||||
self.viewports.insert(
|
||||
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 {
|
||||
painter: egui_wgpu::winit::Painter,
|
||||
ctx: Context,
|
||||
@@ -251,6 +306,7 @@ struct Viewport {
|
||||
impl Viewport {
|
||||
pub fn new(
|
||||
event_loop: &ActiveEventLoop,
|
||||
wgpu: &WgpuState,
|
||||
icon: Option<Arc<IconData>>,
|
||||
mut app: Box<dyn AppWindow>,
|
||||
) -> Self {
|
||||
@@ -274,20 +330,16 @@ impl Viewport {
|
||||
});
|
||||
egui_extras::install_image_loaders(&ctx);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut wgpu_config = egui_wgpu::WgpuConfiguration {
|
||||
let wgpu_config = egui_wgpu::WgpuConfiguration {
|
||||
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()
|
||||
};
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let egui_wgpu::WgpuSetup::CreateNew {
|
||||
supported_backends, ..
|
||||
} = &mut wgpu_config.wgpu_setup
|
||||
{
|
||||
*supported_backends -= wgpu::Backends::VULKAN;
|
||||
}
|
||||
}
|
||||
|
||||
let mut painter =
|
||||
egui_wgpu::winit::Painter::new(ctx.clone(), wgpu_config, 1, None, false, true);
|
||||
|
||||
+3
-3
@@ -311,7 +311,7 @@ impl Emulator {
|
||||
}
|
||||
let debug = DebugInfo {
|
||||
sender,
|
||||
stop_reason: Some(DebugStopReason::Trapped),
|
||||
stop_reason: Some(DebugStopReason::Paused),
|
||||
};
|
||||
self.debuggers.insert(sim_id, debug);
|
||||
self.state
|
||||
@@ -334,7 +334,7 @@ impl Emulator {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -686,7 +686,7 @@ pub enum DebugStopReason {
|
||||
// We hit a watchpoint
|
||||
Watchpoint(VBWatchpointType, u32),
|
||||
// The debugger told us to pause
|
||||
Trapped,
|
||||
Paused,
|
||||
}
|
||||
|
||||
struct DebugInfo {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
ops::Bound,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AddressSet {
|
||||
ranges: HashSet<(u32, usize)>,
|
||||
ranges: BTreeSet<(u32, usize)>,
|
||||
bounds: BTreeMap<u32, usize>,
|
||||
}
|
||||
|
||||
@@ -109,6 +109,22 @@ impl AddressSet {
|
||||
.next_back()
|
||||
.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)]
|
||||
@@ -120,6 +136,7 @@ mod tests {
|
||||
let set = AddressSet::new();
|
||||
assert!(set.is_empty());
|
||||
assert!(!set.contains(0x13374200));
|
||||
assert_eq!(set.start_of_range_containing(0x13374200), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -127,6 +144,7 @@ mod tests {
|
||||
let mut set = AddressSet::new();
|
||||
set.add(0x00000000, 0x100000000);
|
||||
assert!(set.contains(0x13374200));
|
||||
assert_eq!(set.start_of_range_containing(0x13374200), Some(0x00000000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -228,4 +246,25 @@ mod tests {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,13 +215,13 @@ extern "C" fn on_read(
|
||||
// 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() };
|
||||
|
||||
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) {
|
||||
VBWatchpointType::Access
|
||||
} else {
|
||||
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.
|
||||
@@ -242,13 +242,13 @@ extern "C" fn on_write(
|
||||
// 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() };
|
||||
|
||||
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) {
|
||||
VBWatchpointType::Access
|
||||
} else {
|
||||
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.
|
||||
|
||||
+6
-2
@@ -521,7 +521,11 @@ fn parse_memory_range(req: &mut Request<'_>) -> Option<(u32, usize)> {
|
||||
|
||||
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
||||
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 {
|
||||
result += "swbreak;";
|
||||
}
|
||||
@@ -542,7 +546,7 @@ fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
||||
DebugStopReason::Trace => "trace;",
|
||||
DebugStopReason::Breakpoint => "breakpoint;",
|
||||
DebugStopReason::Watchpoint(_, _) => "watchpoint;",
|
||||
DebugStopReason::Trapped => "trap;",
|
||||
DebugStopReason::Paused => "trap;",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+3
-7
@@ -142,8 +142,7 @@ impl GameWindow {
|
||||
fn show_options_menu(&mut self, ctx: &Context, ui: &mut Ui) {
|
||||
ui.menu_button("Video", |ui| {
|
||||
ui.menu_button("Screen Size", |ui| {
|
||||
let current_dims = ctx.input(|i| i.viewport().inner_rect.unwrap());
|
||||
let current_dims = current_dims.max - current_dims.min;
|
||||
let current_dims = self.config.dimensions;
|
||||
|
||||
for scale in 1..=4 {
|
||||
let label = format!("x{scale}");
|
||||
@@ -183,10 +182,7 @@ impl GameWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
let current_dims = {
|
||||
let viewport = ctx.input(|i| i.viewport().inner_rect.unwrap());
|
||||
viewport.max - viewport.min
|
||||
};
|
||||
let current_dims = self.config.dimensions;
|
||||
let new_proportions = display_mode.proportions();
|
||||
let scale = new_proportions / old_proportions;
|
||||
if scale != Vec2::new(1.0, 1.0) {
|
||||
@@ -330,7 +326,7 @@ impl AppWindow for GameWindow {
|
||||
|
||||
fn show(&mut self, ctx: &Context) {
|
||||
let dimensions = {
|
||||
let bounds = ctx.input(|i| i.viewport().inner_rect.unwrap());
|
||||
let bounds = ctx.available_rect();
|
||||
bounds.max - bounds.min
|
||||
};
|
||||
self.update_config(|c| c.dimensions = dimensions);
|
||||
|
||||
Reference in New Issue
Block a user