2021-08-18 23:32:55 +02:00
|
|
|
extern crate ferretro_components;
|
2021-12-11 00:53:02 +01:00
|
|
|
#[cfg(feature = "ffmpeg_comp")]
|
2021-08-17 13:06:40 +02:00
|
|
|
extern crate ffmpeg_next as ffmpeg;
|
|
|
|
extern crate sdl2;
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
2021-12-10 11:33:43 +01:00
|
|
|
use sdl2::video::FullscreenType;
|
2021-08-17 13:06:40 +02:00
|
|
|
use structopt::StructOpt;
|
|
|
|
|
2021-12-11 00:53:02 +01:00
|
|
|
use ferretro_components::base::ControlFlow;
|
2021-08-18 23:32:55 +02:00
|
|
|
use ferretro_components::prelude::*;
|
|
|
|
use ferretro_components::provided::{
|
2021-08-19 06:10:48 +02:00
|
|
|
sdl2::*,
|
2021-11-04 19:23:02 +01:00
|
|
|
stdlib::*,
|
2021-08-17 13:06:40 +02:00
|
|
|
};
|
2021-12-17 11:47:08 +01:00
|
|
|
#[cfg(feature = "cpal_comp")]
|
|
|
|
use ferretro_components::provided::cpal::CpalAudioComponent;
|
2021-12-11 00:53:02 +01:00
|
|
|
#[cfg(feature = "ffmpeg_comp")]
|
|
|
|
use ferretro_components::provided::ffmpeg::FfmpegComponent;
|
2021-08-17 13:06:40 +02:00
|
|
|
|
|
|
|
#[derive(StructOpt)]
|
|
|
|
struct Opt {
|
|
|
|
/// Core module to use.
|
|
|
|
#[structopt(short, long, parse(from_os_str))]
|
|
|
|
core: PathBuf,
|
|
|
|
/// ROM to load using the core.
|
2021-12-10 11:33:43 +01:00
|
|
|
#[structopt(parse(from_os_str))]
|
2021-08-17 13:06:40 +02:00
|
|
|
rom: PathBuf,
|
|
|
|
/// Save state to load at startup.
|
|
|
|
#[structopt(long, parse(from_os_str))]
|
|
|
|
state: Option<PathBuf>,
|
|
|
|
/// System directory, often containing BIOS files
|
|
|
|
#[structopt(short, long, parse(from_os_str))]
|
|
|
|
system: Option<PathBuf>,
|
|
|
|
/// Recorded video to write.
|
2021-12-11 00:53:02 +01:00
|
|
|
#[cfg(feature = "ffmpeg_comp")]
|
|
|
|
#[structopt(long, parse(from_os_str))]
|
|
|
|
record_video: Option<PathBuf>,
|
2021-11-04 23:27:13 +01:00
|
|
|
/// Disable OpenGL context creation.
|
|
|
|
#[structopt(long)]
|
|
|
|
no_opengl: bool,
|
2021-12-10 11:33:43 +01:00
|
|
|
/// Fullscreen (borderless).
|
|
|
|
#[structopt(short, long)]
|
|
|
|
fullscreen: bool,
|
2021-12-10 08:40:19 +01:00
|
|
|
/// Print core-provided system information to stderr.
|
|
|
|
#[structopt(long)]
|
|
|
|
sys_info: bool,
|
|
|
|
/// Trace all API calls to stderr.
|
2021-11-05 00:27:18 +01:00
|
|
|
#[structopt(long)]
|
|
|
|
trace_api: bool,
|
2021-08-17 13:06:40 +02:00
|
|
|
}
|
|
|
|
|
2021-11-10 06:58:51 +01:00
|
|
|
pub fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
2021-08-17 13:06:40 +02:00
|
|
|
let opt: Opt = Opt::from_args();
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-08-17 13:06:40 +02:00
|
|
|
let mut emu = RetroComponentBase::new(&opt.core);
|
2021-08-19 06:10:48 +02:00
|
|
|
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
let mut sdl_context = sdl2::init()?;
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-11-04 02:11:14 +01:00
|
|
|
let mut stderr_log = StderrLogComponent::default();
|
|
|
|
stderr_log.prefix = "{log} ".to_string();
|
|
|
|
emu.register_component(stderr_log)?;
|
2021-08-21 06:47:48 +02:00
|
|
|
|
2021-12-10 08:40:19 +01:00
|
|
|
let mut stderr_msg = StderrMessageComponent::default();
|
|
|
|
stderr_msg.prefix = "{message} ".to_string();
|
|
|
|
emu.register_component(stderr_msg)?;
|
|
|
|
|
2021-11-05 00:27:18 +01:00
|
|
|
if opt.trace_api {
|
2021-11-05 06:54:27 +01:00
|
|
|
emu.register_component(StderrCallTraceComponent { prefix: "{trace} ".to_string() })?;
|
2021-11-05 00:27:18 +01:00
|
|
|
}
|
2021-08-19 06:10:48 +02:00
|
|
|
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
// must register before opengl so it can have priority in queries about what N64 plugin to use
|
|
|
|
// (only supports software-rendered 2D frames currently)
|
2021-12-11 00:53:02 +01:00
|
|
|
#[cfg(feature = "ffmpeg_comp")]
|
|
|
|
if let Some(video_path) = opt.record_video {
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
ffmpeg::log::set_level(ffmpeg::log::Level::Info);
|
|
|
|
ffmpeg::init()?;
|
2021-12-11 00:53:02 +01:00
|
|
|
let ffmpeg_comp = FfmpegComponent::new(emu.libretro_core(), video_path);
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
emu.register_component(ffmpeg_comp)?;
|
|
|
|
}
|
|
|
|
|
2021-12-10 11:33:43 +01:00
|
|
|
let ft = if opt.fullscreen {
|
|
|
|
FullscreenType::Desktop
|
|
|
|
} else {
|
|
|
|
FullscreenType::Off
|
|
|
|
};
|
|
|
|
|
2021-11-04 23:27:13 +01:00
|
|
|
let sdl2_ogl_res = if opt.no_opengl {
|
|
|
|
Err("OpenGL disabled".into())
|
|
|
|
} else {
|
2021-12-10 11:33:43 +01:00
|
|
|
SimpleSdl2OpenglComponent::new(&mut sdl_context, ft)
|
2021-11-04 23:27:13 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(sdl2_ogl) = sdl2_ogl_res {
|
|
|
|
emu.register_component(sdl2_ogl)?;
|
|
|
|
} else {
|
2021-12-10 11:33:43 +01:00
|
|
|
match SimpleSdl2CanvasComponent::new(&mut sdl_context, ft) {
|
2021-11-04 23:27:13 +01:00
|
|
|
Ok(sdl2_canvas) => emu.register_component(sdl2_canvas)?,
|
|
|
|
Err(e) => eprintln!("Couldn't initialize SDL2 video component: {:?}", e),
|
|
|
|
}
|
|
|
|
}
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-12-17 11:47:08 +01:00
|
|
|
#[cfg(not(feature = "cpal_comp"))]
|
|
|
|
{
|
|
|
|
emu.register_component(SimpleSdl2AudioThreadComponent::new(&mut sdl_context)?)?;
|
|
|
|
}
|
|
|
|
#[cfg(feature = "cpal_comp")]
|
|
|
|
{
|
|
|
|
emu.register_component(CpalAudioComponent::default())?;
|
|
|
|
}
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-12-10 11:33:43 +01:00
|
|
|
emu.register_component(SimpleSdl2KeyboardComponent::new(&mut sdl_context)?)?;
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
emu.register_component(SimpleSdl2GamepadComponent::new(&mut sdl_context))?;
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-11-04 02:31:40 +01:00
|
|
|
emu.register_component(SleepFramerateLimitComponent::default())?;
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-08-17 13:06:40 +02:00
|
|
|
emu.register_component(PathBufComponent {
|
|
|
|
sys_path: opt.system.clone(),
|
|
|
|
libretro_path: Some(opt.core.to_path_buf()),
|
|
|
|
core_assets_path: None,
|
|
|
|
save_path: Some(std::env::temp_dir()),
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
})?;
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-12-08 03:10:49 +01:00
|
|
|
emu.register_component(LocalFileSaveComponent::default())?;
|
|
|
|
|
2021-12-10 08:40:19 +01:00
|
|
|
if opt.sys_info {
|
|
|
|
emu.register_component(StderrSysInfoLogComponent::default())?;
|
|
|
|
}
|
|
|
|
|
2021-12-11 10:38:34 +01:00
|
|
|
emu.register_component(EnvironmentVariableComponent::default())?;
|
|
|
|
|
|
|
|
let mut var_store = VariableStoreComponent::default();
|
2021-12-10 08:40:19 +01:00
|
|
|
// TODO: load a config file specified on the CLI
|
2021-12-11 10:38:34 +01:00
|
|
|
var_store.insert("mgba_skip_bios", "ON");
|
|
|
|
var_store.insert("gpsp_drc", "enabled");
|
|
|
|
var_store.insert("gpsp_frame_mixing", "enabled");
|
|
|
|
var_store.insert("gpsp_save_method", "libretro");
|
|
|
|
emu.register_component(var_store)?;
|
2021-12-10 08:40:19 +01:00
|
|
|
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
emu.init()?;
|
|
|
|
emu.load_game(&opt.rom)?;
|
2021-08-17 13:06:40 +02:00
|
|
|
if let Some(state) = opt.state {
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
emu.unserialize_path(state)?;
|
2021-08-17 13:06:40 +02:00
|
|
|
}
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-12-10 09:06:38 +01:00
|
|
|
let mut frame = 0u64;
|
2021-08-17 13:06:40 +02:00
|
|
|
while let ControlFlow::Continue = emu.run() {
|
2021-12-10 09:06:38 +01:00
|
|
|
frame = frame.wrapping_add(1);
|
2021-08-17 13:06:40 +02:00
|
|
|
}
|
2021-08-19 06:10:48 +02:00
|
|
|
|
2021-08-17 13:06:40 +02:00
|
|
|
eprintln!("Ran for {} frames.", frame);
|
video and audio callback API change:
fn video_refresh(&mut self, data: &[u8], width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_dupe(&mut self, width: c_uint, height: c_uint, pitch: c_uint);
fn video_refresh_hw(&mut self, width: c_uint, height: c_uint);
fn audio_sample(&mut self, left: i16, right: i16);
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize;
have been replaced with
fn video_refresh(&mut self, frame: &VideoFrame);
fn audio_samples(&mut self, stereo_pcm: &[i16]) -> usize;
where VideoFrame is
pub enum VideoFrame<'a> {
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
HardwareRender { width: c_uint, height: c_uint, },
}
use `pub fn VideoFrame::data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)>` for things that need to access the framebuffer data as a byte array rather than a pixel array.
2021-11-01 08:02:25 +01:00
|
|
|
Ok(())
|
2021-08-17 13:06:40 +02:00
|
|
|
}
|