mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2025-03-25 03:19:27 +00:00
Add frame cap configuration
This commit is contained in:
parent
339f822a80
commit
ca1d7a8642
|
@ -56,7 +56,20 @@
|
|||
"subpixel_scrolling": "Subpixel scrolling:",
|
||||
"original_textures": "Original 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...",
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
"on": "オン",
|
||||
"off": "オフ"
|
||||
},
|
||||
|
||||
"menus": {
|
||||
"main_menu": {
|
||||
"start": "ゲームスタート",
|
||||
|
@ -17,7 +16,6 @@
|
|||
"jukebox": "ジュークボックス",
|
||||
"quit": "辞める"
|
||||
},
|
||||
|
||||
"pause_menu": {
|
||||
"resume": "再開",
|
||||
"retry": "リトライ",
|
||||
|
@ -27,26 +25,22 @@
|
|||
"quit": "辞める",
|
||||
"quit_confirm": "辞める?"
|
||||
},
|
||||
|
||||
"save_menu": {
|
||||
"new": "新しいデータ",
|
||||
"delete_info": "右矢印キーで削除",
|
||||
"delete_confirm": "消去?"
|
||||
},
|
||||
|
||||
"difficulty_menu": {
|
||||
"title": "難易度選択",
|
||||
"easy": "簡単",
|
||||
"normal": "普通",
|
||||
"hard": "難しい"
|
||||
},
|
||||
|
||||
"challenge_menu": {
|
||||
"start": "スタート",
|
||||
"no_replay": "ノーリプレイ",
|
||||
"replay_best": "ベストプレイを再生"
|
||||
},
|
||||
|
||||
"options_menu": {
|
||||
"graphics": "グラフィック",
|
||||
"graphics_menu": {
|
||||
|
@ -56,9 +50,21 @@
|
|||
"subpixel_scrolling": "サブピクセルスクロール:",
|
||||
"original_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_menu": {
|
||||
"music_volume": "BGM音量",
|
||||
|
@ -78,9 +84,7 @@
|
|||
},
|
||||
"soundtrack": "サウンドトラック: {soundtrack}"
|
||||
},
|
||||
|
||||
"language": "言語",
|
||||
|
||||
"game_timing": {
|
||||
"entry": "ゲームのタイミング:",
|
||||
"50tps": "50tps (freeware)",
|
||||
|
@ -88,14 +92,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
"soundtrack": {
|
||||
"organya": "オルガーニャ",
|
||||
"remastered": "リマスター",
|
||||
"new": "新",
|
||||
"famitracks": "ファミトラック"
|
||||
},
|
||||
|
||||
"game": {
|
||||
"cutscene_skip": "{key} を押し続け、カットシーンをスキップ"
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::Game;
|
||||
use crate::graphics::VSyncMode;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
|
@ -30,7 +31,7 @@ pub trait Backend {
|
|||
pub trait BackendEventLoop {
|
||||
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 {
|
||||
|
@ -40,6 +41,8 @@ pub trait BackendRenderer {
|
|||
|
||||
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 {
|
||||
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();
|
||||
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();
|
||||
imgui.io_mut().display_size = [640.0, 480.0];
|
||||
imgui.fonts().build_alpha8_texture();
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::ffi::c_void;
|
|||
use std::ops::Deref;
|
||||
use std::ptr::{null, null_mut};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use imgui::internal::RawWrapper;
|
||||
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::GameError::RenderError;
|
||||
use crate::GAME_SUSPENDED;
|
||||
use crate::graphics::VSyncMode;
|
||||
|
||||
pub struct SDL2Backend {
|
||||
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")]
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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))));
|
||||
} else {
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::framework::error::GameResult;
|
|||
use crate::framework::filesystem::Filesystem;
|
||||
use crate::framework::keyboard::KeyboardContext;
|
||||
use crate::Game;
|
||||
use crate::graphics::VSyncMode;
|
||||
|
||||
pub struct Context {
|
||||
pub headless: bool,
|
||||
|
@ -13,6 +14,7 @@ pub struct Context {
|
|||
pub(crate) real_screen_size: (u32, u32),
|
||||
pub(crate) screen_size: (f32, f32),
|
||||
pub(crate) screen_insets: (f32, f32, f32, f32),
|
||||
pub(crate) vsync_mode: VSyncMode,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
@ -26,13 +28,14 @@ impl Context {
|
|||
real_screen_size: (320, 240),
|
||||
screen_size: (320.0, 240.0),
|
||||
screen_insets: (0.0, 0.0, 0.0, 0.0),
|
||||
vsync_mode: VSyncMode::Uncapped,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self, game: &mut Game) -> GameResult {
|
||||
let backend = init_backend(self.headless, self.size_hint)?;
|
||||
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);
|
||||
|
||||
|
|
|
@ -23,6 +23,20 @@ pub enum BlendMode {
|
|||
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) {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
renderer.clear(color)
|
||||
|
@ -30,20 +44,29 @@ pub fn clear(ctx: &mut Context, color: Color) {
|
|||
}
|
||||
|
||||
pub fn present(ctx: &mut Context) -> GameResult {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
renderer.present()?;
|
||||
}
|
||||
|
||||
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)]
|
||||
pub fn renderer_initialized(ctx: &mut Context) -> bool {
|
||||
ctx.renderer.is_some()
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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>> {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
return renderer.draw_rect(rect, color);
|
||||
}
|
||||
|
||||
|
@ -97,7 +120,7 @@ pub fn draw_rect(ctx: &mut Context, rect: Rect, color: Color) -> GameResult {
|
|||
|
||||
#[allow(unused)]
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -167,7 +190,7 @@ pub fn draw_triangle_list(
|
|||
texture: Option<&Box<dyn BackendTexture>>,
|
||||
shader: BackendShader,
|
||||
) -> GameResult {
|
||||
if let Some(renderer) = ctx.renderer.as_mut() {
|
||||
if let Some(renderer) = &mut ctx.renderer {
|
||||
return renderer.draw_triangle_list(vertices, texture, shader);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{RefCell, UnsafeCell};
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::hint::unreachable_unchecked;
|
||||
use std::mem;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::null;
|
||||
|
@ -10,19 +11,23 @@ use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
|
|||
|
||||
use crate::common::{Color, Rect};
|
||||
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::GameResult;
|
||||
use crate::framework::gl;
|
||||
use crate::framework::gl::types::*;
|
||||
use crate::framework::graphics::BlendMode;
|
||||
use crate::framework::util::{field_offset, return_param};
|
||||
use crate::GameError;
|
||||
use crate::graphics::VSyncMode;
|
||||
|
||||
pub struct GLContext {
|
||||
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 swap_buffers: unsafe fn(user_data: &mut *mut c_void),
|
||||
pub user_data: *mut c_void,
|
||||
pub ctx: *mut Context,
|
||||
}
|
||||
|
||||
pub struct OpenGLTexture {
|
||||
|
@ -410,7 +415,17 @@ struct RenderShader {
|
|||
|
||||
impl Default for RenderShader {
|
||||
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(())
|
||||
}
|
||||
|
||||
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 {
|
||||
if let Some((_, gl)) = self.get_context() {
|
||||
unsafe {
|
||||
|
|
46
src/lib.rs
46
src/lib.rs
|
@ -8,7 +8,7 @@ use std::cell::UnsafeCell;
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Instant;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
|
@ -18,6 +18,7 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::filesystem::{mount_user_vfs, mount_vfs};
|
||||
use crate::framework::graphics;
|
||||
use crate::framework::graphics::VSyncMode;
|
||||
use crate::framework::ui::UI;
|
||||
use crate::framework::vfs::PhysicalFS;
|
||||
use crate::scene::loading_scene::LoadingScene;
|
||||
|
@ -84,7 +85,9 @@ pub struct Game {
|
|||
start_time: Instant,
|
||||
last_tick: u128,
|
||||
next_tick: u128,
|
||||
loops: u64,
|
||||
loops: u32,
|
||||
next_tick_draw: u128,
|
||||
present: bool,
|
||||
fps: Fps,
|
||||
}
|
||||
|
||||
|
@ -98,6 +101,8 @@ impl Game {
|
|||
last_tick: 0,
|
||||
next_tick: 0,
|
||||
loops: 0,
|
||||
next_tick_draw: 0,
|
||||
present: true,
|
||||
fps: Fps::new(),
|
||||
};
|
||||
|
||||
|
@ -144,6 +149,7 @@ impl Game {
|
|||
for _ in 0..self.loops {
|
||||
scene.tick(state_ref, ctx)?;
|
||||
}
|
||||
self.fps.tick_count = self.fps.tick_count.saturating_add(self.loops as u32);
|
||||
}
|
||||
TimingMode::FrameSynchronized => {
|
||||
scene.tick(state_ref, ctx)?;
|
||||
|
@ -156,6 +162,40 @@ impl Game {
|
|||
fn draw(&mut self, ctx: &mut Context) -> GameResult {
|
||||
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 {
|
||||
self.loops = 0;
|
||||
state_ref.frame_time = 1.0;
|
||||
|
@ -197,7 +237,7 @@ impl Game {
|
|||
}
|
||||
|
||||
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)?;
|
||||
|
|
|
@ -37,7 +37,7 @@ impl MenuEntry {
|
|||
MenuEntry::Disabled(_) => 16.0,
|
||||
MenuEntry::Toggle(_, _) => 16.0,
|
||||
MenuEntry::Options(_, _, _) => 16.0,
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _) => 16.0,
|
||||
MenuEntry::DescriptiveOptions(_, _, _, _) => 32.0,
|
||||
MenuEntry::OptionsBar(_, _) => 16.0,
|
||||
MenuEntry::SaveData(_) => 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()))
|
||||
{
|
||||
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.load_confirm.selected = 0;
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::menu::{Menu, MenuSelectionResult};
|
|||
use crate::scene::title_scene::TitleScene;
|
||||
use crate::shared_game_state::{Language, SharedGameState, TimingMode};
|
||||
use crate::sound::InterpolationMode;
|
||||
use crate::{graphics, VSyncMode};
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
#[repr(u8)]
|
||||
|
@ -47,6 +48,24 @@ impl SettingsMenu {
|
|||
}
|
||||
|
||||
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(
|
||||
state.t("menus.options_menu.graphics_menu.lighting_effects"),
|
||||
state.settings.shader_effects,
|
||||
|
@ -265,6 +284,23 @@ impl SettingsMenu {
|
|||
},
|
||||
CurrentMenu::GraphicsMenu => match self.graphics.tick(controller, state) {
|
||||
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 {
|
||||
state.settings.shader_effects = !state.settings.shader_effects;
|
||||
let _ = state.settings.save(ctx);
|
||||
|
@ -272,7 +308,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.shader_effects;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(1, toggle) => {
|
||||
MenuSelectionResult::Selected(2, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.light_cone = !state.settings.light_cone;
|
||||
let _ = state.settings.save(ctx);
|
||||
|
@ -280,7 +316,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.light_cone;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(2, toggle) => {
|
||||
MenuSelectionResult::Selected(3, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.motion_interpolation = !state.settings.motion_interpolation;
|
||||
let _ = state.settings.save(ctx);
|
||||
|
@ -288,7 +324,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.motion_interpolation;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(3, toggle) => {
|
||||
MenuSelectionResult::Selected(4, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.subpixel_coords = !state.settings.subpixel_coords;
|
||||
let _ = state.settings.save(ctx);
|
||||
|
@ -296,7 +332,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.subpixel_coords;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(4, toggle) => {
|
||||
MenuSelectionResult::Selected(5, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.original_textures = !state.settings.original_textures;
|
||||
if self.on_title {
|
||||
|
@ -309,7 +345,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.original_textures;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(5, toggle) => {
|
||||
MenuSelectionResult::Selected(6, toggle) => {
|
||||
if let MenuEntry::Toggle(_, value) = toggle {
|
||||
state.settings.seasonal_textures = !state.settings.seasonal_textures;
|
||||
state.reload_graphics();
|
||||
|
@ -318,7 +354,7 @@ impl SettingsMenu {
|
|||
*value = state.settings.seasonal_textures;
|
||||
}
|
||||
}
|
||||
MenuSelectionResult::Selected(7, _) | MenuSelectionResult::Canceled => {
|
||||
MenuSelectionResult::Selected(8, _) | MenuSelectionResult::Canceled => {
|
||||
self.current = CurrentMenu::MainMenu
|
||||
}
|
||||
_ => (),
|
||||
|
@ -366,6 +402,8 @@ impl SettingsMenu {
|
|||
if let MenuEntry::Active(soundtrack) = entry {
|
||||
if soundtrack == &state.settings.soundtrack {
|
||||
active_soundtrack_index = idx;
|
||||
let _ = state.settings.save(ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::framework::context::Context;
|
||||
use crate::framework::error::GameResult;
|
||||
use crate::graphics;
|
||||
use crate::scene::no_data_scene::NoDataScene;
|
||||
use crate::scene::Scene;
|
||||
use crate::shared_game_state::SharedGameState;
|
||||
|
@ -43,6 +44,8 @@ impl Scene for LoadingScene {
|
|||
}
|
||||
|
||||
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") {
|
||||
Ok(batch) => {
|
||||
batch.add(
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::framework::context::Context;
|
|||
use crate::framework::error::GameResult;
|
||||
use crate::framework::filesystem::{user_create, user_open};
|
||||
use crate::framework::keyboard::ScanCode;
|
||||
use crate::graphics::VSyncMode;
|
||||
use crate::input::keyboard_player_controller::KeyboardController;
|
||||
use crate::input::player_controller::PlayerController;
|
||||
use crate::input::touch_player_controller::TouchPlayerController;
|
||||
|
@ -47,6 +48,8 @@ pub struct Settings {
|
|||
pub debug_outlines: bool,
|
||||
pub fps_counter: bool,
|
||||
pub locale: Language,
|
||||
#[serde(default = "default_vsync")]
|
||||
pub vsync_mode: VSyncMode,
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
|
@ -55,7 +58,7 @@ fn default_true() -> bool {
|
|||
|
||||
#[inline(always)]
|
||||
fn current_version() -> u32 {
|
||||
7
|
||||
8
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -83,6 +86,11 @@ fn default_locale() -> Language {
|
|||
Language::English
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn default_vsync() -> VSyncMode {
|
||||
VSyncMode::VSync
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn load(ctx: &Context) -> GameResult<Settings> {
|
||||
if let Ok(file) = user_open(ctx, "/settings.json") {
|
||||
|
@ -125,6 +133,11 @@ impl Settings {
|
|||
self.locale = default_locale();
|
||||
}
|
||||
|
||||
if self.version == 7 {
|
||||
self.version = 8;
|
||||
self.vsync_mode = default_vsync();
|
||||
}
|
||||
|
||||
if self.version != initial_version {
|
||||
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
|
||||
}
|
||||
|
@ -176,6 +189,7 @@ impl Default for Settings {
|
|||
debug_outlines: false,
|
||||
fps_counter: false,
|
||||
locale: Language::English,
|
||||
vsync_mode: VSyncMode::VSync,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,23 +137,28 @@ impl FontData {
|
|||
pub struct Fps {
|
||||
pub frame_count: u32,
|
||||
pub fps: u32,
|
||||
pub tick_count: u32,
|
||||
pub tps: u32,
|
||||
last_capture: u128,
|
||||
}
|
||||
|
||||
impl 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 {
|
||||
if time - self.last_capture > 1000000000 {
|
||||
self.fps = self.frame_count;
|
||||
self.frame_count = 0;
|
||||
self.tps = self.tick_count;
|
||||
self.tick_count = 0;
|
||||
self.last_capture = time;
|
||||
} 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, 16.0, self.tps as usize, Alignment::Right, state, ctx)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue