1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2025-03-24 19:09:22 +00:00

Cleanup OpenGL backend and graphics code

This commit is contained in:
Alula 2024-08-27 17:07:21 +02:00
parent 1b7a64b5d9
commit 56832514eb
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
9 changed files with 196 additions and 205 deletions

View file

@ -3,10 +3,12 @@ use std::rc::Rc;
use imgui::DrawData;
use super::context::Context;
use super::error::GameResult;
use super::graphics::BlendMode;
use super::graphics::SwapMode;
use crate::common::{Color, Rect};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::{BlendMode, VSyncMode};
use crate::game::Game;
#[repr(C)]
@ -46,7 +48,7 @@ pub trait BackendRenderer {
fn present(&mut self) -> GameResult;
fn set_vsync_mode(&mut self, _mode: VSyncMode) -> GameResult {
fn set_swap_mode(&mut self, _mode: SwapMode) -> GameResult {
Ok(())
}
@ -107,26 +109,26 @@ pub trait BackendGamepad {
#[allow(unreachable_code)]
pub fn init_backend(headless: bool, size_hint: (u16, u16)) -> GameResult<Box<dyn Backend>> {
if headless {
return crate::framework::backend_null::NullBackend::new();
return super::backend_null::NullBackend::new();
}
#[cfg(all(feature = "backend-horizon"))]
{
return crate::framework::backend_horizon::HorizonBackend::new();
return super::backend_horizon::HorizonBackend::new();
}
#[cfg(all(feature = "backend-winit"))]
{
return crate::framework::backend_winit::WinitBackend::new();
return super::backend_winit::WinitBackend::new();
}
#[cfg(feature = "backend-sdl")]
{
return crate::framework::backend_sdl2::SDL2Backend::new(size_hint);
return super::backend_sdl2::SDL2Backend::new(size_hint);
}
log::warn!("No backend compiled in, using null backend instead.");
crate::framework::backend_null::NullBackend::new()
super::backend_null::NullBackend::new()
}
pub enum SpriteBatchCommand {

View file

@ -32,13 +32,13 @@ use crate::framework::backend::{
};
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem;
use crate::framework::gamepad::{Axis, Button, GamepadType};
use crate::framework::graphics::BlendMode;
use crate::framework::graphics::{BlendMode, SwapMode};
use crate::framework::keyboard::ScanCode;
#[cfg(feature = "render-opengl")]
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
use crate::framework::ui::init_imgui;
use crate::framework::{filesystem, render_opengl};
use crate::game::shared_game_state::WindowMode;
use crate::game::Game;
use crate::game::GAME_SUSPENDED;
@ -465,37 +465,39 @@ impl BackendEventLoop for SDL2EventLoop {
key_map[ImGuiKey_Delete as usize] = Scancode::Delete as u32;
key_map[ImGuiKey_Enter as usize] = Scancode::Return as u32;
let refs = self.refs.clone();
struct SDL2GLPlatform(Rc<RefCell<SDL2Context>>);
let user_data = Rc::into_raw(refs) as *mut c_void;
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
let result = {
let refs = &mut *refs.as_ptr();
impl render_opengl::GLPlatformFunctions for SDL2GLPlatform {
fn get_proc_address(&self, name: &str) -> *const c_void {
let refs = self.0.borrow();
refs.video.gl_get_proc_address(name) as *const _
};
*user_data = Rc::into_raw(refs) as *mut c_void;
result
}
unsafe fn swap_buffers(user_data: &mut *mut c_void) {
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
{
let refs = &mut *refs.as_ptr();
}
fn swap_buffers(&self) {
let mut refs = self.0.borrow();
refs.window.window().gl_swap_window();
}
*user_data = Rc::into_raw(refs) as *mut c_void;
fn set_swap_mode(&self, mode: SwapMode) {
match mode {
SwapMode::Immediate => unsafe {
sdl2_sys::SDL_GL_SetSwapInterval(0);
},
SwapMode::VSync => unsafe {
sdl2_sys::SDL_GL_SetSwapInterval(1);
},
SwapMode::Adaptive => unsafe {
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);
}
},
}
}
}
let gl_context =
GLContext { gles2_mode: false, is_sdl: true, get_proc_address, swap_buffers, user_data, ctx };
let platform = Box::new(SDL2GLPlatform(self.refs.clone()));
let gl_context: GLContext = GLContext { gles2_mode: false, is_sdl: true, platform, ctx };
return Ok(Box::new(OpenGLRenderer::new(gl_context, imgui)));
}

View file

@ -1,11 +1,12 @@
use crate::framework::backend::{init_backend, BackendRenderer};
use crate::framework::error::GameResult;
use crate::framework::filesystem::Filesystem;
use crate::framework::gamepad::GamepadContext;
use crate::framework::graphics::VSyncMode;
use crate::framework::keyboard::KeyboardContext;
use crate::game::Game;
use super::backend::{init_backend, BackendRenderer};
use super::error::GameResult;
use super::filesystem::Filesystem;
use super::gamepad::GamepadContext;
use super::graphics::SwapMode;
use super::keyboard::KeyboardContext;
pub struct Context {
pub headless: bool,
pub shutdown_requested: bool,
@ -18,7 +19,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,
pub(crate) swap_mode: SwapMode,
}
impl Context {
@ -35,7 +36,7 @@ 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,
swap_mode: SwapMode::VSync,
}
}

View file

@ -23,18 +23,11 @@ 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,
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SwapMode {
Immediate = 0,
VSync = 1,
Adaptive = -1,
}
pub fn clear(ctx: &mut Context, color: Color) {
@ -51,10 +44,12 @@ pub fn present(ctx: &mut Context) -> GameResult {
Ok(())
}
pub fn set_vsync_mode(ctx: &mut Context, mode: VSyncMode) -> GameResult {
pub fn set_swap_mode(ctx: &mut Context, mode: SwapMode) -> GameResult {
if let Some(renderer) = &mut ctx.renderer {
ctx.vsync_mode = mode;
renderer.set_vsync_mode(mode);
if ctx.swap_mode != mode {
ctx.swap_mode = mode;
renderer.set_swap_mode(mode);
}
}
Ok(())
@ -135,7 +130,6 @@ pub fn set_clip_rect(ctx: &mut Context, rect: Option<Rect>) -> GameResult {
Err(GameError::RenderError("Rendering backend hasn't been initialized yet.".to_string()))
}
pub fn imgui_context(ctx: &Context) -> GameResult<&mut imgui::Context> {
if let Some(renderer) = ctx.renderer.as_ref() {
return renderer.imgui();

View file

@ -1,38 +1,41 @@
use std::any::Any;
use std::cell::{RefCell, UnsafeCell};
use std::borrow::BorrowMut;
use std::cell::{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;
use std::rc::Rc;
use std::sync::Arc;
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert, TextureId, Ui};
use super::backend::{BackendRenderer, BackendShader, BackendTexture, SpriteBatchCommand, VertexData};
use super::context::Context;
use super::error::GameError;
use super::error::GameError::RenderError;
use super::error::GameResult;
use super::gl;
use super::gl::types::*;
use super::graphics::BlendMode;
use super::graphics::SwapMode;
use super::util::{field_offset, return_param};
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, VSyncMode};
use crate::framework::util::{field_offset, return_param};
use crate::game::GAME_SUSPENDED;
pub trait GLPlatformFunctions {
fn get_proc_address(&self, name: &str) -> *const c_void;
fn swap_buffers(&self);
fn set_swap_mode(&self, mode: SwapMode);
}
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 platform: Box<dyn GLPlatformFunctions>,
pub ctx: *mut Context,
}
@ -44,7 +47,7 @@ pub struct OpenGLTexture {
shader: RenderShader,
vbo: GLuint,
vertices: Vec<VertexData>,
context_active: Arc<RefCell<bool>>,
gl: Rc<Gl>,
}
impl BackendTexture for OpenGLTexture {
@ -224,38 +227,35 @@ impl BackendTexture for OpenGLTexture {
fn draw(&mut self) -> GameResult {
unsafe {
if let Some(gl) = &GL_PROC {
if self.texture_id == 0 {
return Ok(());
}
if gl.gl.BindSampler.is_loaded() {
gl.gl.BindSampler(0, 0);
}
gl.gl.Enable(gl::TEXTURE_2D);
gl.gl.Enable(gl::BLEND);
gl.gl.Disable(gl::DEPTH_TEST);
self.shader.bind_attrib_pointer(gl, self.vbo);
gl.gl.BindTexture(gl::TEXTURE_2D, self.texture_id);
gl.gl.BufferData(
gl::ARRAY_BUFFER,
(self.vertices.len() * mem::size_of::<VertexData>()) as _,
self.vertices.as_ptr() as _,
gl::STREAM_DRAW,
);
gl.gl.DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as _);
gl.gl.BindTexture(gl::TEXTURE_2D, 0);
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
Ok(())
} else {
Err(RenderError("No OpenGL context available!".to_string()))
let gl = self.gl.as_ref();
if self.texture_id == 0 {
return Ok(());
}
if gl.gl.BindSampler.is_loaded() {
gl.gl.BindSampler(0, 0);
}
gl.gl.Enable(gl::TEXTURE_2D);
gl.gl.Enable(gl::BLEND);
gl.gl.Disable(gl::DEPTH_TEST);
self.shader.bind_attrib_pointer(gl, self.vbo);
gl.gl.BindTexture(gl::TEXTURE_2D, self.texture_id);
gl.gl.BufferData(
gl::ARRAY_BUFFER,
(self.vertices.len() * mem::size_of::<VertexData>()) as _,
self.vertices.as_ptr() as _,
gl::STREAM_DRAW,
);
gl.gl.DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as _);
gl.gl.BindTexture(gl::TEXTURE_2D, 0);
gl.gl.BindBuffer(gl::ARRAY_BUFFER, 0);
Ok(())
}
}
@ -266,16 +266,20 @@ impl BackendTexture for OpenGLTexture {
impl Drop for OpenGLTexture {
fn drop(&mut self) {
if *self.context_active.as_ref().borrow() {
unsafe {
if let Some(gl) = &GL_PROC {
if self.texture_id != 0 {
let texture_id = &self.texture_id;
gl.gl.DeleteTextures(1, texture_id as *const _);
}
unsafe {
let gl = self.gl.as_ref();
if !*gl.context_active.borrow() {
return;
}
if self.framebuffer_id != 0 {}
}
if self.texture_id != 0 {
let texture_id = &self.texture_id;
gl.gl.DeleteTextures(1, texture_id as *const _);
}
if self.framebuffer_id != 0 {
let framebuffer_id = &self.framebuffer_id;
gl.gl.DeleteFramebuffers(1, framebuffer_id as *const _);
}
}
}
@ -492,21 +496,18 @@ impl RenderData {
// iOS has "unusual" framebuffer setup, where we can't rely on 0 as the system provided render target.
self.render_fbo = return_param(|x| gl.gl.GetIntegerv(gl::FRAMEBUFFER_BINDING, x));
self.tex_shader =
RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
log::error!("Failed to compile texture shader: {}", e);
RenderShader::default()
});
self.fill_shader =
RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
log::error!("Failed to compile fill shader: {}", e);
RenderShader::default()
});
self.fill_water_shader =
RenderShader::compile(gl, vshdr_basic, fshdr_fill_water).unwrap_or_else(|e| {
log::error!("Failed to compile fill water shader: {}", e);
RenderShader::default()
});
self.tex_shader = RenderShader::compile(gl, vshdr_basic, fshdr_tex).unwrap_or_else(|e| {
log::error!("Failed to compile texture shader: {}", e);
RenderShader::default()
});
self.fill_shader = RenderShader::compile(gl, vshdr_basic, fshdr_fill).unwrap_or_else(|e| {
log::error!("Failed to compile fill shader: {}", e);
RenderShader::default()
});
self.fill_water_shader = RenderShader::compile(gl, vshdr_basic, fshdr_fill_water).unwrap_or_else(|e| {
log::error!("Failed to compile fill water shader: {}", e);
RenderShader::default()
});
self.vbo = return_param(|x| gl.gl.GenBuffers(1, x));
self.ebo = return_param(|x| gl.gl.GenBuffers(1, x));
@ -575,17 +576,12 @@ impl RenderData {
pub struct Gl {
pub gl: gl::Gles2,
pub context_active: RefCell<bool>,
}
static mut GL_PROC: Option<Gl> = None;
pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
pub fn load_gl(gl_context: &mut GLContext) -> Gl {
unsafe {
if let Some(gl) = &GL_PROC {
return gl;
}
let gl = gl::Gles2::load_with(|ptr| (gl_context.get_proc_address)(&mut gl_context.user_data, ptr));
let gl = gl::Gles2::load_with(|ptr| gl_context.platform.get_proc_address(ptr));
let version = {
let p = gl.GetString(gl::VERSION);
@ -599,16 +595,15 @@ pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
log::info!("OpenGL version {}", version);
GL_PROC = Some(Gl { gl });
GL_PROC.as_ref().unwrap()
Gl { gl, context_active: RefCell::new(true) }
}
}
pub struct OpenGLRenderer {
refs: GLContext,
gl: Option<Rc<Gl>>,
imgui: UnsafeCell<imgui::Context>,
render_data: RenderData,
context_active: Arc<RefCell<bool>>,
def_matrix: [[f32; 4]; 4],
curr_matrix: [[f32; 4]; 4],
}
@ -617,22 +612,25 @@ impl OpenGLRenderer {
pub fn new(refs: GLContext, imgui: imgui::Context) -> OpenGLRenderer {
OpenGLRenderer {
refs,
gl: None,
imgui: UnsafeCell::new(imgui),
render_data: RenderData::new(),
context_active: Arc::new(RefCell::new(true)),
def_matrix: [[0.0; 4]; 4],
curr_matrix: [[0.0; 4]; 4],
}
}
fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> {
fn get_context(&mut self) -> Option<(&mut GLContext, Rc<Gl>)> {
let imgui = unsafe { &mut *self.imgui.get() };
let gles2 = self.refs.gles2_mode;
let gl = load_gl(&mut self.refs);
if let None = self.gl {
self.gl = Some(Rc::new(load_gl(&mut self.refs)));
}
let gl = self.gl.clone().unwrap();
if !self.render_data.initialized {
self.render_data.init(gles2, imgui, gl);
self.render_data.init(gles2, imgui, &gl);
}
Some((&mut self.refs, gl))
@ -674,7 +672,7 @@ impl BackendRenderer for OpenGLRenderer {
let matrix =
[[2.0f32, 0.0, 0.0, 0.0], [0.0, -2.0, 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [-1.0, 1.0, 0.0, 1.0]];
self.render_data.tex_shader.bind_attrib_pointer(gl, self.render_data.vbo);
self.render_data.tex_shader.bind_attrib_pointer(&gl, self.render_data.vbo);
gl.gl.UniformMatrix4fv(self.render_data.tex_shader.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
let color = (255, 255, 255, 255);
@ -693,46 +691,24 @@ impl BackendRenderer for OpenGLRenderer {
self.render_data.surf_texture,
BackendShader::Texture,
)?;
//gl.gl.Finish();
}
if let Some((context, _)) = self.get_context() {
(context.swap_buffers)(&mut context.user_data);
context.platform.swap_buffers();
}
}
Ok(())
}
fn set_vsync_mode(&mut self, mode: VSyncMode) -> GameResult {
if !self.refs.is_sdl {
return Ok(());
fn set_swap_mode(&mut self, mode: SwapMode) -> GameResult {
if let Some((ctx, _)) = self.get_context() {
ctx.platform.set_swap_mode(mode);
Ok(())
} else {
Err(RenderError("No OpenGL context available!".to_string()))
}
#[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 {
@ -854,7 +830,7 @@ impl BackendRenderer for OpenGLRenderer {
vertices: Vec::new(),
shader: self.render_data.tex_shader,
vbo: self.render_data.vbo,
context_active: self.context_active.clone(),
gl: gl.clone(),
}))
}
} else {
@ -893,7 +869,7 @@ impl BackendRenderer for OpenGLRenderer {
vertices: Vec::new(),
shader: self.render_data.tex_shader,
vbo: self.render_data.vbo,
context_active: self.context_active.clone(),
gl: gl.clone(),
}))
}
} else {
@ -1009,7 +985,7 @@ impl BackendRenderer for OpenGLRenderer {
fn draw_rect(&mut self, rect: Rect<isize>, color: Color) -> GameResult {
unsafe {
if let Some(gl) = &GL_PROC {
if let Some((_, gl)) = self.get_context() {
let color = color.to_rgba();
let mut uv = self.render_data.font_tex_size;
uv.0 = 0.0 / uv.0;
@ -1024,7 +1000,7 @@ impl BackendRenderer for OpenGLRenderer {
VertexData { position: (rect.right as _, rect.bottom as _), uv, color },
];
self.render_data.fill_shader.bind_attrib_pointer(gl, self.render_data.vbo);
self.render_data.fill_shader.bind_attrib_pointer(&gl, self.render_data.vbo);
gl.gl.BindTexture(gl::TEXTURE_2D, self.render_data.font_texture);
gl.gl.BindBuffer(gl::ARRAY_BUFFER, self.render_data.vbo);
@ -1262,16 +1238,16 @@ impl OpenGLRenderer {
mut texture: u32,
shader: BackendShader,
) -> GameResult<()> {
if let Some(gl) = &GL_PROC {
if let Some((_, gl)) = self.get_context() {
match shader {
BackendShader::Fill => {
self.render_data.fill_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
self.render_data.fill_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
}
BackendShader::Texture => {
self.render_data.tex_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
self.render_data.tex_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
}
BackendShader::WaterFill(scale, t, frame_pos) => {
self.render_data.fill_water_shader.bind_attrib_pointer(gl, self.render_data.vbo)?;
self.render_data.fill_water_shader.bind_attrib_pointer(&gl, self.render_data.vbo)?;
gl.gl.Uniform1f(self.render_data.fill_water_shader.scale, scale);
gl.gl.Uniform1f(self.render_data.fill_water_shader.time, t);
gl.gl.Uniform2f(self.render_data.fill_water_shader.frame_offset, frame_pos.0, frame_pos.1);
@ -1301,6 +1277,8 @@ impl OpenGLRenderer {
impl Drop for OpenGLRenderer {
fn drop(&mut self) {
*self.context_active.as_ref().borrow_mut() = false;
if let Some(gl) = &self.gl {
gl.context_active.replace(false);
}
}
}

View file

@ -11,10 +11,10 @@ use scripting::tsc::text_script::ScriptMode;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::framework::graphics::VSyncMode;
use crate::framework::graphics::{self, SwapMode};
use crate::framework::ui::UI;
use crate::game::filesystem_container::FilesystemContainer;
use crate::game::settings::VSyncMode;
use crate::game::shared_game_state::{Fps, SharedGameState, TimingMode};
use crate::graphics::texture_set::{G_MAG, I_MAG};
use crate::scene::loading_scene::LoadingScene;
@ -61,7 +61,7 @@ pub struct Game {
impl Game {
fn new(ctx: &mut Context) -> GameResult<Game> {
let s = Game {
scene: RefCell::new( None),
scene: RefCell::new(None),
ui: UI::new(ctx)?,
state: RefCell::new(SharedGameState::new(ctx)?),
start_time: Instant::now(),
@ -80,14 +80,12 @@ impl Game {
let state_ref = self.state.get_mut();
if let Some(scene) = self.scene.get_mut() {
let speed = if state_ref.textscript_vm.mode == ScriptMode::Map
&& state_ref.textscript_vm.flags.cutscene_skip()
{
4.0
} else {
1.0
} * state_ref.settings.speed;
let speed =
if state_ref.textscript_vm.mode == ScriptMode::Map && state_ref.textscript_vm.flags.cutscene_skip() {
4.0
} else {
1.0
} * state_ref.settings.speed;
match state_ref.settings.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
@ -133,7 +131,7 @@ impl Game {
if let Some(mut next_scene) = next_scene {
next_scene.init(state_ref, ctx)?;
*self.scene.get_mut() = Some(next_scene);
self.loops = 0;
state_ref.frame_time = 0.0;
}
@ -142,14 +140,21 @@ impl Game {
}
pub(crate) fn draw(&mut self, ctx: &mut Context) -> GameResult {
match ctx.vsync_mode {
VSyncMode::Uncapped | VSyncMode::VSync => {
let vsync_mode = self.state.get_mut().settings.vsync_mode;
match vsync_mode {
VSyncMode::Uncapped => {
graphics::set_swap_mode(ctx, SwapMode::Immediate)?;
self.present = true;
}
VSyncMode::VSync => {
graphics::set_swap_mode(ctx, SwapMode::VSync)?;
self.present = true;
}
_ => unsafe {
graphics::set_swap_mode(ctx, SwapMode::Adaptive)?;
self.present = false;
let divisor = match ctx.vsync_mode {
let divisor = match vsync_mode {
VSyncMode::VRRTickSync1x => 1,
VSyncMode::VRRTickSync2x => 2,
VSyncMode::VRRTickSync3x => 3,
@ -194,7 +199,8 @@ impl Game {
let n1 = (elapsed - self.last_tick) as f64;
let n2 = (self.next_tick - self.last_tick) as f64;
self.state.get_mut().frame_time = if self.state.get_mut().settings.motion_interpolation { n1 / n2 } else { 1.0 };
self.state.get_mut().frame_time =
if self.state.get_mut().settings.motion_interpolation { n1 / n2 } else { 1.0 };
}
unsafe {
G_MAG = if self.state.get_mut().settings.subpixel_coords { self.state.get_mut().scale } else { 1.0 };

View file

@ -4,7 +4,6 @@ use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem::{user_create, user_open};
use crate::framework::gamepad::{Axis, AxisDirection, Button, PlayerControllerInputType};
use crate::framework::graphics::VSyncMode;
use crate::framework::keyboard::ScanCode;
use crate::game::player::TargetPlayer;
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, TimingMode, WindowMode};
@ -582,6 +581,20 @@ pub fn default_controller_axis_sensitivity() -> f64 {
0.3
}
#[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,
}
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq)]
pub enum LightingEngine {
Default,

View file

@ -2,9 +2,9 @@ use itertools::Itertools;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::VSyncMode;
use crate::framework::{filesystem, graphics};
use crate::framework::filesystem;
use crate::game::settings::LightingEngine;
use crate::game::settings::VSyncMode;
use crate::game::shared_game_state::{CutsceneSkipMode, ScreenShakeIntensity, SharedGameState, TimingMode, WindowMode};
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
@ -725,7 +725,6 @@ impl SettingsMenu {
*value = new_value;
state.settings.vsync_mode = new_mode;
graphics::set_vsync_mode(ctx, new_mode)?;
let _ = state.settings.save(ctx);
}
@ -742,7 +741,6 @@ impl SettingsMenu {
*value = new_value;
state.settings.vsync_mode = new_mode;
graphics::set_vsync_mode(ctx, new_mode)?;
let _ = state.settings.save(ctx);
}

View file

@ -1,6 +1,5 @@
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics;
use crate::game::shared_game_state::SharedGameState;
use crate::scene::no_data_scene::NoDataScene;
use crate::scene::Scene;
@ -44,8 +43,6 @@ 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(