Compare commits
4 Commits
3cdc0583a6
...
2966a7c407
Author | SHA1 | Date |
---|---|---|
|
2966a7c407 | |
|
6142179e31 | |
|
2c71c20f20 | |
|
802da8f93e |
|
@ -427,6 +427,12 @@ version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "az"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.74"
|
version = "0.3.74"
|
||||||
|
@ -451,7 +457,7 @@ dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
|
@ -834,6 +840,12 @@ version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cursor-icon"
|
name = "cursor-icon"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -1166,6 +1178,19 @@ dependencies = [
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixed"
|
||||||
|
version = "1.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"bytemuck",
|
||||||
|
"half",
|
||||||
|
"num-traits",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.35"
|
version = "1.0.35"
|
||||||
|
@ -1443,6 +1468,16 @@ dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.2"
|
version = "0.15.2"
|
||||||
|
@ -1691,6 +1726,15 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
@ -1779,10 +1823,11 @@ dependencies = [
|
||||||
"egui-wgpu",
|
"egui-wgpu",
|
||||||
"egui-winit",
|
"egui-winit",
|
||||||
"egui_extras",
|
"egui_extras",
|
||||||
|
"fixed",
|
||||||
"gilrs",
|
"gilrs",
|
||||||
"hex",
|
"hex",
|
||||||
"image",
|
"image",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"oneshot",
|
"oneshot",
|
||||||
|
@ -3372,6 +3417,12 @@ dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uds_windows"
|
name = "uds_windows"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
|
@ -21,10 +21,11 @@ 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" }
|
||||||
egui-winit = "0.30"
|
egui-winit = "0.30"
|
||||||
egui-wgpu = { version = "0.30", features = ["winit"] }
|
egui-wgpu = { version = "0.30", features = ["winit"] }
|
||||||
|
fixed = { version = "1.28", features = ["num-traits"] }
|
||||||
gilrs = { version = "0.11", features = ["serde-serialize"] }
|
gilrs = { version = "0.11", features = ["serde-serialize"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", default-features = false, features = ["png"] }
|
||||||
itertools = "0.13"
|
itertools = "0.14"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
oneshot = "0.1"
|
oneshot = "0.1"
|
||||||
|
|
|
@ -22,8 +22,8 @@ use crate::{
|
||||||
persistence::Persistence,
|
persistence::Persistence,
|
||||||
vram::VramProcessor,
|
vram::VramProcessor,
|
||||||
window::{
|
window::{
|
||||||
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, GameWindow, GdbServerWindow,
|
AboutWindow, AppWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GameWindow,
|
||||||
InputWindow, ObjectWindow, WorldWindow,
|
GdbServerWindow, InputWindow, ObjectWindow, WorldWindow,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,6 +229,10 @@ impl ApplicationHandler<UserEvent> for Application {
|
||||||
let world = WorldWindow::new(sim_id, &self.memory, &mut self.vram);
|
let world = WorldWindow::new(sim_id, &self.memory, &mut self.vram);
|
||||||
self.open(event_loop, Box::new(world));
|
self.open(event_loop, Box::new(world));
|
||||||
}
|
}
|
||||||
|
UserEvent::OpenFrameBuffers(sim_id) => {
|
||||||
|
let world = FrameBufferWindow::new(sim_id, &self.memory, &mut self.vram);
|
||||||
|
self.open(event_loop, Box::new(world));
|
||||||
|
}
|
||||||
UserEvent::OpenDebugger(sim_id) => {
|
UserEvent::OpenDebugger(sim_id) => {
|
||||||
let debugger =
|
let debugger =
|
||||||
GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone());
|
GdbServerWindow::new(sim_id, self.client.clone(), self.proxy.clone());
|
||||||
|
@ -491,6 +495,7 @@ pub enum UserEvent {
|
||||||
OpenBgMap(SimId),
|
OpenBgMap(SimId),
|
||||||
OpenObjects(SimId),
|
OpenObjects(SimId),
|
||||||
OpenWorlds(SimId),
|
OpenWorlds(SimId),
|
||||||
|
OpenFrameBuffers(SimId),
|
||||||
OpenDebugger(SimId),
|
OpenDebugger(SimId),
|
||||||
OpenInput,
|
OpenInput,
|
||||||
OpenPlayer2,
|
OpenPlayer2,
|
||||||
|
|
|
@ -238,11 +238,10 @@ impl MemoryRegion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, data: &[u8]) {
|
pub fn update(&self, data: &[u8]) {
|
||||||
let gens: Vec<u64> = self
|
let gens = self
|
||||||
.gens
|
.gens
|
||||||
.iter()
|
.each_ref()
|
||||||
.map(|i| i.load(std::sync::atomic::Ordering::Acquire))
|
.map(|i| i.load(std::sync::atomic::Ordering::Acquire));
|
||||||
.collect();
|
|
||||||
let next_gen = gens.iter().max().unwrap() + 1;
|
let next_gen = gens.iter().max().unwrap() + 1;
|
||||||
let indices = gens
|
let indices = gens
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
@ -3,7 +3,7 @@ use egui::{Context, ViewportBuilder, ViewportId};
|
||||||
pub use game::GameWindow;
|
pub use game::GameWindow;
|
||||||
pub use gdb::GdbServerWindow;
|
pub use gdb::GdbServerWindow;
|
||||||
pub use input::InputWindow;
|
pub use input::InputWindow;
|
||||||
pub use vram::{BgMapWindow, CharacterDataWindow, ObjectWindow, WorldWindow};
|
pub use vram::{BgMapWindow, CharacterDataWindow, FrameBufferWindow, ObjectWindow, WorldWindow};
|
||||||
use winit::event::KeyEvent;
|
use winit::event::KeyEvent;
|
||||||
|
|
||||||
use crate::emulator::SimId;
|
use crate::emulator::SimId;
|
||||||
|
|
|
@ -156,6 +156,12 @@ impl GameWindow {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
ui.close_menu();
|
ui.close_menu();
|
||||||
}
|
}
|
||||||
|
if ui.button("Frame Buffers").clicked() {
|
||||||
|
self.proxy
|
||||||
|
.send_event(UserEvent::OpenFrameBuffers(self.sim_id))
|
||||||
|
.unwrap();
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
ui.menu_button("Help", |ui| {
|
ui.menu_button("Help", |ui| {
|
||||||
if ui.button("About").clicked() {
|
if ui.button("About").clicked() {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use egui::{
|
||||||
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
Response, RichText, Rounding, Sense, Shape, Stroke, TextEdit, Ui, UiBuilder, Vec2, Widget,
|
||||||
WidgetText,
|
WidgetText,
|
||||||
};
|
};
|
||||||
use num_traits::PrimInt;
|
use num_traits::{CheckedAdd, CheckedSub, One};
|
||||||
|
|
||||||
pub trait UiExt {
|
pub trait UiExt {
|
||||||
fn section(&mut self, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui));
|
fn section(&mut self, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui));
|
||||||
|
@ -97,12 +97,20 @@ enum Direction {
|
||||||
Down,
|
Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Number: PrimInt + Display + FromStr + Send + Sync + 'static {}
|
pub trait Number:
|
||||||
impl<T: PrimInt + Display + FromStr + Send + Sync + 'static> Number for T {}
|
Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl<
|
||||||
|
T: Copy + One + CheckedAdd + CheckedSub + Eq + Ord + Display + FromStr + Send + Sync + 'static,
|
||||||
|
> Number for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
pub struct NumberEdit<'a, T: Number> {
|
pub struct NumberEdit<'a, T: Number> {
|
||||||
value: &'a mut T,
|
value: &'a mut T,
|
||||||
increment: T,
|
increment: T,
|
||||||
|
precision: usize,
|
||||||
min: Option<T>,
|
min: Option<T>,
|
||||||
max: Option<T>,
|
max: Option<T>,
|
||||||
}
|
}
|
||||||
|
@ -112,11 +120,16 @@ impl<'a, T: Number> NumberEdit<'a, T> {
|
||||||
Self {
|
Self {
|
||||||
value,
|
value,
|
||||||
increment: T::one(),
|
increment: T::one(),
|
||||||
|
precision: 3,
|
||||||
min: None,
|
min: None,
|
||||||
max: None,
|
max: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn precision(self, precision: usize) -> Self {
|
||||||
|
Self { precision, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn range(self, range: impl RangeBounds<T>) -> Self {
|
pub fn range(self, range: impl RangeBounds<T>) -> Self {
|
||||||
let min = match range.start_bound() {
|
let min = match range.start_bound() {
|
||||||
Bound::Unbounded => None,
|
Bound::Unbounded => None,
|
||||||
|
@ -135,18 +148,19 @@ impl<'a, T: Number> NumberEdit<'a, T> {
|
||||||
impl<T: Number> Widget for NumberEdit<'_, T> {
|
impl<T: Number> Widget for NumberEdit<'_, T> {
|
||||||
fn ui(self, ui: &mut Ui) -> Response {
|
fn ui(self, ui: &mut Ui) -> Response {
|
||||||
let id = ui.id();
|
let id = ui.id();
|
||||||
|
let to_string = |val: &T| format!("{val:.0$}", self.precision);
|
||||||
|
|
||||||
let (last_value, mut str, focus) = ui.memory(|m| {
|
let (last_value, mut str, focus) = ui.memory(|m| {
|
||||||
let (lv, s) = m
|
let (lv, s) = m
|
||||||
.data
|
.data
|
||||||
.get_temp(id)
|
.get_temp(id)
|
||||||
.unwrap_or((*self.value, self.value.to_string()));
|
.unwrap_or((*self.value, to_string(self.value)));
|
||||||
let focus = m.has_focus(id);
|
let focus = m.has_focus(id);
|
||||||
(lv, s, focus)
|
(lv, s, focus)
|
||||||
});
|
});
|
||||||
let mut stale = false;
|
let mut stale = false;
|
||||||
if *self.value != last_value {
|
if *self.value != last_value {
|
||||||
str = self.value.to_string();
|
str = to_string(self.value);
|
||||||
stale = true;
|
stale = true;
|
||||||
}
|
}
|
||||||
let valid = str.parse().is_ok_and(|v: T| v == *self.value);
|
let valid = str.parse().is_ok_and(|v: T| v == *self.value);
|
||||||
|
@ -236,7 +250,7 @@ impl<T: Number> Widget for NumberEdit<'_, T> {
|
||||||
if let Some(new_value) = value.filter(in_range) {
|
if let Some(new_value) = value.filter(in_range) {
|
||||||
*self.value = new_value;
|
*self.value = new_value;
|
||||||
}
|
}
|
||||||
str = self.value.to_string();
|
str = to_string(self.value);
|
||||||
stale = true;
|
stale = true;
|
||||||
} else if res.changed {
|
} else if res.changed {
|
||||||
if let Some(new_value) = str.parse().ok().filter(in_range) {
|
if let Some(new_value) = str.parse().ok().filter(in_range) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
mod bgmap;
|
mod bgmap;
|
||||||
mod chardata;
|
mod chardata;
|
||||||
|
mod framebuffer;
|
||||||
mod object;
|
mod object;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
pub use bgmap::*;
|
pub use bgmap::*;
|
||||||
pub use chardata::*;
|
pub use chardata::*;
|
||||||
|
pub use framebuffer::*;
|
||||||
pub use object::*;
|
pub use object::*;
|
||||||
pub use world::*;
|
pub use world::*;
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use egui::{
|
||||||
|
Align, CentralPanel, Color32, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui,
|
||||||
|
ViewportBuilder, ViewportId,
|
||||||
|
};
|
||||||
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
emulator::SimId,
|
||||||
|
memory::{MemoryClient, MemoryView},
|
||||||
|
vram::{VramImage, VramParams, VramProcessor, VramRenderer, VramTextureLoader},
|
||||||
|
window::{
|
||||||
|
utils::{NumberEdit, UiExt as _},
|
||||||
|
AppWindow,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::utils;
|
||||||
|
|
||||||
|
pub struct FrameBufferWindow {
|
||||||
|
sim_id: SimId,
|
||||||
|
loader: Arc<VramTextureLoader>,
|
||||||
|
index: usize,
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
generic_palette: bool,
|
||||||
|
params: VramParams<FrameBufferParams>,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBufferWindow {
|
||||||
|
pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self {
|
||||||
|
let initial_params = FrameBufferParams {
|
||||||
|
index: 0,
|
||||||
|
left: true,
|
||||||
|
right: true,
|
||||||
|
generic_palette: false,
|
||||||
|
left_color: Color32::from_rgb(0xff, 0x00, 0x00),
|
||||||
|
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
|
||||||
|
};
|
||||||
|
let renderer = FrameBufferRenderer::new(sim_id, memory);
|
||||||
|
let ([buffer], params) = vram.add(renderer, initial_params);
|
||||||
|
let loader = VramTextureLoader::new([("vram://buffer".into(), buffer)]);
|
||||||
|
Self {
|
||||||
|
sim_id,
|
||||||
|
loader: Arc::new(loader),
|
||||||
|
index: params.index,
|
||||||
|
left: params.left,
|
||||||
|
right: params.right,
|
||||||
|
generic_palette: params.generic_palette,
|
||||||
|
params,
|
||||||
|
scale: 2.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_form(&mut self, ui: &mut Ui) {
|
||||||
|
let row_height = ui.spacing().interact_size.y;
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::remainder())
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Index");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut self.index).range(0..2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Left");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let address = self.index * 0x00008000;
|
||||||
|
let mut address_str = format!("{address:08x}");
|
||||||
|
ui.add_enabled(
|
||||||
|
false,
|
||||||
|
TextEdit::singleline(&mut address_str).horizontal_align(Align::Max),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Right");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let address = self.index * 0x00008000 + 0x00010000;
|
||||||
|
let mut address_str = format!("{address:08x}");
|
||||||
|
ui.add_enabled(
|
||||||
|
false,
|
||||||
|
TextEdit::singleline(&mut address_str).horizontal_align(Align::Max),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.section("Display", |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Scale");
|
||||||
|
ui.spacing_mut().slider_width = ui.available_width();
|
||||||
|
let slider = Slider::new(&mut self.scale, 1.0..=10.0)
|
||||||
|
.step_by(1.0)
|
||||||
|
.show_value(false);
|
||||||
|
ui.add(slider);
|
||||||
|
});
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.columns(Column::remainder(), 2)
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.checkbox(&mut self.left, "Left");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.checkbox(&mut self.right, "Right");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ui.checkbox(&mut self.generic_palette, "Generic colors");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
self.params.write(FrameBufferParams {
|
||||||
|
index: self.index,
|
||||||
|
left: self.left,
|
||||||
|
right: self.right,
|
||||||
|
generic_palette: self.generic_palette,
|
||||||
|
..*self.params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_buffers(&mut self, ui: &mut Ui) {
|
||||||
|
let image = Image::new("vram://buffer")
|
||||||
|
.fit_to_original_size(self.scale)
|
||||||
|
.texture_options(TextureOptions::NEAREST);
|
||||||
|
ui.add(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWindow for FrameBufferWindow {
|
||||||
|
fn viewport_id(&self) -> ViewportId {
|
||||||
|
ViewportId::from_hash_of(format!("framebuffer-{}", self.sim_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sim_id(&self) -> SimId {
|
||||||
|
self.sim_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_viewport(&self) -> ViewportBuilder {
|
||||||
|
ViewportBuilder::default()
|
||||||
|
.with_title(format!("Frame Buffers ({})", self.sim_id))
|
||||||
|
.with_inner_size((640.0, 480.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_init(&mut self, ctx: &Context, _render_state: &egui_wgpu::RenderState) {
|
||||||
|
ctx.add_texture_loader(self.loader.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, ctx: &Context) {
|
||||||
|
CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.horizontal_top(|ui| {
|
||||||
|
StripBuilder::new(ui)
|
||||||
|
.size(Size::relative(0.3))
|
||||||
|
.size(Size::remainder())
|
||||||
|
.horizontal(|mut strip| {
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ScrollArea::vertical().show(ui, |ui| self.show_form(ui));
|
||||||
|
});
|
||||||
|
strip.cell(|ui| {
|
||||||
|
ScrollArea::both().show(ui, |ui| self.show_buffers(ui));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
struct FrameBufferParams {
|
||||||
|
index: usize,
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
generic_palette: bool,
|
||||||
|
left_color: Color32,
|
||||||
|
right_color: Color32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrameBufferRenderer {
|
||||||
|
buffers: [MemoryView; 4],
|
||||||
|
brightness: MemoryView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBufferRenderer {
|
||||||
|
fn new(sim_id: SimId, memory: &MemoryClient) -> Self {
|
||||||
|
Self {
|
||||||
|
buffers: [
|
||||||
|
memory.watch(sim_id, 0x00000000, 0x6000),
|
||||||
|
memory.watch(sim_id, 0x00008000, 0x6000),
|
||||||
|
memory.watch(sim_id, 0x00010000, 0x6000),
|
||||||
|
memory.watch(sim_id, 0x00018000, 0x6000),
|
||||||
|
],
|
||||||
|
brightness: memory.watch(sim_id, 0x0005f824, 8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VramRenderer<1> for FrameBufferRenderer {
|
||||||
|
type Params = FrameBufferParams;
|
||||||
|
|
||||||
|
fn sizes(&self) -> [[usize; 2]; 1] {
|
||||||
|
[[384, 224]]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, params: &Self::Params, images: &mut [VramImage; 1]) {
|
||||||
|
let image = &mut images[0];
|
||||||
|
|
||||||
|
let left_buffer = self.buffers[params.index * 2].borrow();
|
||||||
|
let right_buffer = self.buffers[params.index * 2 + 1].borrow();
|
||||||
|
|
||||||
|
let colors = if params.generic_palette {
|
||||||
|
[
|
||||||
|
utils::generic_palette(params.left_color),
|
||||||
|
utils::generic_palette(params.right_color),
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
let brts = self.brightness.borrow().read::<[u8; 8]>(0);
|
||||||
|
let shades = utils::parse_shades(&brts);
|
||||||
|
[
|
||||||
|
shades.map(|s| utils::shade(s, params.left_color)),
|
||||||
|
shades.map(|s| utils::shade(s, params.right_color)),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let left_cols = left_buffer.range::<u8>(0, 0x6000);
|
||||||
|
let right_cols = right_buffer.range::<u8>(0, 0x6000);
|
||||||
|
for (index, (left, right)) in left_cols.zip(right_cols).enumerate() {
|
||||||
|
let top = (index % 64) * 4;
|
||||||
|
if top >= 224 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixels = [0, 2, 4, 6].map(|i| {
|
||||||
|
let left = if params.left {
|
||||||
|
colors[0][(left >> i) as usize & 0x3]
|
||||||
|
} else {
|
||||||
|
Color32::BLACK
|
||||||
|
};
|
||||||
|
let right = if params.right {
|
||||||
|
colors[1][(right >> i) as usize & 0x3]
|
||||||
|
} else {
|
||||||
|
Color32::BLACK
|
||||||
|
};
|
||||||
|
Color32::from_rgb(
|
||||||
|
left.r() + right.r(),
|
||||||
|
left.g() + right.g(),
|
||||||
|
left.b() + right.b(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let x = index / 64;
|
||||||
|
for (i, pixel) in pixels.into_iter().enumerate() {
|
||||||
|
let y = top + i;
|
||||||
|
image.write((x, y), pixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,12 @@ use egui::{
|
||||||
TextureOptions, Ui, ViewportBuilder, ViewportId,
|
TextureOptions, Ui, ViewportBuilder, ViewportId,
|
||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
use fixed::{
|
||||||
|
types::extra::{U3, U9},
|
||||||
|
FixedI32,
|
||||||
|
};
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
emulator::SimId,
|
emulator::SimId,
|
||||||
|
@ -23,6 +27,7 @@ use super::utils::{self, shade, CellData, Object};
|
||||||
pub struct WorldWindow {
|
pub struct WorldWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
loader: Arc<VramTextureLoader>,
|
loader: Arc<VramTextureLoader>,
|
||||||
|
memory: Arc<MemoryClient>,
|
||||||
worlds: MemoryView,
|
worlds: MemoryView,
|
||||||
bgmaps: MemoryView,
|
bgmaps: MemoryView,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
@ -33,7 +38,7 @@ pub struct WorldWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorldWindow {
|
impl WorldWindow {
|
||||||
pub fn new(sim_id: SimId, memory: &MemoryClient, vram: &mut VramProcessor) -> Self {
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, vram: &mut VramProcessor) -> Self {
|
||||||
let initial_params = WorldParams {
|
let initial_params = WorldParams {
|
||||||
index: 31,
|
index: 31,
|
||||||
generic_palette: false,
|
generic_palette: false,
|
||||||
|
@ -46,7 +51,8 @@ impl WorldWindow {
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
loader: Arc::new(loader),
|
loader: Arc::new(loader),
|
||||||
worlds: memory.watch(sim_id, 0x3d800, 0x400),
|
memory: memory.clone(),
|
||||||
|
worlds: memory.watch(sim_id, 0x0003d800, 0x400),
|
||||||
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
|
||||||
index: params.index,
|
index: params.index,
|
||||||
param_index: 0,
|
param_index: 0,
|
||||||
|
@ -76,7 +82,7 @@ impl WorldWindow {
|
||||||
ui.label("Address");
|
ui.label("Address");
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
let address = 0x3d800 + self.index * 32;
|
let address = 0x0003d800 + self.index * 32;
|
||||||
let mut address_str = format!("{address:08x}");
|
let mut address_str = format!("{address:08x}");
|
||||||
ui.add_enabled(
|
ui.add_enabled(
|
||||||
false,
|
false,
|
||||||
|
@ -85,7 +91,7 @@ impl WorldWindow {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let data = {
|
let mut data = {
|
||||||
let worlds = self.worlds.borrow();
|
let worlds = self.worlds.borrow();
|
||||||
worlds.read(self.index)
|
worlds.read(self.index)
|
||||||
};
|
};
|
||||||
|
@ -266,6 +272,10 @@ impl WorldWindow {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if world.update(&mut data) {
|
||||||
|
let address = 0x0003d800 + self.index * 32;
|
||||||
|
self.memory.write(self.sim_id, address as u32, &data);
|
||||||
|
}
|
||||||
if world.header.mode == WorldMode::HBias {
|
if world.header.mode == WorldMode::HBias {
|
||||||
ui.section("H-bias", |ui| {
|
ui.section("H-bias", |ui| {
|
||||||
TableBuilder::new(ui)
|
TableBuilder::new(ui)
|
||||||
|
@ -277,10 +287,11 @@ impl WorldWindow {
|
||||||
ui.label("Index");
|
ui.label("Index");
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.add(NumberEdit::new(&mut self.param_index).range(0..32));
|
let max = world.height.max(8) as usize;
|
||||||
|
ui.add(NumberEdit::new(&mut self.param_index).range(0..max));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let base = world.param_base + self.param_index * 2;
|
let base = (world.param_base + self.param_index * 2) & 0x1ffff;
|
||||||
let mut param = HBiasParam::load(&self.bgmaps.borrow(), base);
|
let mut param = HBiasParam::load(&self.bgmaps.borrow(), base);
|
||||||
body.row(row_height, |mut row| {
|
body.row(row_height, |mut row| {
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
|
@ -312,6 +323,81 @@ impl WorldWindow {
|
||||||
ui.add(NumberEdit::new(&mut param.right).range(-4096..4096));
|
ui.add(NumberEdit::new(&mut param.right).range(-4096..4096));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
param.save(&self.memory, self.sim_id, base);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if world.header.mode == WorldMode::Affine {
|
||||||
|
ui.section("Affine", |ui| {
|
||||||
|
TableBuilder::new(ui)
|
||||||
|
.column(Column::remainder())
|
||||||
|
.column(Column::remainder())
|
||||||
|
.body(|mut body| {
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Index");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let max = world.height.max(1) as usize;
|
||||||
|
ui.add(NumberEdit::new(&mut self.param_index).range(0..max));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let base = (world.param_base + self.param_index * 8) & 0x1ffff;
|
||||||
|
let mut param = AffineParam::load(&self.bgmaps.borrow(), base);
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Address");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
let address = 0x00020000 + base * 2;
|
||||||
|
let mut address_str = format!("{address:08x}");
|
||||||
|
ui.add_enabled(
|
||||||
|
false,
|
||||||
|
TextEdit::singleline(&mut address_str)
|
||||||
|
.horizontal_align(Align::Max),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Src X");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut param.src_x).precision(3));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Src Y");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut param.src_y).precision(3));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Src parallax");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut param.src_parallax));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Delta X");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut param.dx).precision(9));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
body.row(row_height, |mut row| {
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.label("Delta Y");
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.add(NumberEdit::new(&mut param.dy).precision(9));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
param.save(&self.memory, self.sim_id, base);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -521,6 +607,12 @@ 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 VramImage) {
|
||||||
image.clear();
|
image.clear();
|
||||||
|
|
||||||
|
let width = if world.header.mode == WorldMode::Affine {
|
||||||
|
world.width & 0x03ff
|
||||||
|
} else {
|
||||||
|
world.width
|
||||||
|
};
|
||||||
|
|
||||||
let height = if world.header.mode == WorldMode::Affine {
|
let height = if world.header.mode == WorldMode::Affine {
|
||||||
world.height.max(8)
|
world.height.max(8)
|
||||||
} else {
|
} else {
|
||||||
|
@ -528,7 +620,7 @@ impl WorldRenderer {
|
||||||
};
|
};
|
||||||
|
|
||||||
let dx1 = world.dst_x;
|
let dx1 = world.dst_x;
|
||||||
let dx2 = dx1 + world.width;
|
let dx2 = dx1 + width;
|
||||||
if dx1 - world.dst_parallax > 384 || dx2 + world.dst_parallax < 0 {
|
if dx1 - world.dst_parallax > 384 || dx2 + world.dst_parallax < 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -564,7 +656,7 @@ impl WorldRenderer {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for x in 0..world.width {
|
for x in 0..width {
|
||||||
let dx = x + world.dst_x - world.dst_parallax;
|
let dx = x + world.dst_x - world.dst_parallax;
|
||||||
if world.header.lon && (0..384).contains(&dx) {
|
if world.header.lon && (0..384).contains(&dx) {
|
||||||
let (sx, sy) = source.left(x, y);
|
let (sx, sy) = source.left(x, y);
|
||||||
|
@ -661,6 +753,52 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(&self, source: &mut [u16; 16]) -> bool {
|
||||||
|
let mut changed = self.header.update(&mut source[0]);
|
||||||
|
|
||||||
|
let new_dst_x = (self.dst_x as u16 & 0x03ff) | (source[1] & 0xfc00);
|
||||||
|
changed |= source[1] != new_dst_x;
|
||||||
|
source[1] = new_dst_x;
|
||||||
|
|
||||||
|
let new_dst_parallax = (self.dst_parallax as u16 & 0x03ff) | (source[2] & 0xfc00);
|
||||||
|
changed |= source[2] != new_dst_parallax;
|
||||||
|
source[2] = new_dst_parallax;
|
||||||
|
|
||||||
|
let new_dst_y = self.dst_y as u16;
|
||||||
|
changed |= source[3] != new_dst_y;
|
||||||
|
source[3] = new_dst_y;
|
||||||
|
|
||||||
|
let new_src_x = (self.src_x as u16 & 0x1fff) | (source[4] & 0xe000);
|
||||||
|
changed |= source[4] != new_src_x;
|
||||||
|
source[4] = new_src_x;
|
||||||
|
|
||||||
|
let new_src_parallax = (self.src_parallax as u16 & 0x7fff) | (source[4] & 0x8000);
|
||||||
|
changed |= source[5] != new_src_parallax;
|
||||||
|
source[5] = new_src_parallax;
|
||||||
|
|
||||||
|
let new_src_y = (self.src_y as u16 & 0x1fff) | (source[6] & 0xe000);
|
||||||
|
changed |= source[6] != new_src_y;
|
||||||
|
source[6] = new_src_y;
|
||||||
|
|
||||||
|
let new_width = ((self.width - 1) as u16 & 0x1fff) | (source[7] & 0xe000);
|
||||||
|
changed |= source[7] != new_width;
|
||||||
|
source[7] = new_width;
|
||||||
|
|
||||||
|
let new_height = (self.height - 1) as u16;
|
||||||
|
changed |= source[8] != new_height;
|
||||||
|
source[8] = new_height;
|
||||||
|
|
||||||
|
let new_param_base = self.param_base as u16;
|
||||||
|
changed |= source[9] != new_param_base;
|
||||||
|
source[9] = new_param_base;
|
||||||
|
|
||||||
|
let new_overplane = self.overplane as u16;
|
||||||
|
changed |= source[10] != new_overplane;
|
||||||
|
source[10] = new_overplane;
|
||||||
|
|
||||||
|
changed
|
||||||
|
}
|
||||||
|
|
||||||
fn source_cell(&self, sx: i16, sy: i16) -> usize {
|
fn source_cell(&self, sx: i16, sy: i16) -> usize {
|
||||||
if self.header.over {
|
if self.header.over {
|
||||||
let bg_width = 1 << self.header.scx << 9;
|
let bg_width = 1 << self.header.scx << 9;
|
||||||
|
@ -716,6 +854,21 @@ impl WorldHeader {
|
||||||
base,
|
base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(&self, source: &mut u16) -> bool {
|
||||||
|
let new_value = (*source & 0x0030)
|
||||||
|
| if self.lon { 0x8000 } else { 0x0000 }
|
||||||
|
| if self.ron { 0x4000 } else { 0x0000 }
|
||||||
|
| self.mode.to_u16().unwrap() << 12
|
||||||
|
| ((self.scx as u16) << 10)
|
||||||
|
| ((self.scy as u16) << 8)
|
||||||
|
| if self.over { 0x0080 } else { 0x0000 }
|
||||||
|
| if self.end { 0x0040 } else { 0x0000 }
|
||||||
|
| (self.base as u16 & 0x000f);
|
||||||
|
let changed = *source != new_value;
|
||||||
|
*source = new_value;
|
||||||
|
changed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
||||||
|
@ -812,14 +965,19 @@ impl<'a> SourceCoordCalculator<'a> {
|
||||||
fn left(&mut self, x: i16, y: i16) -> (i16, i16) {
|
fn left(&mut self, x: i16, y: i16) -> (i16, i16) {
|
||||||
self.update_param(y);
|
self.update_param(y);
|
||||||
match &self.param {
|
match &self.param {
|
||||||
|
SourceParam::Normal => {
|
||||||
|
let sx = x + self.world.src_x - self.world.src_parallax;
|
||||||
|
let sy = y + self.world.src_y;
|
||||||
|
(sx, sy)
|
||||||
|
}
|
||||||
SourceParam::HBias(HBiasParam { left, .. }) => {
|
SourceParam::HBias(HBiasParam { left, .. }) => {
|
||||||
let sx = x + self.world.src_x - self.world.src_parallax + *left;
|
let sx = x + self.world.src_x - self.world.src_parallax + *left;
|
||||||
let sy = y + self.world.src_y;
|
let sy = y + self.world.src_y;
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
SourceParam::Normal => {
|
SourceParam::Affine(affine) => {
|
||||||
let sx = x + self.world.src_x - self.world.src_parallax;
|
let sx = affine_coord(affine.src_x, x, affine.dx, affine.src_parallax.min(0).abs());
|
||||||
let sy = y + self.world.src_y;
|
let sy = affine_coord(affine.src_y, x, affine.dy, affine.src_parallax.min(0).abs());
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -828,14 +986,19 @@ impl<'a> SourceCoordCalculator<'a> {
|
||||||
fn right(&mut self, x: i16, y: i16) -> (i16, i16) {
|
fn right(&mut self, x: i16, y: i16) -> (i16, i16) {
|
||||||
self.update_param(y);
|
self.update_param(y);
|
||||||
match &self.param {
|
match &self.param {
|
||||||
|
SourceParam::Normal => {
|
||||||
|
let sx = x + self.world.src_x + self.world.src_parallax;
|
||||||
|
let sy = y + self.world.src_y;
|
||||||
|
(sx, sy)
|
||||||
|
}
|
||||||
SourceParam::HBias(HBiasParam { right, .. }) => {
|
SourceParam::HBias(HBiasParam { right, .. }) => {
|
||||||
let sx = x + self.world.src_x + self.world.src_parallax + *right;
|
let sx = x + self.world.src_x + self.world.src_parallax + *right;
|
||||||
let sy = y + self.world.src_y;
|
let sy = y + self.world.src_y;
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
SourceParam::Normal => {
|
SourceParam::Affine(affine) => {
|
||||||
let sx = x + self.world.src_x + self.world.src_parallax;
|
let sx = affine_coord(affine.src_x, x, affine.dx, -affine.src_parallax.max(0));
|
||||||
let sy = y + self.world.src_y;
|
let sy = affine_coord(affine.src_y, x, affine.dy, -affine.src_parallax.max(0));
|
||||||
(sx, sy)
|
(sx, sy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -849,6 +1012,10 @@ impl<'a> SourceCoordCalculator<'a> {
|
||||||
let base = self.world.param_base + (2 * y as usize);
|
let base = self.world.param_base + (2 * y as usize);
|
||||||
self.param = SourceParam::HBias(HBiasParam::load(self.params, base));
|
self.param = SourceParam::HBias(HBiasParam::load(self.params, base));
|
||||||
}
|
}
|
||||||
|
if self.world.header.mode == WorldMode::Affine {
|
||||||
|
let base = self.world.param_base + (8 * y as usize);
|
||||||
|
self.param = SourceParam::Affine(AffineParam::load(self.params, base));
|
||||||
|
}
|
||||||
self.y = y;
|
self.y = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -856,17 +1023,110 @@ impl<'a> SourceCoordCalculator<'a> {
|
||||||
enum SourceParam {
|
enum SourceParam {
|
||||||
Normal,
|
Normal,
|
||||||
HBias(HBiasParam),
|
HBias(HBiasParam),
|
||||||
|
Affine(AffineParam),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HBiasParam {
|
struct HBiasParam {
|
||||||
left: i16,
|
left: i16,
|
||||||
right: i16,
|
right: i16,
|
||||||
|
data: [u16; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HBiasParam {
|
impl HBiasParam {
|
||||||
fn load(params: &MemoryRef, index: usize) -> Self {
|
fn load(params: &MemoryRef, index: usize) -> Self {
|
||||||
let left = params.read::<i16>(index) << 3 >> 3;
|
let data = [params.read::<u16>(index), params.read::<u16>(index | 1)];
|
||||||
let right = params.read::<i16>(index | 1) << 3 >> 3;
|
let left = (data[0] as i16) << 3 >> 3;
|
||||||
Self { left, right }
|
let right = (data[1] as i16) << 3 >> 3;
|
||||||
|
Self { left, right, data }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, memory: &MemoryClient, sim: SimId, index: usize) {
|
||||||
|
let new_left = (self.left as u16 & 0x1fff) | (self.data[0] & 0xe000);
|
||||||
|
if new_left != self.data[0] {
|
||||||
|
let address = 0x00020000 + (index * 2);
|
||||||
|
memory.write(sim, address as u32, &new_left);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_right = (self.right as u16 & 0x1fff) | (self.data[1] & 0xe000);
|
||||||
|
if new_right != self.data[1] {
|
||||||
|
let address = 0x00020000 + ((index | 1) * 2);
|
||||||
|
memory.write(sim, address as u32, &new_right);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AffineParam {
|
||||||
|
src_x: FixedI32<U3>,
|
||||||
|
src_parallax: i16,
|
||||||
|
src_y: FixedI32<U3>,
|
||||||
|
dx: FixedI32<U9>,
|
||||||
|
dy: FixedI32<U9>,
|
||||||
|
data: [u16; 5],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AffineParam {
|
||||||
|
fn load(params: &MemoryRef, index: usize) -> Self {
|
||||||
|
let data = [
|
||||||
|
params.read(index & 0xffff),
|
||||||
|
params.read((index + 1) & 0xffff),
|
||||||
|
params.read((index + 2) & 0xffff),
|
||||||
|
params.read((index + 3) & 0xffff),
|
||||||
|
params.read((index + 4) & 0xffff),
|
||||||
|
];
|
||||||
|
|
||||||
|
let src_x = FixedI32::from_bits(data[0] as i16 as i32);
|
||||||
|
let src_parallax = data[1] as i16;
|
||||||
|
let src_y = FixedI32::from_bits(data[2] as i16 as i32);
|
||||||
|
let dx = FixedI32::from_bits(data[3] as i16 as i32);
|
||||||
|
let dy = FixedI32::from_bits(data[4] as i16 as i32);
|
||||||
|
|
||||||
|
AffineParam {
|
||||||
|
src_x,
|
||||||
|
src_parallax,
|
||||||
|
src_y,
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save(&self, memory: &MemoryClient, sim: SimId, index: usize) {
|
||||||
|
let new_src_x = self.src_x.to_bits() as u16;
|
||||||
|
if new_src_x != self.data[0] {
|
||||||
|
let address = 0x00020000 + 2 * (index & 0xffff);
|
||||||
|
memory.write(sim, address as u32, &new_src_x);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_src_parallax = self.src_parallax as u16;
|
||||||
|
if new_src_parallax != self.data[1] {
|
||||||
|
let address = 0x00020000 + 2 * ((index + 1) & 0xffff);
|
||||||
|
memory.write(sim, address as u32, &new_src_parallax);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_src_y = self.src_y.to_bits() as u16;
|
||||||
|
if new_src_y != self.data[2] {
|
||||||
|
let address = 0x00020000 + 2 * ((index + 2) & 0xffff);
|
||||||
|
memory.write(sim, address as u32, &new_src_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_dx = self.dx.to_bits() as u16;
|
||||||
|
if new_dx != self.data[3] {
|
||||||
|
let address = 0x00020000 + 2 * ((index + 3) & 0xffff);
|
||||||
|
memory.write(sim, address as u32, &new_dx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_dy = self.dy.to_bits() as u16;
|
||||||
|
if new_dy != self.data[4] {
|
||||||
|
let address = 0x00020000 + 2 * ((index + 4) & 0xffff);
|
||||||
|
memory.write(sim, address as u32, &new_dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn affine_coord(start: FixedI32<U3>, distance: i16, delta: FixedI32<U9>, parallax: i16) -> i16 {
|
||||||
|
let start = FixedI32::<U9>::from_num(start);
|
||||||
|
let distance = FixedI32::<U9>::from_num(distance);
|
||||||
|
let parallax = FixedI32::<U9>::from_num(parallax);
|
||||||
|
let coord = start + ((distance + parallax) * delta);
|
||||||
|
coord.to_num::<i32>() as i16
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue