scaling / ui tweaks, persistent timing

This commit is contained in:
Alula 2021-10-14 09:43:17 +02:00
parent 4854d9e758
commit d147242199
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
8 changed files with 178 additions and 43 deletions

View File

@ -1,5 +1,5 @@
use crate::common::Rect;
use crate::components::draw_common::{Alignment, draw_number};
use crate::components::draw_common::{draw_number, Alignment};
use crate::entity::GameEntity;
use crate::frame::Frame;
use crate::framework::context::Context;
@ -61,6 +61,15 @@ impl InventoryUI {
fn get_item_event_number_action(&self, inventory: &Inventory) -> u16 {
inventory.get_item_idx(self.selected_item as usize).map(|i| i.0 + 6000).unwrap_or(6000)
}
fn exit(&mut self, state: &mut SharedGameState, player: &mut Player, inventory: &mut Inventory) {
self.focus = InventoryFocus::None;
inventory.current_item = 0;
self.text_y_pos = 16;
state.textscript_vm.reset();
state.textscript_vm.set_mode(ScriptMode::Map);
player.controller.update_trigger();
}
}
impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
@ -80,12 +89,7 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
|| player.controller.trigger_menu_back()
|| (state.settings.touch_controls && state.touch_controls.consume_click_in(slot_rect)))
{
self.focus = InventoryFocus::None;
inventory.current_item = 0;
self.text_y_pos = 16;
state.textscript_vm.reset();
state.textscript_vm.set_mode(ScriptMode::Map);
player.controller.update_trigger();
self.exit(state, player, inventory);
return Ok(());
}
@ -231,6 +235,7 @@ impl GameEntity<(&mut Context, &mut Player, &mut Inventory)> for InventoryUI {
self.selected_weapon = i;
inventory.current_weapon = i;
state.textscript_vm.start_script(get_weapon_event_number(inventory));
self.exit(state, player, inventory);
}
}

View File

@ -128,6 +128,13 @@ fn get_insets() -> GameResult<(f32, f32, f32, f32)> {
}
}
fn get_scaled_size(width: u32, height: u32) -> (f32, f32) {
let scaled_height = ((height / 480).max(1) * 480) as f32;
let scaled_width = width as f32 * (scaled_height as f32 / height as f32);
(scaled_width, scaled_height)
}
impl BackendEventLoop for GlutinEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
let event_loop = EventLoop::new();
@ -137,7 +144,8 @@ impl BackendEventLoop for GlutinEventLoop {
{
let size = window.window().inner_size();
ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32);
ctx.real_screen_size = (size.width, size.height);
ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1));
state_ref.handle_resize(ctx).unwrap();
}
@ -188,7 +196,8 @@ impl BackendEventLoop for GlutinEventLoop {
imgui.io_mut().display_size = [size.width as f32, size.height as f32];
}
ctx.screen_size = (size.width as f32, size.height as f32);
ctx.real_screen_size = (size.width, size.height);
ctx.screen_size = get_scaled_size(size.width.max(1), size.height.max(1));
state_ref.handle_resize(ctx).unwrap();
}
}
@ -197,19 +206,21 @@ impl BackendEventLoop for GlutinEventLoop {
{
let mut controls = &mut state_ref.touch_controls;
let scale = state_ref.scale as f64;
let loc_x = (touch.location.x * ctx.screen_size.0 as f64 / ctx.real_screen_size.0 as f64) / scale;
let loc_y = (touch.location.y * ctx.screen_size.1 as f64 / ctx.real_screen_size.1 as f64) / scale;
match touch.phase {
TouchPhase::Started | TouchPhase::Moved => {
if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) {
point.last_position = point.position;
point.position = (touch.location.x / scale, touch.location.y / scale);
point.position = (loc_x, loc_y);
} else {
controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1);
let point = TouchPoint {
id: touch.id,
touch_id: controls.touch_id_counter,
position: (touch.location.x / scale, touch.location.y / scale),
position: (loc_x, loc_y),
last_position: (0.0, 0.0),
};
controls.points.push(point);

View File

@ -9,6 +9,7 @@ pub struct Context {
pub(crate) filesystem: Filesystem,
pub(crate) renderer: Option<Box<dyn BackendRenderer>>,
pub(crate) keyboard_context: KeyboardContext,
pub(crate) real_screen_size: (u32, u32),
pub(crate) screen_size: (f32, f32),
pub(crate) screen_insets: (f32, f32, f32, f32),
}
@ -20,6 +21,7 @@ impl Context {
filesystem: Filesystem::new(),
renderer: None,
keyboard_context: KeyboardContext::new(),
real_screen_size: (320, 240),
screen_size: (320.0, 240.0),
screen_insets: (0.0, 0.0, 0.0, 0.0),
}

View File

@ -2,6 +2,7 @@ use std::cell::{RefCell, UnsafeCell};
use std::ffi::{c_void, CStr};
use std::mem;
use std::mem::MaybeUninit;
use std::ptr::null;
use std::sync::Arc;
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert};
@ -395,6 +396,9 @@ struct ImguiData {
ebo: GLuint,
font_texture: GLuint,
font_tex_size: (f32, f32),
surf_framebuffer: GLuint,
surf_texture: GLuint,
last_size: (u32, u32),
}
impl ImguiData {
@ -409,6 +413,9 @@ impl ImguiData {
ebo: 0,
font_texture: 0,
font_tex_size: (1.0, 1.0),
surf_framebuffer: 0,
surf_texture: 0,
last_size: (320, 240),
}
}
@ -510,7 +517,36 @@ impl ImguiData {
atlas.tex_id = (self.font_texture as usize).into();
}
gl.gl.BindTexture(gl::TEXTURE_2D, current_texture as _);
let texture_id = return_param(|x| gl.gl.GenTextures(1, x));
gl.gl.BindTexture(gl::TEXTURE_2D, texture_id);
gl.gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
gl.gl.TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl.gl.TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as _,
320 as _,
240 as _,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
null() as _,
);
gl.gl.BindTexture(gl::TEXTURE_2D, 0 as _);
self.surf_texture = texture_id;
let framebuffer_id = return_param(|x| gl.gl.GenFramebuffers(1, x));
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, framebuffer_id);
gl.gl.FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, texture_id, 0);
let draw_buffers = [gl::COLOR_ATTACHMENT0];
gl.gl.DrawBuffers(1, draw_buffers.as_ptr() as _);
self.surf_framebuffer = framebuffer_id;
}
}
}
@ -631,10 +667,36 @@ impl BackendRenderer for OpenGLRenderer {
}
}
if let Some((context, gl)) = self.get_context() {
unsafe {
gl.gl.Finish();
let ImguiData { program_tex, surf_texture, tex_locs: Locs { proj_mtx, .. }, .. } = self.imgui_data;
unsafe {
if let Some((_, gl)) = self.get_context() {
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
gl.gl.ClearColor(0.0, 0.0, 0.0, 1.0);
gl.gl.Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
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]];
gl.gl.UseProgram(program_tex);
gl.gl.UniformMatrix4fv(proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
let color = (255, 255, 255, 255);
let vertices = vec![
VertexData { position: (0.0, 1.0), uv: (0.0, 0.0), color },
VertexData { position: (0.0, 0.0), uv: (0.0, 1.0), color },
VertexData { position: (1.0, 0.0), uv: (1.0, 1.0), color },
VertexData { position: (0.0, 1.0), uv: (0.0, 0.0), color },
VertexData { position: (1.0, 0.0), uv: (1.0, 1.0), color },
VertexData { position: (1.0, 1.0), uv: (1.0, 0.0), color },
];
self.draw_arrays_tex_id(gl::TRIANGLES, vertices, surf_texture, BackendShader::Texture)?;
gl.gl.Finish();
}
if let Some((context, _)) = self.get_context() {
(context.swap_buffers)(&mut context.user_data);
}
}
@ -645,11 +707,35 @@ impl BackendRenderer for OpenGLRenderer {
fn prepare_draw(&mut self, width: f32, height: f32) -> GameResult {
if let Some((_, gl)) = self.get_context() {
unsafe {
let (width_u, height_u) = (width as u32, height as u32);
if self.imgui_data.last_size != (width_u, height_u) {
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
gl.gl.BindTexture(gl::TEXTURE_2D, self.imgui_data.surf_texture);
gl.gl.TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGBA as _,
width_u as _,
height_u as _,
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
null() as _,
);
gl.gl.BindTexture(gl::TEXTURE_2D, 0 as _);
}
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, self.imgui_data.surf_framebuffer);
gl.gl.ClearColor(0.0, 0.0, 0.0, 0.0);
gl.gl.Clear(gl::COLOR_BUFFER_BIT);
gl.gl.ActiveTexture(gl::TEXTURE0);
gl.gl.BlendEquation(gl::FUNC_ADD);
gl.gl.BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl.gl.Viewport(0, 0, width as _, height as _);
gl.gl.Viewport(0, 0, width_u as _, height_u as _);
self.def_matrix = [
[2.0 / width, 0.0, 0.0, 0.0],
@ -677,7 +763,6 @@ impl BackendRenderer for OpenGLRenderer {
fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
if let Some((_, gl)) = self.get_context() {
unsafe {
let data = vec![0u8; width as usize * height as usize * 4];
let current_texture_id = return_param(|x| gl.gl.GetIntegerv(gl::TEXTURE_BINDING_2D, x)) as u32;
let texture_id = return_param(|x| gl.gl.GenTextures(1, x));
@ -694,7 +779,7 @@ impl BackendRenderer for OpenGLRenderer {
0,
gl::RGBA,
gl::UNSIGNED_BYTE,
data.as_ptr() as _,
null() as _,
);
gl.gl.BindTexture(gl::TEXTURE_2D, current_texture_id);
@ -707,6 +792,8 @@ impl BackendRenderer for OpenGLRenderer {
gl.gl.DrawBuffers(1, draw_buffers.as_ptr() as _);
gl.gl.Viewport(0, 0, width as _, height as _);
gl.gl.ClearColor(0.0, 0.0, 0.0, 0.0);
gl.gl.Clear(gl::COLOR_BUFFER_BIT);
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
// todo error checking: glCheckFramebufferStatus()
@ -805,10 +892,20 @@ impl BackendRenderer for OpenGLRenderer {
];
gl.gl.UseProgram(self.imgui_data.program_fill);
gl.gl.UniformMatrix4fv(self.imgui_data.fill_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _);
gl.gl.UniformMatrix4fv(
self.imgui_data.fill_locs.proj_mtx,
1,
gl::FALSE,
self.curr_matrix.as_ptr() as _,
);
gl.gl.UseProgram(self.imgui_data.program_tex);
gl.gl.Uniform1i(self.imgui_data.tex_locs.texture, 0);
gl.gl.UniformMatrix4fv(self.imgui_data.tex_locs.proj_mtx, 1, gl::FALSE, self.curr_matrix.as_ptr() as _);
gl.gl.UniformMatrix4fv(
self.imgui_data.tex_locs.proj_mtx,
1,
gl::FALSE,
self.curr_matrix.as_ptr() as _,
);
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, gl_texture.framebuffer_id);
} else {
@ -829,7 +926,7 @@ impl BackendRenderer for OpenGLRenderer {
gl::FALSE,
self.def_matrix.as_ptr() as _,
);
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, 0);
gl.gl.BindFramebuffer(gl::FRAMEBUFFER, self.imgui_data.surf_framebuffer);
}
}
@ -1076,6 +1173,23 @@ impl OpenGLRenderer {
return Ok(());
}
let texture_id = if let Some(texture) = texture {
let gl_texture: &Box<OpenGLTexture> = std::mem::transmute(texture);
gl_texture.texture_id
} else {
0
};
self.draw_arrays_tex_id(vert_type, vertices, texture_id, shader)
}
unsafe fn draw_arrays_tex_id(
&mut self,
vert_type: GLenum,
vertices: Vec<VertexData>,
texture: u32,
shader: BackendShader,
) -> GameResult<()> {
if let Some(gl) = GL_PROC.as_ref() {
match shader {
BackendShader::Fill => {
@ -1148,14 +1262,7 @@ impl OpenGLRenderer {
}
}
let texture_id = if let Some(texture) = texture {
let gl_texture: &Box<OpenGLTexture> = std::mem::transmute(texture);
gl_texture.texture_id
} else {
0
};
gl.gl.BindTexture(gl::TEXTURE_2D, texture_id);
gl.gl.BindTexture(gl::TEXTURE_2D, texture);
gl.gl.BufferData(
gl::ARRAY_BUFFER,
(vertices.len() * mem::size_of::<VertexData>()) as _,

View File

@ -104,16 +104,16 @@ impl Game {
if let Some(scene) = self.scene.as_mut() {
let state_ref = unsafe { &mut *self.state.get() };
match state_ref.timing_mode {
match state_ref.settings.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
let last_tick = self.next_tick;
while self.start_time.elapsed().as_nanos() >= self.next_tick && self.loops < 10 {
if (state_ref.settings.speed - 1.0).abs() < 0.01 {
self.next_tick += state_ref.timing_mode.get_delta() as u128;
self.next_tick += state_ref.settings.timing_mode.get_delta() as u128;
} else {
self.next_tick +=
(state_ref.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
(state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
}
self.loops += 1;
}
@ -122,7 +122,7 @@ impl Game {
log::warn!("Frame skip is way too high, a long system lag occurred?");
self.last_tick = self.start_time.elapsed().as_nanos();
self.next_tick = self.last_tick
+ (state_ref.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
+ (state_ref.settings.timing_mode.get_delta() as f64 / state_ref.settings.speed) as u128;
self.loops = 0;
}
@ -152,7 +152,7 @@ impl Game {
return Ok(());
}
if state_ref.timing_mode != TimingMode::FrameSynchronized {
if state_ref.settings.timing_mode != TimingMode::FrameSynchronized {
let mut elapsed = self.start_time.elapsed().as_nanos();
// Even with the non-monotonic Instant mitigation at the start of the event loop, there's still a chance of it not working.

View File

@ -64,7 +64,7 @@ impl SettingsMenu {
self.main.push_entry(MenuEntry::Options(
"Game timing:".to_owned(),
if state.timing_mode == TimingMode::_50Hz { 0 } else { 1 },
if state.settings.timing_mode == TimingMode::_50Hz { 0 } else { 1 },
vec!["50tps (freeware)".to_owned(), "60tps (CS+)".to_owned()],
));
@ -125,13 +125,13 @@ impl SettingsMenu {
}
MenuSelectionResult::Selected(2, toggle) => {
if let MenuEntry::Options(_, value, _) = toggle {
match state.timing_mode {
match state.settings.timing_mode {
TimingMode::_50Hz => {
state.timing_mode = TimingMode::_60Hz;
state.settings.timing_mode = TimingMode::_60Hz;
*value = 1;
}
TimingMode::_60Hz => {
state.timing_mode = TimingMode::_50Hz;
state.settings.timing_mode = TimingMode::_50Hz;
*value = 0;
}
_ => {}

View File

@ -6,6 +6,7 @@ use crate::input::keyboard_player_controller::KeyboardController;
use crate::input::player_controller::PlayerController;
use crate::input::touch_player_controller::TouchPlayerController;
use crate::player::TargetPlayer;
use crate::shared_game_state::TimingMode;
use crate::sound::InterpolationMode;
#[derive(serde::Serialize, serde::Deserialize)]
@ -24,6 +25,8 @@ pub struct Settings {
pub motion_interpolation: bool,
pub touch_controls: bool,
pub soundtrack: String,
#[serde(default = "default_timing")]
pub timing_mode: TimingMode,
#[serde(default = "default_interpolation")]
pub organya_interpolation: InterpolationMode,
#[serde(default = "p1_default_keymap")]
@ -43,7 +46,10 @@ pub struct Settings {
fn default_true() -> bool { true }
#[inline(always)]
fn current_version() -> u32 { 3 }
fn current_version() -> u32 { 4 }
#[inline(always)]
fn default_timing() -> TimingMode { TimingMode::_50Hz }
#[inline(always)]
fn default_interpolation() -> InterpolationMode { InterpolationMode::Linear }
@ -73,6 +79,11 @@ impl Settings {
self.light_cone = true;
}
if self.version == 3 {
self.version = 4;
self.timing_mode = default_timing();
}
if self.version != initial_version {
log::info!("Upgraded configuration file from version {} to {}.", initial_version, self.version);
}
@ -112,6 +123,7 @@ impl Default for Settings {
motion_interpolation: true,
touch_controls: cfg!(target_os = "android"),
soundtrack: "".to_string(),
timing_mode: default_timing(),
organya_interpolation: InterpolationMode::Linear,
player1_key_map: p1_default_keymap(),
player2_key_map: p2_default_keymap(),

View File

@ -32,7 +32,7 @@ use crate::str;
use crate::text_script::{ScriptMode, TextScriptExecutionState, TextScriptVM};
use crate::texture_set::TextureSet;
#[derive(PartialEq, Eq, Copy, Clone)]
#[derive(PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum TimingMode {
_50Hz,
_60Hz,
@ -109,7 +109,6 @@ impl TileSize {
}
pub struct SharedGameState {
pub timing_mode: TimingMode,
pub control_flags: ControlFlags,
pub game_flags: BitVec,
pub skip_flags: BitVec,
@ -212,7 +211,6 @@ impl SharedGameState {
init_hooks();
Ok(SharedGameState {
timing_mode: TimingMode::_50Hz,
control_flags: ControlFlags(0),
game_flags: bitvec::bitvec![0; 8000],
skip_flags: bitvec::bitvec![0; 64],
@ -422,7 +420,7 @@ impl SharedGameState {
}
pub fn current_tps(&self) -> f64 {
self.timing_mode.get_tps() as f64 * self.settings.speed
self.settings.timing_mode.get_tps() as f64 * self.settings.speed
}
pub fn shutdown(&mut self) {