tetanes-core 0.14.0

A NES Emulator written in Rust
//! PPUMASK register implementation.
//!
//! See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK>

use crate::common::{Clock, NesRegion, Reset, ResetKind};
use bitflags::bitflags;
use serde::{Deserialize, Serialize};

/// PPUMASK register.
///
/// See: <https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK>
#[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
#[must_use]
pub struct Mask {
    pub emphasis: u16,
    pub grayscale: u8,
    pub rendering_enabled: bool,
    pub prev_rendering_enabled: bool,
    pub pending_rendering_update: bool,
    pub show_left_bg: bool,
    pub show_left_spr: bool,
    pub show_bg: bool,
    pub show_spr: bool,
    pub bits: Bits,
    pub region: NesRegion,
}

bitflags! {
    // $2001 PPUMASK
    //
    // https://wiki.nesdev.org/w/index.php/PPU_registers#PPUMASK
    // BGRs bMmG
    // |||| |||+- Grayscale (0: normal color, 1: produce a grayscale display)
    // |||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide
    // |||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
    // |||| +---- 1: Show background
    // |||+------ 1: Show sprites
    // ||+------- Emphasize red
    // |+-------- Emphasize green
    // +--------- Emphasize blue
    #[derive(Default, Serialize, Deserialize, Debug, Copy, Clone)]
    #[must_use]
    pub struct Bits: u8 {
        const GRAYSCALE = 0x01;
        const SHOW_LEFT_BG = 0x02;
        const SHOW_LEFT_SPR = 0x04;
        const SHOW_BG = 0x08;
        const SHOW_SPR = 0x10;
        const EMPHASIZE_RED = 0x20;
        const EMPHASIZE_GREEN = 0x40;
        const EMPHASIZE_BLUE = 0x80;
    }
}

impl Mask {
    pub fn new(region: NesRegion) -> Self {
        let mut mask = Self {
            region,
            ..Default::default()
        };
        mask.write(0);
        mask
    }

    #[inline]
    pub fn write(&mut self, val: u8) {
        self.bits = Bits::from_bits_truncate(val);
        self.grayscale = if self.bits.contains(Bits::GRAYSCALE) {
            0x30
        } else {
            0x3F
        };
        self.show_left_bg = self.bits.contains(Bits::SHOW_LEFT_BG);
        self.show_left_spr = self.bits.contains(Bits::SHOW_LEFT_SPR);
        self.show_bg = self.bits.contains(Bits::SHOW_BG);
        self.show_spr = self.bits.contains(Bits::SHOW_SPR);
        self.pending_rendering_update = self.rendering_enabled != (self.show_bg || self.show_spr);
        self.update_emphasis();
    }

    pub fn update_emphasis(&mut self) {
        self.emphasis = u16::from(
            match self.region {
                NesRegion::Auto | NesRegion::Ntsc => self.bits.intersection(
                    Bits::EMPHASIZE_RED | Bits::EMPHASIZE_GREEN | Bits::EMPHASIZE_BLUE,
                ),
                NesRegion::Pal | NesRegion::Dendy => {
                    // Red/Green are swapped for PAL/Dendy
                    let mut emphasis = self.bits.intersection(Bits::EMPHASIZE_BLUE);
                    emphasis.set(
                        Bits::EMPHASIZE_GREEN,
                        self.bits.contains(Bits::EMPHASIZE_RED),
                    );
                    emphasis.set(
                        Bits::EMPHASIZE_RED,
                        self.bits.contains(Bits::EMPHASIZE_GREEN),
                    );
                    emphasis
                }
            }
            .bits(),
        ) << 1;
    }

    #[inline]
    pub fn set_region(&mut self, region: NesRegion) {
        self.region = region;
        self.update_emphasis();
    }
}

impl Reset for Mask {
    // https://www.nesdev.org/wiki/PPU_power_up_state
    fn reset(&mut self, _kind: ResetKind) {
        self.write(0);
    }
}

impl Clock for Mask {
    fn clock(&mut self) {
        // Rendering enabled flag is set with a 1 cycle delay (setting it at cycle N won't take
        // effect until cycle N+2)
        if self.pending_rendering_update {
            self.pending_rendering_update = false;

            self.prev_rendering_enabled = self.rendering_enabled;
            self.rendering_enabled = self.show_bg || self.show_spr;
            self.pending_rendering_update = self.prev_rendering_enabled != self.rendering_enabled;
        }
    }
}