mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-11-30 16:18:00 +00:00
Add frame cap configuration
This commit is contained in:
parent
339f822a80
commit
ca1d7a8642
|
|
@ -56,7 +56,20 @@
|
||||||
"subpixel_scrolling": "Subpixel scrolling:",
|
"subpixel_scrolling": "Subpixel scrolling:",
|
||||||
"original_textures": "Original textures:",
|
"original_textures": "Original textures:",
|
||||||
"seasonal_textures": "Seasonal textures:",
|
"seasonal_textures": "Seasonal textures:",
|
||||||
"renderer": "Renderer:"
|
"renderer": "Renderer:",
|
||||||
|
"vsync_mode": {
|
||||||
|
"entry": "V-Sync:",
|
||||||
|
"uncapped": "Uncapped",
|
||||||
|
"uncapped_desc": "No V-Sync, game runs at uncapped frame rate.",
|
||||||
|
"vsync": "Enabled",
|
||||||
|
"vsync_desc": "Frame rate is synchronized to V-Sync interval.",
|
||||||
|
"vrr_1x": "Variable Refresh Rate - 1x game tick",
|
||||||
|
"vrr_1x_desc": "Synced to 1*TPS, uses (G-/Free)Sync if available.",
|
||||||
|
"vrr_2x": "Variable Refresh Rate - 2x game tick",
|
||||||
|
"vrr_2x_desc": "Synced to 2*TPS, uses (G-/Free)Sync if available.",
|
||||||
|
"vrr_3x": "Variable Refresh Rate - 3x game tick",
|
||||||
|
"vrr_3x_desc": "Synced to 3*TPS, uses (G-/Free)Sync if available."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"sound": "Sound...",
|
"sound": "Sound...",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
"on": "オン",
|
"on": "オン",
|
||||||
"off": "オフ"
|
"off": "オフ"
|
||||||
},
|
},
|
||||||
|
|
||||||
"menus": {
|
"menus": {
|
||||||
"main_menu": {
|
"main_menu": {
|
||||||
"start": "ゲームスタート",
|
"start": "ゲームスタート",
|
||||||
|
|
@ -17,7 +16,6 @@
|
||||||
"jukebox": "ジュークボックス",
|
"jukebox": "ジュークボックス",
|
||||||
"quit": "辞める"
|
"quit": "辞める"
|
||||||
},
|
},
|
||||||
|
|
||||||
"pause_menu": {
|
"pause_menu": {
|
||||||
"resume": "再開",
|
"resume": "再開",
|
||||||
"retry": "リトライ",
|
"retry": "リトライ",
|
||||||
|
|
@ -27,26 +25,22 @@
|
||||||
"quit": "辞める",
|
"quit": "辞める",
|
||||||
"quit_confirm": "辞める?"
|
"quit_confirm": "辞める?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"save_menu": {
|
"save_menu": {
|
||||||
"new": "新しいデータ",
|
"new": "新しいデータ",
|
||||||
"delete_info": "右矢印キーで削除",
|
"delete_info": "右矢印キーで削除",
|
||||||
"delete_confirm": "消去?"
|
"delete_confirm": "消去?"
|
||||||
},
|
},
|
||||||
|
|
||||||
"difficulty_menu": {
|
"difficulty_menu": {
|
||||||
"title": "難易度選択",
|
"title": "難易度選択",
|
||||||
"easy": "簡単",
|
"easy": "簡単",
|
||||||
"normal": "普通",
|
"normal": "普通",
|
||||||
"hard": "難しい"
|
"hard": "難しい"
|
||||||
},
|
},
|
||||||
|
|
||||||
"challenge_menu": {
|
"challenge_menu": {
|
||||||
"start": "スタート",
|
"start": "スタート",
|
||||||
"no_replay": "ノーリプレイ",
|
"no_replay": "ノーリプレイ",
|
||||||
"replay_best": "ベストプレイを再生"
|
"replay_best": "ベストプレイを再生"
|
||||||
},
|
},
|
||||||
|
|
||||||
"options_menu": {
|
"options_menu": {
|
||||||
"graphics": "グラフィック",
|
"graphics": "グラフィック",
|
||||||
"graphics_menu": {
|
"graphics_menu": {
|
||||||
|
|
@ -56,9 +50,21 @@
|
||||||
"subpixel_scrolling": "サブピクセルスクロール:",
|
"subpixel_scrolling": "サブピクセルスクロール:",
|
||||||
"original_textures": "オリジナルテクスチャ:",
|
"original_textures": "オリジナルテクスチャ:",
|
||||||
"seasonal_textures": "季節ものテクスチャ:",
|
"seasonal_textures": "季節ものテクスチャ:",
|
||||||
"renderer": "レンダラ:"
|
"renderer": "レンダラ:",
|
||||||
|
"vsync_mode": {
|
||||||
|
"entry": "V-Sync:",
|
||||||
|
"uncapped": "Uncapped",
|
||||||
|
"uncapped_desc": "No V-Sync, game runs at uncapped frame rate.",
|
||||||
|
"vsync": "Enabled",
|
||||||
|
"vsync_desc": "Frame rate is synchronized to V-Sync interval.",
|
||||||
|
"vrr_1x": "Variable Refresh Rate - 1x game tick",
|
||||||
|
"vrr_1x_desc": "Synced to 1*TPS, uses (G-/Free)Sync if available.",
|
||||||
|
"vrr_2x": "Variable Refresh Rate - 2x game tick",
|
||||||
|
"vrr_2x_desc": "Synced to 2*TPS, uses (G-/Free)Sync if available.",
|
||||||
|
"vrr_3x": "Variable Refresh Rate - 3x game tick",
|
||||||
|
"vrr_3x_desc": "Synced to 3*TPS, uses (G-/Free)Sync if available."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"sound": "サウンド",
|
"sound": "サウンド",
|
||||||
"sound_menu": {
|
"sound_menu": {
|
||||||
"music_volume": "BGM音量",
|
"music_volume": "BGM音量",
|
||||||
|
|
@ -78,9 +84,7 @@
|
||||||
},
|
},
|
||||||
"soundtrack": "サウンドトラック: {soundtrack}"
|
"soundtrack": "サウンドトラック: {soundtrack}"
|
||||||
},
|
},
|
||||||
|
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
|
|
||||||
"game_timing": {
|
"game_timing": {
|
||||||
"entry": "ゲームのタイミング:",
|
"entry": "ゲームのタイミング:",
|
||||||
"50tps": "50tps (freeware)",
|
"50tps": "50tps (freeware)",
|
||||||
|
|
@ -88,14 +92,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"soundtrack": {
|
"soundtrack": {
|
||||||
"organya": "オルガーニャ",
|
"organya": "オルガーニャ",
|
||||||
"remastered": "リマスター",
|
"remastered": "リマスター",
|
||||||
"new": "新",
|
"new": "新",
|
||||||
"famitracks": "ファミトラック"
|
"famitracks": "ファミトラック"
|
||||||
},
|
},
|
||||||
|
|
||||||
"game": {
|
"game": {
|
||||||
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
|
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::graphics::BlendMode;
|
use crate::framework::graphics::BlendMode;
|
||||||
use crate::Game;
|
use crate::Game;
|
||||||
|
use crate::graphics::VSyncMode;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|
@ -30,7 +31,7 @@ pub trait Backend {
|
||||||
pub trait BackendEventLoop {
|
pub trait BackendEventLoop {
|
||||||
fn run(&mut self, game: &mut Game, ctx: &mut Context);
|
fn run(&mut self, game: &mut Game, ctx: &mut Context);
|
||||||
|
|
||||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>>;
|
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BackendRenderer {
|
pub trait BackendRenderer {
|
||||||
|
|
@ -40,6 +41,8 @@ pub trait BackendRenderer {
|
||||||
|
|
||||||
fn present(&mut self) -> GameResult;
|
fn present(&mut self) -> GameResult;
|
||||||
|
|
||||||
|
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult { Ok(()) }
|
||||||
|
|
||||||
fn prepare_draw(&mut self, _width: f32, _height: f32) -> GameResult {
|
fn prepare_draw(&mut self, _width: f32, _height: f32) -> GameResult {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ impl BackendEventLoop for GlutinEventLoop {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
|
fn new_renderer(&self, _ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
|
||||||
let mut imgui = imgui::Context::create();
|
let mut imgui = imgui::Context::create();
|
||||||
imgui.io_mut().display_size = [640.0, 480.0];
|
imgui.io_mut().display_size = [640.0, 480.0];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl BackendEventLoop for NullEventLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
|
fn new_renderer(&self, _ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
|
||||||
let mut imgui = imgui::Context::create();
|
let mut imgui = imgui::Context::create();
|
||||||
imgui.io_mut().display_size = [640.0, 480.0];
|
imgui.io_mut().display_size = [640.0, 480.0];
|
||||||
imgui.fonts().build_alpha8_texture();
|
imgui.fonts().build_alpha8_texture();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::ffi::c_void;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::ptr::{null, null_mut};
|
use std::ptr::{null, null_mut};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use imgui::internal::RawWrapper;
|
use imgui::internal::RawWrapper;
|
||||||
use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
|
use imgui::{ConfigFlags, DrawCmd, DrawData, DrawIdx, DrawVert, Key, MouseCursor, TextureId, Ui};
|
||||||
|
|
@ -32,6 +32,7 @@ use crate::framework::ui::init_imgui;
|
||||||
use crate::Game;
|
use crate::Game;
|
||||||
use crate::GameError::RenderError;
|
use crate::GameError::RenderError;
|
||||||
use crate::GAME_SUSPENDED;
|
use crate::GAME_SUSPENDED;
|
||||||
|
use crate::graphics::VSyncMode;
|
||||||
|
|
||||||
pub struct SDL2Backend {
|
pub struct SDL2Backend {
|
||||||
context: Sdl,
|
context: Sdl,
|
||||||
|
|
@ -315,7 +316,7 @@ impl BackendEventLoop for SDL2EventLoop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
|
fn new_renderer(&self, ctx: *mut Context) -> GameResult<Box<dyn BackendRenderer>> {
|
||||||
#[cfg(feature = "render-opengl")]
|
#[cfg(feature = "render-opengl")]
|
||||||
{
|
{
|
||||||
let mut refs = self.refs.borrow_mut();
|
let mut refs = self.refs.borrow_mut();
|
||||||
|
|
@ -364,7 +365,7 @@ impl BackendEventLoop for SDL2EventLoop {
|
||||||
*user_data = Rc::into_raw(refs) as *mut c_void;
|
*user_data = Rc::into_raw(refs) as *mut c_void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let gl_context = GLContext { gles2_mode: false, get_proc_address, swap_buffers, user_data };
|
let gl_context = GLContext { gles2_mode: false, is_sdl: true, get_proc_address, swap_buffers, user_data, ctx };
|
||||||
|
|
||||||
return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))));
|
return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use crate::framework::error::GameResult;
|
||||||
use crate::framework::filesystem::Filesystem;
|
use crate::framework::filesystem::Filesystem;
|
||||||
use crate::framework::keyboard::KeyboardContext;
|
use crate::framework::keyboard::KeyboardContext;
|
||||||
use crate::Game;
|
use crate::Game;
|
||||||
|
use crate::graphics::VSyncMode;
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub headless: bool,
|
pub headless: bool,
|
||||||
|
|
@ -13,6 +14,7 @@ pub struct Context {
|
||||||
pub(crate) real_screen_size: (u32, u32),
|
pub(crate) real_screen_size: (u32, u32),
|
||||||
pub(crate) screen_size: (f32, f32),
|
pub(crate) screen_size: (f32, f32),
|
||||||
pub(crate) screen_insets: (f32, f32, f32, f32),
|
pub(crate) screen_insets: (f32, f32, f32, f32),
|
||||||
|
pub(crate) vsync_mode: VSyncMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
|
@ -26,13 +28,14 @@ impl Context {
|
||||||
real_screen_size: (320, 240),
|
real_screen_size: (320, 240),
|
||||||
screen_size: (320.0, 240.0),
|
screen_size: (320.0, 240.0),
|
||||||
screen_insets: (0.0, 0.0, 0.0, 0.0),
|
screen_insets: (0.0, 0.0, 0.0, 0.0),
|
||||||
|
vsync_mode: VSyncMode::Uncapped,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, game: &mut Game) -> GameResult {
|
pub fn run(&mut self, game: &mut Game) -> GameResult {
|
||||||
let backend = init_backend(self.headless, self.size_hint)?;
|
let backend = init_backend(self.headless, self.size_hint)?;
|
||||||
let mut event_loop = backend.create_event_loop()?;
|
let mut event_loop = backend.create_event_loop()?;
|
||||||
self.renderer = Some(event_loop.new_renderer()?);
|
self.renderer = Some(event_loop.new_renderer(self as *mut Context)?);
|
||||||
|
|
||||||
event_loop.run(game, self);
|
event_loop.run(game, self);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,20 @@ pub enum BlendMode {
|
||||||
Multiply,
|
Multiply,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum VSyncMode {
|
||||||
|
/// No V-Sync - uncapped frame rate
|
||||||
|
Uncapped,
|
||||||
|
/// Synchronized to V-Sync
|
||||||
|
VSync,
|
||||||
|
/// Variable Refresh Rate - Synchronized to game tick interval
|
||||||
|
VRRTickSync1x,
|
||||||
|
/// Variable Refresh Rate - Synchronized to 2 * game tick interval
|
||||||
|
VRRTickSync2x,
|
||||||
|
/// Variable Refresh Rate - Synchronized to 3 * game tick interval
|
||||||
|
VRRTickSync3x,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(ctx: &mut Context, color: Color) {
|
pub fn clear(ctx: &mut Context, color: Color) {
|
||||||
if let Some(renderer) = &mut ctx.renderer {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
renderer.clear(color)
|
renderer.clear(color)
|
||||||
|
|
@ -30,20 +44,29 @@ pub fn clear(ctx: &mut Context, color: Color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn present(ctx: &mut Context) -> GameResult {
|
pub fn present(ctx: &mut Context) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
renderer.present()?;
|
renderer.present()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_vsync_mode(ctx: &mut Context, mode: VSyncMode) -> GameResult {
|
||||||
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
|
ctx.vsync_mode = mode;
|
||||||
|
renderer.set_vsync_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn renderer_initialized(ctx: &mut Context) -> bool {
|
pub fn renderer_initialized(ctx: &mut Context) -> bool {
|
||||||
ctx.renderer.is_some()
|
ctx.renderer.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
|
pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.create_texture_mutable(width, height);
|
return renderer.create_texture_mutable(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,7 +74,7 @@ pub fn create_texture_mutable(ctx: &mut Context, width: u16, height: u16) -> Gam
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>> {
|
pub fn create_texture(ctx: &mut Context, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>> {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.create_texture(width, height, data);
|
return renderer.create_texture(width, height, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +95,7 @@ pub fn screen_insets_scaled(ctx: &mut Context, scale: f32) -> (f32, f32, f32, f3
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendTexture>>) -> GameResult {
|
pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendTexture>>) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.set_render_target(texture);
|
return renderer.set_render_target(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +103,7 @@ pub fn set_render_target(ctx: &mut Context, texture: Option<&Box<dyn BackendText
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult {
|
pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.set_blend_mode(blend);
|
return renderer.set_blend_mode(blend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +111,7 @@ pub fn set_blend_mode(ctx: &mut Context, blend: BlendMode) -> GameResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
|
pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.draw_rect(rect, color);
|
return renderer.draw_rect(rect, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +120,7 @@ pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color: Color) -> GameResult {
|
pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color: Color) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.draw_outline_rect(rect, line_width, color);
|
return renderer.draw_outline_rect(rect, line_width, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,7 +128,7 @@ pub fn draw_outline_rect(ctx: &mut Context, rect: Rect, line_width: usize, color
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
|
pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.set_clip_rect(rect);
|
return renderer.set_clip_rect(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +153,7 @@ pub fn imgui_texture_id(ctx: &Context, texture: &Box<dyn BackendTexture>) -> Gam
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_imgui(ctx: &mut Context, ui: &imgui::Ui) -> GameResult {
|
pub fn prepare_imgui(ctx: &mut Context, ui: &imgui::Ui) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.prepare_imgui(ui);
|
return renderer.prepare_imgui(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +161,7 @@ pub fn prepare_imgui(ctx: &mut Context, ui: &imgui::Ui) -> GameResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResult {
|
pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.render_imgui(draw_data);
|
return renderer.render_imgui(draw_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -146,7 +169,7 @@ pub fn render_imgui(ctx: &mut Context, draw_data: &imgui::DrawData) -> GameResul
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_draw(ctx: &mut Context) -> GameResult {
|
pub fn prepare_draw(ctx: &mut Context) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.prepare_draw(ctx.screen_size.0, ctx.screen_size.1);
|
return renderer.prepare_draw(ctx.screen_size.0, ctx.screen_size.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +190,7 @@ pub fn draw_triangle_list(
|
||||||
texture: Option<&Box<dyn BackendTexture>>,
|
texture: Option<&Box<dyn BackendTexture>>,
|
||||||
shader: BackendShader,
|
shader: BackendShader,
|
||||||
) -> GameResult {
|
) -> GameResult {
|
||||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
if let Some(renderer) = &mut ctx.renderer {
|
||||||
return renderer.draw_triangle_list(vertices, texture, shader);
|
return renderer.draw_triangle_list(vertices, texture, shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::{RefCell, UnsafeCell};
|
use std::cell::{RefCell, UnsafeCell};
|
||||||
use std::ffi::{c_void, CStr};
|
use std::ffi::{c_void, CStr};
|
||||||
|
use std::hint::unreachable_unchecked;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ptr::null;
|
use std::ptr::null;
|
||||||
|
|
@ -10,19 +11,23 @@ use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
|
||||||
|
|
||||||
use crate::common::{Color, Rect};
|
use crate::common::{Color, Rect};
|
||||||
use crate::framework::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
|
use crate::framework::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
|
||||||
|
use crate::framework::context::Context;
|
||||||
|
use crate::framework::error::GameError;
|
||||||
use crate::framework::error::GameError::RenderError;
|
use crate::framework::error::GameError::RenderError;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::gl;
|
use crate::framework::gl;
|
||||||
use crate::framework::gl::types::*;
|
use crate::framework::gl::types::*;
|
||||||
use crate::framework::graphics::BlendMode;
|
use crate::framework::graphics::BlendMode;
|
||||||
use crate::framework::util::{field_offset, return_param};
|
use crate::framework::util::{field_offset, return_param};
|
||||||
use crate::GameError;
|
use crate::graphics::VSyncMode;
|
||||||
|
|
||||||
pub struct GLContext {
|
pub struct GLContext {
|
||||||
pub gles2_mode: bool,
|
pub gles2_mode: bool,
|
||||||
|
pub is_sdl: bool,
|
||||||
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
|
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
|
||||||
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
|
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
|
||||||
pub user_data: *mut c_void,
|
pub user_data: *mut c_void,
|
||||||
|
pub ctx: *mut Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OpenGLTexture {
|
pub struct OpenGLTexture {
|
||||||
|
|
@ -410,7 +415,17 @@ struct RenderShader {
|
||||||
|
|
||||||
impl Default for RenderShader {
|
impl Default for RenderShader {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { program_id: 0, texture: 0, proj_mtx: 0, scale: 0, time: 0, frame_offset: 0, position: 0, uv: 0, color: 0 }
|
Self {
|
||||||
|
program_id: 0,
|
||||||
|
texture: 0,
|
||||||
|
proj_mtx: 0,
|
||||||
|
scale: 0,
|
||||||
|
time: 0,
|
||||||
|
frame_offset: 0,
|
||||||
|
position: 0,
|
||||||
|
uv: 0,
|
||||||
|
color: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -760,6 +775,34 @@ impl BackendRenderer for OpenGLRenderer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_vsync_mode(&mut self, mode: VSyncMode) -> GameResult {
|
||||||
|
if !self.refs.is_sdl {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "backend-sdl")]
|
||||||
|
unsafe {
|
||||||
|
let ctx = &mut *self.refs.ctx;
|
||||||
|
|
||||||
|
match mode {
|
||||||
|
VSyncMode::Uncapped => {
|
||||||
|
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||||
|
},
|
||||||
|
VSyncMode::VSync => {
|
||||||
|
sdl2_sys::SDL_GL_SetSwapInterval(1);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if sdl2_sys::SDL_GL_SetSwapInterval(-1) == -1 {
|
||||||
|
log::warn!("Failed to enable variable refresh rate, falling back to non-V-Sync.");
|
||||||
|
sdl2_sys::SDL_GL_SetSwapInterval(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
|
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
|
||||||
if let Some((_, gl)) = self.get_context() {
|
if let Some((_, gl)) = self.get_context() {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
||||||
46
src/lib.rs
46
src/lib.rs
|
|
@ -8,7 +8,7 @@ use std::cell::UnsafeCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Instant;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use directories::ProjectDirs;
|
use directories::ProjectDirs;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
@ -18,6 +18,7 @@ use crate::framework::context::Context;
|
||||||
use crate::framework::error::{GameError, GameResult};
|
use crate::framework::error::{GameError, GameResult};
|
||||||
use crate::framework::filesystem::{mount_user_vfs, mount_vfs};
|
use crate::framework::filesystem::{mount_user_vfs, mount_vfs};
|
||||||
use crate::framework::graphics;
|
use crate::framework::graphics;
|
||||||
|
use crate::framework::graphics::VSyncMode;
|
||||||
use crate::framework::ui::UI;
|
use crate::framework::ui::UI;
|
||||||
use crate::framework::vfs::PhysicalFS;
|
use crate::framework::vfs::PhysicalFS;
|
||||||
use crate::scene::loading_scene::LoadingScene;
|
use crate::scene::loading_scene::LoadingScene;
|
||||||
|
|
@ -84,7 +85,9 @@ pub struct Game {
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
last_tick: u128,
|
last_tick: u128,
|
||||||
next_tick: u128,
|
next_tick: u128,
|
||||||
loops: u64,
|
loops: u32,
|
||||||
|
next_tick_draw: u128,
|
||||||
|
present: bool,
|
||||||
fps: Fps,
|
fps: Fps,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +101,8 @@ impl Game {
|
||||||
last_tick: 0,
|
last_tick: 0,
|
||||||
next_tick: 0,
|
next_tick: 0,
|
||||||
loops: 0,
|
loops: 0,
|
||||||
|
next_tick_draw: 0,
|
||||||
|
present: true,
|
||||||
fps: Fps::new(),
|
fps: Fps::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -144,6 +149,7 @@ impl Game {
|
||||||
for _ in 0..self.loops {
|
for _ in 0..self.loops {
|
||||||
scene.tick(state_ref, ctx)?;
|
scene.tick(state_ref, ctx)?;
|
||||||
}
|
}
|
||||||
|
self.fps.tick_count = self.fps.tick_count.saturating_add(self.loops as u32);
|
||||||
}
|
}
|
||||||
TimingMode::FrameSynchronized => {
|
TimingMode::FrameSynchronized => {
|
||||||
scene.tick(state_ref, ctx)?;
|
scene.tick(state_ref, ctx)?;
|
||||||
|
|
@ -156,6 +162,40 @@ impl Game {
|
||||||
fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
||||||
let state_ref = unsafe { &mut *self.state.get() };
|
let state_ref = unsafe { &mut *self.state.get() };
|
||||||
|
|
||||||
|
match ctx.vsync_mode {
|
||||||
|
VSyncMode::Uncapped | VSyncMode::VSync => {
|
||||||
|
self.present = true;
|
||||||
|
}
|
||||||
|
_ => unsafe {
|
||||||
|
self.present = false;
|
||||||
|
|
||||||
|
let divisor = match ctx.vsync_mode {
|
||||||
|
VSyncMode::VRRTickSync1x => 1,
|
||||||
|
VSyncMode::VRRTickSync2x => 2,
|
||||||
|
VSyncMode::VRRTickSync3x => 3,
|
||||||
|
_ => std::hint::unreachable_unchecked(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let delta = (state_ref.settings.timing_mode.get_delta() / divisor) as u64;
|
||||||
|
|
||||||
|
let now = self.start_time.elapsed().as_nanos();
|
||||||
|
if now > self.next_tick_draw + delta as u128 * 4 {
|
||||||
|
self.next_tick_draw = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.start_time.elapsed().as_nanos() >= self.next_tick_draw {
|
||||||
|
self.next_tick_draw += delta as u128;
|
||||||
|
self.present = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.present {
|
||||||
|
std::thread::sleep(Duration::from_millis(2));
|
||||||
|
self.loops = 0;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.headless {
|
if ctx.headless {
|
||||||
self.loops = 0;
|
self.loops = 0;
|
||||||
state_ref.frame_time = 1.0;
|
state_ref.frame_time = 1.0;
|
||||||
|
|
@ -197,7 +237,7 @@ impl Game {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state_ref.settings.fps_counter {
|
if state_ref.settings.fps_counter {
|
||||||
self.fps.act(state_ref, ctx, self.last_tick)?;
|
self.fps.act(state_ref, ctx, self.start_time.elapsed().as_nanos())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.draw(state_ref, ctx, scene)?;
|
self.ui.draw(state_ref, ctx, scene)?;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl MenuEntry {
|
||||||
MenuEntry::Disabled(_) => 16.0,
|
MenuEntry::Disabled(_) => 16.0,
|
||||||
MenuEntry::Toggle(_, _) => 16.0,
|
MenuEntry::Toggle(_, _) => 16.0,
|
||||||
MenuEntry::Options(_, _, _) => 16.0,
|
MenuEntry::Options(_, _, _) => 16.0,
|
||||||
MenuEntry::DescriptiveOptions(_, _, _, _) => 16.0,
|
MenuEntry::DescriptiveOptions(_, _, _, _) => 32.0,
|
||||||
MenuEntry::OptionsBar(_, _) => 16.0,
|
MenuEntry::OptionsBar(_, _) => 16.0,
|
||||||
MenuEntry::SaveData(_) => 32.0,
|
MenuEntry::SaveData(_) => 32.0,
|
||||||
MenuEntry::SaveDataSingle(_) => 32.0,
|
MenuEntry::SaveDataSingle(_) => 32.0,
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,8 @@ impl SaveSelectMenu {
|
||||||
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string()))
|
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or("".to_string()))
|
||||||
{
|
{
|
||||||
if let MenuEntry::SaveData(save) = self.save_menu.entries[slot] {
|
if let MenuEntry::SaveData(save) = self.save_menu.entries[slot] {
|
||||||
self.save_detailed.entries[0] = MenuEntry::SaveDataSingle(save);
|
self.save_detailed.entries.clear();
|
||||||
|
self.save_detailed.push_entry(MenuEntry::SaveDataSingle(save));
|
||||||
}
|
}
|
||||||
self.current_menu = CurrentMenu::LoadConfirm;
|
self.current_menu = CurrentMenu::LoadConfirm;
|
||||||
self.load_confirm.selected = 0;
|
self.load_confirm.selected = 0;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use crate::menu::{Menu, MenuSelectionResult};
|
||||||
use crate::scene::title_scene::TitleScene;
|
use crate::scene::title_scene::TitleScene;
|
||||||
use crate::shared_game_state::{Language, SharedGameState, TimingMode};
|
use crate::shared_game_state::{Language, SharedGameState, TimingMode};
|
||||||
use crate::sound::InterpolationMode;
|
use crate::sound::InterpolationMode;
|
||||||
|
use crate::{graphics, VSyncMode};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
|
@ -47,6 +48,24 @@ impl SettingsMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
pub fn init(&mut self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
self.graphics.push_entry(MenuEntry::DescriptiveOptions(
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.entry"),
|
||||||
|
state.settings.vsync_mode as usize,
|
||||||
|
vec![
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x"),
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.uncapped_desc"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vsync_desc"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_1x_desc"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_2x_desc"),
|
||||||
|
state.t("menus.options_menu.graphics_menu.vsync_mode.vrr_3x_desc"),
|
||||||
|
],
|
||||||
|
));
|
||||||
self.graphics.push_entry(MenuEntry::Toggle(
|
self.graphics.push_entry(MenuEntry::Toggle(
|
||||||
state.t("menus.options_menu.graphics_menu.lighting_effects"),
|
state.t("menus.options_menu.graphics_menu.lighting_effects"),
|
||||||
state.settings.shader_effects,
|
state.settings.shader_effects,
|
||||||
|
|
@ -265,6 +284,23 @@ impl SettingsMenu {
|
||||||
},
|
},
|
||||||
CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) {
|
CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) {
|
||||||
MenuSelectionResult::Selected(0, toggle) => {
|
MenuSelectionResult::Selected(0, toggle) => {
|
||||||
|
if let MenuEntry::DescriptiveOptions(_, value, _, _) = toggle {
|
||||||
|
let (new_mode, new_value) = match *value {
|
||||||
|
0 => (VSyncMode::VSync, 1),
|
||||||
|
1 => (VSyncMode::VRRTickSync1x, 2),
|
||||||
|
2 => (VSyncMode::VRRTickSync2x, 3),
|
||||||
|
3 => (VSyncMode::VRRTickSync3x, 4),
|
||||||
|
_ => (VSyncMode::Uncapped, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
*value = new_value;
|
||||||
|
state.settings.vsync_mode = new_mode;
|
||||||
|
graphics::set_vsync_mode(ctx, new_mode);
|
||||||
|
|
||||||
|
let _ = state.settings.save(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuSelectionResult::Selected(1, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.shader_effects = !state.settings.shader_effects;
|
state.settings.shader_effects = !state.settings.shader_effects;
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
|
|
@ -272,7 +308,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.shader_effects;
|
*value = state.settings.shader_effects;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(1, toggle) => {
|
MenuSelectionResult::Selected(2, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.light_cone = !state.settings.light_cone;
|
state.settings.light_cone = !state.settings.light_cone;
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
|
|
@ -280,7 +316,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.light_cone;
|
*value = state.settings.light_cone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(2, toggle) => {
|
MenuSelectionResult::Selected(3, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.motion_interpolation = !state.settings.motion_interpolation;
|
state.settings.motion_interpolation = !state.settings.motion_interpolation;
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
|
|
@ -288,7 +324,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.motion_interpolation;
|
*value = state.settings.motion_interpolation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(3, toggle) => {
|
MenuSelectionResult::Selected(4, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.subpixel_coords = !state.settings.subpixel_coords;
|
state.settings.subpixel_coords = !state.settings.subpixel_coords;
|
||||||
let _ = state.settings.save(ctx);
|
let _ = state.settings.save(ctx);
|
||||||
|
|
@ -296,7 +332,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.subpixel_coords;
|
*value = state.settings.subpixel_coords;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(4, toggle) => {
|
MenuSelectionResult::Selected(5, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.original_textures = !state.settings.original_textures;
|
state.settings.original_textures = !state.settings.original_textures;
|
||||||
if self.on_title {
|
if self.on_title {
|
||||||
|
|
@ -309,7 +345,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.original_textures;
|
*value = state.settings.original_textures;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(5, toggle) => {
|
MenuSelectionResult::Selected(6, toggle) => {
|
||||||
if let MenuEntry::Toggle(_, value) = toggle {
|
if let MenuEntry::Toggle(_, value) = toggle {
|
||||||
state.settings.seasonal_textures = !state.settings.seasonal_textures;
|
state.settings.seasonal_textures = !state.settings.seasonal_textures;
|
||||||
state.reload_graphics();
|
state.reload_graphics();
|
||||||
|
|
@ -318,7 +354,7 @@ impl SettingsMenu {
|
||||||
*value = state.settings.seasonal_textures;
|
*value = state.settings.seasonal_textures;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => {
|
MenuSelectionResult::Selected(8, _) | MenuSelectionResult::Canceled => {
|
||||||
self.current = CurrentMenu::MainMenu
|
self.current = CurrentMenu::MainMenu
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
@ -366,6 +402,8 @@ impl SettingsMenu {
|
||||||
if let MenuEntry::Active(soundtrack) = entry {
|
if let MenuEntry::Active(soundtrack) = entry {
|
||||||
if soundtrack == &state.settings.soundtrack {
|
if soundtrack == &state.settings.soundtrack {
|
||||||
active_soundtrack_index = idx;
|
active_soundtrack_index = idx;
|
||||||
|
let _ = state.settings.save(ctx);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
|
use crate::graphics;
|
||||||
use crate::scene::no_data_scene::NoDataScene;
|
use crate::scene::no_data_scene::NoDataScene;
|
||||||
use crate::scene::Scene;
|
use crate::scene::Scene;
|
||||||
use crate::shared_game_state::SharedGameState;
|
use crate::shared_game_state::SharedGameState;
|
||||||
|
|
@ -43,6 +44,8 @@ impl Scene for LoadingScene {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
fn draw(&self, state: &mut SharedGameState, ctx: &mut Context) -> GameResult {
|
||||||
|
graphics::set_vsync_mode(ctx, state.settings.vsync_mode)?;
|
||||||
|
|
||||||
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
|
match state.texture_set.get_or_load_batch(ctx, &state.constants, "Loading") {
|
||||||
Ok(batch) => {
|
Ok(batch) => {
|
||||||
batch.add(
|
batch.add(
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use crate::framework::context::Context;
|
||||||
use crate::framework::error::GameResult;
|
use crate::framework::error::GameResult;
|
||||||
use crate::framework::filesystem::{user_create, user_open};
|
use crate::framework::filesystem::{user_create, user_open};
|
||||||
use crate::framework::keyboard::ScanCode;
|
use crate::framework::keyboard::ScanCode;
|
||||||
|
use crate::graphics::VSyncMode;
|
||||||
use crate::input::keyboard_player_controller::KeyboardController;
|
use crate::input::keyboard_player_controller::KeyboardController;
|
||||||
use crate::input::player_controller::PlayerController;
|
use crate::input::player_controller::PlayerController;
|
||||||
use crate::input::touch_player_controller::TouchPlayerController;
|
use crate::input::touch_player_controller::TouchPlayerController;
|
||||||
|
|
@ -47,6 +48,8 @@ pub struct Settings {
|
||||||
pub debug_outlines: bool,
|
pub debug_outlines: bool,
|
||||||
pub fps_counter: bool,
|
pub fps_counter: bool,
|
||||||
pub locale: Language,
|
pub locale: Language,
|
||||||
|
#[serde(default = "default_vsync")]
|
||||||
|
pub vsync_mode: VSyncMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_true() -> bool {
|
fn default_true() -> bool {
|
||||||
|
|
@ -55,7 +58,7 @@ fn default_true() -> bool {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn current_version() -> u32 {
|
fn current_version() -> u32 {
|
||||||
7
|
8
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
@ -83,6 +86,11 @@ fn default_locale() -> Language {
|
||||||
Language::English
|
Language::English
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn default_vsync() -> VSyncMode {
|
||||||
|
VSyncMode::VSync
|
||||||
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
pub fn load(ctx: &Context) -> GameResult<Settings> {
|
pub fn load(ctx: &Context) -> GameResult<Settings> {
|
||||||
if let Ok(file) = user_open(ctx, "/settings.json") {
|
if let Ok(file) = user_open(ctx, "/settings.json") {
|
||||||
|
|
@ -125,6 +133,11 @@ impl Settings {
|
||||||
self.locale = default_locale();
|
self.locale = default_locale();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.version == 7 {
|
||||||
|
self.version = 8;
|
||||||
|
self.vsync_mode = default_vsync();
|
||||||
|
}
|
||||||
|
|
||||||
if self.version != initial_version {
|
if self.version != initial_version {
|
||||||
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
|
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
|
||||||
}
|
}
|
||||||
|
|
@ -176,6 +189,7 @@ impl Default for Settings {
|
||||||
debug_outlines: false,
|
debug_outlines: false,
|
||||||
fps_counter: false,
|
fps_counter: false,
|
||||||
locale: Language::English,
|
locale: Language::English,
|
||||||
|
vsync_mode: VSyncMode::VSync,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,23 +137,28 @@ impl FontData {
|
||||||
pub struct Fps {
|
pub struct Fps {
|
||||||
pub frame_count: u32,
|
pub frame_count: u32,
|
||||||
pub fps: u32,
|
pub fps: u32,
|
||||||
|
pub tick_count: u32,
|
||||||
|
pub tps: u32,
|
||||||
last_capture: u128,
|
last_capture: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fps {
|
impl Fps {
|
||||||
pub fn new() -> Fps {
|
pub fn new() -> Fps {
|
||||||
Fps { frame_count: 0, fps: 0, last_capture: 0 }
|
Fps { frame_count: 0, fps: 0, tick_count: 0, tps: 0, last_capture: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn act(&mut self, state: &mut SharedGameState, ctx: &mut Context, time: u128) -> GameResult {
|
pub fn act(&mut self, state: &mut SharedGameState, ctx: &mut Context, time: u128) -> GameResult {
|
||||||
if time - self.last_capture > 1000000000 {
|
if time - self.last_capture > 1000000000 {
|
||||||
self.fps = self.frame_count;
|
self.fps = self.frame_count;
|
||||||
self.frame_count = 0;
|
self.frame_count = 0;
|
||||||
|
self.tps = self.tick_count;
|
||||||
|
self.tick_count = 0;
|
||||||
self.last_capture = time;
|
self.last_capture = time;
|
||||||
} else {
|
} else {
|
||||||
self.frame_count += 1;
|
self.frame_count = self.frame_count.saturating_add(1);
|
||||||
}
|
}
|
||||||
draw_number(state.canvas_size.0 - 8.0, 8.0, self.fps as usize, Alignment::Right, state, ctx)?;
|
draw_number(state.canvas_size.0 - 8.0, 8.0, self.fps as usize, Alignment::Right, state, ctx)?;
|
||||||
|
draw_number(state.canvas_size.0 - 8.0, 16.0, self.tps as usize, Alignment::Right, state, ctx)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue