use alloc::sync::Arc;
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};
use crate::core::error::Result;
use crate::core::hosts::{Captured, HostKind, HostObjects};
use crate::dev::ppu::NES_PPU_CLASS;
use crate::machine::BuildOptions;
pub fn install(options: &mut BuildOptions) -> Result<()> {
let seen: Arc<Captured<NesPpu>> =
options
.realize
.hosts
.open(HostKind::CAPTURE, NES_PPU_CLASS.name, Captured::new)?;
options.bindings.replace(NES_PPU_CLASS.name, move |props| {
let ppu = Arc::new(NesPpu::new(props)?);
seen.push(&ppu);
Ok(ppu)
});
Ok(())
}
#[must_use]
pub fn take(hosts: &HostObjects) -> Option<NesScanout> {
let seen = hosts
.get::<Captured<NesPpu>>(HostKind::CAPTURE, NES_PPU_CLASS.name)
.ok()
.flatten()?;
seen.take().map(NesScanout::new)
}
}