spectrusty/chip/ula3/
video.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8use core::iter::StepBy;
9use core::ops::Range;
10
11#[cfg(feature = "snapshot")]
12use serde::{Serialize, Deserialize};
13
14use crate::clock::{VideoTs, Ts, VFrameTsCounter};
15use crate::chip::{
16    ula128::{Ula128VidFrame, video::create_ula128_renderer}
17};
18use crate::video::{
19    BorderSize, BorderColor, PixelBuffer, Palette,
20    VideoFrame, Video,
21    frame_cache::{pixel_address_coords, color_address_coords}
22};
23use super::{Ula3, Ula3MemContention};
24
25/// Implements [VideoFrame] for Amstrad Gate Array (+3/+2A models).
26#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
27#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
28pub struct Ula3VidFrame;
29
30impl VideoFrame for Ula3VidFrame {
31    /// A range of horizontal T-states, 0 should be where the frame starts.
32    const HTS_RANGE: Range<Ts> = Ula128VidFrame::HTS_RANGE;
33    /// The first video scan line index of the top border.
34    const VSL_BORDER_TOP: Ts = Ula128VidFrame::VSL_BORDER_TOP;
35    /// A range of video scan line indexes for the pixel area.
36    const VSL_PIXELS: Range<Ts> = Ula128VidFrame::VSL_PIXELS;
37    /// The last video scan line index of the bottom border.
38    const VSL_BORDER_BOT: Ts = Ula128VidFrame::VSL_BORDER_BOT;
39    /// A total number of video scan lines.
40    const VSL_COUNT: Ts = Ula128VidFrame::VSL_COUNT;
41
42    type BorderHtsIter = StepBy<Range<Ts>>;
43
44    #[inline(always)]
45    fn border_whole_line_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter {
46        Ula128VidFrame::border_whole_line_hts_iter(border_size)
47    }
48    #[inline(always)]
49    fn border_left_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter {
50        Ula128VidFrame::border_left_hts_iter(border_size)
51    }
52    #[inline(always)]
53    fn border_right_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter {
54        Ula128VidFrame::border_right_hts_iter(border_size)
55    }
56
57    #[inline]
58    fn is_contended_line_no_mreq(_vsl: Ts) -> bool {
59        false
60    }
61
62    #[inline]
63    fn contention(hc: Ts) -> Ts {
64        if (-3..125).contains(&hc) {
65            let ct = (hc + 2) & 7;
66            if ct != 0 {
67                return hc + 8 - ct;
68            }
69        }
70        hc
71    }
72}
73
74impl<D, X> Video for Ula3<D, X> {
75    type VideoFrame = Ula3VidFrame;
76    type Contention = Ula3MemContention;
77
78    #[inline]
79    fn border_color(&self) -> BorderColor {
80        self.ula.border_color()
81    }
82
83    fn set_border_color(&mut self, border: BorderColor) {
84        self.ula.set_border_color(border)
85    }
86
87    fn render_video_frame<'a, B: PixelBuffer<'a>, P: Palette<Pixel=B::Pixel>>(
88            &mut self,
89            buffer: &'a mut [u8],
90            pitch: usize,
91            border_size: BorderSize
92        )
93    {
94        create_ula128_renderer(border_size,
95                               &mut self.ula,
96                               self.beg_screen_shadow,
97                               &self.shadow_frame_cache,
98                               &mut self.screen_changes)
99        .render_pixels::<B, P, Self::VideoFrame>(buffer, pitch)
100    }
101
102    fn visible_screen_bank(&self) -> usize {
103        self.cur_screen_shadow.into()
104    }
105
106    fn current_video_ts(&self) -> VideoTs {
107        self.ula.current_video_ts()
108    }
109
110    fn current_video_clock(&self) -> VFrameTsCounter<Self::VideoFrame, Self::Contention> {
111        let contention = self.memory_contention();
112        VFrameTsCounter::from_video_ts(self.ula.current_video_ts(), contention)
113    }
114
115    fn set_video_ts(&mut self, vts: VideoTs) {
116        self.ula.set_video_ts(vts);
117    }
118
119    fn flash_state(&self) -> bool {
120        self.ula.flash_state()
121    }
122}
123
124impl<B, X> Ula3<B, X> {
125    #[inline]
126    pub(super) fn update_frame_cache(&mut self, addr: u16, ts: VideoTs) {
127        let maybe_shadow = match addr {
128            0x4000..=0x5AFF => self.page1_screen_shadow_bank(),
129            0xC000..=0xDAFF => self.page3_screen_shadow_bank(),
130            _ => return
131        };
132        let frame_cache = match maybe_shadow {
133            Some(false) => &mut self.ula.frame_cache,
134            Some(true)  => &mut self.shadow_frame_cache,
135            None => return
136        };
137        if addr & 0x1800 != 0x1800 {
138            let coords = pixel_address_coords(addr);
139            frame_cache.update_frame_pixels(&self.ula.memory, coords, addr, ts);
140        }
141        else {
142            let coords = color_address_coords(addr);
143            frame_cache.update_frame_colors(&self.ula.memory, coords, addr, ts);
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::clock::{TimestampOps, VFrameTs};
151    use super::*;
152    type TestVideoFrame = Ula3VidFrame;
153    type TestVFTs = VFrameTs<TestVideoFrame>;
154
155    #[test]
156    fn test_contention() {
157        let vts0 = TestVFTs::new(0, 0);
158        let tstates = [(14361, 14362),
159                       (14362, 14362),
160                       (14363, 14370),
161                       (14364, 14370),
162                       (14365, 14370),
163                       (14366, 14370),
164                       (14367, 14370),
165                       (14368, 14370)];
166        for offset in (0..16).map(|x| x * 8i32) {
167            for (testing, target) in tstates.iter().copied() {
168                let mut vts: TestVFTs = vts0 + testing + offset as u32;
169                vts.hc = TestVideoFrame::contention(vts.hc);
170                assert_eq!(vts.normalized(),
171                           TestVFTs::from_tstates(target + offset));
172            }
173        }
174        let refts = tstates[0].0 as i32;
175        for ts in (refts - 100..refts)
176            .chain(refts + 128..refts+TestVideoFrame::HTS_COUNT as i32) {
177            let vts = TestVFTs::from_tstates(ts);
178            assert_eq!(TestVideoFrame::contention(vts.hc), vts.hc);
179        }
180    }
181
182    #[test]
183    fn test_video_frame_vts_utils() {
184        assert_eq!(TestVFTs::EOF, TestVFTs::from_tstates(TestVideoFrame::FRAME_TSTATES_COUNT));
185        let items = [((  0, -73),   -73, ( 0, 70835), false, true , (  0, -73)),
186                     ((  0,   0),     0, ( 1,     0), false, true , (  0,   0)),
187                     ((  0,  -1),    -1, ( 0, 70907), false, true , (  0,  -1)),
188                     (( -1,   0),  -228, ( 0, 70680), false, true , ( -1,   0)),
189                     ((  1,   0),   228, ( 1,   228), false, true , (  1,   0)),
190                     ((311,  -1), 70907, ( 1, 70907), true , true , (311,  -1)),
191                     ((311,   0), 70908, ( 2,     0), true , true , (311,   0)),
192                     ((  0, 228),   228, ( 1,   228), false, false, (  1,   0)),
193                     ((622,-227),141589, ( 2, 70681), true,  false, (621,   1))];
194        for ((vc, hc), fts, (nfr, nfts), eof, is_norm, (nvc, nhc)) in items.iter().copied() {
195            let vts = TestVFTs::new(vc, hc);
196            let nvts = TestVFTs::new(nvc, nhc);
197            assert_eq!(TestVideoFrame::vc_hc_to_tstates(vc, hc), fts);
198            assert_eq!(vts.into_tstates(), fts);
199            assert_eq!(TestVFTs::from_tstates(fts), nvts);
200            assert_eq!(vts.into_frame_tstates(1), (nfr, nfts));
201            assert_eq!(vts.is_eof(), eof);
202            assert_eq!(vts.is_normalized(), is_norm);
203            assert_eq!(vts.normalized(), nvts);
204        }
205        assert_eq!(TestVFTs::max_value(), TestVFTs::new(i16::max_value(), 154));
206        assert_eq!(TestVFTs::min_value(), TestVFTs::new(i16::min_value(), -73));
207
208        let items = [((  0,   0),     0, (  0,   0)),
209                     ((  0,   0),     1, (  0,   1)),
210                     (( -1, 154),     1, (  0, -73)),
211                     ((  0,   0),   228, (  1,   0)),
212                     (( -1,   1),   227, (  0,   0)),
213                     ((  0,   0), 70908, (311,   0)),
214                     ((  1,  -1), 70908, (312,  -1)),
215                     ((  2, 228), 70908, (314,   0))];
216        for ((vc0, hc0), delta, (vc1, hc1)) in items.iter().copied() {
217            let vts0 = TestVFTs::new(vc0, hc0);
218            let vts1 = TestVFTs::new(vc1, hc1);
219            assert_eq!(vts0 + delta, vts1);
220            assert_eq!(vts1.diff_from(vts0), delta as i32);
221            assert_eq!(vts0.diff_from(vts1), -(delta as i32));
222        }
223        let items = [((   311,      0), (     0,      0)),
224                     ((   311,    -73), (     0,    -73)),
225                     ((   621,    154), (   310,    154)),
226                     ((     0,    228), (  -311,    228)),
227                     ((-32767, -32768), (-32768, -32768)),
228                     ((-32768, -32768), (-32768, -32768))];
229        for ((vc0, hc0), (vc1, hc1)) in items.iter().copied() {
230            let vts0 = TestVFTs::new(vc0, hc0);
231            let vts1 = TestVFTs::new(vc1, hc1);
232            assert_eq!(vts0.saturating_sub_frame(), vts1);
233        }
234        let items = [((     0,      0), (     0,      0), (     0,      0), (     0,      0)),
235                     ((     1,      1), (     1,      1), (     0,      0), (     2,      2)),
236                     ((     1,      1), (    -1,     -1), (     2,      2), (     0,      0)),
237                     ((     1,    154), (     1,      1), (     0,    153), (     3,    -73)),
238                     ((-32768,    -73), (     1,      1), (-32768,    -73), (-32767,    -72)),
239                     ((-32768,    -73), (-32768,    -73), (     0,      0), (-32768,    -73)),
240                     (( 32767,    154), (     1,      1), ( 32766,    153), ( 32767,    154)),
241                     (( 32767,    154), ( 32767,    154), (     0,      0), ( 32767,    154))];
242        for ((vc0, hc0), (vc1, hc1), (svc, shc), (avc, ahc)) in items.iter().copied() {
243            let vts0 = TestVFTs::new(vc0, hc0);
244            let vts1 = TestVFTs::new(vc1, hc1);
245            let subvts = TestVFTs::new(svc, shc);
246            let addvts = TestVFTs::new(avc, ahc);
247            assert_eq!(vts0.saturating_sub(vts1), subvts);
248            assert_eq!(vts0.saturating_add(vts1), addvts);
249            assert_eq!(vts1.saturating_add(vts0), addvts);
250        }
251    }
252}