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"] } 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 = "6" directories = "6"
egui = { version = "0.34", features = ["serde"] } egui = { version = "0.33", features = ["serde"] }
egui_extras = { version = "0.34", features = ["image"] } egui_extras = { version = "0.33", features = ["image"] }
egui-notify = "0.22" egui-notify = "0.21"
egui-winit = "0.34" egui-winit = "0.33"
egui-wgpu = { version = "0.34", features = ["winit"] } egui-wgpu = { version = "0.33", features = ["winit"] }
fxprof-processed-profile = "0.8" fxprof-processed-profile = "0.8"
fixed = { version = "1.28", features = ["num-traits"] } fixed = { version = "1.28", features = ["num-traits"] }
gilrs = { version = "0.11", features = ["serde-serialize"] } gilrs = { version = "0.11", features = ["serde-serialize"] }
@ -33,8 +33,8 @@ normpath = "1"
notify = "8" notify = "8"
num-derive = "0.4" num-derive = "0.4"
num-traits = "0.2" num-traits = "0.2"
object = "0.39" object = "0.38"
oneshot = { version = "0.2", features = ["async", "std"] } oneshot = "0.1"
pollster = "0.4" pollster = "0.4"
rand = "0.10" rand = "0.10"
rfd = "0.17" rfd = "0.17"
@ -46,7 +46,7 @@ thread-priority = "3"
tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "sync", "time"] } tokio = { version = "1", features = ["io-util", "macros", "net", "rt", "sync", "time"] }
tracing = { version = "0.1", features = ["release_max_level_info"] } tracing = { version = "0.1", features = ["release_max_level_info"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
wgpu = "29" wgpu = "27"
wholesym = "0.8" wholesym = "0.8"
winit = { version = "0.30", features = ["serde"] } 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 && \ 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 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 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 llvm.sources /etc/apt/sources.list.d/llvm.sources
COPY install-llvm.sh . COPY install-llvm.sh .

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
use std::{ use std::{
collections::{HashMap, hash_map::Entry}, collections::HashMap,
ops::Deref, ops::Deref,
sync::{Arc, Mutex, Weak}, sync::{Arc, Mutex, Weak},
thread, thread,
@ -7,21 +7,17 @@ use std::{
}; };
use egui::{ use egui::{
Color32, ColorImage, TextureHandle, Color32, ColorImage, TextureHandle, TextureOptions,
epaint::ImageDelta, epaint::ImageDelta,
generate_loader_id,
load::{LoadError, SizedTexture, TextureLoader, TexturePoll}, load::{LoadError, SizedTexture, TextureLoader, TexturePoll},
}; };
use tokio::{sync::mpsc, time::timeout}; use tokio::{sync::mpsc, time::timeout};
use crate::emulator::SimId; pub struct ImageProcessor {
pub struct ImageTextureLoader {
cache: Mutex<HashMap<String, ImageEntry>>,
sender: mpsc::UnboundedSender<Box<dyn ImageRendererImpl>>, sender: mpsc::UnboundedSender<Box<dyn ImageRendererImpl>>,
} }
impl ImageTextureLoader { 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 || {
@ -30,52 +26,29 @@ impl ImageTextureLoader {
.build() .build()
.unwrap() .unwrap()
.block_on(async move { .block_on(async move {
let mut worker = ImageTextureLoaderWorker { let mut worker = ImageProcessorWorker {
receiver, receiver,
renderers: vec![], renderers: vec![],
}; };
worker.run().await worker.run().await
}) })
}); });
Self { Self { sender }
cache: Mutex::new(HashMap::new()),
sender,
}
} }
pub fn add<const N: usize, R: ImageRenderer<N> + 'static>( pub fn add<const N: usize, R: ImageRenderer<N> + 'static>(
&self, &self,
sim_id: SimId,
renderer: R, renderer: R,
params: R::Params, params: R::Params,
) -> ImageParams<R::Params> { ) -> ([ImageHandle; N], ImageParams<R::Params>) {
let states = renderer.sizes().map(ImageState::new); 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 let images = renderer
.sizes() .sizes()
.map(|[width, height]| ImageBuffer::new(width, height)); .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 sink = Arc::new(Mutex::new(params.clone()));
let _ = self.sender.send(Box::new(ImageRendererWrapper { let _ = self.sender.send(Box::new(ImageRendererWrapper {
renderer, renderer,
@ -83,87 +56,20 @@ impl ImageTextureLoader {
images, images,
states, states,
})); }));
ImageParams { let params = ImageParams {
value: params, value: params,
sink, sink,
}
}
}
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 { (handles, params)
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>>, receiver: mpsc::UnboundedReceiver<Box<dyn ImageRendererImpl>>,
renderers: Vec<Box<dyn ImageRendererImpl>>, renderers: Vec<Box<dyn ImageRendererImpl>>,
} }
impl ImageTextureLoaderWorker { impl ImageProcessorWorker {
async fn run(&mut self) { async fn run(&mut self) {
loop { loop {
if self.renderers.is_empty() { if self.renderers.is_empty() {
@ -236,10 +142,16 @@ impl ImageBuffer {
} }
} }
struct ImageEntry { #[derive(Clone)]
pub struct ImageHandle {
size: [f32; 2], size: [f32; 2],
data: Weak<Mutex<Option<Arc<ColorImage>>>>, data: Arc<Mutex<Option<Arc<ColorImage>>>>,
texture: Option<TextureHandle>, }
impl ImageHandle {
fn pull(&mut self) -> Option<Arc<ColorImage>> {
self.data.lock().unwrap().take()
}
} }
pub struct ImageParams<T> { pub struct ImageParams<T> {
@ -266,7 +178,6 @@ impl<T: Clone + Eq> ImageParams<T> {
pub trait ImageRenderer<const N: usize>: Send { pub trait ImageRenderer<const N: usize>: Send {
type Params: Clone + Send; type Params: Clone + Send;
fn names(&self) -> [&str; N];
fn sizes(&self) -> [[usize; 2]; N]; fn sizes(&self) -> [[usize; 2]; N];
fn render(&mut self, params: &Self::Params, images: &mut [ImageBuffer; 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(()) 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; use std::sync::Arc;
pub use about::AboutWindow; pub use about::AboutWindow;
use egui::{Ui, ViewportBuilder, ViewportId}; use egui::{Context, ViewportBuilder, ViewportId};
pub use game::GameWindow; pub use game::GameWindow;
pub use game_screen::DisplayMode; pub use game_screen::DisplayMode;
pub use gdb::GdbServerWindow; pub use gdb::GdbServerWindow;
@ -32,11 +32,8 @@ pub trait AppWindow {
fn sim_id(&self) -> SimId { fn sim_id(&self) -> SimId {
SimId::Player1 SimId::Player1
} }
fn image_url(&self, name: &str) -> String {
format!("vip://{}/{name}", self.sim_id())
}
fn initial_viewport(&self) -> ViewportBuilder; 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) { fn on_init(&mut self, args: InitArgs) {
let _ = args; let _ = args;
} }
@ -52,6 +49,7 @@ pub trait AppWindow {
} }
pub struct InitArgs<'a> { pub struct InitArgs<'a> {
pub ctx: &'a Context,
pub window: &'a Arc<Window>, pub window: &'a Arc<Window>,
pub render_state: &'a egui_wgpu::RenderState, 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; use super::AppWindow;
@ -15,8 +15,8 @@ impl AppWindow for AboutWindow {
.with_inner_size((300.0, 200.0)) .with_inner_size((300.0, 200.0))
} }
fn show(&mut self, ui: &mut Ui) { fn show(&mut self, ctx: &Context) {
CentralPanel::default().show_inside(ui, |ui| { CentralPanel::default().show(ctx, |ui| {
ui.vertical_centered(|ui| { ui.vertical_centered(|ui| {
ui.label("Lemur Virtual Boy Emulator"); ui.label("Lemur Virtual Boy Emulator");
ui.label(format!("Version {}", env!("CARGO_PKG_VERSION"))); ui.label(format!("Version {}", env!("CARGO_PKG_VERSION")));

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use egui::{ use egui::{
Button, CentralPanel, Event, KeyboardShortcut, Label, Layout, Slider, Ui, ViewportBuilder, Button, CentralPanel, Context, Event, KeyboardShortcut, Label, Layout, Slider, Ui,
ViewportId, ViewportBuilder, ViewportId,
}; };
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
@ -135,8 +135,8 @@ impl AppWindow for HotkeysWindow {
.with_inner_size((400.0, 400.0)) .with_inner_size((400.0, 400.0))
} }
fn show(&mut self, ui: &mut Ui) { fn show(&mut self, ctx: &Context) {
CentralPanel::default().show_inside(ui, |ui| { CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Use defaults").clicked() { if ui.button("Use defaults").clicked() {
self.shortcuts.reset(); 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 egui_extras::{Column, TableBuilder};
use gilrs::{EventType, GamepadId}; use gilrs::{EventType, GamepadId};
use std::sync::RwLock; use std::sync::RwLock;
@ -170,8 +172,8 @@ impl AppWindow for InputWindow {
.with_inner_size((600.0, 400.0)) .with_inner_size((600.0, 400.0))
} }
fn show(&mut self, ui: &mut Ui) { fn show(&mut self, ctx: &Context) {
Panel::top("options").show_inside(ui, |ui| { TopBottomPanel::top("options").show(ctx, |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let old_active_tab = self.active_tab; let old_active_tab = self.active_tab;
ui.selectable_value(&mut self.active_tab, InputTab::Player1, "Player 1"); 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 { match self.active_tab {
InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1), InputTab::Player1 => self.show_key_bindings(ui, SimId::Player1),
InputTab::Player2 => self.show_key_bindings(ui, SimId::Player2), 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)) .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 status = self.profiler.status();
let recording = matches!(status, ProfilerStatus::Recording); let recording = matches!(status, ProfilerStatus::Recording);
CentralPanel::default().show_inside(ui, |ui| { CentralPanel::default().show(ctx, |ui| {
ui.horizontal_wrapped(|ui| { ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
ui.add( 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) { fn on_init(&mut self, args: InitArgs) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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