use alloc::sync::Arc;
use alloc::vec::Vec;
use super::palette::nes_rgb;
use super::{PixelFormat, Scanout, Surface, SurfaceInfo};
use crate::dev::ppu::{NesPpu, SCREEN_HEIGHT, SCREEN_WIDTH};
#[derive(Debug, Clone)]
pub struct NesScanout {
ppu: Arc<NesPpu>,
}
impl NesScanout {
#[must_use]
pub fn new(ppu: Arc<NesPpu>) -> NesScanout {
NesScanout { ppu }
}
#[must_use]
pub fn ppu(&self) -> &Arc<NesPpu> {
&self.ppu
}
}
impl Scanout for NesScanout {
fn info(&self) -> SurfaceInfo {
SurfaceInfo::new(
SCREEN_WIDTH as u32,
SCREEN_HEIGHT as u32,
PixelFormat::RGBA8888,
)
}
fn frame_counter(&self) -> u64 {
self.ppu.frame()
}
fn frame_period_ns(&self) -> u64 {
let region = self.ppu.tv_region();
let (hz_num, hz_den) = region.master_clock();
let dots = self.ppu.geometry().dots_per_frame;
dots.saturating_mul(region.dot_divider())
.saturating_mul(hz_den)
.saturating_mul(1_000_000_000)
/ hz_num
}
fn capture(&self, dst: &mut Surface) -> u64 {
let info = self.info();
dst.reshape(dst.format(), info.width, info.height);
let serial = self.ppu.frame();
self.ppu.with_framebuffer(|fb| {
for y in 0..info.height {
let row = (y as usize) * SCREEN_WIDTH;
for x in 0..info.width {
let pixel = fb[row + x as usize];
dst.put(x, y, nes_rgb(pixel.0));
}
}
});
dst.set_serial(serial);
serial
}
}
pub mod capture {
use super::{Arc, NesPpu, NesScanout, Vec};
use crate::core::error::Result;
use crate::core::props::Props;
use crate::core::sync::{Global, LockRank};
use crate::dev::ppu::NES_PPU_CLASS;
use crate::machine::realize::Instance;
use crate::machine::{Bindings, BuildOptions};
static CONSTRUCTED: Global<Vec<Arc<NesPpu>>> = Global::with_rank(LockRank::LEAF, Vec::new());
fn construct(props: &Props) -> Result<Arc<dyn Instance>> {
let ppu = Arc::new(NesPpu::new(props)?);
CONSTRUCTED.lock().push(Arc::clone(&ppu));
Ok(ppu)
}
pub fn intercept(bindings: &Bindings) -> Result<Bindings> {
let mut out = Bindings::new();
let mut replaced = false;
let classes: Vec<&'static str> = bindings.classes().collect();
for class in classes {
if class == NES_PPU_CLASS.name {
out.bind(class, construct)?;
replaced = true;
} else if let Some(ctor) = bindings.get(class) {
out.bind(class, ctor)?;
}
}
if !replaced {
out.bind(NES_PPU_CLASS.name, construct)?;
}
Ok(out)
}
pub fn install(options: &mut BuildOptions) -> Result<()> {
options.bindings = intercept(&options.bindings)?;
Ok(())
}
#[must_use]
pub fn take() -> Option<NesScanout> {
let mut table = CONSTRUCTED.lock();
let last = table.pop();
table.clear();
last.map(NesScanout::new)
}
pub fn clear() {
CONSTRUCTED.lock().clear();
}
}