screen recording
This commit is contained in:
parent
5e8b3e612c
commit
d769f90336
2 changed files with 69 additions and 14 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue