1pub mod pixel;
10
11use core::str::FromStr;
12use core::convert::TryFrom;
13use core::fmt::{self, Debug};
14use core::ops::{BitAnd, BitOr, Shl, Shr, Range};
15
16#[cfg(feature = "snapshot")]
17use serde::{Serialize, Deserialize};
18
19use bitflags::bitflags;
20
21use crate::clock::{Ts, FTs, VideoTs, VFrameTsCounter, MemoryContention};
22use crate::chip::UlaPortFlags;
23
24pub use pixel::{Palette, PixelBuffer};
25
26pub const PAL_VC: u32 = 576/2;
28pub const PAL_HC: u32 = 704/2;
30pub const MAX_BORDER_SIZE: u32 = 6*8;
32
33#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
36#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
37#[repr(u8)]
38pub enum BorderSize {
39 Full = 6,
40 Large = 5,
41 Medium = 4,
42 Small = 3,
43 Tiny = 2,
44 Minimal = 1,
45 Nil = 0
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct TryFromUIntBorderSizeError(pub u8);
50
51#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct ParseBorderSizeError;
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct CellCoords {
57 pub column: u8,
59 pub row: u8
61}
62
63bitflags! {
64 #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
66 #[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
67 #[derive(Default)]
68 pub struct BorderColor: u8 {
69 const BLACK = 0b000;
70 const BLUE = 0b001;
71 const RED = 0b010;
72 const MAGENTA = 0b011;
73 const GREEN = 0b100;
74 const CYAN = 0b101;
75 const YELLOW = 0b110;
76 const WHITE = 0b111;
77 }
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct TryFromU8BorderColorError(pub u8);
82
83pub trait Video {
85 const PIXEL_DENSITY: u32 = 1;
90 type VideoFrame: VideoFrame;
92 type Contention: MemoryContention;
94 fn border_color(&self) -> BorderColor;
96 fn set_border_color(&mut self, border: BorderColor);
98 fn render_video_frame<'a, B: PixelBuffer<'a>, P: Palette<Pixel=B::Pixel>>(
116 &mut self,
117 buffer: &'a mut [u8],
118 pitch: usize,
119 border_size: BorderSize
120 );
121 fn render_size_pixels(border_size: BorderSize) -> (u32, u32) {
126 let (width, height) = Self::VideoFrame::screen_size_pixels(border_size);
127 (width * Self::pixel_density(), height)
128 }
129 fn pixel_density() -> u32 {
131 Self::PIXEL_DENSITY
132 }
133 fn visible_screen_bank(&self) -> usize { 0 }
140 fn current_video_ts(&self) -> VideoTs;
142 fn set_video_ts(&mut self, vts: VideoTs);
144 fn current_video_clock(&self) -> VFrameTsCounter<Self::VideoFrame, Self::Contention>;
146 fn flash_state(&self) -> bool;
148}
149pub trait VideoFrame: Copy + Debug {
168 const HTS_RANGE: Range<Ts>;
170 const HTS_COUNT: Ts = Self::HTS_RANGE.end - Self::HTS_RANGE.start;
172 const VSL_BORDER_TOP: Ts;
174 const VSL_PIXELS: Range<Ts>;
176 const VSL_BORDER_BOT: Ts;
178 const VSL_COUNT: Ts;
180 const FRAME_TSTATES_COUNT: FTs = Self::HTS_COUNT as FTs * Self::VSL_COUNT as FTs;
182 fn border_size_pixels(border_size: BorderSize) -> u32 {
187 match border_size {
188 BorderSize::Full => MAX_BORDER_SIZE,
189 BorderSize::Large => MAX_BORDER_SIZE - 8,
190 BorderSize::Medium => MAX_BORDER_SIZE - 2*8,
191 BorderSize::Small => MAX_BORDER_SIZE - 3*8,
192 BorderSize::Tiny => MAX_BORDER_SIZE - 4*8,
193 BorderSize::Minimal => MAX_BORDER_SIZE - 5*8,
194 BorderSize::Nil => 0
195 }
196 }
197 fn screen_size_pixels(border_size: BorderSize) -> (u32, u32) {
202 let border = 2 * Self::border_size_pixels(border_size);
203 let w = PAL_HC - 2*MAX_BORDER_SIZE + border;
204 let h = (PAL_VC - 2*MAX_BORDER_SIZE + border)
205 .min((Self::VSL_BORDER_BOT + 1 - Self::VSL_BORDER_TOP) as u32);
206 (w, h)
207 }
208 fn border_top_vsl_iter(border_size: BorderSize) -> Range<Ts> {
210 let border = Self::border_size_pixels(border_size) as Ts;
211 let top = (Self::VSL_PIXELS.start - border).max(Self::VSL_BORDER_TOP);
212 top..Self::VSL_PIXELS.start
213 }
214 fn border_bot_vsl_iter(border_size: BorderSize) -> Range<Ts> {
216 let border = Self::border_size_pixels(border_size) as Ts;
217 let bot = (Self::VSL_PIXELS.end + border).min(Self::VSL_BORDER_BOT);
218 Self::VSL_PIXELS.end..bot
219 }
220
221 type BorderHtsIter: Iterator<Item=Ts>;
223 fn border_whole_line_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter;
225 fn border_left_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter;
227 fn border_right_hts_iter(border_size: BorderSize) -> Self::BorderHtsIter;
229
230 fn contention(hc: Ts) -> Ts;
233 fn floating_bus_offset(_hc: Ts) -> Option<u16> {
235 None
236 }
237 #[inline]
241 fn floating_bus_screen_address(VideoTs { vc, hc }: VideoTs) -> Option<u16> {
242 let line = vc - Self::VSL_PIXELS.start;
243 if line >= 0 && vc < Self::VSL_PIXELS.end {
244 Self::floating_bus_offset(hc).map(|offs| {
245 let y = line as u16;
246 let col = (offs >> 3) << 1;
247 match offs & 3 {
249 0 => pixel_line_offset(y) + col,
250 1 => 0x1800 + color_line_offset(y) + col,
251 2 => 0x0001 + pixel_line_offset(y) + col,
252 3 => 0x1801 + color_line_offset(y) + col,
253 _ => unsafe { core::hint::unreachable_unchecked() }
254 }
255 })
256 }
257 else {
258 None
259 }
260 }
261 fn snow_interference_coords(_ts: VideoTs) -> Option<CellCoords> {
263 None
264 }
265 #[inline]
269 fn is_contended_line_mreq(vsl: Ts) -> bool {
270 vsl >= Self::VSL_PIXELS.start && vsl < Self::VSL_PIXELS.end
271 }
272 #[inline]
277 fn is_contended_line_no_mreq(vsl: Ts) -> bool {
278 vsl >= Self::VSL_PIXELS.start && vsl < Self::VSL_PIXELS.end
279 }
280 #[inline]
282 fn vc_hc_to_tstates(vc: Ts, hc: Ts) -> FTs {
283 vc as FTs * Self::HTS_COUNT as FTs + hc as FTs
284 }
285}
286
287impl From<BorderSize> for &'static str {
288 fn from(border: BorderSize) -> &'static str {
289 match border {
290 BorderSize::Full => "full",
291 BorderSize::Large => "large",
292 BorderSize::Medium => "medium",
293 BorderSize::Small => "small",
294 BorderSize::Tiny => "tiny",
295 BorderSize::Minimal => "minimal",
296 BorderSize::Nil => "none",
297 }
298 }
299}
300
301impl fmt::Display for BorderSize {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 f.write_str(<&str>::from(*self))
304 }
305}
306
307impl std::error::Error for ParseBorderSizeError {}
308
309impl fmt::Display for ParseBorderSizeError {
310 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
311 f.write_str("unrecognized border size")
312 }
313}
314
315impl FromStr for BorderSize {
316 type Err = ParseBorderSizeError;
317 fn from_str(name: &str) -> Result<Self, Self::Err> {
320 if name.eq_ignore_ascii_case("full") ||
321 name.eq_ignore_ascii_case("maxi") ||
322 name.eq_ignore_ascii_case("max") {
323 Ok(BorderSize::Full)
324 }
325 else if name.eq_ignore_ascii_case("large") ||
326 name.eq_ignore_ascii_case("big") {
327 Ok(BorderSize::Large)
328 }
329 else if name.eq_ignore_ascii_case("medium") ||
330 name.eq_ignore_ascii_case("medi") ||
331 name.eq_ignore_ascii_case("med") {
332 Ok(BorderSize::Medium)
333 }
334 else if name.eq_ignore_ascii_case("small") ||
335 name.eq_ignore_ascii_case("sm") {
336 Ok(BorderSize::Small)
337 }
338 else if name.eq_ignore_ascii_case("tiny") {
339 Ok(BorderSize::Tiny)
340 }
341 else if name.eq_ignore_ascii_case("minimal") ||
342 name.eq_ignore_ascii_case("mini") ||
343 name.eq_ignore_ascii_case("min") {
344 Ok(BorderSize::Minimal)
345 }
346 else if name.eq_ignore_ascii_case("none") ||
347 name.eq_ignore_ascii_case("nil") ||
348 name.eq_ignore_ascii_case("null") ||
349 name.eq_ignore_ascii_case("zero") {
350 Ok(BorderSize::Nil)
351 }
352 else {
353 u8::from_str(name).map_err(|_| ParseBorderSizeError)
354 .and_then(|size|
355 BorderSize::try_from(size).map_err(|_| ParseBorderSizeError)
356 )
357 }
358 }
359}
360
361impl From<BorderSize> for u8 {
362 fn from(border: BorderSize) -> u8 {
363 border as u8
364 }
365}
366
367impl std::error::Error for TryFromUIntBorderSizeError {}
368
369impl fmt::Display for TryFromUIntBorderSizeError {
370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371 write!(f, "converted integer ({}) out of range for `BorderSize`", self.0)
372 }
373}
374
375impl TryFrom<u8> for BorderSize {
376 type Error = TryFromUIntBorderSizeError;
377 fn try_from(border: u8) -> Result<Self, Self::Error> {
378 use BorderSize::*;
379 Ok(match border {
380 6 => Full,
381 5 => Large,
382 4 => Medium,
383 3 => Small,
384 2 => Tiny,
385 1 => Minimal,
386 0 => Nil,
387 _ => return Err(TryFromUIntBorderSizeError(border))
388 })
389 }
390}
391
392impl std::error::Error for TryFromU8BorderColorError {}
393
394impl fmt::Display for TryFromU8BorderColorError {
395 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396 write!(f, "converted integer ({}) out of range for `BorderColor`", self.0)
397 }
398}
399
400impl TryFrom<u8> for BorderColor {
401 type Error = TryFromU8BorderColorError;
402 fn try_from(color: u8) -> core::result::Result<Self, Self::Error> {
403 BorderColor::from_bits(color).ok_or(TryFromU8BorderColorError(color))
404 }
405}
406
407impl From<UlaPortFlags> for BorderColor {
408 #[inline]
409 fn from(flags: UlaPortFlags) -> Self {
410 BorderColor::from_bits_truncate((flags & UlaPortFlags::BORDER_MASK).bits())
411 }
412}
413
414impl From<BorderColor> for u8 {
415 fn from(color: BorderColor) -> u8 {
416 color.bits()
417 }
418}
419
420#[inline(always)]
422pub fn pixel_line_offset<T>(y: T) -> T
423 where T: Copy + From<u16> + BitAnd<Output=T> + Shl<u16, Output=T> + BitOr<Output=T>
424{
425 (y & T::from(0b0000_0111) ) << 8 |
426 (y & T::from(0b0011_1000) ) << 2 |
427 (y & T::from(0b1100_0000) ) << 5
428}
429
430#[inline(always)]
432pub fn color_line_offset<T>(y: T) -> T
433 where T: Copy + From<u16> + Shr<u16, Output=T> + Shl<u16, Output=T>
434{
435 (y >> 3) << 5
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 #[test]
443 fn video_offsets_works() {
444 assert_eq!(pixel_line_offset(0usize), 0usize);
445 assert_eq!(pixel_line_offset(1usize), 256usize);
446 assert_eq!(pixel_line_offset(8usize), 32usize);
447 assert_eq!(color_line_offset(0usize), 0usize);
448 assert_eq!(color_line_offset(1usize), 0usize);
449 assert_eq!(color_line_offset(8usize), 32usize);
450 assert_eq!(color_line_offset(191usize), 736usize);
451 }
452}