diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9ac0a7d..05394b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,7 +102,7 @@ jobs: path: target/${{ matrix.target }}/release/gopher64 build-windows: - runs-on: windows-latest + runs-on: windows-2025 strategy: fail-fast: false matrix: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0ffe90d..4d7c98e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,6 +19,10 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: clippy, rustfmt + - name: Install SDL2 + run: | + sudo apt-get update + sudo apt-get -y install libsdl2-dev - name: Rustfmt Check uses: actions-rust-lang/rustfmt@v1 - name: Lint diff --git a/Cargo.toml b/Cargo.toml index 8ddd0dc..e5de0e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ eframe = "0.30" sha2 = "0.10" rusttype = "0.9" tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] } -sdl2-sys = "0.37" +sdl3-sys = { version = "0.4", features = ["build-from-source-static"] } spin_sleep = "1.3" encoding_rs = "0.8" mimalloc = "0.1" @@ -32,11 +32,6 @@ version = "0.12" default-features = false features = ["http2","rustls-tls","charset","json"] -[dependencies.sdl2] -version = "0.37" -default-features = false -features = ["static-link","bundled"] - [dependencies.rfd] version = "0.15" default-features = false diff --git a/build.rs b/build.rs index 05df125..1a8fab7 100644 --- a/build.rs +++ b/build.rs @@ -52,7 +52,10 @@ fn main() { .include("parallel-rdp/parallel-rdp-standalone/vulkan") .include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include") .include("parallel-rdp/parallel-rdp-standalone/util") - .includes(std::env::var("DEP_SDL2_INCLUDE")); + .include( + std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap()) + .join("include"), + ); let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index 4db181e..3b8acc9 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -129,28 +129,19 @@ static const unsigned cmd_len_lut[64] = { 1, }; -int sdl_event_filter(void *userdata, SDL_Event *event) +bool sdl_event_filter(void *userdata, SDL_Event *event) { - if (event->type == SDL_WINDOWEVENT) + if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { - switch (event->window.event) - { - case SDL_WINDOWEVENT_CLOSE: - emu_running = false; - break; - case SDL_WINDOWEVENT_RESIZED: - if (emu_running) - { - wsi_platform->do_resize(); - } - break; - default: - break; - } + emu_running = false; } - else if (fullscreen && event->type == SDL_KEYDOWN) + else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running) { - switch (event->key.keysym.scancode) + wsi_platform->do_resize(); + } + else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen) + { + switch (event->key.scancode) { case SDL_SCANCODE_ESCAPE: emu_running = false; @@ -394,8 +385,8 @@ uint64_t rdp_process_commands() do { offset &= 0xFF8; - cmd_data[2 * cmd_ptr + 0] = SDL_SwapBE32(*reinterpret_cast(gfx_info.DMEM + offset)); - cmd_data[2 * cmd_ptr + 1] = SDL_SwapBE32(*reinterpret_cast(gfx_info.DMEM + offset + 4)); + cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast(gfx_info.DMEM + offset)); + cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast(gfx_info.DMEM + offset + 4)); offset += sizeof(uint64_t); cmd_ptr++; } while (--length > 0); diff --git a/parallel-rdp/wsi_platform.cpp b/parallel-rdp/wsi_platform.cpp index e7d2377..7a91423 100644 --- a/parallel-rdp/wsi_platform.cpp +++ b/parallel-rdp/wsi_platform.cpp @@ -1,11 +1,11 @@ #include "wsi_platform.hpp" -#include +#include VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu) { VkSurfaceKHR surface = nullptr; - SDL_bool result = SDL_Vulkan_CreateSurface(window, instance, &surface); - if (result != SDL_TRUE) + bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface); + if (result != true) { printf("Error creating surface\n"); } @@ -14,18 +14,23 @@ VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevi void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface) { + SDL_Vulkan_DestroySurface(instance, surface, NULL); } std::vector SDL_WSIPlatform::get_instance_extensions() { unsigned int extensionCount = 0; - SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr); - std::vector extensionNames(extensionCount); - SDL_bool result = SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames.data()); - if (result != SDL_TRUE) + char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount); + if (extensions == NULL) { - printf("Error creating surface\n"); + printf("Error getting instance extensions\n"); + } + + std::vector extensionNames; + for (int i = 0; i < extensionCount; ++i) + { + extensionNames.push_back(extensions[i]); } return extensionNames; } diff --git a/parallel-rdp/wsi_platform.hpp b/parallel-rdp/wsi_platform.hpp index c8dbdf8..ddc606a 100644 --- a/parallel-rdp/wsi_platform.hpp +++ b/parallel-rdp/wsi_platform.hpp @@ -1,7 +1,7 @@ #pragma once #include "wsi.hpp" -#include +#include class SDL_WSIPlatform : public Vulkan::WSIPlatform { diff --git a/src/device.rs b/src/device.rs index 67b0ff5..b56589d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -79,7 +79,9 @@ pub fn run_game( cpu::run(device); - ui::video::close(); + ui::input::close(&mut device.ui); + ui::video::close(&device.ui); + ui::audio::close(&mut device.ui); ui::storage::write_saves(&device.ui, &device.netplay); } diff --git a/src/device/ai.rs b/src/device/ai.rs index 08bb398..bb62d70 100644 --- a/src/device/ai.rs +++ b/src/device/ai.rs @@ -144,6 +144,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u AI_DACRATE_REG => { if device.ai.regs[reg as usize] != value & mask { let frequency = device.vi.clock / (1 + (value & mask)) as u64; + ui::audio::close(&mut device.ui); ui::audio::init(&mut device.ui, frequency) } device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask) diff --git a/src/main.rs b/src/main.rs index 1d2729e..9df8fe7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod device; mod netplay; mod ui; use clap::Parser; +use ui::gui; /// N64 emulator #[derive(Parser, Debug)] @@ -47,7 +48,7 @@ struct Args { value_name = "CONTROLLER_NUMBER", help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port" )] - assign_controller: Option, + assign_controller: Option, #[arg( short, long, @@ -119,7 +120,10 @@ async fn main() { } } if args.list_controllers { - ui::input::list_controllers(&device.ui); + let controllers = gui::get_controller_names(); + for (i, controller) in controllers.iter().enumerate() { + println!("Controller {}: {}", i, controller); + } return; } if args.assign_controller.is_some() { diff --git a/src/ui.rs b/src/ui.rs index 9c93638..15a75b9 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -7,6 +7,7 @@ pub mod video; pub mod vru; pub struct Ui { + pub keyboard_state: *const bool, pub controllers: [input::Controllers; 4], pub config_file_path: std::path::PathBuf, pub config: config::Config, @@ -15,19 +16,19 @@ pub struct Ui { pub game_hash: String, pub paths: storage::Paths, pub saves: storage::Saves, - pub sdl_context: Option, - pub video_subsystem: Option, - pub audio_subsystem: Option, - pub pak_audio: Option, - pub joystick_subsystem: Option, - #[allow(dead_code)] - pub controller_subsystem: Option, - pub window: Option, - pub audio_device: Option>, + pub pak_audio: audio::PakAudio, + pub window: *mut sdl3_sys::video::SDL_Window, + pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, + pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, + pub audio_freq: f64, + pub audio_device: u32, } impl Drop for Ui { fn drop(&mut self) { + unsafe { + sdl3_sys::init::SDL_Quit(); + } write_config(self); } } @@ -37,13 +38,18 @@ fn write_config(ui: &Ui) { serde_json::to_writer_pretty(f, &ui.config).unwrap(); } +pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) { + unsafe { + let init = sdl3_sys::init::SDL_WasInit(0); + if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) { + panic!("Could not initialize SDL subsystem: {}", flag); + } + } +} + impl Ui { pub fn new(config_dir: std::path::PathBuf) -> Ui { - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); - let audio_subsystem = sdl_context.audio().unwrap(); - let joystick_subsystem = sdl_context.joystick().unwrap(); - let controller_subsystem = sdl_context.game_controller().unwrap(); + sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); let config_file_path = config_dir.join("config.json"); let config_file = std::fs::read(config_file_path.clone()); @@ -58,26 +64,27 @@ impl Ui { Ui { controllers: [ input::Controllers { - game_controller: None, - joystick: None, + game_controller: std::ptr::null_mut(), + joystick: std::ptr::null_mut(), rumble: false, }, input::Controllers { - game_controller: None, - joystick: None, + game_controller: std::ptr::null_mut(), + joystick: std::ptr::null_mut(), rumble: false, }, input::Controllers { - game_controller: None, - joystick: None, + game_controller: std::ptr::null_mut(), + joystick: std::ptr::null_mut(), rumble: false, }, input::Controllers { - game_controller: None, - joystick: None, + game_controller: std::ptr::null_mut(), + joystick: std::ptr::null_mut(), rumble: false, }, ], + keyboard_state: std::ptr::null_mut(), config_file_path, config: config_map, save_type: vec![], @@ -97,14 +104,15 @@ impl Ui { mempak: (Vec::new(), false), romsave: (serde_json::Map::new(), false), }, - sdl_context: Some(sdl_context), - video_subsystem: Some(video_subsystem), - audio_subsystem: Some(audio_subsystem), - joystick_subsystem: Some(joystick_subsystem), - controller_subsystem: Some(controller_subsystem), - window: None, - audio_device: None, - pak_audio: None, + pak_audio: audio::PakAudio { + mempak: include_bytes!("../data/mempak.wav").to_vec(), + rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(), + }, + window: std::ptr::null_mut(), + audio_stream: std::ptr::null_mut(), + pak_audio_stream: std::ptr::null_mut(), + audio_freq: 0.0, + audio_device: 0, } } } diff --git a/src/ui/audio.rs b/src/ui/audio.rs index a998b1d..f0fbe19 100644 --- a/src/ui/audio.rs +++ b/src/ui/audio.rs @@ -2,74 +2,101 @@ use crate::device; use crate::ui; pub struct PakAudio { - mempak: Vec, - rumblepak: Vec, + pub mempak: Vec, + pub rumblepak: Vec, } pub fn init(ui: &mut ui::Ui, frequency: u64) { - let desired_spec = sdl2::audio::AudioSpecDesired { - freq: Some(frequency as i32), - channels: Some(2), - samples: None, + ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO); + + let audio_spec = sdl3_sys::audio::SDL_AudioSpec { + format: sdl3_sys::audio::SDL_AUDIO_S16LE, + freq: frequency as i32, + channels: 2, }; - ui.audio_device = Some( - ui.audio_subsystem - .as_ref() - .unwrap() - .open_queue::(None, &desired_spec) - .unwrap(), - ); - let audio_device = ui.audio_device.as_ref().unwrap(); - audio_device.resume(); - - let mempak_audio = Box::new(sdl2::audio::AudioSpecWAV::load_wav_rw( - &mut sdl2::rwops::RWops::from_bytes(include_bytes!("../../data/mempak.wav")) - .expect("Could not mempak WAV file"), - )) - .expect("Could not load mempak WAV file"); - let rumblepak_audio = Box::new( - sdl2::audio::AudioSpecWAV::load_wav_rw( - &mut sdl2::rwops::RWops::from_bytes(include_bytes!("../../data/rumblepak.wav")) - .expect("Could not load rumblepak WAV file"), + ui.audio_device = unsafe { + sdl3_sys::audio::SDL_OpenAudioDevice( + sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, + &audio_spec, ) - .expect("Could not load rumblepak WAV file"), - ); + }; + if ui.audio_device == 0 { + panic!("Could not open audio device"); + } - let cvt = sdl2::audio::AudioCVT::new( - mempak_audio.format, - mempak_audio.channels, - mempak_audio.freq, - audio_device.spec().format, - audio_device.spec().channels, - audio_device.spec().freq, - ) - .expect("Could not create AudioCVT"); + let mut dst = Default::default(); + if !unsafe { + sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut()) + } { + panic!("Could not get audio device format"); + } - ui.pak_audio = Some(PakAudio { - mempak: cvt.convert(mempak_audio.buffer().to_vec()), - rumblepak: cvt.convert(rumblepak_audio.buffer().to_vec()), - }); + ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) }; + if ui.audio_stream.is_null() { + return; + } + if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } { + panic!("Could not bind audio stream"); + } + + let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec { + format: sdl3_sys::audio::SDL_AUDIO_S16LE, + freq: 24000, + channels: 1, + }; + + ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) }; + if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } { + panic!("Could not bind audio stream"); + } + + ui.audio_freq = audio_spec.freq as f64; +} + +pub fn close(ui: &mut ui::Ui) { + unsafe { + if !ui.audio_stream.is_null() { + sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream); + ui.audio_stream = std::ptr::null_mut(); + } + if !ui.pak_audio_stream.is_null() { + sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream); + ui.pak_audio_stream = std::ptr::null_mut(); + } + sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device); + ui.audio_device = 0; + } } pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) { + if ui.pak_audio_stream.is_null() { + return; + } + let sound; if pak == device::controller::PakType::RumblePak { - sound = &ui.pak_audio.as_ref().unwrap().rumblepak; + sound = &ui.pak_audio.rumblepak; } else if pak == device::controller::PakType::MemPak { - sound = &ui.pak_audio.as_ref().unwrap().mempak; + sound = &ui.pak_audio.mempak; } else { return; } - let audio_device = ui.audio_device.as_ref().unwrap(); - let i16_buffer: Vec = sound - .chunks_exact(2) - .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]])) - .collect(); - audio_device.queue_audio(&i16_buffer).unwrap(); + if !unsafe { + sdl3_sys::audio::SDL_PutAudioStreamData( + ui.pak_audio_stream, + sound.as_ptr() as *const std::ffi::c_void, + sound.len() as i32, + ) + } { + panic!("Could not play audio"); + } } pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) { - let audio_device = device.ui.audio_device.as_ref().unwrap(); + if device.ui.audio_stream.is_null() { + return; + } + let mut primary_buffer: Vec = vec![0; length as usize / 2]; let mut i = 0; while i < length as usize / 2 { @@ -83,16 +110,33 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) { i += 2; } - let audio_queued = audio_device.size() as f64; - let acceptable_latency = (audio_device.spec().freq as f64 * 0.2) * 4.0; - let min_latency = (audio_device.spec().freq as f64 * 0.02) * 4.0; + let audio_queued = + unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64; + let acceptable_latency = (device.ui.audio_freq * 0.2) * 4.0; + let min_latency = (device.ui.audio_freq * 0.02) * 4.0; if audio_queued < min_latency { - let silence_buffer: Vec = vec![0; ((min_latency - audio_queued) * 2.0) as usize & !1]; - audio_device.queue_audio(&silence_buffer).unwrap(); + let silence_buffer: Vec = vec![0; (min_latency - audio_queued) as usize & !3]; + if !unsafe { + sdl3_sys::audio::SDL_PutAudioStreamData( + device.ui.audio_stream, + silence_buffer.as_ptr() as *const std::ffi::c_void, + silence_buffer.len() as i32, + ) + } { + panic!("Could not play audio"); + } } - if audio_queued < acceptable_latency { - audio_device.queue_audio(&primary_buffer).unwrap(); + if audio_queued < acceptable_latency + && !unsafe { + sdl3_sys::audio::SDL_PutAudioStreamData( + device.ui.audio_stream, + primary_buffer.as_ptr() as *const std::ffi::c_void, + primary_buffer.len() as i32 * 2, + ) + } + { + panic!("Could not play audio"); } } diff --git a/src/ui/config.rs b/src/ui/config.rs index 2711e03..5864959 100644 --- a/src/ui/config.rs +++ b/src/ui/config.rs @@ -5,9 +5,9 @@ pub struct InputProfile { pub keys: [(bool, i32); ui::input::PROFILE_SIZE], pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE], pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE], - pub joystick_buttons: [(bool, u32); ui::input::PROFILE_SIZE], - pub joystick_hat: [(bool, u32, u8); ui::input::PROFILE_SIZE], - pub joystick_axis: [(bool, u32, i16); ui::input::PROFILE_SIZE], + pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE], + pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE], + pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE], pub dinput: bool, } diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 61fbb3e..dee758a 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -10,7 +10,7 @@ pub struct GopherEguiApp { data_dir: std::path::PathBuf, configure_profile: bool, profile_name: String, - controllers: Vec, + controller_names: Vec, selected_controller: [i32; 4], selected_profile: [String; 4], input_profiles: Vec, @@ -46,17 +46,46 @@ fn get_input_profiles(game_ui: &ui::Ui) -> Vec { profiles } -fn get_controllers(game_ui: &ui::Ui) -> Vec { +pub fn get_controller_names() -> Vec { let mut controllers: Vec = vec![]; - let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap(); - let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); - for i in 0..num_joysticks { - controllers.push(joystick_subsystem.name_for_index(i).unwrap()); + let mut joystick_count = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut joystick_count) }; + if !joysticks.is_null() { + for offset in 0..joystick_count as isize { + let name = unsafe { + std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID( + *(joysticks.offset(offset)), + )) + }; + controllers.push(name.to_string_lossy().to_string()); + } + unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }; } controllers } +pub fn get_controller_paths() -> Vec { + let mut controller_paths: Vec = vec![]; + + let mut joystick_count = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut joystick_count) }; + if !joysticks.is_null() { + for offset in 0..joystick_count as isize { + let path = unsafe { + std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID( + *(joysticks.offset(offset)), + )) + .to_string_lossy() + .to_string() + }; + controller_paths.push(path); + } + unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }; + } + controller_paths +} + impl GopherEguiApp { pub fn new( cc: &eframe::CreationContext<'_>, @@ -66,12 +95,8 @@ impl GopherEguiApp { ) -> GopherEguiApp { add_japanese_font(&cc.egui_ctx); let game_ui = ui::Ui::new(config_dir.clone()); - let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap(); - let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); - let mut guids: Vec = vec![]; - for i in 0..num_joysticks { - guids.push(joystick_subsystem.device_guid(i).unwrap().to_string()); - } + let controller_paths = get_controller_paths(); + let mut selected_controller = [-1, -1, -1, -1]; for (pos, item) in game_ui .config @@ -81,9 +106,9 @@ impl GopherEguiApp { .enumerate() { if item.is_some() { - for (guid_pos, guid) in guids.iter().enumerate() { - if item.as_deref().unwrap() == *guid { - selected_controller[pos] = guid_pos as i32; + for (path_pos, path) in controller_paths.iter().enumerate() { + if item.as_deref().unwrap() == *path { + selected_controller[pos] = path_pos as i32; break; } } @@ -97,7 +122,7 @@ impl GopherEguiApp { profile_name: "".to_string(), selected_profile: game_ui.config.input.input_profile_binding.clone(), selected_controller, - controllers: get_controllers(&game_ui), + controller_names: get_controller_names(), input_profiles: get_input_profiles(&game_ui), controller_enabled: game_ui.config.input.controller_enabled, upscale: game_ui.config.video.upscale, @@ -116,15 +141,11 @@ impl GopherEguiApp { } fn save_config(game_ui: &mut ui::Ui, save_config_items: SaveConfig) { - let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap(); + let controller_paths = get_controller_paths(); for (pos, item) in save_config_items.selected_controller.iter().enumerate() { if *item != -1 { - game_ui.config.input.controller_assignment[pos] = Some( - joystick_subsystem - .device_guid(*item as u32) - .unwrap() - .to_string(), - ); + game_ui.config.input.controller_assignment[pos] = + Some(controller_paths[*item as usize].clone()); } else { game_ui.config.input.controller_assignment[pos] = None } @@ -437,7 +458,7 @@ impl eframe::App for GopherEguiApp { let controller_text = if self.selected_controller[i] == -1 { "None".to_string() } else { - self.controllers[self.selected_controller[i] as usize].clone() + self.controller_names[self.selected_controller[i] as usize].clone() }; egui::ComboBox::from_id_salt(format!("controller-combo-{}", i)) .selected_text(controller_text) @@ -447,11 +468,11 @@ impl eframe::App for GopherEguiApp { -1, "None".to_string(), ); - for j in 0..self.controllers.len() { + for j in 0..self.controller_names.len() { ui.selectable_value( &mut self.selected_controller[i], j as i32, - self.controllers[j].clone(), + self.controller_names[j].clone(), ); } }); diff --git a/src/ui/input.rs b/src/ui/input.rs index a01064d..0846820 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -30,8 +30,8 @@ const MAX_AXIS_VALUE: f64 = 85.0; pub struct Controllers { pub rumble: bool, - pub game_controller: Option, - pub joystick: Option, + pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad, + pub joystick: *mut sdl3_sys::joystick::SDL_Joystick, } fn bound_axis(x: &mut f64, y: &mut f64) { @@ -50,30 +50,38 @@ fn bound_axis(x: &mut f64, y: &mut f64) { fn set_axis_from_joystick( profile: &ui::config::InputProfile, - joystick: &sdl2::joystick::Joystick, + joystick: *mut sdl3_sys::joystick::SDL_Joystick, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.joystick_axis[AXIS_LEFT].0 { - let axis_position = joystick.axis(profile.joystick_axis[AXIS_LEFT].1).unwrap(); + let axis_position = unsafe { + sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].1) + }; if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 { x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; } } if profile.joystick_axis[AXIS_RIGHT].0 { - let axis_position = joystick.axis(profile.joystick_axis[AXIS_RIGHT].1).unwrap(); + let axis_position = unsafe { + sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].1) + }; if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 { x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; } } if profile.joystick_axis[AXIS_DOWN].0 { - let axis_position = joystick.axis(profile.joystick_axis[AXIS_DOWN].1).unwrap(); + let axis_position = unsafe { + sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].1) + }; if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 { y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); } } if profile.joystick_axis[AXIS_UP].0 { - let axis_position = joystick.axis(profile.joystick_axis[AXIS_UP].1).unwrap(); + let axis_position = unsafe { + sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].1) + }; if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 { y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); } @@ -81,68 +89,85 @@ fn set_axis_from_joystick( (x, y) } -fn get_axis_from_i32(axis: i32) -> sdl2::controller::Axis { +fn get_axis_from_i32(axis: i32) -> sdl3_sys::gamepad::SDL_GamepadAxis { match axis { - 0 => sdl2::controller::Axis::LeftX, - 1 => sdl2::controller::Axis::LeftY, - 2 => sdl2::controller::Axis::RightX, - 3 => sdl2::controller::Axis::RightY, - 4 => sdl2::controller::Axis::TriggerLeft, - 5 => sdl2::controller::Axis::TriggerRight, + 0 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTX, + 1 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTY, + 2 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTX, + 3 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTY, + 4 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFT_TRIGGER, + 5 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHT_TRIGGER, _ => panic!("Invalid axis"), } } -fn get_button_from_i32(button: i32) -> sdl2::controller::Button { +fn get_button_from_i32(button: i32) -> sdl3_sys::gamepad::SDL_GamepadButton { match button { - 0 => sdl2::controller::Button::A, - 1 => sdl2::controller::Button::B, - 2 => sdl2::controller::Button::X, - 3 => sdl2::controller::Button::Y, - 4 => sdl2::controller::Button::Back, - 5 => sdl2::controller::Button::Guide, - 6 => sdl2::controller::Button::Start, - 7 => sdl2::controller::Button::LeftStick, - 8 => sdl2::controller::Button::RightStick, - 9 => sdl2::controller::Button::LeftShoulder, - 10 => sdl2::controller::Button::RightShoulder, - 11 => sdl2::controller::Button::DPadUp, - 12 => sdl2::controller::Button::DPadDown, - 13 => sdl2::controller::Button::DPadLeft, - 14 => sdl2::controller::Button::DPadRight, + 0 => sdl3_sys::gamepad::SDL_GamepadButton::SOUTH, + 1 => sdl3_sys::gamepad::SDL_GamepadButton::EAST, + 2 => sdl3_sys::gamepad::SDL_GamepadButton::WEST, + 3 => sdl3_sys::gamepad::SDL_GamepadButton::NORTH, + 4 => sdl3_sys::gamepad::SDL_GamepadButton::BACK, + 5 => sdl3_sys::gamepad::SDL_GamepadButton::GUIDE, + 6 => sdl3_sys::gamepad::SDL_GamepadButton::START, + 7 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_STICK, + 8 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_STICK, + 9 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_SHOULDER, + 10 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_SHOULDER, + 11 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_UP, + 12 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_DOWN, + 13 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_LEFT, + 14 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_RIGHT, _ => panic!("Invalid button"), } } fn set_axis_from_controller( profile: &ui::config::InputProfile, - controller: &sdl2::controller::GameController, + controller: *mut sdl3_sys::gamepad::SDL_Gamepad, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.controller_axis[AXIS_LEFT].0 { - let axis_position = - controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1)); + let axis_position = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadAxis( + controller, + get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1), + ) + }; if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 { x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; } } if profile.controller_axis[AXIS_RIGHT].0 { - let axis_position = - controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1)); + let axis_position = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadAxis( + controller, + get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1), + ) + }; if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 { x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64; } } if profile.controller_axis[AXIS_DOWN].0 { - let axis_position = - controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1)); + let axis_position = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadAxis( + controller, + get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1), + ) + }; if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 { y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); } } if profile.controller_axis[AXIS_UP].0 { - let axis_position = controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_UP].1)); + let axis_position = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadAxis( + controller, + get_axis_from_i32(profile.controller_axis[AXIS_UP].1), + ) + }; if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 { y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg(); } @@ -152,35 +177,27 @@ fn set_axis_from_controller( fn set_axis_from_keys( profile: &ui::config::InputProfile, - keyboard_state: &sdl2::keyboard::KeyboardState, + keyboard_state: *const bool, ) -> (f64, f64) { let mut x = 0.0; let mut y = 0.0; if profile.keys[AXIS_LEFT].0 - && keyboard_state.is_scancode_pressed( - sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_LEFT].1).unwrap(), - ) + && unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].1 as isize) } { x = -MAX_AXIS_VALUE } if profile.keys[AXIS_RIGHT].0 - && keyboard_state.is_scancode_pressed( - sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_RIGHT].1).unwrap(), - ) + && unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].1 as isize) } { x = MAX_AXIS_VALUE } if profile.keys[AXIS_DOWN].0 - && keyboard_state.is_scancode_pressed( - sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_DOWN].1).unwrap(), - ) + && unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].1 as isize) } { y = -MAX_AXIS_VALUE } if profile.keys[AXIS_UP].0 - && keyboard_state.is_scancode_pressed( - sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_UP].1).unwrap(), - ) + && unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].1 as isize) } { y = MAX_AXIS_VALUE } @@ -190,25 +207,29 @@ fn set_axis_from_keys( fn set_buttons_from_joystick( profile: &ui::config::InputProfile, i: usize, - joystick: &sdl2::joystick::Joystick, + joystick: *mut sdl3_sys::joystick::SDL_Joystick, keys: &mut u32, ) { let profile_joystick_button = profile.joystick_buttons[i]; if profile_joystick_button.0 { - *keys |= (joystick.button(profile_joystick_button.1).unwrap() as u32) << i; + *keys |= (unsafe { + sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.1) + } as u32) + << i; } let profile_joystick_hat = profile.joystick_hat[i]; if profile_joystick_hat.0 - && joystick.hat(profile_joystick_hat.1).unwrap() - == sdl2::joystick::HatState::from_raw(profile_joystick_hat.2) + && unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.1) } + == profile_joystick_hat.2 { *keys |= 1 << i; } let profile_joystick_axis = profile.joystick_axis[i]; if profile_joystick_axis.0 { - let axis_position = joystick.axis(profile_joystick_axis.1).unwrap(); + let axis_position = + unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.1) }; if axis_position as isize * profile_joystick_axis.2 as isize > 0 && axis_position.saturating_abs() > i16::MAX / 2 { @@ -220,17 +241,28 @@ fn set_buttons_from_joystick( fn set_buttons_from_controller( profile: &ui::config::InputProfile, i: usize, - controller: &sdl2::controller::GameController, + controller: *mut sdl3_sys::gamepad::SDL_Gamepad, keys: &mut u32, ) { let profile_controller_button = profile.controller_buttons[i]; if profile_controller_button.0 { - *keys |= (controller.button(get_button_from_i32(profile_controller_button.1)) as u32) << i; + *keys |= (unsafe { + sdl3_sys::gamepad::SDL_GetGamepadButton( + controller, + get_button_from_i32(profile_controller_button.1), + ) + } as u32) + << i; } let profile_controller_axis = profile.controller_axis[i]; if profile_controller_axis.0 { - let axis_position = controller.axis(get_axis_from_i32(profile_controller_axis.1)); + let axis_position = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadAxis( + controller, + get_axis_from_i32(profile_controller_axis.1), + ) + }; if axis_position as isize * profile_controller_axis.2 as isize > 0 && axis_position.saturating_abs() > i16::MAX / 2 { @@ -243,36 +275,34 @@ pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) { if !ui.controllers[channel].rumble { return; } - let controller = &mut ui.controllers[channel].game_controller; - let joystick = &mut ui.controllers[channel].joystick; - if controller.is_some() { - controller - .as_mut() - .unwrap() - .set_rumble( + let controller = ui.controllers[channel].game_controller; + let joystick = ui.controllers[channel].joystick; + if !controller.is_null() { + unsafe { + sdl3_sys::gamepad::SDL_RumbleGamepad( + controller, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u32 * 60000, ) - .unwrap(); - } else if joystick.is_some() { - joystick - .as_mut() - .unwrap() - .set_rumble( + }; + } else if !joystick.is_null() { + unsafe { + sdl3_sys::joystick::SDL_RumbleJoystick( + joystick, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u16 * u16::MAX, (rumble & 1) as u32 * 60000, ) - .unwrap(); + }; } } fn change_paks( profile: &ui::config::InputProfile, - joystick: &Option, - controller: &Option, - keyboard_state: &sdl2::keyboard::KeyboardState, + joystick: *mut sdl3_sys::joystick::SDL_Joystick, + controller: *mut sdl3_sys::gamepad::SDL_Gamepad, + keyboard_state: *const bool, ) -> bool { let controller_button = profile.controller_buttons[CHANGE_PAK]; let joystick_button = profile.joystick_buttons[CHANGE_PAK]; @@ -280,51 +310,42 @@ fn change_paks( let key = profile.keys[CHANGE_PAK]; let mut pressed = false; - if controller_button.0 && controller.is_some() { - pressed = controller - .as_ref() - .unwrap() - .button(get_button_from_i32(controller_button.1)); - } else if joystick_button.0 && joystick.is_some() { - pressed = joystick - .as_ref() - .unwrap() - .button(joystick_button.1) - .unwrap(); - } else if joystick_hat.0 && joystick.is_some() { - pressed = joystick.as_ref().unwrap().hat(joystick_hat.1).unwrap() - == sdl2::joystick::HatState::from_raw(joystick_hat.2); + if controller_button.0 && !controller.is_null() { + pressed = unsafe { + sdl3_sys::gamepad::SDL_GetGamepadButton( + controller, + get_button_from_i32(controller_button.1), + ) + }; + } else if joystick_button.0 && !joystick.is_null() { + pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.1) }; + } else if joystick_hat.0 && !joystick.is_null() { + pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.1) } + == joystick_hat.2; } else if key.0 { - pressed = - keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::from_i32(key.1).unwrap()); + pressed = unsafe { *keyboard_state.offset(key.1 as isize) }; } pressed } pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) { - let events = ui.sdl_context.as_ref().unwrap().event_pump().unwrap(); - let keyboard_state = events.keyboard_state(); - let profile_name = ui.config.input.input_profile_binding[channel].clone(); let profile = ui.config.input.input_profiles.get(&profile_name).unwrap(); let mut keys = 0; - let controller = &ui.controllers[channel].game_controller; - let joystick = &ui.controllers[channel].joystick; + let controller = ui.controllers[channel].game_controller; + let joystick = ui.controllers[channel].joystick; for i in 0..14 { if profile_name != "default" || channel == 0 { let profile_key = profile.keys[i]; if profile_key.0 { - keys |= (keyboard_state - .is_scancode_pressed(sdl2::keyboard::Scancode::from_i32(profile_key.1).unwrap()) - as u32) - << i; + keys |= (unsafe { *ui.keyboard_state.offset(profile_key.1 as isize) } as u32) << i; } } - if controller.is_some() { - set_buttons_from_controller(profile, i, controller.as_ref().unwrap(), &mut keys); - } else if joystick.is_some() { - set_buttons_from_joystick(profile, i, joystick.as_ref().unwrap(), &mut keys); + if !controller.is_null() { + set_buttons_from_controller(profile, i, controller, &mut keys); + } else if !joystick.is_null() { + set_buttons_from_joystick(profile, i, joystick, &mut keys); } } @@ -332,13 +353,13 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) { let mut y: f64 = 0.0; if profile_name != "default" || channel == 0 { - (x, y) = set_axis_from_keys(profile, &keyboard_state); + (x, y) = set_axis_from_keys(profile, ui.keyboard_state); } - if controller.is_some() { - (x, y) = set_axis_from_controller(profile, controller.as_ref().unwrap()) - } else if joystick.is_some() { - (x, y) = set_axis_from_joystick(profile, joystick.as_ref().unwrap()) + if !controller.is_null() { + (x, y) = set_axis_from_controller(profile, controller) + } else if !joystick.is_null() { + (x, y) = set_axis_from_joystick(profile, joystick) } bound_axis(&mut x, &mut y); @@ -347,33 +368,27 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) { ( keys, - change_paks(profile, joystick, controller, &keyboard_state), + change_paks(profile, joystick, controller, ui.keyboard_state), ) } -pub fn list_controllers(ui: &ui::Ui) { - let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); - let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); - if num_joysticks == 0 { - println!("No controllers connected") - } - for i in 0..num_joysticks { - println!("{}: {}", i, joystick_subsystem.name_for_index(i).unwrap()) - } -} - -pub fn assign_controller(ui: &mut ui::Ui, controller: u32, port: usize) { - let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); - let num_joysticks = joystick_subsystem.num_joysticks().unwrap(); - if controller < num_joysticks { - ui.config.input.controller_assignment[port - 1] = Some( - joystick_subsystem - .device_guid(controller) - .unwrap() - .to_string(), - ); - } else { - println!("Invalid controller number") +pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) { + let mut joystick_count = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut joystick_count) }; + if !joysticks.is_null() { + if controller < joystick_count { + let path = unsafe { + std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID( + *(joysticks.offset(controller as isize)), + )) + .to_string_lossy() + .to_string() + }; + ui.config.input.controller_assignment[port - 1] = Some(path); + } else { + println!("Invalid controller number") + } + unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }; } } @@ -392,7 +407,21 @@ pub fn clear_bindings(ui: &mut ui::Ui) { } } +fn close_controllers( + open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick>, + open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad>, +) { + for joystick in open_joysticks { + unsafe { sdl3_sys::joystick::SDL_CloseJoystick(joystick) } + } + for controller in open_controllers { + unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller) } + } +} + pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) { + ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO); + if profile == "default" { println!("Profile name cannot be default"); return; @@ -401,30 +430,48 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) { println!("Profile name cannot be empty"); return; } - let controller_subsystem = ui.controller_subsystem.as_ref().unwrap(); - let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); - let mut controllers = vec![]; - let mut joysticks = vec![]; - for i in 0..joystick_subsystem.num_joysticks().unwrap() { - if !dinput { - if let Ok(controller) = controller_subsystem.open(i) { - controllers.push(controller); - continue; + let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new(); + let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new(); + + let mut joystick_count = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut joystick_count) }; + if !joysticks.is_null() { + for offset in 0..joystick_count as isize { + if !dinput { + let controller = + unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*joysticks.offset(offset)) }; + if !controller.is_null() { + open_controllers.push(controller); + } + } else { + let joystick = + unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*joysticks.offset(offset)) }; + if !joystick.is_null() { + open_joysticks.push(joystick); + } } } - if let Ok(joystick) = joystick_subsystem.open(i) { - joysticks.push(joystick); - } + unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }; } - let mut builder = - ui.video_subsystem - .as_ref() - .unwrap() - .window("configure input profile", 640, 480); - builder.position_centered(); - let window = builder.build().unwrap(); - let mut canvas = window.into_canvas().build().unwrap(); + let title = std::ffi::CString::new("configure input profile").unwrap(); + let mut window: *mut sdl3_sys::video::SDL_Window = std::ptr::null_mut(); + let mut renderer: *mut sdl3_sys::render::SDL_Renderer = std::ptr::null_mut(); + if !unsafe { + sdl3_sys::render::SDL_CreateWindowAndRenderer( + title.as_ptr(), + 640, + 480, + 0, + &mut window, + &mut renderer, + ) + } { + panic!("Could not create window and renderer") + } + if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } { + panic!("Could not show window") + } let font = rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap(); @@ -451,101 +498,90 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) { ]; let mut new_keys = [(false, 0); PROFILE_SIZE]; - let mut new_joystick_buttons = [(false, 0u32); PROFILE_SIZE]; - let mut new_joystick_hat = [(false, 0u32, 0); PROFILE_SIZE]; - let mut new_joystick_axis = [(false, 0u32, 0); PROFILE_SIZE]; + let mut new_joystick_buttons = [(false, 0i32); PROFILE_SIZE]; + let mut new_joystick_hat = [(false, 0i32, 0); PROFILE_SIZE]; + let mut new_joystick_axis = [(false, 0i32, 0); PROFILE_SIZE]; let mut new_controller_buttons = [(false, 0i32); PROFILE_SIZE]; let mut new_controller_axis = [(false, 0i32, 0); PROFILE_SIZE]; let mut last_joystick_axis_result = (false, 0, 0); let mut last_controller_axis_result = (false, 0, 0); - let mut events = ui.sdl_context.as_ref().unwrap().event_pump().unwrap(); for (key, value) in key_labels.iter() { - for _event in events.poll_iter() {} // clear events + let mut event: sdl3_sys::events::SDL_Event = Default::default(); + while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {} // clear events ui::video::draw_text( format!("Select binding for: {key}").as_str(), - &mut canvas, + renderer, &font, ); let mut key_set = false; while !key_set { std::thread::sleep(std::time::Duration::from_millis(100)); - for event in events.poll_iter() { - match event { - sdl2::event::Event::Window { - win_event: sdl2::event::WindowEvent::Close, - .. - } => return, - sdl2::event::Event::KeyDown { - scancode: Some(scancode), - .. - } => { - new_keys[*value] = (true, scancode as i32); + while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } { + let event_type = unsafe { event.r#type }; + if event_type == u32::from(sdl3_sys::events::SDL_EVENT_WINDOW_CLOSE_REQUESTED) { + close_controllers(open_joysticks, open_controllers); + return; + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_KEY_DOWN) { + new_keys[*value] = (true, i32::from(unsafe { event.key.scancode })); + key_set = true + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + if !open_controllers.is_empty() { + new_controller_buttons[*value] = + (true, i32::from(unsafe { event.gbutton.button })); key_set = true } - sdl2::event::Event::ControllerButtonDown { button, .. } => { - if !controllers.is_empty() { - new_controller_buttons[*value] = (true, button as i32); + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_AXIS_MOTION) { + let axis_value = unsafe { event.gaxis.value }; + let axis = unsafe { event.gaxis.axis }; + if !open_controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 { + let result = (true, axis as i32, axis_value / axis_value.saturating_abs()); + if result != last_controller_axis_result { + new_controller_axis[*value] = result; + last_controller_axis_result = result; key_set = true } } - sdl2::event::Event::ControllerAxisMotion { - axis, - value: axis_value, - .. - } => { - if !controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 { - let result = - (true, axis as i32, axis_value / axis_value.saturating_abs()); - if result != last_controller_axis_result { - new_controller_axis[*value] = result; - last_controller_axis_result = result; - key_set = true - } - } + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_BUTTON_DOWN) + { + if !open_joysticks.is_empty() { + new_joystick_buttons[*value] = + (true, i32::from(unsafe { event.jbutton.button })); + key_set = true } - sdl2::event::Event::JoyButtonDown { button_idx, .. } => { - if !joysticks.is_empty() { - new_joystick_buttons[*value] = (true, button_idx as u32); + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_HAT_MOTION) { + let state = unsafe { event.jhat.value }; + let hat = unsafe { event.jhat.hat }; + if !open_joysticks.is_empty() && state != sdl3_sys::joystick::SDL_HAT_CENTERED { + new_joystick_hat[*value] = (true, hat as i32, state); + key_set = true + } + } else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_AXIS_MOTION) + { + let axis_value = unsafe { event.jaxis.value }; + let axis = unsafe { event.jaxis.axis }; + if !open_joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 { + let result = (true, axis as i32, axis_value / axis_value.saturating_abs()); + if result != last_joystick_axis_result { + new_joystick_axis[*value] = result; + last_joystick_axis_result = result; key_set = true } } - sdl2::event::Event::JoyHatMotion { hat_idx, state, .. } => { - if !joysticks.is_empty() && state != sdl2::joystick::HatState::Centered { - new_joystick_hat[*value] = ( - true, - hat_idx as u32, - sdl2::joystick::HatState::to_raw(state), - ); - key_set = true - } - } - sdl2::event::Event::JoyAxisMotion { - axis_idx, - value: axis_value, - .. - } => { - if !joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 { - let result = ( - true, - axis_idx as u32, - axis_value / axis_value.saturating_abs(), - ); - if result != last_joystick_axis_result { - new_joystick_axis[*value] = result; - last_joystick_axis_result = result; - key_set = true - } - } - } - _ => {} } } } } + close_controllers(open_joysticks, open_controllers); + + unsafe { + sdl3_sys::render::SDL_DestroyRenderer(renderer); + sdl3_sys::video::SDL_DestroyWindow(window); + } + let new_profile = ui::config::InputProfile { keys: new_keys, controller_buttons: new_controller_buttons, @@ -562,45 +598,103 @@ pub fn get_default_profile() -> ui::config::InputProfile { let mut default_controller_buttons = [(false, 0); PROFILE_SIZE]; let mut default_controller_axis = [(false, 0, 0); PROFILE_SIZE]; let mut default_keys = [(false, 0); PROFILE_SIZE]; - default_keys[R_DPAD] = (true, sdl2::keyboard::Scancode::D as i32); - default_keys[L_DPAD] = (true, sdl2::keyboard::Scancode::A as i32); - default_keys[D_DPAD] = (true, sdl2::keyboard::Scancode::S as i32); - default_keys[U_DPAD] = (true, sdl2::keyboard::Scancode::W as i32); - default_keys[START_BUTTON] = (true, sdl2::keyboard::Scancode::Return as i32); - default_keys[Z_TRIG] = (true, sdl2::keyboard::Scancode::Z as i32); - default_keys[B_BUTTON] = (true, sdl2::keyboard::Scancode::LCtrl as i32); - default_keys[A_BUTTON] = (true, sdl2::keyboard::Scancode::LShift as i32); - default_keys[R_CBUTTON] = (true, sdl2::keyboard::Scancode::L as i32); - default_keys[L_CBUTTON] = (true, sdl2::keyboard::Scancode::J as i32); - default_keys[D_CBUTTON] = (true, sdl2::keyboard::Scancode::K as i32); - default_keys[U_CBUTTON] = (true, sdl2::keyboard::Scancode::I as i32); - default_keys[R_TRIG] = (true, sdl2::keyboard::Scancode::C as i32); - default_keys[L_TRIG] = (true, sdl2::keyboard::Scancode::X as i32); - default_keys[AXIS_LEFT] = (true, sdl2::keyboard::Scancode::Left as i32); - default_keys[AXIS_RIGHT] = (true, sdl2::keyboard::Scancode::Right as i32); - default_keys[AXIS_UP] = (true, sdl2::keyboard::Scancode::Up as i32); - default_keys[AXIS_DOWN] = (true, sdl2::keyboard::Scancode::Down as i32); - default_keys[CHANGE_PAK] = (true, sdl2::keyboard::Scancode::Comma as i32); + default_keys[R_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_D)); + default_keys[L_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_A)); + default_keys[D_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_S)); + default_keys[U_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_W)); + default_keys[START_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN)); + default_keys[Z_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z)); + default_keys[B_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL)); + default_keys[A_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT)); + default_keys[R_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_L)); + default_keys[L_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_J)); + default_keys[D_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_K)); + default_keys[U_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_I)); + default_keys[R_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_C)); + default_keys[L_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_X)); + default_keys[AXIS_LEFT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT)); + default_keys[AXIS_RIGHT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT)); + default_keys[AXIS_UP] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP)); + default_keys[AXIS_DOWN] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN)); + default_keys[CHANGE_PAK] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA)); - default_controller_buttons[R_DPAD] = (true, sdl2::controller::Button::DPadRight as i32); - default_controller_buttons[L_DPAD] = (true, sdl2::controller::Button::DPadLeft as i32); - default_controller_buttons[D_DPAD] = (true, sdl2::controller::Button::DPadDown as i32); - default_controller_buttons[U_DPAD] = (true, sdl2::controller::Button::DPadUp as i32); - default_controller_buttons[START_BUTTON] = (true, sdl2::controller::Button::Start as i32); - default_controller_axis[Z_TRIG] = (true, sdl2::controller::Axis::TriggerLeft as i32, 1); - default_controller_buttons[B_BUTTON] = (true, sdl2::controller::Button::X as i32); - default_controller_buttons[A_BUTTON] = (true, sdl2::controller::Button::A as i32); - default_controller_axis[R_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, 1); - default_controller_axis[L_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, -1); - default_controller_axis[D_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, 1); - default_controller_axis[U_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, -1); - default_controller_buttons[R_TRIG] = (true, sdl2::controller::Button::RightShoulder as i32); - default_controller_buttons[L_TRIG] = (true, sdl2::controller::Button::LeftShoulder as i32); - default_controller_axis[AXIS_LEFT] = (true, sdl2::controller::Axis::LeftX as i32, -1); - default_controller_axis[AXIS_RIGHT] = (true, sdl2::controller::Axis::LeftX as i32, 1); - default_controller_axis[AXIS_UP] = (true, sdl2::controller::Axis::LeftY as i32, -1); - default_controller_axis[AXIS_DOWN] = (true, sdl2::controller::Axis::LeftY as i32, 1); - default_controller_buttons[CHANGE_PAK] = (true, sdl2::controller::Button::Back as i32); + default_controller_buttons[R_DPAD] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT), + ); + default_controller_buttons[L_DPAD] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT), + ); + default_controller_buttons[D_DPAD] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN), + ); + default_controller_buttons[U_DPAD] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP), + ); + default_controller_buttons[START_BUTTON] = + (true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START)); + default_controller_axis[Z_TRIG] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER), + 1, + ); + default_controller_buttons[B_BUTTON] = + (true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST)); + default_controller_buttons[A_BUTTON] = + (true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH)); + default_controller_axis[R_CBUTTON] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX), + 1, + ); + default_controller_axis[L_CBUTTON] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX), + -1, + ); + default_controller_axis[D_CBUTTON] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY), + 1, + ); + default_controller_axis[U_CBUTTON] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY), + -1, + ); + default_controller_buttons[R_TRIG] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), + ); + default_controller_buttons[L_TRIG] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), + ); + default_controller_axis[AXIS_LEFT] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX), + -1, + ); + default_controller_axis[AXIS_RIGHT] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX), + 1, + ); + default_controller_axis[AXIS_UP] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY), + -1, + ); + default_controller_axis[AXIS_DOWN] = ( + true, + i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY), + 1, + ); + default_controller_buttons[CHANGE_PAK] = + (true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK)); ui::config::InputProfile { keys: default_keys, @@ -614,54 +708,69 @@ pub fn get_default_profile() -> ui::config::InputProfile { } pub fn init(ui: &mut ui::Ui) { - let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap(); - let controller_subsystem = ui.controller_subsystem.as_ref().unwrap(); + ui.keyboard_state = unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) }; + let mut taken = [false; 4]; for i in 0..4 { let controller_assignment = &ui.config.input.controller_assignment[i]; if controller_assignment.is_some() { - let mut joystick_index = u32::MAX; - let guid = controller_assignment.clone().unwrap(); - for i in 0..joystick_subsystem.num_joysticks().unwrap() { - if joystick_subsystem.device_guid(i).unwrap().to_string() == guid - && !taken[i as usize] - { - joystick_index = i; - taken[i as usize] = true; - break; + let mut joystick_id = 0; + let assigned_path = controller_assignment.as_ref().unwrap(); + + let mut joystick_count = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut joystick_count) }; + if !joysticks.is_null() { + for offset in 0..joystick_count as isize { + let path = unsafe { + std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID( + *(joysticks.offset(offset)), + )) + .to_string_lossy() + .to_string() + }; + if path == *assigned_path && !taken[offset as usize] { + joystick_id = unsafe { *(joysticks.offset(offset)) }; + taken[offset as usize] = true; + break; + } } + unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }; } - if joystick_index < u32::MAX { + if joystick_id != 0 { let profile_name = ui.config.input.input_profile_binding[i].clone(); let profile = ui.config.input.input_profiles.get(&profile_name).unwrap(); if !profile.dinput { - let controller_result = controller_subsystem.open(joystick_index); - if controller_result.is_ok() { - ui.controllers[i].game_controller = Some(controller_result.unwrap()); - if ui.controllers[i] - .game_controller - .as_ref() - .unwrap() - .has_rumble() - { - ui.controllers[i].rumble = true; - } - } - } - - if ui.controllers[i].game_controller.is_none() { - let joystick_result = joystick_subsystem.open(joystick_index); - if joystick_result.is_err() { - println!( - "could not connect joystick: {}", - joystick_result.err().unwrap() - ) + let gamepad = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) }; + if gamepad.is_null() { + println!("could not connect gamepad: {}", joystick_id) } else { - ui.controllers[i].joystick = Some(joystick_result.unwrap()); - if ui.controllers[i].joystick.as_ref().unwrap().has_rumble() { - ui.controllers[i].rumble = true; - } + ui.controllers[i].game_controller = gamepad; + let properties = + unsafe { sdl3_sys::gamepad::SDL_GetGamepadProperties(gamepad) }; + ui.controllers[i].rumble = unsafe { + sdl3_sys::properties::SDL_GetBooleanProperty( + properties, + sdl3_sys::gamepad::SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, + false, + ) + }; + } + } else { + let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) }; + if joystick.is_null() { + println!("could not connect joystick: {}", joystick_id) + } else { + ui.controllers[i].joystick = joystick; + let properties = + unsafe { sdl3_sys::joystick::SDL_GetJoystickProperties(joystick) }; + ui.controllers[i].rumble = unsafe { + sdl3_sys::properties::SDL_GetBooleanProperty( + properties, + sdl3_sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, + false, + ) + }; } } } else { @@ -670,3 +779,16 @@ pub fn init(ui: &mut ui::Ui) { } } } + +pub fn close(ui: &mut ui::Ui) { + for controller in ui.controllers.iter_mut() { + if !controller.joystick.is_null() { + unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) } + controller.joystick = std::ptr::null_mut(); + } + if !controller.game_controller.is_null() { + unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller.game_controller) } + controller.game_controller = std::ptr::null_mut(); + } + } +} diff --git a/src/ui/video.rs b/src/ui/video.rs index 0ca6e8c..dfa52e5 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -1,23 +1,29 @@ #![allow(non_snake_case)] include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs")); use crate::device; +use crate::ui; pub fn init(device: &mut device::Device, fullscreen: bool) { - let mut builder = device - .ui - .video_subsystem - .as_ref() - .unwrap() - .window("gopher64", 640, 480); + ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO); - builder.position_centered().vulkan(); + let title = std::ffi::CString::new("gopher64").unwrap(); + + let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN; if fullscreen { - builder.fullscreen_desktop(); + flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN; } else { - builder.resizable(); + flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE; + } + + device.ui.window = + unsafe { sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), 640, 480, flags) }; + if device.ui.window.is_null() { + panic!("Could not create window"); + } + if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.window) } { + panic!("Could not show window"); } - device.ui.window = Some(builder.build().unwrap()); let gfx_info = GFX_INFO { RDRAM: device.rdram.mem.as_mut_ptr(), @@ -36,7 +42,7 @@ pub fn init(device: &mut device::Device, fullscreen: bool) { unsafe { rdp_init( - device.ui.window.as_ref().unwrap().raw() as *mut std::ffi::c_void, + device.ui.window as *mut std::ffi::c_void, gfx_info, device.ui.config.video.upscale, device.ui.config.video.integer_scaling, @@ -45,9 +51,10 @@ pub fn init(device: &mut device::Device, fullscreen: bool) { } } -pub fn close() { +pub fn close(ui: &ui::Ui) { unsafe { rdp_close(); + sdl3_sys::video::SDL_DestroyWindow(ui.window); } } @@ -66,19 +73,23 @@ pub fn process_rdp_list() -> u64 { unsafe { rdp_process_commands() } } -pub fn draw_text( - text: &str, - canvas: &mut sdl2::render::Canvas, - font: &rusttype::Font, -) { +pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) { let text_size = 32; let scale = rusttype::Scale::uniform(text_size as f32); let v_metrics = font.v_metrics(scale); let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent); // Clear the canvas - canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0)); - canvas.clear(); + unsafe { + sdl3_sys::render::SDL_SetRenderDrawColor( + renderer, + 0, + 0, + 0, + sdl3_sys::pixels::SDL_ALPHA_OPAQUE, + ); + sdl3_sys::render::SDL_RenderClear(renderer); + }; for glyph in font.layout(text, scale, offset) { if let Some(bb) = glyph.pixel_bounding_box() { @@ -86,15 +97,23 @@ pub fn draw_text( let x = x as i32 + bb.min.x; let y = y as i32 + bb.min.y + (240 - text_size); if v > 0.5 { - canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255)); - canvas - .draw_point(sdl2::rect::Point::new(x, y)) - .expect("Error drawing pixel"); + unsafe { + sdl3_sys::render::SDL_SetRenderDrawColor( + renderer, + 255, + 255, + 255, + sdl3_sys::pixels::SDL_ALPHA_OPAQUE, + ); + sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32); + }; } }); } } // Present the canvas - canvas.present(); + if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } { + panic!("Could not present renderer"); + } }