screen recording

This commit is contained in:
Alex Janka 2023-10-12 10:35:36 +11:00
parent 5e8b3e612c
commit d769f90336
2 changed files with 69 additions and 14 deletions

View file

@ -75,6 +75,9 @@ struct Args {
// /// Use webcam as Pocket Camera emulation // /// Use webcam as Pocket Camera emulation
// #[arg(short, long)] // #[arg(short, long)]
// camera: bool, // camera: bool,
/// Record frames to image sequence
#[arg(long)]
record: bool,
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -128,8 +131,6 @@ fn run(args: Args) -> ! {
let config = config_manager.load_or_create_base_config(); let config = config_manager.load_or_create_base_config();
let standalone_config: StandaloneConfig = config_manager.load_or_create_config("standalone"); let standalone_config: StandaloneConfig = config_manager.load_or_create_config("standalone");
let (output, stream) = audio::create_output(args.mute);
let rom_file = RomFile::Path(PathBuf::from(args.rom)); let rom_file = RomFile::Path(PathBuf::from(args.rom));
let (rom, camera) = rom_file let (rom, camera) = rom_file
@ -139,6 +140,15 @@ fn run(args: Args) -> ! {
) )
.expect("Error parsing rom"); .expect("Error parsing rom");
let configs = CONFIGS.get_or_init(|| Configs {
standalone_config,
emu_config: config.clone(),
config_dir: config_manager.dir(),
rom_title: rom.get_title().to_owned(),
});
let (output, stream) = audio::create_output(args.mute);
let will_be_cgb = rom.rom_type == CgbRomType::CgbOnly || config.prefer_cgb; let will_be_cgb = rom.rom_type == CgbRomType::CgbOnly || config.prefer_cgb;
let shader_path = if will_be_cgb { let shader_path = if will_be_cgb {
@ -163,9 +173,9 @@ fn run(args: Args) -> ! {
gb_emu_lib::config::ResolutionOverride::Scale(scale) => Some(scale), gb_emu_lib::config::ResolutionOverride::Scale(scale) => Some(scale),
gb_emu_lib::config::ResolutionOverride::Default => None, gb_emu_lib::config::ResolutionOverride::Default => None,
} }
.unwrap_or(standalone_config.scale_factor); .unwrap_or(configs.standalone_config.scale_factor);
let mut window_manager = WindowManager::new(sender, stream); let mut window_manager = WindowManager::new(sender, stream, args.record);
let window = window_manager.add_main( let window = window_manager.add_main(
scale_override, scale_override,
@ -175,24 +185,22 @@ fn run(args: Args) -> ! {
); );
let tile_window: Option<WindowRenderer> = if args.tile_window { let tile_window: Option<WindowRenderer> = if args.tile_window {
Some(window_manager.add(standalone_config.scale_factor, None, None, false)) Some(window_manager.add(configs.standalone_config.scale_factor, None, None, false))
} else { } else {
None None
}; };
let layer_window: Option<WindowRenderer> = if args.layer_window { let layer_window: Option<WindowRenderer> = if args.layer_window {
Some(window_manager.add(standalone_config.scale_factor.min(2), None, None, false)) Some(window_manager.add(
configs.standalone_config.scale_factor.min(2),
None,
None,
false,
))
} else { } else {
None 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 = let options =
EmulatorOptions::new_with_config(config, config_manager.dir(), window, rom, output) EmulatorOptions::new_with_config(config, config_manager.dir(), window, rom, output)
.with_serial_target(if args.ascii { .with_serial_target(if args.ascii {

View file

@ -42,6 +42,7 @@ struct WindowData {
pub struct WindowManager { pub struct WindowManager {
event_loop: EventLoop<()>, event_loop: EventLoop<()>,
data: WindowManagerData, data: WindowManagerData,
record_main: bool,
} }
struct WindowManagerData { struct WindowManagerData {
@ -54,7 +55,7 @@ struct WindowManagerData {
} }
impl WindowManager { impl WindowManager {
pub(crate) fn new(sender: Sender<EmulatorMessage>, _stream: Stream) -> Self { pub(crate) fn new(sender: Sender<EmulatorMessage>, _stream: Stream, record_main: bool) -> Self {
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
#[cfg(feature = "vulkan")] #[cfg(feature = "vulkan")]
let window_data_manager = let window_data_manager =
@ -69,8 +70,10 @@ impl WindowManager {
window_data_manager, window_data_manager,
input: Arc::new(Mutex::new(WinitInputHelper::new())), input: Arc::new(Mutex::new(WinitInputHelper::new())),
sender, sender,
_stream, _stream,
}, },
record_main,
} }
} }
@ -88,6 +91,7 @@ impl WindowManager {
resizable, resizable,
&self.event_loop, &self.event_loop,
true, true,
self.record_main,
) )
} }
@ -105,6 +109,7 @@ impl WindowManager {
resizable, resizable,
&self.event_loop, &self.event_loop,
false, false,
false,
) )
} }
@ -122,6 +127,7 @@ impl WindowManager {
} }
impl WindowManagerData { impl WindowManagerData {
#[allow(clippy::too_many_arguments)]
fn add( fn add(
&mut self, &mut self,
factor: usize, factor: usize,
@ -130,6 +136,7 @@ impl WindowManagerData {
resizable: bool, resizable: bool,
event_loop: &EventLoop<()>, event_loop: &EventLoop<()>,
is_main: bool, is_main: bool,
record: bool,
) -> WindowRenderer { ) -> WindowRenderer {
let (r, info) = WindowRenderer::new( let (r, info) = WindowRenderer::new(
factor, factor,
@ -139,6 +146,7 @@ impl WindowManagerData {
self.window_data_manager.clone(), self.window_data_manager.clone(),
shader_path, shader_path,
resizable, resizable,
record,
); );
self.windows.insert(info.id, info.data); self.windows.insert(info.id, info.data);
if is_main { if is_main {
@ -256,9 +264,16 @@ pub struct WindowRenderer {
gamepad_handler: Option<Gilrs>, gamepad_handler: Option<Gilrs>,
joypad_state: JoypadState, joypad_state: JoypadState,
current_rumble: bool, current_rumble: bool,
recording: Option<RecordInfo>,
}
struct RecordInfo {
dir: PathBuf,
frame_num: usize,
} }
impl WindowRenderer { impl WindowRenderer {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
factor: usize, factor: usize,
gamepad_handler: Option<Gilrs>, gamepad_handler: Option<Gilrs>,
@ -267,6 +282,7 @@ impl WindowRenderer {
manager: Arc<RendererBackendManager>, manager: Arc<RendererBackendManager>,
shader_path: Option<PathBuf>, shader_path: Option<PathBuf>,
resizable: bool, resizable: bool,
record: bool,
) -> (Self, WindowInfo) { ) -> (Self, WindowInfo) {
let window = WindowBuilder::new() let window = WindowBuilder::new()
.with_title("Gameboy") .with_title("Gameboy")
@ -303,6 +319,22 @@ impl WindowRenderer {
data: data.clone(), data: data.clone(),
}; };
let recording = if record {
let configs = access_config();
let dir = configs.config_dir.join(format!(
"recordings/{} - {}",
chrono::DateTime::<chrono::Local>::from(std::time::SystemTime::now()).to_rfc3339(),
configs.rom_title,
));
std::fs::create_dir_all(&dir).expect("could not create screenshot directory!");
Some(RecordInfo { dir, frame_num: 0 })
} else {
None
};
( (
Self { Self {
data, data,
@ -313,6 +345,7 @@ impl WindowRenderer {
gamepad_handler, gamepad_handler,
joypad_state: JoypadState::default(), joypad_state: JoypadState::default(),
current_rumble: false, current_rumble: false,
recording,
}, },
info, info,
) )
@ -359,6 +392,20 @@ impl Renderer<[u8; 4]> for WindowRenderer {
last_buf.resize(buffer.len(), [0; 4]); last_buf.resize(buffer.len(), [0; 4]);
last_buf.copy_from_slice(buffer); last_buf.copy_from_slice(buffer);
} }
if let Some(ref mut info) = self.recording {
let image = ImageBuffer::<image::Rgba<u8>, _>::from_raw(
self.width as u32,
self.height as u32,
bytemuck::cast_slice(buffer),
)
.unwrap();
let frame_path = info.dir.join(format!("{:0>5}.png", info.frame_num));
image.save(frame_path).expect("Could not save frame!");
info.frame_num += 1;
}
} }
fn set_title(&mut self, title: String) { fn set_title(&mut self, title: String) {