1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
    Copyright (C) 2020-2022  Rafal Michalski

    This file is part of SPECTRUSTY, a Rust library for building emulators.

    For the full copyright notice, see the lib.rs file.
*/
use core::convert::TryInto;
use crate::clock::Ts;
use crate::video::{
  pixel_line_offset, color_line_offset,
  CellCoords
};
use crate::memory::ScreenArray;

/// Implemented by screen data iterators for rendering an image of a video frame using [Renderer].
///
/// The iterator emulates the Spectrum's ULA reading two bytes of video data to compose a single cell
/// consisting of 8 pixels.
///
/// The first byte returned by the iterator is interpreted as INK/PAPER bit selector (an INK mask) and
/// the second as a color attribute.
///
/// [Renderer]: crate::video::Renderer
pub trait VideoFrameDataIterator: Iterator<Item=(u8, u8)> {
    /// Forwards the iterator to the beginning of the next video line.
    fn next_line(&mut self);
}
/// Implemented by screen data iterators for rendering an image of a video frame using [RendererPlus].
///
/// This emulates the Spectrum's ULAplus/Timex's SCLD reading two bytes of video data to compose a single cell
/// consisting of 8 (or 16 in hi-res) pixels.
///
/// In low resolution mode the first byte returned by the iterator is interpreted as INK/PAPER bit selector
/// (an INK mask) and the second as a color attribute.
///
/// In high resolution mode both bytes are being used to render 16 monochrome pixels.
///
/// The third value is a horizontal latch timestamp used to synchronize changes to the screen mode and the palette.
///
/// [RendererPlus]: crate::video::RendererPlus
pub trait PlusVidFrameDataIterator: Iterator<Item=(u8, u8, Ts)> {
    /// Forwards the iterator to the beginning of the next video line.
    fn next_line(&mut self);
}

/// The number of screen columns (bytes in line).
pub const COLUMNS: usize = 32;
/// The number of screen pixel lines.
pub const PIXEL_LINES: usize = 192;
/// The number of screen attribute rows.
pub const ATTR_ROWS: usize = 24;
/// The offset into screen memory where attributes data begin.
pub const ATTRS_OFFSET: usize = 0x1800;

/// Returns cell coordinates of a INK/PAPER bitmap cell ignoring the highest 3 address bits.
#[inline(always)]
pub fn pixel_address_coords(addr: u16) -> CellCoords {
    let column = (addr & 0b0001_1111) as u8;
    let row = (addr >> 5 & 0b1100_0000 |
               addr >> 2 & 0b0011_1000 |
               addr >> 8 & 0b0000_0111) as u8;
    CellCoords { column, row }
}
/// Returns cell coordinates of a screen attribute cell ignoring the highest 6 address bits.
#[inline(always)]
pub fn color_address_coords(addr: u16) -> CellCoords {
    let column = (addr & 0b0001_1111) as u8;
    let row = (addr >> 5 & 0b0001_1111) as u8;
    CellCoords { column, row }
}

/// Returns a reference to display attributes line data.
///
/// * `line` should be in range: [0, [PIXEL_LINES]).
/// * `screen` should be a reference to the screen data.
#[inline(always)]
pub fn attr_line_from(line: usize, screen: &ScreenArray) -> &[u8;COLUMNS] {
    let offset = ATTRS_OFFSET + color_line_offset(line);
    cast_line(&screen[offset..offset + COLUMNS])
}

/// Returns a reference to INK/PAPER line data.
///
/// * `line` should be in range: [0, [PIXEL_LINES]).
/// * `screen` should be a reference to the screen data.
#[inline(always)]
pub fn ink_line_from(line: usize, screen: &ScreenArray) -> &[u8;COLUMNS] {
    let offset = pixel_line_offset(line);
    cast_line(&screen[offset..offset + COLUMNS])
}

#[inline(always)]
fn cast_line(line_slice: &[u8]) -> &[u8; COLUMNS] {
    line_slice.try_into().unwrap()
}