use alloc::sync::Arc;
use super::{PixelFormat, Scanout, Surface, SurfaceInfo};
use crate::dev::sms::vdp::{SCREEN_HEIGHT, SCREEN_WIDTH, SmsVdp, TvRegion};
#[must_use]
pub const fn sms_rgb(colour: u8) -> [u8; 3] {
let r = colour & 0x03;
let g = (colour >> 2) & 0x03;
let b = (colour >> 4) & 0x03;
[r * 85, g * 85, b * 85]
}
#[derive(Debug, Clone)]
pub struct SmsScanout {
vdp: Arc<SmsVdp>,
}
impl SmsScanout {
#[must_use]
pub fn new(vdp: Arc<SmsVdp>) -> SmsScanout {
SmsScanout { vdp }
}
#[must_use]
pub fn vdp(&self) -> &Arc<SmsVdp> {
&self.vdp
}
}
impl Scanout for SmsScanout {
fn info(&self) -> SurfaceInfo {
SurfaceInfo::new(
SCREEN_WIDTH as u32,
u32::from(self.vdp.active_height()),
PixelFormat::RGBA8888,
)
}
fn frame_counter(&self) -> u64 {
self.vdp.frame()
}
fn frame_period_ns(&self) -> u64 {
let region = self.vdp.tv_region();
let (num, den) = match region {
TvRegion::Ntsc => crate::dev::sms::NTSC_MASTER_HZ,
TvRegion::Pal => crate::dev::sms::PAL_MASTER_HZ,
};
let dots = u64::from(region.lines_per_frame()) * crate::dev::sms::vdp::DOTS_PER_LINE;
dots.saturating_mul(crate::dev::sms::DOT_DIVIDER)
.saturating_mul(den)
.saturating_mul(1_000_000_000)
/ num
}
fn capture(&self, dst: &mut Surface) -> u64 {
let info = self.info();
dst.reshape(dst.format(), info.width, info.height);
let serial = self.vdp.frame();
self.vdp.with_framebuffer(|fb| {
for y in 0..info.height {
let row = (y as usize) * SCREEN_WIDTH;
for x in 0..info.width {
dst.put(x, y, sms_rgb(fb[row + x as usize]));
}
}
});
dst.set_serial(serial);
serial
}
}
#[must_use]
pub fn frame_hash(vdp: &SmsVdp) -> u64 {
let height = vdp.active_height() as usize;
vdp.with_framebuffer(|fb| {
let mut hash = 0xcbf2_9ce4_8422_2325u64;
for byte in &fb[..height * SCREEN_WIDTH] {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
})
}
const _: () = assert!(SCREEN_HEIGHT == 240);
pub mod capture {
use super::{Arc, SmsScanout, SmsVdp};
use crate::core::error::Result;
use crate::core::hosts::{Captured, HostKind, HostObjects};
use crate::dev::sms::SdscConsole;
use crate::machine::BuildOptions;
pub fn install(options: &mut BuildOptions) -> Result<()> {
let vdps: Arc<Captured<SmsVdp>> = table(options)?;
options
.bindings
.replace(crate::dev::sms::vdp::CLASS.name, move |props| {
let vdp = Arc::new(SmsVdp::from_props(props)?);
vdps.push(&vdp);
Ok(vdp)
});
let consoles: Arc<Captured<SdscConsole>> = table(options)?;
options
.bindings
.replace(crate::dev::sms::sdsc::CLASS.name, move |props| {
let console = Arc::new(SdscConsole::from_props(props)?);
consoles.push(&console);
Ok(console)
});
Ok(())
}
fn table<T: Captures>(options: &BuildOptions) -> Result<Arc<Captured<T>>> {
options
.realize
.hosts
.open(HostKind::CAPTURE, T::CLASS_NAME, Captured::new)
}
trait Captures: Send + Sync + 'static {
const CLASS_NAME: &'static str;
}
impl Captures for SmsVdp {
const CLASS_NAME: &'static str = crate::dev::sms::vdp::CLASS.name;
}
impl Captures for SdscConsole {
const CLASS_NAME: &'static str = crate::dev::sms::sdsc::CLASS.name;
}
fn taken<T: Captures>(hosts: &HostObjects) -> Option<Arc<T>> {
hosts
.get::<Captured<T>>(HostKind::CAPTURE, T::CLASS_NAME)
.ok()
.flatten()?
.take()
}
#[must_use]
pub fn take_vdp(hosts: &HostObjects) -> Option<SmsScanout> {
taken::<SmsVdp>(hosts).map(SmsScanout::new)
}
#[must_use]
pub fn take_console(hosts: &HostObjects) -> Option<Arc<SdscConsole>> {
taken::<SdscConsole>(hosts)
}
}