diff --git a/Cargo.toml b/Cargo.toml index 346b116..0af0439 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,12 @@ panic = 'abort' opt-level = 3 [features] -default = ["scripting", "backend-sdl", "ogg-playback", "netplay", "exe"] +default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe"] ogg-playback = ["lewton"] backend-sdl = ["sdl2", "sdl2-sys"] #backend-sokol = ["sokol"] -backend-glutin = ["winit", "glutin"] +backend-glutin = ["winit", "glutin", "render-opengl"] +render-opengl = [] scripting = ["lua-ffi"] netplay = [] editor = [] @@ -45,7 +46,7 @@ chrono = "0.4" cpal = { git = "https://github.com/doukutsu-rs/cpal.git", rev = "e027550be0b93b7e2912c7de28a4944a7d04e070" } directories = "3" funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105 -glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "a34ee3c99b3c999b638ca2bae53cf96df2b94c04", optional = true } +glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "a34ee3c99b3c999b638ca2bae53cf96df2b94c04", optional = true, default_features = false, features = ["x11"] } imgui = "0.7.0" image = { version = "0.23", default-features = false, features = ["png", "bmp"] } itertools = "0.10" @@ -54,7 +55,6 @@ lewton = { version = "0.10.2", optional = true } libc = { version = "0.2", optional = true } log = "0.4" lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true } -lru = "0.6.0" num-derive = "0.3.2" num-traits = "0.2.12" paste = "1.0.0" @@ -70,7 +70,7 @@ strum_macros = "0.20" # remove and replace when drain_filter is in stable vec_mut_scan = "0.4" webbrowser = "0.5.5" -winit = { version = "0.24", optional = true } +winit = { version = "0.24", optional = true, default_features = false, features = ["x11"] } #[build-dependencies] #gl_generator = { version = "0.14.0", optional = true } diff --git a/src/framework/backend.rs b/src/framework/backend.rs index c0250a3..cc303c4 100644 --- a/src/framework/backend.rs +++ b/src/framework/backend.rs @@ -17,6 +17,8 @@ pub trait BackendEventLoop { } pub trait BackendRenderer { + fn renderer_name(&self) -> String; + fn clear(&mut self, color: Color); fn present(&mut self) -> GameResult; @@ -55,7 +57,7 @@ pub trait BackendTexture { pub fn init_backend() -> GameResult> { #[cfg(all(feature = "backend-glutin"))] { - return crate::framework::backend_opengl::GlutinBackend::new(); + return crate::framework::backend_glutin::GlutinBackend::new(); } #[cfg(feature = "backend-sokol")] diff --git a/src/framework/backend_glutin.rs b/src/framework/backend_glutin.rs new file mode 100644 index 0000000..c518047 --- /dev/null +++ b/src/framework/backend_glutin.rs @@ -0,0 +1,518 @@ +use std::cell::{RefCell, UnsafeCell}; +use std::ffi::c_void; +use std::mem; +use std::rc::Rc; +use std::sync::Arc; + +use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext}; +use glutin::event::{ElementState, Event, TouchPhase, VirtualKeyCode, WindowEvent}; +use glutin::event_loop::{ControlFlow, EventLoop}; +use glutin::window::WindowBuilder; +use imgui::{DrawCmdParams, DrawData, DrawIdx, DrawVert}; + +use crate::{Game, GAME_SUSPENDED}; +use crate::common::Rect; +use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; +use crate::framework::context::Context; +use crate::framework::error::GameResult; +use crate::framework::gl; +use crate::framework::keyboard::ScanCode; +use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; +use crate::input::touch_controls::TouchPoint; + +pub struct GlutinBackend; + +impl GlutinBackend { + pub fn new() -> GameResult> { + Ok(Box::new(GlutinBackend)) + } +} + +impl Backend for GlutinBackend { + fn create_event_loop(&self) -> GameResult> { + #[cfg(target_os = "android")] + loop { + match ndk_glue::native_window().as_ref() { + Some(_) => { + log::info!("NativeWindow Found: {:?}", ndk_glue::native_window()); + break; + } + None => (), + } + } + + Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) })) + } +} + +pub struct GlutinEventLoop { + refs: Rc>>>, +} + +impl GlutinEventLoop { + fn get_context(&self, event_loop: &EventLoop<()>) -> &mut WindowedContext { + let mut refs = unsafe { &mut *self.refs.get() }; + + if refs.is_none() { + let mut window = WindowBuilder::new(); + let windowed_context = ContextBuilder::new(); + let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGl, (3, 0))); + #[cfg(target_os = "android")] + let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0))); + + let windowed_context = windowed_context.with_gl_profile(GlProfile::Core) + .with_gl_debug_flag(false) + .with_pixel_format(24, 8) + .with_vsync(true); + + #[cfg(target_os = "windows")] + { + use glutin::platform::windows::WindowBuilderExtWindows; + window = window.with_drag_and_drop(false); + } + + window = window.with_title("doukutsu-rs"); + + let windowed_context = windowed_context.build_windowed(window, event_loop).unwrap(); + + let windowed_context = unsafe { windowed_context.make_current().unwrap() }; + + #[cfg(target_os = "android")] + if let Some(nwin) = ndk_glue::native_window().as_ref() { + unsafe { + windowed_context.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void); + } + } + + refs.replace(windowed_context); + } + + refs.as_mut().unwrap() + } +} + +#[cfg(target_os = "android")] +fn request_android_redraw() { + match ndk_glue::native_window().as_ref() { + Some(native_window) => { + let a_native_window: *mut ndk_sys::ANativeWindow = native_window.ptr().as_ptr(); + let a_native_activity: *mut ndk_sys::ANativeActivity = ndk_glue::native_activity().ptr().as_ptr(); + unsafe { + match (*(*a_native_activity).callbacks).onNativeWindowRedrawNeeded { + Some(callback) => callback(a_native_activity, a_native_window), + None => (), + }; + }; + } + None => (), + } +} + +#[cfg(target_os = "android")] +fn get_insets() -> GameResult<(f32, f32, f32, f32)> { + unsafe { + let vm_ptr = ndk_glue::native_activity().vm(); + let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?; + let vm_env = vm.attach_current_thread()?; + + //let class = vm_env.find_class("io/github/doukutsu_rs/MainActivity")?; + let class = vm_env.new_global_ref(ndk_glue::native_activity().activity())?; + let field = vm_env.get_field(class.as_obj(), "displayInsets", "[I")?.to_jni().l as jni::sys::jintArray; + + let mut elements = [0; 4]; + vm_env.get_int_array_region(field, 0, &mut elements)?; + + Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32)) + } +} + +impl BackendEventLoop for GlutinEventLoop { + fn run(&mut self, game: &mut Game, ctx: &mut Context) { + let event_loop = EventLoop::new(); + let state_ref = unsafe { &mut *game.state.get() }; + let window: &'static mut WindowedContext = + unsafe { std::mem::transmute(self.get_context(&event_loop)) }; + + { + let size = window.window().inner_size(); + ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32); + state_ref.handle_resize(ctx).unwrap(); + } + + // it won't ever return + let (game, ctx): (&'static mut Game, &'static mut Context) = + unsafe { (std::mem::transmute(game), std::mem::transmute(ctx)) }; + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event: WindowEvent::CloseRequested, window_id } + if window_id == window.window().id() => + { + state_ref.shutdown(); + } + Event::Resumed => { + println!("resumed!"); + { + let mut mutex = GAME_SUSPENDED.lock().unwrap(); + *mutex = false; + } + + #[cfg(target_os = "android")] + if let Some(nwin) = ndk_glue::native_window().as_ref() { + state_ref.graphics_reset(); + unsafe { + window.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void); + request_android_redraw(); + } + } + } + Event::Suspended => { + println!("suspended!"); + { + let mut mutex = GAME_SUSPENDED.lock().unwrap(); + *mutex = true; + } + + #[cfg(target_os = "android")] + unsafe { + window.surface_destroyed(); + } + } + Event::WindowEvent { event: WindowEvent::Resized(size), window_id } + if window_id == window.window().id() => + { + if let Some(renderer) = ctx.renderer.as_ref() { + if let Ok(imgui) = renderer.imgui() { + imgui.io_mut().display_size = [size.width as f32, size.height as f32]; + } + + ctx.screen_size = (size.width as f32, size.height as f32); + state_ref.handle_resize(ctx).unwrap(); + } + } + Event::WindowEvent { event: WindowEvent::Touch(touch), window_id } + if window_id == window.window().id() => + { + let mut controls = &mut state_ref.touch_controls; + let scale = state_ref.scale as f64; + + match touch.phase { + TouchPhase::Started | TouchPhase::Moved => { + if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) { + point.last_position = point.position; + point.position = (touch.location.x / scale, touch.location.y / scale); + } else { + controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1); + + let point = TouchPoint { + id: touch.id, + touch_id: controls.touch_id_counter, + position: (touch.location.x / scale, touch.location.y / scale), + last_position: (0.0, 0.0), + }; + controls.points.push(point); + + if touch.phase == TouchPhase::Started { + controls.clicks.push(point); + } + } + } + TouchPhase::Ended | TouchPhase::Cancelled => { + controls.points.retain(|p| p.id != touch.id); + controls.clicks.retain(|p| p.id != touch.id); + } + } + } + Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, window_id } + if window_id == window.window().id() => + { + if let Some(keycode) = input.virtual_keycode { + if let Some(drs_scan) = conv_keycode(keycode) { + let key_state = match input.state { + ElementState::Pressed => true, + ElementState::Released => false, + }; + + ctx.keyboard_context.set_key(drs_scan, key_state); + } + } + } + Event::RedrawRequested(id) if id == window.window().id() => { + { + let mutex = GAME_SUSPENDED.lock().unwrap(); + if *mutex { + return; + } + } + + #[cfg(not(target_os = "android"))] + { + if let Err(err) = game.draw(ctx) { + log::error!("Failed to draw frame: {}", err); + } + + window.window().request_redraw(); + } + + #[cfg(target_os = "android")] + request_android_redraw(); + } + Event::MainEventsCleared => { + if state_ref.shutdown { + log::info!("Shutting down..."); + *control_flow = ControlFlow::Exit; + return; + } + + { + let mutex = GAME_SUSPENDED.lock().unwrap(); + if *mutex { + return; + } + } + + game.update(ctx).unwrap(); + + #[cfg(target_os = "android")] + { + match get_insets() { + Ok(insets) => { + ctx.screen_insets = insets; + } + Err(e) => { + log::error!("Failed to update insets: {}", e); + } + } + + if let Err(err) = game.draw(ctx) { + log::error!("Failed to draw frame: {}", err); + } + } + + if state_ref.next_scene.is_some() { + mem::swap(&mut game.scene, &mut state_ref.next_scene); + state_ref.next_scene = None; + game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap(); + game.loops = 0; + state_ref.frame_time = 0.0; + } + } + _ => (), + } + }); + } + + fn new_renderer(&self) -> GameResult> { + let mut imgui = imgui::Context::create(); + imgui.io_mut().display_size = [640.0, 480.0]; + + let refs = self.refs.clone(); + let user_data = Rc::into_raw(refs) as *mut c_void; + + unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void { + let refs = Rc::from_raw(*user_data as *mut UnsafeCell>>); + + let result = { + let refs = &mut *refs.get(); + + if let Some(refs) = refs { + refs.get_proc_address(name) + } else { + std::ptr::null() + } + }; + + *user_data = Rc::into_raw(refs) as *mut c_void; + + result + } + + unsafe fn swap_buffers(user_data: &mut *mut c_void) { + let refs = Rc::from_raw(*user_data as *mut UnsafeCell>>); + + { + let refs = &mut *refs.get(); + + if let Some(refs) = refs { + refs.swap_buffers(); + } + } + + *user_data = Rc::into_raw(refs) as *mut c_void; + } + + let gl_context = GLContext { get_proc_address, swap_buffers, user_data }; + + Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui)))) + } +} + +fn conv_keycode(code: VirtualKeyCode) -> Option { + match code { + VirtualKeyCode::Key1 => Some(ScanCode::Key1), + VirtualKeyCode::Key2 => Some(ScanCode::Key2), + VirtualKeyCode::Key3 => Some(ScanCode::Key3), + VirtualKeyCode::Key4 => Some(ScanCode::Key4), + VirtualKeyCode::Key5 => Some(ScanCode::Key5), + VirtualKeyCode::Key6 => Some(ScanCode::Key6), + VirtualKeyCode::Key7 => Some(ScanCode::Key7), + VirtualKeyCode::Key8 => Some(ScanCode::Key8), + VirtualKeyCode::Key9 => Some(ScanCode::Key9), + VirtualKeyCode::Key0 => Some(ScanCode::Key0), + VirtualKeyCode::A => Some(ScanCode::A), + VirtualKeyCode::B => Some(ScanCode::B), + VirtualKeyCode::C => Some(ScanCode::C), + VirtualKeyCode::D => Some(ScanCode::D), + VirtualKeyCode::E => Some(ScanCode::E), + VirtualKeyCode::F => Some(ScanCode::F), + VirtualKeyCode::G => Some(ScanCode::G), + VirtualKeyCode::H => Some(ScanCode::H), + VirtualKeyCode::I => Some(ScanCode::I), + VirtualKeyCode::J => Some(ScanCode::J), + VirtualKeyCode::K => Some(ScanCode::K), + VirtualKeyCode::L => Some(ScanCode::L), + VirtualKeyCode::M => Some(ScanCode::M), + VirtualKeyCode::N => Some(ScanCode::N), + VirtualKeyCode::O => Some(ScanCode::O), + VirtualKeyCode::P => Some(ScanCode::P), + VirtualKeyCode::Q => Some(ScanCode::Q), + VirtualKeyCode::R => Some(ScanCode::R), + VirtualKeyCode::S => Some(ScanCode::S), + VirtualKeyCode::T => Some(ScanCode::T), + VirtualKeyCode::U => Some(ScanCode::U), + VirtualKeyCode::V => Some(ScanCode::V), + VirtualKeyCode::W => Some(ScanCode::W), + VirtualKeyCode::X => Some(ScanCode::X), + VirtualKeyCode::Y => Some(ScanCode::Y), + VirtualKeyCode::Z => Some(ScanCode::Z), + VirtualKeyCode::Escape => Some(ScanCode::Escape), + VirtualKeyCode::F1 => Some(ScanCode::F1), + VirtualKeyCode::F2 => Some(ScanCode::F2), + VirtualKeyCode::F3 => Some(ScanCode::F3), + VirtualKeyCode::F4 => Some(ScanCode::F4), + VirtualKeyCode::F5 => Some(ScanCode::F5), + VirtualKeyCode::F6 => Some(ScanCode::F6), + VirtualKeyCode::F7 => Some(ScanCode::F7), + VirtualKeyCode::F8 => Some(ScanCode::F8), + VirtualKeyCode::F9 => Some(ScanCode::F9), + VirtualKeyCode::F10 => Some(ScanCode::F10), + VirtualKeyCode::F11 => Some(ScanCode::F11), + VirtualKeyCode::F12 => Some(ScanCode::F12), + VirtualKeyCode::F13 => Some(ScanCode::F13), + VirtualKeyCode::F14 => Some(ScanCode::F14), + VirtualKeyCode::F15 => Some(ScanCode::F15), + VirtualKeyCode::F16 => Some(ScanCode::F16), + VirtualKeyCode::F17 => Some(ScanCode::F17), + VirtualKeyCode::F18 => Some(ScanCode::F18), + VirtualKeyCode::F19 => Some(ScanCode::F19), + VirtualKeyCode::F20 => Some(ScanCode::F20), + VirtualKeyCode::F21 => Some(ScanCode::F21), + VirtualKeyCode::F22 => Some(ScanCode::F22), + VirtualKeyCode::F23 => Some(ScanCode::F23), + VirtualKeyCode::F24 => Some(ScanCode::F24), + VirtualKeyCode::Snapshot => Some(ScanCode::Snapshot), + VirtualKeyCode::Scroll => Some(ScanCode::Scroll), + VirtualKeyCode::Pause => Some(ScanCode::Pause), + VirtualKeyCode::Insert => Some(ScanCode::Insert), + VirtualKeyCode::Home => Some(ScanCode::Home), + VirtualKeyCode::Delete => Some(ScanCode::Delete), + VirtualKeyCode::End => Some(ScanCode::End), + VirtualKeyCode::PageDown => Some(ScanCode::PageDown), + VirtualKeyCode::PageUp => Some(ScanCode::PageUp), + VirtualKeyCode::Left => Some(ScanCode::Left), + VirtualKeyCode::Up => Some(ScanCode::Up), + VirtualKeyCode::Right => Some(ScanCode::Right), + VirtualKeyCode::Down => Some(ScanCode::Down), + VirtualKeyCode::Back => Some(ScanCode::Back), + VirtualKeyCode::Return => Some(ScanCode::Return), + VirtualKeyCode::Space => Some(ScanCode::Space), + VirtualKeyCode::Compose => Some(ScanCode::Compose), + VirtualKeyCode::Caret => Some(ScanCode::Caret), + VirtualKeyCode::Numlock => Some(ScanCode::Numlock), + VirtualKeyCode::Numpad0 => Some(ScanCode::Numpad0), + VirtualKeyCode::Numpad1 => Some(ScanCode::Numpad1), + VirtualKeyCode::Numpad2 => Some(ScanCode::Numpad2), + VirtualKeyCode::Numpad3 => Some(ScanCode::Numpad3), + VirtualKeyCode::Numpad4 => Some(ScanCode::Numpad4), + VirtualKeyCode::Numpad5 => Some(ScanCode::Numpad5), + VirtualKeyCode::Numpad6 => Some(ScanCode::Numpad6), + VirtualKeyCode::Numpad7 => Some(ScanCode::Numpad7), + VirtualKeyCode::Numpad8 => Some(ScanCode::Numpad8), + VirtualKeyCode::Numpad9 => Some(ScanCode::Numpad9), + VirtualKeyCode::NumpadAdd => Some(ScanCode::NumpadAdd), + VirtualKeyCode::NumpadDivide => Some(ScanCode::NumpadDivide), + VirtualKeyCode::NumpadDecimal => Some(ScanCode::NumpadDecimal), + VirtualKeyCode::NumpadComma => Some(ScanCode::NumpadComma), + VirtualKeyCode::NumpadEnter => Some(ScanCode::NumpadEnter), + VirtualKeyCode::NumpadEquals => Some(ScanCode::NumpadEquals), + VirtualKeyCode::NumpadMultiply => Some(ScanCode::NumpadMultiply), + VirtualKeyCode::NumpadSubtract => Some(ScanCode::NumpadSubtract), + VirtualKeyCode::AbntC1 => Some(ScanCode::AbntC1), + VirtualKeyCode::AbntC2 => Some(ScanCode::AbntC2), + VirtualKeyCode::Apostrophe => Some(ScanCode::Apostrophe), + VirtualKeyCode::Apps => Some(ScanCode::Apps), + VirtualKeyCode::Asterisk => Some(ScanCode::Asterisk), + VirtualKeyCode::At => Some(ScanCode::At), + VirtualKeyCode::Ax => Some(ScanCode::Ax), + VirtualKeyCode::Backslash => Some(ScanCode::Backslash), + VirtualKeyCode::Calculator => Some(ScanCode::Calculator), + VirtualKeyCode::Capital => Some(ScanCode::Capital), + VirtualKeyCode::Colon => Some(ScanCode::Colon), + VirtualKeyCode::Comma => Some(ScanCode::Comma), + VirtualKeyCode::Convert => Some(ScanCode::Convert), + VirtualKeyCode::Equals => Some(ScanCode::Equals), + VirtualKeyCode::Grave => Some(ScanCode::Grave), + VirtualKeyCode::Kana => Some(ScanCode::Kana), + VirtualKeyCode::Kanji => Some(ScanCode::Kanji), + VirtualKeyCode::LAlt => Some(ScanCode::LAlt), + VirtualKeyCode::LBracket => Some(ScanCode::LBracket), + VirtualKeyCode::LControl => Some(ScanCode::LControl), + VirtualKeyCode::LShift => Some(ScanCode::LShift), + VirtualKeyCode::LWin => Some(ScanCode::LWin), + VirtualKeyCode::Mail => Some(ScanCode::Mail), + VirtualKeyCode::MediaSelect => Some(ScanCode::MediaSelect), + VirtualKeyCode::MediaStop => Some(ScanCode::MediaStop), + VirtualKeyCode::Minus => Some(ScanCode::Minus), + VirtualKeyCode::Mute => Some(ScanCode::Mute), + VirtualKeyCode::MyComputer => Some(ScanCode::MyComputer), + VirtualKeyCode::NavigateForward => Some(ScanCode::NavigateForward), + VirtualKeyCode::NavigateBackward => Some(ScanCode::NavigateBackward), + VirtualKeyCode::NextTrack => Some(ScanCode::NextTrack), + VirtualKeyCode::NoConvert => Some(ScanCode::NoConvert), + VirtualKeyCode::OEM102 => Some(ScanCode::OEM102), + VirtualKeyCode::Period => Some(ScanCode::Period), + VirtualKeyCode::PlayPause => Some(ScanCode::PlayPause), + VirtualKeyCode::Plus => Some(ScanCode::Plus), + VirtualKeyCode::Power => Some(ScanCode::Power), + VirtualKeyCode::PrevTrack => Some(ScanCode::PrevTrack), + VirtualKeyCode::RAlt => Some(ScanCode::RAlt), + VirtualKeyCode::RBracket => Some(ScanCode::RBracket), + VirtualKeyCode::RControl => Some(ScanCode::RControl), + VirtualKeyCode::RShift => Some(ScanCode::RShift), + VirtualKeyCode::RWin => Some(ScanCode::RWin), + VirtualKeyCode::Semicolon => Some(ScanCode::Semicolon), + VirtualKeyCode::Slash => Some(ScanCode::Slash), + VirtualKeyCode::Sleep => Some(ScanCode::Sleep), + VirtualKeyCode::Stop => Some(ScanCode::Stop), + VirtualKeyCode::Sysrq => Some(ScanCode::Sysrq), + VirtualKeyCode::Tab => Some(ScanCode::Tab), + VirtualKeyCode::Underline => Some(ScanCode::Underline), + VirtualKeyCode::Unlabeled => Some(ScanCode::Unlabeled), + VirtualKeyCode::VolumeDown => Some(ScanCode::VolumeDown), + VirtualKeyCode::VolumeUp => Some(ScanCode::VolumeUp), + VirtualKeyCode::Wake => Some(ScanCode::Wake), + VirtualKeyCode::WebBack => Some(ScanCode::WebBack), + VirtualKeyCode::WebFavorites => Some(ScanCode::WebFavorites), + VirtualKeyCode::WebForward => Some(ScanCode::WebForward), + VirtualKeyCode::WebHome => Some(ScanCode::WebHome), + VirtualKeyCode::WebRefresh => Some(ScanCode::WebRefresh), + VirtualKeyCode::WebSearch => Some(ScanCode::WebSearch), + VirtualKeyCode::WebStop => Some(ScanCode::WebStop), + VirtualKeyCode::Yen => Some(ScanCode::Yen), + VirtualKeyCode::Copy => Some(ScanCode::Copy), + VirtualKeyCode::Paste => Some(ScanCode::Paste), + VirtualKeyCode::Cut => Some(ScanCode::Cut), + } +} diff --git a/src/framework/backend_null.rs b/src/framework/backend_null.rs index 6a24cf2..ad11614 100644 --- a/src/framework/backend_null.rs +++ b/src/framework/backend_null.rs @@ -82,6 +82,10 @@ impl BackendTexture for NullTexture { pub struct NullRenderer(RefCell); impl BackendRenderer for NullRenderer { + fn renderer_name(&self) -> String { + "Null".to_owned() + } + fn clear(&mut self, _color: Color) { } diff --git a/src/framework/backend_sdl2.rs b/src/framework/backend_sdl2.rs index 93f82ab..c194bbd 100644 --- a/src/framework/backend_sdl2.rs +++ b/src/framework/backend_sdl2.rs @@ -1,6 +1,7 @@ use core::mem; -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; use std::collections::HashMap; +use std::ffi::c_void; use std::rc::Rc; use imgui::internal::RawWrapper; @@ -11,14 +12,15 @@ use sdl2::mouse::{Cursor, SystemCursor}; use sdl2::pixels::PixelFormatEnum; use sdl2::render::{Texture, TextureCreator, WindowCanvas}; use sdl2::video::WindowContext; -use sdl2::{keyboard, pixels, EventPump, Sdl}; +use sdl2::{keyboard, pixels, EventPump, Sdl, VideoSubsystem}; use crate::common::{Color, Rect}; use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; use crate::framework::context::Context; use crate::framework::error::{GameError, GameResult}; -use crate::framework::graphics::{BlendMode}; +use crate::framework::graphics::BlendMode; use crate::framework::keyboard::ScanCode; +use crate::framework::render_opengl::{GLContext, OpenGLRenderer}; use crate::framework::ui::init_imgui; use crate::Game; use crate::GAME_SUSPENDED; @@ -46,29 +48,49 @@ impl Backend for SDL2Backend { struct SDL2EventLoop { event_pump: EventPump, refs: Rc>, + opengl_available: RefCell, } struct SDL2Context { + video: VideoSubsystem, canvas: WindowCanvas, texture_creator: TextureCreator, + gl_context: Option, blend_mode: sdl2::render::BlendMode, } impl SDL2EventLoop { pub fn new(sdl: &Sdl) -> GameResult> { - sdl2::hint::set("SDL_HINT_RENDER_DRIVER", "opengles2"); - let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?; let video = sdl.video().map_err(|e| GameError::WindowError(e))?; - let window = - video.window("Cave Story (doukutsu-rs)", 640, 480).position_centered().resizable().build().map_err(|e| GameError::WindowError(e.to_string()))?; + let window = video + .window("Cave Story (doukutsu-rs)", 640, 480) + .position_centered() + .resizable() + .build() + .map_err(|e| GameError::WindowError(e.to_string()))?; - let canvas = window.into_canvas().accelerated().present_vsync().build().map_err(|e| GameError::RenderError(e.to_string()))?; + let canvas = window + .into_canvas() + .accelerated() + .present_vsync() + .build() + .map_err(|e| GameError::RenderError(e.to_string()))?; let texture_creator = canvas.texture_creator(); - let event_loop = - SDL2EventLoop { event_pump, refs: Rc::new(RefCell::new(SDL2Context { canvas, texture_creator, blend_mode: sdl2::render::BlendMode::Blend })) }; + let opengl_available = if let Ok(v) = std::env::var("CAVESTORY_NO_OPENGL") { v != "1" } else { true }; + let event_loop = SDL2EventLoop { + event_pump, + refs: Rc::new(RefCell::new(SDL2Context { + video, + canvas, + texture_creator, + gl_context: None, + blend_mode: sdl2::render::BlendMode::Blend, + })), + opengl_available: RefCell::new(opengl_available), + }; Ok(Box::new(event_loop)) } @@ -169,12 +191,71 @@ impl BackendEventLoop for SDL2EventLoop { state.frame_time = 0.0; } - imgui_sdl2.prepare_frame(imgui.io_mut(), self.refs.borrow().canvas.window(), &self.event_pump.mouse_state()); + imgui_sdl2.prepare_frame( + imgui.io_mut(), + self.refs.borrow().canvas.window(), + &self.event_pump.mouse_state(), + ); game.draw(ctx).unwrap(); } } fn new_renderer(&self) -> GameResult> { + #[cfg(feature = "render-opengl")] + { + let mut refs = self.refs.borrow_mut(); + match refs.canvas.window().gl_create_context() { + Ok(gl_ctx) => { + refs.canvas.window().gl_make_current(&gl_ctx); + refs.gl_context = Some(gl_ctx); + } + Err(err) => { + *self.opengl_available.borrow_mut() = false; + log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err); + } + } + } + + #[cfg(feature = "render-opengl")] + if *self.opengl_available.borrow() { + let mut imgui = init_imgui()?; + + let refs = self.refs.clone(); + + let user_data = Rc::into_raw(refs) as *mut c_void; + + unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void { + let refs = Rc::from_raw(*user_data as *mut RefCell); + + let result = { + let refs = &mut *refs.as_ptr(); + refs.video.gl_get_proc_address(name) as *const _ + }; + + log::info!("gl proc {} -> {:?}", name, result); + + *user_data = Rc::into_raw(refs) as *mut c_void; + + result + } + + unsafe fn swap_buffers(user_data: &mut *mut c_void) { + let refs = Rc::from_raw(*user_data as *mut RefCell); + + { + let refs = &mut *refs.as_ptr(); + + refs.canvas.window().gl_swap_window(); + } + + *user_data = Rc::into_raw(refs) as *mut c_void; + } + + let gl_context = GLContext { get_proc_address, swap_buffers, user_data }; + + return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui)))); + } + SDL2Renderer::new(self.refs.clone()) } } @@ -224,7 +305,13 @@ impl SDL2Renderer { imgui_textures.insert( id, - SDL2Texture { refs: refs.clone(), texture: Some(texture), width: font_tex.width as u16, height: font_tex.height as u16, commands: vec![] }, + SDL2Texture { + refs: refs.clone(), + texture: Some(texture), + width: font_tex.width as u16, + height: font_tex.height as u16, + commands: vec![], + }, ); } @@ -233,7 +320,12 @@ impl SDL2Renderer { ImguiSdl2::new(&mut imgui, refs.canvas.window()) }; - Ok(Box::new(SDL2Renderer { refs, imgui: Rc::new(RefCell::new(imgui)), imgui_event: Rc::new(RefCell::new(imgui_sdl2)), imgui_textures })) + Ok(Box::new(SDL2Renderer { + refs, + imgui: Rc::new(RefCell::new(imgui)), + imgui_event: Rc::new(RefCell::new(imgui_sdl2)), + imgui_textures, + })) } } @@ -242,7 +334,10 @@ fn to_sdl(color: Color) -> pixels::Color { pixels::Color::RGBA(r, g, b, a) } -unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *mut sdl2::sys::SDL_Texture) -> GameResult { +unsafe fn set_raw_target( + renderer: *mut sdl2::sys::SDL_Renderer, + raw_texture: *mut sdl2::sys::SDL_Texture, +) -> GameResult { if sdl2::sys::SDL_SetRenderTarget(renderer, raw_texture) == 0 { Ok(()) } else { @@ -271,6 +366,10 @@ fn max3(x: f32, y: f32, z: f32) -> f32 { } impl BackendRenderer for SDL2Renderer { + fn renderer_name(&self) -> String { + "SDL2_Renderer".to_owned() + } + fn clear(&mut self, color: Color) { let mut refs = self.refs.borrow_mut(); @@ -366,7 +465,12 @@ impl BackendRenderer for SDL2Renderer { refs.canvas.set_draw_color(pixels::Color::RGBA(r, g, b, a)); refs.canvas - .fill_rect(sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, rect.height() as u32)) + .fill_rect(sdl2::rect::Rect::new( + rect.left as i32, + rect.top as i32, + rect.width() as u32, + rect.height() as u32, + )) .map_err(|e| GameError::RenderError(e.to_string()))?; Ok(()) @@ -383,15 +487,30 @@ impl BackendRenderer for SDL2Renderer { 0 => {} // no-op 1 => { refs.canvas - .draw_rect(sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, rect.height() as u32)) + .draw_rect(sdl2::rect::Rect::new( + rect.left as i32, + rect.top as i32, + rect.width() as u32, + rect.height() as u32, + )) .map_err(|e| GameError::RenderError(e.to_string()))?; } _ => { let rects = [ sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, line_width as u32), - sdl2::rect::Rect::new(rect.left as i32, rect.bottom as i32 - line_width as i32, rect.width() as u32, line_width as u32), + sdl2::rect::Rect::new( + rect.left as i32, + rect.bottom as i32 - line_width as i32, + rect.width() as u32, + line_width as u32, + ), sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, line_width as u32, rect.height() as u32), - sdl2::rect::Rect::new(rect.right as i32 - line_width as i32, rect.top as i32, line_width as u32, rect.height() as u32), + sdl2::rect::Rect::new( + rect.right as i32 - line_width as i32, + rect.top as i32, + line_width as u32, + rect.height() as u32, + ), ]; refs.canvas.fill_rects(&rects).map_err(|e| GameError::RenderError(e.to_string()))?; @@ -433,9 +552,12 @@ impl BackendRenderer for SDL2Renderer { continue; } - let v1 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize]; - let v2 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize]; - let v3 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize]; + let v1 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize]; + let v2 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize]; + let v3 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize]; vert_x[0] = (v1.pos[0] - 0.5) as i16; vert_y[0] = (v1.pos[1] - 0.5) as i16; @@ -446,9 +568,12 @@ impl BackendRenderer for SDL2Renderer { #[allow(clippy::float_cmp)] if i < count - 3 { - let v4 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize]; - let v5 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize]; - let v6 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize]; + let v4 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize]; + let v5 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize]; + let v6 = draw_list.vtx_buffer() + [cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize]; min[0] = min3(v1.pos[0], v2.pos[0], v3.pos[0]); min[1] = min3(v1.pos[1], v2.pos[1], v3.pos[1]); @@ -484,13 +609,20 @@ impl BackendRenderer for SDL2Renderer { ((tex_pos[2] - tex_pos[0]) * surf.width as f32) as u32, ((tex_pos[3] - tex_pos[1]) * surf.height as f32) as u32, ); - let dest = sdl2::rect::Rect::new(min[0] as i32, min[1] as i32, (max[0] - min[0]) as u32, (max[1] - min[1]) as u32); + let dest = sdl2::rect::Rect::new( + min[0] as i32, + min[1] as i32, + (max[0] - min[0]) as u32, + (max[1] - min[1]) as u32, + ); let tex = surf.texture.as_mut().unwrap(); tex.set_color_mod(v1.col[0], v1.col[1], v1.col[2]); tex.set_alpha_mod(v1.col[3]); - refs.canvas.copy(tex, src, dest).map_err(|e| GameError::RenderError(e.to_string()))?; + refs.canvas + .copy(tex, src, dest) + .map_err(|e| GameError::RenderError(e.to_string()))?; } else { /*sdl2::sys::gfx::primitives::filledPolygonRGBA( refs.canvas.raw(), @@ -842,7 +974,9 @@ impl ImguiSdl2 { pub fn ignore_event(&self, event: &Event) -> bool { match *event { - Event::KeyDown { .. } | Event::KeyUp { .. } | Event::TextEditing { .. } | Event::TextInput { .. } => self.ignore_keyboard, + Event::KeyDown { .. } | Event::KeyUp { .. } | Event::TextEditing { .. } | Event::TextInput { .. } => { + self.ignore_keyboard + } Event::MouseMotion { .. } | Event::MouseButtonDown { .. } | Event::MouseButtonUp { .. } @@ -910,7 +1044,12 @@ impl ImguiSdl2 { } } - pub fn prepare_frame(&mut self, io: &mut imgui::Io, window: &sdl2::video::Window, mouse_state: &sdl2::mouse::MouseState) { + pub fn prepare_frame( + &mut self, + io: &mut imgui::Io, + window: &sdl2::video::Window, + mouse_state: &sdl2::mouse::MouseState, + ) { let mouse_util = window.subsystem().sdl().mouse(); let (win_w, win_h) = window.size(); diff --git a/src/framework/backend_sokol.rs b/src/framework/backend_sokol.rs deleted file mode 100644 index 3590c16..0000000 --- a/src/framework/backend_sokol.rs +++ /dev/null @@ -1,358 +0,0 @@ -use std::cell::RefCell; -use std::mem; -use std::ops::DerefMut; -use std::time::Duration; - -use imgui::{DrawData, Ui}; -use ndk::input_queue::InputQueue; -use sokol::app::{SApp, SAppDesc, SAppEvent, SAppEventType, SAppKeycode}; -use sokol::gfx::{sg_isvalid, sg_query_backend, sg_shutdown}; - -use crate::common::{Color, Rect}; -use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; -use crate::framework::context::Context; -use crate::framework::error::GameResult; -use crate::framework::graphics::BlendMode; -use crate::framework::keyboard::ScanCode; -use crate::Game; - -pub struct SokolBackend; - -impl SokolBackend { - pub fn new() -> GameResult> { - Ok(Box::new(SokolBackend)) - } -} - -impl Backend for SokolBackend { - fn create_event_loop(&self) -> GameResult> { - Ok(Box::new(SokolEventLoop)) - } -} - -pub struct SokolEventLoop; - -#[cfg(target_os = "android")] -extern "C" { - fn sapp_android_on_create( - activity: *mut ndk_sys::ANativeActivity, - window: *mut ndk_sys::ANativeWindow, - input_queue: *mut ndk_sys::AInputQueue, - ); -} - -impl BackendEventLoop for SokolEventLoop { - fn run(&mut self, game: &mut Game, ctx: &mut Context) { - #[cfg(target_os = "android")] - unsafe { - let activity = ndk_glue::native_activity().ptr().as_ptr(); - let window = match ndk_glue::native_window().as_ref() { - None => std::ptr::null_mut(), - Some(p) => p.ptr().as_ptr(), - }; - let input_queue = match ndk_glue::input_queue().as_ref() { - None => std::ptr::null_mut(), - Some(p) => p.ptr().as_ptr(), - }; - - println!("activity = {:?} window = {:?} input_queue = {:?}", activity, window, input_queue); - - sapp_android_on_create(activity, window, input_queue); - } - - struct Callbacks<'a, 'b> { - ctx: &'a mut Context, - game: &'b mut Game, - }; - - impl<'a, 'b> SApp for Callbacks<'a, 'b> { - fn sapp_init(&mut self) { - let state_ref = unsafe { &mut *self.game.state.get() }; - - self.ctx.screen_size = (640.0, 480.0); - state_ref.handle_resize(self.ctx).unwrap(); - } - - fn sapp_frame(&mut self) { - let state_ref = unsafe { &mut *self.game.state.get() }; - - self.game.update(self.ctx).unwrap(); - - // todo: not really supported on iOS/consoles - if state_ref.shutdown { - log::info!("Shutting down..."); - std::process::exit(0); - return; - } - - if state_ref.next_scene.is_some() { - mem::swap(&mut self.game.scene, &mut state_ref.next_scene); - state_ref.next_scene = None; - self.game.scene.as_mut().unwrap().init(state_ref, self.ctx).unwrap(); - self.game.loops = 0; - state_ref.frame_time = 0.0; - } - - self.game.draw(self.ctx).unwrap(); - } - - fn sapp_cleanup(&mut self) { - if sg_isvalid() { - sg_shutdown(); - } - } - - fn sapp_event(&mut self, event: SAppEvent) { - let state_ref = unsafe { &mut *self.game.state.get() }; - println!("event: {:?}", event.event_type); - - match event.event_type { - SAppEventType::Invalid => {} - SAppEventType::KeyDown => { - if let Some(drs_scan) = conv_scancode(event.key_code) { - state_ref.process_debug_keys(drs_scan); - self.ctx.keyboard_context.set_key(drs_scan, true); - } - } - SAppEventType::KeyUp => { - if let Some(drs_scan) = conv_scancode(event.key_code) { - self.ctx.keyboard_context.set_key(drs_scan, false); - } - } - SAppEventType::Char => {} - SAppEventType::MouseDown => {} - SAppEventType::MouseUp => {} - SAppEventType::MouseScroll => {} - SAppEventType::MouseMove => {} - SAppEventType::MouseEnter => {} - SAppEventType::MouseLeave => {} - SAppEventType::TouchesBegan => {} - SAppEventType::TouchesMoved => {} - SAppEventType::TouchesEnded => {} - SAppEventType::TouchesCancelled => {} - SAppEventType::Resized => {} - SAppEventType::Iconified => {} - SAppEventType::Restored => {} - SAppEventType::Suspended => {} - SAppEventType::Resumed => {} - SAppEventType::UpdateCursor => {} - SAppEventType::QuitRequested => { - state_ref.shutdown(); - } - } - } - } - - sokol::app::sapp_run( - Callbacks { ctx: unsafe { std::mem::transmute(ctx) }, game: unsafe { std::mem::transmute(game) } }, - SAppDesc { - width: 640, - height: 480, - window_title: "doukutsu-rs".to_string(), - ios_keyboard_resizes_canvas: false, - ..Default::default() - }, - ); - - loop { - std::thread::sleep(Duration::from_millis(10)) - } - } - - fn new_renderer(&self) -> GameResult> { - let mut imgui = imgui::Context::create(); - imgui.io_mut().display_size = [640.0, 480.0]; - imgui.fonts().build_alpha8_texture(); - - log::info!("Using Sokol backend: {:?}", sg_query_backend()); - - Ok(Box::new(SokolRenderer(RefCell::new(imgui)))) - } -} - -pub struct NullTexture(u16, u16); - -impl BackendTexture for NullTexture { - fn dimensions(&self) -> (u16, u16) { - (self.0, self.1) - } - - fn add(&mut self, command: SpriteBatchCommand) {} - - fn clear(&mut self) {} - - fn draw(&mut self) -> GameResult<()> { - Ok(()) - } -} - -pub struct SokolRenderer(RefCell); - -impl BackendRenderer for SokolRenderer { - fn clear(&mut self, color: Color) {} - - fn present(&mut self) -> GameResult { - Ok(()) - } - - fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult> { - Ok(Box::new(NullTexture(width, height))) - } - - fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult> { - Ok(Box::new(NullTexture(width, height))) - } - - fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult { - Ok(()) - } - - fn set_render_target(&mut self, texture: Option<&Box>) -> GameResult { - Ok(()) - } - - fn draw_rect(&mut self, rect: Rect, color: Color) -> GameResult { - Ok(()) - } - - fn draw_outline_rect(&mut self, rect: Rect, line_width: usize, color: Color) -> GameResult { - Ok(()) - } - - fn imgui(&self) -> GameResult<&mut imgui::Context> { - unsafe { Ok(&mut *self.0.as_ptr()) } - } - - fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult { - Ok(()) - } - - fn prepare_frame<'ui>(&self, ui: &Ui<'ui>) -> GameResult { - Ok(()) - } -} - -fn conv_scancode(code: SAppKeycode) -> Option { - match code { - SAppKeycode::KeySpace => Some(ScanCode::Space), - SAppKeycode::KeyApostrophe => Some(ScanCode::Apostrophe), - SAppKeycode::KeyComma => Some(ScanCode::Comma), - SAppKeycode::KeyMinus => Some(ScanCode::Minus), - SAppKeycode::KeyPeriod => Some(ScanCode::Period), - SAppKeycode::KeySlash => Some(ScanCode::Slash), - SAppKeycode::Key0 => Some(ScanCode::Key0), - SAppKeycode::Key1 => Some(ScanCode::Key1), - SAppKeycode::Key2 => Some(ScanCode::Key2), - SAppKeycode::Key3 => Some(ScanCode::Key3), - SAppKeycode::Key4 => Some(ScanCode::Key4), - SAppKeycode::Key5 => Some(ScanCode::Key5), - SAppKeycode::Key6 => Some(ScanCode::Key6), - SAppKeycode::Key7 => Some(ScanCode::Key7), - SAppKeycode::Key8 => Some(ScanCode::Key8), - SAppKeycode::Key9 => Some(ScanCode::Key9), - SAppKeycode::KeySemicolon => Some(ScanCode::Semicolon), - SAppKeycode::KeyEqual => Some(ScanCode::Equals), - SAppKeycode::KeyA => Some(ScanCode::A), - SAppKeycode::KeyB => Some(ScanCode::B), - SAppKeycode::KeyC => Some(ScanCode::C), - SAppKeycode::KeyD => Some(ScanCode::D), - SAppKeycode::KeyE => Some(ScanCode::E), - SAppKeycode::KeyF => Some(ScanCode::F), - SAppKeycode::KeyG => Some(ScanCode::G), - SAppKeycode::KeyH => Some(ScanCode::H), - SAppKeycode::KeyI => Some(ScanCode::I), - SAppKeycode::KeyJ => Some(ScanCode::J), - SAppKeycode::KeyK => Some(ScanCode::K), - SAppKeycode::KeyL => Some(ScanCode::L), - SAppKeycode::KeyM => Some(ScanCode::M), - SAppKeycode::KeyN => Some(ScanCode::N), - SAppKeycode::KeyO => Some(ScanCode::O), - SAppKeycode::KeyP => Some(ScanCode::P), - SAppKeycode::KeyQ => Some(ScanCode::Q), - SAppKeycode::KeyR => Some(ScanCode::R), - SAppKeycode::KeyS => Some(ScanCode::S), - SAppKeycode::KeyT => Some(ScanCode::T), - SAppKeycode::KeyU => Some(ScanCode::U), - SAppKeycode::KeyV => Some(ScanCode::V), - SAppKeycode::KeyW => Some(ScanCode::W), - SAppKeycode::KeyX => Some(ScanCode::X), - SAppKeycode::KeyY => Some(ScanCode::Y), - SAppKeycode::KeyZ => Some(ScanCode::Z), - SAppKeycode::KeyLeftBracket => Some(ScanCode::LBracket), - SAppKeycode::KeyBackslash => Some(ScanCode::Backslash), - SAppKeycode::KeyRightBracket => Some(ScanCode::RBracket), - SAppKeycode::KeyGraveAccent => Some(ScanCode::Grave), - SAppKeycode::KeyWorld1 => Some(ScanCode::AbntC1), - SAppKeycode::KeyWorld2 => Some(ScanCode::AbntC2), - SAppKeycode::KeyEscape => Some(ScanCode::Escape), - SAppKeycode::KeyEnter => Some(ScanCode::Return), - SAppKeycode::KeyTab => Some(ScanCode::Tab), - SAppKeycode::KeyBackspace => Some(ScanCode::Backspace), - SAppKeycode::KeyInsert => Some(ScanCode::Insert), - SAppKeycode::KeyDelete => Some(ScanCode::Delete), - SAppKeycode::KeyRight => Some(ScanCode::Right), - SAppKeycode::KeyLeft => Some(ScanCode::Left), - SAppKeycode::KeyDown => Some(ScanCode::Down), - SAppKeycode::KeyUp => Some(ScanCode::Up), - SAppKeycode::KeyPageUp => Some(ScanCode::PageUp), - SAppKeycode::KeyPageDown => Some(ScanCode::PageDown), - SAppKeycode::KeyHome => Some(ScanCode::Home), - SAppKeycode::KeyEnd => Some(ScanCode::End), - SAppKeycode::KeyCapsLock => Some(ScanCode::Capslock), - SAppKeycode::KeyScrollLock => Some(ScanCode::Scrolllock), - SAppKeycode::KeyNumLock => Some(ScanCode::Numlock), - SAppKeycode::KeyPrintScreen => Some(ScanCode::Sysrq), - SAppKeycode::KeyPause => Some(ScanCode::Pause), - SAppKeycode::KeyF1 => Some(ScanCode::F1), - SAppKeycode::KeyF2 => Some(ScanCode::F2), - SAppKeycode::KeyF3 => Some(ScanCode::F3), - SAppKeycode::KeyF4 => Some(ScanCode::F4), - SAppKeycode::KeyF5 => Some(ScanCode::F5), - SAppKeycode::KeyF6 => Some(ScanCode::F6), - SAppKeycode::KeyF7 => Some(ScanCode::F7), - SAppKeycode::KeyF8 => Some(ScanCode::F8), - SAppKeycode::KeyF9 => Some(ScanCode::F9), - SAppKeycode::KeyF10 => Some(ScanCode::F10), - SAppKeycode::KeyF11 => Some(ScanCode::F11), - SAppKeycode::KeyF12 => Some(ScanCode::F12), - SAppKeycode::KeyF13 => Some(ScanCode::F13), - SAppKeycode::KeyF14 => Some(ScanCode::F14), - SAppKeycode::KeyF15 => Some(ScanCode::F15), - SAppKeycode::KeyF16 => Some(ScanCode::F16), - SAppKeycode::KeyF17 => Some(ScanCode::F17), - SAppKeycode::KeyF18 => Some(ScanCode::F18), - SAppKeycode::KeyF19 => Some(ScanCode::F19), - SAppKeycode::KeyF20 => Some(ScanCode::F20), - SAppKeycode::KeyF21 => Some(ScanCode::F21), - SAppKeycode::KeyF22 => Some(ScanCode::F22), - SAppKeycode::KeyF23 => Some(ScanCode::F23), - SAppKeycode::KeyF24 => Some(ScanCode::F24), - SAppKeycode::KeyKP0 => Some(ScanCode::Numpad0), - SAppKeycode::KeyKP1 => Some(ScanCode::Numpad1), - SAppKeycode::KeyKP2 => Some(ScanCode::Numpad2), - SAppKeycode::KeyKP3 => Some(ScanCode::Numpad3), - SAppKeycode::KeyKP4 => Some(ScanCode::Numpad4), - SAppKeycode::KeyKP5 => Some(ScanCode::Numpad5), - SAppKeycode::KeyKP6 => Some(ScanCode::Numpad6), - SAppKeycode::KeyKP7 => Some(ScanCode::Numpad7), - SAppKeycode::KeyKP8 => Some(ScanCode::Numpad8), - SAppKeycode::KeyKP9 => Some(ScanCode::Numpad9), - SAppKeycode::KeyKPDecimal => Some(ScanCode::NumpadDecimal), - SAppKeycode::KeyKPDivide => Some(ScanCode::NumpadDivide), - SAppKeycode::KeyKPMultiply => Some(ScanCode::NumpadMultiply), - SAppKeycode::KeyKPSubtract => Some(ScanCode::NumpadSubtract), - SAppKeycode::KeyKPAdd => Some(ScanCode::NumpadAdd), - SAppKeycode::KeyKPEnter => Some(ScanCode::NumpadEnter), - SAppKeycode::KeyKPEqual => Some(ScanCode::NumpadEquals), - SAppKeycode::KeyLeftShift => Some(ScanCode::LShift), - SAppKeycode::KeyLeftControl => Some(ScanCode::LControl), - SAppKeycode::KeyLeftAlt => Some(ScanCode::LAlt), - SAppKeycode::KeyLeftSuper => Some(ScanCode::LWin), - SAppKeycode::KeyRightShift => Some(ScanCode::RShift), - SAppKeycode::KeyRightControl => Some(ScanCode::RControl), - SAppKeycode::KeyRightAlt => Some(ScanCode::RAlt), - SAppKeycode::KeyRightSuper => Some(ScanCode::RWin), - SAppKeycode::KeyMenu => Some(ScanCode::Menu), - _ => None, - } -} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index 1ae9e4e..1670f70 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -1,8 +1,8 @@ pub mod backend; pub mod backend_null; #[cfg(feature = "backend-glutin")] -pub mod backend_opengl; -#[cfg(feature = "backend-glutin")] +pub mod backend_glutin; +#[cfg(feature = "render-opengl")] mod gl; #[cfg(feature = "backend-sdl")] pub mod backend_sdl2; @@ -13,5 +13,7 @@ pub mod error; pub mod filesystem; pub mod graphics; pub mod keyboard; +#[cfg(feature = "render-opengl")] +pub mod render_opengl; pub mod ui; pub mod vfs; diff --git a/src/framework/backend_opengl.rs b/src/framework/render_opengl.rs similarity index 57% rename from src/framework/backend_opengl.rs rename to src/framework/render_opengl.rs index c042491..38053c7 100644 --- a/src/framework/backend_opengl.rs +++ b/src/framework/render_opengl.rs @@ -1,325 +1,27 @@ use std::cell::{RefCell, UnsafeCell}; -use std::ffi::CStr; +use std::ffi::{c_void, CStr}; use std::mem; use std::rc::Rc; use std::sync::Arc; -use glutin::event::{Event, TouchPhase, WindowEvent, ElementState, VirtualKeyCode}; -use glutin::event_loop::{ControlFlow, EventLoop}; -use glutin::window::WindowBuilder; -use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext}; use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert}; -use gl::types::*; - use crate::common::{Color, Rect}; -use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand}; -use crate::framework::context::Context; +use crate::framework::backend::{BackendRenderer, BackendTexture, SpriteBatchCommand}; use crate::framework::error::GameError::RenderError; use crate::framework::error::GameResult; use crate::framework::gl; +use crate::framework::gl::types::*; use crate::framework::graphics::BlendMode; -use crate::input::touch_controls::TouchPoint; -use crate::{Game, GAME_SUSPENDED}; -use crate::framework::keyboard::ScanCode; +use std::mem::MaybeUninit; -pub struct GlutinBackend; - -impl GlutinBackend { - pub fn new() -> GameResult> { - Ok(Box::new(GlutinBackend)) - } +pub struct GLContext { + pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void, + pub swap_buffers: unsafe fn(user_data: &mut *mut c_void), + pub user_data: *mut c_void, } -impl Backend for GlutinBackend { - fn create_event_loop(&self) -> GameResult> { - #[cfg(target_os = "android")] - loop { - match ndk_glue::native_window().as_ref() { - Some(_) => { - log::info!("NativeWindow Found: {:?}", ndk_glue::native_window()); - break; - } - None => (), - } - } - - Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) })) - } -} - -pub struct GlutinEventLoop { - refs: Rc>>>, -} - -impl GlutinEventLoop { - fn get_context(&self, event_loop: &EventLoop<()>) -> &mut WindowedContext { - let mut refs = unsafe { &mut *self.refs.get() }; - - if refs.is_none() { - let mut window = WindowBuilder::new(); - let windowed_context = ContextBuilder::new() - .with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0))) - .with_gl_profile(GlProfile::Core) - .with_gl_debug_flag(false) - .with_pixel_format(24, 8) - .with_vsync(true); - - #[cfg(target_os = "windows")] - { - use glutin::platform::windows::WindowBuilderExtWindows; - window = window.with_drag_and_drop(false); - } - - window = window.with_title("doukutsu-rs"); - - let windowed_context = windowed_context.build_windowed(window, event_loop) - .unwrap(); - - let windowed_context = unsafe { windowed_context.make_current().unwrap() }; - - #[cfg(target_os = "android")] - if let Some(nwin) = ndk_glue::native_window().as_ref() { - unsafe { - windowed_context.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void); - } - } - - refs.replace(windowed_context); - } - - refs.as_mut().unwrap() - } -} - -#[cfg(target_os = "android")] -fn request_android_redraw() { - match ndk_glue::native_window().as_ref() { - Some(native_window) => { - let a_native_window: *mut ndk_sys::ANativeWindow = native_window.ptr().as_ptr(); - let a_native_activity: *mut ndk_sys::ANativeActivity = ndk_glue::native_activity().ptr().as_ptr(); - unsafe { - match (*(*a_native_activity).callbacks).onNativeWindowRedrawNeeded { - Some(callback) => callback(a_native_activity, a_native_window), - None => (), - }; - }; - } - None => (), - } -} - -#[cfg(target_os = "android")] -fn get_insets() -> GameResult<(f32, f32, f32, f32)> { - unsafe { - let vm_ptr = ndk_glue::native_activity().vm(); - let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?; - let vm_env = vm.attach_current_thread()?; - - //let class = vm_env.find_class("io/github/doukutsu_rs/MainActivity")?; - let class = vm_env.new_global_ref(ndk_glue::native_activity().activity())?; - let field = vm_env.get_field(class.as_obj(), "displayInsets", "[I")?.to_jni().l as jni::sys::jintArray; - - let mut elements = [0; 4]; - vm_env.get_int_array_region(field, 0, &mut elements)?; - - Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32)) - } -} - -impl BackendEventLoop for GlutinEventLoop { - fn run(&mut self, game: &mut Game, ctx: &mut Context) { - let event_loop = EventLoop::new(); - let state_ref = unsafe { &mut *game.state.get() }; - let window: &'static mut WindowedContext = - unsafe { std::mem::transmute(self.get_context(&event_loop)) }; - - { - let size = window.window().inner_size(); - ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32); - state_ref.handle_resize(ctx).unwrap(); - } - - // it won't ever return - let (game, ctx): (&'static mut Game, &'static mut Context) = - unsafe { (std::mem::transmute(game), std::mem::transmute(ctx)) }; - - event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; - - match event { - Event::WindowEvent { event: WindowEvent::CloseRequested, window_id } - if window_id == window.window().id() => - { - state_ref.shutdown(); - } - Event::Resumed => { - println!("resumed!"); - { - let mut mutex = GAME_SUSPENDED.lock().unwrap(); - *mutex = false; - } - - #[cfg(target_os = "android")] - if let Some(nwin) = ndk_glue::native_window().as_ref() { - state_ref.graphics_reset(); - unsafe { - window.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void); - request_android_redraw(); - } - } - } - Event::Suspended => { - println!("suspended!"); - { - let mut mutex = GAME_SUSPENDED.lock().unwrap(); - *mutex = true; - } - - #[cfg(target_os = "android")] - unsafe { - window.surface_destroyed(); - } - } - Event::WindowEvent { event: WindowEvent::Resized(size), window_id } - if window_id == window.window().id() => - { - if let Some(renderer) = ctx.renderer.as_ref() { - if let Ok(imgui) = renderer.imgui() { - imgui.io_mut().display_size = [size.width as f32, size.height as f32]; - } - - ctx.screen_size = (size.width as f32, size.height as f32); - state_ref.handle_resize(ctx).unwrap(); - } - } - Event::WindowEvent { event: WindowEvent::Touch(touch), window_id } - if window_id == window.window().id() => - { - let mut controls = &mut state_ref.touch_controls; - let scale = state_ref.scale as f64; - - match touch.phase { - TouchPhase::Started | TouchPhase::Moved => { - if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) { - point.last_position = point.position; - point.position = (touch.location.x / scale, touch.location.y / scale); - } else { - controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1); - - let point = TouchPoint { - id: touch.id, - touch_id: controls.touch_id_counter, - position: (touch.location.x / scale, touch.location.y / scale), - last_position: (0.0, 0.0), - }; - controls.points.push(point); - - if touch.phase == TouchPhase::Started { - controls.clicks.push(point); - } - } - } - TouchPhase::Ended | TouchPhase::Cancelled => { - controls.points.retain(|p| p.id != touch.id); - controls.clicks.retain(|p| p.id != touch.id); - } - } - } - Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, window_id } - if window_id == window.window().id() => { - - if let Some(keycode) = input.virtual_keycode { - if let Some(drs_scan) = conv_keycode(keycode) { - let key_state = match input.state { - ElementState::Pressed => true, - ElementState::Released => false, - }; - - ctx.keyboard_context.set_key(drs_scan, key_state); - } - } - } - Event::RedrawRequested(id) if id == window.window().id() => { - { - let mutex = GAME_SUSPENDED.lock().unwrap(); - if *mutex { - return; - } - } - - #[cfg(not(target_os = "android"))] - { - if let Err(err) = game.draw(ctx) { - log::error!("Failed to draw frame: {}", err); - } - - window.window().request_redraw(); - } - - #[cfg(target_os = "android")] - request_android_redraw(); - } - Event::MainEventsCleared => { - if state_ref.shutdown { - log::info!("Shutting down..."); - *control_flow = ControlFlow::Exit; - return; - } - - { - let mutex = GAME_SUSPENDED.lock().unwrap(); - if *mutex { - return; - } - } - - game.update(ctx).unwrap(); - - #[cfg(target_os = "android")] - { - match get_insets() { - Ok(insets) => { - ctx.screen_insets = insets; - } - Err(e) => { - log::error!("Failed to update insets: {}", e); - } - } - - if let Err(err) = game.draw(ctx) { - log::error!("Failed to draw frame: {}", err); - } - } - - if state_ref.next_scene.is_some() { - mem::swap(&mut game.scene, &mut state_ref.next_scene); - state_ref.next_scene = None; - game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap(); - game.loops = 0; - state_ref.frame_time = 0.0; - } - } - _ => (), - } - }); - } - - fn new_renderer(&self) -> GameResult> { - let mut imgui = imgui::Context::create(); - imgui.io_mut().display_size = [640.0, 480.0]; - - Ok(Box::new(GlutinRenderer { - refs: self.refs.clone(), - imgui: UnsafeCell::new(imgui), - imgui_data: ImguiData::new(), - context_active: Arc::new(RefCell::new(true)), - def_matrix: [[0.0; 4]; 4], - })) - } -} - -pub struct GlutinTexture { +pub struct OpenGLTexture { width: u16, height: u16, texture_id: u32, @@ -338,7 +40,7 @@ struct VertexData { color: (u8, u8, u8, u8), } -impl BackendTexture for GlutinTexture { +impl BackendTexture for OpenGLTexture { fn dimensions(&self) -> (u16, u16) { (self.width, self.height) } @@ -537,7 +239,7 @@ impl BackendTexture for GlutinTexture { } } -impl Drop for GlutinTexture { +impl Drop for OpenGLTexture { fn drop(&mut self) { if *self.context_active.as_ref().borrow() { unsafe { @@ -582,7 +284,7 @@ fn check_shader_compile_status(shader: u32, gl: &Gl) -> bool { true } -const IMGUI_SHADER_VERT: &str = r" +const VERTEX_SHADER_BASIC: &str = r" #version 100 precision mediump float; @@ -603,7 +305,7 @@ void main() "; -const IMGUI_SHADER_FRAG: &str = r" +const FRAGMENT_SHADER_TEXTURED: &str = r" #version 100 precision mediump float; @@ -619,6 +321,21 @@ void main() "; +const FRAGMENT_SHADER_COLOR: &str = r" +#version 100 + +precision mediump float; + +varying vec2 Frag_UV; +varying vec4 Frag_Color; + +void main() +{ + gl_FragColor = Frag_Color; +} + +"; + #[derive(Copy, Clone)] struct Locs { texture: GLint, @@ -654,10 +371,10 @@ impl ImguiData { fn init(&mut self, imgui: &mut imgui::Context, gl: &Gl) { self.initialized = true; - let vert_sources = [IMGUI_SHADER_VERT.as_ptr() as *const GLchar]; - let frag_sources = [IMGUI_SHADER_FRAG.as_ptr() as *const GLchar]; - let vert_sources_len = [IMGUI_SHADER_VERT.len() as GLint - 1]; - let frag_sources_len = [IMGUI_SHADER_FRAG.len() as GLint - 1]; + let vert_sources = [VERTEX_SHADER_BASIC.as_ptr() as *const GLchar]; + let frag_sources = [FRAGMENT_SHADER_TEXTURED.as_ptr() as *const GLchar]; + let vert_sources_len = [VERTEX_SHADER_BASIC.len() as GLint - 1]; + let frag_sources_len = [FRAGMENT_SHADER_TEXTURED.len() as GLint - 1]; unsafe { self.program = gl.gl.CreateProgram(); @@ -726,31 +443,28 @@ impl ImguiData { } } -pub struct GlutinRenderer { - refs: Rc>>>, - imgui: UnsafeCell, - imgui_data: ImguiData, - context_active: Arc>, - def_matrix: [[f32; 4]; 4], -} - pub struct Gl { pub gl: gl::Gles2, } static mut GL_PROC: Option = None; -pub fn load_gl(gl_context: &glutin::Context) -> &'static Gl { +pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl { unsafe { if let Some(gl) = GL_PROC.as_ref() { return gl; } - let gl = gl::Gles2::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _); + let gl = gl::Gles2::load_with(|ptr| (gl_context.get_proc_address)(&mut gl_context.user_data, ptr)); let version = unsafe { - let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _).to_bytes().to_vec(); - String::from_utf8(data).unwrap() + let p = gl.GetString(gl::VERSION); + if p.is_null() { + "unknown".to_owned() + } else { + let data = CStr::from_ptr(p as *const _).to_bytes().to_vec(); + String::from_utf8(data).unwrap() + } }; log::info!("OpenGL version {}", version); @@ -760,25 +474,41 @@ pub fn load_gl(gl_context: &glutin::Context) -> &'static Gl { } } -impl GlutinRenderer { - fn get_context(&mut self) -> Option<(&mut WindowedContext, &'static Gl)> { - let (refs, imgui) = unsafe { ((&mut *self.refs.get()).as_mut(), &mut *self.imgui.get()) }; +pub struct OpenGLRenderer { + refs: GLContext, + imgui: UnsafeCell, + imgui_data: ImguiData, + context_active: Arc>, + def_matrix: [[f32; 4]; 4], +} - refs.map(|context| { - let gl = load_gl(context); +impl OpenGLRenderer { + pub fn new(refs: GLContext, imgui: UnsafeCell) -> OpenGLRenderer { + OpenGLRenderer { + refs, + imgui, + imgui_data: ImguiData::new(), + context_active: Arc::new(RefCell::new(true)), + def_matrix: [[0.0; 4]; 4], + } + } - if !self.imgui_data.initialized { - self.imgui_data.init(imgui, gl); - } + fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> { + let imgui = unsafe { &mut *self.imgui.get() }; - (context, gl) - }) + let gl = load_gl(&mut self.refs); + + if !self.imgui_data.initialized { + self.imgui_data.init(imgui, gl); + } + + Some((&mut self.refs, gl)) } } fn field_offset FnOnce(&'a T) -> &'a U>(f: F) -> usize { unsafe { - let instance = mem::zeroed::(); + let instance = MaybeUninit::uninit().assume_init(); let offset = { let field: &U = f(&instance); @@ -800,7 +530,11 @@ where val } -impl BackendRenderer for GlutinRenderer { +impl BackendRenderer for OpenGLRenderer { + fn renderer_name(&self) -> String { + "OpenGL(ES)".to_owned() + } + fn clear(&mut self, color: Color) { if let Some((_, gl)) = self.get_context() { unsafe { @@ -812,7 +546,7 @@ impl BackendRenderer for GlutinRenderer { fn present(&mut self) -> GameResult { { - let mutex = GAME_SUSPENDED.lock().unwrap(); + let mutex = crate::GAME_SUSPENDED.lock().unwrap(); if *mutex { return Ok(()); } @@ -821,9 +555,9 @@ impl BackendRenderer for GlutinRenderer { if let Some((context, gl)) = self.get_context() { unsafe { gl.gl.Finish(); - } - context.swap_buffers().map_err(|e| RenderError(e.to_string()))?; + (context.swap_buffers)(&mut context.user_data); + } } Ok(()) @@ -895,7 +629,7 @@ impl BackendRenderer for GlutinRenderer { // todo error checking: glCheckFramebufferStatus() - Ok(Box::new(GlutinTexture { + Ok(Box::new(OpenGLTexture { texture_id, framebuffer_id, width, @@ -934,7 +668,7 @@ impl BackendRenderer for GlutinRenderer { gl.gl.BindTexture(gl::TEXTURE_2D, current_texture_id); - Ok(Box::new(GlutinTexture { + Ok(Box::new(OpenGLTexture { texture_id, framebuffer_id: 0, width, @@ -977,7 +711,7 @@ impl BackendRenderer for GlutinRenderer { if let Some((_, gl)) = self.get_context() { unsafe { if let Some(texture) = texture { - let gl_texture: &Box = std::mem::transmute(texture); + let gl_texture: &Box = std::mem::transmute(texture); let matrix = [ [2.0 / (gl_texture.width as f32), 0.0, 0.0, 0.0], @@ -1201,6 +935,7 @@ impl BackendRenderer for GlutinRenderer { } } + gl.gl.Disable(gl::SCISSOR_TEST); //gl.gl.DeleteVertexArrays(1, &vao); } } @@ -1209,176 +944,8 @@ impl BackendRenderer for GlutinRenderer { } } -impl Drop for GlutinRenderer { +impl Drop for OpenGLRenderer { fn drop(&mut self) { *self.context_active.as_ref().borrow_mut() = false; } } - -fn conv_keycode(code: VirtualKeyCode) -> Option { - match code { - VirtualKeyCode::Key1 => Some(ScanCode::Key1), - VirtualKeyCode::Key2 => Some(ScanCode::Key2), - VirtualKeyCode::Key3 => Some(ScanCode::Key3), - VirtualKeyCode::Key4 => Some(ScanCode::Key4), - VirtualKeyCode::Key5 => Some(ScanCode::Key5), - VirtualKeyCode::Key6 => Some(ScanCode::Key6), - VirtualKeyCode::Key7 => Some(ScanCode::Key7), - VirtualKeyCode::Key8 => Some(ScanCode::Key8), - VirtualKeyCode::Key9 => Some(ScanCode::Key9), - VirtualKeyCode::Key0 => Some(ScanCode::Key0), - VirtualKeyCode::A => Some(ScanCode::A), - VirtualKeyCode::B => Some(ScanCode::B), - VirtualKeyCode::C => Some(ScanCode::C), - VirtualKeyCode::D => Some(ScanCode::D), - VirtualKeyCode::E => Some(ScanCode::E), - VirtualKeyCode::F => Some(ScanCode::F), - VirtualKeyCode::G => Some(ScanCode::G), - VirtualKeyCode::H => Some(ScanCode::H), - VirtualKeyCode::I => Some(ScanCode::I), - VirtualKeyCode::J => Some(ScanCode::J), - VirtualKeyCode::K => Some(ScanCode::K), - VirtualKeyCode::L => Some(ScanCode::L), - VirtualKeyCode::M => Some(ScanCode::M), - VirtualKeyCode::N => Some(ScanCode::N), - VirtualKeyCode::O => Some(ScanCode::O), - VirtualKeyCode::P => Some(ScanCode::P), - VirtualKeyCode::Q => Some(ScanCode::Q), - VirtualKeyCode::R => Some(ScanCode::R), - VirtualKeyCode::S => Some(ScanCode::S), - VirtualKeyCode::T => Some(ScanCode::T), - VirtualKeyCode::U => Some(ScanCode::U), - VirtualKeyCode::V => Some(ScanCode::V), - VirtualKeyCode::W => Some(ScanCode::W), - VirtualKeyCode::X => Some(ScanCode::X), - VirtualKeyCode::Y => Some(ScanCode::Y), - VirtualKeyCode::Z => Some(ScanCode::Z), - VirtualKeyCode::Escape => Some(ScanCode::Escape), - VirtualKeyCode::F1 => Some(ScanCode::F1), - VirtualKeyCode::F2 => Some(ScanCode::F2), - VirtualKeyCode::F3 => Some(ScanCode::F3), - VirtualKeyCode::F4 => Some(ScanCode::F4), - VirtualKeyCode::F5 => Some(ScanCode::F5), - VirtualKeyCode::F6 => Some(ScanCode::F6), - VirtualKeyCode::F7 => Some(ScanCode::F7), - VirtualKeyCode::F8 => Some(ScanCode::F8), - VirtualKeyCode::F9 => Some(ScanCode::F9), - VirtualKeyCode::F10 => Some(ScanCode::F10), - VirtualKeyCode::F11 => Some(ScanCode::F11), - VirtualKeyCode::F12 => Some(ScanCode::F12), - VirtualKeyCode::F13 => Some(ScanCode::F13), - VirtualKeyCode::F14 => Some(ScanCode::F14), - VirtualKeyCode::F15 => Some(ScanCode::F15), - VirtualKeyCode::F16 => Some(ScanCode::F16), - VirtualKeyCode::F17 => Some(ScanCode::F17), - VirtualKeyCode::F18 => Some(ScanCode::F18), - VirtualKeyCode::F19 => Some(ScanCode::F19), - VirtualKeyCode::F20 => Some(ScanCode::F20), - VirtualKeyCode::F21 => Some(ScanCode::F21), - VirtualKeyCode::F22 => Some(ScanCode::F22), - VirtualKeyCode::F23 => Some(ScanCode::F23), - VirtualKeyCode::F24 => Some(ScanCode::F24), - VirtualKeyCode::Snapshot => Some(ScanCode::Snapshot), - VirtualKeyCode::Scroll => Some(ScanCode::Scroll), - VirtualKeyCode::Pause => Some(ScanCode::Pause), - VirtualKeyCode::Insert => Some(ScanCode::Insert), - VirtualKeyCode::Home => Some(ScanCode::Home), - VirtualKeyCode::Delete => Some(ScanCode::Delete), - VirtualKeyCode::End => Some(ScanCode::End), - VirtualKeyCode::PageDown => Some(ScanCode::PageDown), - VirtualKeyCode::PageUp => Some(ScanCode::PageUp), - VirtualKeyCode::Left => Some(ScanCode::Left), - VirtualKeyCode::Up => Some(ScanCode::Up), - VirtualKeyCode::Right => Some(ScanCode::Right), - VirtualKeyCode::Down => Some(ScanCode::Down), - VirtualKeyCode::Back => Some(ScanCode::Back), - VirtualKeyCode::Return => Some(ScanCode::Return), - VirtualKeyCode::Space => Some(ScanCode::Space), - VirtualKeyCode::Compose => Some(ScanCode::Compose), - VirtualKeyCode::Caret => Some(ScanCode::Caret), - VirtualKeyCode::Numlock => Some(ScanCode::Numlock), - VirtualKeyCode::Numpad0 => Some(ScanCode::Numpad0), - VirtualKeyCode::Numpad1 => Some(ScanCode::Numpad1), - VirtualKeyCode::Numpad2 => Some(ScanCode::Numpad2), - VirtualKeyCode::Numpad3 => Some(ScanCode::Numpad3), - VirtualKeyCode::Numpad4 => Some(ScanCode::Numpad4), - VirtualKeyCode::Numpad5 => Some(ScanCode::Numpad5), - VirtualKeyCode::Numpad6 => Some(ScanCode::Numpad6), - VirtualKeyCode::Numpad7 => Some(ScanCode::Numpad7), - VirtualKeyCode::Numpad8 => Some(ScanCode::Numpad8), - VirtualKeyCode::Numpad9 => Some(ScanCode::Numpad9), - VirtualKeyCode::NumpadAdd => Some(ScanCode::NumpadAdd), - VirtualKeyCode::NumpadDivide => Some(ScanCode::NumpadDivide), - VirtualKeyCode::NumpadDecimal => Some(ScanCode::NumpadDecimal), - VirtualKeyCode::NumpadComma => Some(ScanCode::NumpadComma), - VirtualKeyCode::NumpadEnter => Some(ScanCode::NumpadEnter), - VirtualKeyCode::NumpadEquals => Some(ScanCode::NumpadEquals), - VirtualKeyCode::NumpadMultiply => Some(ScanCode::NumpadMultiply), - VirtualKeyCode::NumpadSubtract => Some(ScanCode::NumpadSubtract), - VirtualKeyCode::AbntC1 => Some(ScanCode::AbntC1), - VirtualKeyCode::AbntC2 => Some(ScanCode::AbntC2), - VirtualKeyCode::Apostrophe => Some(ScanCode::Apostrophe), - VirtualKeyCode::Apps => Some(ScanCode::Apps), - VirtualKeyCode::Asterisk => Some(ScanCode::Asterisk), - VirtualKeyCode::At => Some(ScanCode::At), - VirtualKeyCode::Ax => Some(ScanCode::Ax), - VirtualKeyCode::Backslash => Some(ScanCode::Backslash), - VirtualKeyCode::Calculator => Some(ScanCode::Calculator), - VirtualKeyCode::Capital => Some(ScanCode::Capital), - VirtualKeyCode::Colon => Some(ScanCode::Colon), - VirtualKeyCode::Comma => Some(ScanCode::Comma), - VirtualKeyCode::Convert => Some(ScanCode::Convert), - VirtualKeyCode::Equals => Some(ScanCode::Equals), - VirtualKeyCode::Grave => Some(ScanCode::Grave), - VirtualKeyCode::Kana => Some(ScanCode::Kana), - VirtualKeyCode::Kanji => Some(ScanCode::Kanji), - VirtualKeyCode::LAlt => Some(ScanCode::LAlt), - VirtualKeyCode::LBracket => Some(ScanCode::LBracket), - VirtualKeyCode::LControl => Some(ScanCode::LControl), - VirtualKeyCode::LShift => Some(ScanCode::LShift), - VirtualKeyCode::LWin => Some(ScanCode::LWin), - VirtualKeyCode::Mail => Some(ScanCode::Mail), - VirtualKeyCode::MediaSelect => Some(ScanCode::MediaSelect), - VirtualKeyCode::MediaStop => Some(ScanCode::MediaStop), - VirtualKeyCode::Minus => Some(ScanCode::Minus), - VirtualKeyCode::Mute => Some(ScanCode::Mute), - VirtualKeyCode::MyComputer => Some(ScanCode::MyComputer), - VirtualKeyCode::NavigateForward => Some(ScanCode::NavigateForward), - VirtualKeyCode::NavigateBackward => Some(ScanCode::NavigateBackward), - VirtualKeyCode::NextTrack => Some(ScanCode::NextTrack), - VirtualKeyCode::NoConvert => Some(ScanCode::NoConvert), - VirtualKeyCode::OEM102 => Some(ScanCode::OEM102), - VirtualKeyCode::Period => Some(ScanCode::Period), - VirtualKeyCode::PlayPause => Some(ScanCode::PlayPause), - VirtualKeyCode::Plus => Some(ScanCode::Plus), - VirtualKeyCode::Power => Some(ScanCode::Power), - VirtualKeyCode::PrevTrack => Some(ScanCode::PrevTrack), - VirtualKeyCode::RAlt => Some(ScanCode::RAlt), - VirtualKeyCode::RBracket => Some(ScanCode::RBracket), - VirtualKeyCode::RControl => Some(ScanCode::RControl), - VirtualKeyCode::RShift => Some(ScanCode::RShift), - VirtualKeyCode::RWin => Some(ScanCode::RWin), - VirtualKeyCode::Semicolon => Some(ScanCode::Semicolon), - VirtualKeyCode::Slash => Some(ScanCode::Slash), - VirtualKeyCode::Sleep => Some(ScanCode::Sleep), - VirtualKeyCode::Stop => Some(ScanCode::Stop), - VirtualKeyCode::Sysrq => Some(ScanCode::Sysrq), - VirtualKeyCode::Tab => Some(ScanCode::Tab), - VirtualKeyCode::Underline => Some(ScanCode::Underline), - VirtualKeyCode::Unlabeled => Some(ScanCode::Unlabeled), - VirtualKeyCode::VolumeDown => Some(ScanCode::VolumeDown), - VirtualKeyCode::VolumeUp => Some(ScanCode::VolumeUp), - VirtualKeyCode::Wake => Some(ScanCode::Wake), - VirtualKeyCode::WebBack => Some(ScanCode::WebBack), - VirtualKeyCode::WebFavorites => Some(ScanCode::WebFavorites), - VirtualKeyCode::WebForward => Some(ScanCode::WebForward), - VirtualKeyCode::WebHome => Some(ScanCode::WebHome), - VirtualKeyCode::WebRefresh => Some(ScanCode::WebRefresh), - VirtualKeyCode::WebSearch => Some(ScanCode::WebSearch), - VirtualKeyCode::WebStop => Some(ScanCode::WebStop), - VirtualKeyCode::Yen => Some(ScanCode::Yen), - VirtualKeyCode::Copy => Some(ScanCode::Copy), - VirtualKeyCode::Paste => Some(ScanCode::Paste), - VirtualKeyCode::Cut => Some(ScanCode::Cut), - } -} diff --git a/src/scene/title_scene.rs b/src/scene/title_scene.rs index 3371226..bbe7068 100644 --- a/src/scene/title_scene.rs +++ b/src/scene/title_scene.rs @@ -121,8 +121,8 @@ impl Scene for TitleScene { } else { self.option_menu.push_entry(MenuEntry::Disabled("Seasonal textures".to_string())); } - self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string())); - self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned())); + self.option_menu.push_entry(MenuEntry::Active(DISCORD_LINK.to_owned())); + self.option_menu.push_entry(MenuEntry::Disabled(["Renderer: ", &ctx.renderer.as_ref().unwrap().renderer_name()].join(""))); self.option_menu.push_entry(MenuEntry::Active("Back".to_string())); self.save_select_menu.push_entry(MenuEntry::NewSave);