diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..5124200 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,6 @@ +[target.aarch64-linux-android] +rustflags = [ + "-C", "link-arg=-lc++_static", + "-C", "link-arg=-lc++abi", + "-C", "link-arg=-lEGL", +] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc679ad..141834d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,6 +73,7 @@ jobs: files: | doukutsu-rs.x86_64 data + !data/.git !data/README.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -134,6 +135,7 @@ jobs: path: | doukutsu-rs.exe data + !data/.git !data/README.md if-no-files-found: error diff --git a/Cargo.toml b/Cargo.toml index 43eedcc..9bb6a67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,29 @@ edition = "2018" name = "doukutsu-rs" version = "0.1.0" +[lib] +crate-type = ["lib", "cdylib"] + +[package.metadata.android] +android_version = 28 +target_sdk_version = 28 +min_sdk_version = 26 +build_targets = ["aarch64-linux-android"] +package_name = "io.github.doukutsu_rs.android" +apk_label = "doukutsu-rs" +opengles_version = [2, 0] +fullscreen = true +orientation = "sensorLandscape" +permission = [ + {name = "android.permission.MANAGE_EXTERNAL_STORAGE"}, + {name = "android.permission.READ_EXTERNAL_STORAGE"}, + {name = "android.permission.WRITE_EXTERNAL_STORAGE"} +] +application_metadatas = [ + {name = "android:hardwareAccelerated", value = "true"}, + {name = "android:requestLegacyExternalStorage", value = "true"} +] + [profile.release] lto = 'thin' panic = 'abort' @@ -12,32 +35,33 @@ panic = 'abort' opt-level = 1 [dependencies] +#cpal = {path = "./cpal"} +#gfx_device_gl = {path = "./gfx/src/backend/gl"} +#glutin = {path = "./glutin/glutin"} + approx = "0.3" bitflags = "1" bitvec = "0.17.4" byteorder = "1.3" case_insensitive_hashmap = "1.0.0" -cpal = "0.12.1" +cpal = {git = "https://github.com/alula/cpal.git", branch = "android-support"} directories = "2" gfx = "0.18" gfx_core = "0.9" -gfx_device_gl = "0.16" -gfx_window_glutin = "0.30" +gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"} gilrs = "0.7" -glyph_brush = "0.5" -glutin = "0.20" -imgui = "0.4.0" -imgui-ext = "0.3.0" -imgui-gfx-renderer = "0.4.0" -imgui-winit-support = {version = "0.4.0", default-features = false, features = ["winit-19"] } -image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] } +glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"} +imgui = {git = "https://github.com/JMS55/imgui-rs.git"} +imgui-gfx-renderer = {git = "https://github.com/JMS55/imgui-rs.git"} +imgui-winit-support = {git = "https://github.com/JMS55/imgui-rs.git", default-features = false, features = ["winit-23"]} +image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]} itertools = "0.9.0" lazy_static = "1.4.0" log = "0.4" lru = "0.6.0" lyon = "0.13" mint = "0.5" -nalgebra = {version = "0.18", features = ["mint"] } +nalgebra = {version = "0.18", features = ["mint"]} num-derive = "0.3.2" num-traits = "0.2.12" owning_ref = "0.4.1" @@ -53,4 +77,9 @@ varint = "0.9.0" # remove and replace when drain_filter is in stable vec_mut_scan = "0.3.0" webbrowser = "0.5.5" -winit = { version = "0.19.3" } +winit = "0.23.0" + +[target.'cfg(target_os = "android")'.dependencies] +ndk = "0.2.0" +ndk-glue = "0.2.0" +jni = "0.17" diff --git a/src/builtin/touch.png b/src/builtin/touch.png new file mode 100644 index 0000000..7fcfdbe Binary files /dev/null and b/src/builtin/touch.png differ diff --git a/src/ggez/conf.rs b/src/ggez/conf.rs index 1df2668..2b31825 100644 --- a/src/ggez/conf.rs +++ b/src/ggez/conf.rs @@ -168,7 +168,7 @@ pub struct WindowSetup { pub icon: String, /// Whether or not to enable sRGB (gamma corrected color) /// handling on the display. - #[default = true] + #[default = false] pub srgb: bool, } diff --git a/src/ggez/context.rs b/src/ggez/context.rs index f3ced62..25755e5 100644 --- a/src/ggez/context.rs +++ b/src/ggez/context.rs @@ -15,6 +15,7 @@ use crate::ggez::filesystem::Filesystem; use crate::ggez::graphics::{self, FilterMode, Point2}; use crate::ggez::input::{gamepad, keyboard, mouse}; use crate::ggez::timer; +use glutin::platform::ContextTraitExt; /// A `Context` is an object that holds on to global resources. /// It basically tracks hardware state such as the screen, audio @@ -79,14 +80,13 @@ impl fmt::Debug for Context { impl Context { /// Tries to create a new Context using settings from the given [`Conf`](../conf/struct.Conf.html) object. /// Usually called by [`ContextBuilder::build()`](struct.ContextBuilder.html#method.build). - fn from_conf(conf: conf::Conf, mut fs: Filesystem) -> GameResult<(Context, winit::EventsLoop)> { + fn from_conf(conf: conf::Conf, events_loop: &winit::event_loop::EventLoopWindowTarget<()>, mut fs: Filesystem) -> GameResult { let debug_id = DebugId::new(); - let events_loop = winit::EventsLoop::new(); let timer_context = timer::TimeContext::new(); let backend_spec = graphics::GlBackendSpec::from(conf.backend); let graphics_context = graphics::context::GraphicsContext::new( &mut fs, - &events_loop, + events_loop, &conf.window_setup, conf.window_mode, backend_spec, @@ -95,7 +95,12 @@ impl Context { let mouse_context = mouse::MouseContext::new(); let keyboard_context = keyboard::KeyboardContext::new(); let gamepad_context: Box = if conf.modules.gamepad { - Box::new(gamepad::GilrsGamepadContext::new()?) + let gp: Box = if let Ok(ctx) = gamepad::GilrsGamepadContext::new() { + Box::new(ctx) + } else { + Box::new(gamepad::NullGamepadContext::default()) + }; + gp } else { Box::new(gamepad::NullGamepadContext::default()) }; @@ -114,7 +119,7 @@ impl Context { debug_id, }; - Ok((ctx, events_loop)) + Ok(ctx) } // TODO LATER: This should be a function in `ggez::event`, per the @@ -124,13 +129,11 @@ impl Context { /// state it needs to, such as detecting window resizes. If you are /// rolling your own event loop, you should call this on the events /// you receive before processing them yourself. - pub fn process_event(&mut self, event: &winit::Event) { - match event.clone() { + pub fn process_event<'a>(&mut self, event: &winit::event::Event<'a, ()>) { + match event { winit_event::Event::WindowEvent { event, .. } => match event { - winit_event::WindowEvent::Resized(logical_size) => { - let hidpi_factor = self.gfx_context.window.get_hidpi_factor(); - let physical_size = logical_size.to_physical(hidpi_factor as f64); - self.gfx_context.window.resize(physical_size); + winit_event::WindowEvent::Resized(physical_size) => { + self.gfx_context.window.resize(*physical_size); self.gfx_context.resize_viewport(); } winit_event::WindowEvent::CursorMoved { @@ -147,11 +150,11 @@ impl Context { winit_event::ElementState::Pressed => true, winit_event::ElementState::Released => false, }; - self.mouse_context.set_button(button, pressed); + self.mouse_context.set_button(*button, pressed); } winit_event::WindowEvent::KeyboardInput { input: - winit::KeyboardInput { + winit::event::KeyboardInput { state, virtual_keycode: Some(keycode), modifiers, @@ -164,34 +167,29 @@ impl Context { winit_event::ElementState::Released => false, }; self.keyboard_context - .set_modifiers(keyboard::KeyMods::from(modifiers)); - self.keyboard_context.set_key(keycode, pressed); - } - winit_event::WindowEvent::HiDpiFactorChanged(_) => { - // Nope. + .set_modifiers(keyboard::KeyMods::from(*modifiers)); + self.keyboard_context.set_key(*keycode, pressed); } _ => (), }, winit_event::Event::DeviceEvent { event, .. } => { if let winit_event::DeviceEvent::MouseMotion { delta: (x, y) } = event { self.mouse_context - .set_last_delta(Point2::new(x as f32, y as f32)); + .set_last_delta(Point2::new(*x as f32, *y as f32)); } } - _ => (), }; } } /// A builder object for creating a [`Context`](struct.Context.html). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct ContextBuilder { pub(crate) game_id: String, pub(crate) conf: conf::Conf, pub(crate) paths: Vec, pub(crate) memory_zip_files: Vec>, - pub(crate) load_conf_file: bool, } impl ContextBuilder { @@ -202,7 +200,6 @@ impl ContextBuilder { conf: conf::Conf::default(), paths: vec![], memory_zip_files: vec![], - load_conf_file: true, } } @@ -249,30 +246,15 @@ impl ContextBuilder { self } - /// Specifies whether or not to load the `conf.toml` file if it - /// exists and use its settings to override the provided values. - /// Defaults to `true` which is usually what you want, but being - /// able to fiddle with it is sometimes useful for debugging. - pub fn with_conf_file(mut self, load_conf_file: bool) -> Self { - self.load_conf_file = load_conf_file; - self - } - /// Build the `Context`. - pub fn build(self) -> GameResult<(Context, winit::EventsLoop)> { + pub fn build(mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> GameResult { let mut fs = Filesystem::new(self.game_id.as_ref())?; for path in &self.paths { fs.mount(path, true); } - let config = if self.load_conf_file { - fs.read_config().unwrap_or(self.conf) - } else { - self.conf - }; - - Context::from_conf(config, fs) + Context::from_conf(self.conf, event_loop, fs) } } diff --git a/src/ggez/error.rs b/src/ggez/error.rs index 24350bc..c5bc884 100644 --- a/src/ggez/error.rs +++ b/src/ggez/error.rs @@ -181,8 +181,8 @@ impl From for GameError { } } -impl From for GameError { - fn from(_: glutin::EventsLoopClosed) -> GameError { +impl From> for GameError { + fn from(_: winit::event_loop::EventLoopClosed) -> GameError { let e = "An event loop proxy attempted to wake up an event loop that no longer exists." .to_owned(); GameError::EventLoopError(e) @@ -208,6 +208,13 @@ impl From for GameError { } } +#[cfg(target_os = "android")] +impl From for GameError { + fn from(e: jni::errors::Error) -> GameError { + GameError::WindowError(e.to_string()) + } +} + impl From for GameError { fn from(s: lyon::lyon_tessellation::TessellationError) -> GameError { let errstr = format!( diff --git a/src/ggez/event.rs b/src/ggez/event.rs index a455636..be2edb8 100644 --- a/src/ggez/event.rs +++ b/src/ggez/event.rs @@ -11,36 +11,35 @@ //! example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs). use gilrs; +/// An analog axis of some device (gamepad thumbstick, joystick...). +pub use gilrs::Axis; +/// A button of some device (gamepad, joystick...). +pub use gilrs::Button; use winit::{self, dpi}; +/// A mouse button. +pub use winit::event::MouseButton; +/// `winit` event loop. +pub use winit::event_loop::EventLoop; + +use crate::ggez::context::Context; +use crate::ggez::error::GameResult; +pub use crate::ggez::input::gamepad::GamepadId; +pub use crate::ggez::input::keyboard::{KeyCode, KeyMods}; + +use self::winit_event::*; +use crate::ggez::graphics::window; // TODO LATER: I kinda hate all these re-exports. I kinda hate // a lot of the details of the `EventHandler` and input now though, // and look forward to ripping it all out and replacing it with newer winit. -/// A mouse button. -pub use winit::MouseButton; - -/// An analog axis of some device (gamepad thumbstick, joystick...). -pub use gilrs::Axis; -/// A button of some device (gamepad, joystick...). -pub use gilrs::Button; - /// `winit` events; nested in a module for re-export neatness. pub mod winit_event { - pub use super::winit::{ + pub use winit::event::{ DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, WindowEvent, }; } -pub use crate::ggez::input::gamepad::GamepadId; -pub use crate::ggez::input::keyboard::{KeyCode, KeyMods}; - -use self::winit_event::*; -/// `winit` event loop. -pub use winit::EventsLoop; - -use crate::ggez::context::Context; -use crate::ggez::error::GameResult; /// A trait defining event callbacks. This is your primary interface with /// `ggez`'s event loop. Implement this trait for a type and @@ -71,8 +70,7 @@ pub trait EventHandler { _button: MouseButton, _x: f32, _y: f32, - ) { - } + ) {} /// A mouse button was released fn mouse_button_up_event( @@ -81,8 +79,7 @@ pub trait EventHandler { _button: MouseButton, _x: f32, _y: f32, - ) { - } + ) {} /// The mouse was moved; it provides both absolute x and y coordinates in the window, /// and relative x and y coordinates compared to its last position. @@ -130,8 +127,7 @@ pub trait EventHandler { /// A gamepad axis moved; `id` identifies which gamepad. /// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about /// the gamepad. - fn gamepad_axis_event(&mut self, _ctx: &mut Context, _axis: Axis, _value: f32, _id: GamepadId) { - } + fn gamepad_axis_event(&mut self, _ctx: &mut Context, _axis: Axis, _value: f32, _id: GamepadId) {} /// Called when the window is shown or hidden. fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) {} @@ -155,24 +151,25 @@ pub fn quit(ctx: &mut Context) { ctx.continuing = false; } +/* /// Runs the game's main loop, calling event callbacks on the given state /// object as events occur. /// /// It does not try to do any type of framerate limiting. See the /// documentation for the [`timer`](../timer/index.html) module for more info. -pub fn run(ctx: &mut Context, events_loop: &mut EventsLoop, state: &mut S) -> GameResult -where - S: EventHandler, +pub fn run(ctx: &'static mut Context, events_loop: &mut EventLoop<()>, state: &'static mut S) -> GameResult + where + S: EventHandler, { use crate::ggez::input::{keyboard, mouse}; + // If you are writing your own event loop, make sure + // you include `timer_context.tick()` and + // `ctx.process_event()` calls. These update ggez's + // internal state however necessary. while ctx.continuing { - // If you are writing your own event loop, make sure - // you include `timer_context.tick()` and - // `ctx.process_event()` calls. These update ggez's - // internal state however necessary. - ctx.timer_context.tick(); - events_loop.poll_events(|event| { + events_loop.run_return(|event, _target, _flow| { + ctx.timer_context.tick(); ctx.process_event(&event); match event { Event::WindowEvent { event, .. } => match event { @@ -197,12 +194,12 @@ where } WindowEvent::KeyboardInput { input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(keycode), - modifiers, - .. - }, + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(keycode), + modifiers, + .. + }, .. } => { let repeat = keyboard::is_key_repeated(ctx); @@ -210,12 +207,12 @@ where } WindowEvent::KeyboardInput { input: - KeyboardInput { - state: ElementState::Released, - virtual_keycode: Some(keycode), - modifiers, - .. - }, + KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(keycode), + modifiers, + .. + }, .. } => { state.key_up_event(ctx, keycode, modifiers.into()); @@ -256,30 +253,38 @@ where Event::DeviceEvent { event, .. } => match event { _ => (), }, - Event::Awakened => (), - Event::Suspended(_) => (), + Event::Resumed => (), + Event::Suspended => (), + Event::RedrawRequested(win) => { + if win == ctx.gfx_context.window.window().id() { + state.draw(ctx).unwrap(); + } + } + Event::MainEventsCleared => { + // Handle gamepad events if necessary. + if ctx.conf.modules.gamepad { + while let Some(gilrs::Event { id, event, .. }) = ctx.gamepad_context.next_event() { + match event { + gilrs::EventType::ButtonPressed(button, _) => { + state.gamepad_button_down_event(ctx, button, GamepadId(id)); + } + gilrs::EventType::ButtonReleased(button, _) => { + state.gamepad_button_up_event(ctx, button, GamepadId(id)); + } + gilrs::EventType::AxisChanged(axis, value, _) => { + state.gamepad_axis_event(ctx, axis, value, GamepadId(id)); + } + _ => {} + } + } + } + state.update(ctx).unwrap(); + window(ctx).window().request_redraw(); + } + _ => {} } }); - // Handle gamepad events if necessary. - if ctx.conf.modules.gamepad { - while let Some(gilrs::Event { id, event, .. }) = ctx.gamepad_context.next_event() { - match event { - gilrs::EventType::ButtonPressed(button, _) => { - state.gamepad_button_down_event(ctx, button, GamepadId(id)); - } - gilrs::EventType::ButtonReleased(button, _) => { - state.gamepad_button_up_event(ctx, button, GamepadId(id)); - } - gilrs::EventType::AxisChanged(axis, value, _) => { - state.gamepad_axis_event(ctx, axis, value, GamepadId(id)); - } - _ => {} - } - } - } - state.update(ctx)?; - state.draw(ctx)?; } Ok(()) -} +}*/ diff --git a/src/ggez/graphics/context.rs b/src/ggez/graphics/context.rs index f3a0d87..948fe33 100644 --- a/src/ggez/graphics/context.rs +++ b/src/ggez/graphics/context.rs @@ -1,18 +1,20 @@ use std::cell::RefCell; +use std::path::Path; use std::rc::Rc; -use gfx::traits::FactoryExt; use gfx::Factory; +use gfx::traits::FactoryExt; use glutin; -use glyph_brush::{GlyphBrush, GlyphBrushBuilder}; +use glutin::PossiblyCurrent; use winit::{self, dpi}; +use winit::window::Fullscreen; use crate::ggez::conf::{FullscreenType, WindowMode, WindowSetup}; use crate::ggez::context::DebugId; -use crate::ggez::filesystem::Filesystem; -use crate::ggez::graphics::*; - use crate::ggez::error::GameResult; +use crate::ggez::filesystem::Filesystem; +use crate::ggez::GameError; +use crate::ggez::graphics::*; /// A structure that contains graphics state. /// For instance, @@ -20,8 +22,8 @@ use crate::ggez::error::GameResult; /// /// As an end-user you shouldn't ever have to touch this. pub(crate) struct GraphicsContextGeneric -where - B: BackendSpec, + where + B: BackendSpec, { shader_globals: Globals, pub(crate) projection: Matrix4, @@ -30,10 +32,9 @@ where pub(crate) screen_rect: Rect, color_format: gfx::format::Format, depth_format: gfx::format::Format, - srgb: bool, pub(crate) backend_spec: B, - pub(crate) window: glutin::WindowedContext, + pub(crate) window: glutin::WindowedContext, pub(crate) multisample_samples: u8, pub(crate) device: Box, pub(crate) factory: Box, @@ -52,15 +53,11 @@ where default_shader: ShaderId, pub(crate) current_shader: Rc>>, pub(crate) shaders: Vec>>, - - pub(crate) glyph_brush: GlyphBrush<'static, DrawParam>, - pub(crate) glyph_cache: ImageGeneric, - pub(crate) glyph_state: Rc>, } impl fmt::Debug for GraphicsContextGeneric -where - B: BackendSpec, + where + B: BackendSpec, { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "", self) @@ -74,24 +71,16 @@ impl GraphicsContextGeneric { /// Create a new GraphicsContext pub(crate) fn new( filesystem: &mut Filesystem, - events_loop: &winit::EventsLoop, + events_loop: &winit::event_loop::EventLoopWindowTarget<()>, window_setup: &WindowSetup, window_mode: WindowMode, backend: GlBackendSpec, debug_id: DebugId, ) -> GameResult { - let srgb = window_setup.srgb; - let color_format = if srgb { - gfx::format::Format( - gfx::format::SurfaceType::R8_G8_B8_A8, - gfx::format::ChannelType::Srgb, - ) - } else { - gfx::format::Format( - gfx::format::SurfaceType::R8_G8_B8_A8, - gfx::format::ChannelType::Unorm, - ) - }; + let color_format = gfx::format::Format( + gfx::format::SurfaceType::R8_G8_B8_A8, + gfx::format::ChannelType::Unorm, + ); let depth_format = gfx::format::Format( gfx::format::SurfaceType::D24_S8, gfx::format::ChannelType::Unorm, @@ -110,10 +99,10 @@ impl GraphicsContextGeneric { .with_vsync(window_setup.vsync); let window_size = - dpi::LogicalSize::from((f64::from(window_mode.width), f64::from(window_mode.height))); - let mut window_builder = winit::WindowBuilder::new() + dpi::LogicalSize::::from((f64::from(window_mode.width), f64::from(window_mode.height))); + let mut window_builder = glutin::window::WindowBuilder::new() .with_title(window_setup.title.clone()) - .with_dimensions(window_size) + .with_inner_size(window_size) .with_resizable(window_mode.resizable); window_builder = if !window_setup.icon.is_empty() { @@ -136,19 +125,15 @@ impl GraphicsContextGeneric { // since we have no good control over it. { // Log a bunch of OpenGL state info pulled out of winit and gfx - let dpi::LogicalSize { + let dpi::PhysicalSize { width: w, height: h, - } = window - .get_outer_size() - .ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?; - let dpi::LogicalSize { + } = window.window().outer_size(); + let dpi::PhysicalSize { width: dw, height: dh, - } = window - .get_inner_size() - .ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?; - let hidpi_factor = window.get_hidpi_factor(); + } = window.window().inner_size(); + let hidpi_factor = window.window().scale_factor(); debug!( "Window created, desired size {}x{}, hidpi factor {}.", window_mode.width, window_mode.height, hidpi_factor @@ -226,33 +211,6 @@ impl GraphicsContextGeneric { let texture = white_image.texture.clone(); let typed_thingy = backend.raw_to_typed_shader_resource(texture); - let data = pipe::Data { - vbuf: quad_vertex_buffer.clone(), - tex: (typed_thingy, sampler), - rect_instance_properties: rect_inst_props, - globals: globals_buffer, - out: screen_render_target.clone(), - }; - - // Glyph cache stuff. - let glyph_brush = - GlyphBrushBuilder::using_font_bytes(Font::default_font_bytes().to_vec()).build(); - let (glyph_cache_width, glyph_cache_height) = glyph_brush.texture_dimensions(); - let initial_contents = - vec![255; 4 * glyph_cache_width as usize * glyph_cache_height as usize]; - let glyph_cache = ImageGeneric::make_raw( - &mut factory, - &sampler_info, - glyph_cache_width as u16, - glyph_cache_height as u16, - &initial_contents, - color_format, - debug_id, - )?; - let glyph_state = Rc::new(RefCell::new(spritebatch::SpriteBatch::new( - glyph_cache.clone(), - ))); - // Set initial uniform values let left = 0.0; let right = window_mode.width; @@ -264,6 +222,15 @@ impl GraphicsContextGeneric { mvp_matrix: initial_projection.into(), }; + let data = pipe::Data { + vbuf: quad_vertex_buffer.clone(), + mvp: globals.mvp_matrix.into(), + tex: (typed_thingy, sampler), + rect_instance_properties: rect_inst_props, + globals: globals_buffer, + out: screen_render_target.clone(), + }; + let mut gfx = Self { shader_globals: globals, projection: initial_projection, @@ -272,7 +239,6 @@ impl GraphicsContextGeneric { screen_rect: Rect::new(left, top, right - left, bottom - top), color_format, depth_format, - srgb, backend_spec: backend, window, @@ -293,10 +259,6 @@ impl GraphicsContextGeneric { default_shader: shader.shader_id(), current_shader: Rc::new(RefCell::new(None)), shaders: vec![draw], - - glyph_brush, - glyph_cache, - glyph_state, }; gfx.set_window_mode(window_mode)?; @@ -320,11 +282,11 @@ impl GraphicsContextGeneric { // but still better than // having `winit` try to do the image loading for us. // see https://github.com/tomaka/winit/issues/661 -pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameResult { +pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameResult { use ::image; use ::image::GenericImageView; use std::io::Read; - use winit::Icon; + use winit::window::Icon; let mut buf = Vec::new(); let mut reader = filesystem.open(icon_file)?; @@ -338,14 +300,13 @@ pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameRe } impl GraphicsContextGeneric -where - B: BackendSpec + 'static, + where + B: BackendSpec + 'static, { /// Sends the current value of the graphics context's shader globals /// to the graphics card. pub(crate) fn update_globals(&mut self) -> GameResult { - self.encoder - .update_buffer(&self.data.globals, &[self.shader_globals], 0)?; + self.encoder.update_constant_buffer(&self.data.globals, &self.shader_globals); Ok(()) } @@ -358,6 +319,7 @@ where .last() .expect("Transform stack empty; should never happen"); let mvp = self.projection * modelview; + self.data.mvp = mvp.into(); self.shader_globals.mvp_matrix = mvp.into(); } @@ -406,7 +368,7 @@ where pub(crate) fn update_instance_properties(&mut self, draw_params: DrawTransform) -> GameResult { let mut new_draw_params = draw_params; new_draw_params.color = draw_params.color; - let properties = new_draw_params.to_instance_properties(self.srgb); + let properties = new_draw_params.to_instance_properties(); self.encoder .update_buffer(&self.data.rect_instance_properties, &[properties], 0)?; Ok(()) @@ -509,7 +471,7 @@ where /// Sets window mode from a WindowMode object. pub(crate) fn set_window_mode(&mut self, mode: WindowMode) -> GameResult { - let window = &self.window; + let window = &self.window.window(); window.set_maximized(mode.maximized); @@ -522,7 +484,7 @@ where } else { None }; - window.set_min_dimensions(min_dimensions); + window.set_min_inner_size(min_dimensions); let max_dimensions = if mode.max_width > 0.0 && mode.max_height > 0.0 { Some(dpi::LogicalSize { @@ -532,9 +494,10 @@ where } else { None }; - window.set_max_dimensions(max_dimensions); + window.set_max_inner_size(max_dimensions); - let monitor = window.get_current_monitor(); + let monitor = window.current_monitor(); + #[cfg(not(target_os = "android"))] match mode.fullscreen_type { FullscreenType::Windowed => { window.set_fullscreen(None); @@ -546,20 +509,23 @@ where window.set_resizable(mode.resizable); } FullscreenType::True => { - window.set_fullscreen(Some(monitor)); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); window.set_inner_size(dpi::LogicalSize { width: f64::from(mode.width), height: f64::from(mode.height), }); } FullscreenType::Desktop => { - let position = monitor.get_position(); - let dimensions = monitor.get_dimensions(); - let hidpi_factor = window.get_hidpi_factor(); - window.set_fullscreen(None); - window.set_decorations(false); - window.set_inner_size(dimensions.to_logical(hidpi_factor)); - window.set_position(position.to_logical(hidpi_factor)); + if let Some(monitor) = monitor { + let position = monitor.position(); + let dimensions = monitor.size(); + let hidpi_factor = window.scale_factor(); + + window.set_fullscreen(None); + window.set_decorations(false); + window.set_inner_size(dimensions.to_logical::(hidpi_factor)); + window.set_outer_position(position.to_logical::(hidpi_factor)); + } } } Ok(()) diff --git a/src/ggez/graphics/drawparam.rs b/src/ggez/graphics/drawparam.rs index a62efaa..c865479 100644 --- a/src/ggez/graphics/drawparam.rs +++ b/src/ggez/graphics/drawparam.rs @@ -244,14 +244,9 @@ impl From for DrawTransform { } impl DrawTransform { - pub(crate) fn to_instance_properties(&self, srgb: bool) -> InstanceProperties { + pub(crate) fn to_instance_properties(&self) -> InstanceProperties { let mat: [[f32; 4]; 4] = self.matrix.into(); - let color: [f32; 4] = if srgb { - let linear_color: types::LinearColor = self.color.into(); - linear_color.into() - } else { - self.color.into() - }; + let color: [f32; 4] = self.color.into(); InstanceProperties { src: self.src.into(), col1: mat[0], diff --git a/src/ggez/graphics/glutin_ext.rs b/src/ggez/graphics/glutin_ext.rs new file mode 100644 index 0000000..088351c --- /dev/null +++ b/src/ggez/graphics/glutin_ext.rs @@ -0,0 +1,200 @@ +//! Extensions for [glutin](https://crates.io/crates/glutin) to initialize & update old school +//! [gfx](https://crates.io/crates/gfx). _An alternative to gfx_window_glutin_. +//! +//! # Example +//! ```no_run +//! use old_school_gfx_glutin_ext::*; +//! +//! type ColorFormat = gfx::format::Srgba8; +//! type DepthFormat = gfx::format::DepthStencil; +//! +//! # fn main() -> Result<(), glutin::CreationError> { +//! # let event_loop = glutin::event_loop::EventLoop::new(); +//! # let window_config = glutin::window::WindowBuilder::new(); +//! // Initialize +//! let (window_ctx, mut device, mut factory, mut main_color, mut main_depth) = +//! glutin::ContextBuilder::new() +//! .with_gfx_color_depth::() +//! .build_windowed(window_config, &event_loop)? +//! .init_gfx::(); +//! +//! # let new_size = glutin::dpi::PhysicalSize::new(1, 1); +//! // Update, ie after a resize +//! window_ctx.update_gfx(&mut main_color, &mut main_depth); +//! # Ok(()) } +//! ``` + +#![allow(unsafe_code)] +use gfx_core::{ + format::{ChannelType, DepthFormat, Format, RenderFormat}, + handle::{DepthStencilView, RawDepthStencilView, RawRenderTargetView, RenderTargetView}, + memory::Typed, + texture, +}; +use gfx_device_gl::Resources as R; +use glutin::{NotCurrent, PossiblyCurrent}; + +type GfxInitTuple = ( + glutin::WindowedContext, + gfx_device_gl::Device, + gfx_device_gl::Factory, + RenderTargetView, + DepthStencilView, +); + +pub trait ContextBuilderExt { + /// Calls `with_pixel_format` & `with_srgb` according to the color format. + fn with_gfx_color(self) -> Self; + /// Calls `with_pixel_format` & `with_srgb` according to the color format. + fn with_gfx_color_raw(self, color_format: Format) -> Self; + /// Calls `with_depth_buffer` & `with_stencil_buffer` according to the depth format. + fn with_gfx_depth(self) -> Self; + /// Calls `with_depth_buffer` & `with_stencil_buffer` according to the depth format. + fn with_gfx_depth_raw(self, ds_format: Format) -> Self; + /// Calls `with_gfx_color` & `with_gfx_depth`. + fn with_gfx_color_depth(self) -> Self; +} + +impl ContextBuilderExt for glutin::ContextBuilder<'_, NotCurrent> { + fn with_gfx_color(self) -> Self { + self.with_gfx_color_raw(Color::get_format()) + } + + fn with_gfx_color_raw(self, Format(surface, channel): Format) -> Self { + let color_total_bits = surface.get_total_bits(); + let alpha_bits = surface.get_alpha_stencil_bits(); + + self.with_pixel_format(color_total_bits - alpha_bits, alpha_bits) + } + + fn with_gfx_depth(self) -> Self { + self.with_gfx_depth_raw(Depth::get_format()) + } + + fn with_gfx_depth_raw(self, Format(surface, _): Format) -> Self { + let depth_total_bits = surface.get_total_bits(); + let stencil_bits = surface.get_alpha_stencil_bits(); + + self.with_depth_buffer(depth_total_bits - stencil_bits) + .with_stencil_buffer(stencil_bits) + } + + fn with_gfx_color_depth(self) -> Self { + self.with_gfx_color::().with_gfx_depth::() + } +} + +pub trait WindowInitExt { + /// Make the context current, creates the gfx device, factory and views. + fn init_gfx(self) -> GfxInitTuple; + /// Make the context current, creates the gfx device, factory and views. + fn init_gfx_raw( + self, + color_format: Format, + ds_format: Format, + ) -> ( + glutin::WindowedContext, + gfx_device_gl::Device, + gfx_device_gl::Factory, + RawRenderTargetView, + RawDepthStencilView, + ); +} + +impl WindowInitExt for glutin::WindowedContext { + fn init_gfx(self) -> GfxInitTuple { + let (window, device, factory, color_view, ds_view) = + self.init_gfx_raw(Color::get_format(), Depth::get_format()); + ( + window, + device, + factory, + Typed::new(color_view), + Typed::new(ds_view), + ) + } + + fn init_gfx_raw( + self, + color_format: Format, + ds_format: Format, + ) -> ( + glutin::WindowedContext, + gfx_device_gl::Device, + gfx_device_gl::Factory, + RawRenderTargetView, + RawDepthStencilView, + ) { + let window = unsafe { self.make_current().unwrap() }; + let (device, factory) = + gfx_device_gl::create(|s| window.get_proc_address(s) as *const std::os::raw::c_void); + + let dim = get_window_dimensions(&window); + let (color_view, ds_view) = + gfx_device_gl::create_main_targets_raw(dim, color_format.0, ds_format.0); + + (window, device, factory, color_view, ds_view) + } +} + +pub trait WindowUpdateExt { + /// Recreates the views if the dimensions have changed. + fn update_gfx( + &self, + color_view: &mut RenderTargetView, + ds_view: &mut DepthStencilView, + ); + /// Return new main target views if the window resolution has changed from the old dimensions. + fn updated_views_raw( + &self, + old_dimensions: texture::Dimensions, + color_format: Format, + ds_format: Format, + ) -> Option<(RawRenderTargetView, RawDepthStencilView)>; +} + +impl WindowUpdateExt for glutin::WindowedContext { + fn update_gfx( + &self, + color_view: &mut RenderTargetView, + ds_view: &mut DepthStencilView, + ) { + let dim = color_view.get_dimensions(); + debug_assert_eq!(dim, ds_view.get_dimensions()); + if let Some((cv, dv)) = + self.updated_views_raw(dim, Color::get_format(), Depth::get_format()) + { + *color_view = Typed::new(cv); + *ds_view = Typed::new(dv); + } + } + + fn updated_views_raw( + &self, + old_dimensions: texture::Dimensions, + color_format: Format, + ds_format: Format, + ) -> Option<(RawRenderTargetView, RawDepthStencilView)> { + let dim = get_window_dimensions(self); + if dim != old_dimensions { + Some(gfx_device_gl::create_main_targets_raw( + dim, + color_format.0, + ds_format.0, + )) + } else { + None + } + } +} + +fn get_window_dimensions(ctx: &glutin::WindowedContext) -> texture::Dimensions { + let window = ctx.window(); + let (width, height) = { + let size = window.inner_size(); + (size.width as _, size.height as _) + }; + let aa = ctx.get_pixel_format().multisampling.unwrap_or(0) as texture::NumSamples; + + (width, height, 1, aa.into()) +} diff --git a/src/ggez/graphics/mod.rs b/src/ggez/graphics/mod.rs index db7403a..0fec44f 100644 --- a/src/ggez/graphics/mod.rs +++ b/src/ggez/graphics/mod.rs @@ -25,11 +25,17 @@ use std::path::Path; use std::u16; use gfx; -use gfx::texture; use gfx::Device; use gfx::Factory; +use gfx::texture; use gfx_device_gl; use glutin; +use glutin::{NotCurrent, PossiblyCurrent}; +pub use mint; +pub(crate) use nalgebra as na; +use winit::event_loop::EventLoopWindowTarget; + +use glutin_ext::*; use crate::ggez::conf; use crate::ggez::conf::WindowMode; @@ -37,29 +43,24 @@ use crate::ggez::context::Context; use crate::ggez::context::DebugId; use crate::ggez::GameError; use crate::ggez::GameResult; - -pub(crate) mod canvas; -pub(crate) mod context; -pub(crate) mod drawparam; -pub(crate) mod image; -pub(crate) mod mesh; -pub(crate) mod shader; -pub(crate) mod text; -pub(crate) mod types; - -pub use mint; -pub(crate) use nalgebra as na; - -pub mod spritebatch; - pub use crate::ggez::graphics::canvas::*; pub use crate::ggez::graphics::drawparam::*; pub use crate::ggez::graphics::image::*; pub use crate::ggez::graphics::mesh::*; pub use crate::ggez::graphics::shader::*; -pub use crate::ggez::graphics::text::*; pub use crate::ggez::graphics::types::*; +pub(crate) mod canvas; +pub(crate) mod context; +pub(crate) mod drawparam; +pub(crate) mod glutin_ext; +pub(crate) mod image; +pub(crate) mod mesh; +pub(crate) mod shader; +pub(crate) mod types; + +pub mod spritebatch; + // This isn't really particularly nice, but it's only used // in a couple places and it's not very easy to change or configure. // Since the next major project is "rewrite the graphics engine" I think @@ -68,9 +69,12 @@ pub use crate::ggez::graphics::types::*; // It exists basically because gfx-rs is incomplete and we can't *always* // specify texture formats and such entirely at runtime, which we need to // do to make sRGB handling work properly. -pub(crate) type BuggoSurfaceFormat = gfx::format::Srgba8; +pub(crate) type BuggoSurfaceFormat = gfx::format::Rgba8; type ShaderResourceType = [f32; 4]; +type ColorFormat = gfx::format::Rgba8; +type DepthFormat = gfx::format::DepthStencil; + /// A trait providing methods for working with a particular backend, such as OpenGL, /// with associated gfx-rs types for that backend. As a user you probably /// don't need to touch this unless you want to write a new graphics backend @@ -83,7 +87,7 @@ pub trait BackendSpec: fmt::Debug { /// gfx command buffer type type CommandBuffer: gfx::CommandBuffer; /// gfx device type - type Device: gfx::Device; + type Device: gfx::Device; /// A helper function to take a RawShaderResourceView and turn it into a typed one based on /// the surface type defined in a `BackendSpec`. @@ -104,27 +108,6 @@ pub trait BackendSpec: fmt::Debug { typed_view } - /// Helper function that turns a raw to typed texture. - /// A bit hacky since we can't really specify surface formats as part - /// of this that well, alas. There's some functions, like - /// `gfx::Encoder::update_texture()`, that don't seem to have a `_raw()` - /// counterpart, so we need this, so we need `BuggoSurfaceFormat` to - /// keep fixed at compile time what texture format we're actually using. - /// Oh well! - fn raw_to_typed_texture( - &self, - texture_view: gfx::handle::RawTexture, - ) -> gfx::handle::Texture< - ::Resources, - ::Surface, - > { - let typed_view: gfx::handle::Texture< - _, - ::Surface, - > = gfx::memory::Typed::new(texture_view); - typed_view - } - /// Returns the version of the backend, `(major, minor)`. /// /// So for instance if the backend is using OpenGL version 3.2, @@ -144,14 +127,14 @@ pub trait BackendSpec: fmt::Debug { /// Creates the window. fn init<'a>( &self, - window_builder: glutin::WindowBuilder, - gl_builder: glutin::ContextBuilder<'a>, - events_loop: &glutin::EventsLoop, + window_builder: glutin::window::WindowBuilder, + gl_builder: glutin::ContextBuilder<'a, NotCurrent>, + events_loop: &winit::event_loop::EventLoopWindowTarget<()>, color_format: gfx::format::Format, depth_format: gfx::format::Format, ) -> Result< ( - glutin::WindowedContext, + glutin::WindowedContext, Self::Device, Self::Factory, gfx::handle::RawRenderTargetView, @@ -170,7 +153,7 @@ pub trait BackendSpec: fmt::Debug { depth_view: &gfx::handle::RawDepthStencilView, color_format: gfx::format::Format, depth_format: gfx::format::Format, - window: &glutin::WindowedContext, + window: &glutin::WindowedContext, ) -> Option<( gfx::handle::RawRenderTargetView, gfx::handle::RawDepthStencilView, @@ -231,12 +214,12 @@ impl BackendSpec for GlBackendSpec { fn shaders(&self) -> (&'static [u8], &'static [u8]) { match self.api { glutin::Api::OpenGl => ( - include_bytes!("shader/basic_150.glslv"), - include_bytes!("shader/basic_150.glslf"), + include_bytes!("shader/basic_150.vert.glsl"), + include_bytes!("shader/basic_150.frag.glsl"), ), glutin::Api::OpenGlEs => ( - include_bytes!("shader/basic_es300.glslv"), - include_bytes!("shader/basic_es300.glslf"), + include_bytes!("shader/basic_es100.vert.glsl"), + include_bytes!("shader/basic_es100.frag.glsl"), ), a => panic!("Unsupported API: {:?}, should never happen", a), } @@ -244,14 +227,14 @@ impl BackendSpec for GlBackendSpec { fn init<'a>( &self, - window_builder: glutin::WindowBuilder, - gl_builder: glutin::ContextBuilder<'a>, - events_loop: &glutin::EventsLoop, + window_builder: glutin::window::WindowBuilder, + gl_builder: glutin::ContextBuilder<'a, NotCurrent>, + events_loop: &EventLoopWindowTarget<()>, color_format: gfx::format::Format, depth_format: gfx::format::Format, ) -> Result< ( - glutin::WindowedContext, + glutin::WindowedContext, Self::Device, Self::Factory, gfx::handle::RawRenderTargetView, @@ -259,13 +242,10 @@ impl BackendSpec for GlBackendSpec { ), glutin::CreationError, > { - gfx_window_glutin::init_raw( - window_builder, - gl_builder, - events_loop, - color_format, - depth_format, - ) + Ok(gl_builder + .with_gfx_color_depth::() + .build_windowed(window_builder, &events_loop)? + .init_gfx_raw(color_format, depth_format)) } fn info(&self, device: &Self::Device) -> String { @@ -289,7 +269,7 @@ impl BackendSpec for GlBackendSpec { depth_view: &gfx::handle::RawDepthStencilView, color_format: gfx::format::Format, depth_format: gfx::format::Format, - window: &glutin::WindowedContext, + window: &glutin::WindowedContext, ) -> Option<( gfx::handle::RawRenderTargetView, gfx::handle::RawDepthStencilView, @@ -298,8 +278,7 @@ impl BackendSpec for GlBackendSpec { // gfx_window_glutin::update_views() let dim = color_view.get_dimensions(); assert_eq!(dim, depth_view.get_dimensions()); - if let Some((cv, dv)) = - gfx_window_glutin::update_views_raw(window, dim, color_format, depth_format) + if let Some((cv, dv)) = window.updated_views_raw(dim, color_format, depth_format) { Some((cv, dv)) } else { @@ -366,6 +345,7 @@ gfx_defines! { // breaks the gfx_defines! macro though. :-( pipeline pipe { vbuf: gfx::VertexBuffer = (), + mvp: gfx::Global<[[f32; 4]; 4]> = "u_MVP", tex: gfx::TextureSampler<[f32; 4]> = "t_Texture", globals: gfx::ConstantBuffer = "Globals", rect_instance_properties: gfx::InstanceBuffer = (), @@ -373,7 +353,7 @@ gfx_defines! { // pipeline init values in `shader::create_shader()`. out: gfx::RawRenderTarget = ("Target0", - gfx::format::Format(gfx::format::SurfaceType::R8_G8_B8_A8, gfx::format::ChannelType::Srgb), + gfx::format::Format(gfx::format::SurfaceType::R8_G8_B8_A8, gfx::format::ChannelType::Unorm), gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA) ), } @@ -409,18 +389,19 @@ impl Default for InstanceProperties { } } } + /// A structure for conveniently storing `Sampler`'s, based off /// their `SamplerInfo`. pub(crate) struct SamplerCache -where - B: BackendSpec, + where + B: BackendSpec, { samplers: HashMap>, } impl SamplerCache -where - B: BackendSpec, + where + B: BackendSpec, { fn new() -> Self { SamplerCache { @@ -467,17 +448,16 @@ impl From for GameError { /// Clear the screen to the background color. pub fn clear(ctx: &mut Context, color: Color) { let gfx = &mut ctx.gfx_context; - let linear_color: types::LinearColor = color.into(); - let c: [f32; 4] = linear_color.into(); + let c: [f32; 4] = color.into(); gfx.encoder.clear_raw(&gfx.data.out, c.into()); } /// Draws the given `Drawable` object to the screen by calling its /// [`draw()`](trait.Drawable.html#tymethod.draw) method. pub fn draw(ctx: &mut Context, drawable: &D, params: T) -> GameResult -where - D: Drawable, - T: Into, + where + D: Drawable, + T: Into, { let params = params.into(); drawable.draw(ctx, params) @@ -646,8 +626,8 @@ pub fn set_screen_coordinates(context: &mut Context, rect: Rect) -> GameResult { /// after calling this to apply these changes and recalculate the /// underlying MVP matrix. pub fn set_projection(context: &mut Context, proj: M) -where - M: Into>, + where + M: Into>, { let proj = Matrix4::from(proj.into()); let gfx = &mut context.gfx_context; @@ -660,8 +640,8 @@ where /// after calling this to apply these changes and recalculate the /// underlying MVP matrix. pub fn mul_projection(context: &mut Context, transform: M) -where - M: Into>, + where + M: Into>, { let transform = Matrix4::from(transform.into()); let gfx = &mut context.gfx_context; @@ -698,8 +678,8 @@ pub fn projection(context: &Context) -> mint::ColumnMatrix4 { /// # } /// ``` pub fn push_transform(context: &mut Context, transform: Option) -where - M: Into>, + where + M: Into>, { let transform = transform.map(|transform| Matrix4::from(transform.into())); let gfx = &mut context.gfx_context; @@ -746,8 +726,8 @@ pub fn pop_transform(context: &mut Context) { /// # } /// ``` pub fn set_transform(context: &mut Context, transform: M) -where - M: Into>, + where + M: Into>, { let transform = transform.into(); let gfx = &mut context.gfx_context; @@ -782,8 +762,8 @@ pub fn transform(context: &Context) -> mint::ColumnMatrix4 { /// # } /// ``` pub fn mul_transform(context: &mut Context, transform: M) -where - M: Into>, + where + M: Into>, { let transform = Matrix4::from(transform.into()); let gfx = &mut context.gfx_context; @@ -863,20 +843,21 @@ pub fn set_window_icon>(context: &mut Context, path: Option

) - } None => None, }; - context.gfx_context.window.set_window_icon(icon); + + context.gfx_context.window.window().set_window_icon(icon); Ok(()) } /// Sets the window title. pub fn set_window_title(context: &Context, title: &str) { - context.gfx_context.window.set_title(title); + context.gfx_context.window.window().set_title(title); } /// Returns a reference to the Glutin window. /// Ideally you should not need to use this because ggez /// would provide all the functions you need without having /// to dip into Glutin itself. But life isn't always ideal. -pub fn window(context: &Context) -> &glutin::WindowedContext { +pub fn window(context: &Context) -> &glutin::WindowedContext { let gfx = &context.gfx_context; &gfx.window } @@ -886,20 +867,16 @@ pub fn window(context: &Context) -> &glutin::WindowedContext { /// Returns zeros if the window doesn't exist. pub fn size(context: &Context) -> (f32, f32) { let gfx = &context.gfx_context; - gfx.window - .get_outer_size() - .map(|logical_size| (logical_size.width as f32, logical_size.height as f32)) - .unwrap_or((0.0, 0.0)) + let size = gfx.window.window().outer_size(); + (size.width as f32, size.height as f32) } /// Returns the size of the window's underlying drawable in pixels as (width, height). /// Returns zeros if window doesn't exist. pub fn drawable_size(context: &Context) -> (f32, f32) { let gfx = &context.gfx_context; - gfx.window - .get_inner_size() - .map(|logical_size| (logical_size.width as f32, logical_size.height as f32)) - .unwrap_or((0.0, 0.0)) + let size = gfx.window.window().inner_size(); + (size.width as f32, size.height as f32) } /// Returns raw `gfx-rs` state objects, if you want to use `gfx-rs` to write @@ -971,10 +948,12 @@ pub fn transform_rect(rect: Rect, param: DrawParam) -> Rect { #[cfg(test)] mod tests { - use crate::graphics::{transform_rect, DrawParam, Rect}; - use approx::assert_relative_eq; use std::f32::consts::PI; + use approx::assert_relative_eq; + + use crate::graphics::{DrawParam, Rect, transform_rect}; + #[test] fn headless_test_transform_rect() { { diff --git a/src/ggez/graphics/shader/basic_150.glslf b/src/ggez/graphics/shader/basic_150.frag.glsl similarity index 100% rename from src/ggez/graphics/shader/basic_150.glslf rename to src/ggez/graphics/shader/basic_150.frag.glsl diff --git a/src/ggez/graphics/shader/basic_150.glslv b/src/ggez/graphics/shader/basic_150.vert.glsl similarity index 100% rename from src/ggez/graphics/shader/basic_150.glslv rename to src/ggez/graphics/shader/basic_150.vert.glsl diff --git a/src/ggez/graphics/shader/basic_es100.frag.glsl b/src/ggez/graphics/shader/basic_es100.frag.glsl new file mode 100644 index 0000000..9d48bd8 --- /dev/null +++ b/src/ggez/graphics/shader/basic_es100.frag.glsl @@ -0,0 +1,13 @@ +#version 100 + +uniform mediump sampler2D t_Texture; +varying mediump vec2 v_Uv; +varying mediump vec4 v_Color; + +//uniform mediump mat4 u_MVP; + +mediump vec4 Target0; + +void main() { + gl_FragColor = texture2D(t_Texture, v_Uv) * v_Color; +} diff --git a/src/ggez/graphics/shader/basic_es100.vert.glsl b/src/ggez/graphics/shader/basic_es100.vert.glsl new file mode 100644 index 0000000..ba03785 --- /dev/null +++ b/src/ggez/graphics/shader/basic_es100.vert.glsl @@ -0,0 +1,26 @@ +#version 100 + +attribute mediump vec2 a_Pos; +attribute mediump vec2 a_Uv; +attribute mediump vec4 a_VertColor; + +attribute mediump vec4 a_Src; +attribute mediump vec4 a_TCol1; +attribute mediump vec4 a_TCol2; +attribute mediump vec4 a_TCol3; +attribute mediump vec4 a_TCol4; +attribute mediump vec4 a_Color; + +uniform mediump mat4 u_MVP; + +varying mediump vec2 v_Uv; +varying mediump vec4 v_Color; + +void main() { + v_Uv = a_Uv * a_Src.zw + a_Src.xy; + v_Color = a_Color * a_VertColor; + mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4); + vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0); + + gl_Position = u_MVP * position; +} diff --git a/src/ggez/graphics/shader/basic_es300.glslf b/src/ggez/graphics/shader/basic_es300.glslf deleted file mode 100644 index 9496780..0000000 --- a/src/ggez/graphics/shader/basic_es300.glslf +++ /dev/null @@ -1,14 +0,0 @@ -#version 300 es - -uniform mediump sampler2D t_Texture; -in mediump vec2 v_Uv; -in mediump vec4 v_Color; -out mediump vec4 Target0; - -layout (std140) uniform Globals { - mediump mat4 u_MVP; -}; - -void main() { - Target0 = texture(t_Texture, v_Uv) * v_Color; -} diff --git a/src/ggez/graphics/shader/basic_es300.glslv b/src/ggez/graphics/shader/basic_es300.glslv deleted file mode 100644 index 044cec1..0000000 --- a/src/ggez/graphics/shader/basic_es300.glslv +++ /dev/null @@ -1,28 +0,0 @@ -#version 300 es - -in mediump vec2 a_Pos; -in mediump vec2 a_Uv; -in mediump vec4 a_VertColor; - -in mediump vec4 a_Src; -in mediump vec4 a_TCol1; -in mediump vec4 a_TCol2; -in mediump vec4 a_TCol3; -in mediump vec4 a_TCol4; -in mediump vec4 a_Color; - -layout (std140) uniform Globals { - mediump mat4 u_MVP; -}; - -out mediump vec2 v_Uv; -out mediump vec4 v_Color; - -void main() { - v_Uv = a_Uv * a_Src.zw + a_Src.xy; - v_Color = a_Color * a_VertColor; - mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4); - vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0); - - gl_Position = u_MVP * position; -} diff --git a/src/ggez/graphics/shader/glyphbrush.glslf b/src/ggez/graphics/shader/glyphbrush.glslf deleted file mode 100644 index cf37c6a..0000000 --- a/src/ggez/graphics/shader/glyphbrush.glslf +++ /dev/null @@ -1,16 +0,0 @@ -#version 150 - -uniform sampler2D font_tex; - -in vec2 f_tex_pos; -in vec4 f_color; - -out vec4 Target0; - -void main() { - float alpha = texture(font_tex, f_tex_pos).r; - if (alpha <= 0.0) { - discard; - } - Target0 = f_color * vec4(1.0, 1.0, 1.0, alpha); -} diff --git a/src/ggez/graphics/shader/glyphbrush.glslv b/src/ggez/graphics/shader/glyphbrush.glslv deleted file mode 100644 index 0dca978..0000000 --- a/src/ggez/graphics/shader/glyphbrush.glslv +++ /dev/null @@ -1,43 +0,0 @@ -#version 150 - -uniform mat4 transform; - -in vec3 left_top; -in vec2 right_bottom; -in vec2 tex_left_top; -in vec2 tex_right_bottom; -in vec4 color; - -out vec2 f_tex_pos; -out vec4 f_color; - -// generate positional data based on vertex ID -void main() { - vec2 pos = vec2(0.0); - float left = left_top.x; - float right = right_bottom.x; - float top = left_top.y; - float bottom = right_bottom.y; - - switch (gl_VertexID) { - case 0: - pos = vec2(left, top); - f_tex_pos = tex_left_top; - break; - case 1: - pos = vec2(right, top); - f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y); - break; - case 2: - pos = vec2(left, bottom); - f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y); - break; - case 3: - pos = vec2(right, bottom); - f_tex_pos = tex_right_bottom; - break; - } - - f_color = color; - gl_Position = transform * vec4(pos, left_top.z, 1.0); -} diff --git a/src/ggez/graphics/spritebatch.rs b/src/ggez/graphics/spritebatch.rs index 4a1058f..125c80f 100644 --- a/src/ggez/graphics/spritebatch.rs +++ b/src/ggez/graphics/spritebatch.rs @@ -108,7 +108,7 @@ impl SpriteBatch { new_param.scale = real_scale.into(); new_param.color = new_param.color; let primitive_param = graphics::DrawTransform::from(new_param); - primitive_param.to_instance_properties(ctx.gfx_context.is_srgb()) + primitive_param.to_instance_properties() }) .collect::>(); diff --git a/src/ggez/graphics/text.rs b/src/ggez/graphics/text.rs deleted file mode 100644 index f507619..0000000 --- a/src/ggez/graphics/text.rs +++ /dev/null @@ -1,717 +0,0 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::f32; -use std::fmt; -use std::io::Read; -use std::path; - -use glyph_brush::{self, FontId, Layout, SectionText, VariedSection}; -pub use glyph_brush::{HorizontalAlign as Align, rusttype::Scale}; -use glyph_brush::GlyphPositioner; -use mint; - -use crate::ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, Rect, WHITE, draw, Image, BackendSpec, GlBackendSpec, Point2}; -use crate::ggez::{Context, GameResult}; -use gfx::texture::ImageInfoCommon; - -/// Default size for fonts. -pub const DEFAULT_FONT_SCALE: f32 = 16.0; - -/// A handle referring to a loaded Truetype font. -/// -/// This is just an integer referring to a loaded font stored in the -/// `Context`, so is cheap to copy. Note that fonts are cached and -/// currently never *removed* from the cache, since that would -/// invalidate the whole cache and require re-loading all the other -/// fonts. So, you do not want to load a font more than once. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Font { - font_id: FontId, - // Add DebugId? It makes Font::default() less convenient. -} - -/// A piece of text with optional color, font and font scale information. -/// Drawing text generally involves one or more of these. -/// These options take precedence over any similar field/argument. -/// Implements `From` for `char`, `&str`, `String` and -/// `(String, Font, Scale)`. -#[derive(Clone, Debug)] -pub struct TextFragment { - /// Text string itself. - pub text: String, - /// Fragment's color, defaults to text's color. - pub color: Option, - /// Fragment's font, defaults to text's font. - pub font: Option, - /// Fragment's scale, defaults to text's scale. - pub scale: Option, -} - -impl Default for TextFragment { - fn default() -> Self { - TextFragment { - text: "".into(), - color: None, - font: None, - scale: None, - } - } -} - -impl TextFragment { - /// Creates a new fragment from `String` or `&str`. - pub fn new>(text: T) -> Self { - text.into() - } - - /// Set fragment's color, overrides text's color. - pub fn color(mut self, color: Color) -> TextFragment { - self.color = Some(color); - self - } - - /// Set fragment's font, overrides text's font. - pub fn font(mut self, font: Font) -> TextFragment { - self.font = Some(font); - self - } - - /// Set fragment's scale, overrides text's scale. - pub fn scale(mut self, scale: Scale) -> TextFragment { - self.scale = Some(scale); - self - } -} - -impl<'a> From<&'a str> for TextFragment { - fn from(text: &'a str) -> TextFragment { - TextFragment { - text: text.to_owned(), - ..Default::default() - } - } -} - -impl From for TextFragment { - fn from(ch: char) -> TextFragment { - TextFragment { - text: ch.to_string(), - ..Default::default() - } - } -} - -impl From for TextFragment { - fn from(text: String) -> TextFragment { - TextFragment { - text, - ..Default::default() - } - } -} - -impl From<(T, Font, f32)> for TextFragment - where - T: Into, -{ - fn from((text, font, scale): (T, Font, f32)) -> TextFragment { - text.into().font(font).scale(Scale::uniform(scale)) - } -} - -/// Cached font metrics that we can keep attached to a `Text` -/// so we don't have to keep recalculating them. -#[derive(Clone, Debug)] -struct CachedMetrics { - string: Option, - width: Option, - height: Option, -} - -impl Default for CachedMetrics { - fn default() -> CachedMetrics { - CachedMetrics { - string: None, - width: None, - height: None, - } - } -} - -/// Drawable text object. Essentially a list of [`TextFragment`](struct.TextFragment.html)'s -/// and some cached size information. -/// -/// It implements [`Drawable`](trait.Drawable.html) so it can be drawn immediately with -/// [`graphics::draw()`](fn.draw.html), or many of them can be queued with [`graphics::queue_text()`](fn.queue_text.html) -/// and then all drawn at once with [`graphics::draw_queued_text()`](fn.draw_queued_text.html). -#[derive(Debug, Clone)] -pub struct Text { - fragments: Vec, - blend_mode: Option, - filter_mode: FilterMode, - bounds: Point2, - layout: Layout, - font_id: FontId, - font_scale: Scale, - cached_metrics: RefCell, -} - -impl Default for Text { - fn default() -> Self { - Text { - fragments: Vec::new(), - blend_mode: None, - filter_mode: FilterMode::Linear, - bounds: Point2::new(f32::INFINITY, f32::INFINITY), - layout: Layout::default(), - font_id: FontId::default(), - font_scale: Scale::uniform(DEFAULT_FONT_SCALE), - cached_metrics: RefCell::new(CachedMetrics::default()), - } - } -} - -impl Text { - /// Creates a `Text` from a `TextFragment`. - /// - /// ```rust - /// # use ggez::graphics::Text; - /// # fn main() { - /// let text = Text::new("foo"); - /// # } - /// ``` - pub fn new(fragment: F) -> Text - where - F: Into, - { - let mut text = Text::default(); - let _ = text.add(fragment); - text - } - - /// Appends a `TextFragment` to the `Text`. - pub fn add(&mut self, fragment: F) -> &mut Text - where - F: Into, - { - self.fragments.push(fragment.into()); - self.invalidate_cached_metrics(); - self - } - - /// Returns a read-only slice of all `TextFragment`'s. - pub fn fragments(&self) -> &[TextFragment] { - &self.fragments - } - - /// Returns a mutable slice with all fragments. - pub fn fragments_mut(&mut self) -> &mut [TextFragment] { - &mut self.fragments - } - - /// Specifies rectangular dimensions to try and fit contents inside of, - /// by wrapping, and alignment within the bounds. To disable wrapping, - /// give it a layout with `f32::INF` for the x value. - pub fn set_bounds

(&mut self, bounds: P, alignment: Align) -> &mut Text - where - P: Into>, - { - self.bounds = Point2::from(bounds.into()); - if self.bounds.x == f32::INFINITY { - // Layouts don't make any sense if we don't wrap text at all. - self.layout = Layout::default(); - } else { - self.layout = self.layout.h_align(alignment); - } - self.invalidate_cached_metrics(); - self - } - - /// Specifies text's font and font scale; used for fragments that don't have their own. - pub fn set_font(&mut self, font: Font, font_scale: Scale) -> &mut Text { - self.font_id = font.font_id; - self.font_scale = font_scale; - self.invalidate_cached_metrics(); - self - } - - /// Converts `Text` to a type `glyph_brush` can understand and queue. - fn generate_varied_section( - &self, - relative_dest: Point2, - color: Option, - ) -> VariedSection { - let sections: Vec = self - .fragments - .iter() - .map(|fragment| { - let color = fragment.color.or(color).unwrap_or(WHITE); - let font_id = fragment - .font - .map(|font| font.font_id) - .unwrap_or(self.font_id); - let scale = fragment.scale.unwrap_or(self.font_scale); - SectionText { - text: &fragment.text, - color: <[f32; 4]>::from(color), - font_id, - scale, - } - }) - .collect(); - - let relative_dest_x = { - // This positions text within bounds with relative_dest being to the left, always. - let mut dest_x = relative_dest.x; - if self.bounds.x != f32::INFINITY { - use glyph_brush::Layout::Wrap; - match self.layout { - Wrap { - h_align: Align::Center, - .. - } => dest_x += self.bounds.x * 0.5, - Wrap { - h_align: Align::Right, - .. - } => dest_x += self.bounds.x, - _ => (), - } - } - dest_x - }; - let relative_dest = (relative_dest_x, relative_dest.y); - VariedSection { - screen_position: relative_dest, - bounds: (self.bounds.x, self.bounds.y), - layout: self.layout, - text: sections, - ..Default::default() - } - } - - fn invalidate_cached_metrics(&mut self) { - if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() { - *metrics = CachedMetrics::default(); - // Returning early avoids a double-borrow in the "else" - // part. - return; - } - warn!("Cached metrics RefCell has been poisoned."); - self.cached_metrics = RefCell::new(CachedMetrics::default()); - } - - /// Returns the string that the text represents. - pub fn contents(&self) -> String { - if let Ok(metrics) = self.cached_metrics.try_borrow() { - if let Some(ref string) = metrics.string { - return string.clone(); - } - } - let string_accm: String = self - .fragments - .iter() - .map(|frag| frag.text.as_str()) - .collect(); - - if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() { - metrics.string = Some(string_accm.clone()); - } - string_accm - } - - /// Calculates, caches, and returns width and height of formatted and wrapped text. - fn calculate_dimensions(&self, context: &mut Context) -> (u32, u32) { - let mut max_width = 0; - let mut max_height = 0; - { - let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None); - use glyph_brush::GlyphCruncher; - let glyphs = context.gfx_context.glyph_brush.glyphs(varied_section); - for positioned_glyph in glyphs { - if let Some(rect) = positioned_glyph.pixel_bounding_box() { - let font = positioned_glyph.font().expect("Glyph doesn't have a font"); - let v_metrics = font.v_metrics(positioned_glyph.scale()); - let max_y = positioned_glyph.position().y + positioned_glyph.scale().y - - v_metrics.ascent; - let max_y = max_y.ceil() as u32; - max_width = std::cmp::max(max_width, rect.max.x as u32); - max_height = std::cmp::max(max_height, max_y); - } - } - } - let (width, height) = (max_width, max_height); - if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() { - metrics.width = Some(width); - metrics.height = Some(height); - } - (width, height) - } - - /// Returns the width and height of the formatted and wrapped text. - pub fn dimensions(&self, context: &mut Context) -> (u32, u32) { - if let Ok(metrics) = self.cached_metrics.try_borrow() { - if let (Some(width), Some(height)) = (metrics.width, metrics.height) { - return (width, height); - } - } - self.calculate_dimensions(context) - } - - /// Returns the width of formatted and wrapped text, in screen coordinates. - pub fn width(&self, context: &mut Context) -> u32 { - self.dimensions(context).0 - } - - /// Returns the height of formatted and wrapped text, in screen coordinates. - pub fn height(&self, context: &mut Context) -> u32 { - self.dimensions(context).1 - } -} - -impl Drawable for Text { - fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult { - // Converts fraction-of-bounding-box to screen coordinates, as required by `draw_queued()`. - queue_text(ctx, self, Point2::new(0.0, 0.0), Some(param.color)); - draw_queued_text(ctx, param, self.blend_mode, self.filter_mode) - } - - fn dimensions(&self, ctx: &mut Context) -> Option { - let (w, h) = self.dimensions(ctx); - Some(Rect { - w: w as _, - h: h as _, - x: 0.0, - y: 0.0, - }) - } - - fn set_blend_mode(&mut self, mode: Option) { - self.blend_mode = mode; - } - - fn blend_mode(&self) -> Option { - self.blend_mode - } -} - -impl Font { - /// Load a new TTF font from the given file. - pub fn new

(context: &mut Context, path: P) -> GameResult - where - P: AsRef + fmt::Debug, - { - use crate::filesystem; - let mut stream = filesystem::open(context, path.as_ref())?; - let mut buf = Vec::new(); - let _ = stream.read_to_end(&mut buf)?; - - Font::new_glyph_font_bytes(context, &buf) - } - - /// Loads a new TrueType font from given bytes and into a `gfx::GlyphBrush` owned - /// by the `Context`. - pub fn new_glyph_font_bytes(context: &mut Context, bytes: &[u8]) -> GameResult { - // Take a Cow here to avoid this clone where unnecessary? - // Nah, let's not complicate things more than necessary. - let v = bytes.to_vec(); - let font_id = context.gfx_context.glyph_brush.add_font_bytes(v); - - Ok(Font { font_id }) - } - - /// Returns the baked-in bytes of default font (currently `DejaVuSerif.ttf`). - pub(crate) fn default_font_bytes() -> &'static [u8] { - include_bytes!("DejaVuSansMono.ttf") - } -} - -impl Default for Font { - fn default() -> Self { - Font { font_id: FontId(0) } - } -} - -/// Queues the `Text` to be drawn by [`draw_queued_text()`](fn.draw_queued_text.html). -/// `relative_dest` is relative to the [`DrawParam::dest`](struct.DrawParam.html#structfield.dest) -/// passed to `draw_queued()`. Note, any `Text` drawn via [`graphics::draw()`](fn.draw.html) -/// will also draw everything already the queue. -pub fn queue_text

(context: &mut Context, batch: &Text, relative_dest: P, color: Option) - where - P: Into>, -{ - let p = Point2::from(relative_dest.into()); - let varied_section = batch.generate_varied_section(p, color); - context.gfx_context.glyph_brush.queue(varied_section); -} - -/// Exposes `glyph_brush`'s drawing API in case `ggez`'s text drawing is insufficient. -/// It takes `glyph_brush`'s `VariedSection` and `GlyphPositioner`, which give you lower- -/// level control over how text is drawn. -pub fn queue_text_raw<'a, S, G>(context: &mut Context, section: S, custom_layout: Option<&G>) - where - S: Into>>, - G: GlyphPositioner, -{ - let brush = &mut context.gfx_context.glyph_brush; - match custom_layout { - Some(layout) => brush.queue_custom_layout(section, layout), - None => brush.queue(section), - } -} - -/// Draws all of the [`Text`](struct.Text.html)s added via [`queue_text()`](fn.queue_text.html). -/// -/// the `DrawParam` applies to everything in the queue; offset is in -/// screen coordinates; color is ignored - specify it when using -/// `queue_text()` instead. -/// -/// Note that all text will, and in fact must, be drawn with the same -/// `BlendMode` and `FilterMode`. This is unfortunate but currently -/// unavoidable, see [this issue](https://github.com/ggez/ggez/issues/561) -/// for more info. -pub fn draw_queued_text( - ctx: &mut Context, - param: D, - blend: Option, - filter: FilterMode, -) -> GameResult - where - D: Into, -{ - let param: DrawParam = param.into(); - - let gb = &mut ctx.gfx_context.glyph_brush; - let encoder = &mut ctx.gfx_context.encoder; - let gc = &ctx.gfx_context.glyph_cache.texture_handle; - let backend = &ctx.gfx_context.backend_spec; - - let action = gb.process_queued( - |rect, tex_data| update_texture::(backend, encoder, gc, rect, tex_data), - to_vertex, - ); - match action { - Ok(glyph_brush::BrushAction::ReDraw) => { - let spritebatch = ctx.gfx_context.glyph_state.clone(); - let spritebatch = &mut *spritebatch.borrow_mut(); - spritebatch.set_blend_mode(blend); - spritebatch.set_filter(filter); - draw(ctx, &*spritebatch, param)?; - } - Ok(glyph_brush::BrushAction::Draw(drawparams)) => { - // Gotta clone the image to avoid double-borrow's. - let spritebatch = ctx.gfx_context.glyph_state.clone(); - let spritebatch = &mut *spritebatch.borrow_mut(); - spritebatch.clear(); - spritebatch.set_blend_mode(blend); - spritebatch.set_filter(filter); - for p in &drawparams { - // Ignore returned sprite index. - let _ = spritebatch.add(*p); - } - draw(ctx, &*spritebatch, param)?; - } - Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => { - let (new_width, new_height) = suggested; - let data = vec![255; 4 * new_width as usize * new_height as usize]; - let new_glyph_cache = - Image::from_rgba8(ctx, new_width as u16, new_height as u16, &data)?; - ctx.gfx_context.glyph_cache = new_glyph_cache.clone(); - let spritebatch = ctx.gfx_context.glyph_state.clone(); - let spritebatch = &mut *spritebatch.borrow_mut(); - let _ = spritebatch.set_image(new_glyph_cache); - ctx.gfx_context - .glyph_brush - .resize_texture(new_width, new_height); - } - } - Ok(()) -} - -fn update_texture( - backend: &B, - encoder: &mut gfx::Encoder, - texture: &gfx::handle::RawTexture, - rect: glyph_brush::rusttype::Rect, - tex_data: &[u8], -) where - B: BackendSpec, -{ - let offset = [rect.min.x as u16, rect.min.y as u16]; - let size = [rect.width() as u16, rect.height() as u16]; - let info = ImageInfoCommon { - xoffset: offset[0], - yoffset: offset[1], - zoffset: 0, - width: size[0], - height: size[1], - depth: 0, - format: (), - mipmap: 0, - }; - - let tex_data_chunks: Vec<[u8; 4]> = tex_data.iter().map(|c| [255, 255, 255, *c]).collect(); - let typed_tex = backend.raw_to_typed_texture(texture.clone()); - encoder - .update_texture::<::Surface, super::BuggoSurfaceFormat>( - &typed_tex, None, info, &tex_data_chunks, - ) - .unwrap(); -} - -/// I THINK what we're going to need to do is have a -/// `SpriteBatch` that actually does the stuff and stores the -/// UV's and verts and such, while -/// -/// Basically, `glyph_brush`'s "`to_vertex`" callback is really -/// `to_quad`; in the default code it -fn to_vertex(v: glyph_brush::GlyphVertex) -> DrawParam { - let src_rect = Rect { - x: v.tex_coords.min.x, - y: v.tex_coords.min.y, - w: v.tex_coords.max.x - v.tex_coords.min.x, - h: v.tex_coords.max.y - v.tex_coords.min.y, - }; - // it LOOKS like pixel_coords are the output coordinates? - // I'm not sure though... - let dest_pt = Point2::new(v.pixel_coords.min.x as f32, v.pixel_coords.min.y as f32); - DrawParam::default() - .src(src_rect) - .dest(dest_pt) - .color(v.color.into()) -} - -#[cfg(test)] -mod tests { - /* - use super::*; - #[test] - fn test_metrics() { - let f = Font::default_font().expect("Could not get default font"); - assert_eq!(f.height(), 17); - assert_eq!(f.width("Foo!"), 33); - - // http://www.catipsum.com/index.php - let text_to_wrap = "Walk on car leaving trail of paw prints on hood and windshield sniff \ - other cat's butt and hang jaw half open thereafter for give attitude. \ - Annoy kitten\nbrother with poking. Mrow toy mouse squeak roll over. \ - Human give me attention meow."; - let (len, v) = f.wrap(text_to_wrap, 250); - println!("{} {:?}", len, v); - assert_eq!(len, 249); - - /* - let wrapped_text = vec![ - "Walk on car leaving trail of paw prints", - "on hood and windshield sniff other", - "cat\'s butt and hang jaw half open", - "thereafter for give attitude. Annoy", - "kitten", - "brother with poking. Mrow toy", - "mouse squeak roll over. Human give", - "me attention meow." - ]; - */ - let wrapped_text = vec![ - "Walk on car leaving trail of paw", - "prints on hood and windshield", - "sniff other cat\'s butt and hang jaw", - "half open thereafter for give", - "attitude. Annoy kitten", - "brother with poking. Mrow toy", - "mouse squeak roll over. Human", - "give me attention meow.", - ]; - - assert_eq!(&v, &wrapped_text); - } - - // We sadly can't have this test in the general case because it needs to create a Context, - // which creates a window, which fails on a headless server like our CI systems. :/ - //#[test] - #[allow(dead_code)] - fn test_wrapping() { - use conf; - let c = conf::Conf::new(); - let (ctx, _) = &mut Context::load_from_conf("test_wrapping", "ggez", c) - .expect("Could not create context?"); - let font = Font::default_font().expect("Could not get default font"); - let text_to_wrap = "Walk on car leaving trail of paw prints on hood and windshield sniff \ - other cat's butt and hang jaw half open thereafter for give attitude. \ - Annoy kitten\nbrother with poking. Mrow toy mouse squeak roll over. \ - Human give me attention meow."; - let wrap_length = 250; - let (len, v) = font.wrap(text_to_wrap, wrap_length); - assert!(len < wrap_length); - for line in &v { - let t = Text::new(ctx, line, &font).unwrap(); - println!( - "Width is claimed to be <= {}, should be <= {}, is {}", - len, - wrap_length, - t.width() - ); - // Why does this not match? x_X - //assert!(t.width() as usize <= len); - assert!(t.width() as usize <= wrap_length); - } - } - */ -} -/* -// Creates a gfx texture with the given data -fn create_texture( - factory: &mut F, - width: u32, - height: u32, -) -> Result<(TexSurfaceHandle, TexShaderView), Box> -where - R: gfx::Resources, - F: gfx::Factory, -{ - let kind = texture::Kind::D2( - width as texture::Size, - height as texture::Size, - texture::AaMode::Single, - ); - - let tex = factory.create_texture( - kind, - 1 as texture::Level, - gfx::memory::Bind::SHADER_RESOURCE, - gfx::memory::Usage::Dynamic, - Some(::get_channel_type()), - )?; - - let view = - factory.view_texture_as_shader_resource::(&tex, (0, 0), format::Swizzle::new())?; - - Ok((tex, view)) -} - -// Updates a texture with the given data (used for updating the GlyphCache texture) -#[inline] -fn update_texture( - encoder: &mut gfx::Encoder, - texture: &handle::Texture, - offset: [u16; 2], - size: [u16; 2], - data: &[u8], -) where - R: gfx::Resources, - C: gfx::CommandBuffer, -{ - let info = texture::ImageInfoCommon { - xoffset: offset[0], - yoffset: offset[1], - zoffset: 0, - width: size[0], - height: size[1], - depth: 0, - format: (), - mipmap: 0, - }; - encoder - .update_texture::(texture, None, info, data) - .unwrap(); -} -*/ diff --git a/src/ggez/input/keyboard.rs b/src/ggez/input/keyboard.rs index 09b4758..9ec18d1 100644 --- a/src/ggez/input/keyboard.rs +++ b/src/ggez/input/keyboard.rs @@ -68,9 +68,9 @@ use crate::ggez::context::Context; use std::collections::HashSet; -use winit::ModifiersState; +use winit::event::ModifiersState; /// A key code. -pub use winit::VirtualKeyCode as KeyCode; +pub use winit::event::VirtualKeyCode as KeyCode; bitflags! { /// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`. @@ -93,16 +93,16 @@ bitflags! { impl From for KeyMods { fn from(state: ModifiersState) -> Self { let mut keymod = KeyMods::empty(); - if state.shift { + if state.shift() { keymod |= Self::SHIFT; } - if state.ctrl { + if state.ctrl() { keymod |= Self::CTRL; } - if state.alt { + if state.alt() { keymod |= Self::ALT; } - if state.logo { + if state.logo() { keymod |= Self::LOGO; } keymod diff --git a/src/ggez/input/mouse.rs b/src/ggez/input/mouse.rs index 5ad31b1..5fc7e73 100644 --- a/src/ggez/input/mouse.rs +++ b/src/ggez/input/mouse.rs @@ -7,7 +7,7 @@ use crate::ggez::graphics; use crate::ggez::graphics::Point2; use std::collections::HashMap; use winit::dpi; -pub use winit::{MouseButton, MouseCursor}; +pub use winit::event::{MouseButton}; /// Stores state information for the mouse. #[derive(Clone, Debug)] @@ -15,7 +15,6 @@ pub struct MouseContext { last_position: Point2, last_delta: Point2, buttons_pressed: HashMap, - cursor_type: MouseCursor, cursor_grabbed: bool, cursor_hidden: bool, } @@ -25,7 +24,6 @@ impl MouseContext { Self { last_position: Point2::origin(), last_delta: Point2::origin(), - cursor_type: MouseCursor::Default, buttons_pressed: HashMap::new(), cursor_grabbed: false, cursor_hidden: false, @@ -55,17 +53,6 @@ impl Default for MouseContext { } } -/// Returns the current mouse cursor type of the window. -pub fn cursor_type(ctx: &Context) -> MouseCursor { - ctx.mouse_context.cursor_type -} - -/// Modifies the mouse cursor type of the window. -pub fn set_cursor_type(ctx: &mut Context, cursor_type: MouseCursor) { - ctx.mouse_context.cursor_type = cursor_type; - graphics::window(ctx).set_cursor(cursor_type); -} - /// Get whether or not the mouse is grabbed (confined to the window) pub fn cursor_grabbed(ctx: &Context) -> bool { ctx.mouse_context.cursor_grabbed @@ -74,8 +61,8 @@ pub fn cursor_grabbed(ctx: &Context) -> bool { /// Set whether or not the mouse is grabbed (confined to the window) pub fn set_cursor_grabbed(ctx: &mut Context, grabbed: bool) -> GameResult<()> { ctx.mouse_context.cursor_grabbed = grabbed; - graphics::window(ctx) - .grab_cursor(grabbed) + graphics::window(ctx).window() + .set_cursor_grab(grabbed) .map_err(|e| GameError::WindowError(e.to_string())) } @@ -87,7 +74,7 @@ pub fn cursor_hidden(ctx: &Context) -> bool { /// Set whether or not the mouse is hidden (invisible). pub fn set_cursor_hidden(ctx: &mut Context, hidden: bool) { ctx.mouse_context.cursor_hidden = hidden; - graphics::window(ctx).hide_cursor(hidden) + graphics::window(ctx).window().set_cursor_visible(!hidden) } /// Get the current position of the mouse cursor, in pixels. @@ -105,7 +92,7 @@ where { let mintpoint = point.into(); ctx.mouse_context.last_position = Point2::from(mintpoint); - graphics::window(ctx) + graphics::window(ctx).window() .set_cursor_position(dpi::LogicalPosition { x: f64::from(mintpoint.x), y: f64::from(mintpoint.y), diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ee27b96 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,409 @@ +#[macro_use] +extern crate bitflags; +#[macro_use] +extern crate gfx; +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde_derive; +#[macro_use] +extern crate smart_default; +extern crate strum; +#[macro_use] +extern crate strum_macros; + +use std::{env, mem}; +use std::path; +use std::time::Instant; + +use log::*; +use pretty_env_logger::env_logger::Env; +use winit::event::{ElementState, Event, KeyboardInput, WindowEvent, TouchPhase}; +use winit::event_loop::ControlFlow; + +use crate::builtin_fs::BuiltinFS; +use crate::ggez::{Context, ContextBuilder, filesystem, GameResult}; +use crate::ggez::conf::{Backend, WindowMode, WindowSetup}; +use crate::ggez::event::{KeyCode, KeyMods}; +use crate::ggez::graphics; +use crate::ggez::graphics::{Canvas, DrawParam, window}; +use crate::ggez::input::keyboard; +use crate::ggez::mint::ColumnMatrix4; +use crate::ggez::nalgebra::Vector2; +use crate::scene::loading_scene::LoadingScene; +use crate::scene::Scene; +use crate::shared_game_state::{SharedGameState, TimingMode}; +use crate::ui::UI; +use crate::ggez::event::winit_event::ModifiersState; + +mod bmfont; +mod bmfont_renderer; +mod builtin_fs; +mod bullet; +mod caret; +mod common; +mod encoding; +mod engine_constants; +mod entity; +mod frame; +mod inventory; +mod ggez; +mod live_debugger; +mod macros; +mod map; +mod menu; +mod npc; +mod physics; +mod player; +mod player_hit; +mod profile; +mod rng; +mod scene; +mod shared_game_state; +mod stage; +mod sound; +mod text_script; +mod texture_set; +mod ui; +mod weapon; + +struct Game { + scene: Option>, + state: SharedGameState, + ui: UI, + def_matrix: ColumnMatrix4, + start_time: Instant, + next_tick: u64, + loops: u64, +} + +impl Game { + fn new(ctx: &mut Context) -> GameResult { + let s = Game { + scene: None, + ui: UI::new(ctx)?, + def_matrix: DrawParam::new().to_matrix(), + state: SharedGameState::new(ctx)?, + start_time: Instant::now(), + next_tick: 0, + loops: 0, + }; + + Ok(s) + } + + fn update(&mut self, ctx: &mut Context) -> GameResult { + if let Some(scene) = self.scene.as_mut() { + match self.state.timing_mode { + TimingMode::_50Hz | TimingMode::_60Hz => { + while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 { + self.next_tick += self.state.timing_mode.get_delta() as u64; + self.loops += 1; + } + + for _ in 0..self.loops { + scene.tick(&mut self.state, ctx)?; + if self.state.settings.speed_hack { + scene.tick(&mut self.state, ctx)?; + } + } + } + TimingMode::FrameSynchronized => { + scene.tick(&mut self.state, ctx)?; + if self.state.settings.speed_hack { + scene.tick(&mut self.state, ctx)?; + } + } + } + } + Ok(()) + } + + fn draw(&mut self, ctx: &mut Context) -> GameResult { + graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into()); + graphics::set_transform(ctx, DrawParam::new() + .scale(Vector2::new(self.state.scale, self.state.scale)) + .to_matrix()); + graphics::apply_transformations(ctx)?; + + if let Some(scene) = self.scene.as_mut() { + scene.draw(&mut self.state, ctx)?; + + graphics::set_transform(ctx, self.def_matrix); + graphics::apply_transformations(ctx)?; + self.ui.draw(&mut self.state, ctx, scene)?; + } + + graphics::present(ctx)?; + self.loops = 0; + Ok(()) + } + + fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) { + if repeat { return; } + + // todo: proper keymaps? + let state = &mut self.state; + match key_code { + KeyCode::Left => { state.key_state.set_left(true) } + KeyCode::Right => { state.key_state.set_right(true) } + KeyCode::Up => { state.key_state.set_up(true) } + KeyCode::Down => { state.key_state.set_down(true) } + KeyCode::Z => { state.key_state.set_jump(true) } + KeyCode::X => { state.key_state.set_fire(true) } + KeyCode::A => { state.key_state.set_weapon_prev(true) } + KeyCode::S => { state.key_state.set_weapon_next(true) } + KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines } + KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode } + KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) } + _ => {} + } + } + + + fn key_up_event(&mut self, key_code: KeyCode, _key_mod: KeyMods) { + let state = &mut self.state; + + match key_code { + KeyCode::Left => { state.key_state.set_left(false) } + KeyCode::Right => { state.key_state.set_right(false) } + KeyCode::Up => { state.key_state.set_up(false) } + KeyCode::Down => { state.key_state.set_down(false) } + KeyCode::Z => { state.key_state.set_jump(false) } + KeyCode::X => { state.key_state.set_fire(false) } + KeyCode::A => { state.key_state.set_weapon_prev(false) } + KeyCode::S => { state.key_state.set_weapon_next(false) } + _ => {} + } + } +} + +#[cfg(target_os = "android")] +fn request_perms() -> GameResult { + use jni::objects::JValue; + use jni::objects::JObject; + + let native_activity = ndk_glue::native_activity(); + let vm_ptr = native_activity.vm(); + let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?; + let vm_env = vm.attach_current_thread()?; + + fn perm_name<'a, 'b, 'c>(vm_env: &'b jni::AttachGuard<'a>, name: &'c str) -> GameResult> { + let class = vm_env.find_class("android/Manifest$permission")?; + Ok(vm_env.get_static_field(class, name.to_owned(), "Ljava/lang/String;")?) + } + + fn has_permission(vm_env: &jni::AttachGuard, activity: &jni::sys::jobject, name: &str) -> GameResult { + let perm_granted = { + let class = vm_env.find_class("android/content/pm/PackageManager")?; + vm_env.get_static_field(class, "PERMISSION_GRANTED", "I")?.i()? + }; + + let perm = perm_name(vm_env, name)?; + let activity_obj = JObject::from(*activity); + let result = vm_env.call_method(activity_obj, "checkSelfPermission", "(Ljava/lang/String;)I", &[perm])?.i()?; + Ok(result == perm_granted) + } + + let str_class = vm_env.find_class("java/lang/String")?; + let array = vm_env.new_object_array(2, str_class, JObject::null())?; + vm_env.set_object_array_element(array, 0, perm_name(&vm_env, "READ_EXTERNAL_STORAGE")?.l()?)?; + vm_env.set_object_array_element(array, 1, perm_name(&vm_env, "WRITE_EXTERNAL_STORAGE")?.l()?)?; + let activity_obj = JObject::from(native_activity.activity()); + + loop { + if has_permission(&vm_env, &native_activity.activity(), "READ_EXTERNAL_STORAGE")? + && has_permission(&vm_env, &native_activity.activity(), "WRITE_EXTERNAL_STORAGE")? { + break; + } + + vm_env.call_method(activity_obj, "requestPermissions", "([Ljava/lang/String;I)V", &[JValue::from(array), JValue::from(0)])?; + } + + Ok(()) +} + +#[cfg(target_os = "android")] +#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))] +pub fn android_main() { + println!("main invoked."); + + request_perms().expect("Failed to attach to the JVM and request storage permissions."); + + env::set_var("CAVESTORY_DATA_DIR", "/storage/emulated/0/doukutsu"); + init().unwrap(); +} + +fn init_ctx>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult { + let backend = if cfg!(target_os = "android") { + Backend::OpenGLES { major: 2, minor: 0 } + } else { + Backend::OpenGL { major: 3, minor: 2 } + }; + + let mut ctx = ContextBuilder::new("doukutsu-rs") + .window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)")) + .window_mode(WindowMode::default() + .resizable(true) + .min_dimensions(320.0, 240.0) + .dimensions(854.0, 480.0)) + .add_resource_path(resource_dir) + .add_resource_path(path::PathBuf::from(str!("./"))) + .backend(backend) + .build(event_loop)?; + + ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new())); + + Ok(ctx) +} + +pub fn init() -> GameResult { + pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) + .filter(Some("gfx_device_gl::factory"), LevelFilter::Warn) + .init(); + + let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { + path::PathBuf::from(data_dir) + } else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { + let mut path = path::PathBuf::from(manifest_dir); + path.push("data"); + path + } else { + path::PathBuf::from("data") + }; + + info!("Resource directory: {:?}", resource_dir); + info!("Initializing engine..."); + + let event_loop = winit::event_loop::EventLoop::new(); + let mut context: Option = None; + let mut game: Option = None; + + #[cfg(target_os = "android")] + { + loop { + match ndk_glue::native_window().as_ref() { + Some(_) => { + println!("NativeScreen Found:{:?}", ndk_glue::native_window()); + break; + } + None => () + } + } + } + + context = Some(init_ctx(&event_loop, resource_dir.clone())?); + + event_loop.run(move |event, target, flow| { + if let Some(ctx) = &mut context { + ctx.timer_context.tick(); + ctx.process_event(&event); + + if let Some(game) = &mut game { + game.ui.handle_events(ctx, &event); + } else { + let mut new_game = Game::new(ctx).unwrap(); + new_game.state.next_scene = Some(Box::new(LoadingScene::new())); + game = Some(new_game); + } + } + + match event { + Event::Resumed => { + #[cfg(target_os = "android")] + if context.is_none() { + context = Some(init_ctx(target, resource_dir.clone()).unwrap()); + } + + if let Some(game) = &mut game { + game.loops = 0; + } + } + Event::Suspended => { + #[cfg(target_os = "android")] + { + context = None; + } + if let Some(game) = &mut game { + game.loops = 0; + } + } + Event::WindowEvent { event, .. } => { + match event { + WindowEvent::CloseRequested => { + if let Some(game) = &mut game { + game.state.shutdown(); + } + *flow = ControlFlow::Exit; + } + WindowEvent::Resized(_) => { + if let (Some(ctx), Some(game)) = (&mut context, &mut game) { + game.state.handle_resize(ctx).unwrap(); + game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap(); + //old_school_gfx_glutin_ext::WindowUpdateExt::update_gfx(graphics::window(ctx), &mut game.ui.main_color, &mut game.ui.main_depth); + } + } + WindowEvent::Touch(touch) => { + if let Some(game) = &mut game { + + } + } + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: el_state, + virtual_keycode: Some(keycode), + modifiers, + .. + }, + .. + } => { + if let (Some(ctx), Some(game)) = (&mut context, &mut game) { + match el_state { + ElementState::Pressed => { + let repeat = keyboard::is_key_repeated(ctx); + game.key_down_event( keycode, modifiers.into(), repeat); + } + ElementState::Released => { + game.key_up_event(keycode, modifiers.into()); + } + } + } + } + _ => {} + } + } + Event::RedrawRequested(win) => { + if let (Some(ctx), Some(game)) = (&mut context, &mut game) { + if win == window(ctx).window().id() { + game.draw(ctx).unwrap(); + } + } + } + Event::MainEventsCleared => { + if let (Some(ctx), Some(game)) = (&mut context, &mut game) { + game.update(ctx).unwrap(); + + #[cfg(target_os = "android")] + game.draw(ctx).unwrap(); // redraw request is unimplemented on shitdroid + + window(ctx).window().request_redraw(); + + if game.state.shutdown { + log::info!("Shutting down..."); + *flow = ControlFlow::Exit; + return; + } + + if game.state.next_scene.is_some() { + mem::swap(&mut game.scene, &mut game.state.next_scene); + game.state.next_scene = None; + + game.scene.as_mut().unwrap().init(&mut game.state, ctx).unwrap(); + } + } + } + _ => {} + } + }); +} diff --git a/src/main.rs b/src/main.rs index 116c95c..7a93c43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,268 +1,3 @@ -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate gfx; -#[macro_use] -extern crate log; -#[macro_use] -extern crate serde_derive; -#[macro_use] -extern crate smart_default; -extern crate strum; -#[macro_use] -extern crate strum_macros; - -use std::{env, mem}; -use std::path; -use std::time::Instant; - -use log::*; -use pretty_env_logger::env_logger::Env; -use winit::{ElementState, Event, KeyboardInput, WindowEvent}; - -use crate::builtin_fs::BuiltinFS; -use crate::ggez::{Context, ContextBuilder, filesystem, GameResult}; -use crate::ggez::conf::{WindowMode, WindowSetup}; -use crate::ggez::event::{KeyCode, KeyMods}; -use crate::ggez::graphics; -use crate::ggez::graphics::{Canvas, DrawParam}; -use crate::ggez::input::keyboard; -use crate::ggez::mint::ColumnMatrix4; -use crate::ggez::nalgebra::Vector2; -use crate::scene::loading_scene::LoadingScene; -use crate::scene::Scene; -use crate::shared_game_state::{SharedGameState, TimingMode}; -use crate::ui::UI; - -mod bmfont; -mod bmfont_renderer; -mod builtin_fs; -mod bullet; -mod caret; -mod common; -mod encoding; -mod engine_constants; -mod entity; -mod frame; -mod inventory; -mod ggez; -mod live_debugger; -mod macros; -mod map; -mod menu; -mod npc; -mod physics; -mod player; -mod player_hit; -mod profile; -mod rng; -mod scene; -mod shared_game_state; -mod stage; -mod sound; -mod text_script; -mod texture_set; -mod ui; -mod weapon; - -struct Game { - scene: Option>, - state: SharedGameState, - ui: UI, - def_matrix: ColumnMatrix4, - start_time: Instant, - next_tick: u64, - loops: u64, -} - -impl Game { - fn new(ctx: &mut Context) -> GameResult { - let s = Game { - scene: None, - ui: UI::new(ctx)?, - def_matrix: DrawParam::new().to_matrix(), - state: SharedGameState::new(ctx)?, - start_time: Instant::now(), - next_tick: 0, - loops: 0, - }; - - Ok(s) - } - - fn update(&mut self, ctx: &mut Context) -> GameResult { - if let Some(scene) = self.scene.as_mut() { - match self.state.timing_mode { - TimingMode::_50Hz | TimingMode::_60Hz => { - while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 { - self.next_tick += self.state.timing_mode.get_delta() as u64; - self.loops += 1; - } - - for _ in 0..self.loops { - scene.tick(&mut self.state, ctx)?; - if self.state.settings.speed_hack { - scene.tick(&mut self.state, ctx)?; - } - } - } - TimingMode::FrameSynchronized => { - scene.tick(&mut self.state, ctx)?; - if self.state.settings.speed_hack { - scene.tick(&mut self.state, ctx)?; - } - } - } - } - Ok(()) - } - - fn draw(&mut self, ctx: &mut Context) -> GameResult { - graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into()); - graphics::set_transform(ctx, DrawParam::new() - .scale(Vector2::new(self.state.scale, self.state.scale)) - .to_matrix()); - graphics::apply_transformations(ctx)?; - - if let Some(scene) = self.scene.as_mut() { - scene.draw(&mut self.state, ctx)?; - - graphics::set_transform(ctx, self.def_matrix); - graphics::apply_transformations(ctx)?; - self.ui.draw(&mut self.state, ctx, scene)?; - } - - graphics::present(ctx)?; - self.loops = 0; - Ok(()) - } - - fn key_down_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) { - if repeat { return; } - - // todo: proper keymaps? - let state = &mut self.state; - match key_code { - KeyCode::Left => { state.key_state.set_left(true) } - KeyCode::Right => { state.key_state.set_right(true) } - KeyCode::Up => { state.key_state.set_up(true) } - KeyCode::Down => { state.key_state.set_down(true) } - KeyCode::Z => { state.key_state.set_jump(true) } - KeyCode::X => { state.key_state.set_fire(true) } - KeyCode::A => { state.key_state.set_weapon_prev(true) } - KeyCode::S => { state.key_state.set_weapon_next(true) } - KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines } - KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode } - KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) } - _ => {} - } - } - - - fn key_up_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods) { - let state = &mut self.state; - - match key_code { - KeyCode::Left => { state.key_state.set_left(false) } - KeyCode::Right => { state.key_state.set_right(false) } - KeyCode::Up => { state.key_state.set_up(false) } - KeyCode::Down => { state.key_state.set_down(false) } - KeyCode::Z => { state.key_state.set_jump(false) } - KeyCode::X => { state.key_state.set_fire(false) } - KeyCode::A => { state.key_state.set_weapon_prev(false) } - KeyCode::S => { state.key_state.set_weapon_next(false) } - _ => {} - } - } -} - -pub fn main() -> GameResult { - pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info")) - .filter(Some("gfx_device_gl::factory"), LevelFilter::Warn) - .init(); - - let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") { - path::PathBuf::from(data_dir) - } else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let mut path = path::PathBuf::from(manifest_dir); - path.push("data"); - path - } else { - path::PathBuf::from("data") - }; - - info!("Resource directory: {:?}", resource_dir); - info!("Initializing engine..."); - - let cb = ContextBuilder::new("doukutsu-rs") - .window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)")) - .window_mode(WindowMode::default() - .resizable(true) - .min_dimensions(320.0, 240.0) - .dimensions(854.0, 480.0)) - .add_resource_path(resource_dir) - .add_resource_path(path::PathBuf::from(str!("./"))); - - let (ctx, event_loop) = &mut cb.build()?; - ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new())); - - let game = &mut Game::new(ctx)?; - game.state.next_scene = Some(Box::new(LoadingScene::new())); - - while ctx.continuing { - ctx.timer_context.tick(); - event_loop.poll_events(|event| { - ctx.process_event(&event); - game.ui.handle_events(ctx, &event); - - if let Event::WindowEvent { event, .. } = event { - match event { - WindowEvent::CloseRequested => { game.state.shutdown(); } - WindowEvent::Resized(_) => { - game.state.handle_resize(ctx).unwrap(); - game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap(); - gfx_window_glutin::update_views(graphics::window(ctx), &mut game.ui.main_color, &mut game.ui.main_depth); - } - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: el_state, - virtual_keycode: Some(keycode), - modifiers, - .. - }, - .. - } => { - match el_state { - ElementState::Pressed => { - let repeat = keyboard::is_key_repeated(ctx); - game.key_down_event(ctx, keycode, modifiers.into(), repeat); - } - ElementState::Released => { - game.key_up_event(ctx, keycode, modifiers.into()); - } - } - } - _ => {} - } - } - }); - - game.update(ctx)?; - game.draw(ctx)?; - - if game.state.shutdown { - log::info!("Shutting down..."); - break; - } - - if game.state.next_scene.is_some() { - mem::swap(&mut game.scene, &mut game.state.next_scene); - game.state.next_scene = None; - - game.scene.as_mut().unwrap().init(&mut game.state, ctx)?; - } - } - - Ok(()) +fn main() { + doukutsu_rs::init().unwrap(); } diff --git a/src/stage.rs b/src/stage.rs index 1c7750e..35778d5 100644 --- a/src/stage.rs +++ b/src/stage.rs @@ -197,14 +197,15 @@ impl StageData { // todo: refactor to make it less repetitive. pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult> { let stage_tbl_path = [root, "stage.tbl"].join(""); - let stage_dat_path = [root, "stage.dat"].join(""); + let stage_sect_path = [root, "stage.sect"].join(""); let mrmap_bin_path = [root, "mrmap.bin"].join(""); + let stage_dat_path = [root, "stage.dat"].join(""); if filesystem::exists(ctx, &stage_tbl_path) { // Cave Story+ stage table. let mut stages = Vec::new(); - info!("Loading CaveStory+/Booster's Lab style stage table from {}", &stage_tbl_path); + info!("Loading Cave Story+/Booster's Lab style stage table from {}", &stage_tbl_path); let mut data = Vec::new(); filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?; @@ -250,6 +251,59 @@ impl StageData { stages.push(stage); } + return Ok(stages); + } else if filesystem::exists(ctx, &stage_sect_path) { + // Cave Story freeware executable dump. + let mut stages = Vec::new(); + + info!("Loading Cave Story freeware exe dump style stage table from {}", &stage_sect_path); + + let mut data = Vec::new(); + filesystem::open(ctx, stage_sect_path)?.read_to_end(&mut data)?; + + let count = data.len() / 0xc8; + let mut f = Cursor::new(data); + for _ in 0..count { + let mut ts_buf = vec![0u8; 0x20]; + let mut map_buf = vec![0u8; 0x20]; + let mut back_buf = vec![0u8; 0x20]; + let mut npc1_buf = vec![0u8; 0x20]; + let mut npc2_buf = vec![0u8; 0x20]; + let mut name_buf = vec![0u8; 0x20]; + + f.read_exact(&mut ts_buf)?; + f.read_exact(&mut map_buf)?; + let bg_type = f.read_u32::()? as usize; + f.read_exact(&mut back_buf)?; + f.read_exact(&mut npc1_buf)?; + f.read_exact(&mut npc2_buf)?; + let boss_no = f.read_u8()? as usize; + f.read_exact(&mut name_buf)?; + // alignment + let _ = f.read_u8()?; + let _ = f.read_u8()?; + let _ = f.read_u8()?; + + let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]); + let map = from_shift_jis(&map_buf[0..zero_index(&map_buf)]); + let background = from_shift_jis(&back_buf[0..zero_index(&back_buf)]); + let npc1 = from_shift_jis(&npc1_buf[0..zero_index(&npc1_buf)]); + let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]); + let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]); + + let stage = StageData { + name: name.clone(), + map: map.clone(), + boss_no, + tileset: Tileset::new(&tileset), + background: Background::new(&background), + background_type: BackgroundType::new(bg_type), + npc1: NpcType::new(&npc1), + npc2: NpcType::new(&npc2), + }; + stages.push(stage); + } + return Ok(stages); } else if filesystem::exists(ctx, &mrmap_bin_path) { // CSE2E stage table diff --git a/src/text_script.rs b/src/text_script.rs index ce091c6..bd56dae 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -1124,8 +1124,6 @@ impl TextScriptVM { } else if let Some(dir) = Direction::from_int(direction) { npc.direction = dir; } - - break; } } } @@ -1182,8 +1180,6 @@ impl TextScriptVM { } tick_npc = *npc_id; - - break; } } } diff --git a/src/ui.rs b/src/ui.rs index d465cdb..d1be8d3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -122,7 +122,7 @@ impl UI { colors[ImGuiCol_ModalWindowDimBg as usize] = [0.20, 0.20, 0.20, 0.35]; let mut platform = WinitPlatform::init(&mut imgui); - platform.attach_window(imgui.io_mut(), graphics::window(ctx), HiDpiMode::Rounded); + platform.attach_window(imgui.io_mut(), graphics::window(ctx).window(), HiDpiMode::Rounded); let (factory, dev, _, depth, color) = graphics::gfx_objects(ctx); let shaders = { @@ -161,23 +161,25 @@ impl UI { }) } - pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::Event) { - self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx), &event); + pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::event::Event<()>) { + self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx).window(), &event); } pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box) -> GameResult { { let io = self.imgui.io_mut(); - self.platform.prepare_frame(io, graphics::window(ctx)).map_err(|e| RenderError(e))?; + self.platform.prepare_frame(io, graphics::window(ctx).window()) + .map_err(|e| RenderError(e.to_string()))?; - io.update_delta_time(self.last_frame); - self.last_frame = Instant::now(); + let now = Instant::now(); + io.update_delta_time(now - self.last_frame); + self.last_frame = now; } let mut ui = self.imgui.frame(); scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?; - self.platform.prepare_render(&ui, graphics::window(ctx)); + self.platform.prepare_render(&ui, graphics::window(ctx).window()); let draw_data = ui.render(); let (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx); self.renderer