spectrusty/chip/ula/
frame_cache.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::fmt;
9use core::marker::PhantomData;
10use crate::clock::{Ts, VideoTs};
11use crate::memory::{ZxMemory, ScreenArray};
12use crate::video::{
13    pixel_line_offset, color_line_offset,
14    VideoFrame, CellCoords,
15    frame_cache::*
16};
17
18// static COL_INK_HTS:  &[Ts] = &[4, 6, 12, 14, 20, 22, 28, 30, 36, 38, 44, 46, 52, 54, 60, 62, 68, 70, 76, 78, 84, 86, 92, 94, 100, 102, 108, 110, 116, 118, 124, 126];
19// static COL_ATTR_HTS: &[Ts] = &[5, 7, 13, 15, 21, 23, 29, 31, 37, 39, 45, 47, 53, 55, 61, 63, 69, 71, 77, 79, 85, 87, 93, 95, 101, 103, 109, 111, 117, 119, 125, 127];
20const COL_INK_HTS:  &[Ts;COLUMNS] = &[1, 3,  9, 11, 17, 19, 25, 27, 33, 35, 41, 43, 49, 51, 57, 59, 65, 67, 73, 75, 81, 83, 89, 91, 97,  99, 105, 107, 113, 115, 121, 123];
21const COL_ATTR_HTS: &[Ts;COLUMNS] = &[2, 4, 10, 12, 18, 20, 26, 28, 34, 36, 42, 44, 50, 52, 58, 60, 66, 68, 74, 76, 82, 84, 90, 92, 98, 100, 106, 108, 114, 116, 122, 124];
22
23/// Spectrum's video data frame cache.
24///
25/// When a byte is being written to the video memory and the video beam position has already passed
26/// the referenced screen cell, the cell value is being cached from memory before the data in memory
27/// is being modified.
28///
29/// The caching is performed only once per frame for each potential cell so subsequent writes to the same
30/// memory address won't modify the cached cell value.
31///
32/// When a screen is being drawn the data stored in cache will override any value currently residing in
33/// video memory.
34#[derive(Clone)]
35pub struct UlaFrameCache<V> {
36    pub frame_pixels: [(u32, [u8;COLUMNS]);PIXEL_LINES],      // read precedence pixels < memory
37    pub frame_colors: [(u32, [u8;COLUMNS]);PIXEL_LINES],
38    pub frame_colors_coarse: [(u32, [u8;COLUMNS]);ATTR_ROWS], // read precedence colors < colors_coarse < memory
39    _video_frame: PhantomData<V>
40}
41
42/// A reference to screen data with a relevant frame cache.
43pub struct UlaFrameRef<'a, V> {
44    pub screen: &'a ScreenArray,
45    pub frame_cache: &'a UlaFrameCache<V>,
46}
47
48pub(crate) struct UlaFrameLineIter<'a> {
49    pub column: usize,
50    pub ink_line: &'a[u8;COLUMNS],
51    pub attr_line: &'a[u8;COLUMNS],
52    pub frame_pixels: &'a(u32, [u8;COLUMNS]),
53    pub frame_colors: &'a(u32, [u8;COLUMNS]),
54    pub frame_colors_coarse: &'a(u32, [u8;COLUMNS])
55}
56
57/// Implements a [VideoFrameDataIterator] for 16k/48k ULA based Spectrum models.
58pub struct UlaFrameProducer<'a, V> {
59    frame_ref: UlaFrameRef<'a, V>,
60    line: usize,
61    line_iter: UlaFrameLineIter<'a>
62}
63
64impl<'a, V> UlaFrameRef<'a, V> {
65    pub const fn new(screen: &'a ScreenArray, frame_cache: &'a UlaFrameCache<V>) -> Self {
66        UlaFrameRef { screen, frame_cache }
67    }
68}
69
70impl<'a> UlaFrameLineIter<'a> {
71    pub fn new<V>(line: usize, screen: &'a ScreenArray, fc: &'a UlaFrameCache<V>) -> Self {
72        let frame_pixels = &fc.frame_pixels[line];
73        let frame_colors = &fc.frame_colors[line];
74        let frame_colors_coarse = &fc.frame_colors_coarse[line >> 3];
75        let ink_line = ink_line_from(line, screen);
76        let attr_line = attr_line_from(line, screen);
77        UlaFrameLineIter {
78            column: 0,
79            ink_line,
80            attr_line,
81            frame_pixels,
82            frame_colors,
83            frame_colors_coarse
84        }
85    }
86}
87
88impl<'a, V> UlaFrameProducer<'a, V> {
89    pub fn new(screen: &'a ScreenArray, frame_cache: &'a UlaFrameCache<V>) -> Self {
90        let line = 0;
91        let line_iter = UlaFrameLineIter::new(line, screen, frame_cache);
92        let frame_ref = UlaFrameRef::new(screen, frame_cache);
93        UlaFrameProducer { line, frame_ref, line_iter }
94    }
95
96    pub fn swap_frame(&mut self, frame_ref: &mut UlaFrameRef<'a, V>) {
97        core::mem::swap(&mut self.frame_ref, frame_ref);
98        self.update_iter()
99    }
100
101    #[inline(always)]
102    pub fn column(&mut self) -> usize {
103        self.line_iter.column
104    }
105
106    #[inline(always)]
107    pub fn line(&mut self) -> usize {
108        self.line
109    }
110
111    pub fn update_iter(&mut self) {
112        let line = self.line;
113        let fc = &self.frame_ref.frame_cache;
114        self.line_iter.frame_pixels = &fc.frame_pixels[line];
115        self.line_iter.frame_colors = &fc.frame_colors[line];
116        self.line_iter.frame_colors_coarse = &fc.frame_colors_coarse[line >> 3];
117        self.line_iter.ink_line = ink_line_from(line, self.frame_ref.screen);
118        self.line_iter.attr_line = attr_line_from(line, self.frame_ref.screen);
119    }
120}
121
122impl<'a, V> VideoFrameDataIterator for UlaFrameProducer<'a, V> {
123    fn next_line(&mut self) {
124        let line = self.line + 1;
125        if line < PIXEL_LINES {
126            self.line = line;
127            self.update_iter();
128            self.line_iter.column = 0;
129        }
130    }
131}
132
133impl<'a, V> Iterator for UlaFrameProducer<'a, V> {
134    type Item = (u8, u8);
135
136    #[inline(always)]
137    fn next(&mut self) -> Option<Self::Item> {
138        self.line_iter.next()
139    }
140}
141
142impl<'a> Iterator for UlaFrameLineIter<'a> {
143    type Item = (u8, u8);
144
145    fn next(&mut self) -> Option<Self::Item> {
146        let column = self.column;
147        if column >= COLUMNS {
148            return None
149        }
150        self.column = column + 1;
151        let bitmask = 1 << column;
152        let ink: u8 = if self.frame_pixels.0 & bitmask != 0 {
153            self.frame_pixels.1[column & (COLUMNS - 1)]
154        }
155        else {
156            self.ink_line[column & (COLUMNS - 1)]
157        };
158        let attr: u8 = if self.frame_colors.0 & bitmask != 0 {
159            self.frame_colors.1[column & (COLUMNS - 1)]
160        }
161        else if self.frame_colors_coarse.0 & bitmask != 0 {
162            self.frame_colors_coarse.1[column & (COLUMNS - 1)]
163        }
164        else {
165            self.attr_line[column & (COLUMNS - 1)]
166        };
167        Some((ink, attr))
168    }
169}
170
171impl<V> Default for UlaFrameCache<V> {
172    fn default() -> Self {
173        UlaFrameCache {
174            frame_pixels: [(0, [0;COLUMNS]);PIXEL_LINES],
175            frame_colors: [(0, [0;COLUMNS]);PIXEL_LINES],
176            frame_colors_coarse: [(0, [0;COLUMNS]);ATTR_ROWS],
177            _video_frame: PhantomData
178        }
179    }
180}
181
182impl<V> fmt::Debug for UlaFrameCache<V> {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        let count = |frame: &[(u32, _)]| -> u32 {
185            frame.iter().map(|(mask,_)| mask.count_ones()).sum()
186        };
187        f.debug_struct("UlaFrameCache")
188            .field("frame_pixels", &count(&self.frame_pixels))
189            .field("frame_colors", &count(&self.frame_colors))
190            .field("frame_colors_coarse", &count(&self.frame_colors_coarse))
191            .finish()
192    }
193}
194
195impl<V> UlaFrameCache<V> {
196    pub fn clear(&mut self) {
197        for p in self.frame_pixels.iter_mut() {
198            p.0 = 0;
199        }
200        for p in self.frame_colors.iter_mut() {
201            p.0 = 0;
202        }
203        for p in self.frame_colors_coarse.iter_mut() {
204            p.0 = 0;
205        }
206    }
207}
208
209impl<V: VideoFrame> UlaFrameCache<V> {
210    /// Compares the given bitmap cell coordinates with the video timestamp and depending
211    /// on the result of that comparison caches (or not) the bitmap cell with the value
212    /// from the memory at the given address.
213    #[inline(never)]
214    pub fn update_frame_pixels<M: ZxMemory>(
215            &mut self,
216            memory: &M,
217            CellCoords { column, row }: CellCoords,
218            addr: u16,
219            ts: VideoTs
220        )
221    {
222        let column = column as usize & 31;
223        let vy = ts.vc - V::VSL_PIXELS.start;
224        let y = Ts::from(row);
225        if y < vy || y == vy && ts.hc > COL_INK_HTS[column] {
226            let (mask, pixels) = &mut self.frame_pixels[row as usize];
227            let mbit = 1 << column;
228            if *mask & mbit == 0 {
229                pixels[column] = memory.read(addr);
230                *mask |= mbit;
231            }
232        }
233    }
234    /// Compares the given attribute cell coordinates with the video timestamp and depending
235    /// on the result of that comparison caches (or not) the attribute cell or cells with
236    /// the value from the memory at the given address.
237    #[inline(never)]
238    pub fn update_frame_colors<M: ZxMemory>(
239            &mut self,
240            memory: &M,
241            CellCoords { column, row }: CellCoords,
242            addr: u16,
243            ts: VideoTs
244        )
245    {
246        let column = column as usize & 31;
247        let vy = ts.vc - V::VSL_PIXELS.start;
248        let coarse_vy = vy >> 3;
249        let coarse_y = Ts::from(row);
250        if coarse_y < coarse_vy ||
251                coarse_y == coarse_vy &&
252                vy & 0b111 == 0b111 &&
253                ts.hc > COL_ATTR_HTS[column] {
254            let (mask, colors) = &mut self.frame_colors_coarse[row as usize];
255            let mbit = 1 << column;
256            if *mask & mbit == 0 {
257                *mask |= mbit;
258                colors[column] = memory.read(addr);
259            }
260        }
261        else if coarse_y == coarse_vy {
262            let line_top = (coarse_vy << 3) as usize;
263            let line_bot = if ts.hc > COL_ATTR_HTS[column] {
264                vy + 1
265            } else {
266                vy
267            } as usize;
268            if line_top < line_bot {
269                let memval = memory.read(addr);
270                let mbit = 1 << column;
271                for (mask, colors) in self.frame_colors[line_top..line_bot].iter_mut().rev() {
272                    if *mask & mbit != 0 {
273                        break;
274                    }
275                    *mask |= mbit;
276                    colors[column] = memval;
277                }
278            }
279        }
280    }
281    /// Caches the bitmap and attribute cell at the given coordinates with the `snow` distortion applied.
282    pub fn apply_snow_interference(
283            &mut self,
284            screen: &ScreenArray,
285            CellCoords { column, row }: CellCoords,
286            snow: u8
287        )
288    {
289        let (row, column) = (row as usize, column as usize & 31);
290        let mbit = 1 << column;
291        let (mask, pixels) = &mut self.frame_pixels[row];
292        let offset_snow = (pixel_line_offset(row) & 0x1F00) | snow as usize;
293        pixels[column] = screen[offset_snow];
294        *mask |= mbit;
295
296        let offset = ATTRS_OFFSET + color_line_offset(row);
297        let offset_snow = (offset & 0x1F00) | snow as usize;
298        let (mask, colors) = &mut self.frame_colors[row];
299        colors[column] = screen[offset_snow];
300        *mask |= mbit;
301        // cache the remaining attribute sub-cell lines preceding the distorted cell
302        let row_top = row & !7;
303        if row_top < row {
304            let memval = screen[offset];
305            for (mask, colors) in self.frame_colors[row_top..row].iter_mut().rev() {
306                if *mask & mbit != 0 {
307                    break;
308                }
309                *mask |= mbit;
310                colors[column] = memval;
311            }
312        }
313    }
314}