From 25e3081d27e7ca6a0631187709dbaed59205e140 Mon Sep 17 00:00:00 2001 From: kelpsyberry <138107494+kelpsyberry@users.noreply.github.com> Date: Sun, 3 Dec 2023 19:42:46 +0100 Subject: [PATCH] Update dependencies Also switched to vendored imgui until imgui-winit-support is updated --- core/Cargo.toml | 4 +- core/src/cpu/debug.rs | 11 +- core/src/emu/input.rs | 1 + frontend/desktop/Cargo.toml | 23 +- frontend/desktop/src/audio/input/cpal.rs | 12 +- frontend/desktop/src/audio/output/cpal.rs | 12 +- .../desktop/src/debug_views/common/disasm.rs | 1 + frontend/desktop/src/emu/gdb_server.rs | 1 + frontend/desktop/src/input.rs | 26 +- frontend/desktop/src/input/key_codes.rs | 290 +++++++++++++++ frontend/desktop/src/input/map.rs | 41 +- frontend/desktop/src/input/state.rs | 24 +- frontend/desktop/src/input/trigger.rs | 350 ++++++++++++------ frontend/desktop/src/ui.rs | 5 +- .../desktop/src/ui/config_editor/input_map.rs | 35 +- frontend/desktop/src/ui/utils.rs | 1 + frontend/desktop/src/ui/window.rs | 127 ++++--- frontend/desktop/src/utils.rs | 2 +- frontend/web/crate/src/lib.rs | 2 + render/soft-2d/base/Cargo.toml | 2 +- render/soft-3d/Cargo.toml | 2 +- render/wgpu-2d/Cargo.toml | 4 +- render/wgpu-2d/src/common/gfx.rs | 13 +- render/wgpu-3d/Cargo.toml | 4 +- render/wgpu-3d/src/lib.rs | 14 +- 25 files changed, 750 insertions(+), 257 deletions(-) create mode 100644 frontend/desktop/src/input/key_codes.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 770cdbb..c8e45fe 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -31,8 +31,8 @@ debugger-hooks = ["bft-r", "bft-w"] [dependencies] emu-utils = { git = "https://github.com/kelpsyberry/emu-utils" } -proc-bitfield = { version = "0.2", features = ["nightly"] } -bitflags = "1.2" +proc-bitfield = { version = "0.3", features = ["nightly"] } +bitflags = "2.4" cfg-if = "1.0" slog = { version = "2.7", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/core/src/cpu/debug.rs b/core/src/cpu/debug.rs index 60eb483..34578c5 100644 --- a/core/src/cpu/debug.rs +++ b/core/src/cpu/debug.rs @@ -11,6 +11,7 @@ pub struct MemWatchpointSubTable(pub [Option>; 0x800 pub struct MemWatchpointLeafTable(pub [usize; MWLT_ENTRY_COUNT as usize]); bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MemWatchpointRwMask: u8 { const READ = 1 << 0; const WRITE = 1 << 1; @@ -76,15 +77,9 @@ impl MemWatchpointRootTable { pub(super) fn remove(&mut self, addr: u32, size: u8, rw: MemWatchpointRwMask) { let root_i = (addr >> 21) as usize; - let sub_table = match &mut self.0[root_i] { - Some(sub_table_ptr) => sub_table_ptr, - None => return, - }; + let Some(sub_table) = &mut self.0[root_i] else { return }; let sub_i = (addr >> 10 & 0x7FF) as usize; - let leaf_table = match &mut sub_table.0[sub_i] { - Some(leaf_table_ptr) => leaf_table_ptr, - None => return, - }; + let Some(leaf_table) = &mut sub_table.0[sub_i] else { return }; let mut mask = rw.bits() as usize; for i in 0..size.trailing_zeros() { mask |= mask << (2 << i); diff --git a/core/src/emu/input.rs b/core/src/emu/input.rs index f27d072..03dfaa5 100644 --- a/core/src/emu/input.rs +++ b/core/src/emu/input.rs @@ -3,6 +3,7 @@ use crate::{cpu, utils::Savestate}; use bitflags::bitflags; bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Keys: u32 { const A = 1; const B = 1 << 1; diff --git a/frontend/desktop/Cargo.toml b/frontend/desktop/Cargo.toml index 9f9db56..8657906 100644 --- a/frontend/desktop/Cargo.toml +++ b/frontend/desktop/Cargo.toml @@ -38,19 +38,18 @@ dust-wgpu-2d = { path = "../../render/wgpu-2d" } dust-wgpu-3d = { path = "../../render/wgpu-3d", features = ["threaded"] } # UI -winit = { version = "0.27", features = ["serde"] } -wgpu = "0.14" -# TODO: Switch to imgui 0.9 when it's released with the docking API -imgui = { git = "https://github.com/imgui-rs/imgui-rs", features = ["docking", "tables-api"] } -imgui-winit-support = { git = "https://github.com/imgui-rs/imgui-rs" } +winit = { version = "0.29", features = ["serde"] } +wgpu = { git = "https://github.com/gfx-rs/wgpu" } +imgui = { version = "0.11", features = ["docking", "tables-api"] } +imgui-winit-support = { git = "https://github.com/kelpsyberry/imgui-rs" } imgui-wgpu = { git = "https://github.com/kelpsyberry/imgui-wgpu" } -opener = "0.5" +opener = "0.6" # System resources -rfd = "0.10" +rfd = "0.12" directories = "5.0" -copypasta = "0.8" -cpal = "0.14" +copypasta = "0.10" +cpal = "0.15" chrono = { version = "0.4", features = ["serde"] } libc = "0.2" @@ -59,8 +58,8 @@ ahash = "0.8" futures-executor = "0.3" crossbeam-channel = "0.5" parking_lot = "0.12" -bitflags = "1.3" -miniz_oxide = { version = "0.6", features = ["simd"] } +bitflags = "2.4" +miniz_oxide = { version = "0.7", features = ["simd"] } fatfs = { version = "0.3", optional = true } tempdir = { version = "0.3", optional = true } @@ -83,5 +82,5 @@ realfft = { version = "3.0", optional = true } gdb-protocol = { version = "0.1", optional = true } [target.'cfg(target_os = "macos")'.dependencies] -cocoa = "0.24" +cocoa = "0.25" objc = "0.2" diff --git a/frontend/desktop/src/audio/input/cpal.rs b/frontend/desktop/src/audio/input/cpal.rs index 308555b..92e5ddf 100644 --- a/frontend/desktop/src/audio/input/cpal.rs +++ b/frontend/desktop/src/audio/input/cpal.rs @@ -38,26 +38,30 @@ impl InputStream { fract: 0.0, }; - let err_callback = |err| panic!("Error in default audio output device stream: {err}"); + let err_callback = |err| panic!("Error in default audio input device stream: {err}"); let stream = match supported_input_config.sample_format() { SampleFormat::U16 => input_device.build_input_stream( &supported_input_config.config(), move |data: &[u16], _| input_data.fill(data), err_callback, + None, ), SampleFormat::I16 => input_device.build_input_stream( &supported_input_config.config(), move |data: &[i16], _| input_data.fill(data), err_callback, + None, ), SampleFormat::F32 => input_device.build_input_stream( &supported_input_config.config(), move |data: &[f32], _| input_data.fill(data), err_callback, + None, ), + _ => panic!("Unsupported audio input sample format"), } .ok()?; - stream.play().expect("Couldn't start audio output stream"); + stream.play().expect("couldn't start audio input stream"); Some(InputStream { _stream: stream, @@ -86,7 +90,7 @@ struct InputData { } impl InputData { - fn fill(&mut self, data: &[T]) { + fn fill(&mut self, data: &[T]) where f64: cpal::FromSample { if let Some(interp) = self.interp_rx.try_iter().last() { self.interp = interp; } @@ -95,7 +99,7 @@ impl InputData { for input_samples in data.chunks(self.channels as usize) { let mut input_sample = 0.0; for sample in input_samples { - input_sample += sample.to_f32() as f64; + input_sample += sample.to_sample::(); } self.interp .push_input_sample([input_sample / self.channels as f64]); diff --git a/frontend/desktop/src/audio/output/cpal.rs b/frontend/desktop/src/audio/output/cpal.rs index c9fb6ff..ac67c8e 100644 --- a/frontend/desktop/src/audio/output/cpal.rs +++ b/frontend/desktop/src/audio/output/cpal.rs @@ -83,20 +83,24 @@ impl OutputStream { &supported_output_config.config(), move |data: &mut [u16], _| output_data.fill(data), err_callback, + None, ), SampleFormat::I16 => output_device.build_output_stream( &supported_output_config.config(), move |data: &mut [i16], _| output_data.fill(data), err_callback, + None, ), SampleFormat::F32 => output_device.build_output_stream( &supported_output_config.config(), move |data: &mut [f32], _| output_data.fill(data), err_callback, + None, ), + _ => panic!("Unsupported audio output sample format"), } .ok()?; - stream.play().expect("Couldn't start audio output stream"); + stream.play().expect("couldn't start audio output stream"); Some(OutputStream { _stream: stream, @@ -143,7 +147,7 @@ struct OutputData { } impl OutputData { - fn fill(&mut self, data: &mut [T]) { + fn fill>(&mut self, data: &mut [T]) { if let Some(interp) = self.interp_rx.try_iter().last() { self.interp = interp; } @@ -170,8 +174,8 @@ impl OutputData { return; } let result = self.interp.get_output_sample(fract); - data[output_i] = T::from(&(result[0] as f32 * volume)); - data[output_i + 1] = T::from(&(result[1] as f32 * volume)); + data[output_i] = T::from_sample(result[0] as f32 * volume); + data[output_i + 1] = T::from_sample(result[1] as f32 * volume); fract += sample_rate_ratio; output_i += 2; } diff --git a/frontend/desktop/src/debug_views/common/disasm.rs b/frontend/desktop/src/debug_views/common/disasm.rs index ec68a13..8b95bd7 100644 --- a/frontend/desktop/src/debug_views/common/disasm.rs +++ b/frontend/desktop/src/debug_views/common/disasm.rs @@ -6,6 +6,7 @@ use imgui::{Key, MouseButton, StyleColor, StyleVar, Ui, WindowHoveredFlags}; use std::{fmt::Write, num::NonZeroU8}; bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Flags: u8 { // Creation flags const SHOW_VIEW_OPTIONS = 1 << 0; diff --git a/frontend/desktop/src/emu/gdb_server.rs b/frontend/desktop/src/emu/gdb_server.rs index cad3f33..5a11680 100644 --- a/frontend/desktop/src/emu/gdb_server.rs +++ b/frontend/desktop/src/emu/gdb_server.rs @@ -26,6 +26,7 @@ use gdb_protocol::packet::{CheckedPacket, Kind as PacketKind}; use std::{cell::RefCell, io::Write, net::ToSocketAddrs, rc::Rc, str}; bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct ThreadMask: u8 { const ARM9 = 1 << 0; const ARM7 = 1 << 1; diff --git a/frontend/desktop/src/input.rs b/frontend/desktop/src/input.rs index e1335a9..7a26016 100644 --- a/frontend/desktop/src/input.rs +++ b/frontend/desktop/src/input.rs @@ -3,8 +3,10 @@ pub use map::Map; mod state; pub use state::{Changes, State}; pub mod trigger; +pub mod key_codes; +pub use key_codes::{KeyCode, ScanCode}; -use winit::event::{ScanCode, VirtualKeyCode}; +use winit::keyboard::PhysicalKey; #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub enum Action { @@ -16,4 +18,24 @@ pub enum Action { ToggleFullWindowScreen, } -pub type PressedKey = (Option, ScanCode); +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum PressedKey { + KeyCode(KeyCode), + ScanCode(ScanCode), +} + +impl TryFrom for PressedKey { + type Error = (); + + fn try_from(value: PhysicalKey) -> Result { + Ok(match value { + PhysicalKey::Code(key_code) => PressedKey::KeyCode(key_code.into()), + PhysicalKey::Unidentified(scan_code) => { + let Ok(scan_code) = scan_code.try_into() else { + return Err(()); + }; + PressedKey::ScanCode(scan_code) + } + }) + } +} diff --git a/frontend/desktop/src/input/key_codes.rs b/frontend/desktop/src/input/key_codes.rs new file mode 100644 index 0000000..d67331d --- /dev/null +++ b/frontend/desktop/src/input/key_codes.rs @@ -0,0 +1,290 @@ +use std::str::FromStr; +use winit::keyboard::{KeyCode as WKeyCode, NativeKeyCode}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct KeyCode(pub WKeyCode); + +impl From for KeyCode { + fn from(value: WKeyCode) -> Self { + KeyCode(value) + } +} + +static KEY_CODE_STR_MAP: &[(WKeyCode, &str)] = &[ + (WKeyCode::Backquote, "Backquote"), + (WKeyCode::Backslash, "Backslash"), + (WKeyCode::BracketLeft, "BracketLeft"), + (WKeyCode::BracketRight, "BracketRight"), + (WKeyCode::Comma, "Comma"), + (WKeyCode::Digit0, "0"), + (WKeyCode::Digit1, "1"), + (WKeyCode::Digit2, "2"), + (WKeyCode::Digit3, "3"), + (WKeyCode::Digit4, "4"), + (WKeyCode::Digit5, "5"), + (WKeyCode::Digit6, "6"), + (WKeyCode::Digit7, "7"), + (WKeyCode::Digit8, "8"), + (WKeyCode::Digit9, "9"), + (WKeyCode::Equal, "Equal"), + (WKeyCode::IntlBackslash, "IntlBackslash"), + (WKeyCode::IntlRo, "IntlRo"), + (WKeyCode::IntlYen, "IntlYen"), + (WKeyCode::KeyA, "A"), + (WKeyCode::KeyB, "B"), + (WKeyCode::KeyC, "C"), + (WKeyCode::KeyD, "D"), + (WKeyCode::KeyE, "E"), + (WKeyCode::KeyF, "F"), + (WKeyCode::KeyG, "G"), + (WKeyCode::KeyH, "H"), + (WKeyCode::KeyI, "I"), + (WKeyCode::KeyJ, "J"), + (WKeyCode::KeyK, "K"), + (WKeyCode::KeyL, "L"), + (WKeyCode::KeyM, "M"), + (WKeyCode::KeyN, "N"), + (WKeyCode::KeyO, "O"), + (WKeyCode::KeyP, "P"), + (WKeyCode::KeyQ, "Q"), + (WKeyCode::KeyR, "R"), + (WKeyCode::KeyS, "S"), + (WKeyCode::KeyT, "T"), + (WKeyCode::KeyU, "U"), + (WKeyCode::KeyV, "V"), + (WKeyCode::KeyW, "W"), + (WKeyCode::KeyX, "X"), + (WKeyCode::KeyY, "Y"), + (WKeyCode::KeyZ, "Z"), + (WKeyCode::Minus, "Minus"), + (WKeyCode::Period, "Period"), + (WKeyCode::Quote, "Quote"), + (WKeyCode::Semicolon, "Semicolon"), + (WKeyCode::Slash, "Slash"), + (WKeyCode::AltLeft, "AltLeft"), + (WKeyCode::AltRight, "AltRight"), + (WKeyCode::Backspace, "Backspace"), + (WKeyCode::CapsLock, "CapsLock"), + (WKeyCode::ContextMenu, "ContextMenu"), + (WKeyCode::ControlLeft, "ControlLeft"), + (WKeyCode::ControlRight, "ControlRight"), + (WKeyCode::Enter, "Enter"), + (WKeyCode::SuperLeft, "SuperLeft"), + (WKeyCode::SuperRight, "SuperRight"), + (WKeyCode::ShiftLeft, "ShiftLeft"), + (WKeyCode::ShiftRight, "ShiftRight"), + (WKeyCode::Space, "Space"), + (WKeyCode::Tab, "Tab"), + (WKeyCode::Convert, "Convert"), + (WKeyCode::KanaMode, "KanaMode"), + (WKeyCode::Lang1, "Lang1"), + (WKeyCode::Lang2, "Lang2"), + (WKeyCode::Lang3, "Lang3"), + (WKeyCode::Lang4, "Lang4"), + (WKeyCode::Lang5, "Lang5"), + (WKeyCode::NonConvert, "NonConvert"), + (WKeyCode::Delete, "Delete"), + (WKeyCode::End, "End"), + (WKeyCode::Help, "Help"), + (WKeyCode::Home, "Home"), + (WKeyCode::Insert, "Insert"), + (WKeyCode::PageDown, "PageDown"), + (WKeyCode::PageUp, "PageUp"), + (WKeyCode::ArrowDown, "ArrowDown"), + (WKeyCode::ArrowLeft, "ArrowLeft"), + (WKeyCode::ArrowRight, "ArrowRight"), + (WKeyCode::ArrowUp, "ArrowUp"), + (WKeyCode::NumLock, "NumLock"), + (WKeyCode::Numpad0, "Numpad0"), + (WKeyCode::Numpad1, "Numpad1"), + (WKeyCode::Numpad2, "Numpad2"), + (WKeyCode::Numpad3, "Numpad3"), + (WKeyCode::Numpad4, "Numpad4"), + (WKeyCode::Numpad5, "Numpad5"), + (WKeyCode::Numpad6, "Numpad6"), + (WKeyCode::Numpad7, "Numpad7"), + (WKeyCode::Numpad8, "Numpad8"), + (WKeyCode::Numpad9, "Numpad9"), + (WKeyCode::NumpadAdd, "NumpadAdd"), + (WKeyCode::NumpadBackspace, "NumpadBackspace"), + (WKeyCode::NumpadClear, "NumpadClear"), + (WKeyCode::NumpadClearEntry, "NumpadClearEntry"), + (WKeyCode::NumpadComma, "NumpadComma"), + (WKeyCode::NumpadDecimal, "NumpadDecimal"), + (WKeyCode::NumpadDivide, "NumpadDivide"), + (WKeyCode::NumpadEnter, "NumpadEnter"), + (WKeyCode::NumpadEqual, "NumpadEqual"), + (WKeyCode::NumpadHash, "NumpadHash"), + (WKeyCode::NumpadMemoryAdd, "NumpadMemoryAdd"), + (WKeyCode::NumpadMemoryClear, "NumpadMemoryClear"), + (WKeyCode::NumpadMemoryRecall, "NumpadMemoryRecall"), + (WKeyCode::NumpadMemoryStore, "NumpadMemoryStore"), + (WKeyCode::NumpadMemorySubtract, "NumpadMemorySubtract"), + (WKeyCode::NumpadMultiply, "NumpadMultiply"), + (WKeyCode::NumpadParenLeft, "NumpadParenLeft"), + (WKeyCode::NumpadParenRight, "NumpadParenRight"), + (WKeyCode::NumpadStar, "NumpadStar"), + (WKeyCode::NumpadSubtract, "NumpadSubtract"), + (WKeyCode::Escape, "Escape"), + (WKeyCode::Fn, "Fn"), + (WKeyCode::FnLock, "FnLock"), + (WKeyCode::PrintScreen, "PrintScreen"), + (WKeyCode::ScrollLock, "ScrollLock"), + (WKeyCode::Pause, "Pause"), + (WKeyCode::BrowserBack, "BrowserBack"), + (WKeyCode::BrowserFavorites, "BrowserFavorites"), + (WKeyCode::BrowserForward, "BrowserForward"), + (WKeyCode::BrowserHome, "BrowserHome"), + (WKeyCode::BrowserRefresh, "BrowserRefresh"), + (WKeyCode::BrowserSearch, "BrowserSearch"), + (WKeyCode::BrowserStop, "BrowserStop"), + (WKeyCode::Eject, "Eject"), + (WKeyCode::LaunchApp1, "LaunchApp1"), + (WKeyCode::LaunchApp2, "LaunchApp2"), + (WKeyCode::LaunchMail, "LaunchMail"), + (WKeyCode::MediaPlayPause, "MediaPlayPause"), + (WKeyCode::MediaSelect, "MediaSelect"), + (WKeyCode::MediaStop, "MediaStop"), + (WKeyCode::MediaTrackNext, "MediaTrackNext"), + (WKeyCode::MediaTrackPrevious, "MediaTrackPrevious"), + (WKeyCode::Power, "Power"), + (WKeyCode::Sleep, "Sleep"), + (WKeyCode::AudioVolumeDown, "AudioVolumeDown"), + (WKeyCode::AudioVolumeMute, "AudioVolumeMute"), + (WKeyCode::AudioVolumeUp, "AudioVolumeUp"), + (WKeyCode::WakeUp, "WakeUp"), + (WKeyCode::Meta, "Meta"), + (WKeyCode::Hyper, "Hyper"), + (WKeyCode::Turbo, "Turbo"), + (WKeyCode::Abort, "Abort"), + (WKeyCode::Resume, "Resume"), + (WKeyCode::Suspend, "Suspend"), + (WKeyCode::Again, "Again"), + (WKeyCode::Copy, "Copy"), + (WKeyCode::Cut, "Cut"), + (WKeyCode::Find, "Find"), + (WKeyCode::Open, "Open"), + (WKeyCode::Paste, "Paste"), + (WKeyCode::Props, "Props"), + (WKeyCode::Select, "Select"), + (WKeyCode::Undo, "Undo"), + (WKeyCode::Hiragana, "Hiragana"), + (WKeyCode::Katakana, "Katakana"), + (WKeyCode::F1, "F1"), + (WKeyCode::F2, "F2"), + (WKeyCode::F3, "F3"), + (WKeyCode::F4, "F4"), + (WKeyCode::F5, "F5"), + (WKeyCode::F6, "F6"), + (WKeyCode::F7, "F7"), + (WKeyCode::F8, "F8"), + (WKeyCode::F9, "F9"), + (WKeyCode::F10, "F10"), + (WKeyCode::F11, "F11"), + (WKeyCode::F12, "F12"), + (WKeyCode::F13, "F13"), + (WKeyCode::F14, "F14"), + (WKeyCode::F15, "F15"), + (WKeyCode::F16, "F16"), + (WKeyCode::F17, "F17"), + (WKeyCode::F18, "F18"), + (WKeyCode::F19, "F19"), + (WKeyCode::F20, "F20"), + (WKeyCode::F21, "F21"), + (WKeyCode::F22, "F22"), + (WKeyCode::F23, "F23"), + (WKeyCode::F24, "F24"), + (WKeyCode::F25, "F25"), + (WKeyCode::F26, "F26"), + (WKeyCode::F27, "F27"), + (WKeyCode::F28, "F28"), + (WKeyCode::F29, "F29"), + (WKeyCode::F30, "F30"), + (WKeyCode::F31, "F31"), + (WKeyCode::F32, "F32"), + (WKeyCode::F33, "F33"), + (WKeyCode::F34, "F34"), + (WKeyCode::F35, "F35"), +]; + +impl From for &'static str { + fn from(value: KeyCode) -> Self { + KEY_CODE_STR_MAP + .iter() + .find_map(|(key_code, str)| (*key_code == value.0).then_some(*str)) + .expect("invalid key code") + } +} + +impl FromStr for KeyCode { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(KeyCode( + KEY_CODE_STR_MAP + .iter() + .find_map(|(key_code, str)| (*str == s).then_some(*key_code)) + .ok_or(())?, + )) + } +} + +impl TryFrom<&str> for KeyCode { + type Error = (); + + fn try_from(value: &str) -> Result { + Self::from_str(value) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct ScanCode(NativeKeyCode); + +impl TryFrom for ScanCode { + type Error = (); + + fn try_from(value: NativeKeyCode) -> Result { + if matches!(value, NativeKeyCode::Unidentified) { + return Err(()); + } + Ok(ScanCode(value)) + } +} + +impl ToString for ScanCode { + fn to_string(&self) -> String { + match self.0 { + NativeKeyCode::Android(scan_code) => format!("A{scan_code}"), + NativeKeyCode::MacOS(scan_code) => format!("M{scan_code}"), + NativeKeyCode::Windows(scan_code) => format!("W{scan_code}"), + NativeKeyCode::Xkb(scan_code) => format!("X{scan_code}"), + NativeKeyCode::Unidentified => unreachable!(), + } + } +} + +impl FromStr for ScanCode { + type Err = (); + + fn from_str(s: &str) -> Result { + if !s.is_char_boundary(1) { + return Err(()); + } + let (first_char, scan_code) = s.split_at(1); + Ok(ScanCode(match first_char { + "A" => NativeKeyCode::Android(scan_code.parse().map_err(drop)?), + "M" => NativeKeyCode::MacOS(scan_code.parse().map_err(drop)?), + "W" => NativeKeyCode::Windows(scan_code.parse().map_err(drop)?), + "X" => NativeKeyCode::Xkb(scan_code.parse().map_err(drop)?), + _ => return Err(()), + })) + } +} + +impl TryFrom<&str> for ScanCode { + type Error = (); + + fn try_from(value: &str) -> Result { + Self::from_str(value) + } +} diff --git a/frontend/desktop/src/input/map.rs b/frontend/desktop/src/input/map.rs index 0697a5d..88c8fac 100644 --- a/frontend/desktop/src/input/map.rs +++ b/frontend/desktop/src/input/map.rs @@ -1,5 +1,5 @@ use super::{ - trigger::{self, Trigger}, + trigger::{Op, Trigger}, Action, }; use crate::config::SettingOrigin; @@ -11,7 +11,7 @@ use serde::{ Deserialize, Deserializer, Serialize, Serializer, }; use std::{fmt, hash::Hash}; -use winit::event::VirtualKeyCode; +use winit::keyboard::KeyCode; static KEY_IDENTS: &[(Keys, &str)] = &[ (Keys::A, "a"), @@ -58,27 +58,36 @@ impl Map { fn default_keypad_map() -> HashMap> { [ - (Keys::A, Some(Trigger::KeyCode(VirtualKeyCode::X))), - (Keys::B, Some(Trigger::KeyCode(VirtualKeyCode::Z))), - (Keys::X, Some(Trigger::KeyCode(VirtualKeyCode::S))), - (Keys::Y, Some(Trigger::KeyCode(VirtualKeyCode::A))), - (Keys::L, Some(Trigger::KeyCode(VirtualKeyCode::Q))), - (Keys::R, Some(Trigger::KeyCode(VirtualKeyCode::W))), - (Keys::START, Some(Trigger::KeyCode(VirtualKeyCode::Return))), + (Keys::A, Some(Trigger::KeyCode(KeyCode::KeyX.into()))), + (Keys::B, Some(Trigger::KeyCode(KeyCode::KeyZ.into()))), + (Keys::X, Some(Trigger::KeyCode(KeyCode::KeyS.into()))), + (Keys::Y, Some(Trigger::KeyCode(KeyCode::KeyA.into()))), + (Keys::L, Some(Trigger::KeyCode(KeyCode::KeyQ.into()))), + (Keys::R, Some(Trigger::KeyCode(KeyCode::KeyW.into()))), + (Keys::START, Some(Trigger::KeyCode(KeyCode::Enter.into()))), ( Keys::SELECT, Some(Trigger::Chain( - trigger::Op::Or, + Op::Or, vec![ - Trigger::KeyCode(VirtualKeyCode::LShift), - Trigger::KeyCode(VirtualKeyCode::RShift), + Trigger::KeyCode(KeyCode::ShiftLeft.into()), + Trigger::KeyCode(KeyCode::ShiftRight.into()), ], )), ), - (Keys::RIGHT, Some(Trigger::KeyCode(VirtualKeyCode::Right))), - (Keys::LEFT, Some(Trigger::KeyCode(VirtualKeyCode::Left))), - (Keys::UP, Some(Trigger::KeyCode(VirtualKeyCode::Up))), - (Keys::DOWN, Some(Trigger::KeyCode(VirtualKeyCode::Down))), + ( + Keys::RIGHT, + Some(Trigger::KeyCode(KeyCode::ArrowRight.into())), + ), + ( + Keys::LEFT, + Some(Trigger::KeyCode(KeyCode::ArrowLeft.into())), + ), + (Keys::UP, Some(Trigger::KeyCode(KeyCode::ArrowUp.into()))), + ( + Keys::DOWN, + Some(Trigger::KeyCode(KeyCode::ArrowDown.into())), + ), (Keys::DEBUG, None), ] .into_iter() diff --git a/frontend/desktop/src/input/state.rs b/frontend/desktop/src/input/state.rs index 26e59ec..d293453 100644 --- a/frontend/desktop/src/input/state.rs +++ b/frontend/desktop/src/input/state.rs @@ -3,7 +3,7 @@ use ahash::AHashSet as HashSet; use dust_core::emu::input::Keys as EmuKeys; use winit::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{ElementState, Event, MouseButton, WindowEvent}, + event::{Event, KeyEvent, MouseButton, WindowEvent}, }; pub struct State { @@ -120,15 +120,23 @@ impl State { if let Event::WindowEvent { event, .. } = event { match event { WindowEvent::KeyboardInput { - input, - is_synthetic: false, + event: + KeyEvent { + physical_key, + state, + .. + }, .. } => { - let key = (input.virtual_keycode, input.scancode); - if input.state == ElementState::Released { + let Ok(key) = (*physical_key).try_into() else { + return; + }; + if state.is_pressed() { + if catch_new { + self.pressed_keys.insert(key); + } + } else { self.pressed_keys.remove(&key); - } else if catch_new { - self.pressed_keys.insert(key); } } @@ -144,7 +152,7 @@ impl State { button: MouseButton::Left, .. } => { - if *state == ElementState::Pressed { + if state.is_pressed() { if catch_new { self.recalculate_touch_pos::(); } diff --git a/frontend/desktop/src/input/trigger.rs b/frontend/desktop/src/input/trigger.rs index 5a96b88..901c74a 100644 --- a/frontend/desktop/src/input/trigger.rs +++ b/frontend/desktop/src/input/trigger.rs @@ -1,27 +1,33 @@ -use super::PressedKey; -use serde::{de::IntoDeserializer, Deserialize, Serialize}; +use super::{KeyCode, PressedKey, ScanCode}; +use serde::{Deserialize, Serialize}; use std::{ error::Error, fmt::{self, Write}, str::FromStr, }; -use winit::event::{ScanCode, VirtualKeyCode}; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Op { And, Or, Xor, } -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +impl fmt::Display for Op { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Op::And => f.write_char('&'), + Op::Or => f.write_char('|'), + Op::Xor => f.write_char('^'), + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[serde(try_from = "&str", into = "String")] pub enum Trigger { - KeyCode(VirtualKeyCode), - // TODO: Proper keyboard key to character conversion; right now winit doesn't support reading - // the keyboard layout or the character corresponding to a key other than through virtual key - // code mapping - ScanCode(ScanCode, Option), + KeyCode(KeyCode), + ScanCode(ScanCode), Not(Box), Chain(Op, Vec), } @@ -32,12 +38,12 @@ impl Trigger { pressed_keys: impl IntoIterator + Copy, ) -> bool { match self { - Trigger::KeyCode(keycode) => { - pressed_keys.into_iter().any(|key| key.0 == Some(*keycode)) - } - Trigger::ScanCode(scancode, _) => { - pressed_keys.into_iter().any(|key| key.1 == *scancode) - } + Trigger::KeyCode(key_code) => pressed_keys + .into_iter() + .any(|key| matches!(key, PressedKey::KeyCode(key_code_) if key_code_ == key_code)), + Trigger::ScanCode(scan_code) => pressed_keys.into_iter().any( + |key| matches!(key, PressedKey::ScanCode(scan_code_) if scan_code_ == scan_code), + ), Trigger::Not(trigger) => !trigger.activated(pressed_keys), Trigger::Chain(op, triggers) => match op { Op::And => triggers @@ -56,13 +62,17 @@ impl Trigger { impl ToString for Trigger { fn to_string(&self) -> String { - fn write_trigger(result: &mut String, trigger: &Trigger, needs_parens_if_multiple: bool) { + fn write_trigger( + result: &mut String, + trigger: &Trigger, + needs_parens_if_multiple: bool, + ) { match trigger { &Trigger::KeyCode(key_code) => { - write!(result, "v{key_code:?}").unwrap(); + write!(result, "v{}", <&str>::from(key_code)).unwrap(); } - &Trigger::ScanCode(scan_code, key_code) => { - write!(result, "s{scan_code}v{key_code:?}").unwrap(); + &Trigger::ScanCode(scan_code) => { + write!(result, "s{}", scan_code.to_string()).unwrap(); } Trigger::Not(trigger) => { result.push('!'); @@ -97,18 +107,51 @@ impl ToString for Trigger { } impl From for String { - fn from(trigger: Trigger) -> Self { - trigger.to_string() + fn from(value: Trigger) -> Self { + value.to_string() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ParseErrorKind { + UnexpectedCharacter, + UnexpectedClosingParen, + InvalidKeyScanCode, + ExpectedValue, + UnexpectedValue, + UnexpectedUnaryOperator, + MismatchedOperators { expected: Op, found: Op }, +} + +impl fmt::Display for ParseErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedCharacter => f.write_str("unexpected character"), + Self::UnexpectedClosingParen => f.write_str("unexpected closing parens"), + Self::InvalidKeyScanCode => f.write_str("invalid key/scan code"), + Self::ExpectedValue => f.write_str("expected value"), + Self::UnexpectedValue => f.write_str("unexpected value"), + Self::UnexpectedUnaryOperator => f.write_str("unexpected unary operator after values"), + Self::MismatchedOperators { expected, found } => write!( + f, + "mismatched operators: expected {}, found {}", + expected, found + ), + } } } -pub struct ParseError; +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct ParseError { + pos: usize, + kind: ParseErrorKind, +} impl Error for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("parse error") + write!(f, "parse error at character {}: {}", self.pos, self.kind) } } @@ -118,119 +161,192 @@ impl fmt::Debug for ParseError { } } -impl FromStr for Trigger { - type Err = ParseError; +struct TriggerParser<'a> { + s: &'a str, + pos: usize, + new_pos: usize, +} - fn from_str(mut s: &str) -> Result { - fn parse_key_code(s: &mut &str) -> Result { - let end_index = s - .char_indices() - .find_map(|(i, c)| if c.is_alphanumeric() { None } else { Some(i) }) - .unwrap_or(s.len()); - let key_code_str = &s[..end_index]; - *s = &s[end_index..]; - - VirtualKeyCode::deserialize(key_code_str.into_deserializer()) - .map_err(|_: serde::de::value::Error| ParseError) +impl<'a> TriggerParser<'a> { + fn parse(s: &'a str) -> Result { + TriggerParser { + s, + pos: 0, + new_pos: 0, } + .parse_trigger(false) + } - fn parse_value(s: &mut &str) -> Result { - let mut negate = false; - let mut operator = None; - let mut values = Vec::new(); + fn consume_char(&mut self) -> Option { + let mut char_indices = self.s.char_indices(); + let next_char = char_indices.next().map(|(_, c)| c); + let end_index = match char_indices.next() { + Some((i, _)) => i, + None => self.s.len(), + }; + self.s = &self.s[end_index..]; + self.new_pos += end_index; + next_char + } - loop { - *s = s.trim_start(); + fn commit(&mut self) { + self.pos = self.new_pos; + } - let mut char_indices = s.char_indices(); - let next_char = char_indices.next().map(|(_, c)| c); + fn validate_and_change_op( + &mut self, + new_op: Op, + op: &mut Option, + expects_value: &mut bool, + ) -> Result<(), ParseError> { + if *expects_value { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::ExpectedValue, + }); + } - if next_char == Some(')') || next_char.is_none() { - if let Some(operator) = operator { - if values.len() <= 1 { - return Err(ParseError); - } - return Ok(Trigger::Chain(operator, values)); - } else { - if values.len() != 1 { - return Err(ParseError); - } - return Ok(values.remove(0)); - } - } + if let Some(op) = *op { + if op != new_op { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::MismatchedOperators { + expected: new_op, + found: op, + }, + }); + } + } - if let Some((new_start_index, _)) = char_indices.next() { - *s = &s[new_start_index..]; - } + *op = Some(new_op); + *expects_value = true; - let value = match next_char { - Some('!') => { - negate = !negate; - continue; - } + self.commit(); - Some('&') => { - operator = Some(Op::And); - continue; - } + Ok(()) + } - Some('|') => { - operator = Some(Op::Or); - continue; - } + fn parse_value(&mut self) -> Result { + let end_index = self + .s + .char_indices() + .find_map(|(i, c)| (!c.is_alphanumeric()).then_some(i)) + .unwrap_or(self.s.len()); + let value_str = &self.s[..end_index]; + self.s = &self.s[end_index..]; + self.new_pos += end_index; - Some('^') => { - operator = Some(Op::Xor); - continue; - } + let result = value_str.parse().map_err(|_| ParseError { + pos: self.pos, + kind: ParseErrorKind::InvalidKeyScanCode, + })?; - Some('v') => Trigger::KeyCode(parse_key_code(s)?), - - Some('s') => { - let mut char_indices = s.char_indices(); - let (scan_code_end_index, scan_code_end_char) = char_indices - .find_map(|(i, c)| { - if c.is_numeric() { - None - } else { - Some((i, Some(c))) - } - }) - .unwrap_or((s.len(), None)); - let scan_code_str = &s[..scan_code_end_index]; - *s = &s[scan_code_end_index..]; - - let scan_code = - ScanCode::from_str(scan_code_str).map_err(|_| ParseError)?; - - let virtual_key_code = match scan_code_end_char { - Some('v') => Some(parse_key_code(s)?), - Some(c) if c.is_alphanumeric() => return Err(ParseError), - _ => None, - }; - - Trigger::ScanCode(scan_code, virtual_key_code) - } + self.commit(); - Some('(') => { - let value = parse_value(s)?; - *s = s.strip_prefix(')').ok_or(ParseError)?; - value - } + Ok(result) + } - _ => return Err(ParseError), - }; + fn parse_trigger(&mut self, expect_parens: bool) -> Result { + let mut negate = false; + let mut op = None; + let mut values = vec![]; + let mut expects_value = true; + + loop { + self.s = self.s.trim_start(); + + let next_char = self.consume_char(); + if next_char == Some(')') { + if !expect_parens { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::UnexpectedClosingParen, + }); + } + self.commit(); + } - values.push(if negate { - Trigger::Not(Box::new(value)) + if next_char == Some(')') || next_char.is_none() { + if expects_value { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::ExpectedValue, + }); + } + + if let Some(op) = op { + return Ok(Trigger::Chain(op, values)); } else { - value + return Ok(values.remove(0)); + } + } + let next_char = next_char.unwrap(); + + match next_char { + '!' => { + if !expects_value { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::UnexpectedUnaryOperator, + }); + } + negate = !negate; + self.commit(); + continue; + } + + '&' => { + self.validate_and_change_op(Op::And, &mut op, &mut expects_value)?; + continue; + } + + '|' => { + self.validate_and_change_op(Op::Or, &mut op, &mut expects_value)?; + continue; + } + + '^' => { + self.validate_and_change_op(Op::Xor, &mut op, &mut expects_value)?; + continue; + } + + _ => {} + } + + if !matches!(next_char, 'v' | 's' | '(') { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::UnexpectedCharacter, }); - negate = false; } + + if !expects_value { + return Err(ParseError { + pos: self.pos, + kind: ParseErrorKind::UnexpectedValue, + }); + } + + let trigger = match next_char { + 'v' => Trigger::KeyCode(self.parse_value::()?), + 's' => Trigger::ScanCode(self.parse_value::()?), + '(' => { + self.commit(); + self.parse_trigger(true)? + } + _ => unreachable!(), + }; + values.push(trigger); + expects_value = false; } + } +} + +impl FromStr for Trigger { + type Err = ParseError; - parse_value(&mut s) + fn from_str(s: &str) -> Result { + TriggerParser::parse(s) } } diff --git a/frontend/desktop/src/ui.rs b/frontend/desktop/src/ui.rs index 4eb5e89..67c4fa6 100644 --- a/frontend/desktop/src/ui.rs +++ b/frontend/desktop/src/ui.rs @@ -244,6 +244,7 @@ impl UiState { } bitflags::bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct TitleComponents: u8 { const EMU_NAME = 1 << 0; const GAME_TITLE = 1 << 1; @@ -1788,10 +1789,12 @@ pub fn main() { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); window::ControlFlow::Continue }, diff --git a/frontend/desktop/src/ui/config_editor/input_map.rs b/frontend/desktop/src/ui/config_editor/input_map.rs index c09c652..c019e93 100644 --- a/frontend/desktop/src/ui/config_editor/input_map.rs +++ b/frontend/desktop/src/ui/config_editor/input_map.rs @@ -13,7 +13,7 @@ use imgui::{ ItemHoveredFlags, MouseButton, StyleColor, TableColumnFlags, TableColumnSetup, TableFlags, Ui, }; use rfd::FileDialog; -use winit::event::{ElementState, Event, WindowEvent}; +use winit::event::{Event, KeyEvent, WindowEvent}; #[derive(Clone, Copy, PartialEq, Eq)] enum Selection { @@ -464,28 +464,27 @@ impl Editor { if let Event::WindowEvent { event: WindowEvent::KeyboardInput { - input, - is_synthetic: false, + event: + KeyEvent { + physical_key, + state, + .. + }, .. }, .. } = event { - let key = (input.virtual_keycode, input.scancode); - if input.state == ElementState::Released { - self.pressed_keys.remove(&key); - - if self.state.is_capturing() { - self.finalize(config); - } - } else { + let Ok(key) = (*physical_key).try_into() else { + return; + }; + if state.is_pressed() { self.pressed_keys.insert(key); if self.state.is_capturing() { - let new_trigger = if let Some(key_code) = input.virtual_keycode { - Trigger::KeyCode(key_code) - } else { - Trigger::ScanCode(input.scancode, None) + let new_trigger = match key { + PressedKey::KeyCode(key_code) => Trigger::KeyCode(key_code), + PressedKey::ScanCode(scan_code) => Trigger::ScanCode(scan_code), }; if let Some(trigger) = &mut self.current_trigger { @@ -510,6 +509,12 @@ impl Editor { self.current_trigger = Some(new_trigger); } } + } else { + self.pressed_keys.remove(&key); + + if self.state.is_capturing() { + self.finalize(config); + } } } } diff --git a/frontend/desktop/src/ui/utils.rs b/frontend/desktop/src/ui/utils.rs index 322a530..ff872f7 100644 --- a/frontend/desktop/src/ui/utils.rs +++ b/frontend/desktop/src/ui/utils.rs @@ -51,6 +51,7 @@ macro_rules! error { .set_description(&format!($($desc)*)) .set_buttons(rfd::MessageButtons::YesNo) .show() + == rfd::MessageDialogResult::Yes }; ($title: expr, $($desc: tt)*) => { rfd::MessageDialog::new() diff --git a/frontend/desktop/src/ui/window.rs b/frontend/desktop/src/ui/window.rs index 459b354..2ba6cc7 100644 --- a/frontend/desktop/src/ui/window.rs +++ b/frontend/desktop/src/ui/window.rs @@ -1,6 +1,6 @@ #[cfg(target_os = "macos")] use cocoa::{ - appkit::{NSWindow, NSWindowOcclusionState, NSWindowStyleMask}, + appkit::{NSWindow, NSWindowStyleMask}, base::id, foundation::NSRect, }; @@ -12,11 +12,12 @@ use std::{ time::{Duration, Instant}, }; #[cfg(target_os = "macos")] -use winit::platform::macos::{WindowBuilderExtMacOS, WindowExtMacOS}; +use winit::platform::macos::WindowBuilderExtMacOS; use winit::{ dpi::{LogicalSize, PhysicalSize}, event::{Event, StartCause, WindowEvent}, - event_loop::{ControlFlow as WinitControlFlow, EventLoop}, + event_loop::EventLoop, + raw_window_handle::{HasWindowHandle, RawWindowHandle}, window::{Window as WinitWindow, WindowBuilder as WinitWindowBuilder}, }; @@ -26,7 +27,7 @@ pub enum AdapterSelection { } pub struct GfxState { - surface: wgpu::Surface, + surface: wgpu::Surface<'static>, adapter: wgpu::Adapter, device: Arc, queue: Arc, @@ -41,8 +42,11 @@ impl GfxState { features: wgpu::Features, adapter: AdapterSelection, ) -> Self { - let instance = wgpu::Instance::new(wgpu::Backends::all()); - let surface = unsafe { instance.create_surface(window) }; + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + ..Default::default() + }); + let surface = unsafe { instance.create_surface_from_raw(window) }.expect("Couldn't create surface"); let adapter = match adapter { AdapterSelection::Auto(power_preference) => { @@ -58,14 +62,14 @@ impl GfxState { instance.enumerate_adapters(backends).find(suitable) } } - .expect("couldn't create graphics adapter"); + .expect("Couldn't create graphics adapter"); let (device, queue) = adapter .request_device( &wgpu::DeviceDescriptor { label: None, - features, - limits: wgpu::Limits { + required_features: features, + required_limits: wgpu::Limits { max_texture_dimension_2d: 4096, ..wgpu::Limits::downlevel_webgl2_defaults() }, @@ -73,22 +77,19 @@ impl GfxState { None, ) .await - .expect("couldn't open connection to graphics device"); + .expect("Couldn't open connection to graphics device"); let size = window.inner_size(); let surf_config = wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: { - let formats = surface.get_supported_formats(&adapter); + let formats = surface.get_capabilities(&adapter).formats; let preferred = formats .get(0) - .expect("couldn't get surface preferred format"); + .expect("Couldn't get surface preferred format"); #[cfg(target_os = "macos")] { - *formats - .iter() - .find(|f| !f.describe().srgb) - .unwrap_or(preferred) + *formats.iter().find(|f| !f.is_srgb()).unwrap_or(preferred) } #[cfg(not(target_os = "macos"))] *preferred @@ -97,6 +98,7 @@ impl GfxState { height: size.height, present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: Vec::new(), }; surface.configure(&device, &surf_config); @@ -151,9 +153,10 @@ impl GfxState { fn update_format_and_rebuild_swapchain(&mut self, size: PhysicalSize) { let new_format = *self .surface - .get_supported_formats(&self.adapter) + .get_capabilities(&self.adapter) + .formats .get(0) - .expect("couldn't get surface preferred format"); + .expect("Couldn't get surface preferred format"); if new_format != self.surface_config.format { self.surface_config.format = new_format; self.surface_format_changed = true; @@ -282,7 +285,7 @@ impl ImGuiState { }), }]); - if gfx_state.surface_config.format.describe().srgb { + if gfx_state.surface_config.format.is_srgb() { let style = imgui.style_mut(); for color in &mut style.colors { for component in &mut color[..3] { @@ -331,8 +334,9 @@ pub struct Window { pub imgui: ImGuiState, + is_occluded: bool, #[cfg(target_os = "macos")] - macos_title_bar_hidden: bool, + macos_title_bar_is_hidden: bool, #[cfg(target_os = "macos")] pub macos_title_bar_height: f32, } @@ -351,26 +355,38 @@ impl Window { } #[cfg(target_os = "macos")] - fn macos_title_bar_height(&self) -> f32 { - let content_layout_rect: NSRect = - unsafe { msg_send![self.window.ns_window() as id, contentLayoutRect] }; + fn ns_window(&self) -> Option { + if let RawWindowHandle::AppKit(window) = + RawWindowHandle::from(self.window.window_handle().ok()?) + { + Some(window.ns_view.as_ptr() as id) + } else { + None + } + } + + #[cfg(target_os = "macos")] + fn macos_title_bar_height(&self, ns_window: id) -> f32 { + let content_layout_rect: NSRect = unsafe { msg_send![ns_window, contentLayoutRect] }; (self.window.outer_size().height as f64 / self.scale_factor - content_layout_rect.size.height) as f32 } #[cfg(target_os = "macos")] pub fn set_macos_title_bar_hidden(&mut self, hidden: bool) { - self.macos_title_bar_hidden = hidden; + let Some(ns_window) = self.ns_window() else { + return; + }; + self.macos_title_bar_is_hidden = hidden; self.macos_title_bar_height = if hidden { - self.macos_title_bar_height() + self.macos_title_bar_height(ns_window) } else { 0.0 }; unsafe { - let window = self.window.ns_window() as id; - window.setTitlebarAppearsTransparent_(hidden as cocoa::base::BOOL); - let prev_style_mask = window.styleMask(); - window.setStyleMask_(if hidden { + ns_window.setTitlebarAppearsTransparent_(hidden as cocoa::base::BOOL); + let prev_style_mask = ns_window.styleMask(); + ns_window.setStyleMask_(if hidden { prev_style_mask | NSWindowStyleMask::NSFullSizeContentViewWindowMask } else { prev_style_mask & !NSWindowStyleMask::NSFullSizeContentViewWindowMask @@ -380,7 +396,7 @@ impl Window { pub fn main_menu_bar(&mut self, ui: &imgui::Ui, f: impl FnOnce(&mut Self)) { #[cfg(target_os = "macos")] - let frame_padding = if self.macos_title_bar_hidden { + let frame_padding = if self.macos_title_bar_is_hidden { Some(ui.push_style_var(imgui::StyleVar::FramePadding([ 0.0, 0.5 * (self.macos_title_bar_height - ui.text_line_height()), @@ -393,7 +409,7 @@ impl Window { #[cfg(target_os = "macos")] { drop(frame_padding); - if self.macos_title_bar_hidden && self.window.fullscreen().is_none() { + if self.macos_title_bar_is_hidden && self.window.fullscreen().is_none() { // TODO: There has to be some way to compute this width instead of // hardcoding it. ui.dummy([68.0, 0.0]); @@ -420,7 +436,7 @@ impl Builder { default_logical_size: (u32, u32), #[cfg(target_os = "macos")] macos_title_bar_hidden: bool, ) -> Self { - let event_loop = EventLoop::new(); + let event_loop = EventLoop::new().expect("Couldn't create event loop"); let window_builder = WinitWindowBuilder::new() .with_title(title) .with_inner_size(LogicalSize::new( @@ -457,15 +473,18 @@ impl Builder { imgui: imgui_state, + is_occluded: false, #[cfg(target_os = "macos")] - macos_title_bar_hidden, + macos_title_bar_is_hidden: macos_title_bar_hidden, #[cfg(target_os = "macos")] macos_title_bar_height: 0.0, }; #[cfg(target_os = "macos")] if macos_title_bar_hidden { - window.macos_title_bar_height = window.macos_title_bar_height(); + if let Some(ns_window) = window.ns_window() { + window.macos_title_bar_height = window.macos_title_bar_height(ns_window); + } } Builder { @@ -516,7 +535,7 @@ impl Builder { let mut on_exit_imgui = ManuallyDrop::new(on_exit_imgui); let mut on_exit = ManuallyDrop::new(on_exit); - self.event_loop.run(move |event, _, control_flow| { + let _ = self.event_loop.run(move |event, elwt| { let window = &mut *window_; let imgui = &mut *imgui_; window @@ -526,14 +545,14 @@ impl Builder { process_event(window, &mut state, &event); match event { Event::NewEvents(StartCause::Init) => { - *control_flow = WinitControlFlow::Poll; + // *control_flow = WinitControlFlow::Poll; } Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { - *control_flow = WinitControlFlow::Exit; + elwt.exit(); } Event::WindowEvent { @@ -551,7 +570,17 @@ impl Builder { window.gfx.invalidate_swapchain(); } - Event::RedrawRequested(_) => { + Event::WindowEvent { + event: WindowEvent::Occluded(is_occluded), + .. + } => { + window.is_occluded = is_occluded; + } + + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { let now = Instant::now(); let delta_time = now - window.last_frame; window.last_frame = now; @@ -579,13 +608,13 @@ impl Builder { let ui = imgui.frame(); if draw_imgui(window, &mut state, ui) == ControlFlow::Exit { - *control_flow = WinitControlFlow::Exit; + elwt.exit(); } if draw(window, &mut state, &frame, &mut encoder, delta_time) == ControlFlow::Exit { - *control_flow = WinitControlFlow::Exit; + elwt.exit(); } window.imgui.winit.prepare_render(ui, &window.window); @@ -600,31 +629,24 @@ impl Builder { ); window.gfx.queue.submit(iter::once(encoder.finish())); + window.window.pre_present_notify(); frame.present(); window.gfx.device.poll(wgpu::Maintain::Poll); - } - Event::RedrawEventsCleared => { if window.is_hidden { window.is_hidden = false; window.window.set_visible(true); } + } - // TODO: https://github.com/rust-windowing/winit/issues/2022 - // Mitigation for https://github.com/gfx-rs/wgpu/issues/1783 - #[cfg(target_os = "macos")] - let window_visible = - unsafe { (window.window.ns_window() as id).occlusionState() } - .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible); - #[cfg(not(target_os = "macos"))] - let window_visible = true; - if window_visible { + Event::AboutToWait => { + if !window.is_occluded { window.window.request_redraw(); } } - Event::LoopDestroyed => { + Event::LoopExiting => { unsafe { ManuallyDrop::take(&mut on_exit_imgui)( window, @@ -641,5 +663,6 @@ impl Builder { _ => {} } }); + std::process::exit(0); } } diff --git a/frontend/desktop/src/utils.rs b/frontend/desktop/src/utils.rs index b4be206..daf2e9b 100644 --- a/frontend/desktop/src/utils.rs +++ b/frontend/desktop/src/utils.rs @@ -32,7 +32,7 @@ static BASE_DIRS: LazyLock = LazyLock::new(|| { }); pub fn base_dirs<'a>() -> &'a BaseDirs { - &*BASE_DIRS + &BASE_DIRS } pub struct Lazy { diff --git a/frontend/web/crate/src/lib.rs b/frontend/web/crate/src/lib.rs index aa6b605..6957233 100644 --- a/frontend/web/crate/src/lib.rs +++ b/frontend/web/crate/src/lib.rs @@ -101,6 +101,8 @@ pub struct EmuState { arm9_bios: Option>>, } +// rust-analyzer needs this not to trigger a warning about generated function names +#[allow(non_snake_case)] #[wasm_bindgen] impl EmuState { pub fn reset(&mut self) { diff --git a/render/soft-2d/base/Cargo.toml b/render/soft-2d/base/Cargo.toml index 94013cb..abb460e 100644 --- a/render/soft-2d/base/Cargo.toml +++ b/render/soft-2d/base/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] dust-core = { path = "../../../core" } -proc-bitfield = { version = "0.2", features = ["nightly"] } +proc-bitfield = { version = "0.3", features = ["nightly"] } diff --git a/render/soft-3d/Cargo.toml b/render/soft-3d/Cargo.toml index 82ec750..190eca5 100644 --- a/render/soft-3d/Cargo.toml +++ b/render/soft-3d/Cargo.toml @@ -6,4 +6,4 @@ publish = false [dependencies] dust-core = { path = "../../core" } -proc-bitfield = { version = "0.2", features = ["nightly"] } +proc-bitfield = { version = "0.3", features = ["nightly"] } diff --git a/render/wgpu-2d/Cargo.toml b/render/wgpu-2d/Cargo.toml index da8631f..88ff2ba 100644 --- a/render/wgpu-2d/Cargo.toml +++ b/render/wgpu-2d/Cargo.toml @@ -8,7 +8,7 @@ publish = false dust-core = { path = "../../core" } emu-utils = { git = "https://github.com/kelpsyberry/emu-utils", features = ["std"] } dust-soft-2d-base = { path = "../soft-2d/base" } -proc-bitfield = { version = "0.2", features = ["nightly"] } -wgpu = "0.14" +proc-bitfield = { version = "0.3", features = ["nightly"] } +wgpu = { git = "https://github.com/gfx-rs/wgpu" } crossbeam-channel = "0.5" parking_lot = "0.12" diff --git a/render/wgpu-2d/src/common/gfx.rs b/render/wgpu-2d/src/common/gfx.rs index d334225..5f1db84 100644 --- a/render/wgpu-2d/src/common/gfx.rs +++ b/render/wgpu-2d/src/common/gfx.rs @@ -3,7 +3,7 @@ use dust_core::gpu::{engine_3d, Scanline, SCREEN_HEIGHT, SCREEN_WIDTH}; use emu_utils::triple_buffer; use parking_lot::RwLock; use std::{ - num::{NonZeroU32, NonZeroU64}, + num::NonZeroU64, slice, sync::{ atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering}, @@ -81,6 +81,7 @@ impl OutputAttachments { dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let color_view = color.create_view(&wgpu::TextureViewDescriptor { label: Some("2D renderer color view"), @@ -476,6 +477,7 @@ impl GfxThreadData { dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::R32Uint, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let color_output_view = color_output_texture.create_view(&Default::default()); ( @@ -540,6 +542,7 @@ impl GfxThreadData { dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rg32Uint, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let fb_texture_view = fb_texture.create_view(&Default::default()); @@ -717,7 +720,7 @@ impl GfxThreadData { }, wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new((SCREEN_WIDTH * 4) as u32), + bytes_per_row: Some((SCREEN_WIDTH * 4) as u32), rows_per_image: None, }, wgpu::Extent3d { @@ -738,7 +741,7 @@ impl GfxThreadData { }, wgpu::ImageDataLayout { offset: 0, - bytes_per_row: NonZeroU32::new((SCREEN_WIDTH * 8) as u32), + bytes_per_row: Some((SCREEN_WIDTH * 8) as u32), rows_per_image: None, }, wgpu::Extent3d { @@ -769,10 +772,12 @@ impl GfxThreadData { resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, }); render_pass.set_bind_group(0, &self.fb_data_bg, &[]); diff --git a/render/wgpu-3d/Cargo.toml b/render/wgpu-3d/Cargo.toml index 2f64695..4fb32f1 100644 --- a/render/wgpu-3d/Cargo.toml +++ b/render/wgpu-3d/Cargo.toml @@ -11,8 +11,8 @@ threaded = ["emu-utils", "crossbeam-channel", "parking_lot"] dust-core = { path = "../../core", features = ["3d-hi-res-coords"] } dust-soft-3d = { path = "../soft-3d" } emu-utils = { git = "https://github.com/kelpsyberry/emu-utils", features = ["std"], optional = true} -proc-bitfield = { version = "0.2", features = ["nightly"] } +proc-bitfield = { version = "0.3", features = ["nightly"] } ahash = "0.8" -wgpu = "0.14" +wgpu = { git = "https://github.com/gfx-rs/wgpu" } crossbeam-channel = { version = "0.5", optional = true } parking_lot = { version = "0.12", optional = true } diff --git a/render/wgpu-3d/src/lib.rs b/render/wgpu-3d/src/lib.rs index 836a073..7c2a0b8 100644 --- a/render/wgpu-3d/src/lib.rs +++ b/render/wgpu-3d/src/lib.rs @@ -19,7 +19,6 @@ mod utils; use ahash::AHashMap as HashMap; use core::{ mem::{self, MaybeUninit}, - num::NonZeroU32, simd::SimdUint, // simd::u16x2, slice, @@ -285,6 +284,7 @@ fn create_texture( dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); decode_buffer.clear(); @@ -543,7 +543,7 @@ fn create_texture( slice::from_raw_parts(decode_buffer.as_ptr() as *const u8, decode_buffer.len() * 4), wgpu::ImageDataLayout { offset: 0, - bytes_per_row: Some(NonZeroU32::new_unchecked(width << 2)), + bytes_per_row: Some(width << 2), rows_per_image: None, }, size, @@ -606,6 +606,7 @@ impl OutputAttachments { dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: &[], }); let color_view = color.create_view(&wgpu::TextureViewDescriptor { label: Some("3D renderer color view"), @@ -624,6 +625,7 @@ impl OutputAttachments { dimension: wgpu::TextureDimension::D2, format: wgpu::TextureFormat::Depth32FloatStencil8, usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], }); let depth_view = depth.create_view(&wgpu::TextureViewDescriptor { label: Some("3D renderer depth view"), @@ -930,7 +932,7 @@ impl Renderer { color_to_wgpu_f64(frame.rendering.clear_color) }, ), - store: true, + store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { @@ -943,13 +945,15 @@ impl Renderer { frame.rendering.clear_depth as f32 / (1 << 24) as f32 }, ), - store: false, + store: wgpu::StoreOp::Discard, }), stencil_ops: Some(wgpu::Operations { load: wgpu::LoadOp::Clear(0), - store: false, + store: wgpu::StoreOp::Discard, }), }), + timestamp_writes: None, + occlusion_query_set: None, }); if frame.rendering.control.rear_plane_bitmap_enabled() {