use core::fmt;
#[cfg(feature = "snapshot")]
use serde::{Serialize, Deserialize};
pub mod frame_cache;
mod audio_earmic;
mod io;
mod video;
#[cfg(feature = "formats")]
mod screen;
use crate::z80emu::{*, host::Result};
use crate::bus::BusDevice;
use crate::clock::{
VFrameTs, VideoTs, VFrameTsCounter, MemoryContention,
VideoTsData2, VideoTsData6
};
use crate::chip::{
ControlUnit, MemoryAccess,
UlaPortFlags, ScldCtrlFlags, UlaPlusRegFlags, ColorMode, Ula128MemFlags, Ula3CtrlFlags,
UlaControl,
InnerAccess,
scld::frame_cache::SourceMode,
ula::{
UlaControlExt, UlaCpuExt,
frame_cache::UlaFrameCache
},
};
use crate::video::{
BorderColor, Video, RenderMode, UlaPlusPalette, PaletteChange,
VideoFrame
};
use crate::memory::{ZxMemory, MemoryExtension};
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
#[derive(Clone)]
pub struct UlaPlus<U: Video> {
ula: U,
ulaplus_disabled: bool,
scld_mode_rw: bool,
scld_mode: ScldCtrlFlags,
register_flags: UlaPlusRegFlags,
color_mode: ColorMode,
beg_render_mode: RenderMode,
cur_render_mode: RenderMode,
beg_source_mode: SourceMode,
cur_source_mode: SourceMode,
#[cfg_attr(feature = "snapshot", serde(default))]
beg_palette: UlaPlusPalette,
#[cfg_attr(feature = "snapshot", serde(default))]
cur_palette: UlaPlusPalette,
#[cfg_attr(feature = "snapshot", serde(skip))]
sec_frame_cache: UlaFrameCache<U::VideoFrame>,
#[cfg_attr(feature = "snapshot", serde(skip))]
shadow_sec_frame_cache: UlaFrameCache<U::VideoFrame>,
#[cfg_attr(feature = "snapshot", serde(skip))]
palette_changes: Vec<PaletteChange>,
#[cfg_attr(feature = "snapshot", serde(skip))]
mode_changes: Vec<VideoTsData6>,
#[cfg_attr(feature = "snapshot", serde(skip))]
source_changes: Vec<VideoTsData2>,
}
pub struct VideoRenderDataView<'a, I, M, V>
where I: Iterator<Item=VideoTs> + 'a,
M: ZxMemory,
V: VideoFrame
{
pub screen_changes: I,
pub memory: &'a M,
pub frame_cache: &'a UlaFrameCache<V>,
pub frame_cache_shadow: &'a UlaFrameCache<V>,
}
pub trait UlaPlusInner<'a>: Video + MemoryAccess {
type ScreenSwapIter: Iterator<Item=VideoTs> + 'a;
fn is_ula_port(port: u16) -> bool {
port & 1 == 0
}
fn ula_write_earmic(&mut self, flags: UlaPortFlags, ts: VideoTs);
fn push_screen_change(&mut self, ts: VideoTs);
fn update_last_border_color(&mut self, border: BorderColor) -> bool;
fn page1_screen0_shadow_bank(&self) -> Option<bool>;
fn page1_screen1_shadow_bank(&self) -> Option<bool>;
fn page3_screen0_shadow_bank(&self) -> Option<bool>;
fn page3_screen1_shadow_bank(&self) -> Option<bool>;
fn frame_cache_mut_mem_ref(&mut self) -> (&mut UlaFrameCache<Self::VideoFrame>, &Self::Memory);
fn shadow_frame_cache_mut_mem_ref(&mut self) -> (&mut UlaFrameCache<Self::VideoFrame>, &Self::Memory);
fn beg_screen_shadow(&self) -> bool;
fn cur_screen_shadow(&self) -> bool;
fn video_render_data_view(
&'a mut self
) -> VideoRenderDataView<'a, Self::ScreenSwapIter, Self::Memory, Self::VideoFrame>;
}
impl<U> Default for UlaPlus<U>
where U: Video + Default
{
fn default() -> Self {
let ula = U::default();
let border = ula.border_color();
UlaPlus {
ula: U::default(),
ulaplus_disabled: false,
scld_mode_rw: false,
scld_mode: ScldCtrlFlags::default(),
register_flags: UlaPlusRegFlags::default(),
color_mode: ColorMode::default(),
beg_render_mode: RenderMode::from_border_color(border),
cur_render_mode: RenderMode::from_border_color(border),
beg_source_mode: SourceMode::default(),
cur_source_mode: SourceMode::default(),
sec_frame_cache: UlaFrameCache::default(),
shadow_sec_frame_cache: UlaFrameCache::default(),
beg_palette: UlaPlusPalette::default(),
cur_palette: UlaPlusPalette::default(),
palette_changes: Vec::new(),
source_changes: Vec::new(),
mode_changes: Vec::new(),
}
}
}
impl<U> fmt::Debug for UlaPlus<U>
where U: fmt::Debug + Video
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UlaPlus")
.field("ula", &self.ula)
.field("ulaplus_disabled", &self.ulaplus_disabled)
.field("scld_mode_rw", &self.scld_mode_rw)
.field("scld_mode", &self.scld_mode)
.field("register_flags", &self.register_flags)
.field("color_mode", &self.color_mode)
.field("beg_render_mode", &self.beg_render_mode)
.field("cur_render_mode", &self.cur_render_mode)
.field("beg_source_mode", &self.beg_source_mode)
.field("cur_source_mode", &self.cur_source_mode)
.field("sec_frame_cache", &self.sec_frame_cache)
.field("shadow_sec_frame_cache", &self.shadow_sec_frame_cache)
.field("beg_palette", &self.beg_palette)
.field("cur_palette", &self.cur_palette)
.finish()
}
}
impl<U> InnerAccess for UlaPlus<U>
where U: Video
{
type Inner = U;
fn inner_ref(&self) -> &Self::Inner {
&self.ula
}
fn inner_mut(&mut self) -> &mut Self::Inner {
&mut self.ula
}
fn into_inner(self) -> Self::Inner {
self.ula
}
}
impl<'a, U> UlaControl for UlaPlus<U>
where U: UlaControl + UlaPlusInner<'a>
{
fn has_late_timings(&self) -> bool {
self.ula.has_late_timings()
}
fn set_late_timings(&mut self, late_timings: bool) {
self.ula.set_late_timings(late_timings)
}
fn ula128_mem_port_value(&self) -> Option<Ula128MemFlags> {
self.ula.ula128_mem_port_value()
}
fn set_ula128_mem_port_value(&mut self, value: Ula128MemFlags) -> bool {
self.ula.set_ula128_mem_port_value(value)
}
fn ula3_ctrl_port_value(&self) -> Option<Ula3CtrlFlags> {
self.ula.ula3_ctrl_port_value()
}
fn set_ula3_ctrl_port_value(&mut self, value: Ula3CtrlFlags) -> bool {
self.ula.set_ula3_ctrl_port_value(value)
}
fn scld_ctrl_port_value(&self) -> Option<ScldCtrlFlags> {
Some(self.scld_mode)
}
fn set_scld_ctrl_port_value(&mut self, value: ScldCtrlFlags) -> bool {
self.write_scld_ctrl_port(value, self.ula.current_video_ts());
true
}
fn scld_mmu_port_value(&self) -> Option<u8> {
self.ula.scld_mmu_port_value()
}
fn set_scld_mmu_port_value(&mut self, value: u8) -> bool {
self.ula.set_scld_mmu_port_value(value)
}
fn ulaplus_reg_port_value(&self) -> Option<UlaPlusRegFlags> {
Some(self.register_flags)
}
fn set_ulaplus_reg_port_value(&mut self, value: UlaPlusRegFlags) -> bool {
self.write_plus_regs_port(value, self.ula.current_video_ts());
true
}
fn ulaplus_data_port_value(&self) -> Option<u8> {
Some(self.read_plus_data_port())
}
fn set_ulaplus_data_port_value(&mut self, value: u8) -> bool {
self.write_plus_data_port(value, self.ula.current_video_ts());
true
}
}
impl<'a, U> UlaPlus<U>
where U: UlaPlusInner<'a>
{
pub fn can_read_scld_mode(&mut self) -> bool {
self.scld_mode_rw
}
pub fn enable_reading_scld_mode(&mut self, enable_reading: bool) {
self.scld_mode_rw = enable_reading;
}
pub fn is_ulaplus_enabled(&self) -> bool {
!self.ulaplus_disabled
}
pub fn enable_ulaplus_modes(&mut self, enable: bool) {
let ts = self.ula.current_video_ts();
self.ulaplus_disabled = !enable;
if enable {
let flags = self.scld_mode.into_plus_mode();
let render_mode = self.render_mode_from_plus_flags(flags)
.with_color_mode(self.color_mode);
self.update_render_mode(render_mode, ts);
self.update_source_mode(SourceMode::from_plus_reg_flags(flags), ts);
}
else if !enable {
let render_mode = RenderMode::from_border_color(self.ula.border_color());
self.update_render_mode(render_mode, ts);
self.update_source_mode(SourceMode::default(), ts);
}
}
pub fn render_mode(&self) -> RenderMode {
self.cur_render_mode
}
pub fn source_mode(&self) -> SourceMode {
self.cur_source_mode
}
pub fn ulaplus_palette(&self) -> &UlaPlusPalette {
&self.cur_palette
}
pub fn ulaplus_palette_mut(&mut self) -> &mut UlaPlusPalette {
&mut self.cur_palette
}
fn push_mode_change(&mut self, ts: VideoTs) {
self.mode_changes.push((ts, self.cur_render_mode.bits()).into());
}
fn update_render_mode(&mut self, render_mode: RenderMode, ts: VideoTs) {
if render_mode != self.cur_render_mode {
self.cur_render_mode = render_mode;
self.push_mode_change(ts);
}
}
fn update_source_mode(&mut self, source_mode: SourceMode, ts: VideoTs) {
let source_diff = source_mode ^ self.cur_source_mode;
if source_diff.intersects(SourceMode::SOURCE_MASK) {
self.source_changes.push((ts, source_mode.bits()).into());
}
if source_diff.intersects(SourceMode::SHADOW_BANK) {
self.ula.push_screen_change(ts);
}
self.cur_source_mode = source_mode;
}
#[inline]
fn change_border_color(&mut self, border: BorderColor, ts: VideoTs) {
if self.ula.update_last_border_color(border)
&& !self.cur_render_mode.is_hi_res()
{
self.cur_render_mode = self.cur_render_mode.with_color(border.into());
self.push_mode_change(ts);
}
}
fn render_mode_from_plus_flags(&self, flags: UlaPlusRegFlags) -> RenderMode {
if flags.is_screen_hi_res() {
(self.cur_render_mode | RenderMode::HI_RESOLUTION)
.with_color(flags.hires_color_index())
}
else {
(self.cur_render_mode & !RenderMode::HI_RESOLUTION)
.with_color(self.ula.border_color().into())
}
}
fn update_screen_mode(&mut self, flags: UlaPlusRegFlags, ts: VideoTs) {
self.update_source_mode(SourceMode::from_plus_reg_flags(flags), ts);
self.update_render_mode(self.render_mode_from_plus_flags(flags), ts);
}
fn write_scld_ctrl_port(&mut self, flags: ScldCtrlFlags, ts: VideoTs) {
self.scld_mode = flags;
self.update_screen_mode(flags.into_plus_mode(), ts);
}
fn write_plus_regs_port(&mut self, flags: UlaPlusRegFlags, ts: VideoTs) {
if flags.is_mode_group() {
self.update_screen_mode(flags|self.scld_mode.into_plus_mode(), ts);
}
self.register_flags = flags;
}
fn write_plus_data_port(&mut self, data: u8, ts: VideoTs) {
if let Some(index) = self.register_flags.palette_group() {
let entry = &mut self.cur_palette[index as usize];
if data != *entry {
*entry = data;
self.palette_changes.push((ts, index, data).into());
}
}
else if self.register_flags.is_mode_group() {
let color_mode = ColorMode::from_bits_truncate(data);
self.color_mode = color_mode;
self.update_render_mode(self.cur_render_mode.with_color_mode(color_mode), ts);
}
}
fn read_plus_data_port(&self) -> u8 {
if let Some(index) = self.register_flags.palette_group() {
self.cur_palette[index as usize]
}
else {
self.color_mode.bits()
}
}
}
impl<U> MemoryAccess for UlaPlus<U>
where U: Video + MemoryAccess,
{
type Memory = U::Memory;
type MemoryExt = U::MemoryExt;
#[inline(always)]
fn memory_ext_ref(&self) -> &Self::MemoryExt {
self.ula.memory_ext_ref()
}
#[inline(always)]
fn memory_ext_mut(&mut self) -> &mut Self::MemoryExt {
self.ula.memory_ext_mut()
}
#[inline(always)]
fn memory_mut(&mut self) -> &mut Self::Memory {
self.ula.memory_mut()
}
#[inline(always)]
fn memory_ref(&self) -> &Self::Memory {
self.ula.memory_ref()
}
fn memory_with_ext_mut(&mut self) -> (&mut Self::Memory, &mut Self::MemoryExt) {
self.ula.memory_with_ext_mut()
}
}
impl<U, B, X> ControlUnit for UlaPlus<U>
where U: for<'a> UlaPlusInner<'a>
+ ControlUnit<BusDevice=B>
+ UlaControlExt
+ MemoryAccess<MemoryExt=X>
+ Memory<Timestamp=VideoTs>
+ Io<Timestamp=VideoTs, WrIoBreak=(), RetiBreak=()>,
B: BusDevice,
B::Timestamp: From<VFrameTs<U::VideoFrame>>,
X: MemoryExtension
{
type BusDevice = U::BusDevice;
#[inline]
fn bus_device_mut(&mut self) -> &mut Self::BusDevice {
self.ula.bus_device_mut()
}
#[inline]
fn bus_device_ref(&self) -> &Self::BusDevice {
self.ula.bus_device_ref()
}
#[inline]
fn into_bus_device(self) -> Self::BusDevice {
self.ula.into_bus_device()
}
fn reset<C: Cpu>(&mut self, cpu: &mut C, hard: bool) {
self.ula.reset(cpu, hard);
if hard {
let ts = self.ula.current_video_ts();
self.scld_mode = ScldCtrlFlags::default();
self.color_mode = ColorMode::default();
self.register_flags = UlaPlusRegFlags::default();
let render_mode = RenderMode::from_border_color(self.ula.border_color());
self.update_render_mode(render_mode, ts);
self.update_source_mode(SourceMode::default(), ts);
for (p, index) in self.cur_palette.iter_mut().zip(0..64) {
if *p != 0 {
self.palette_changes.push((ts, index, 0).into());
}
*p = 0;
}
}
}
fn nmi<C: Cpu>(&mut self, cpu: &mut C) -> bool {
self.ula_nmi(cpu)
}
fn execute_next_frame<C: Cpu>(&mut self, cpu: &mut C) {
while !self.ula_execute_next_frame_with_breaks(cpu) {}
}
fn ensure_next_frame(&mut self) {
self.ensure_next_frame_vtsc();
}
fn execute_single_step<C: Cpu, F: FnOnce(CpuDebug)>(
&mut self,
cpu: &mut C,
debug: Option<F>
) -> Result<(),()>
{
self.ula_execute_single_step(cpu, debug)
}
}
impl<U> UlaControlExt for UlaPlus<U>
where U: for<'a> UlaPlusInner<'a> + ControlUnit + UlaControlExt
{
fn prepare_next_frame<C: MemoryContention>(
&mut self,
vtsc: VFrameTsCounter<U::VideoFrame, C>
) -> VFrameTsCounter<U::VideoFrame, C>
{
self.beg_render_mode = self.cur_render_mode;
self.beg_source_mode = self.cur_source_mode;
self.beg_palette = self.cur_palette;
self.sec_frame_cache.clear();
self.shadow_sec_frame_cache.clear();
self.palette_changes.clear();
self.source_changes.clear();
self.mode_changes.clear();
self.ula.prepare_next_frame(vtsc)
}
}