mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-23 06:02:55 +00:00
Initial Android support and some ggez rewrite
This commit is contained in:
parent
8a478c6f72
commit
2f111919d6
6
.cargo/config
Normal file
6
.cargo/config
Normal file
|
@ -0,0 +1,6 @@
|
|||
[target.aarch64-linux-android]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-lc++_static",
|
||||
"-C", "link-arg=-lc++abi",
|
||||
"-C", "link-arg=-lEGL",
|
||||
]
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -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
|
||||
|
||||
|
|
53
Cargo.toml
53
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"
|
||||
|
|
BIN
src/builtin/touch.png
Normal file
BIN
src/builtin/touch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 967 B |
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Context> {
|
||||
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<dyn gamepad::GamepadContext> = if conf.modules.gamepad {
|
||||
Box::new(gamepad::GilrsGamepadContext::new()?)
|
||||
let gp: Box<dyn gamepad::GamepadContext> = 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<path::PathBuf>,
|
||||
pub(crate) memory_zip_files: Vec<Cow<'static, [u8]>>,
|
||||
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<Context> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -181,8 +181,8 @@ impl From<gfx::shade::ProgramError> for GameError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<winit::EventsLoopClosed> for GameError {
|
||||
fn from(_: glutin::EventsLoopClosed) -> GameError {
|
||||
impl<T> From<winit::event_loop::EventLoopClosed<T>> for GameError {
|
||||
fn from(_: winit::event_loop::EventLoopClosed<T>) -> 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<gilrs::Error> for GameError {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
impl From<jni::errors::Error> for GameError {
|
||||
fn from(e: jni::errors::Error) -> GameError {
|
||||
GameError::WindowError(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lyon::lyon_tessellation::TessellationError> for GameError {
|
||||
fn from(s: lyon::lyon_tessellation::TessellationError) -> GameError {
|
||||
let errstr = format!(
|
||||
|
|
|
@ -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<S>(ctx: &mut Context, events_loop: &mut EventsLoop, state: &mut S) -> GameResult
|
||||
where
|
||||
S: EventHandler,
|
||||
pub fn run<S>(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(())
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -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<B>
|
||||
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<PossiblyCurrent>,
|
||||
pub(crate) multisample_samples: u8,
|
||||
pub(crate) device: Box<B::Device>,
|
||||
pub(crate) factory: Box<B::Factory>,
|
||||
|
@ -52,15 +53,11 @@ where
|
|||
default_shader: ShaderId,
|
||||
pub(crate) current_shader: Rc<RefCell<Option<ShaderId>>>,
|
||||
pub(crate) shaders: Vec<Box<dyn ShaderHandle<B>>>,
|
||||
|
||||
pub(crate) glyph_brush: GlyphBrush<'static, DrawParam>,
|
||||
pub(crate) glyph_cache: ImageGeneric<B>,
|
||||
pub(crate) glyph_state: Rc<RefCell<spritebatch::SpriteBatch>>,
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for GraphicsContextGeneric<B>
|
||||
where
|
||||
B: BackendSpec,
|
||||
where
|
||||
B: BackendSpec,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "<GraphicsContext: {:p}>", self)
|
||||
|
@ -74,24 +71,16 @@ impl GraphicsContextGeneric<GlBackendSpec> {
|
|||
/// 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<Self> {
|
||||
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<GlBackendSpec> {
|
|||
.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::<f64>::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<GlBackendSpec> {
|
|||
// 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<GlBackendSpec> {
|
|||
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<GlBackendSpec> {
|
|||
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<GlBackendSpec> {
|
|||
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<GlBackendSpec> {
|
|||
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<GlBackendSpec> {
|
|||
// 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<winit::Icon> {
|
||||
pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameResult<winit::window::Icon> {
|
||||
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<B> GraphicsContextGeneric<B>
|
||||
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::<f64>(hidpi_factor));
|
||||
window.set_outer_position(position.to_logical::<f64>(hidpi_factor));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -244,14 +244,9 @@ impl From<DrawParam> 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],
|
||||
|
|
200
src/ggez/graphics/glutin_ext.rs
Normal file
200
src/ggez/graphics/glutin_ext.rs
Normal file
|
@ -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::<ColorFormat, DepthFormat>()
|
||||
//! .build_windowed(window_config, &event_loop)?
|
||||
//! .init_gfx::<ColorFormat, DepthFormat>();
|
||||
//!
|
||||
//! # 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<Color, Depth> = (
|
||||
glutin::WindowedContext<PossiblyCurrent>,
|
||||
gfx_device_gl::Device,
|
||||
gfx_device_gl::Factory,
|
||||
RenderTargetView<R, Color>,
|
||||
DepthStencilView<R, Depth>,
|
||||
);
|
||||
|
||||
pub trait ContextBuilderExt {
|
||||
/// Calls `with_pixel_format` & `with_srgb` according to the color format.
|
||||
fn with_gfx_color<Color: RenderFormat>(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<Depth: DepthFormat>(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<Color: RenderFormat, Depth: DepthFormat>(self) -> Self;
|
||||
}
|
||||
|
||||
impl ContextBuilderExt for glutin::ContextBuilder<'_, NotCurrent> {
|
||||
fn with_gfx_color<Color: RenderFormat>(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<Depth: DepthFormat>(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<Color: RenderFormat, Depth: DepthFormat>(self) -> Self {
|
||||
self.with_gfx_color::<Color>().with_gfx_depth::<Depth>()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WindowInitExt {
|
||||
/// Make the context current, creates the gfx device, factory and views.
|
||||
fn init_gfx<Color: RenderFormat, Depth: DepthFormat>(self) -> GfxInitTuple<Color, Depth>;
|
||||
/// Make the context current, creates the gfx device, factory and views.
|
||||
fn init_gfx_raw(
|
||||
self,
|
||||
color_format: Format,
|
||||
ds_format: Format,
|
||||
) -> (
|
||||
glutin::WindowedContext<PossiblyCurrent>,
|
||||
gfx_device_gl::Device,
|
||||
gfx_device_gl::Factory,
|
||||
RawRenderTargetView<R>,
|
||||
RawDepthStencilView<R>,
|
||||
);
|
||||
}
|
||||
|
||||
impl WindowInitExt for glutin::WindowedContext<NotCurrent> {
|
||||
fn init_gfx<Color: RenderFormat, Depth: DepthFormat>(self) -> GfxInitTuple<Color, Depth> {
|
||||
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<PossiblyCurrent>,
|
||||
gfx_device_gl::Device,
|
||||
gfx_device_gl::Factory,
|
||||
RawRenderTargetView<R>,
|
||||
RawDepthStencilView<R>,
|
||||
) {
|
||||
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<Color: RenderFormat, Depth: DepthFormat>(
|
||||
&self,
|
||||
color_view: &mut RenderTargetView<R, Color>,
|
||||
ds_view: &mut DepthStencilView<R, Depth>,
|
||||
);
|
||||
/// 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<R>, RawDepthStencilView<R>)>;
|
||||
}
|
||||
|
||||
impl WindowUpdateExt for glutin::WindowedContext<PossiblyCurrent> {
|
||||
fn update_gfx<Color: RenderFormat, Depth: DepthFormat>(
|
||||
&self,
|
||||
color_view: &mut RenderTargetView<R, Color>,
|
||||
ds_view: &mut DepthStencilView<R, Depth>,
|
||||
) {
|
||||
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<R>, RawDepthStencilView<R>)> {
|
||||
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<PossiblyCurrent>) -> 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())
|
||||
}
|
|
@ -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<Self::Resources>;
|
||||
/// gfx device type
|
||||
type Device: gfx::Device<Resources = Self::Resources, CommandBuffer = Self::CommandBuffer>;
|
||||
type Device: gfx::Device<Resources=Self::Resources, CommandBuffer=Self::CommandBuffer>;
|
||||
|
||||
/// 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<Self::Resources>,
|
||||
) -> gfx::handle::Texture<
|
||||
<Self as BackendSpec>::Resources,
|
||||
<BuggoSurfaceFormat as gfx::format::Formatted>::Surface,
|
||||
> {
|
||||
let typed_view: gfx::handle::Texture<
|
||||
_,
|
||||
<BuggoSurfaceFormat as gfx::format::Formatted>::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<PossiblyCurrent>,
|
||||
Self::Device,
|
||||
Self::Factory,
|
||||
gfx::handle::RawRenderTargetView<Self::Resources>,
|
||||
|
@ -170,7 +153,7 @@ pub trait BackendSpec: fmt::Debug {
|
|||
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
|
||||
color_format: gfx::format::Format,
|
||||
depth_format: gfx::format::Format,
|
||||
window: &glutin::WindowedContext,
|
||||
window: &glutin::WindowedContext<PossiblyCurrent>,
|
||||
) -> Option<(
|
||||
gfx::handle::RawRenderTargetView<Self::Resources>,
|
||||
gfx::handle::RawDepthStencilView<Self::Resources>,
|
||||
|
@ -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<PossiblyCurrent>,
|
||||
Self::Device,
|
||||
Self::Factory,
|
||||
gfx::handle::RawRenderTargetView<Self::Resources>,
|
||||
|
@ -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::<ColorFormat, DepthFormat>()
|
||||
.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<Self::Resources>,
|
||||
color_format: gfx::format::Format,
|
||||
depth_format: gfx::format::Format,
|
||||
window: &glutin::WindowedContext,
|
||||
window: &glutin::WindowedContext<PossiblyCurrent>,
|
||||
) -> Option<(
|
||||
gfx::handle::RawRenderTargetView<Self::Resources>,
|
||||
gfx::handle::RawDepthStencilView<Self::Resources>,
|
||||
|
@ -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<Vertex> = (),
|
||||
mvp: gfx::Global<[[f32; 4]; 4]> = "u_MVP",
|
||||
tex: gfx::TextureSampler<[f32; 4]> = "t_Texture",
|
||||
globals: gfx::ConstantBuffer<Globals> = "Globals",
|
||||
rect_instance_properties: gfx::InstanceBuffer<InstanceProperties> = (),
|
||||
|
@ -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<B>
|
||||
where
|
||||
B: BackendSpec,
|
||||
where
|
||||
B: BackendSpec,
|
||||
{
|
||||
samplers: HashMap<texture::SamplerInfo, gfx::handle::Sampler<B::Resources>>,
|
||||
}
|
||||
|
||||
impl<B> SamplerCache<B>
|
||||
where
|
||||
B: BackendSpec,
|
||||
where
|
||||
B: BackendSpec,
|
||||
{
|
||||
fn new() -> Self {
|
||||
SamplerCache {
|
||||
|
@ -467,17 +448,16 @@ impl From<gfx::buffer::CreationError> 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<D, T>(ctx: &mut Context, drawable: &D, params: T) -> GameResult
|
||||
where
|
||||
D: Drawable,
|
||||
T: Into<DrawParam>,
|
||||
where
|
||||
D: Drawable,
|
||||
T: Into<DrawParam>,
|
||||
{
|
||||
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<M>(context: &mut Context, proj: M)
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
{
|
||||
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<M>(context: &mut Context, transform: M)
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
{
|
||||
let transform = Matrix4::from(transform.into());
|
||||
let gfx = &mut context.gfx_context;
|
||||
|
@ -698,8 +678,8 @@ pub fn projection(context: &Context) -> mint::ColumnMatrix4<f32> {
|
|||
/// # }
|
||||
/// ```
|
||||
pub fn push_transform<M>(context: &mut Context, transform: Option<M>)
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
{
|
||||
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<M>(context: &mut Context, transform: M)
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
{
|
||||
let transform = transform.into();
|
||||
let gfx = &mut context.gfx_context;
|
||||
|
@ -782,8 +762,8 @@ pub fn transform(context: &Context) -> mint::ColumnMatrix4<f32> {
|
|||
/// # }
|
||||
/// ```
|
||||
pub fn mul_transform<M>(context: &mut Context, transform: M)
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
where
|
||||
M: Into<mint::ColumnMatrix4<f32>>,
|
||||
{
|
||||
let transform = Matrix4::from(transform.into());
|
||||
let gfx = &mut context.gfx_context;
|
||||
|
@ -863,20 +843,21 @@ pub fn set_window_icon<P: AsRef<Path>>(context: &mut Context, path: Option<P>) -
|
|||
}
|
||||
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<PossiblyCurrent> {
|
||||
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() {
|
||||
{
|
||||
|
|
13
src/ggez/graphics/shader/basic_es100.frag.glsl
Normal file
13
src/ggez/graphics/shader/basic_es100.frag.glsl
Normal file
|
@ -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;
|
||||
}
|
26
src/ggez/graphics/shader/basic_es100.vert.glsl
Normal file
26
src/ggez/graphics/shader/basic_es100.vert.glsl
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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::<Vec<_>>();
|
||||
|
||||
|
|
|
@ -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<Color>,
|
||||
/// Fragment's font, defaults to text's font.
|
||||
pub font: Option<Font>,
|
||||
/// Fragment's scale, defaults to text's scale.
|
||||
pub scale: Option<Scale>,
|
||||
}
|
||||
|
||||
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<T: Into<Self>>(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<char> for TextFragment {
|
||||
fn from(ch: char) -> TextFragment {
|
||||
TextFragment {
|
||||
text: ch.to_string(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for TextFragment {
|
||||
fn from(text: String) -> TextFragment {
|
||||
TextFragment {
|
||||
text,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<(T, Font, f32)> for TextFragment
|
||||
where
|
||||
T: Into<TextFragment>,
|
||||
{
|
||||
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<String>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
}
|
||||
|
||||
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<TextFragment>,
|
||||
blend_mode: Option<BlendMode>,
|
||||
filter_mode: FilterMode,
|
||||
bounds: Point2,
|
||||
layout: Layout<glyph_brush::BuiltInLineBreaker>,
|
||||
font_id: FontId,
|
||||
font_scale: Scale,
|
||||
cached_metrics: RefCell<CachedMetrics>,
|
||||
}
|
||||
|
||||
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<F>(fragment: F) -> Text
|
||||
where
|
||||
F: Into<TextFragment>,
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let _ = text.add(fragment);
|
||||
text
|
||||
}
|
||||
|
||||
/// Appends a `TextFragment` to the `Text`.
|
||||
pub fn add<F>(&mut self, fragment: F) -> &mut Text
|
||||
where
|
||||
F: Into<TextFragment>,
|
||||
{
|
||||
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<P>(&mut self, bounds: P, alignment: Align) -> &mut Text
|
||||
where
|
||||
P: Into<mint::Point2<f32>>,
|
||||
{
|
||||
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<Color>,
|
||||
) -> VariedSection {
|
||||
let sections: Vec<SectionText> = 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<Rect> {
|
||||
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<BlendMode>) {
|
||||
self.blend_mode = mode;
|
||||
}
|
||||
|
||||
fn blend_mode(&self) -> Option<BlendMode> {
|
||||
self.blend_mode
|
||||
}
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Load a new TTF font from the given file.
|
||||
pub fn new<P>(context: &mut Context, path: P) -> GameResult<Font>
|
||||
where
|
||||
P: AsRef<path::Path> + 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<Self> {
|
||||
// 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<P>(context: &mut Context, batch: &Text, relative_dest: P, color: Option<Color>)
|
||||
where
|
||||
P: Into<mint::Point2<f32>>,
|
||||
{
|
||||
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<Cow<'a, VariedSection<'a>>>,
|
||||
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<D>(
|
||||
ctx: &mut Context,
|
||||
param: D,
|
||||
blend: Option<BlendMode>,
|
||||
filter: FilterMode,
|
||||
) -> GameResult
|
||||
where
|
||||
D: Into<DrawParam>,
|
||||
{
|
||||
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::<GlBackendSpec>(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<B>(
|
||||
backend: &B,
|
||||
encoder: &mut gfx::Encoder<B::Resources, B::CommandBuffer>,
|
||||
texture: &gfx::handle::RawTexture<B::Resources>,
|
||||
rect: glyph_brush::rusttype::Rect<u32>,
|
||||
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::<<super::BuggoSurfaceFormat as gfx::format::Formatted>::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<F, R>(
|
||||
factory: &mut F,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(TexSurfaceHandle<R>, TexShaderView<R>), Box<dyn Error>>
|
||||
where
|
||||
R: gfx::Resources,
|
||||
F: gfx::Factory<R>,
|
||||
{
|
||||
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(<TexChannel as format::ChannelTyped>::get_channel_type()),
|
||||
)?;
|
||||
|
||||
let view =
|
||||
factory.view_texture_as_shader_resource::<TexForm>(&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<R, C>(
|
||||
encoder: &mut gfx::Encoder<R, C>,
|
||||
texture: &handle::Texture<R, TexSurface>,
|
||||
offset: [u16; 2],
|
||||
size: [u16; 2],
|
||||
data: &[u8],
|
||||
) where
|
||||
R: gfx::Resources,
|
||||
C: gfx::CommandBuffer<R>,
|
||||
{
|
||||
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::<TexSurface, TexForm>(texture, None, info, data)
|
||||
.unwrap();
|
||||
}
|
||||
*/
|
|
@ -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<ModifiersState> 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
|
||||
|
|
|
@ -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<MouseButton, bool>,
|
||||
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),
|
||||
|
|
409
src/lib.rs
Normal file
409
src/lib.rs
Normal file
|
@ -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<Box<dyn Scene>>,
|
||||
state: SharedGameState,
|
||||
ui: UI,
|
||||
def_matrix: ColumnMatrix4<f32>,
|
||||
start_time: Instant,
|
||||
next_tick: u64,
|
||||
loops: u64,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn new(ctx: &mut Context) -> GameResult<Game> {
|
||||
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<jni::objects::JValue<'a>> {
|
||||
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<bool> {
|
||||
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<P: Into<path::PathBuf>>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
|
||||
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<Context> = None;
|
||||
let mut game: Option<Game> = 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
269
src/main.rs
269
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<Box<dyn Scene>>,
|
||||
state: SharedGameState,
|
||||
ui: UI,
|
||||
def_matrix: ColumnMatrix4<f32>,
|
||||
start_time: Instant,
|
||||
next_tick: u64,
|
||||
loops: u64,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
fn new(ctx: &mut Context) -> GameResult<Game> {
|
||||
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();
|
||||
}
|
||||
|
|
58
src/stage.rs
58
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<Vec<Self>> {
|
||||
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::<LE>()? 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
src/ui.rs
16
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<dyn Scene>) -> 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
|
||||
|
|
Loading…
Reference in a new issue