Compare commits

..

No commits in common. "0d2d6bf86357c6ea3c84f39fa2ada08c5f12f146" and "acd7a27c5bd88c91073b67c667afb9800b9fff66" have entirely different histories.

22 changed files with 720 additions and 872 deletions

1058
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,11 @@ bytemuck = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] }
cpal = { git = "https://github.com/sidit77/cpal.git", rev = "66ed6be" }
directories = "6"
egui = { version = "0.34", features = ["serde"] }
egui_extras = { version = "0.34", features = ["image"] }
egui-notify = "0.22"
egui-winit = "0.34"
egui-wgpu = { version = "0.34", features = ["winit"] }
egui = { version = "0.33", features = ["serde"] }
egui_extras = { version = "0.33", features = ["image"] }
egui-notify = "0.21"
egui-winit = "0.33"
egui-wgpu = { version = "0.33", features = ["winit"] }
fxprof-processed-profile = "0.8"
fixed = { version = "1.28", features = ["num-traits"] }
gilrs = { version = "0.11", features = ["serde-serialize"] }
@ -33,8 +33,8 @@ normpath = "1"
notify = "8"
num-derive = "0.4"
num-traits = "0.2"
object = "0.39"
oneshot = { version = "0.2", features = ["async", "std"] }
object = "0.38"
oneshot = "0.1"
pollster = "0.4"
rand = "0.10"
rfd = "0.17"
@ -46,7 +46,7 @@ thread-priority = "3"
tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "sync", "time"] }
tracing = { version = "0.1", features = ["release_max_level_info"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
wgpu = "29"
wgpu = "27"
wholesym = "0.8"
winit = { version = "0.30", features = ["serde"] }

View File

@ -18,7 +18,7 @@ RUN apt-get update && \
ln -s $(which ld64.lld-21) /usr/bin/ld64.lld && \
SDK_VERSION=14.5 UNATTENDED=1 ENABLE_COMPILER_RT_INSTALL=1 TARGET_DIR=/osxcross ./build.sh
FROM rust:1.94-bookworm
FROM rust:1.93-bookworm
ADD --chmod=644 "https://apt.llvm.org/llvm-snapshot.gpg.key" /etc/apt/trusted.gpg.d/apt.llvm.org.asc
COPY llvm.sources /etc/apt/sources.list.d/llvm.sources
COPY install-llvm.sh .

@ -1 +1 @@
Subproject commit 29ade46a0a58e885a9a913f738cdb30d54e0a9c5
Subproject commit 534ce852f46f1deba9014971b1bae29e974984d7

View File

@ -19,7 +19,7 @@ use crate::{
config::CliArgs,
controller::ControllerManager,
emulator::{EmulatorClient, EmulatorCommand, SimId},
images::ImageTextureLoader,
images::ImageProcessor,
input::{MappingProvider, ShortcutProvider},
memory::MemoryClient,
persistence::Persistence,
@ -50,7 +50,7 @@ pub struct Application {
shortcuts: ShortcutProvider,
controllers: ControllerManager,
memory: Arc<MemoryClient>,
images: Arc<ImageTextureLoader>,
images: ImageProcessor,
persistence: Persistence,
viewports: HashMap<ViewportId, Viewport>,
focused: Option<ViewportId>,
@ -78,7 +78,7 @@ impl Application {
let shortcuts = ShortcutProvider::new(persistence.clone());
let controllers = ControllerManager::new(client.clone(), &mappings);
let memory = Arc::new(MemoryClient::new(client.clone()));
let images = Arc::new(ImageTextureLoader::new());
let images = ImageProcessor::new();
{
let mappings = mappings.clone();
let proxy = proxy.clone();
@ -126,13 +126,7 @@ impl Application {
}
self.viewports.insert(
viewport_id,
Viewport::new(
&self.images,
event_loop,
&self.wgpu,
self.icon.clone(),
window,
),
Viewport::new(event_loop, &self.wgpu, self.icon.clone(), window),
);
}
}
@ -160,23 +154,23 @@ impl ApplicationHandler<UserEvent> for Application {
self.open(event_loop, Box::new(profiler));
}
if self.init_chardata {
let chardata = CharacterDataWindow::new(sim_id, &self.memory, &self.images);
let chardata = CharacterDataWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(chardata));
}
if self.init_bgmap {
let bgmap = BgMapWindow::new(sim_id, &self.memory, &self.images);
let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(bgmap));
}
if self.init_objects {
let objects = ObjectWindow::new(sim_id, &self.memory, &self.images);
let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(objects));
}
if self.init_worlds {
let world = WorldWindow::new(sim_id, &self.memory, &self.images);
let world = WorldWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(world));
}
if self.init_framebuffers {
let fb = FrameBufferWindow::new(sim_id, &self.memory, &self.images);
let fb = FrameBufferWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(fb));
}
if self.init_registers {
@ -286,23 +280,23 @@ impl ApplicationHandler<UserEvent> for Application {
self.open(event_loop, Box::new(about));
}
UserEvent::OpenCharacterData(sim_id) => {
let chardata = CharacterDataWindow::new(sim_id, &self.memory, &self.images);
let chardata = CharacterDataWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(chardata));
}
UserEvent::OpenBgMap(sim_id) => {
let bgmap = BgMapWindow::new(sim_id, &self.memory, &self.images);
let bgmap = BgMapWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(bgmap));
}
UserEvent::OpenObjects(sim_id) => {
let objects = ObjectWindow::new(sim_id, &self.memory, &self.images);
let objects = ObjectWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(objects));
}
UserEvent::OpenWorlds(sim_id) => {
let world = WorldWindow::new(sim_id, &self.memory, &self.images);
let world = WorldWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(world));
}
UserEvent::OpenFrameBuffers(sim_id) => {
let fb = FrameBufferWindow::new(sim_id, &self.memory, &self.images);
let fb = FrameBufferWindow::new(sim_id, &self.memory, &mut self.images);
self.open(event_loop, Box::new(fb));
}
UserEvent::OpenRegisters(sim_id) => {
@ -391,9 +385,9 @@ impl WgpuState {
#[cfg(windows)]
let backends = wgpu::Backends::from_env()
.unwrap_or((wgpu::Backends::PRIMARY | wgpu::Backends::GL) - wgpu::Backends::VULKAN);
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends,
..wgpu::InstanceDescriptor::new_without_display_handle_from_env()
..wgpu::InstanceDescriptor::default()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
@ -428,7 +422,6 @@ struct Viewport {
}
impl Viewport {
pub fn new(
images: &Arc<ImageTextureLoader>,
event_loop: &ActiveEventLoop,
wgpu: &WgpuState,
icon: Option<Arc<IconData>>,
@ -448,13 +441,12 @@ impl Viewport {
.unwrap()
.insert(0, "Selawik".into());
ctx.set_fonts(fonts);
ctx.global_style_mut(|s| {
ctx.style_mut(|s| {
s.wrap_mode = Some(TextWrapMode::Extend);
s.visuals.menu_corner_radius = Default::default();
s.spacing.scroll = ScrollStyle::thin();
});
egui_extras::install_image_loaders(&ctx);
ctx.add_texture_loader(images.clone());
let wgpu_config = egui_wgpu::WgpuConfiguration {
present_mode: wgpu::PresentMode::AutoVsync,
@ -485,6 +477,7 @@ impl Viewport {
let render_state = painter.render_state();
let args = InitArgs {
ctx: &ctx,
window: &window,
render_state: render_state.as_ref().unwrap(),
};
@ -535,8 +528,8 @@ impl Viewport {
fn redraw(&mut self, event_loop: &ActiveEventLoop) -> Option<Action> {
let mut input = self.state.take_egui_input(&self.window);
input.viewports = std::iter::once((ViewportId::ROOT, self.info.clone())).collect();
let mut output = self.ctx.run_ui(input, |ui| {
self.app.show(ui);
let mut output = self.ctx.run(input, |ctx| {
self.app.show(ctx);
});
let clipped_primitives = self.ctx.tessellate(output.shapes, output.pixels_per_point);
self.painter.paint_and_update_textures(

View File

@ -7,7 +7,6 @@ use crate::{emulator::SimId, persistence::Persistence, window::DisplayMode};
use std::path::PathBuf;
#[derive(Parser)]
#[command(version, long_version = env!("CARGO_PKG_VERSION"))]
pub struct CliArgs {
/// The path to a virtual boy ROM to run.
pub rom: Option<PathBuf>,

View File

@ -1,5 +1,5 @@
use std::{
collections::{HashMap, hash_map::Entry},
collections::HashMap,
ops::Deref,
sync::{Arc, Mutex, Weak},
thread,
@ -7,21 +7,17 @@ use std::{
};
use egui::{
Color32, ColorImage, TextureHandle,
Color32, ColorImage, TextureHandle, TextureOptions,
epaint::ImageDelta,
generate_loader_id,
load::{LoadError, SizedTexture, TextureLoader, TexturePoll},
};
use tokio::{sync::mpsc, time::timeout};
use crate::emulator::SimId;
pub struct ImageTextureLoader {
cache: Mutex<HashMap<String, ImageEntry>>,
pub struct ImageProcessor {
sender: mpsc::UnboundedSender<Box<dyn ImageRendererImpl>>,
}
impl ImageTextureLoader {
impl ImageProcessor {
pub fn new() -> Self {
let (sender, receiver) = mpsc::unbounded_channel();
thread::spawn(move || {
@ -30,52 +26,29 @@ impl ImageTextureLoader {
.build()
.unwrap()
.block_on(async move {
let mut worker = ImageTextureLoaderWorker {
let mut worker = ImageProcessorWorker {
receiver,
renderers: vec![],
};
worker.run().await
})
});
Self {
cache: Mutex::new(HashMap::new()),
sender,
}
Self { sender }
}
pub fn add<const N: usize, R: ImageRenderer<N> + 'static>(
&self,
sim_id: SimId,
renderer: R,
params: R::Params,
) -> ImageParams<R::Params> {
) -> ([ImageHandle; N], ImageParams<R::Params>) {
let states = renderer.sizes().map(ImageState::new);
let handles = states.clone().map(|state| ImageHandle {
size: state.size.map(|i| i as f32),
data: state.sink,
});
let images = renderer
.sizes()
.map(|[width, height]| ImageBuffer::new(width, height));
{
let mut cache = self.cache.lock().unwrap();
for (name, state) in renderer.names().into_iter().zip(&states) {
let url = format!("vip://{sim_id}/{name}");
let entry = ImageEntry {
size: state.size.map(|i| i as f32),
data: Arc::downgrade(&state.sink),
texture: None,
};
match cache.entry(url) {
Entry::Vacant(e) => {
e.insert(entry);
}
Entry::Occupied(mut e) => {
// Only overwrite an old entry if that entry is dead.
// Otherwise, we clear out the reference to a texture actually in use.
if e.get().data.strong_count() == 0 {
e.insert(entry);
}
}
}
}
}
let sink = Arc::new(Mutex::new(params.clone()));
let _ = self.sender.send(Box::new(ImageRendererWrapper {
renderer,
@ -83,87 +56,20 @@ impl ImageTextureLoader {
images,
states,
}));
ImageParams {
let params = ImageParams {
value: params,
sink,
}
};
(handles, params)
}
}
impl TextureLoader for ImageTextureLoader {
fn id(&self) -> &str {
generate_loader_id!(ImageTextureLoader2)
}
fn load(
&self,
ctx: &egui::Context,
uri: &str,
texture_options: egui::TextureOptions,
_size_hint: egui::SizeHint,
) -> Result<TexturePoll, LoadError> {
let mut cache = self.cache.lock().unwrap();
let Some(entry) = cache.get_mut(uri) else {
return Err(LoadError::NotSupported);
};
if texture_options != egui::TextureOptions::NEAREST {
return Err(LoadError::Loading(
"Only TextureOptions::NEAREST are supported".into(),
));
}
let Some(data) = entry.data.upgrade() else {
cache.remove(uri);
return Err(LoadError::Loading("Backing loader no longer exists".into()));
};
match (data.lock().unwrap().take(), entry.texture.as_ref()) {
(Some(update), Some(handle)) => {
let delta = ImageDelta::full(update, texture_options);
ctx.tex_manager().write().set(handle.id(), delta);
let texture = SizedTexture::new(handle, entry.size);
Ok(TexturePoll::Ready { texture })
}
(Some(update), None) => {
let handle = ctx.load_texture(uri, update, texture_options);
let texture = SizedTexture::new(&handle, entry.size);
entry.texture.replace(handle);
Ok(TexturePoll::Ready { texture })
}
(None, Some(handle)) => {
let texture = SizedTexture::new(handle, entry.size);
Ok(TexturePoll::Ready { texture })
}
(None, None) => {
let size = entry.size.into();
Ok(TexturePoll::Pending { size: Some(size) })
}
}
}
fn forget(&self, uri: &str) {
let _ = uri;
}
fn forget_all(&self) {}
fn byte_size(&self) -> usize {
self.cache
.lock()
.unwrap()
.values()
.map(|entry| {
let [width, height] = entry.size;
width as usize * height as usize * 4
})
.sum()
}
}
struct ImageTextureLoaderWorker {
struct ImageProcessorWorker {
receiver: mpsc::UnboundedReceiver<Box<dyn ImageRendererImpl>>,
renderers: Vec<Box<dyn ImageRendererImpl>>,
}
impl ImageTextureLoaderWorker {
impl ImageProcessorWorker {
async fn run(&mut self) {
loop {
if self.renderers.is_empty() {
@ -236,10 +142,16 @@ impl ImageBuffer {
}
}
struct ImageEntry {
#[derive(Clone)]
pub struct ImageHandle {
size: [f32; 2],
data: Weak<Mutex<Option<Arc<ColorImage>>>>,
texture: Option<TextureHandle>,
data: Arc<Mutex<Option<Arc<ColorImage>>>>,
}
impl ImageHandle {
fn pull(&mut self) -> Option<Arc<ColorImage>> {
self.data.lock().unwrap().take()
}
}
pub struct ImageParams<T> {
@ -266,7 +178,6 @@ impl<T: Clone + Eq> ImageParams<T> {
pub trait ImageRenderer<const N: usize>: Send {
type Params: Clone + Send;
fn names(&self) -> [&str; N];
fn sizes(&self) -> [[usize; 2]; N];
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; N]);
}
@ -335,3 +246,83 @@ impl<const N: usize, R: ImageRenderer<N> + Send> ImageRendererImpl for ImageRend
Ok(())
}
}
pub struct ImageTextureLoader {
cache: Mutex<HashMap<String, (ImageHandle, Option<TextureHandle>)>>,
}
impl ImageTextureLoader {
pub fn new(renderers: impl IntoIterator<Item = (String, ImageHandle)>) -> Self {
let mut cache = HashMap::new();
for (key, image) in renderers {
cache.insert(key, (image, None));
}
Self {
cache: Mutex::new(cache),
}
}
}
impl TextureLoader for ImageTextureLoader {
fn id(&self) -> &str {
concat!(module_path!(), "ImageTextureLoader")
}
fn load(
&self,
ctx: &egui::Context,
uri: &str,
texture_options: TextureOptions,
_size_hint: egui::SizeHint,
) -> Result<TexturePoll, LoadError> {
let mut cache = self.cache.lock().unwrap();
let Some((image, maybe_handle)) = cache.get_mut(uri) else {
return Err(LoadError::NotSupported);
};
if texture_options != TextureOptions::NEAREST {
return Err(LoadError::Loading(
"Only TextureOptions::NEAREST are supported".into(),
));
}
match (image.pull(), maybe_handle.as_ref()) {
(Some(update), Some(handle)) => {
let delta = ImageDelta::full(update, texture_options);
ctx.tex_manager().write().set(handle.id(), delta);
let texture = SizedTexture::new(handle, image.size);
Ok(TexturePoll::Ready { texture })
}
(Some(update), None) => {
let handle = ctx.load_texture(uri, update, texture_options);
let texture = SizedTexture::new(&handle, image.size);
maybe_handle.replace(handle);
Ok(TexturePoll::Ready { texture })
}
(None, Some(handle)) => {
let texture = SizedTexture::new(handle, image.size);
Ok(TexturePoll::Ready { texture })
}
(None, None) => {
let size = image.size.into();
Ok(TexturePoll::Pending { size: Some(size) })
}
}
}
fn forget(&self, uri: &str) {
let _ = uri;
}
fn forget_all(&self) {}
fn byte_size(&self) -> usize {
self.cache
.lock()
.unwrap()
.values()
.map(|(image, _)| {
let [width, height] = image.size;
width as usize * height as usize * 4
})
.sum()
}
}

View File

@ -1,7 +1,7 @@
use std::sync::Arc;
pub use about::AboutWindow;
use egui::{Ui, ViewportBuilder, ViewportId};
use egui::{Context, ViewportBuilder, ViewportId};
pub use game::GameWindow;
pub use game_screen::DisplayMode;
pub use gdb::GdbServerWindow;
@ -32,11 +32,8 @@ pub trait AppWindow {
fn sim_id(&self) -> SimId {
SimId::Player1
}
fn image_url(&self, name: &str) -> String {
format!("vip://{}/{name}", self.sim_id())
}
fn initial_viewport(&self) -> ViewportBuilder;
fn show(&mut self, ui: &mut Ui);
fn show(&mut self, ctx: &Context);
fn on_init(&mut self, args: InitArgs) {
let _ = args;
}
@ -52,6 +49,7 @@ pub trait AppWindow {
}
pub struct InitArgs<'a> {
pub ctx: &'a Context,
pub window: &'a Arc<Window>,
pub render_state: &'a egui_wgpu::RenderState,
}

View File

@ -1,4 +1,4 @@
use egui::{CentralPanel, Image, Ui, ViewportBuilder, ViewportId};
use egui::{CentralPanel, Context, Image, ViewportBuilder, ViewportId};
use super::AppWindow;
@ -15,8 +15,8 @@ impl AppWindow for AboutWindow {
.with_inner_size((300.0, 200.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn show(&mut self, ctx: &Context) {
CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.label("Lemur Virtual Boy Emulator");
ui.label(format!("Version {}", env!("CARGO_PKG_VERSION")));

View File

@ -13,7 +13,7 @@ use crate::{
};
use anyhow::Context as _;
use egui::{
Align2, Button, CentralPanel, Color32, Context, Frame, MenuBar, Panel, Ui, Vec2,
Align2, Button, CentralPanel, Color32, Context, Frame, MenuBar, TopBottomPanel, Ui, Vec2,
ViewportBuilder, ViewportCommand, ViewportId, Window,
};
use egui_notify::{Anchor, Toast, Toasts};
@ -67,7 +67,7 @@ impl GameWindow {
}
}
fn show_menu(&mut self, 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;
@ -208,7 +208,7 @@ impl GameWindow {
pollster::block_on(self.take_screenshot());
}
});
ui.menu_button("Options", |ui| self.show_options_menu(ui));
ui.menu_button("Options", |ui| self.show_options_menu(ctx, ui));
ui.menu_button("Multiplayer", |ui| {
let has_player_2 = self.client.sim_state(SimId::Player2) != SimState::Uninitialized;
if self.sim_id == SimId::Player1
@ -328,7 +328,7 @@ impl GameWindow {
Ok(Some(path.display().to_string()))
}
fn show_options_menu(&mut self, ui: &mut Ui) {
fn show_options_menu(&mut self, ctx: &Context, ui: &mut Ui) {
ui.menu_button("Video", |ui| {
ui.menu_button("Screen Size", |ui| {
let current_dims = self.config.dimensions;
@ -344,7 +344,7 @@ impl GameWindow {
.selectable_button((current_dims - dims).length() < 1.0, label)
.clicked()
{
ui.send_viewport_cmd(ViewportCommand::InnerSize(dims));
ctx.send_viewport_cmd(ViewportCommand::InnerSize(dims));
}
}
});
@ -374,7 +374,7 @@ impl GameWindow {
let new_proportions = display_mode.proportions();
let scale = new_proportions / old_proportions;
if scale != Vec2::new(1.0, 1.0) {
ui.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
ctx.send_viewport_cmd(ViewportCommand::InnerSize(current_dims * scale));
}
self.update_config(|c| {
@ -497,9 +497,9 @@ impl AppWindow for GameWindow {
.with_inner_size(self.config.dimensions)
}
fn show(&mut self, ui: &mut Ui) {
fn show(&mut self, ctx: &Context) {
let dimensions = {
let bounds = ui.content_rect();
let bounds = ctx.available_rect();
bounds.max - bounds.min
};
self.update_config(|c| c.dimensions = dimensions);
@ -509,11 +509,11 @@ impl AppWindow for GameWindow {
self.toasts.add(toast);
}
}
Panel::top("menubar")
.exact_size(22.0)
.show_inside(ui, |ui| {
TopBottomPanel::top("menubar")
.exact_height(22.0)
.show(ctx, |ui| {
MenuBar::new().ui(ui, |ui| {
self.show_menu(ui);
self.show_menu(ctx, ui);
});
});
if self.color_picker.is_some() {
@ -521,18 +521,18 @@ impl AppWindow for GameWindow {
.title_bar(false)
.resizable(false)
.anchor(Align2::CENTER_CENTER, Vec2::ZERO)
.show(ui, |ui| {
.show(ctx, |ui| {
self.show_color_picker(ui);
});
}
let frame = Frame::central_panel(ui.style()).fill(Color32::BLACK);
CentralPanel::default().frame(frame).show_inside(ui, |ui| {
let frame = Frame::central_panel(&ctx.style()).fill(Color32::BLACK);
CentralPanel::default().frame(frame).show(ctx, |ui| {
if let Some(screen) = self.screen.as_mut() {
screen.update(self.config.display_mode, self.config.colors);
ui.add(screen);
}
});
self.toasts.show(ui);
self.toasts.show(ctx);
}
fn on_init(&mut self, args: InitArgs) {

View File

@ -53,8 +53,8 @@ impl GameScreen {
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("render pipeline layout"),
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let create_render_pipeline = |entry_point: &str| {
@ -92,7 +92,7 @@ impl GameScreen {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
multiview: None,
cache: None,
})
};

View File

@ -56,10 +56,10 @@ impl AppWindow for GdbServerWindow {
.with_inner_size((300.0, 200.0))
}
fn show(&mut self, ui: &mut egui::Ui) {
fn show(&mut self, ctx: &egui::Context) {
let port_num: Option<u16> = self.port_str.parse().ok();
let status = self.server.status();
CentralPanel::default().show_inside(ui, |ui| {
CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
if port_num.is_none() {
let style = ui.style_mut();

View File

@ -1,6 +1,6 @@
use egui::{
Button, CentralPanel, Event, KeyboardShortcut, Label, Layout, Slider, Ui, ViewportBuilder,
ViewportId,
Button, CentralPanel, Context, Event, KeyboardShortcut, Label, Layout, Slider, Ui,
ViewportBuilder, ViewportId,
};
use egui_extras::{Column, TableBuilder};
@ -135,8 +135,8 @@ impl AppWindow for HotkeysWindow {
.with_inner_size((400.0, 400.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn show(&mut self, ctx: &Context) {
CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("Use defaults").clicked() {
self.shortcuts.reset();

View File

@ -1,4 +1,6 @@
use egui::{Button, CentralPanel, Label, Layout, Panel, Ui, ViewportBuilder, ViewportId};
use egui::{
Button, CentralPanel, Context, Label, Layout, TopBottomPanel, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, TableBuilder};
use gilrs::{EventType, GamepadId};
use std::sync::RwLock;
@ -170,8 +172,8 @@ impl AppWindow for InputWindow {
.with_inner_size((600.0, 400.0))
}
fn show(&mut self, ui: &mut Ui) {
Panel::top("options").show_inside(ui, |ui| {
fn show(&mut self, ctx: &Context) {
TopBottomPanel::top("options").show(ctx, |ui| {
ui.horizontal(|ui| {
let old_active_tab = self.active_tab;
ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1");
@ -186,7 +188,7 @@ impl AppWindow for InputWindow {
}
});
});
CentralPanel::default().show_inside(ui, |ui| {
CentralPanel::default().show(ctx, |ui| {
match self.active_tab {
InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1),
InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2),

View File

@ -96,10 +96,10 @@ impl AppWindow for ProfileWindow {
.with_inner_size((300.0, 200.0))
}
fn show(&mut self, ui: &mut egui::Ui) {
fn show(&mut self, ctx: &egui::Context) {
let status = self.profiler.status();
let recording = matches!(status, ProfilerStatus::Recording);
CentralPanel::default().show_inside(ui, |ui| {
CentralPanel::default().show(ctx, |ui| {
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.add(
@ -164,7 +164,7 @@ impl AppWindow for ProfileWindow {
_ => {}
}
});
self.toasts.show(ui);
self.toasts.show(ctx);
}
fn on_init(&mut self, args: InitArgs) {

View File

@ -1,7 +1,8 @@
use std::{collections::VecDeque, sync::mpsc};
use egui::{
Align, CentralPanel, FontFamily, Label, RichText, ScrollArea, Ui, ViewportBuilder, ViewportId,
Align, CentralPanel, Context, FontFamily, Label, RichText, ScrollArea, ViewportBuilder,
ViewportId,
};
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
@ -45,7 +46,7 @@ impl AppWindow for TerminalWindow {
.with_inner_size((640.0, 480.0))
}
fn show(&mut self, ui: &mut Ui) {
fn show(&mut self, ctx: &Context) {
if let Ok(text) = self.receiver.try_recv() {
let mut rest = text.as_str();
while let Some(index) = rest.find('\n') {
@ -60,7 +61,7 @@ impl AppWindow for TerminalWindow {
}
self.lines.back_mut().unwrap().push_str(rest);
}
CentralPanel::default().show_inside(ui, |ui| {
CentralPanel::default().show(ctx, |ui| {
ScrollArea::vertical()
.stick_to_bottom(true)
.auto_shrink([false, false])

View File

@ -1,17 +1,17 @@
use std::sync::Arc;
use egui::{
Align, CentralPanel, Checkbox, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit,
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
TextureOptions, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView},
window::{
AppWindow,
AppWindow, InitArgs,
utils::{NumberEdit, UiExt},
},
};
@ -20,6 +20,7 @@ use super::utils::{self, CellData, CharacterGrid};
pub struct BgMapWindow {
sim_id: SimId,
loader: Arc<ImageTextureLoader>,
memory: Arc<MemoryClient>,
bgmaps: MemoryView,
cell_index: usize,
@ -30,11 +31,14 @@ pub struct BgMapWindow {
}
impl BgMapWindow {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
let renderer = BgMapRenderer::new(sim_id, memory);
let params = images.add(sim_id, renderer, BgMapParams::default());
let ([cell, bgmap], params) = images.add(renderer, BgMapParams::default());
let loader =
ImageTextureLoader::new([("vip://cell".into(), cell), ("vip://bgmap".into(), bgmap)]);
Self {
sim_id,
loader: Arc::new(loader),
memory: memory.clone(),
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
cell_index: params.cell_index,
@ -86,7 +90,7 @@ impl BgMapWindow {
});
});
});
let image = Image::new(self.image_url("bgmap-cell"))
let image = Image::new("vip://cell")
.maintain_aspect_ratio(true)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
@ -158,7 +162,7 @@ impl BgMapWindow {
}
fn show_bgmap(&mut self, ui: &mut Ui) {
let grid = CharacterGrid::new(self.image_url("bgmap"))
let grid = CharacterGrid::new("vip://bgmap")
.with_scale(self.scale)
.with_grid(self.show_grid)
.with_selected(self.cell_index % 4096);
@ -183,8 +187,12 @@ impl AppWindow for BgMapWindow {
.with_inner_size((640.0, 480.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn on_init(&mut self, args: InitArgs) {
args.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).at_most(200.0))
@ -292,10 +300,6 @@ impl BgMapRenderer {
impl ImageRenderer<2> for BgMapRenderer {
type Params = BgMapParams;
fn names(&self) -> [&str; 2] {
["bgmap-cell", "bgmap"]
}
fn sizes(&self) -> [[usize; 2]; 2] {
[[8, 8], [8 * 64, 8 * 64]]
}

View File

@ -1,18 +1,18 @@
use std::fmt::Display;
use std::{fmt::Display, sync::Arc};
use egui::{
Align, CentralPanel, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit, TextureOptions,
Ui, Vec2, ViewportBuilder, ViewportId,
Align, CentralPanel, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
TextureOptions, Ui, Vec2, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use serde::{Deserialize, Serialize};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView},
window::{
AppWindow,
AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _},
},
};
@ -81,6 +81,7 @@ impl Display for Palette {
pub struct CharacterDataWindow {
sim_id: SimId,
loader: Arc<ImageTextureLoader>,
brightness: MemoryView,
palettes: MemoryView,
palette: Palette,
@ -91,11 +92,16 @@ pub struct CharacterDataWindow {
}
impl CharacterDataWindow {
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self {
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self {
let renderer = CharDataRenderer::new(sim_id, memory);
let params = images.add(sim_id, renderer, CharDataParams::default());
let ([char, chardata], params) = images.add(renderer, CharDataParams::default());
let loader = ImageTextureLoader::new([
("vip://char".into(), char),
("vip://chardata".into(), chardata),
]);
Self {
sim_id,
loader: Arc::new(loader),
brightness: memory.watch(sim_id, 0x0005f824, 8),
palettes: memory.watch(sim_id, 0x0005f860, 16),
palette: params.palette,
@ -154,7 +160,7 @@ impl CharacterDataWindow {
});
});
});
let image = Image::new(self.image_url("chardata-char"))
let image = Image::new("vip://char")
.maintain_aspect_ratio(true)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
@ -220,7 +226,7 @@ impl CharacterDataWindow {
}
fn show_chardata(&mut self, ui: &mut Ui) {
let grid = CharacterGrid::new(self.image_url("chardata"))
let grid = CharacterGrid::new("vip://chardata")
.with_scale(self.scale)
.with_grid(self.show_grid)
.with_selected(self.index);
@ -245,8 +251,12 @@ impl AppWindow for CharacterDataWindow {
.with_inner_size((640.0, 480.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn on_init(&mut self, args: InitArgs) {
args.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).at_most(200.0))
@ -279,10 +289,6 @@ struct CharDataRenderer {
impl ImageRenderer<2> for CharDataRenderer {
type Params = CharDataParams;
fn names(&self) -> [&str; 2] {
["chardata-char", "chardata"]
}
fn sizes(&self) -> [[usize; 2]; 2] {
[[8, 8], [16 * 8, 128 * 8]]
}

View File

@ -1,15 +1,17 @@
use std::sync::Arc;
use egui::{
Align, CentralPanel, Color32, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui,
Align, CentralPanel, Color32, Context, Image, ScrollArea, Slider, TextEdit, TextureOptions, Ui,
ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView},
window::{
AppWindow,
AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _},
},
};
@ -18,6 +20,7 @@ use super::utils;
pub struct FrameBufferWindow {
sim_id: SimId,
loader: Arc<ImageTextureLoader>,
index: usize,
left: bool,
right: bool,
@ -27,7 +30,7 @@ pub struct FrameBufferWindow {
}
impl FrameBufferWindow {
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &ImageTextureLoader) -> Self {
pub fn new(sim_id: SimId, memory: &MemoryClient, images: &mut ImageProcessor) -> Self {
let initial_params = FrameBufferParams {
index: 0,
left: true,
@ -37,9 +40,11 @@ impl FrameBufferWindow {
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
};
let renderer = FrameBufferRenderer::new(sim_id, memory);
let params = images.add(sim_id, renderer, initial_params);
let ([buffer], params) = images.add(renderer, initial_params);
let loader = ImageTextureLoader::new([("vip://buffer".into(), buffer)]);
Self {
sim_id,
loader: Arc::new(loader),
index: params.index,
left: params.left,
right: params.right,
@ -126,7 +131,7 @@ impl FrameBufferWindow {
}
fn show_buffers(&mut self, ui: &mut Ui) {
let image = Image::new(self.image_url("buffer"))
let image = Image::new("vip://buffer")
.fit_to_original_size(self.scale)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
@ -148,8 +153,12 @@ impl AppWindow for FrameBufferWindow {
.with_inner_size((640.0, 480.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn on_init(&mut self, args: InitArgs) {
args.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).at_most(200.0))
@ -199,10 +208,6 @@ impl FrameBufferRenderer {
impl ImageRenderer<1> for FrameBufferRenderer {
type Params = FrameBufferParams;
fn names(&self) -> [&str; 1] {
["buffer"]
}
fn sizes(&self) -> [[usize; 2]; 1] {
[[384, 224]]
}

View File

@ -1,17 +1,17 @@
use std::sync::Arc;
use egui::{
Align, CentralPanel, Checkbox, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit,
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
TextureOptions, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryView},
window::{
AppWindow,
AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _},
},
};
@ -20,6 +20,7 @@ use super::utils::{self, Object};
pub struct ObjectWindow {
sim_id: SimId,
loader: Arc<ImageTextureLoader>,
memory: Arc<MemoryClient>,
objects: MemoryView,
index: usize,
@ -29,7 +30,7 @@ pub struct ObjectWindow {
}
impl ObjectWindow {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
let initial_params = ObjectParams {
index: 0,
generic_palette: false,
@ -37,9 +38,12 @@ impl ObjectWindow {
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
};
let renderer = ObjectRenderer::new(sim_id, memory);
let params = images.add(sim_id, renderer, initial_params);
let ([zoom, full], params) = images.add(renderer, initial_params);
let loader =
ImageTextureLoader::new([("vip://zoom".into(), zoom), ("vip://full".into(), full)]);
Self {
sim_id,
loader: Arc::new(loader),
memory: memory.clone(),
objects: memory.watch(sim_id, 0x0003e000, 0x2000),
index: params.index,
@ -78,7 +82,7 @@ impl ObjectWindow {
});
});
});
let image = Image::new(self.image_url("object-zoom"))
let image = Image::new("vip://zoom")
.maintain_aspect_ratio(true)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
@ -182,7 +186,7 @@ impl ObjectWindow {
}
fn show_object(&mut self, ui: &mut Ui) {
let image = Image::new(self.image_url("object-full"))
let image = Image::new("vip://full")
.fit_to_original_size(self.scale)
.texture_options(TextureOptions::NEAREST);
ui.add(image);
@ -204,8 +208,12 @@ impl AppWindow for ObjectWindow {
.with_inner_size((640.0, 500.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn on_init(&mut self, args: InitArgs) {
args.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).at_most(200.0))
@ -319,10 +327,6 @@ impl ObjectRenderer {
impl ImageRenderer<2> for ObjectRenderer {
type Params = ObjectParams;
fn names(&self) -> [&str; 2] {
["object-zoom", "object-full"]
}
fn sizes(&self) -> [[usize; 2]; 2] {
[[8, 8], [384, 224]]
}

View File

@ -1,8 +1,8 @@
use std::sync::Arc;
use egui::{
Align, Button, CentralPanel, Checkbox, Color32, Direction, Label, Layout, ScrollArea, TextEdit,
Ui, ViewportBuilder, ViewportId,
Align, Button, CentralPanel, Checkbox, Color32, Context, Direction, Label, Layout, ScrollArea,
TextEdit, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
@ -644,8 +644,8 @@ impl AppWindow for RegisterWindow {
.with_inner_size((800.0, 480.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn show(&mut self, ctx: &Context) {
CentralPanel::default().show(ctx, |ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.horizontal_top(|ui| {
let width = ui.available_width();

View File

@ -1,7 +1,7 @@
use std::{fmt::Display, sync::Arc};
use egui::{
Align, CentralPanel, Checkbox, Color32, ComboBox, Image, ScrollArea, Slider, TextEdit,
Align, CentralPanel, Checkbox, Color32, ComboBox, Context, Image, ScrollArea, Slider, TextEdit,
TextureOptions, Ui, ViewportBuilder, ViewportId,
};
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
@ -14,10 +14,10 @@ use num_traits::{FromPrimitive, ToPrimitive};
use crate::{
emulator::SimId,
images::{ImageBuffer, ImageParams, ImageRenderer, ImageTextureLoader},
images::{ImageBuffer, ImageParams, ImageProcessor, ImageRenderer, ImageTextureLoader},
memory::{MemoryClient, MemoryRef, MemoryView},
window::{
AppWindow,
AppWindow, InitArgs,
utils::{NumberEdit, UiExt as _},
},
};
@ -26,6 +26,7 @@ use super::utils::{self, CellData, Object, shade};
pub struct WorldWindow {
sim_id: SimId,
loader: Arc<ImageTextureLoader>,
memory: Arc<MemoryClient>,
worlds: MemoryView,
bgmaps: MemoryView,
@ -38,7 +39,7 @@ pub struct WorldWindow {
}
impl WorldWindow {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &ImageTextureLoader) -> Self {
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>, images: &mut ImageProcessor) -> Self {
let initial_params = WorldParams {
index: 31,
generic_palette: false,
@ -46,9 +47,11 @@ impl WorldWindow {
right_color: Color32::from_rgb(0x00, 0xc6, 0xf0),
};
let renderer = WorldRenderer::new(sim_id, memory);
let params = images.add(sim_id, renderer, initial_params);
let ([world], params) = images.add(renderer, initial_params);
let loader = ImageTextureLoader::new([("vip://world".into(), world)]);
Self {
sim_id,
loader: Arc::new(loader),
memory: memory.clone(),
worlds: memory.watch(sim_id, 0x0003d800, 0x400),
bgmaps: memory.watch(sim_id, 0x00020000, 0x20000),
@ -423,7 +426,7 @@ impl WorldWindow {
}
fn show_world(&mut self, ui: &mut Ui) {
let image = Image::new(self.image_url("world"))
let image = Image::new("vip://world")
.fit_to_original_size(self.scale)
.texture_options(TextureOptions::NEAREST);
let res = ui.add(image);
@ -520,8 +523,12 @@ impl AppWindow for WorldWindow {
.with_inner_size((640.0, 520.0))
}
fn show(&mut self, ui: &mut Ui) {
CentralPanel::default().show_inside(ui, |ui| {
fn on_init(&mut self, args: InitArgs) {
args.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).at_most(200.0))
@ -772,10 +779,6 @@ impl WorldRenderer {
impl ImageRenderer<1> for WorldRenderer {
type Params = WorldParams;
fn names(&self) -> [&str; 1] {
["world"]
}
fn sizes(&self) -> [[usize; 2]; 1] {
[[384, 224]]
}