From 6b707a1ab7ea8a77196bf208b810dd88fdcc43bf Mon Sep 17 00:00:00 2001 From: IBotDEU Date: Sun, 9 Aug 2020 20:30:22 +0000 Subject: [PATCH] implemented ability to select bit depth and refresh rate and implemented seperate setting for fullscreen resolution --- CHANGELOG.md | 1 + Cargo.lock | 1 + assets/voxygen/i18n/de_DE.ron | 5 + assets/voxygen/i18n/en.ron | 4 + voxygen/Cargo.toml | 1 + voxygen/src/hud/mod.rs | 12 ++ voxygen/src/hud/settings_window.rs | 175 ++++++++++++++++++++++- voxygen/src/session.rs | 31 +++++ voxygen/src/settings.rs | 6 + voxygen/src/window.rs | 217 ++++++++++++++++++++++++++--- 10 files changed, 433 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 078d5790e9..06d7096ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add detection of entities under the cursor - Functional group-system with exp-sharing and disabled damage to group members - Some Campfire, fireball & bomb; particle, light & sound effects. +- Added setting to change resolution ### Changed diff --git a/Cargo.lock b/Cargo.lock index c748709104..afe3d80245 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4719,6 +4719,7 @@ dependencies = [ "guillotiere", "hashbrown", "image", + "itertools", "msgbox", "num 0.2.1", "old_school_gfx_glutin_ext", diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index f1f3f9da17..86f73364f8 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -70,6 +70,7 @@ VoxygenLocalization( "common.fatal_error": "Fataler Fehler", "common.decline": "Ablehnen", "common.you": "Ihr", + "common.automatic": "Auto", /// End Common section // Message when connection to the server is lost @@ -297,6 +298,10 @@ magischen Gegenstände ergattern?"#, "hud.settings.fluid_rendering_mode.cheap": "Niedrig", "hud.settings.fluid_rendering_mode.shiny": "Hoch", "hud.settings.cloud_rendering_mode.regular": "Realistisch", + "hud.settings.particles": "Partikel", + "hud.settings.resolution": "Auflösung", + "hud.settings.bit_depth": "Bittiefe", + "hud.settings.refresh_rate": "Bildwiederholrate", "hud.settings.fullscreen": "Vollbild", "hud.settings.save_window_size": "Größe speichern", diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index b3a2004539..a9272a153e 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -72,6 +72,7 @@ VoxygenLocalization( "common.error": "Error", "common.fatal_error": "Fatal Error", "common.you": "You", + "common.automatic": "Auto", // Message when connection to the server is lost "common.connection_lost": r#"Connection lost! @@ -298,6 +299,9 @@ magically infused items?"#, "hud.settings.fluid_rendering_mode.shiny": "Shiny", "hud.settings.cloud_rendering_mode.regular": "Regular", "hud.settings.particles": "Particles", + "hud.settings.resolution": "Resolution", + "hud.settings.bit_depth": "Bit Depth", + "hud.settings.refresh_rate": "Refresh Rate", "hud.settings.fullscreen": "Fullscreen", "hud.settings.save_window_size": "Save window size", diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index e8ff69392e..937e894ffb 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -69,6 +69,7 @@ bincode = "1.2" deunicode = "1.0" uvth = "3.1.1" const-tweaker = { version = "0.3.1", optional = true } +itertools = "0.9.0" # Logging tracing = "0.1" diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 20a48df35e..440bb77848 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -283,6 +283,9 @@ pub enum Event { ChangeAaMode(AaMode), ChangeCloudMode(CloudMode), ChangeFluidMode(FluidMode), + ChangeResolution([u16; 2]), + ChangeBitDepth(Option), + ChangeRefreshRate(Option), CrosshairTransp(f32), ChatTransp(f32), ChatCharName(bool), @@ -1908,6 +1911,15 @@ impl Hud { settings_window::Event::ChangeFluidMode(new_fluid_mode) => { events.push(Event::ChangeFluidMode(new_fluid_mode)); }, + settings_window::Event::ChangeResolution(new_resolution) => { + events.push(Event::ChangeResolution(new_resolution)); + }, + settings_window::Event::ChangeBitDepth(new_bit_depth) => { + events.push(Event::ChangeBitDepth(new_bit_depth)); + }, + settings_window::Event::ChangeRefreshRate(new_refresh_rate) => { + events.push(Event::ChangeRefreshRate(new_refresh_rate)); + }, settings_window::Event::ChangeLanguage(language) => { events.push(Event::ChangeLanguage(language)); }, diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index f9e6b7d17b..7ece673e69 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -17,6 +17,10 @@ use conrod_core::{ WidgetCommon, }; +use itertools::Itertools; +use std::iter::once; +use winit::monitor::VideoMode; + const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; widget_ids! { @@ -111,8 +115,17 @@ widget_ids! { cloud_mode_list, fluid_mode_text, fluid_mode_list, + // + resolution, + resolution_label, + bit_depth, + bit_depth_label, + refresh_rate, + refresh_rate_label, + // particles_button, particles_label, + // fullscreen_button, fullscreen_label, save_window_size_button, @@ -239,6 +252,9 @@ pub enum Event { ChangeAaMode(AaMode), ChangeCloudMode(CloudMode), ChangeFluidMode(FluidMode), + ChangeResolution([u16; 2]), + ChangeBitDepth(Option), + ChangeRefreshRate(Option), AdjustMusicVolume(f32), AdjustSfxVolume(f32), ChangeAudioDevice(String), @@ -2039,11 +2055,168 @@ impl<'a> Widget for SettingsWindow<'a> { events.push(Event::ToggleParticlesEnabled(particles_enabled)); } + // Resolution, Bit Depth and Refresh Rate + let video_modes: Vec = self + .global_state + .window + .window() + .window() + .current_monitor() + .video_modes() + .collect(); + + // Resolution + let resolutions: Vec<[u16; 2]> = video_modes + .iter() + .sorted_by_key(|mode| mode.size().height) + .sorted_by_key(|mode| mode.size().width) + .map(|mode| [mode.size().width as u16, mode.size().height as u16]) + .dedup() + .collect(); + + Text::new(&self.localized_strings.get("hud.settings.resolution")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.particles_label, 8.0) + .color(TEXT_COLOR) + .set(state.ids.resolution_label, ui); + + if let Some(clicked) = DropDownList::new( + resolutions + .iter() + .map(|res| format!("{}x{}", res[0], res[1])) + .collect::>() + .as_slice(), + resolutions + .iter() + .position(|res| res == &self.global_state.settings.graphics.resolution), + ) + .w_h(128.0, 22.0) + .color(MENU_BG) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.opensans.conrod_id) + .down_from(state.ids.resolution_label, 10.0) + .set(state.ids.resolution, ui) + { + events.push(Event::ChangeResolution(resolutions[clicked])); + } + + // Bit Depth and Refresh Rate + let correct_res: Vec = video_modes + .into_iter() + .filter(|mode| { + mode.size().width == self.global_state.settings.graphics.resolution[0] as u32 + }) + .filter(|mode| { + mode.size().height == self.global_state.settings.graphics.resolution[1] as u32 + }) + .collect(); + + // Bit Depth + let bit_depths: Vec = correct_res + .iter() + .filter( + |mode| match self.global_state.settings.graphics.refresh_rate { + Some(refresh_rate) => mode.refresh_rate() == refresh_rate, + None => true, + }, + ) + .sorted_by_key(|mode| mode.bit_depth()) + .map(|mode| mode.bit_depth()) + .rev() + .dedup() + .collect(); + + Text::new(&self.localized_strings.get("hud.settings.bit_depth")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.particles_label, 8.0) + .right_from(state.ids.resolution, 8.0) + .color(TEXT_COLOR) + .set(state.ids.bit_depth_label, ui); + + if let Some(clicked) = DropDownList::new( + once(String::from(self.localized_strings.get("common.automatic"))) + .chain(bit_depths.iter().map(|depth| format!("{}", depth))) + .collect::>() + .as_slice(), + match self.global_state.settings.graphics.bit_depth { + Some(bit_depth) => bit_depths + .iter() + .position(|depth| depth == &bit_depth) + .map(|index| index + 1), + None => Some(0), + }, + ) + .w_h(128.0, 22.0) + .color(MENU_BG) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.opensans.conrod_id) + .down_from(state.ids.bit_depth_label, 10.0) + .right_from(state.ids.resolution, 8.0) + .set(state.ids.bit_depth, ui) + { + events.push(Event::ChangeBitDepth(if clicked == 0 { + None + } else { + Some(bit_depths[clicked - 1]) + })); + } + + // Refresh Rate + let refresh_rates: Vec = correct_res + .into_iter() + .filter(|mode| match self.global_state.settings.graphics.bit_depth { + Some(bit_depth) => mode.bit_depth() == bit_depth, + None => true, + }) + .sorted_by_key(|mode| mode.refresh_rate()) + .map(|mode| mode.refresh_rate()) + .rev() + .dedup() + .collect(); + + Text::new(&self.localized_strings.get("hud.settings.refresh_rate")) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.particles_label, 8.0) + .right_from(state.ids.bit_depth, 8.0) + .color(TEXT_COLOR) + .set(state.ids.refresh_rate_label, ui); + + if let Some(clicked) = DropDownList::new( + once(String::from(self.localized_strings.get("common.automatic"))) + .chain(refresh_rates.iter().map(|rate| format!("{}", rate))) + .collect::>() + .as_slice(), + match self.global_state.settings.graphics.refresh_rate { + Some(refresh_rate) => refresh_rates + .iter() + .position(|rate| rate == &refresh_rate) + .map(|index| index + 1), + None => Some(0), + }, + ) + .w_h(128.0, 22.0) + .color(MENU_BG) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.opensans.conrod_id) + .down_from(state.ids.refresh_rate_label, 10.0) + .right_from(state.ids.bit_depth, 8.0) + .set(state.ids.refresh_rate, ui) + { + events.push(Event::ChangeRefreshRate(if clicked == 0 { + None + } else { + Some(refresh_rates[clicked - 1]) + })); + } + // Fullscreen Text::new(&self.localized_strings.get("hud.settings.fullscreen")) .font_size(self.fonts.cyri.scale(14)) .font_id(self.fonts.cyri.conrod_id) - .down_from(state.ids.particles_label, 8.0) + .down_from(state.ids.resolution, 8.0) .color(TEXT_COLOR) .set(state.ids.fullscreen_label, ui); diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 0decee01f3..dc43964b3d 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -949,6 +949,37 @@ impl PlayState for SessionState { global_state.settings.graphics.fluid_mode = new_fluid_mode; global_state.settings.save_to_file_warn(); }, + HudEvent::ChangeResolution(new_resolution) => { + // Do this first so if it crashes the setting isn't saved :) + global_state.window.fullscreen( + global_state.settings.graphics.fullscreen, + new_resolution, + global_state.settings.graphics.bit_depth, + global_state.settings.graphics.refresh_rate, + ); + global_state.settings.graphics.resolution = new_resolution; + global_state.settings.save_to_file_warn(); + }, + HudEvent::ChangeBitDepth(new_bit_depth) => { + global_state.window.fullscreen( + global_state.settings.graphics.fullscreen, + global_state.settings.graphics.resolution, + new_bit_depth, + global_state.settings.graphics.refresh_rate, + ); + global_state.settings.graphics.bit_depth = new_bit_depth; + global_state.settings.save_to_file_warn(); + }, + HudEvent::ChangeRefreshRate(new_refresh_rate) => { + global_state.window.fullscreen( + global_state.settings.graphics.fullscreen, + global_state.settings.graphics.resolution, + global_state.settings.graphics.bit_depth, + new_refresh_rate, + ); + global_state.settings.graphics.refresh_rate = new_refresh_rate; + global_state.settings.save_to_file_warn(); + }, HudEvent::ChangeLanguage(new_language) => { global_state.settings.language.selected_language = new_language.language_identifier; diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 43b0f2f19c..86b3c0c9e7 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -619,6 +619,9 @@ pub struct GraphicsSettings { pub aa_mode: AaMode, pub cloud_mode: CloudMode, pub fluid_mode: FluidMode, + pub resolution: [u16; 2], + pub bit_depth: Option, + pub refresh_rate: Option, pub window_size: [u16; 2], pub fullscreen: bool, } @@ -636,6 +639,9 @@ impl Default for GraphicsSettings { aa_mode: AaMode::Fxaa, cloud_mode: CloudMode::Regular, fluid_mode: FluidMode::Shiny, + resolution: [1920, 1080], + bit_depth: None, + refresh_rate: None, window_size: [1920, 1080], fullscreen: false, } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index b8a6f913c4..8b43a2d0c1 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -7,11 +7,13 @@ use crate::{ use crossbeam::channel; use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; +use itertools::Itertools; use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use tracing::{error, info, warn}; use vek::*; +use winit::monitor::VideoMode; /// Represents a key that the game recognises after input mapping. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] @@ -610,7 +612,12 @@ impl Window { toggle_fullscreen: false, }; - this.fullscreen(settings.graphics.fullscreen); + this.fullscreen( + settings.graphics.fullscreen, + settings.graphics.resolution, + settings.graphics.bit_depth, + settings.graphics.refresh_rate, + ); Ok((this, event_loop)) } @@ -1056,34 +1063,206 @@ impl Window { } pub fn toggle_fullscreen(&mut self, settings: &mut Settings) { - self.fullscreen(!self.is_fullscreen()); + self.fullscreen( + !self.is_fullscreen(), + settings.graphics.resolution, + settings.graphics.bit_depth, + settings.graphics.refresh_rate, + ); settings.graphics.fullscreen = self.is_fullscreen(); settings.save_to_file_warn(); } pub fn is_fullscreen(&self) -> bool { self.fullscreen } - pub fn fullscreen(&mut self, fullscreen: bool) { + pub fn select_video_mode_rec( + &self, + resolution: [u16; 2], + bit_depth: Option, + refresh_rate: Option, + correct_res: Option>, + correct_depth: Option>, + correct_rate: Option>, + ) -> Option { + // if a previous iteration of this method filtered the available video modes for + // the correct resolution already, load that value, otherwise filter it + // in this iteration + let correct_res = correct_res.unwrap_or_else(|| { + let window = self.window.window(); + window + .current_monitor() + .video_modes() + .filter(|mode| mode.size().width == resolution[0] as u32) + .filter(|mode| mode.size().height == resolution[1] as u32) + .collect() + }); + + match bit_depth { + // A bit depth is given + Some(depth) => { + // analogous to correct_res + let correct_depth = correct_depth.unwrap_or_else(|| { + correct_res + .iter() + .find(|mode| mode.bit_depth() == depth) + .cloned() + }); + + match refresh_rate { + // A bit depth and a refresh rate is given + Some(rate) => { + // analogous to correct_res + let correct_rate = correct_rate.unwrap_or_else(|| { + correct_res + .iter() + .find(|mode| mode.refresh_rate() == rate) + .cloned() + }); + + // if no video mode with the given bit depth and refresh rate exists, fall + // back to a video mode that fits the resolution and either bit depth or + // refresh rate depending on which parameter was causing the correct video + // mode not to be found + correct_res + .iter() + .filter(|mode| mode.bit_depth() == depth) + .find(|mode| mode.refresh_rate() == rate) + .cloned() + .or_else(|| { + if correct_depth.is_none() && correct_rate.is_none() { + warn!( + "Bit depth and refresh rate specified in settings are \ + incompatible with the monitor. Choosing highest bit \ + depth and refresh rate possible instead." + ); + } + + self.select_video_mode_rec( + resolution, + correct_depth.is_some().then_some(depth), + correct_rate.is_some().then_some(rate), + Some(correct_res), + Some(correct_depth), + Some(correct_rate), + ) + }) + }, + // A bit depth and no refresh rate is given + // if no video mode with the given bit depth exists, fall + // back to a video mode that fits only the resolution + None => match correct_depth { + Some(mode) => Some(mode), + None => { + warn!( + "Bit depth specified in settings is incompatible with the \ + monitor. Choosing highest bit depth possible instead." + ); + + self.select_video_mode_rec( + resolution, + None, + None, + Some(correct_res), + Some(correct_depth), + None, + ) + }, + }, + } + }, + // No bit depth is given + None => match refresh_rate { + // No bit depth and a refresh rate is given + Some(rate) => { + // analogous to correct_res + let correct_rate = correct_rate.unwrap_or_else(|| { + correct_res + .iter() + .find(|mode| mode.refresh_rate() == rate) + .cloned() + }); + + // if no video mode with the given bit depth exists, fall + // back to a video mode that fits only the resolution + match correct_rate { + Some(mode) => Some(mode), + None => { + warn!( + "Refresh rate specified in settings is incompatible with the \ + monitor. Choosing highest refresh rate possible instead." + ); + + self.select_video_mode_rec( + resolution, + None, + None, + Some(correct_res), + None, + Some(correct_rate), + ) + }, + } + }, + // No bit depth and no refresh rate is given + // get the video mode with the specified resolution and the max bit depth and + // refresh rate + None => correct_res + .into_iter() + // Prefer bit depth over refresh rate + .sorted_by_key(|mode| mode.bit_depth()) + .max_by_key(|mode| mode.refresh_rate()), + }, + } + } + + pub fn select_video_mode( + &self, + resolution: [u16; 2], + bit_depth: Option, + refresh_rate: Option, + ) -> VideoMode { + // (resolution, bit depth, refresh rate) represents a video mode + // spec: as specified + // max: maximum value available + + // order of fallbacks as follows: + // (spec, spec, spec) + // (spec, spec, max), (spec, max, spec) + // (spec, max, max) + // (max, max, max) + self.select_video_mode_rec(resolution, bit_depth, refresh_rate, None, None, None) + // if there is no video mode with the specified resolution, fall back to the video mode with max resolution, bit depth and refresh rate + .unwrap_or_else(|| { + warn!( + "Resolution specified in settings is incompatible with the monitor. Choosing \ + highest resolution possible instead." + ); + + self + .window + .window() + .current_monitor() + .video_modes() + // Prefer bit depth over refresh rate + .sorted_by_key(|mode| mode.refresh_rate()) + .sorted_by_key(|mode| mode.bit_depth()) + .max_by_key(|mode| mode.size().width) + .expect("No video modes available!!") + }) + } + + pub fn fullscreen( + &mut self, + fullscreen: bool, + resolution: [u16; 2], + bit_depth: Option, + refresh_rate: Option, + ) { let window = self.window.window(); self.fullscreen = fullscreen; if fullscreen { window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( - window - .current_monitor() - .video_modes() - .filter(|mode| mode.bit_depth() >= 24 && mode.refresh_rate() >= 59) - .max_by_key(|mode| mode.size().width) - .unwrap_or_else(|| { - warn!( - "No video mode with a bit depth of at least 24 and a refresh rate of \ - at least 60Hz found" - ); - window - .current_monitor() - .video_modes() - .max_by_key(|mode| mode.size().width) - .expect("No video modes available!!") - }), + self.select_video_mode(resolution, bit_depth, refresh_rate), ))); } else { window.set_fullscreen(None);