Compare commits
34
Commits
0ae2a2f54f
..
v0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be866656a | ||
|
|
b58746fee4 | ||
|
|
57f27559b1 | ||
|
|
caf3a9426e | ||
|
|
4a3385f04c | ||
|
|
d3f51df577 | ||
|
|
4646c20823 | ||
|
|
f649cf72eb | ||
|
|
427c5ec4a9 | ||
|
|
0043cf8a95 | ||
|
|
9a34856c2b | ||
|
|
fddc7bdd0e | ||
|
|
d475e806c7 | ||
|
|
316d297c91 | ||
|
|
273b3f3409 | ||
|
|
3f216f85ae | ||
|
|
6df198b510 | ||
|
|
bbffde50ec | ||
|
|
fcfb75fead | ||
|
|
7654666a17 | ||
|
|
9fc9e4c4d9 | ||
|
|
5a5c5d4d9b | ||
|
|
fb6f09fce5 | ||
|
|
f2d3f5ec07 | ||
|
|
e96ce2af07 | ||
|
|
25c7f29735 | ||
|
|
54edfbc111 | ||
|
|
d05c79532f | ||
|
|
1b5fb1a6ed | ||
|
|
59e6ba515d | ||
|
|
5c5b6a0b9e | ||
|
|
ad994f8851 | ||
|
|
49343b6903 | ||
|
|
6ab9d2ee53 |
Generated
+706
-379
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -4,8 +4,8 @@ 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.3.2"
|
version = "0.5.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
@@ -15,7 +15,7 @@ bitflags = { version = "2", features = ["serde"] }
|
|||||||
bytemuck = { version = "1", features = ["derive"] }
|
bytemuck = { version = "1", features = ["derive"] }
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" }
|
cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" }
|
||||||
directories = "5"
|
directories = "6"
|
||||||
egui = { version = "0.30", features = ["serde"] }
|
egui = { version = "0.30", features = ["serde"] }
|
||||||
egui_extras = { version = "0.30", features = ["image"] }
|
egui_extras = { version = "0.30", features = ["image"] }
|
||||||
egui-toast = { git = "https://github.com/urholaukkarinen/egui-toast.git", rev = "d0bcf97" }
|
egui-toast = { git = "https://github.com/urholaukkarinen/egui-toast.git", rev = "d0bcf97" }
|
||||||
@@ -30,7 +30,7 @@ num-derive = "0.4"
|
|||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
oneshot = "0.1"
|
oneshot = "0.1"
|
||||||
pollster = "0.4"
|
pollster = "0.4"
|
||||||
rfd = "0.15"
|
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "async-std"]}
|
||||||
rtrb = "0.3"
|
rtrb = "0.3"
|
||||||
rubato = "0.16"
|
rubato = "0.16"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
@@ -43,7 +43,7 @@ wgpu = "23"
|
|||||||
winit = { version = "0.30", features = ["serde"] }
|
winit = { version = "0.30", features = ["serde"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.58", features = ["Win32_System_Threading"] }
|
windows = { version = "0.59", features = ["Win32_System_Threading"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1"
|
cc = "1"
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ A Virtual Boy emulator built around the shrooms-vb core. Written in Rust, using
|
|||||||
|
|
||||||
Install the following dependencies:
|
Install the following dependencies:
|
||||||
- `cargo` (via [rustup](https://rustup.rs/), the version from your package manager is too old)
|
- `cargo` (via [rustup](https://rustup.rs/), the version from your package manager is too old)
|
||||||
|
- a C compiler (any will do, [the build script](https://docs.rs/cc/latest/cc/#compile-time-requirements) will find it automatically)
|
||||||
|
- (on linux) `libasound2-dev` and `libudev-dev`
|
||||||
|
|
||||||
Run
|
Run
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -32,6 +32,5 @@ ENV PATH="/osxcross/bin:$PATH" \
|
|||||||
CARGO_TARGET_X86_64_APPLE_DARWIN_AR="llvm-ar-19" \
|
CARGO_TARGET_X86_64_APPLE_DARWIN_AR="llvm-ar-19" \
|
||||||
CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="oa64-clang" \
|
CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="oa64-clang" \
|
||||||
CARGO_TARGET_AARCH64_APPLE_DARWIN_AR="llvm-ar-19" \
|
CARGO_TARGET_AARCH64_APPLE_DARWIN_AR="llvm-ar-19" \
|
||||||
CROSS_COMPILE="setting-this-to-silence-a-warning-" \
|
|
||||||
RC_PATH="llvm-rc-19" \
|
RC_PATH="llvm-rc-19" \
|
||||||
MACOSX_DEPLOYMENT_TARGET="14.5"
|
MACOSX_DEPLOYMENT_TARGET="14.5"
|
||||||
|
|||||||
+1
-1
Submodule shrooms-vb-core updated: 155a3aa678...ecbd103917
+27
-14
@@ -1,9 +1,10 @@
|
|||||||
use std::{collections::HashSet, num::NonZero, sync::Arc, thread, time::Duration};
|
use std::{collections::HashSet, num::NonZero, sync::Arc, thread, time::Duration};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
ahash::{HashMap, HashMapExt},
|
|
||||||
Context, FontData, FontDefinitions, FontFamily, IconData, TextWrapMode, ViewportBuilder,
|
Context, FontData, FontDefinitions, FontFamily, IconData, TextWrapMode, ViewportBuilder,
|
||||||
ViewportCommand, ViewportId, ViewportInfo,
|
ViewportCommand, ViewportId, ViewportInfo,
|
||||||
|
ahash::{HashMap, HashMapExt},
|
||||||
|
style::ScrollStyle,
|
||||||
};
|
};
|
||||||
use gilrs::{EventType, Gilrs};
|
use gilrs::{EventType, Gilrs};
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
@@ -17,13 +18,13 @@ use winit::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
controller::ControllerManager,
|
controller::ControllerManager,
|
||||||
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
||||||
input::MappingProvider,
|
images::ImageProcessor,
|
||||||
|
input::{MappingProvider, ShortcutProvider},
|
||||||
memory::MemoryClient,
|
memory::MemoryClient,
|
||||||
persistence::Persistence,
|
persistence::Persistence,
|
||||||
vram::VramProcessor,
|
|
||||||
window::{
|
window::{
|
||||||
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
|
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
|
||||||
GdbServerWindow, InputWindow, ObjectWindow, RegisterWindow, WorldWindow,
|
GdbServerWindow, HotkeysWindow, InputWindow, ObjectWindow, RegisterWindow, WorldWindow,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,9 +45,10 @@ pub struct Application {
|
|||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
mappings: MappingProvider,
|
mappings: MappingProvider,
|
||||||
|
shortcuts: ShortcutProvider,
|
||||||
controllers: ControllerManager,
|
controllers: ControllerManager,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
vram: VramProcessor,
|
images: ImageProcessor,
|
||||||
persistence: Persistence,
|
persistence: Persistence,
|
||||||
viewports: HashMap<ViewportId, Viewport>,
|
viewports: HashMap<ViewportId, Viewport>,
|
||||||
focused: Option<ViewportId>,
|
focused: Option<ViewportId>,
|
||||||
@@ -63,9 +65,10 @@ impl Application {
|
|||||||
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());
|
||||||
|
let shortcuts = ShortcutProvider::new(persistence.clone());
|
||||||
let controllers = ControllerManager::new(client.clone(), &mappings);
|
let controllers = ControllerManager::new(client.clone(), &mappings);
|
||||||
let memory = Arc::new(MemoryClient::new(client.clone()));
|
let memory = Arc::new(MemoryClient::new(client.clone()));
|
||||||
let vram = VramProcessor::new();
|
let images = ImageProcessor::new();
|
||||||
{
|
{
|
||||||
let mappings = mappings.clone();
|
let mappings = mappings.clone();
|
||||||
let proxy = proxy.clone();
|
let proxy = proxy.clone();
|
||||||
@@ -77,8 +80,9 @@ impl Application {
|
|||||||
client,
|
client,
|
||||||
proxy,
|
proxy,
|
||||||
mappings,
|
mappings,
|
||||||
|
shortcuts,
|
||||||
memory,
|
memory,
|
||||||
vram,
|
images,
|
||||||
controllers,
|
controllers,
|
||||||
persistence,
|
persistence,
|
||||||
viewports: HashMap::new(),
|
viewports: HashMap::new(),
|
||||||
@@ -89,7 +93,8 @@ impl Application {
|
|||||||
|
|
||||||
fn open(&mut self, event_loop: &ActiveEventLoop, window: Box<dyn AppWindow>) {
|
fn open(&mut self, event_loop: &ActiveEventLoop, window: Box<dyn AppWindow>) {
|
||||||
let viewport_id = window.viewport_id();
|
let viewport_id = window.viewport_id();
|
||||||
if self.viewports.contains_key(&viewport_id) {
|
if let Some(viewport) = self.viewports.get(&viewport_id) {
|
||||||
|
viewport.window.focus_window();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.viewports.insert(
|
self.viewports.insert(
|
||||||
@@ -111,6 +116,7 @@ impl ApplicationHandler<UserEvent> for Application {
|
|||||||
self.client.clone(),
|
self.client.clone(),
|
||||||
self.proxy.clone(),
|
self.proxy.clone(),
|
||||||
self.persistence.clone(),
|
self.persistence.clone(),
|
||||||
|
self.shortcuts.clone(),
|
||||||
SimId::Player1,
|
SimId::Player1,
|
||||||
);
|
);
|
||||||
self.open(event_loop, Box::new(app));
|
self.open(event_loop, Box::new(app));
|
||||||
@@ -214,23 +220,23 @@ impl ApplicationHandler<UserEvent> for Application {
|
|||||||
self.open(event_loop, Box::new(about));
|
self.open(event_loop, Box::new(about));
|
||||||
}
|
}
|
||||||
UserEvent::OpenCharacterData(sim_id) => {
|
UserEvent::OpenCharacterData(sim_id) => {
|
||||||
let vram = CharacterDataWindow::new(sim_id, &self.memory, &mut self.vram);
|
let chardata = CharacterDataWindow::new(sim_id, &self.memory, &mut self.images);
|
||||||
self.open(event_loop, Box::new(vram));
|
self.open(event_loop, Box::new(chardata));
|
||||||
}
|
}
|
||||||
UserEvent::OpenBgMap(sim_id) => {
|
UserEvent::OpenBgMap(sim_id) => {
|
||||||
let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.vram);
|
let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.images);
|
||||||
self.open(event_loop, Box::new(bgmap));
|
self.open(event_loop, Box::new(bgmap));
|
||||||
}
|
}
|
||||||
UserEvent::OpenObjects(sim_id) => {
|
UserEvent::OpenObjects(sim_id) => {
|
||||||
let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.vram);
|
let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.images);
|
||||||
self.open(event_loop, Box::new(objects));
|
self.open(event_loop, Box::new(objects));
|
||||||
}
|
}
|
||||||
UserEvent::OpenWorlds(sim_id) => {
|
UserEvent::OpenWorlds(sim_id) => {
|
||||||
let world = WorldWindow::new(sim_id, &self.memory, &mut self.vram);
|
let world = WorldWindow::new(sim_id, &self.memory, &mut self.images);
|
||||||
self.open(event_loop, Box::new(world));
|
self.open(event_loop, Box::new(world));
|
||||||
}
|
}
|
||||||
UserEvent::OpenFrameBuffers(sim_id) => {
|
UserEvent::OpenFrameBuffers(sim_id) => {
|
||||||
let world = FrameBufferWindow::new(sim_id, &self.memory, &mut self.vram);
|
let world = FrameBufferWindow::new(sim_id, &self.memory, &mut self.images);
|
||||||
self.open(event_loop, Box::new(world));
|
self.open(event_loop, Box::new(world));
|
||||||
}
|
}
|
||||||
UserEvent::OpenRegisters(sim_id) => {
|
UserEvent::OpenRegisters(sim_id) => {
|
||||||
@@ -246,11 +252,16 @@ impl ApplicationHandler<UserEvent> for Application {
|
|||||||
let input = InputWindow::new(self.mappings.clone());
|
let input = InputWindow::new(self.mappings.clone());
|
||||||
self.open(event_loop, Box::new(input));
|
self.open(event_loop, Box::new(input));
|
||||||
}
|
}
|
||||||
|
UserEvent::OpenHotkeys => {
|
||||||
|
let hotkeys = HotkeysWindow::new(self.shortcuts.clone());
|
||||||
|
self.open(event_loop, Box::new(hotkeys));
|
||||||
|
}
|
||||||
UserEvent::OpenPlayer2 => {
|
UserEvent::OpenPlayer2 => {
|
||||||
let p2 = GameWindow::new(
|
let p2 = GameWindow::new(
|
||||||
self.client.clone(),
|
self.client.clone(),
|
||||||
self.proxy.clone(),
|
self.proxy.clone(),
|
||||||
self.persistence.clone(),
|
self.persistence.clone(),
|
||||||
|
self.shortcuts.clone(),
|
||||||
SimId::Player2,
|
SimId::Player2,
|
||||||
);
|
);
|
||||||
self.open(event_loop, Box::new(p2));
|
self.open(event_loop, Box::new(p2));
|
||||||
@@ -367,6 +378,7 @@ impl Viewport {
|
|||||||
ctx.style_mut(|s| {
|
ctx.style_mut(|s| {
|
||||||
s.wrap_mode = Some(TextWrapMode::Extend);
|
s.wrap_mode = Some(TextWrapMode::Extend);
|
||||||
s.visuals.menu_rounding = Default::default();
|
s.visuals.menu_rounding = Default::default();
|
||||||
|
s.spacing.scroll = ScrollStyle::thin();
|
||||||
});
|
});
|
||||||
egui_extras::install_image_loaders(&ctx);
|
egui_extras::install_image_loaders(&ctx);
|
||||||
|
|
||||||
@@ -503,6 +515,7 @@ pub enum UserEvent {
|
|||||||
OpenRegisters(SimId),
|
OpenRegisters(SimId),
|
||||||
OpenDebugger(SimId),
|
OpenDebugger(SimId),
|
||||||
OpenInput,
|
OpenInput,
|
||||||
|
OpenHotkeys,
|
||||||
OpenPlayer2,
|
OpenPlayer2,
|
||||||
Quit(SimId),
|
Quit(SimId),
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-4
@@ -1,20 +1,22 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rubato::{FftFixedInOut, Resampler};
|
use rubato::{FastFixedOut, Resampler};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
pub struct Audio {
|
pub struct Audio {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
stream: cpal::Stream,
|
stream: cpal::Stream,
|
||||||
sampler: FftFixedInOut<f32>,
|
sampler: FastFixedOut<f32>,
|
||||||
input_buffer: Vec<Vec<f32>>,
|
input_buffer: Vec<Vec<f32>>,
|
||||||
output_buffer: Vec<Vec<f32>>,
|
output_buffer: Vec<Vec<f32>>,
|
||||||
sample_sink: rtrb::Producer<f32>,
|
sample_sink: rtrb::Producer<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VB_FREQUENCY: usize = 41700;
|
||||||
|
|
||||||
impl Audio {
|
impl Audio {
|
||||||
pub fn init() -> Result<Self> {
|
pub fn init() -> Result<Self> {
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
@@ -28,7 +30,15 @@ impl Audio {
|
|||||||
bail!("No suitable output config available");
|
bail!("No suitable output config available");
|
||||||
};
|
};
|
||||||
let mut config = config.with_max_sample_rate().config();
|
let mut config = config.with_max_sample_rate().config();
|
||||||
let sampler = FftFixedInOut::new(41700, config.sample_rate.0 as usize, 834, 2)?;
|
let resample_ratio = config.sample_rate.0 as f64 / VB_FREQUENCY as f64;
|
||||||
|
let chunk_size = (834.0 * resample_ratio) as usize;
|
||||||
|
let sampler = FastFixedOut::new(
|
||||||
|
resample_ratio,
|
||||||
|
64.0,
|
||||||
|
rubato::PolynomialDegree::Cubic,
|
||||||
|
chunk_size,
|
||||||
|
2,
|
||||||
|
)?;
|
||||||
config.buffer_size = cpal::BufferSize::Fixed(sampler.output_frames_max() as u32);
|
config.buffer_size = cpal::BufferSize::Fixed(sampler.output_frames_max() as u32);
|
||||||
|
|
||||||
let input_buffer = sampler.input_buffer_allocate(true);
|
let input_buffer = sampler.input_buffer_allocate(true);
|
||||||
@@ -101,4 +111,10 @@ impl Audio {
|
|||||||
std::thread::sleep(Duration::from_micros(500));
|
std::thread::sleep(Duration::from_micros(500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_speed(&mut self, speed: f64) -> Result<()> {
|
||||||
|
self.sampler
|
||||||
|
.set_resample_ratio_relative(1.0 / speed, false)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use gilrs::{ev::Code, Event as GamepadEvent, EventType, GamepadId};
|
use gilrs::{Event as GamepadEvent, EventType, GamepadId, ev::Code};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, KeyEvent},
|
event::{ElementState, KeyEvent},
|
||||||
keyboard::PhysicalKey,
|
keyboard::PhysicalKey,
|
||||||
|
|||||||
+43
-3
@@ -5,9 +5,9 @@ use std::{
|
|||||||
io::{Read, Seek, SeekFrom, Write},
|
io::{Read, Seek, SeekFrom, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
|
Arc, Weak,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
mpsc::{self, RecvError, TryRecvError},
|
mpsc::{self, RecvError, TryRecvError},
|
||||||
Arc, Weak,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ use crate::{
|
|||||||
graphics::TextureSink,
|
graphics::TextureSink,
|
||||||
memory::{MemoryRange, MemoryRegion},
|
memory::{MemoryRange, MemoryRegion},
|
||||||
};
|
};
|
||||||
use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE};
|
use shrooms_vb_core::{EXPECTED_FRAME_SIZE, Sim, StopReason};
|
||||||
pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
|
pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
|
||||||
|
|
||||||
mod address_set;
|
mod address_set;
|
||||||
@@ -291,6 +291,30 @@ impl Emulator {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn frame_advance(&mut self) {
|
||||||
|
if self
|
||||||
|
.state
|
||||||
|
.compare_exchange(
|
||||||
|
EmulatorState::Paused,
|
||||||
|
EmulatorState::Stepping,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Acquire,
|
||||||
|
)
|
||||||
|
.is_err_and(|s| s == EmulatorState::Running)
|
||||||
|
{
|
||||||
|
let _ = self.state.compare_exchange(
|
||||||
|
EmulatorState::Running,
|
||||||
|
EmulatorState::Stepping,
|
||||||
|
Ordering::AcqRel,
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_speed(&mut self, speed: f64) -> Result<()> {
|
||||||
|
self.audio.set_speed(speed)
|
||||||
|
}
|
||||||
|
|
||||||
fn save_sram(&mut self, sim_id: SimId) -> Result<()> {
|
fn save_sram(&mut self, sim_id: SimId) -> Result<()> {
|
||||||
let sim = self.sims.get_mut(sim_id.to_index());
|
let sim = self.sims.get_mut(sim_id.to_index());
|
||||||
let cart = self.carts[sim_id.to_index()].as_mut();
|
let cart = self.carts[sim_id.to_index()].as_mut();
|
||||||
@@ -366,6 +390,7 @@ impl Emulator {
|
|||||||
debugger.stop_reason = None;
|
debugger.stop_reason = None;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_step(&mut self, sim_id: SimId) {
|
fn debug_step(&mut self, sim_id: SimId) {
|
||||||
if self.debug_continue(sim_id) {
|
if self.debug_continue(sim_id) {
|
||||||
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
@@ -428,7 +453,7 @@ impl Emulator {
|
|||||||
// Don't emulate if the state is "paused", or if any sim is paused in the debugger
|
// Don't emulate if the state is "paused", or if any sim is paused in the debugger
|
||||||
let running = match state {
|
let running = match state {
|
||||||
EmulatorState::Paused => false,
|
EmulatorState::Paused => false,
|
||||||
EmulatorState::Running => true,
|
EmulatorState::Running | EmulatorState::Stepping => true,
|
||||||
EmulatorState::Debugging => self.debuggers.values().all(|d| d.stop_reason.is_none()),
|
EmulatorState::Debugging => self.debuggers.values().all(|d| d.stop_reason.is_none()),
|
||||||
};
|
};
|
||||||
let p1_running = running && p1_state == SimState::Ready;
|
let p1_running = running && p1_state == SimState::Ready;
|
||||||
@@ -442,6 +467,10 @@ impl Emulator {
|
|||||||
self.sims[SimId::Player2.to_index()].emulate();
|
self.sims[SimId::Player2.to_index()].emulate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if state == EmulatorState::Stepping {
|
||||||
|
self.state.store(EmulatorState::Paused, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
// Debug state
|
// Debug state
|
||||||
if state == EmulatorState::Debugging {
|
if state == EmulatorState::Debugging {
|
||||||
for sim_id in SimId::values() {
|
for sim_id in SimId::values() {
|
||||||
@@ -539,6 +568,14 @@ impl Emulator {
|
|||||||
EmulatorCommand::Resume => {
|
EmulatorCommand::Resume => {
|
||||||
self.resume_sims();
|
self.resume_sims();
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::FrameAdvance => {
|
||||||
|
self.frame_advance();
|
||||||
|
}
|
||||||
|
EmulatorCommand::SetSpeed(speed) => {
|
||||||
|
if let Err(error) = self.set_speed(speed) {
|
||||||
|
self.report_error(SimId::Player1, format!("Error setting speed: {error}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
EmulatorCommand::StartDebugging(sim_id, debugger) => {
|
EmulatorCommand::StartDebugging(sim_id, debugger) => {
|
||||||
self.start_debugging(sim_id, debugger);
|
self.start_debugging(sim_id, debugger);
|
||||||
}
|
}
|
||||||
@@ -665,6 +702,8 @@ pub enum EmulatorCommand {
|
|||||||
StopSecondSim,
|
StopSecondSim,
|
||||||
Pause,
|
Pause,
|
||||||
Resume,
|
Resume,
|
||||||
|
FrameAdvance,
|
||||||
|
SetSpeed(f64),
|
||||||
StartDebugging(SimId, DebugSender),
|
StartDebugging(SimId, DebugSender),
|
||||||
StopDebugging(SimId),
|
StopDebugging(SimId),
|
||||||
DebugInterrupt(SimId),
|
DebugInterrupt(SimId),
|
||||||
@@ -700,6 +739,7 @@ pub enum SimState {
|
|||||||
pub enum EmulatorState {
|
pub enum EmulatorState {
|
||||||
Paused,
|
Paused,
|
||||||
Running,
|
Running,
|
||||||
|
Stepping,
|
||||||
Debugging,
|
Debugging,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{ffi::c_void, ptr, slice};
|
use std::{ffi::c_void, ptr, slice};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -91,7 +91,7 @@ type OnWrite = extern "C" fn(
|
|||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
|
||||||
#[link(name = "vb")]
|
#[link(name = "vb")]
|
||||||
extern "C" {
|
unsafe extern "C" {
|
||||||
#[link_name = "vbEmulate"]
|
#[link_name = "vbEmulate"]
|
||||||
fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int;
|
fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int;
|
||||||
#[link_name = "vbEmulateEx"]
|
#[link_name = "vbEmulateEx"]
|
||||||
@@ -170,7 +170,7 @@ extern "C" {
|
|||||||
fn vb_write(sim: *mut VB, address: u32, _type: VBDataType, value: i32) -> i32;
|
fn vb_write(sim: *mut VB, address: u32, _type: VBDataType, value: i32) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
||||||
// SAFETY: the *mut VB owns its userdata.
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
// 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.
|
||||||
@@ -179,7 +179,7 @@ extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int {
|
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int {
|
||||||
// SAFETY: the *mut VB owns its userdata.
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
// 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.
|
||||||
@@ -196,14 +196,10 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length:
|
|||||||
stopped = true;
|
stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if stopped {
|
if stopped { 1 } else { 0 }
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn on_read(
|
extern "C" fn on_read(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
address: u32,
|
address: u32,
|
||||||
@@ -229,7 +225,7 @@ extern "C" fn on_read(
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[unsafe(no_mangle)]
|
||||||
extern "C" fn on_write(
|
extern "C" fn on_write(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
address: u32,
|
address: u32,
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use registers::REGISTERS;
|
use registers::REGISTERS;
|
||||||
use request::{Request, RequestKind, RequestSource};
|
use request::{Request, RequestKind, RequestSource};
|
||||||
use response::Response;
|
use response::Response;
|
||||||
@@ -12,7 +12,7 @@ use tokio::{
|
|||||||
pin, select,
|
pin, select,
|
||||||
sync::{mpsc, oneshot},
|
sync::{mpsc, oneshot},
|
||||||
};
|
};
|
||||||
use tracing::{debug, enabled, error, info, Level};
|
use tracing::{Level, debug, enabled, error, info};
|
||||||
|
|
||||||
use crate::emulator::{
|
use crate::emulator::{
|
||||||
DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId, VBWatchpointType,
|
DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId, VBWatchpointType,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use atoi::FromRadix16;
|
use atoi::FromRadix16;
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt as _};
|
use tokio::io::{AsyncRead, AsyncReadExt as _};
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -1,12 +1,13 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
|
Arc, Mutex, MutexGuard,
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
mpsc, Arc, Mutex, MutexGuard,
|
mpsc,
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
Device, Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture,
|
Device, Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, Queue, Texture,
|
||||||
|
|||||||
+39
-39
@@ -7,17 +7,17 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
|
Color32, ColorImage, TextureHandle, TextureOptions,
|
||||||
epaint::ImageDelta,
|
epaint::ImageDelta,
|
||||||
load::{LoadError, SizedTexture, TextureLoader, TexturePoll},
|
load::{LoadError, SizedTexture, TextureLoader, TexturePoll},
|
||||||
Color32, ColorImage, TextureHandle, TextureOptions,
|
|
||||||
};
|
};
|
||||||
use tokio::{sync::mpsc, time::timeout};
|
use tokio::{sync::mpsc, time::timeout};
|
||||||
|
|
||||||
pub struct VramProcessor {
|
pub struct ImageProcessor {
|
||||||
sender: mpsc::UnboundedSender<Box<dyn VramRendererImpl>>,
|
sender: mpsc::UnboundedSender<Box<dyn ImageRendererImpl>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramProcessor {
|
impl ImageProcessor {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (sender, receiver) = mpsc::unbounded_channel();
|
let (sender, receiver) = mpsc::unbounded_channel();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
@@ -26,7 +26,7 @@ impl VramProcessor {
|
|||||||
.build()
|
.build()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let mut worker = VramProcessorWorker {
|
let mut worker = ImageProcessorWorker {
|
||||||
receiver,
|
receiver,
|
||||||
renderers: vec![],
|
renderers: vec![],
|
||||||
};
|
};
|
||||||
@@ -36,27 +36,27 @@ impl VramProcessor {
|
|||||||
Self { sender }
|
Self { sender }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add<const N: usize, R: VramRenderer<N> + 'static>(
|
pub fn add<const N: usize, R: ImageRenderer<N> + 'static>(
|
||||||
&self,
|
&self,
|
||||||
renderer: R,
|
renderer: R,
|
||||||
params: R::Params,
|
params: R::Params,
|
||||||
) -> ([VramImageHandle; N], VramParams<R::Params>) {
|
) -> ([ImageHandle; N], ImageParams<R::Params>) {
|
||||||
let states = renderer.sizes().map(VramRenderImageState::new);
|
let states = renderer.sizes().map(ImageState::new);
|
||||||
let handles = states.clone().map(|state| VramImageHandle {
|
let handles = states.clone().map(|state| ImageHandle {
|
||||||
size: state.size.map(|i| i as f32),
|
size: state.size.map(|i| i as f32),
|
||||||
data: state.sink,
|
data: state.sink,
|
||||||
});
|
});
|
||||||
let images = renderer
|
let images = renderer
|
||||||
.sizes()
|
.sizes()
|
||||||
.map(|[width, height]| VramImage::new(width, height));
|
.map(|[width, height]| ImageBuffer::new(width, height));
|
||||||
let sink = Arc::new(Mutex::new(params.clone()));
|
let sink = Arc::new(Mutex::new(params.clone()));
|
||||||
let _ = self.sender.send(Box::new(VramRendererWrapper {
|
let _ = self.sender.send(Box::new(ImageRendererWrapper {
|
||||||
renderer,
|
renderer,
|
||||||
params: Arc::downgrade(&sink),
|
params: Arc::downgrade(&sink),
|
||||||
images,
|
images,
|
||||||
states,
|
states,
|
||||||
}));
|
}));
|
||||||
let params = VramParams {
|
let params = ImageParams {
|
||||||
value: params,
|
value: params,
|
||||||
sink,
|
sink,
|
||||||
};
|
};
|
||||||
@@ -64,12 +64,12 @@ impl VramProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VramProcessorWorker {
|
struct ImageProcessorWorker {
|
||||||
receiver: mpsc::UnboundedReceiver<Box<dyn VramRendererImpl>>,
|
receiver: mpsc::UnboundedReceiver<Box<dyn ImageRendererImpl>>,
|
||||||
renderers: Vec<Box<dyn VramRendererImpl>>,
|
renderers: Vec<Box<dyn ImageRendererImpl>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramProcessorWorker {
|
impl ImageProcessorWorker {
|
||||||
async fn run(&mut self) {
|
async fn run(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if self.renderers.is_empty() {
|
if self.renderers.is_empty() {
|
||||||
@@ -100,12 +100,12 @@ impl VramProcessorWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VramImage {
|
pub struct ImageBuffer {
|
||||||
pub size: [usize; 2],
|
pub size: [usize; 2],
|
||||||
pub pixels: Vec<Color32>,
|
pub pixels: Vec<Color32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramImage {
|
impl ImageBuffer {
|
||||||
pub fn new(width: usize, height: usize) -> Self {
|
pub fn new(width: usize, height: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: [width, height],
|
size: [width, height],
|
||||||
@@ -143,23 +143,23 @@ impl VramImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct VramImageHandle {
|
pub struct ImageHandle {
|
||||||
size: [f32; 2],
|
size: [f32; 2],
|
||||||
data: Arc<Mutex<Option<Arc<ColorImage>>>>,
|
data: Arc<Mutex<Option<Arc<ColorImage>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramImageHandle {
|
impl ImageHandle {
|
||||||
fn pull(&mut self) -> Option<Arc<ColorImage>> {
|
fn pull(&mut self) -> Option<Arc<ColorImage>> {
|
||||||
self.data.lock().unwrap().take()
|
self.data.lock().unwrap().take()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VramParams<T> {
|
pub struct ImageParams<T> {
|
||||||
value: T,
|
value: T,
|
||||||
sink: Arc<Mutex<T>>,
|
sink: Arc<Mutex<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for VramParams<T> {
|
impl<T> Deref for ImageParams<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
@@ -167,7 +167,7 @@ impl<T> Deref for VramParams<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Eq> VramParams<T> {
|
impl<T: Clone + Eq> ImageParams<T> {
|
||||||
pub fn write(&mut self, value: T) {
|
pub fn write(&mut self, value: T) {
|
||||||
if self.value != value {
|
if self.value != value {
|
||||||
self.value = value.clone();
|
self.value = value.clone();
|
||||||
@@ -176,21 +176,21 @@ impl<T: Clone + Eq> VramParams<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait VramRenderer<const N: usize>: Send {
|
pub trait ImageRenderer<const N: usize>: Send {
|
||||||
type Params: Clone + Send;
|
type Params: Clone + Send;
|
||||||
fn sizes(&self) -> [[usize; 2]; N];
|
fn sizes(&self) -> [[usize; 2]; N];
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; N]);
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; N]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VramRenderImageState {
|
struct ImageState {
|
||||||
size: [usize; 2],
|
size: [usize; 2],
|
||||||
buffers: [Arc<ColorImage>; 2],
|
buffers: [Arc<ColorImage>; 2],
|
||||||
last_buffer: usize,
|
last_buffer: usize,
|
||||||
sink: Arc<Mutex<Option<Arc<ColorImage>>>>,
|
sink: Arc<Mutex<Option<Arc<ColorImage>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderImageState {
|
impl ImageState {
|
||||||
fn new(size: [usize; 2]) -> Self {
|
fn new(size: [usize; 2]) -> Self {
|
||||||
let buffers = [
|
let buffers = [
|
||||||
Arc::new(ColorImage::new(size, Color32::BLACK)),
|
Arc::new(ColorImage::new(size, Color32::BLACK)),
|
||||||
@@ -204,7 +204,7 @@ impl VramRenderImageState {
|
|||||||
sink: Arc::new(Mutex::new(Some(sink))),
|
sink: Arc::new(Mutex::new(Some(sink))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn try_send_update(&mut self, image: &VramImage) {
|
fn try_send_update(&mut self, image: &ImageBuffer) {
|
||||||
let last = &self.buffers[self.last_buffer];
|
let last = &self.buffers[self.last_buffer];
|
||||||
if !image.changed(last) {
|
if !image.changed(last) {
|
||||||
return;
|
return;
|
||||||
@@ -218,18 +218,18 @@ impl VramRenderImageState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VramRendererWrapper<const N: usize, R: VramRenderer<N>> {
|
struct ImageRendererWrapper<const N: usize, R: ImageRenderer<N>> {
|
||||||
renderer: R,
|
renderer: R,
|
||||||
params: Weak<Mutex<R::Params>>,
|
params: Weak<Mutex<R::Params>>,
|
||||||
images: [VramImage; N],
|
images: [ImageBuffer; N],
|
||||||
states: [VramRenderImageState; N],
|
states: [ImageState; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
trait VramRendererImpl: Send {
|
trait ImageRendererImpl: Send {
|
||||||
fn try_update(&mut self) -> Result<(), ()>;
|
fn try_update(&mut self) -> Result<(), ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize, R: VramRenderer<N> + Send> VramRendererImpl for VramRendererWrapper<N, R> {
|
impl<const N: usize, R: ImageRenderer<N> + Send> ImageRendererImpl for ImageRendererWrapper<N, R> {
|
||||||
fn try_update(&mut self) -> Result<(), ()> {
|
fn try_update(&mut self) -> Result<(), ()> {
|
||||||
let params = match self.params.upgrade() {
|
let params = match self.params.upgrade() {
|
||||||
Some(params) => params.lock().unwrap().clone(),
|
Some(params) => params.lock().unwrap().clone(),
|
||||||
@@ -247,12 +247,12 @@ impl<const N: usize, R: VramRenderer<N> + Send> VramRendererImpl for VramRendere
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VramTextureLoader {
|
pub struct ImageTextureLoader {
|
||||||
cache: Mutex<HashMap<String, (VramImageHandle, Option<TextureHandle>)>>,
|
cache: Mutex<HashMap<String, (ImageHandle, Option<TextureHandle>)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramTextureLoader {
|
impl ImageTextureLoader {
|
||||||
pub fn new(renderers: impl IntoIterator<Item = (String, VramImageHandle)>) -> Self {
|
pub fn new(renderers: impl IntoIterator<Item = (String, ImageHandle)>) -> Self {
|
||||||
let mut cache = HashMap::new();
|
let mut cache = HashMap::new();
|
||||||
for (key, image) in renderers {
|
for (key, image) in renderers {
|
||||||
cache.insert(key, (image, None));
|
cache.insert(key, (image, None));
|
||||||
@@ -263,9 +263,9 @@ impl VramTextureLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureLoader for VramTextureLoader {
|
impl TextureLoader for ImageTextureLoader {
|
||||||
fn id(&self) -> &str {
|
fn id(&self) -> &str {
|
||||||
concat!(module_path!(), "VramTextureLoader")
|
concat!(module_path!(), "ImageTextureLoader")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(
|
fn load(
|
||||||
+310
-3
@@ -1,12 +1,14 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
cmp::Ordering,
|
||||||
|
collections::{HashMap, hash_map::Entry},
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use gilrs::{ev::Code, Axis, Button, Gamepad, GamepadId};
|
use egui::{Event, Key, KeyboardShortcut, Modifiers};
|
||||||
|
use gilrs::{Axis, Button, Gamepad, GamepadId, ev::Code};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
|
|
||||||
@@ -454,3 +456,308 @@ struct PersistedGamepadMapping {
|
|||||||
default_buttons: Vec<(Code, VBKey)>,
|
default_buttons: Vec<(Code, VBKey)>,
|
||||||
default_axes: Vec<(Code, (VBKey, VBKey))>,
|
default_axes: Vec<(Code, (VBKey, VBKey))>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Shortcut {
|
||||||
|
pub shortcut: KeyboardShortcut,
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Command {
|
||||||
|
OpenRom,
|
||||||
|
Quit,
|
||||||
|
FrameAdvance,
|
||||||
|
FastForward(u32),
|
||||||
|
Reset,
|
||||||
|
PauseResume,
|
||||||
|
// if you update this, update Command::all and add a default
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn all() -> [Self; 6] {
|
||||||
|
[
|
||||||
|
Self::OpenRom,
|
||||||
|
Self::Quit,
|
||||||
|
Self::PauseResume,
|
||||||
|
Self::Reset,
|
||||||
|
Self::FrameAdvance,
|
||||||
|
Self::FastForward(0),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::OpenRom => "Open ROM",
|
||||||
|
Self::Quit => "Exit",
|
||||||
|
Self::PauseResume => "Pause/Resume",
|
||||||
|
Self::Reset => "Reset",
|
||||||
|
Self::FrameAdvance => "Frame Advance",
|
||||||
|
Self::FastForward(_) => "Fast Forward",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Command {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shortcuts {
|
||||||
|
all: Vec<(Command, KeyboardShortcut)>,
|
||||||
|
by_command: HashMap<Command, KeyboardShortcut>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Shortcuts {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut shortcuts = Shortcuts {
|
||||||
|
all: vec![],
|
||||||
|
by_command: HashMap::new(),
|
||||||
|
};
|
||||||
|
shortcuts.set(
|
||||||
|
Command::OpenRom,
|
||||||
|
KeyboardShortcut::new(Modifiers::COMMAND, Key::O),
|
||||||
|
);
|
||||||
|
shortcuts.set(
|
||||||
|
Command::Quit,
|
||||||
|
KeyboardShortcut::new(Modifiers::COMMAND, Key::Q),
|
||||||
|
);
|
||||||
|
shortcuts.set(
|
||||||
|
Command::PauseResume,
|
||||||
|
KeyboardShortcut::new(Modifiers::NONE, Key::F5),
|
||||||
|
);
|
||||||
|
shortcuts.set(
|
||||||
|
Command::Reset,
|
||||||
|
KeyboardShortcut::new(Modifiers::SHIFT, Key::F5),
|
||||||
|
);
|
||||||
|
shortcuts.set(
|
||||||
|
Command::FrameAdvance,
|
||||||
|
KeyboardShortcut::new(Modifiers::NONE, Key::F6),
|
||||||
|
);
|
||||||
|
shortcuts.set(
|
||||||
|
Command::FastForward(0),
|
||||||
|
KeyboardShortcut::new(Modifiers::NONE, Key::Space),
|
||||||
|
);
|
||||||
|
shortcuts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shortcuts {
|
||||||
|
fn set(&mut self, command: Command, shortcut: KeyboardShortcut) {
|
||||||
|
if self.by_command.insert(command, shortcut).is_some() {
|
||||||
|
for (cmd, sht) in &mut self.all {
|
||||||
|
if *cmd == command {
|
||||||
|
*sht = shortcut;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.all.push((command, shortcut));
|
||||||
|
}
|
||||||
|
self.all.sort_by(|l, r| order_shortcut(l.1, r.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, command: Command) {
|
||||||
|
if self.by_command.remove(&command).is_some() {
|
||||||
|
self.all.retain(|(c, _)| *c != command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, saved: &mut PersistedSettings) {
|
||||||
|
for command in Command::all() {
|
||||||
|
let shortcut = self.by_command.get(&command).copied();
|
||||||
|
saved.shortcuts.push((command, shortcut));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn order_shortcut(left: KeyboardShortcut, right: KeyboardShortcut) -> Ordering {
|
||||||
|
left.logical_key.cmp(&right.logical_key).then_with(|| {
|
||||||
|
specificity(left.modifiers)
|
||||||
|
.cmp(&specificity(right.modifiers))
|
||||||
|
.reverse()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn specificity(modifiers: egui::Modifiers) -> usize {
|
||||||
|
let mut mods = 0;
|
||||||
|
if modifiers.alt {
|
||||||
|
mods += 1;
|
||||||
|
}
|
||||||
|
if modifiers.command || modifiers.ctrl {
|
||||||
|
mods += 1;
|
||||||
|
}
|
||||||
|
if modifiers.shift {
|
||||||
|
mods += 1;
|
||||||
|
}
|
||||||
|
mods
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
struct PersistedSettings {
|
||||||
|
shortcuts: Vec<(Command, Option<KeyboardShortcut>)>,
|
||||||
|
#[serde(default)]
|
||||||
|
ff_settings: FastForwardSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct ShortcutState {
|
||||||
|
ff_toggled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Settings {
|
||||||
|
shortcuts: Shortcuts,
|
||||||
|
ff_settings: FastForwardSettings,
|
||||||
|
state: ShortcutState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
fn save(&self) -> PersistedSettings {
|
||||||
|
let mut saved = PersistedSettings {
|
||||||
|
shortcuts: vec![],
|
||||||
|
ff_settings: self.ff_settings.clone(),
|
||||||
|
};
|
||||||
|
self.shortcuts.save(&mut saved);
|
||||||
|
saved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ShortcutProvider {
|
||||||
|
persistence: Persistence,
|
||||||
|
settings: Arc<Mutex<Settings>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShortcutProvider {
|
||||||
|
pub fn new(persistence: Persistence) -> Self {
|
||||||
|
let mut settings = Settings::default();
|
||||||
|
if let Ok(saved) = persistence.load_config::<PersistedSettings>("shortcuts") {
|
||||||
|
for (command, shortcut) in saved.shortcuts {
|
||||||
|
if let Some(shortcut) = shortcut {
|
||||||
|
settings.shortcuts.set(command, shortcut);
|
||||||
|
} else {
|
||||||
|
settings.shortcuts.unset(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
settings.ff_settings = saved.ff_settings;
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
persistence,
|
||||||
|
settings: Arc::new(Mutex::new(settings)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shortcut_for(&self, command: Command) -> Option<KeyboardShortcut> {
|
||||||
|
let lock = self.settings.lock().unwrap();
|
||||||
|
lock.shortcuts.by_command.get(&command).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ff_settings(&self) -> FastForwardSettings {
|
||||||
|
let lock = self.settings.lock().unwrap();
|
||||||
|
lock.ff_settings.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_all(&self, input: &mut egui::InputState) -> Vec<Command> {
|
||||||
|
let mut lock = self.settings.lock().unwrap();
|
||||||
|
let mut state = lock.state.clone();
|
||||||
|
let mut consumed = vec![];
|
||||||
|
for (command, shortcut) in &lock.shortcuts.all {
|
||||||
|
input.events.retain(|event| {
|
||||||
|
let Event::Key {
|
||||||
|
key,
|
||||||
|
pressed,
|
||||||
|
repeat,
|
||||||
|
modifiers,
|
||||||
|
..
|
||||||
|
} = event
|
||||||
|
else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if shortcut.logical_key != *key || !shortcut.modifiers.contains(*modifiers) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if matches!(command, Command::FastForward(_)) {
|
||||||
|
if *repeat {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let sped_up = if lock.ff_settings.toggle {
|
||||||
|
if !*pressed {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
state.ff_toggled = !state.ff_toggled;
|
||||||
|
state.ff_toggled
|
||||||
|
} else {
|
||||||
|
*pressed
|
||||||
|
};
|
||||||
|
let speed = if sped_up { lock.ff_settings.speed } else { 1 };
|
||||||
|
consumed.push(Command::FastForward(speed));
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
if !*pressed {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
consumed.push(*command);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
lock.state = state;
|
||||||
|
consumed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, command: Command, shortcut: KeyboardShortcut) {
|
||||||
|
let updated = {
|
||||||
|
let mut lock = self.settings.lock().unwrap();
|
||||||
|
lock.shortcuts.set(command, shortcut);
|
||||||
|
lock.save()
|
||||||
|
};
|
||||||
|
let _ = self.persistence.save_config("shortcuts", &updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unset(&self, command: Command) {
|
||||||
|
let updated = {
|
||||||
|
let mut lock = self.settings.lock().unwrap();
|
||||||
|
lock.shortcuts.unset(command);
|
||||||
|
lock.save()
|
||||||
|
};
|
||||||
|
let _ = self.persistence.save_config("shortcuts", &updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_ff_settings(&self, ff_settings: FastForwardSettings) {
|
||||||
|
let updated = {
|
||||||
|
let mut lock = self.settings.lock().unwrap();
|
||||||
|
lock.ff_settings = ff_settings;
|
||||||
|
if !lock.ff_settings.toggle {
|
||||||
|
lock.state.ff_toggled = false;
|
||||||
|
}
|
||||||
|
lock.save()
|
||||||
|
};
|
||||||
|
let _ = self.persistence.save_config("shortcuts", &updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self) {
|
||||||
|
let updated = {
|
||||||
|
let mut lock = self.settings.lock().unwrap();
|
||||||
|
*lock = Settings::default();
|
||||||
|
lock.save()
|
||||||
|
};
|
||||||
|
let _ = self.persistence.save_config("shortcuts", &updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct FastForwardSettings {
|
||||||
|
pub toggle: bool,
|
||||||
|
pub speed: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FastForwardSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
toggle: false,
|
||||||
|
speed: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -3,13 +3,13 @@
|
|||||||
|
|
||||||
use std::{path::PathBuf, process, time::SystemTime};
|
use std::{path::PathBuf, process, time::SystemTime};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use app::Application;
|
use app::Application;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use emulator::EmulatorBuilder;
|
use emulator::EmulatorBuilder;
|
||||||
use thread_priority::{ThreadBuilder, ThreadPriority};
|
use thread_priority::{ThreadBuilder, ThreadPriority};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
|
use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
@@ -18,10 +18,10 @@ mod controller;
|
|||||||
mod emulator;
|
mod emulator;
|
||||||
mod gdbserver;
|
mod gdbserver;
|
||||||
mod graphics;
|
mod graphics;
|
||||||
|
mod images;
|
||||||
mod input;
|
mod input;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
mod vram;
|
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|||||||
+5
-5
@@ -2,7 +2,7 @@ use std::{
|
|||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
iter::FusedIterator,
|
iter::FusedIterator,
|
||||||
sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard, TryLockError, Weak},
|
sync::{Arc, Mutex, RwLock, RwLockReadGuard, TryLockError, Weak, atomic::AtomicU64},
|
||||||
};
|
};
|
||||||
|
|
||||||
use bytemuck::BoxBytes;
|
use bytemuck::BoxBytes;
|
||||||
@@ -98,11 +98,11 @@ macro_rules! primitive_memory_value_impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
primitive_memory_value_impl!(u8, 1);
|
primitive_memory_value_impl!(u8, 1);
|
||||||
primitive_memory_value_impl!(i8, 2);
|
primitive_memory_value_impl!(i8, 1);
|
||||||
primitive_memory_value_impl!(u16, 2);
|
primitive_memory_value_impl!(u16, 2);
|
||||||
primitive_memory_value_impl!(i16, 2);
|
primitive_memory_value_impl!(i16, 2);
|
||||||
primitive_memory_value_impl!(u32, 4);
|
primitive_memory_value_impl!(u32, 4);
|
||||||
primitive_memory_value_impl!(i32, 2);
|
primitive_memory_value_impl!(i32, 4);
|
||||||
|
|
||||||
impl<const N: usize, T: MemoryValue> MemoryValue for [T; N] {
|
impl<const N: usize, T: MemoryValue> MemoryValue for [T; N] {
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -223,7 +223,7 @@ impl MemoryRegion {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|i| i.load(std::sync::atomic::Ordering::Acquire))
|
.map(|i| i.load(std::sync::atomic::Ordering::Acquire))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.max_by_key(|(_, gen)| *gen)
|
.max_by_key(|(_, g)| *g)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let inner = match self.bufs[newest_index].try_read() {
|
let inner = match self.bufs[newest_index].try_read() {
|
||||||
@@ -263,7 +263,7 @@ impl MemoryRegion {
|
|||||||
/*
|
/*
|
||||||
* We have four buffers, and (at time of writing) only three threads interacting with memory:
|
* We have four buffers, and (at time of writing) only three threads interacting with memory:
|
||||||
* - The UI thread, reading small regions of memory
|
* - The UI thread, reading small regions of memory
|
||||||
* - The "vram renderer" thread, reading large regions of memory
|
* - The "image renderer" thread, reading large regions of memory
|
||||||
* - The emulation thread, writing memory every so often
|
* - 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,
|
* 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
|
* and (because readers always read the newest buffer) at least one of the oldest three
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -2,8 +2,9 @@ pub use about::AboutWindow;
|
|||||||
use egui::{Context, ViewportBuilder, ViewportId};
|
use egui::{Context, ViewportBuilder, ViewportId};
|
||||||
pub use game::GameWindow;
|
pub use game::GameWindow;
|
||||||
pub use gdb::GdbServerWindow;
|
pub use gdb::GdbServerWindow;
|
||||||
|
pub use hotkeys::HotkeysWindow;
|
||||||
pub use input::InputWindow;
|
pub use input::InputWindow;
|
||||||
pub use vram::{
|
pub use vip::{
|
||||||
BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow,
|
BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, RegisterWindow, WorldWindow,
|
||||||
};
|
};
|
||||||
use winit::event::KeyEvent;
|
use winit::event::KeyEvent;
|
||||||
@@ -14,9 +15,10 @@ mod about;
|
|||||||
mod game;
|
mod game;
|
||||||
mod game_screen;
|
mod game_screen;
|
||||||
mod gdb;
|
mod gdb;
|
||||||
|
mod hotkeys;
|
||||||
mod input;
|
mod input;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod vram;
|
mod vip;
|
||||||
|
|
||||||
pub trait AppWindow {
|
pub trait AppWindow {
|
||||||
fn viewport_id(&self) -> ViewportId;
|
fn viewport_id(&self) -> ViewportId;
|
||||||
|
|||||||
+103
-12
@@ -3,20 +3,21 @@ use std::sync::mpsc;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::UserEvent,
|
app::UserEvent,
|
||||||
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
|
emulator::{EmulatorClient, EmulatorCommand, EmulatorState, SimId, SimState},
|
||||||
|
input::{Command, ShortcutProvider},
|
||||||
persistence::Persistence,
|
persistence::Persistence,
|
||||||
};
|
};
|
||||||
use egui::{
|
use egui::{
|
||||||
menu, Align2, Button, CentralPanel, Color32, Context, Direction, Frame, TopBottomPanel, Ui,
|
Align2, Button, CentralPanel, Color32, Context, Direction, Frame, TopBottomPanel, Ui, Vec2,
|
||||||
Vec2, ViewportBuilder, ViewportCommand, ViewportId, Window,
|
ViewportBuilder, ViewportCommand, ViewportId, Window, menu,
|
||||||
};
|
};
|
||||||
use egui_toast::{Toast, Toasts};
|
use egui_toast::{Toast, Toasts};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use winit::event_loop::EventLoopProxy;
|
use winit::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
AppWindow,
|
||||||
game_screen::{DisplayMode, GameScreen},
|
game_screen::{DisplayMode, GameScreen},
|
||||||
utils::UiExt as _,
|
utils::UiExt as _,
|
||||||
AppWindow,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_PRESETS: [[Color32; 2]; 3] = [
|
const COLOR_PRESETS: [[Color32; 2]; 3] = [
|
||||||
@@ -38,6 +39,7 @@ pub struct GameWindow {
|
|||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
persistence: Persistence,
|
persistence: Persistence,
|
||||||
|
shortcuts: ShortcutProvider,
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
config: GameConfig,
|
config: GameConfig,
|
||||||
screen: Option<GameScreen>,
|
screen: Option<GameScreen>,
|
||||||
@@ -50,6 +52,7 @@ impl GameWindow {
|
|||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
proxy: EventLoopProxy<UserEvent>,
|
proxy: EventLoopProxy<UserEvent>,
|
||||||
persistence: Persistence,
|
persistence: Persistence,
|
||||||
|
shortcuts: ShortcutProvider,
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let config = load_config(&persistence, sim_id);
|
let config = load_config(&persistence, sim_id);
|
||||||
@@ -57,6 +60,7 @@ impl GameWindow {
|
|||||||
client,
|
client,
|
||||||
proxy,
|
proxy,
|
||||||
persistence,
|
persistence,
|
||||||
|
shortcuts,
|
||||||
sim_id,
|
sim_id,
|
||||||
config,
|
config,
|
||||||
screen: None,
|
screen: None,
|
||||||
@@ -66,8 +70,57 @@ impl GameWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) {
|
fn show_menu(&mut self, ctx: &Context, ui: &mut Ui) {
|
||||||
|
let state = self.client.emulator_state();
|
||||||
|
let is_ready = self.client.sim_state(self.sim_id) == SimState::Ready;
|
||||||
|
let can_pause = is_ready && state == EmulatorState::Running;
|
||||||
|
let can_resume = is_ready && state == EmulatorState::Paused;
|
||||||
|
let can_frame_advance = is_ready && state != EmulatorState::Debugging;
|
||||||
|
|
||||||
|
for command in ui.input_mut(|input| self.shortcuts.consume_all(input)) {
|
||||||
|
match command {
|
||||||
|
Command::OpenRom => {
|
||||||
|
let rom = rfd::FileDialog::new()
|
||||||
|
.add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
|
||||||
|
.pick_file();
|
||||||
|
if let Some(path) = rom {
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::LoadGame(self.sim_id, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Quit => {
|
||||||
|
let _ = self.proxy.send_event(UserEvent::Quit(self.sim_id));
|
||||||
|
}
|
||||||
|
Command::PauseResume => {
|
||||||
|
if state == EmulatorState::Paused && can_resume {
|
||||||
|
self.client.send_command(EmulatorCommand::Resume);
|
||||||
|
}
|
||||||
|
if state == EmulatorState::Running && can_pause {
|
||||||
|
self.client.send_command(EmulatorCommand::Pause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::Reset => {
|
||||||
|
if is_ready {
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::Reset(self.sim_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::FrameAdvance => {
|
||||||
|
if can_frame_advance {
|
||||||
|
self.client.send_command(EmulatorCommand::FrameAdvance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::FastForward(speed) => {
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::SetSpeed(speed as f64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.menu_button("ROM", |ui| {
|
ui.menu_button("ROM", |ui| {
|
||||||
if ui.button("Open ROM").clicked() {
|
if ui
|
||||||
|
.add(self.button_for(ui.ctx(), "Open ROM", Command::OpenRom))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
let rom = rfd::FileDialog::new()
|
let rom = rfd::FileDialog::new()
|
||||||
.add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
|
.add_filter("Virtual Boy ROMs", &["vb", "vbrom"])
|
||||||
.pick_file();
|
.pick_file();
|
||||||
@@ -77,29 +130,54 @@ impl GameWindow {
|
|||||||
}
|
}
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.button("Quit").clicked() {
|
if ui
|
||||||
|
.add(self.button_for(ui.ctx(), "Quit", Command::Quit))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
let _ = self.proxy.send_event(UserEvent::Quit(self.sim_id));
|
let _ = self.proxy.send_event(UserEvent::Quit(self.sim_id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Emulation", |ui| {
|
ui.menu_button("Emulation", |ui| {
|
||||||
let state = self.client.emulator_state();
|
|
||||||
let is_ready = self.client.sim_state(self.sim_id) == SimState::Ready;
|
|
||||||
let can_pause = is_ready && state == EmulatorState::Running;
|
|
||||||
let can_resume = is_ready && state == EmulatorState::Paused;
|
|
||||||
if state == EmulatorState::Running {
|
if state == EmulatorState::Running {
|
||||||
if ui.add_enabled(can_pause, Button::new("Pause")).clicked() {
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
can_pause,
|
||||||
|
self.button_for(ui.ctx(), "Pause", Command::PauseResume),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
self.client.send_command(EmulatorCommand::Pause);
|
self.client.send_command(EmulatorCommand::Pause);
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
} else if ui.add_enabled(can_resume, Button::new("Resume")).clicked() {
|
} else if ui
|
||||||
|
.add_enabled(
|
||||||
|
can_resume,
|
||||||
|
self.button_for(ui.ctx(), "Resume", Command::PauseResume),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
self.client.send_command(EmulatorCommand::Resume);
|
self.client.send_command(EmulatorCommand::Resume);
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
if ui.add_enabled(is_ready, Button::new("Reset")).clicked() {
|
if ui
|
||||||
|
.add_enabled(is_ready, self.button_for(ui.ctx(), "Reset", Command::Reset))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::Reset(self.sim_id));
|
.send_command(EmulatorCommand::Reset(self.sim_id));
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
can_frame_advance,
|
||||||
|
self.button_for(ui.ctx(), "Frame Advance", Command::FrameAdvance),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.client.send_command(EmulatorCommand::FrameAdvance);
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui));
|
ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui));
|
||||||
ui.menu_button("Multiplayer", |ui| {
|
ui.menu_button("Multiplayer", |ui| {
|
||||||
@@ -132,6 +210,7 @@ impl GameWindow {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
|
ui.separator();
|
||||||
if ui.button("Character Data").clicked() {
|
if ui.button("Character Data").clicked() {
|
||||||
self.proxy
|
self.proxy
|
||||||
.send_event(UserEvent::OpenCharacterData(self.sim_id))
|
.send_event(UserEvent::OpenCharacterData(self.sim_id))
|
||||||
@@ -283,6 +362,10 @@ impl GameWindow {
|
|||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if ui.button("Hotkeys").clicked() {
|
||||||
|
self.proxy.send_event(UserEvent::OpenHotkeys).unwrap();
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_color_picker(&mut self, ui: &mut Ui) {
|
fn show_color_picker(&mut self, ui: &mut Ui) {
|
||||||
@@ -324,6 +407,14 @@ impl GameWindow {
|
|||||||
}
|
}
|
||||||
self.config = new_config;
|
self.config = new_config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn button_for(&self, ctx: &Context, text: &str, command: Command) -> Button {
|
||||||
|
let button = Button::new(text);
|
||||||
|
match self.shortcuts.shortcut_for(command) {
|
||||||
|
Some(shortcut) => button.shortcut_text(ctx.format_shortcut(&shortcut)),
|
||||||
|
None => button,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_filename(sim_id: SimId) -> &'static str {
|
fn config_filename(sim_id: SimId) -> &'static str {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
|
|
||||||
use egui::{Color32, Rgba, Vec2, Widget};
|
use egui::{Color32, Rgba, Vec2, Widget};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use wgpu::{util::DeviceExt as _, BindGroup, BindGroupLayout, Buffer, RenderPipeline};
|
use wgpu::{BindGroup, BindGroupLayout, Buffer, RenderPipeline, util::DeviceExt as _};
|
||||||
|
|
||||||
use crate::graphics::TextureSink;
|
use crate::graphics::TextureSink;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ impl GameScreen {
|
|||||||
module: &shader,
|
module: &shader,
|
||||||
entry_point: Some(entry_point),
|
entry_point: Some(entry_point),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: wgpu::TextureFormat::Bgra8Unorm,
|
format: render_state.target_format,
|
||||||
blend: Some(wgpu::BlendState::REPLACE),
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
write_mask: wgpu::ColorWrites::ALL,
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
})],
|
})],
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
use egui::{
|
||||||
|
Button, CentralPanel, Context, Event, KeyboardShortcut, Label, Layout, Slider, Ui,
|
||||||
|
ViewportBuilder, ViewportId,
|
||||||
|
};
|
||||||
|
use egui_extras::{Column, TableBuilder};
|
||||||
|
|
||||||
|
use crate::input::{Command, ShortcutProvider};
|
||||||
|
|
||||||
|
use super::{AppWindow, utils::UiExt};
|
||||||
|
|
||||||
|
pub struct HotkeysWindow {
|
||||||
|
shortcuts: ShortcutProvider,
|
||||||
|
now_binding: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HotkeysWindow {
|
||||||
|
pub fn new(shortcuts: ShortcutProvider) -> Self {
|
||||||
|
Self {
|
||||||
|
shortcuts,
|
||||||
|
now_binding: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_shortcuts(&mut self, ui: &mut Ui) {
|
||||||
|
let row_height = ui.spacing().interact_size.y;
|
||||||
|
ui.section("Shortcuts", |ui| {
|
||||||
|
let width = ui.available_width() - 16.0;
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.column(Column::exact(width * 0.3))
|
||||||
|
.column(Column::exact(width * 0.5))
|
||||||
|
.column(Column::exact(width * 0.2))
|
||||||
|
.cell_layout(Layout::left_to_right(egui::Align::Center))
|
||||||
|
.body(|mut body| {
|
||||||
|
for command in Command::all() {
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add_sized(ui.available_size(), Label::new(command.name()));
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let button = if self.now_binding == Some(command) {
|
||||||
|
Button::new("Binding...")
|
||||||
|
} else if let Some(shortcut) = self.shortcuts.shortcut_for(command)
|
||||||
|
{
|
||||||
|
Button::new(ui.ctx().format_shortcut(&shortcut))
|
||||||
|
} else {
|
||||||
|
Button::new("")
|
||||||
|
};
|
||||||
|
if ui.add_sized(ui.available_size(), button).clicked() {
|
||||||
|
self.now_binding = Some(command);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if ui
|
||||||
|
.add_sized(ui.available_size(), Button::new("Clear"))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
self.shortcuts.unset(command);
|
||||||
|
self.now_binding = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if let Some(command) = self.now_binding {
|
||||||
|
if let Some(shortcut) = ui.input_mut(|i| {
|
||||||
|
i.events.iter().find_map(|event| match event {
|
||||||
|
Event::Key {
|
||||||
|
key,
|
||||||
|
pressed: true,
|
||||||
|
modifiers,
|
||||||
|
..
|
||||||
|
} => Some(KeyboardShortcut::new(*modifiers, *key)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
self.shortcuts.set(command, shortcut);
|
||||||
|
self.now_binding = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_ff_settings(&mut self, ui: &mut Ui) {
|
||||||
|
let row_height = ui.spacing().interact_size.y;
|
||||||
|
ui.section("Fast Forward", |ui| {
|
||||||
|
let width = ui.available_width() - 8.0;
|
||||||
|
let mut ff_settings = self.shortcuts.ff_settings();
|
||||||
|
let mut updated = false;
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.column(Column::exact(width * 0.5))
|
||||||
|
.column(Column::exact(width * 0.5))
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Treat button as toggle");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if ui.checkbox(&mut ff_settings.toggle, "").changed() {
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Speed multiplier");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if ui
|
||||||
|
.add_sized(
|
||||||
|
ui.available_size(),
|
||||||
|
Slider::new(&mut ff_settings.speed, 1..=15),
|
||||||
|
)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if updated {
|
||||||
|
self.shortcuts.update_ff_settings(ff_settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWindow for HotkeysWindow {
|
||||||
|
fn viewport_id(&self) -> ViewportId {
|
||||||
|
ViewportId::from_hash_of("shortcuts")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_viewport(&self) -> ViewportBuilder {
|
||||||
|
ViewportBuilder::default()
|
||||||
|
.with_title("Keyboard Shortcuts")
|
||||||
|
.with_inner_size((400.0, 400.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ctx: &Context) {
|
||||||
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Use defaults").clicked() {
|
||||||
|
self.shortcuts.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
self.show_shortcuts(ui);
|
||||||
|
self.show_ff_settings(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -6,9 +6,9 @@ use std::{
|
|||||||
|
|
||||||
use atoi::FromRadix16;
|
use atoi::FromRadix16;
|
||||||
use egui::{
|
use egui::{
|
||||||
ecolor::HexColor, Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect,
|
Align, Color32, CursorIcon, Event, Frame, Key, Layout, Margin, Rect, Response, RichText,
|
||||||
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget, WidgetText,
|
||||||
WidgetText,
|
ecolor::HexColor,
|
||||||
};
|
};
|
||||||
use num_traits::{CheckedAdd, CheckedSub, One};
|
use num_traits::{CheckedAdd, CheckedSub, One};
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,22 +20,22 @@ use super::utils::{self, CellData, CharacterGrid};
|
|||||||
|
|
||||||
pub struct BgMapWindow {
|
pub struct BgMapWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<ImageTextureLoader>,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
bgmaps: MemoryView,
|
bgmaps: MemoryView,
|
||||||
cell_index: usize,
|
cell_index: usize,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
params: VramParams<BgMapParams>,
|
params: ImageParams<BgMapParams>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
show_grid: bool,
|
show_grid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BgMapWindow {
|
impl BgMapWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
|
||||||
let renderer = BgMapRenderer::new(sim_id, memory);
|
let renderer = BgMapRenderer::new(sim_id, memory);
|
||||||
let ([cell, bgmap], params) = vram.add(renderer, BgMapParams::default());
|
let ([cell, bgmap], params) = images.add(renderer, BgMapParams::default());
|
||||||
let loader =
|
let loader =
|
||||||
VramTextureLoader::new([("vram://cell".into(), cell), ("vram://bgmap".into(), bgmap)]);
|
ImageTextureLoader::new([("vip://cell".into(), cell), ("vip://bgmap".into(), bgmap)]);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
loader: Arc::new(loader),
|
loader: Arc::new(loader),
|
||||||
@@ -90,7 +90,7 @@ impl BgMapWindow {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let image = Image::new("vram://cell")
|
let image = Image::new("vip://cell")
|
||||||
.maintain_aspect_ratio(true)
|
.maintain_aspect_ratio(true)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
ui.add(image);
|
||||||
@@ -162,7 +162,7 @@ impl BgMapWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_bgmap(&mut self, ui: &mut Ui) {
|
fn show_bgmap(&mut self, ui: &mut Ui) {
|
||||||
let grid = CharacterGrid::new("vram://bgmap")
|
let grid = CharacterGrid::new("vip://bgmap")
|
||||||
.with_scale(self.scale)
|
.with_scale(self.scale)
|
||||||
.with_grid(self.show_grid)
|
.with_grid(self.show_grid)
|
||||||
.with_selected(self.cell_index % 4096);
|
.with_selected(self.cell_index % 4096);
|
||||||
@@ -233,7 +233,7 @@ impl BgMapRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_bgmap(&self, image: &mut VramImage, bgmap_index: usize, generic_palette: bool) {
|
fn render_bgmap(&self, image: &mut ImageBuffer, 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();
|
||||||
@@ -266,7 +266,7 @@ impl BgMapRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_bgmap_cell(&self, image: &mut VramImage, index: usize, generic_palette: bool) {
|
fn render_bgmap_cell(&self, image: &mut ImageBuffer, 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,14 +297,14 @@ impl BgMapRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderer<2> for BgMapRenderer {
|
impl ImageRenderer<2> for BgMapRenderer {
|
||||||
type Params = BgMapParams;
|
type Params = BgMapParams;
|
||||||
|
|
||||||
fn sizes(&self) -> [[usize; 2]; 2] {
|
fn sizes(&self) -> [[usize; 2]; 2] {
|
||||||
[[8, 8], [8 * 64, 8 * 64]]
|
[[8, 8], [8 * 64, 8 * 64]]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 2]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
||||||
self.render_bgmap_cell(&mut images[0], params.cell_index, params.generic_palette);
|
self.render_bgmap_cell(&mut images[0], params.cell_index, params.generic_palette);
|
||||||
self.render_bgmap(
|
self.render_bgmap(
|
||||||
&mut images[1],
|
&mut images[1],
|
||||||
@@ -9,18 +9,18 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt as _},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt as _},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::utils::{self, CharacterGrid};
|
use super::utils::{self, CharacterGrid};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum VramPalette {
|
pub enum Palette {
|
||||||
#[default]
|
#[default]
|
||||||
Generic,
|
Generic,
|
||||||
Bg0,
|
Bg0,
|
||||||
@@ -33,8 +33,8 @@ pub enum VramPalette {
|
|||||||
Obj3,
|
Obj3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramPalette {
|
impl Palette {
|
||||||
pub const fn values() -> [VramPalette; 9] {
|
pub const fn values() -> [Palette; 9] {
|
||||||
[
|
[
|
||||||
Self::Generic,
|
Self::Generic,
|
||||||
Self::Bg0,
|
Self::Bg0,
|
||||||
@@ -63,7 +63,7 @@ impl VramPalette {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for VramPalette {
|
impl Display for Palette {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Generic => f.write_str("Generic"),
|
Self::Generic => f.write_str("Generic"),
|
||||||
@@ -81,23 +81,23 @@ impl Display for VramPalette {
|
|||||||
|
|
||||||
pub struct CharacterDataWindow {
|
pub struct CharacterDataWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<ImageTextureLoader>,
|
||||||
brightness: MemoryView,
|
brightness: MemoryView,
|
||||||
palettes: MemoryView,
|
palettes: MemoryView,
|
||||||
palette: VramPalette,
|
palette: Palette,
|
||||||
index: usize,
|
index: usize,
|
||||||
params: VramParams<CharDataParams>,
|
params: ImageParams<CharDataParams>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
show_grid: bool,
|
show_grid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CharacterDataWindow {
|
impl CharacterDataWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self {
|
||||||
let renderer = CharDataRenderer::new(sim_id, memory);
|
let renderer = CharDataRenderer::new(sim_id, memory);
|
||||||
let ([char, chardata], params) = vram.add(renderer, CharDataParams::default());
|
let ([char, chardata], params) = images.add(renderer, CharDataParams::default());
|
||||||
let loader = VramTextureLoader::new([
|
let loader = ImageTextureLoader::new([
|
||||||
("vram://char".into(), char),
|
("vip://char".into(), char),
|
||||||
("vram://chardata".into(), chardata),
|
("vip://chardata".into(), chardata),
|
||||||
]);
|
]);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
@@ -160,7 +160,7 @@ impl CharacterDataWindow {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let image = Image::new("vram://char")
|
let image = Image::new("vip://char")
|
||||||
.maintain_aspect_ratio(true)
|
.maintain_aspect_ratio(true)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
ui.add(image);
|
||||||
@@ -171,7 +171,7 @@ impl CharacterDataWindow {
|
|||||||
.selected_text(self.palette.to_string())
|
.selected_text(self.palette.to_string())
|
||||||
.width(ui.available_width())
|
.width(ui.available_width())
|
||||||
.show_ui(ui, |ui| {
|
.show_ui(ui, |ui| {
|
||||||
for palette in VramPalette::values() {
|
for palette in Palette::values() {
|
||||||
ui.selectable_value(
|
ui.selectable_value(
|
||||||
&mut self.palette,
|
&mut self.palette,
|
||||||
palette,
|
palette,
|
||||||
@@ -226,7 +226,7 @@ impl CharacterDataWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_chardata(&mut self, ui: &mut Ui) {
|
fn show_chardata(&mut self, ui: &mut Ui) {
|
||||||
let grid = CharacterGrid::new("vram://chardata")
|
let grid = CharacterGrid::new("vip://chardata")
|
||||||
.with_scale(self.scale)
|
.with_scale(self.scale)
|
||||||
.with_grid(self.show_grid)
|
.with_grid(self.show_grid)
|
||||||
.with_selected(self.index);
|
.with_selected(self.index);
|
||||||
@@ -276,13 +276,13 @@ impl AppWindow for CharacterDataWindow {
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
enum CharDataResource {
|
enum CharDataResource {
|
||||||
Character { palette: VramPalette, index: usize },
|
Character { palette: Palette, index: usize },
|
||||||
CharacterData { palette: VramPalette },
|
CharacterData { palette: Palette },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, PartialEq, Eq)]
|
#[derive(Clone, Default, PartialEq, Eq)]
|
||||||
struct CharDataParams {
|
struct CharDataParams {
|
||||||
palette: VramPalette,
|
palette: Palette,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,14 +292,14 @@ struct CharDataRenderer {
|
|||||||
palettes: MemoryView,
|
palettes: MemoryView,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderer<2> for CharDataRenderer {
|
impl ImageRenderer<2> for CharDataRenderer {
|
||||||
type Params = CharDataParams;
|
type Params = CharDataParams;
|
||||||
|
|
||||||
fn sizes(&self) -> [[usize; 2]; 2] {
|
fn sizes(&self) -> [[usize; 2]; 2] {
|
||||||
[[8, 8], [16 * 8, 128 * 8]]
|
[[8, 8], [16 * 8, 128 * 8]]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, image: &mut [VramImage; 2]) {
|
fn render(&mut self, params: &Self::Params, image: &mut [ImageBuffer; 2]) {
|
||||||
self.render_character(&mut image[0], params.palette, params.index);
|
self.render_character(&mut image[0], params.palette, params.index);
|
||||||
self.render_character_data(&mut image[1], params.palette);
|
self.render_character_data(&mut image[1], params.palette);
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_character(&self, image: &mut VramImage, palette: VramPalette, index: usize) {
|
fn render_character(&self, image: &mut ImageBuffer, palette: Palette, index: usize) {
|
||||||
if index >= 2048 {
|
if index >= 2048 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -329,7 +329,7 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_character_data(&self, image: &mut VramImage, palette: VramPalette) {
|
fn render_character_data(&self, image: &mut ImageBuffer, palette: Palette) {
|
||||||
let palette = self.load_palette(palette);
|
let palette = self.load_palette(palette);
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
for (row, pixels) in chardata.range::<u16>(0, 8 * 2048).enumerate() {
|
for (row, pixels) in chardata.range::<u16>(0, 8 * 2048).enumerate() {
|
||||||
@@ -344,7 +344,7 @@ impl CharDataRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_palette(&self, palette: VramPalette) -> [Color32; 4] {
|
fn load_palette(&self, palette: Palette) -> [Color32; 4] {
|
||||||
let Some(offset) = palette.offset() else {
|
let Some(offset) = palette.offset() else {
|
||||||
return utils::GENERIC_PALETTE.map(|p| utils::shade(p, Color32::RED));
|
return utils::GENERIC_PALETTE.map(|p| utils::shade(p, Color32::RED));
|
||||||
};
|
};
|
||||||
@@ -8,11 +8,11 @@ use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt as _},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt as _},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,17 +20,17 @@ use super::utils;
|
|||||||
|
|
||||||
pub struct FrameBufferWindow {
|
pub struct FrameBufferWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<ImageTextureLoader>,
|
||||||
index: usize,
|
index: usize,
|
||||||
left: bool,
|
left: bool,
|
||||||
right: bool,
|
right: bool,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
params: VramParams<FrameBufferParams>,
|
params: ImageParams<FrameBufferParams>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameBufferWindow {
|
impl FrameBufferWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self {
|
||||||
let initial_params = FrameBufferParams {
|
let initial_params = FrameBufferParams {
|
||||||
index: 0,
|
index: 0,
|
||||||
left: true,
|
left: true,
|
||||||
@@ -40,8 +40,8 @@ impl FrameBufferWindow {
|
|||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
||||||
};
|
};
|
||||||
let renderer = FrameBufferRenderer::new(sim_id, memory);
|
let renderer = FrameBufferRenderer::new(sim_id, memory);
|
||||||
let ([buffer], params) = vram.add(renderer, initial_params);
|
let ([buffer], params) = images.add(renderer, initial_params);
|
||||||
let loader = VramTextureLoader::new([("vram://buffer".into(), buffer)]);
|
let loader = ImageTextureLoader::new([("vip://buffer".into(), buffer)]);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
loader: Arc::new(loader),
|
loader: Arc::new(loader),
|
||||||
@@ -131,7 +131,7 @@ impl FrameBufferWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_buffers(&mut self, ui: &mut Ui) {
|
fn show_buffers(&mut self, ui: &mut Ui) {
|
||||||
let image = Image::new("vram://buffer")
|
let image = Image::new("vip://buffer")
|
||||||
.fit_to_original_size(self.scale)
|
.fit_to_original_size(self.scale)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
ui.add(image);
|
||||||
@@ -205,18 +205,18 @@ impl FrameBufferRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderer<1> for FrameBufferRenderer {
|
impl ImageRenderer<1> for FrameBufferRenderer {
|
||||||
type Params = FrameBufferParams;
|
type Params = FrameBufferParams;
|
||||||
|
|
||||||
fn sizes(&self) -> [[usize; 2]; 1] {
|
fn sizes(&self) -> [[usize; 2]; 1] {
|
||||||
[[384, 224]]
|
[[384, 224]]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 1]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) {
|
||||||
let image = &mut images[0];
|
let image = &mut images[0];
|
||||||
|
|
||||||
let left_buffer = self.buffers[params.index * 2].borrow();
|
let left_buffer = self.buffers[params.index].borrow();
|
||||||
let right_buffer = self.buffers[params.index * 2 + 1].borrow();
|
let right_buffer = self.buffers[2 + params.index].borrow();
|
||||||
|
|
||||||
let colors = if params.generic_palette {
|
let colors = if params.generic_palette {
|
||||||
[
|
[
|
||||||
@@ -8,11 +8,11 @@ use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryView},
|
memory::{MemoryClient, MemoryView},
|
||||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt as _},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt as _},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,17 +20,17 @@ use super::utils::{self, Object};
|
|||||||
|
|
||||||
pub struct ObjectWindow {
|
pub struct ObjectWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<ImageTextureLoader>,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
objects: MemoryView,
|
objects: MemoryView,
|
||||||
index: usize,
|
index: usize,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
params: VramParams<ObjectParams>,
|
params: ImageParams<ObjectParams>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectWindow {
|
impl ObjectWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
|
||||||
let initial_params = ObjectParams {
|
let initial_params = ObjectParams {
|
||||||
index: 0,
|
index: 0,
|
||||||
generic_palette: false,
|
generic_palette: false,
|
||||||
@@ -38,9 +38,9 @@ impl ObjectWindow {
|
|||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
||||||
};
|
};
|
||||||
let renderer = ObjectRenderer::new(sim_id, memory);
|
let renderer = ObjectRenderer::new(sim_id, memory);
|
||||||
let ([zoom, full], params) = vram.add(renderer, initial_params);
|
let ([zoom, full], params) = images.add(renderer, initial_params);
|
||||||
let loader =
|
let loader =
|
||||||
VramTextureLoader::new([("vram://zoom".into(), zoom), ("vram://full".into(), full)]);
|
ImageTextureLoader::new([("vip://zoom".into(), zoom), ("vip://full".into(), full)]);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
loader: Arc::new(loader),
|
loader: Arc::new(loader),
|
||||||
@@ -82,7 +82,7 @@ impl ObjectWindow {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let image = Image::new("vram://zoom")
|
let image = Image::new("vip://zoom")
|
||||||
.maintain_aspect_ratio(true)
|
.maintain_aspect_ratio(true)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
ui.add(image);
|
||||||
@@ -186,7 +186,7 @@ impl ObjectWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_object(&mut self, ui: &mut Ui) {
|
fn show_object(&mut self, ui: &mut Ui) {
|
||||||
let image = Image::new("vram://full")
|
let image = Image::new("vip://full")
|
||||||
.fit_to_original_size(self.scale)
|
.fit_to_original_size(self.scale)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
ui.add(image);
|
||||||
@@ -261,7 +261,13 @@ impl ObjectRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_object(&self, image: &mut VramImage, params: &ObjectParams, use_pos: bool, eye: Eye) {
|
fn render_object(
|
||||||
|
&self,
|
||||||
|
image: &mut ImageBuffer,
|
||||||
|
params: &ObjectParams,
|
||||||
|
use_pos: bool,
|
||||||
|
eye: Eye,
|
||||||
|
) {
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
let objects = self.objects.borrow();
|
let objects = self.objects.borrow();
|
||||||
let brightness = self.brightness.borrow();
|
let brightness = self.brightness.borrow();
|
||||||
@@ -318,14 +324,14 @@ impl ObjectRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderer<2> for ObjectRenderer {
|
impl ImageRenderer<2> for ObjectRenderer {
|
||||||
type Params = ObjectParams;
|
type Params = ObjectParams;
|
||||||
|
|
||||||
fn sizes(&self) -> [[usize; 2]; 2] {
|
fn sizes(&self) -> [[usize; 2]; 2] {
|
||||||
[[8, 8], [384, 224]]
|
[[8, 8], [384, 224]]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 2]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 2]) {
|
||||||
images[0].clear();
|
images[0].clear();
|
||||||
self.render_object(&mut images[0], params, false, Eye::Left);
|
self.render_object(&mut images[0], params, false, Eye::Left);
|
||||||
self.render_object(&mut images[0], params, false, Eye::Right);
|
self.render_object(&mut images[0], params, false, Eye::Right);
|
||||||
@@ -10,8 +10,8 @@ use crate::{
|
|||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3,12 +3,12 @@ use egui::{Color32, Image, ImageSource, Response, Sense, TextureOptions, Ui, Wid
|
|||||||
pub const GENERIC_PALETTE: [u8; 4] = [0, 32, 64, 128];
|
pub const GENERIC_PALETTE: [u8; 4] = [0, 32, 64, 128];
|
||||||
|
|
||||||
pub fn shade(brt: u8, color: Color32) -> Color32 {
|
pub fn shade(brt: u8, color: Color32) -> Color32 {
|
||||||
let corrected = if brt & 0x80 != 0 {
|
let corrected = if brt > 132 {
|
||||||
255
|
255.0
|
||||||
} else {
|
} else {
|
||||||
(brt << 1) | (brt >> 6)
|
(brt as f32 * 255.0 / 133.0).round()
|
||||||
};
|
};
|
||||||
color.gamma_multiply(corrected as f32 / 255.0)
|
color.gamma_multiply(corrected / 255.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generic_palette(color: Color32) -> [Color32; 4] {
|
pub fn generic_palette(color: Color32) -> [Color32; 4] {
|
||||||
@@ -124,7 +124,7 @@ impl CellData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, source: &mut u16) -> bool {
|
pub fn update(&self, source: &mut u16) -> bool {
|
||||||
let new_value = (self.palette_index as u16) << 14
|
let new_value = ((self.palette_index as u16) << 14)
|
||||||
| if self.hflip { 0x2000 } else { 0x0000 }
|
| if self.hflip { 0x2000 } else { 0x0000 }
|
||||||
| if self.vflip { 0x1000 } else { 0x0000 }
|
| if self.vflip { 0x1000 } else { 0x0000 }
|
||||||
| (self.char_index as u16 & 0x07ff)
|
| (self.char_index as u16 & 0x07ff)
|
||||||
@@ -222,12 +222,12 @@ impl Widget for CharacterGrid<'_> {
|
|||||||
for x in (1..grid_width_cells).map(|i| (i as f32) * cell_size) {
|
for x in (1..grid_width_cells).map(|i| (i as f32) * cell_size) {
|
||||||
let p1 = (res.rect.min.x + x, res.rect.min.y).into();
|
let p1 = (res.rect.min.x + x, res.rect.min.y).into();
|
||||||
let p2 = (res.rect.min.x + x, res.rect.max.y).into();
|
let p2 = (res.rect.min.x + x, res.rect.max.y).into();
|
||||||
painter.line(vec![p1, p2], stroke);
|
painter.line_segment([p1, p2], stroke);
|
||||||
}
|
}
|
||||||
for y in (1..grid_height_cells).map(|i| (i as f32) * cell_size) {
|
for y in (1..grid_height_cells).map(|i| (i as f32) * cell_size) {
|
||||||
let p1 = (res.rect.min.x, res.rect.min.y + y).into();
|
let p1 = (res.rect.min.x, res.rect.min.y + y).into();
|
||||||
let p2 = (res.rect.max.x, res.rect.min.y + y).into();
|
let p2 = (res.rect.max.x, res.rect.min.y + y).into();
|
||||||
painter.line(vec![p1, p2], stroke);
|
painter.line_segment([p1, p2], stroke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(selected) = self.selected {
|
if let Some(selected) = self.selected {
|
||||||
@@ -6,39 +6,40 @@ use egui::{
|
|||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
use fixed::{
|
use fixed::{
|
||||||
types::extra::{U3, U9},
|
|
||||||
FixedI32,
|
FixedI32,
|
||||||
|
types::extra::{U3, U9},
|
||||||
};
|
};
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::{FromPrimitive, ToPrimitive};
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
|
||||||
memory::{MemoryClient, MemoryRef, MemoryView},
|
memory::{MemoryClient, MemoryRef, MemoryView},
|
||||||
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
|
||||||
window::{
|
window::{
|
||||||
utils::{NumberEdit, UiExt as _},
|
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
utils::{NumberEdit, UiExt as _},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::utils::{self, shade, CellData, Object};
|
use super::utils::{self, CellData, Object, shade};
|
||||||
|
|
||||||
pub struct WorldWindow {
|
pub struct WorldWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<ImageTextureLoader>,
|
||||||
memory: Arc<MemoryClient>,
|
memory: Arc<MemoryClient>,
|
||||||
worlds: MemoryView,
|
worlds: MemoryView,
|
||||||
bgmaps: MemoryView,
|
bgmaps: MemoryView,
|
||||||
index: usize,
|
index: usize,
|
||||||
param_index: usize,
|
param_index: usize,
|
||||||
generic_palette: bool,
|
generic_palette: bool,
|
||||||
params: VramParams<WorldParams>,
|
show_extents: bool,
|
||||||
|
params: ImageParams<WorldParams>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorldWindow {
|
impl WorldWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
|
||||||
let initial_params = WorldParams {
|
let initial_params = WorldParams {
|
||||||
index: 31,
|
index: 31,
|
||||||
generic_palette: false,
|
generic_palette: false,
|
||||||
@@ -46,8 +47,8 @@ impl WorldWindow {
|
|||||||
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
||||||
};
|
};
|
||||||
let renderer = WorldRenderer::new(sim_id, memory);
|
let renderer = WorldRenderer::new(sim_id, memory);
|
||||||
let ([world], params) = vram.add(renderer, initial_params);
|
let ([world], params) = images.add(renderer, initial_params);
|
||||||
let loader = VramTextureLoader::new([("vram://world".into(), world)]);
|
let loader = ImageTextureLoader::new([("vip://world".into(), world)]);
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
loader: Arc::new(loader),
|
loader: Arc::new(loader),
|
||||||
@@ -57,6 +58,7 @@ impl WorldWindow {
|
|||||||
index: params.index,
|
index: params.index,
|
||||||
param_index: 0,
|
param_index: 0,
|
||||||
generic_palette: params.generic_palette,
|
generic_palette: params.generic_palette,
|
||||||
|
show_extents: false,
|
||||||
params,
|
params,
|
||||||
scale: 1.0,
|
scale: 1.0,
|
||||||
}
|
}
|
||||||
@@ -413,6 +415,7 @@ impl WorldWindow {
|
|||||||
ui.add(slider);
|
ui.add(slider);
|
||||||
});
|
});
|
||||||
ui.checkbox(&mut self.generic_palette, "Generic palette");
|
ui.checkbox(&mut self.generic_palette, "Generic palette");
|
||||||
|
ui.checkbox(&mut self.show_extents, "Show extents");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
self.params.write(WorldParams {
|
self.params.write(WorldParams {
|
||||||
@@ -423,10 +426,85 @@ impl WorldWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn show_world(&mut self, ui: &mut Ui) {
|
fn show_world(&mut self, ui: &mut Ui) {
|
||||||
let image = Image::new("vram://world")
|
let image = Image::new("vip://world")
|
||||||
.fit_to_original_size(self.scale)
|
.fit_to_original_size(self.scale)
|
||||||
.texture_options(TextureOptions::NEAREST);
|
.texture_options(TextureOptions::NEAREST);
|
||||||
ui.add(image);
|
let res = ui.add(image);
|
||||||
|
if self.show_extents {
|
||||||
|
let world = {
|
||||||
|
let worlds = self.worlds.borrow();
|
||||||
|
let data = worlds.read(self.index);
|
||||||
|
World::parse(&data)
|
||||||
|
};
|
||||||
|
if world.header.mode == WorldMode::Object {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lx1 = (world.dst_x - world.dst_parallax) as f32 * self.scale;
|
||||||
|
let lx2 = lx1 + world.width as f32 * self.scale;
|
||||||
|
let rx1 = (world.dst_x + world.dst_parallax) as f32 * self.scale;
|
||||||
|
let rx2 = rx1 + world.width as f32 * self.scale;
|
||||||
|
let y1 = world.dst_y as f32 * self.scale;
|
||||||
|
let y2 = y1 + world.height as f32 * self.scale;
|
||||||
|
|
||||||
|
let left_color = self.params.left_color;
|
||||||
|
let right_color = self.params.right_color;
|
||||||
|
let both_color = Color32::from_rgb(
|
||||||
|
left_color.r() + right_color.r(),
|
||||||
|
left_color.g() + right_color.g(),
|
||||||
|
left_color.b() + right_color.b(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let painter = ui.painter();
|
||||||
|
let draw_rect = |x1: f32, x2: f32, color: Color32| {
|
||||||
|
painter.line(
|
||||||
|
vec![
|
||||||
|
res.rect.min + (x1, y1).into(),
|
||||||
|
res.rect.min + (x2, y1).into(),
|
||||||
|
res.rect.min + (x2, y2).into(),
|
||||||
|
res.rect.min + (x1, y2).into(),
|
||||||
|
res.rect.min + (x1, y1).into(),
|
||||||
|
],
|
||||||
|
(2.0, color),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
match (world.header.lon, world.header.ron) {
|
||||||
|
(false, false) => {}
|
||||||
|
(true, false) => {
|
||||||
|
draw_rect(lx1, lx2, left_color);
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
draw_rect(rx1, rx2, right_color);
|
||||||
|
}
|
||||||
|
(true, true) if world.dst_parallax == 0 => {
|
||||||
|
draw_rect(lx1, lx2, both_color);
|
||||||
|
}
|
||||||
|
(true, true) => {
|
||||||
|
draw_rect(lx1, lx2, left_color);
|
||||||
|
draw_rect(rx1, rx2, right_color);
|
||||||
|
let (x1, x2) = if world.dst_parallax < 0 {
|
||||||
|
(lx1, rx2)
|
||||||
|
} else {
|
||||||
|
(rx1, lx2)
|
||||||
|
};
|
||||||
|
painter.line_segment(
|
||||||
|
[
|
||||||
|
res.rect.min + (x1, y1).into(),
|
||||||
|
res.rect.min + (x2 + 1.0, y1).into(),
|
||||||
|
],
|
||||||
|
(2.0, both_color),
|
||||||
|
);
|
||||||
|
painter.line_segment(
|
||||||
|
[
|
||||||
|
res.rect.min + (x1, y2).into(),
|
||||||
|
res.rect.min + (x2 + 1.0, y2).into(),
|
||||||
|
],
|
||||||
|
(2.0, both_color),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,7 +520,7 @@ impl AppWindow for WorldWindow {
|
|||||||
fn initial_viewport(&self) -> ViewportBuilder {
|
fn initial_viewport(&self) -> ViewportBuilder {
|
||||||
ViewportBuilder::default()
|
ViewportBuilder::default()
|
||||||
.with_title(format!("Worlds ({})", self.sim_id))
|
.with_title(format!("Worlds ({})", self.sim_id))
|
||||||
.with_inner_size((640.0, 500.0))
|
.with_inner_size((640.0, 520.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
|
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
|
||||||
@@ -507,7 +585,7 @@ impl WorldRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_object_world(&mut self, group: usize, params: &WorldParams, image: &mut VramImage) {
|
fn render_object_world(&mut self, group: usize, params: &WorldParams, image: &mut ImageBuffer) {
|
||||||
for cell in self.buffer.iter_mut() {
|
for cell in self.buffer.iter_mut() {
|
||||||
*cell = [0, 0];
|
*cell = [0, 0];
|
||||||
}
|
}
|
||||||
@@ -604,7 +682,7 @@ impl WorldRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_world(&mut self, world: World, params: &WorldParams, image: &mut VramImage) {
|
fn render_world(&mut self, world: World, params: &WorldParams, image: &mut ImageBuffer) {
|
||||||
image.clear();
|
image.clear();
|
||||||
|
|
||||||
let width = if world.header.mode == WorldMode::Affine {
|
let width = if world.header.mode == WorldMode::Affine {
|
||||||
@@ -643,6 +721,15 @@ impl WorldRenderer {
|
|||||||
shades.map(|s| shade(s, params.right_color)),
|
shades.map(|s| shade(s, params.right_color)),
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
let palettes = {
|
||||||
|
let palettes = self.palettes.borrow().read::<[u8; 8]>(0);
|
||||||
|
[
|
||||||
|
utils::parse_palette(palettes[0]),
|
||||||
|
utils::parse_palette(palettes[2]),
|
||||||
|
utils::parse_palette(palettes[4]),
|
||||||
|
utils::parse_palette(palettes[6]),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
let chardata = self.chardata.borrow();
|
let chardata = self.chardata.borrow();
|
||||||
let bgmaps = self.bgmaps.borrow();
|
let bgmaps = self.bgmaps.borrow();
|
||||||
@@ -667,7 +754,8 @@ impl WorldRenderer {
|
|||||||
let row = (sy & 0x7) as usize;
|
let row = (sy & 0x7) as usize;
|
||||||
let col = (sx & 0x7) as usize;
|
let col = (sx & 0x7) as usize;
|
||||||
let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col);
|
let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col);
|
||||||
image.add((dx as usize, dy as usize), colors[0][pixel as usize]);
|
let shade = palettes[cell.palette_index][pixel as usize];
|
||||||
|
image.add((dx as usize, dy as usize), colors[0][shade as usize]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dx = x + world.dst_x + world.dst_parallax;
|
let dx = x + world.dst_x + world.dst_parallax;
|
||||||
@@ -680,21 +768,22 @@ impl WorldRenderer {
|
|||||||
let row = (sy & 0x7) as usize;
|
let row = (sy & 0x7) as usize;
|
||||||
let col = (sx & 0x7) as usize;
|
let col = (sx & 0x7) as usize;
|
||||||
let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col);
|
let pixel = utils::read_char_pixel(char, cell.hflip, cell.vflip, row, col);
|
||||||
image.add((dx as usize, dy as usize), colors[1][pixel as usize]);
|
let shade = palettes[cell.palette_index][pixel as usize];
|
||||||
|
image.add((dx as usize, dy as usize), colors[1][shade as usize]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VramRenderer<1> for WorldRenderer {
|
impl ImageRenderer<1> for WorldRenderer {
|
||||||
type Params = WorldParams;
|
type Params = WorldParams;
|
||||||
|
|
||||||
fn sizes(&self) -> [[usize; 2]; 1] {
|
fn sizes(&self) -> [[usize; 2]; 1] {
|
||||||
[[384, 224]]
|
[[384, 224]]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 1]) {
|
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 1]) {
|
||||||
let image = &mut images[0];
|
let image = &mut images[0];
|
||||||
|
|
||||||
let worlds = self.worlds.borrow();
|
let worlds = self.worlds.borrow();
|
||||||
@@ -859,7 +948,7 @@ impl WorldHeader {
|
|||||||
let new_value = (*source & 0x0030)
|
let new_value = (*source & 0x0030)
|
||||||
| if self.lon { 0x8000 } else { 0x0000 }
|
| if self.lon { 0x8000 } else { 0x0000 }
|
||||||
| if self.ron { 0x4000 } else { 0x0000 }
|
| if self.ron { 0x4000 } else { 0x0000 }
|
||||||
| self.mode.to_u16().unwrap() << 12
|
| (self.mode.to_u16().unwrap() << 12)
|
||||||
| ((self.scx as u16) << 10)
|
| ((self.scx as u16) << 10)
|
||||||
| ((self.scy as u16) << 8)
|
| ((self.scy as u16) << 8)
|
||||||
| if self.over { 0x0080 } else { 0x0000 }
|
| if self.over { 0x0080 } else { 0x0000 }
|
||||||
@@ -976,8 +1065,8 @@ impl<'a> SourceCoordCalculator<'a> {
|
|||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
SourceParam::Affine(affine) => {
|
SourceParam::Affine(affine) => {
|
||||||
let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.min(0).abs());
|
let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.min(0));
|
||||||
let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.min(0).abs());
|
let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.min(0));
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -997,8 +1086,8 @@ impl<'a> SourceCoordCalculator<'a> {
|
|||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
SourceParam::Affine(affine) => {
|
SourceParam::Affine(affine) => {
|
||||||
let sx = affine_coord(affine.src_x, x, affine.dx, -affine.src_parallax.max(0));
|
let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.max(0));
|
||||||
let sy = affine_coord(affine.src_y, x, affine.dy, -affine.src_parallax.max(0));
|
let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.max(0));
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user