diff --git a/lang/cs.lang b/lang/cs.lang index 8ce116b..d912a56 100644 --- a/lang/cs.lang +++ b/lang/cs.lang @@ -371,7 +371,8 @@ dir.w = západ dir.nw = severozápad val.auto = Auto ({0}x) -set.render_scale = Rozlišení 3D +set.render_scale = Pixelový vzhled +set.pixel_rows_val = {0} řádků set.debug_fps = Zobrazit FPS set.debug_frame_time = Zobrazit čas snímku set.debug_ping = Zobrazit ping diff --git a/lang/da.lang b/lang/da.lang index 71e53ae..7cbb252 100644 --- a/lang/da.lang +++ b/lang/da.lang @@ -370,7 +370,8 @@ dir.w = vest dir.nw = nordvest val.auto = Auto ({0}x) -set.render_scale = 3D-opløsning +set.render_scale = Pixel-look +set.pixel_rows_val = {0} linjer set.debug_fps = Vis FPS set.debug_frame_time = Vis billedtid set.debug_ping = Vis ping diff --git a/lang/de.lang b/lang/de.lang index ab0e6f4..79fe0b3 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -370,7 +370,8 @@ dir.w = Westen dir.nw = Nordwesten val.auto = Auto ({0}x) -set.render_scale = 3D-Auflösung +set.render_scale = Pixel-Look +set.pixel_rows_val = {0} Zeilen set.debug_fps = FPS anzeigen set.debug_frame_time = Framezeit anzeigen set.debug_ping = Ping anzeigen diff --git a/lang/el.lang b/lang/el.lang index ce18668..b7ce770 100644 --- a/lang/el.lang +++ b/lang/el.lang @@ -371,7 +371,8 @@ dir.w = δύση dir.nw = βορειοδυτικά val.auto = Αυτόμ. ({0}x) -set.render_scale = Ανάλυση 3D +set.render_scale = Εμφάνιση pixel +set.pixel_rows_val = {0} γραμμές set.debug_fps = Εμφάνιση FPS set.debug_frame_time = Εμφάνιση χρόνου καρέ set.debug_ping = Εμφάνιση ping diff --git a/lang/en.lang b/lang/en.lang index b069a22..a7225f0 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -371,7 +371,8 @@ dir.w = west dir.nw = north-west val.auto = Auto ({0}x) -set.render_scale = 3D resolution +set.render_scale = Pixel look +set.pixel_rows_val = {0} rows set.debug_fps = Show FPS set.debug_frame_time = Show frame time set.debug_ping = Show ping diff --git a/lang/es.lang b/lang/es.lang index cc9b2d6..b3cf572 100644 --- a/lang/es.lang +++ b/lang/es.lang @@ -371,7 +371,8 @@ dir.w = oeste dir.nw = noroeste val.auto = Auto ({0}x) -set.render_scale = Resolución 3D +set.render_scale = Aspecto pixelado +set.pixel_rows_val = {0} líneas set.debug_fps = Mostrar FPS set.debug_frame_time = Mostrar ms por fotograma set.debug_ping = Mostrar ping diff --git a/lang/fi.lang b/lang/fi.lang index 4f7faf0..1b4c85f 100644 --- a/lang/fi.lang +++ b/lang/fi.lang @@ -371,7 +371,8 @@ dir.w = länsi dir.nw = luode val.auto = Auto ({0}x) -set.render_scale = 3D-tarkkuus +set.render_scale = Pikseliulkoasu +set.pixel_rows_val = {0} riviä set.debug_fps = Näytä FPS set.debug_frame_time = Näytä ruutuaika set.debug_ping = Näytä ping diff --git a/lang/hr.lang b/lang/hr.lang index 92def4e..6b7f068 100644 --- a/lang/hr.lang +++ b/lang/hr.lang @@ -371,7 +371,8 @@ dir.w = zapad dir.nw = sjeverozapad val.auto = Auto ({0}x) -set.render_scale = 3D razlučivost +set.render_scale = Pikselni izgled +set.pixel_rows_val = {0} redaka set.debug_fps = Prikaži FPS set.debug_frame_time = Prikaži vrijeme sličice set.debug_ping = Prikaži ping diff --git a/lang/hu.lang b/lang/hu.lang index 80ca308..3941b7b 100644 --- a/lang/hu.lang +++ b/lang/hu.lang @@ -371,7 +371,8 @@ dir.w = nyugat dir.nw = északnyugat val.auto = Auto ({0}x) -set.render_scale = 3D felbontás +set.render_scale = Pixeles kinézet +set.pixel_rows_val = {0} sor set.debug_fps = FPS mutatása set.debug_frame_time = Képidő mutatása set.debug_ping = Ping mutatása diff --git a/lang/it.lang b/lang/it.lang index fd74f93..5e0a5b7 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -371,7 +371,8 @@ dir.w = ovest dir.nw = nord-ovest val.auto = Auto ({0}x) -set.render_scale = Risoluzione 3D +set.render_scale = Aspetto pixel +set.pixel_rows_val = {0} righe set.debug_fps = Mostra FPS set.debug_frame_time = Mostra tempo frame set.debug_ping = Mostra ping diff --git a/lang/nl.lang b/lang/nl.lang index b501a41..69f8ca1 100644 --- a/lang/nl.lang +++ b/lang/nl.lang @@ -371,7 +371,8 @@ dir.w = westen dir.nw = noordwesten val.auto = Auto ({0}x) -set.render_scale = 3D-resolutie +set.render_scale = Pixellook +set.pixel_rows_val = {0} regels set.debug_fps = FPS tonen set.debug_frame_time = Frametijd tonen set.debug_ping = Ping tonen diff --git a/lang/no.lang b/lang/no.lang index 0ddd918..4755627 100644 --- a/lang/no.lang +++ b/lang/no.lang @@ -370,7 +370,8 @@ dir.w = vest dir.nw = nordvest val.auto = Auto ({0}x) -set.render_scale = 3D-oppløsning +set.render_scale = Pikselutseende +set.pixel_rows_val = {0} linjer set.debug_fps = Vis FPS set.debug_frame_time = Vis bildetid set.debug_ping = Vis ping diff --git a/lang/pt.lang b/lang/pt.lang index 8df3ead..d4a1c4f 100644 --- a/lang/pt.lang +++ b/lang/pt.lang @@ -371,7 +371,8 @@ dir.w = oeste dir.nw = noroeste val.auto = Auto ({0}x) -set.render_scale = Resolução 3D +set.render_scale = Aspeto pixelizado +set.pixel_rows_val = {0} linhas set.debug_fps = Mostrar FPS set.debug_frame_time = Mostrar tempo de frame set.debug_ping = Mostrar ping diff --git a/lang/ro.lang b/lang/ro.lang index 3b435b7..05885b5 100644 --- a/lang/ro.lang +++ b/lang/ro.lang @@ -371,7 +371,8 @@ dir.w = vest dir.nw = nord-vest val.auto = Auto ({0}x) -set.render_scale = Rezoluție 3D +set.render_scale = Aspect pixelat +set.pixel_rows_val = {0} rânduri set.debug_fps = Arată FPS set.debug_frame_time = Arată timpul cadrului set.debug_ping = Arată ping diff --git a/lang/ru.lang b/lang/ru.lang index fa4478f..bf5cd22 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -371,7 +371,8 @@ dir.w = запад dir.nw = северо-запад val.auto = Авто ({0}x) -set.render_scale = Разрешение 3D +set.render_scale = Пиксельный вид +set.pixel_rows_val = {0} строк set.debug_fps = Показывать FPS set.debug_frame_time = Время кадра set.debug_ping = Показывать пинг diff --git a/lang/sk.lang b/lang/sk.lang index d809fba..88d166d 100644 --- a/lang/sk.lang +++ b/lang/sk.lang @@ -371,7 +371,8 @@ dir.w = západ dir.nw = severozápad val.auto = Auto ({0}x) -set.render_scale = Rozlíšenie 3D +set.render_scale = Pixelový vzhľad +set.pixel_rows_val = {0} riadkov set.debug_fps = Zobraziť FPS set.debug_frame_time = Zobraziť čas snímky set.debug_ping = Zobraziť ping diff --git a/lang/sr.lang b/lang/sr.lang index ee3ef6e..020c7b7 100644 --- a/lang/sr.lang +++ b/lang/sr.lang @@ -371,7 +371,8 @@ dir.w = запад dir.nw = северозапад val.auto = Ауто ({0}x) -set.render_scale = 3D резолуција +set.render_scale = Пикселни изглед +set.pixel_rows_val = {0} редова set.debug_fps = Прикажи FPS set.debug_frame_time = Прикажи време кадра set.debug_ping = Прикажи пинг diff --git a/lang/sv.lang b/lang/sv.lang index f7b687d..264c60d 100644 --- a/lang/sv.lang +++ b/lang/sv.lang @@ -371,7 +371,8 @@ dir.w = väster dir.nw = nordväst val.auto = Auto ({0}x) -set.render_scale = 3D-upplösning +set.render_scale = Pixelutseende +set.pixel_rows_val = {0} rader set.debug_fps = Visa FPS set.debug_frame_time = Visa bildtid set.debug_ping = Visa ping diff --git a/src/main.rs b/src/main.rs index 2a5ccb3..3ee0f54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -456,7 +456,7 @@ fn apply_settings(s: &Settings, r3d: &mut R3d) { r3d.outlines = s.outlines; r3d.set_wall_detail(s.wall_subdivisions()); r3d.max_lights = s.max_lights as usize; - r3d.render_div = s.render_scale.clamp(1, 16) as u32; + r3d.pixel_rows = if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows as u32 } else { 360 }; audio::set_volumes(s.music_volume, s.volume, s.muted); } diff --git a/src/menu.rs b/src/menu.rs index 1d0747f..c8c7de9 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -151,15 +151,7 @@ fn item_value(it: Item, s: &Settings) -> String { Item::DebugTriggerZones => onoff(s.debug_trigger_zones), Item::DebugCommandMarkers => onoff(s.debug_command_markers), Item::DebugPathLines => onoff(s.debug_path_lines), - Item::RenderScale => { - // the setting is for a 1080 pixel high window; show what applies to this one - let now = crate::render3d::effective_div(s.render_scale.max(1) as u32, crate::ui::sh()); - if now as u8 == s.render_scale.max(1) { - format!("1/{}", s.render_scale.max(1)) - } else { - format!("1/{} (1/{})", s.render_scale.max(1), now) - } - } + Item::RenderScale => crate::i18n::trf("set.pixel_rows_val", &[s.pixel_rows.to_string()]), Item::GameSpeed => format!("{}x", s.game_speed as i32), Item::Hints => onoff(s.hints), Item::Events => onoff(s.events), @@ -201,7 +193,7 @@ fn item_change(it: Item, s: &mut Settings, d: i32) { Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d), Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d), Item::UiScale => s.ui_factor = cycle(&[0u8, 1, 2, 3, 4], s.ui_factor, d), - Item::RenderScale => s.render_scale = cycle(&(1u8..=16).collect::>(), s.render_scale.clamp(1, 16), d), + Item::RenderScale => s.pixel_rows = cycle(&[270u16, 360, 480], if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows } else { 360 }, d), Item::DebugFps => s.debug_fps = !s.debug_fps, Item::DebugFrameTime => s.debug_frame_time = !s.debug_frame_time, Item::DebugPing => s.debug_ping = !s.debug_ping, diff --git a/src/render3d.rs b/src/render3d.rs index 1a1877e..4fb533e 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -1122,7 +1122,8 @@ pub struct R3d { pub outlines: bool, // tile outlines on/off wall_sub: usize, // wall face subdivisions (detail) pub max_lights: usize, - pub render_div: u32, // 3D resolution divisor (pixel look) + pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off) + up: Option<(RenderTarget, u32, u32)>, // the world scaled up by a whole factor (sharp scaling) target: Option<(RenderTarget, u32, u32)>, // low resolution render target lava: Vec, // lava light per tile (see lava_field) } @@ -1130,13 +1131,16 @@ pub struct R3d { // The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a // proportionally smaller divisor, so the 3D pixels keep their size relative to the window. // 1 (full resolution) stays 1. -pub const RENDER_REF_HEIGHT: f32 = 1080.0; -pub fn effective_div(setting: u32, height: f32) -> u32 { - if setting <= 1 { - return 1; +// Pixel look: the world is rendered `rows` rows high on every screen, whatever its resolution +// and pixel density; the width follows the aspect ratio. Returns the target size and the scale +// to the screen (None: the screen has hardly more rows, render at full resolution). +pub fn pixel_target(rows: u32, pw: f32, ph: f32) -> Option<(u32, u32, f32)> { + if rows == 0 || ph < rows as f32 * 1.2 { + return None; } - ((setting as f32 * height / RENDER_REF_HEIGHT).round() as u32).max(1) + let scale = ph / rows as f32; + Some((((pw / scale).round() as u32).max(1), rows, scale)) } fn rgba(u: u32) -> Color { @@ -1182,7 +1186,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 { impl R3d { pub fn new() -> R3d { - R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, render_div: 1, target: None, lava: Vec::new() } + R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: 360, up: None, target: None, lava: Vec::new() } } // Changing the wall detail rebuilds all chunks @@ -1422,13 +1426,13 @@ impl R3d { let m = cam.matrix(); self.update_chunks(g, gfx, &m); EYE.with(|e| e.set(cam.eye_dir())); - // pixel look: render the world at 1/div resolution and scale it up by exactly div + // pixel look: the world is rendered at a fixed number of rows and scaled up to the screen let d = crate::ui::dpr(); let (pw, ph) = ((screen_width() * d).round(), (screen_height() * d).round()); - let div = effective_div(self.render_div, ph); + let pix = pixel_target(self.pixel_rows, pw, ph); + let div = if pix.is_some() { 2 } else { 1 }; // > 1: a target is used let mut cam3 = cam.camera(); - if div > 1 { - let (tw, th) = ((pw / div as f32).ceil() as u32, (ph / div as f32).ceil() as u32); + if let Some((tw, th, _)) = pix { if self.target.as_ref().map_or(true, |(_, w, h)| *w != tw || *h != th) { let rt = render_target_ex(tw, th, RenderTargetParams { sample_count: 1, depth: true }); rt.texture.set_filter(FilterMode::Nearest); @@ -1439,6 +1443,7 @@ impl R3d { cam3.aspect = Some(*tw as f32 / *th as f32); } else { self.target = None; + self.up = None; } set_camera(&cam3); if div > 1 { @@ -1523,9 +1528,31 @@ impl R3d { draw_parts_glow(g); gl_use_default_material(); crate::ui::begin_ui(); - if let (true, Some((rt, tw, th))) = (div > 1, self.target.as_ref()) { - let size = vec2((*tw * div) as f32, (*th * div) as f32); - draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(size), flip_y: true, ..Default::default() }); + if let (Some((_, _, scale)), Some((rt, tw, th))) = (pix, self.target.as_ref()) { + let whole = (scale - scale.round()).abs() < 0.01; + if whole { + // a whole factor: every world pixel becomes the same square of screen pixels + let size = vec2(*tw as f32 * scale.round(), *th as f32 * scale.round()); + draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(size), flip_y: true, ..Default::default() }); + } else { + // sharp scaling: up by the next whole factor (nearest), then smoothly down to the + // screen - all pixels the same size, the edges crisp + let n = scale.ceil() as u32; + let (uw, uh) = (*tw * n, *th * n); + if self.up.as_ref().map_or(true, |(_, w, h)| *w != uw || *h != uh) { + let up = render_target_ex(uw, uh, RenderTargetParams { sample_count: 1, depth: false }); + up.texture.set_filter(FilterMode::Linear); + self.up = Some((up, uw, uh)); + } + let (up, _, _) = self.up.as_ref().unwrap(); + let mut c = Camera2D::from_display_rect(Rect::new(0.0, 0.0, uw as f32, uh as f32)); + c.render_target = Some(up.clone()); + set_camera(&c); + clear_background(Color::from_rgba(0, 0, 0, 255)); + draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(uw as f32, uh as f32)), flip_y: true, ..Default::default() }); + crate::ui::begin_ui(); + draw_texture_ex(&up.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(*tw as f32 * scale, *th as f32 * scale)), flip_y: true, ..Default::default() }); + } } // ---- 2D overlay in device pixels: health bars, sleep marks, levels, gate HP @@ -3228,12 +3255,14 @@ mod tests { #[test] fn pixel_size_follows_the_window() { - assert_eq!(effective_div(1, 400.0), 1); - assert_eq!(effective_div(4, 1080.0), 4); - assert_eq!(effective_div(4, 540.0), 2); - assert_eq!(effective_div(4, 2160.0), 8); - assert_eq!(effective_div(16, 200.0), 3); - assert_eq!(effective_div(2, 100.0), 1); + // the same rows on every screen; the width follows the aspect ratio + assert_eq!(pixel_target(360, 1920.0, 1080.0), Some((640, 360, 3.0))); + assert_eq!(pixel_target(360, 3840.0, 2160.0), Some((640, 360, 6.0))); + let (w, h, s) = pixel_target(360, 1366.0, 768.0).unwrap(); + assert_eq!((w, h), (640, 360)); + assert!((s - 2.1333).abs() < 0.01); + // a screen hardly bigger than the pixel look renders at full resolution + assert_eq!(pixel_target(360, 600.0, 400.0), None); } #[test] diff --git a/src/settings.rs b/src/settings.rs index e43f48d..3f45d6f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -148,7 +148,8 @@ pub struct Settings { pub wall_detail: u8, // 1 low, 2 medium, 3 high pub max_lights: u8, // 8, 16 or 24 pub ui_factor: u8, // interface size: 0 = automatic, 1..4 = fixed integer factor - pub render_scale: u8, // 3D world resolution divisor: 1 = full, 2..16 = pixel look + pub render_scale: u8, // old pixel-look divisor (no longer used, kept for old settings files) + pub pixel_rows: u16, // pixel look: rows of the world on every screen (270, 360, 480) pub touch_ui: u8, // touch interface: 0 = automatic (after the first touch), 1 = on, 2 = off pub debug_fps: bool, // show FPS and rendering backend pub debug_frame_time: bool, // show the frame-time line and graph @@ -186,6 +187,7 @@ impl Default for Settings { max_lights: 24, ui_factor: 0, render_scale: 1, + pixel_rows: 360, touch_ui: 0, debug_fps: false, debug_frame_time: false,