diff options
-rw-r--r-- | CHANGELOG.md | 16 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | source/benoit/benoit.rs | 8 | ||||
-rw-r--r-- | source/benoit/benoit/app.rs | 6 | ||||
-rw-r--r-- | source/benoit/benoit/app/animate.rs | 16 | ||||
-rw-r--r-- | source/benoit/benoit/app/colour_row.rs | 6 | ||||
-rw-r--r-- | source/benoit/benoit/app/dump.rs | 35 | ||||
-rw-r--r-- | source/benoit/benoit/app/handle_keys.rs | 44 | ||||
-rw-r--r-- | source/benoit/benoit/app/image_filename.rs | 36 | ||||
-rw-r--r-- | source/benoit/benoit/app/initialise.rs | 3 | ||||
-rw-r--r-- | source/benoit/benoit/app/loop.rs | 21 | ||||
-rw-r--r-- | source/benoit/benoit/app/render_row_julia.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/app/render_row_normal.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/configuration.rs | 4 | ||||
-rw-r--r-- | source/benoit/benoit/configuration/default.rs | 7 | ||||
-rw-r--r-- | source/benoit/benoit/configuration/load.rs | 54 | ||||
-rw-r--r-- | source/benoit/benoit/video/initialise.rs | 9 |
18 files changed, 237 insertions, 75 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 26434ff..292c4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# 1.2.0 + +* Bump minor version +* Update logging +* Support PNG encoding (set using configuration, depend on png) +* Make window borderless +* Update readme +* Don't set scale from configuration +* Update feedback +* Support setting dump path from configuration +* Update controls +* Colour according to new maximum iteration count if less than previous +* Update default colour range +* Modulise code +* Also dump colour range + # 1.1.0 * Bump minor version @@ -1,6 +1,6 @@ [package] name = "benoit" -version = "1.1.0" +version = "1.2.0" authors = ["Gabriel Bjørnager Jensen"] edition = "2021" description = "Mandelbrot renderer." @@ -16,6 +16,7 @@ codegen-units = 1 lto = "fat" [dependencies] +png = "0.17.10" rayon = "1.7.0" rug = "1.22.0" sdl2 = "0.35.2" @@ -1,6 +1,32 @@ -# Benoit +# BENOÎT -[*Benoit*](https://mandelbrot.dk/benoit) is a free and open-source Mandelbrot renderer written in Rust. It is aimed at producing accurate renders at arbitrary positions in the set as fast as possible. +[*Benoit*](https://mandelbrot.dk/benoit) is a free and open‐source Mandelbrot renderer written in Rust. It is aimed at producing accurate renders at arbitrary positions in the set as fast as possible. Usage: + +``` +benoit [path] +``` + +… where *path* denotes the configuration file to read (optional). If no path is provided, the program is run in *interactive* mode, wherein the fractal is rendered in real‐time. + +# Dependencies + +Benoit makes use of the following external libraries: + +* [PNG](https://crates.io/crates/png) for encoding PNG images +* [Rayon](https://crates.io/crates/rayon) for threadpooling +* [Rug](https://crates.io/crates/rug) for multi‐precision +* [SDL2](https://crates.io/crates/sdl2) for interactive viewports +* [TOML](https://crates.io/crates/toml) for parsing TOML files +* [WebP](https://crates.io/crates/webp) for encoding WebP images + +# Mirrors + +Benoit is officially hosted on the following mirrors: + +* [mandelbrot.dk](https://mandelbrot.dk/benoit) +--- +* [mandelbrot.dk](https://mandelbrot.dk/benoit) +* [mandelbrot.dk](https://mandelbrot.dk/benoit) # Copyright & License diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs index c0ca227..da40689 100644 --- a/source/benoit/benoit.rs +++ b/source/benoit/benoit.rs @@ -40,12 +40,18 @@ pub struct Version<T> { pub const VERSION: Version::<u32> = Version::<u32> { major: 0x1, - minor: 0x1, + minor: 0x2, patch: 0x0, }; pub const PRECISION: u32 = 0x80; +#[derive(Clone, Copy)] +pub enum ImageFormat { + Png, + Webp, +} + pub struct FeedbackInfo<'a> { prev_centre_real: &'a Float, prev_centre_imag: &'a Float, diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs index f7ebb47..255461e 100644 --- a/source/benoit/benoit/app.rs +++ b/source/benoit/benoit/app.rs @@ -21,6 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::ImageFormat; use crate::benoit::fractal::Fractal; use crate::benoit::iteration::IteratorFunction; use crate::benoit::task::render_data::RenderData; @@ -39,6 +40,7 @@ pub mod dump; pub mod get_iterator_function; pub mod get_row_renderer; pub mod handle_keys; +pub mod image_filename; pub mod initialise; pub mod r#loop; pub mod poll_events; @@ -50,6 +52,7 @@ pub mod run; pub type RowRenderer = fn(Arc<RenderData>, u32, IteratorFunction); pub struct App { + #[allow(dead_code)] thread_count: u32, fractal: Fractal, @@ -66,7 +69,8 @@ pub struct App { colour_range: f32, - dump_path: String, + dump_path: String, + image_format: ImageFormat, video: Option<Video>, diff --git a/source/benoit/benoit/app/animate.rs b/source/benoit/benoit/app/animate.rs index f732e61..3de4a76 100644 --- a/source/benoit/benoit/app/animate.rs +++ b/source/benoit/benoit/app/animate.rs @@ -51,12 +51,16 @@ impl App { self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); let render_time = time_start.elapsed(); + eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); + self.colour(&mut image[..], self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); let colour_time = time_start.elapsed() - render_time; + eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); + self.dump(format!("{}/render.webp", self.dump_path), &image, self.canvas_width); - eprintln!(" rend. {:.3}ms, col. {:.3}ms", render_time.as_micros() as f32 / 1000.0, colour_time.as_micros() as f32 / 1000.0); + eprintln!(" done"); return 0x0; } @@ -98,16 +102,24 @@ impl App { eprintln!("animating {} frames at {}{:+}i to {:.3} (fac. {:.3})", self.frame_count, self.centre_real.to_f64(), self.centre_imag.to_f64(), zoom_stop.to_f64(), zoom_factor.to_f64()); for frame in 0x0..self.frame_count { - eprint!("{frame:010} ({:.3}x)...", zoom.to_f32()); + eprint!("{frame:010} (2^{:.9}x)...", zoom.to_f64().log2()); let time_start = Instant::now(); self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &zoom, self.max_iter_count); let render_time = time_start.elapsed(); + eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); + self.colour(&mut image[..], self.max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); let colour_time = time_start.elapsed() - render_time; + eprint!(" {:.3}ms...", colour_time.as_micros() as f32 / 1000.0); + + self.dump(format!("{}/render.webp", self.dump_path), &image, self.canvas_width); + + eprintln!(" done"); + self.dump(format!("{}/frame{frame:010}.webp", self.dump_path), &image, self.canvas_width); eprintln!(" rend. {:.3}ms, col. {:.3}ms", render_time.as_micros() as f32 / 1000.0, colour_time.as_micros() as f32 / 1000.0); diff --git a/source/benoit/benoit/app/colour_row.rs b/source/benoit/benoit/app/colour_row.rs index 81896cc..0348499 100644 --- a/source/benoit/benoit/app/colour_row.rs +++ b/source/benoit/benoit/app/colour_row.rs @@ -34,12 +34,12 @@ impl App { for x in 0x0..data.canvas_width { let x = x as usize; - let iter_count = iter_count_buffer[ x]; - let distance = square_dist_buffer[x].sqrt(); + let iter_count = unsafe { *iter_count_buffer.get_unchecked( x) }; + let distance = unsafe { *square_dist_buffer.get_unchecked(x) }.sqrt(); let factor = (iter_count as f32 + 1.0 - distance.log2().log2()) / data.colour_range % 1.0; - let (red, green, blue) = if iter_count != data.max_iter_count { + let (red, green, blue) = if iter_count < data.max_iter_count { hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0) } else { (0.0, 0.0, 0.0) diff --git a/source/benoit/benoit/app/dump.rs b/source/benoit/benoit/app/dump.rs index 6001e31..094650a 100644 --- a/source/benoit/benoit/app/dump.rs +++ b/source/benoit/benoit/app/dump.rs @@ -21,19 +21,42 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::ImageFormat; use crate::benoit::app::App; +extern crate png; extern crate webp; -use std::fs::write; -use webp::Encoder; +use std::fs::{File, write}; +use std::io::BufWriter; impl App { pub fn dump(&self, path: String, image: &[u8], canvas_width: u32) { - let encoder = Encoder::from_rgb(&image[..], canvas_width, canvas_width); + match self.image_format { + ImageFormat::Png => dump_png( &path, image, canvas_width), + ImageFormat::Webp => dump_webp(&path, image, canvas_width), + } + } +} - let data = encoder.encode_lossless(); +fn dump_png(path: &String, image: &[u8], canvas_width: u32) { + let file = File::create(path).expect("unable to create file"); + let file_buffer = BufWriter::new(file); - write(path, &*data).expect("unable to write image"); - } + let mut encoder = png::Encoder::new(file_buffer, canvas_width, canvas_width); + encoder.set_color(png::ColorType::Rgb); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_compression(png::Compression::Fast); + encoder.set_srgb(png::SrgbRenderingIntent::Perceptual); + + let mut writer = encoder.write_header().expect("unable to write image"); + writer.write_image_data(image).expect("unable to write image"); +} + +fn dump_webp(path: &String, image: &[u8], canvas_width: u32) { + let encoder = webp::Encoder::from_rgb(&image[..], canvas_width, canvas_width); + + let data = encoder.encode_lossless(); + + write(path, &*data).expect("unable to write image"); } diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs index 2b2cff5..50956f8 100644 --- a/source/benoit/benoit/app/handle_keys.rs +++ b/source/benoit/benoit/app/handle_keys.rs @@ -40,13 +40,35 @@ impl App { Scancode::C => self.do_render = true, Scancode::Tab => (self.julia, self.row_renderer) = toggle_julia(self.julia), Scancode::X => self.do_dump = true, - Scancode::Z => eprintln!("c = {}{:+}i -- {}x @ {} iter.", &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count), + Scancode::Z => eprintln!("c = {}{:+}i -- {}x @ {} iter. (range: {:.3})", &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count, self.colour_range), _ => {}, } + self.handle_translation(scan_code); + + self.max_iter_count = match scan_code { + Scancode::F => self.max_iter_count * 0x2, + Scancode::R => self.max_iter_count / 0x2, + _ => self.max_iter_count, + }; + + const COLOUR_RANGE_FACTOR: f32 = 1.0 + 1.0 / 16.0; + + self.colour_range = match scan_code { + Scancode::Up => self.colour_range * COLOUR_RANGE_FACTOR, + Scancode::Down => self.colour_range / COLOUR_RANGE_FACTOR, + _ => self.colour_range, + }; + + return false; + } + + fn handle_translation(&mut self, scan_code: Scancode) { + const ZOOM_FACTOR: f32 = 1.0 + 1.0 / 4.0; + match scan_code { - Scancode::E => self.zoom *= 2.0, - Scancode::Q => self.zoom /= 2.0, + Scancode::E => self.zoom *= ZOOM_FACTOR, + Scancode::Q => self.zoom /= ZOOM_FACTOR, _ => {}, }; @@ -69,22 +91,6 @@ impl App { Scancode::W => self.centre_imag += &translate_ammount, _ => {}, }; - - self.max_iter_count = match scan_code { - Scancode::F => self.max_iter_count * 0x2, - Scancode::R => self.max_iter_count / 0x2, - _ => self.max_iter_count, - }; - - const COLOUR_RANGE_FACTOR: f32 = 1.0 + 1.0 / 16.0; - - self.colour_range = match scan_code { - Scancode::Up => self.colour_range * COLOUR_RANGE_FACTOR, - Scancode::Down => self.colour_range / COLOUR_RANGE_FACTOR, - _ => self.colour_range, - }; - - return false; } } diff --git a/source/benoit/benoit/app/image_filename.rs b/source/benoit/benoit/app/image_filename.rs new file mode 100644 index 0000000..9ab111a --- /dev/null +++ b/source/benoit/benoit/app/image_filename.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021, 2023 Gabriel Bjørnager Jensen. + + This file is part of Benoit. + + Benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU + Affero General Public License as published by + the Free Software Foundation, either version 3 + of the License, or (at your option) any later + version. + + Benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU + Affero General Public License along with Benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::benoit::ImageFormat; +use crate::benoit::app::App; + +impl App { + pub fn image_filename(name: &str, image_format: ImageFormat) -> String { + let file_extension = match image_format { + ImageFormat::Png => ".png", + ImageFormat::Webp => ".webp", + }; + + return name.to_owned() + file_extension; + } +} diff --git a/source/benoit/benoit/app/initialise.rs b/source/benoit/benoit/app/initialise.rs index 15d98ac..a357940 100644 --- a/source/benoit/benoit/app/initialise.rs +++ b/source/benoit/benoit/app/initialise.rs @@ -77,7 +77,8 @@ impl App { colour_range: configuration.colour_range, - dump_path: configuration.dump_path, + dump_path: configuration.dump_path, + image_format: configuration.image_format, video: video, diff --git a/source/benoit/benoit/app/loop.rs b/source/benoit/benoit/app/loop.rs index 9d55431..69eb3d4 100644 --- a/source/benoit/benoit/app/loop.rs +++ b/source/benoit/benoit/app/loop.rs @@ -84,7 +84,7 @@ impl App { self.render(&mut iter_count_buffer[..], &mut square_dist_buffer[..], &self.centre_real, &self.centre_imag, &self.zoom, self.max_iter_count); let render_time = time_start.elapsed(); - eprintln!(" rend. {:.3}ms", render_time.as_micros() as f32 / 1000.0); + eprintln!(" {:.3}ms", render_time.as_micros() as f32 / 1000.0); prev_centre_real.assign(&self.centre_real); prev_centre_imag.assign(&self.centre_imag); @@ -94,7 +94,7 @@ impl App { self.do_render = false; } - self.colour(&mut image[..], prev_max_iter_count, &mut iter_count_buffer[..], &mut square_dist_buffer[..]); + self.colour(&mut image[..], prev_max_iter_count.min(self.max_iter_count), &mut iter_count_buffer[..], &mut square_dist_buffer[..]); { let feedback_info = FeedbackInfo { @@ -106,16 +106,25 @@ impl App { next_zoom: &self.zoom, }; - let feedback_info = match self.julia { - false => Some(&feedback_info), - true => None, + let feedback_info = if { + // Don't draw feedback if rendering a Julia set or + // if we haven't done any viewport translations. + + !self.julia + && (self.centre_real != prev_centre_real + || self.centre_imag != prev_centre_imag + || self.zoom != prev_zoom) + } { + Some(&feedback_info) + } else { + None }; unsafe { self.video.as_mut().unwrap_unchecked().draw(&image[..], self.canvas_width, self.scale, feedback_info) }; } if self.do_dump { - let path = format!("{}/image.webp", self.dump_path); + let path = App::image_filename(format!("{}/image", self.dump_path).as_str(), self.image_format); eprintln!("dumping image at \"{path}\""); self.dump(path, &image, self.canvas_width); diff --git a/source/benoit/benoit/app/render_row_julia.rs b/source/benoit/benoit/app/render_row_julia.rs index 5c3cae8..2710423 100644 --- a/source/benoit/benoit/app/render_row_julia.rs +++ b/source/benoit/benoit/app/render_row_julia.rs @@ -40,9 +40,6 @@ impl App { for x in 0x0..data.canvas_width { let canvas_width = Float::with_val(PRECISION, data.canvas_width); - let x_float = Float::with_val(PRECISION, x); - let y_float = Float::with_val(PRECISION, y); - // For more information, see render_row_normal. let ca = &data.centre_real; @@ -56,7 +53,7 @@ impl App { let mut za = { let mut za = Float::with_val(PRECISION, &canvas_width / 2.0); za.neg_assign(); - za += &x_float; + za += x; za *= 4.0; za /= &canvas_width; @@ -66,7 +63,7 @@ impl App { let mut zb = { let mut zb = Float::with_val(PRECISION, &canvas_width / 2.0); zb.neg_assign(); - zb += &y_float; + zb += y; zb *= 4.0; zb /= &canvas_width; diff --git a/source/benoit/benoit/app/render_row_normal.rs b/source/benoit/benoit/app/render_row_normal.rs index 381b8bc..832d178 100644 --- a/source/benoit/benoit/app/render_row_normal.rs +++ b/source/benoit/benoit/app/render_row_normal.rs @@ -40,13 +40,10 @@ impl App { for x in 0x0..data.canvas_width { let canvas_width = Float::with_val(PRECISION, data.canvas_width); - let x_float = Float::with_val(PRECISION, x); - let y_float = Float::with_val(PRECISION, y); - let ca = { let mut ca = Float::with_val(PRECISION, &canvas_width / 2.0); ca.neg_assign(); - ca += &x_float; + ca += x; ca *= 4.0; ca /= &canvas_width; ca /= &data.zoom; @@ -58,7 +55,7 @@ impl App { let cb = { let mut cb = Float::with_val(PRECISION, &canvas_width / 2.0); cb.neg_assign(); - cb += &y_float; + cb += y; cb *= 4.0; cb /= &canvas_width; cb /= &data.zoom; diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs index f472f62..6e22f01 100644 --- a/source/benoit/benoit/configuration.rs +++ b/source/benoit/benoit/configuration.rs @@ -21,6 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ +use crate::benoit::ImageFormat; use crate::benoit::fractal::Fractal; extern crate rug; @@ -47,7 +48,8 @@ pub struct Configuration { pub colour_range: f32, - pub dump_path: String, + pub dump_path: String, + pub image_format: ImageFormat, pub interactive: bool, } diff --git a/source/benoit/benoit/configuration/default.rs b/source/benoit/benoit/configuration/default.rs index 46373cd..2a0ae9f 100644 --- a/source/benoit/benoit/configuration/default.rs +++ b/source/benoit/benoit/configuration/default.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{ImageFormat, PRECISION}; use crate::benoit::configuration::Configuration; use crate::benoit::fractal::Fractal; @@ -46,9 +46,10 @@ impl Configuration { zoom: Float::with_val(PRECISION, 1.0), max_iter_count: 0x100, - colour_range: 16.0, + colour_range: 64.0, - dump_path: "./render/".to_string(), + dump_path: "./render/".to_string(), + image_format: ImageFormat::Png, interactive: true, }; diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs index 8373c22..cd4b812 100644 --- a/source/benoit/benoit/configuration/load.rs +++ b/source/benoit/benoit/configuration/load.rs @@ -21,7 +21,7 @@ If not, see <https://www.gnu.org/licenses/>. */ -use crate::benoit::PRECISION; +use crate::benoit::{ImageFormat, PRECISION}; use crate::benoit::configuration::Configuration; use crate::benoit::fractal::Fractal; @@ -100,21 +100,18 @@ impl Configuration { get_integer(&mut configuration.thread_count, &configuration_table, "thread_count"); - configuration.fractal = if let Some(name) = get_string(&configuration_table, "fractal") { - match name.as_str() { + if let Some(name) = get_string(&configuration_table, "fractal") { + configuration.fractal = match name.as_str() { "burningship" => Fractal::BurningShip, "mandelbrot" => Fractal::Mandelbrot, "tricorn" => Fractal::Tricorn, - name => panic!("invalid fractal name {name}"), + name => panic!("invalid fractal name \"{name}\""), } - } else { - configuration.fractal - }; + } get_boolean(&mut configuration.julia, &configuration_table, "julia"); get_integer(&mut configuration.canvas_width, &configuration_table, "canvas_width"); - get_integer(&mut configuration.scale, &configuration_table, "scale"); get_integer(&mut configuration.frame_count, &configuration_table, "frame_count"); get_float( &mut configuration.centre_real, &configuration_table, "real"); @@ -124,18 +121,39 @@ impl Configuration { get_float32(&mut configuration.colour_range, &configuration_table, "colour_range"); - // We allow thread counts of zero as those signal - // automatic thread count detection. - if configuration.canvas_width == 0x0 { - panic!("only non-zero values for canvas_width are allowed"); - } else if configuration.scale == 0x0 { - panic!("only non-zero values for scale are allowed"); - } else if configuration.frame_count == 0x0 { - panic!("only non-zero values for frame_count are allowed"); - } else if configuration.max_iter_count == 0x0 { - panic!("only non-zero values for maximum_iteration_count are allowed"); + if let Some(path) = get_string(&configuration_table, "dump_path") { + configuration.dump_path = path.clone(); + } + + if let Some(name) = get_string(&configuration_table, "image_format") { + configuration.image_format = match name.as_str() { + "png" => ImageFormat::Png, + "webp" => ImageFormat::Webp, + name => panic!("invalid image format \"{name}\""), + } + } + + match check_configuration(&configuration) { + Err(message) => panic!("invalid configuration: {message}"), + _ => {}, } return configuration; } } + +fn check_configuration(configuration: &Configuration) -> Result<(), &str> { + // We allow thread counts of zero as those signal + // automatic thread count detection. + if configuration.canvas_width == 0x0 { + return Err("only non-zero values for canvas_width are allowed"); + } else if configuration.scale == 0x0 { + return Err("only non-zero values for scale are allowed"); + } else if configuration.frame_count == 0x0 { + return Err("only non-zero values for frame_count are allowed"); + } else if configuration.max_iter_count == 0x0 { + return Err("only non-zero values for maximum_iteration_count are allowed"); + } + + return Ok(()); +} diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs index f97ae8b..ae633b3 100644 --- a/source/benoit/benoit/video/initialise.rs +++ b/source/benoit/benoit/video/initialise.rs @@ -33,12 +33,19 @@ impl Video { let sdl = sdl2::init().expect("unable to initialise sdl2"); let sdl_video = sdl.video().expect("unable to initialise video"); - let window = sdl_video.window(format!("Benoit {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch).as_str(), canvas_width * scale, canvas_width * scale).position_centered().build().expect("unable to open window"); + let mut window_builder = sdl_video.window(format!("Beno\u{00EE}t {:X}.{:X}.{:X}", VERSION.major, VERSION.minor, VERSION.patch).as_str(), canvas_width * scale, canvas_width * scale); + window_builder.borderless(); + window_builder.position_centered(); + + let window = window_builder.build().expect("unable to open window"); let mut canvas = window.into_canvas().build().expect("unable to create canvas"); canvas.set_blend_mode(BlendMode::Blend); + // We only want to scale the render, not the + // feedback, so we can't use SDL's scaling feature. + return Video { sdl: sdl, sdl_video: sdl_video, |