diff --git a/Cargo.lock b/Cargo.lock index fda7441..518c396 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,8 +509,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", "windows-targets 0.48.5", ] @@ -1247,12 +1249,15 @@ dependencies = [ name = "gb-emu" version = "0.4.0" dependencies = [ + "bytemuck", + "chrono", "clap", "cpal", "ctrlc", "futures", "gb-emu-lib", "gilrs", + "image", "nokhwa", "raw-window-handle", "send_wrapper", diff --git a/gb-emu/Cargo.toml b/gb-emu/Cargo.toml index 1fa34f6..97d6aad 100644 --- a/gb-emu/Cargo.toml +++ b/gb-emu/Cargo.toml @@ -31,3 +31,6 @@ winit = "0.28" winit_input_helper = "0.14" raw-window-handle = { version = "0.5", optional = true } serde = { version = "1.0", features = ["derive"] } +image = { version = "0.24", default-features = false, features = ["png"] } +bytemuck = "1.14" +chrono = "0.4" diff --git a/gb-emu/src/main.rs b/gb-emu/src/main.rs index acbc324..c48290f 100644 --- a/gb-emu/src/main.rs +++ b/gb-emu/src/main.rs @@ -16,7 +16,7 @@ use gilrs::Gilrs; use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, - sync::mpsc::channel, + sync::{mpsc::channel, OnceLock}, time::{Duration, Instant}, }; use window::{WindowManager, WindowRenderer}; @@ -81,14 +81,32 @@ struct Args { #[serde(default)] pub struct StandaloneConfig { scale_factor: usize, + group_screenshots_by_rom: bool, } impl Default for StandaloneConfig { fn default() -> Self { - Self { scale_factor: 3 } + Self { + scale_factor: 3, + group_screenshots_by_rom: true, + } } } +#[allow(dead_code)] +struct Configs { + standalone_config: StandaloneConfig, + emu_config: gb_emu_lib::config::Config, + config_dir: PathBuf, + rom_title: String, +} + +static CONFIGS: OnceLock = OnceLock::new(); + +fn access_config<'a>() -> &'a Configs { + CONFIGS.get().expect("accessed config before it was set!") +} + fn main() { let args = Args::parse(); @@ -168,6 +186,13 @@ fn run(args: Args) -> ! { None }; + CONFIGS.get_or_init(|| Configs { + standalone_config, + emu_config: config.clone(), + config_dir: config_manager.dir(), + rom_title: rom.get_title().to_owned(), + }); + let options = EmulatorOptions::new_with_config(config, config_manager.dir(), window, rom, output) .with_serial_target(if args.ascii { diff --git a/gb-emu/src/window.rs b/gb-emu/src/window.rs index a127e06..0b105ec 100644 --- a/gb-emu/src/window.rs +++ b/gb-emu/src/window.rs @@ -13,6 +13,7 @@ use gilrs::{ ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks}, Button, Gilrs, }; +use image::ImageBuffer; #[cfg(feature = "vulkan")] use raw_window_handle::HasRawDisplayHandle; use winit::{ @@ -24,6 +25,8 @@ use winit::{ }; use winit_input_helper::WinitInputHelper; +use crate::access_config; + pub struct WindowInfo { id: WindowId, data: Arc, @@ -33,6 +36,7 @@ struct WindowData { renderer: Mutex, window: Window, rendered_size: RwLock<(u32, u32)>, + last_buf: Mutex>, } pub struct WindowManager { @@ -129,7 +133,44 @@ impl WindowManagerData { control_flow.set_wait(); if let Ok(mut i) = self.input.lock() { - i.update(&event); + if i.update(&event) && i.key_pressed(VirtualKeyCode::Space) { + self.windows.iter().for_each(|(_id, window)| { + if let Ok(buf) = window.last_buf.lock() { + if let Ok(size) = window.rendered_size.read() { + let image = ImageBuffer::, _>::from_raw( + size.0, + size.1, + bytemuck::cast_slice(buf.as_ref()), + ) + .unwrap(); + let configs = access_config(); + let screenshot_dir = + if configs.standalone_config.group_screenshots_by_rom { + configs + .config_dir + .join(format!("screenshots/{}", configs.rom_title)) + } else { + configs.config_dir.clone() + }; + + std::fs::create_dir_all(&screenshot_dir) + .expect("could not create screenshot directory!"); + + let screenshot_path = screenshot_dir.join(format!( + "{} - {}.png", + chrono::DateTime::::from( + std::time::SystemTime::now() + ) + .to_rfc3339(), + configs.rom_title, + )); + image + .save(screenshot_path) + .expect("Could not save screenshot!"); + } + } + }); + } } match event { @@ -218,6 +259,7 @@ impl WindowRenderer { renderer, window, rendered_size: RwLock::new((1, 1)), + last_buf: Mutex::new(Vec::new()), }); let info = WindowInfo { @@ -277,6 +319,10 @@ impl Renderer<[u8; 4]> for WindowRenderer { data.new_frame(buffer); } self.data.window.request_redraw(); + if let Ok(mut last_buf) = self.data.last_buf.lock() { + last_buf.resize(buffer.len(), [0; 4]); + last_buf.copy_from_slice(buffer); + } } fn set_title(&mut self, title: String) {