1use crate::ppu;
2use crate::ppu::Vram;
3use bincode::{Decode, Encode};
4use jgenesis_common::num::{GetBit, U16Ext, U24Ext};
5use std::cmp;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
8pub enum VerticalDisplaySize {
9    #[default]
10    TwoTwentyFour,
11    TwoThirtyNine,
12}
13
14impl VerticalDisplaySize {
15    pub fn to_lines(self) -> u16 {
16        match self {
17            Self::TwoTwentyFour => 224,
18            Self::TwoThirtyNine => 239,
19        }
20    }
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum BitsPerPixel {
25    // 4-color
26    Two,
27    // 16-color
28    Four,
29    // 256-color
30    Eight,
31}
32
33impl BitsPerPixel {
34    // BG3 and BG4 are always 2bpp
35    pub const BG3: Self = Self::Two;
36    pub const BG4: Self = Self::Two;
37
38    // OBJ is always 4bpp
39    pub const OBJ: Self = Self::Four;
40
41    pub const fn bitplanes(self) -> usize {
42        match self {
43            Self::Two => 2,
44            Self::Four => 4,
45            Self::Eight => 8,
46        }
47    }
48
49    pub const fn tile_size_words(self) -> u16 {
50        match self {
51            Self::Two => 8,
52            Self::Four => 16,
53            Self::Eight => 32,
54        }
55    }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
59pub enum BgMode {
60    // Mode 0: 4x 2bpp background layers
61    Zero,
62    // Mode 1: 2x 4bpp and 1x 2bpp background layers
63    One,
64    // Mode 2: 2x 4bpp background layers with offset-per-tile
65    Two,
66    // Mode 3: 1x 8bpp and 1x 4bpp background layers
67    Three,
68    // Mode 4: 1x 8bpp and 1x 2bpp background layers with offset-per-tile
69    Four,
70    // Mode 5: 1x 4bpp and 1x 2bpp background layers in 512px hi-res
71    Five,
72    // Mode 6: 1x 4bpp background layer in 512px hi-res with offset-per-tile
73    Six,
74    // Mode 7: 1x 8bpp background layer with rotation/scaling
75    #[default]
76    Seven,
77}
78
79impl BgMode {
80    fn from_byte(byte: u8) -> Self {
81        match byte & 0x07 {
82            0x00 => Self::Zero,
83            0x01 => Self::One,
84            0x02 => Self::Two,
85            0x03 => Self::Three,
86            0x04 => Self::Four,
87            0x05 => Self::Five,
88            0x06 => Self::Six,
89            0x07 => Self::Seven,
90            _ => unreachable!("value & 0x07 is always <= 0x07"),
91        }
92    }
93
94    pub fn bg1_bpp(self) -> BitsPerPixel {
95        use BitsPerPixel as BPP;
96
97        match self {
98            Self::Zero => BPP::Two,
99            Self::One | Self::Two | Self::Five | Self::Six => BPP::Four,
100            Self::Three | Self::Four | Self::Seven => BPP::Eight,
101        }
102    }
103
104    pub fn bg2_enabled(self, extbg_enabled: bool) -> bool {
105        // BG2 is enabled in all modes except 6 and 7
106        // Can still display in Mode 7 if EXTBG is enabled (uses same pixel data as BG1)
107        !matches!((self, extbg_enabled), (BgMode::Six, _) | (BgMode::Seven, false))
108    }
109
110    pub fn bg2_bpp(self) -> BitsPerPixel {
111        use BitsPerPixel as BPP;
112
113        match self {
114            Self::Zero | Self::Four | Self::Five => BPP::Two,
115            Self::One | Self::Two | Self::Three => BPP::Four,
116            // BG2 is not rendered in mode 6 or 7; return value doesn't matter
117            Self::Six | Self::Seven => BPP::Eight,
118        }
119    }
120
121    pub fn bg3_enabled(self) -> bool {
122        // BG3 is only _really_ enabled in modes 0 and 1; modes 2/4/6 use it for offset-per-tile
123        matches!(self, Self::Zero | Self::One)
124    }
125
126    pub fn bg4_enabled(self) -> bool {
127        // BG4 is only enabled in mode 0
128        self == Self::Zero
129    }
130
131    pub fn is_offset_per_tile(self) -> bool {
132        // Modes 2/4/6 use BG3 map entries as offsets for BG1/BG2 tiles
133        matches!(self, Self::Two | Self::Four | Self::Six)
134    }
135
136    pub fn is_hi_res(self) -> bool {
137        matches!(self, Self::Five | Self::Six)
138    }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
142pub enum TileSize {
143    // 8x8
144    #[default]
145    Small,
146    // 16x16
147    Large,
148}
149
150impl TileSize {
151    fn from_bit(bit: bool) -> Self {
152        if bit { Self::Large } else { Self::Small }
153    }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
157pub enum ObjTileSize {
158    // 0: 8x8 / 16x16
159    #[default]
160    Zero,
161    // 1: 8x8 / 32x32
162    One,
163    // 2: 8x8 / 64x64
164    Two,
165    // 3: 16x16 / 32x32
166    Three,
167    // 4: 16x16 / 64x64
168    Four,
169    // 5: 32x32 / 64x64
170    Five,
171    // 6: 16x32 / 32x64
172    Six,
173    // 7: 16x32 / 32x32
174    Seven,
175}
176
177impl ObjTileSize {
178    fn from_byte(byte: u8) -> Self {
179        match byte & 0xE0 {
180            0x00 => Self::Zero,
181            0x20 => Self::One,
182            0x40 => Self::Two,
183            0x60 => Self::Three,
184            0x80 => Self::Four,
185            0xA0 => Self::Five,
186            0xC0 => Self::Six,
187            0xE0 => Self::Seven,
188            _ => unreachable!("value & 0xE0 will always be one of the above values"),
189        }
190    }
191
192    pub fn small_size(self) -> (u16, u16) {
193        match self {
194            Self::Zero | Self::One | Self::Two => (8, 8),
195            Self::Three | Self::Four => (16, 16),
196            Self::Five => (32, 32),
197            Self::Six | Self::Seven => (16, 32),
198        }
199    }
200
201    pub fn large_size(self) -> (u16, u16) {
202        match self {
203            Self::Zero => (16, 16),
204            Self::One | Self::Three | Self::Seven => (32, 32),
205            Self::Two | Self::Four | Self::Five => (64, 64),
206            Self::Six => (32, 64),
207        }
208    }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
212pub enum BgScreenSize {
213    #[default]
214    OneScreen,
215    VerticalMirror,
216    HorizontalMirror,
217    FourScreen,
218}
219
220impl BgScreenSize {
221    fn from_byte(byte: u8) -> Self {
222        match byte & 0x03 {
223            0x00 => Self::OneScreen,
224            0x01 => Self::VerticalMirror,
225            0x02 => Self::HorizontalMirror,
226            0x03 => Self::FourScreen,
227            _ => unreachable!("value & 0x03 is always <= 0x03"),
228        }
229    }
230
231    pub fn width_tiles(self) -> u16 {
232        match self {
233            Self::OneScreen | Self::HorizontalMirror => 32,
234            Self::VerticalMirror | Self::FourScreen => 64,
235        }
236    }
237
238    pub fn height_tiles(self) -> u16 {
239        match self {
240            Self::OneScreen | Self::VerticalMirror => 32,
241            Self::HorizontalMirror | Self::FourScreen => 64,
242        }
243    }
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
247pub enum WindowAreaMode {
248    #[default]
249    Disabled,
250    Inside,
251    Outside,
252}
253
254impl WindowAreaMode {
255    fn from_bits(bits: u8) -> Self {
256        match bits & 0x03 {
257            0x00 | 0x01 => Self::Disabled,
258            0x02 => Self::Inside,
259            0x03 => Self::Outside,
260            _ => unreachable!("value & 0x03 is always <= 0x03"),
261        }
262    }
263
264    pub fn to_optional_bool(self, inside: bool) -> Option<bool> {
265        match self {
266            Self::Disabled => None,
267            Self::Inside => Some(inside),
268            Self::Outside => Some(!inside),
269        }
270    }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
274pub enum WindowMaskLogic {
275    #[default]
276    Or,
277    And,
278    Xor,
279    Xnor,
280}
281
282impl WindowMaskLogic {
283    fn from_bits(bits: u8) -> Self {
284        match bits & 0x03 {
285            0x00 => Self::Or,
286            0x01 => Self::And,
287            0x02 => Self::Xor,
288            0x03 => Self::Xnor,
289            _ => unreachable!("value & 0x03 is always <= 0x03"),
290        }
291    }
292
293    pub fn apply(self, window_1: Option<bool>, window_2: Option<bool>) -> bool {
294        match (self, window_1, window_2) {
295            (Self::Or, Some(window_1), Some(window_2)) => window_1 || window_2,
296            (Self::And, Some(window_1), Some(window_2)) => window_1 && window_2,
297            (Self::Xor, Some(window_1), Some(window_2)) => window_1 ^ window_2,
298            (Self::Xnor, Some(window_1), Some(window_2)) => !(window_1 ^ window_2),
299            (_, Some(window_1), None) => window_1,
300            (_, None, Some(window_2)) => window_2,
301            (_, None, None) => false,
302        }
303    }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
307pub enum ColorMathEnableMode {
308    Never,
309    OutsideColorWindow,
310    InsideColorWindow,
311    Always,
312}
313
314impl ColorMathEnableMode {
315    pub fn enabled(self, in_color_window: bool) -> bool {
316        match self {
317            Self::Always => true,
318            Self::InsideColorWindow => in_color_window,
319            Self::OutsideColorWindow => !in_color_window,
320            Self::Never => false,
321        }
322    }
323}
324
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
326pub enum ColorMathOperation {
327    #[default]
328    Add,
329    Subtract,
330}
331
332impl ColorMathOperation {
333    pub fn apply(self, a: u16, b: u16, divide: bool) -> u16 {
334        let (r, g, b) = match self {
335            Self::Add => {
336                let r = (a & 0x1F) + (b & 0x1F);
337                let g = ((a >> 5) & 0x1F) + ((b >> 5) & 0x1F);
338                let b = ((a >> 10) & 0x1F) + ((b >> 10) & 0x1F);
339                (r, g, b)
340            }
341            Self::Subtract => {
342                let r = (a & 0x1F).saturating_sub(b & 0x1F);
343                let g = ((a >> 5) & 0x1F).saturating_sub((b >> 5) & 0x1F);
344                let b = ((a >> 10) & 0x1F).saturating_sub((b >> 10) & 0x1F);
345                (r, g, b)
346            }
347        };
348
349        if divide {
350            let r = r >> 1;
351            let g = g >> 1;
352            let b = b >> 1;
353            r | (g << 5) | (b << 10)
354        } else {
355            let r = cmp::min(r, 0x1F);
356            let g = cmp::min(g, 0x1F);
357            let b = cmp::min(b, 0x1F);
358            r | (g << 5) | (b << 10)
359        }
360    }
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
364pub enum VramAddressTranslation {
365    #[default]
366    None,
367    EightBit,
368    NineBit,
369    TenBit,
370}
371
372impl VramAddressTranslation {
373    fn from_byte(byte: u8) -> Self {
374        match byte & 0x0C {
375            0x00 => Self::None,
376            0x04 => Self::EightBit,
377            0x08 => Self::NineBit,
378            0x0C => Self::TenBit,
379            _ => unreachable!("value & 0x0C is always one of the above values"),
380        }
381    }
382
383    pub fn apply(self, vram_addr: u16) -> u16 {
384        match self {
385            Self::None => vram_addr,
386            Self::EightBit => {
387                (vram_addr & 0xFF00) | ((vram_addr >> 5) & 0x0007) | ((vram_addr & 0x001F) << 3)
388            }
389            Self::NineBit => {
390                (vram_addr & 0xFE00) | ((vram_addr >> 6) & 0x0007) | ((vram_addr & 0x003F) << 3)
391            }
392            Self::TenBit => {
393                (vram_addr & 0xFC00) | ((vram_addr >> 7) & 0x0007) | ((vram_addr & 0x007F) << 3)
394            }
395        }
396    }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
400pub enum VramIncrementMode {
401    #[default]
402    Low,
403    High,
404}
405
406impl VramIncrementMode {
407    fn from_byte(byte: u8) -> Self {
408        if byte.bit(7) { Self::High } else { Self::Low }
409    }
410}
411
412#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
413pub enum ObjPriorityMode {
414    #[default]
415    Normal,
416    Rotate,
417}
418
419impl ObjPriorityMode {
420    fn from_byte(byte: u8) -> Self {
421        if byte.bit(7) { Self::Rotate } else { Self::Normal }
422    }
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
426pub enum Mode7OobBehavior {
427    #[default]
428    Wrap,
429    Transparent,
430    Tile0,
431}
432
433impl Mode7OobBehavior {
434    fn from_byte(byte: u8) -> Self {
435        match byte & 0xC0 {
436            0x00 | 0x40 => Self::Wrap,
437            0x80 => Self::Transparent,
438            0xC0 => Self::Tile0,
439            _ => unreachable!("value & 0xC0 is always one of the above values"),
440        }
441    }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
445pub enum AccessFlipflop {
446    #[default]
447    First,
448    Second,
449}
450
451impl AccessFlipflop {
452    #[must_use]
453    pub fn toggle(self) -> Self {
454        match self {
455            Self::First => Self::Second,
456            Self::Second => Self::First,
457        }
458    }
459}
460
461#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
462pub enum MidScanlineUpdate {
463    Inidisp,
464    Scroll,
465}
466
467#[derive(Debug, Clone, Encode, Decode)]
468pub struct Mode7Registers {
469    // M7SEL
470    pub h_flip: bool,
471    pub v_flip: bool,
472    pub oob_behavior: Mode7OobBehavior,
473    // M7A/M7B/M7C/M7D
474    pub parameter_a: u16,
475    pub parameter_b: u16,
476    pub parameter_c: u16,
477    pub parameter_d: u16,
478    // M7HOFS/M7VOFS
479    pub h_scroll: u16,
480    pub v_scroll: u16,
481    // M7X/M7Y
482    pub center_x: u16,
483    pub center_y: u16,
484}
485
486impl Mode7Registers {
487    fn new() -> Self {
488        Self {
489            h_flip: false,
490            v_flip: false,
491            oob_behavior: Mode7OobBehavior::default(),
492            parameter_a: 0xFFFF,
493            parameter_b: 0xFFFF,
494            parameter_c: 0,
495            parameter_d: 0,
496            h_scroll: 0,
497            v_scroll: 0,
498            center_x: 0,
499            center_y: 0,
500        }
501    }
502}
503
504#[derive(Debug, Clone, Encode, Decode)]
505pub struct Registers {
506    // INIDISP
507    pub forced_blanking: bool,
508    pub brightness: u8,
509    // SETINI
510    pub extbg_enabled: bool,
511    pub pseudo_h_hi_res: bool,
512    pub pseudo_obj_hi_res: bool,
513    pub v_display_size: VerticalDisplaySize,
514    pub interlaced: bool,
515    // TM
516    pub main_bg_enabled: [bool; 4],
517    pub main_obj_enabled: bool,
518    // TS
519    pub sub_bg_enabled: [bool; 4],
520    pub sub_obj_enabled: bool,
521    // BGMODE
522    pub bg_mode: BgMode,
523    pub mode_1_bg3_priority: bool,
524    pub bg_tile_size: [TileSize; 4],
525    // MOSAIC
526    pub mosaic_size: u8,
527    pub bg_mosaic_enabled: [bool; 4],
528    // BG1SC/BG2SC/BG3SC/BG4SC
529    pub bg_screen_size: [BgScreenSize; 4],
530    pub bg_base_address: [u16; 4],
531    // BG12NBA/BG34NBA
532    pub bg_tile_base_address: [u16; 4],
533    // BG1VOFS/BG2VOFS/BG3VOFS/BG4VOFS
534    pub bg_h_scroll: [u16; 4],
535    pub bg_v_scroll: [u16; 4],
536    pub bg_scroll_write_buffer: u8,
537    // OBSEL
538    pub obj_tile_base_address: u16,
539    pub obj_tile_gap_size: u16,
540    pub obj_tile_size: ObjTileSize,
541    // TMW
542    pub main_bg_disabled_in_window: [bool; 4],
543    pub main_obj_disabled_in_window: bool,
544    // TSW
545    pub sub_bg_disabled_in_window: [bool; 4],
546    pub sub_obj_disabled_in_window: bool,
547    // WH0/WH1/WH2/WH3
548    pub window_1_left: u16,
549    pub window_1_right: u16,
550    pub window_2_left: u16,
551    pub window_2_right: u16,
552    // W12SEL/W34SEL/WOBJSEL
553    pub bg_window_1_area: [WindowAreaMode; 4],
554    pub bg_window_2_area: [WindowAreaMode; 4],
555    pub obj_window_1_area: WindowAreaMode,
556    pub obj_window_2_area: WindowAreaMode,
557    pub math_window_1_area: WindowAreaMode,
558    pub math_window_2_area: WindowAreaMode,
559    // WBGLOG/WOBJLOG
560    pub bg_window_mask_logic: [WindowMaskLogic; 4],
561    pub obj_window_mask_logic: WindowMaskLogic,
562    pub math_window_mask_logic: WindowMaskLogic,
563    // CGWSEL
564    pub force_main_screen_black: ColorMathEnableMode,
565    pub color_math_enabled: ColorMathEnableMode,
566    pub sub_bg_obj_enabled: bool,
567    pub direct_color_mode_enabled: bool,
568    // CGADSUB
569    pub color_math_operation: ColorMathOperation,
570    pub color_math_divide_enabled: bool,
571    pub bg_color_math_enabled: [bool; 4],
572    pub obj_color_math_enabled: bool,
573    pub backdrop_color_math_enabled: bool,
574    // COLDATA
575    pub sub_backdrop_color: u16,
576    // VMAIN
577    pub vram_address_increment_step: u16,
578    pub vram_address_translation: VramAddressTranslation,
579    pub vram_address_increment_mode: VramIncrementMode,
580    // VMADDL/VMADDH
581    pub vram_address: u16,
582    // RDVRAML/RDVRAMH
583    pub vram_prefetch_buffer: u16,
584    // OAMADDL/OAMADDH
585    pub oam_address: u16,
586    pub oam_address_reload_value: u16,
587    pub obj_priority_mode: ObjPriorityMode,
588    pub oam_write_buffer: u8,
589    pub oam_data_flipflop: AccessFlipflop,
590    // CGADD
591    pub cgram_address: u8,
592    // CGDATA/RDCGRAM
593    pub cgram_write_buffer: u8,
594    pub cgram_flipflop: AccessFlipflop,
595    // Mode 7
596    pub mode_7: Mode7Registers,
597    pub mode_7_latched: Mode7Registers,
598    pub mode_7_write_buffer: u8,
599    // PPU multiply unit (M7A/M7B)
600    pub multiply_operand_l: i16,
601    pub multiply_operand_r: i8,
602    // OPHCT/OPVCT
603    pub latched_h_counter: u16,
604    pub latched_v_counter: u16,
605    pub new_hv_latched: bool,
606    pub h_counter_flipflop: AccessFlipflop,
607    pub v_counter_flipflop: AccessFlipflop,
608    // Sprite overflow flags (readable in STAT77)
609    pub sprite_overflow: bool,
610    pub sprite_pixel_overflow: bool,
611    // Tracks mid-scanline writes to certain registers (currently just scroll registers and INIDISP)
612    pub mid_line_update: Option<MidScanlineUpdate>,
613    // Copied from WRIO CPU register (needed for H/V counter latching)
614    pub programmable_joypad_port: u8,
615}
616
617impl Registers {
618    pub fn new() -> Self {
619        Self {
620            forced_blanking: true,
621            brightness: 0,
622            extbg_enabled: false,
623            pseudo_h_hi_res: false,
624            pseudo_obj_hi_res: false,
625            v_display_size: VerticalDisplaySize::default(),
626            interlaced: false,
627            main_bg_enabled: [false; 4],
628            main_obj_enabled: false,
629            sub_bg_enabled: [false; 4],
630            sub_obj_enabled: false,
631            bg_mode: BgMode::default(),
632            mode_1_bg3_priority: true,
633            bg_tile_size: [TileSize::default(); 4],
634            mosaic_size: 0,
635            bg_mosaic_enabled: [false; 4],
636            bg_screen_size: [BgScreenSize::default(); 4],
637            bg_base_address: [0; 4],
638            bg_tile_base_address: [0; 4],
639            bg_h_scroll: [0; 4],
640            bg_v_scroll: [0; 4],
641            bg_scroll_write_buffer: 0,
642            obj_tile_base_address: 0,
643            obj_tile_gap_size: 0,
644            obj_tile_size: ObjTileSize::default(),
645            main_bg_disabled_in_window: [false; 4],
646            main_obj_disabled_in_window: false,
647            sub_bg_disabled_in_window: [false; 4],
648            sub_obj_disabled_in_window: false,
649            window_1_left: 0,
650            window_1_right: 0,
651            window_2_left: 0,
652            window_2_right: 0,
653            bg_window_1_area: [WindowAreaMode::default(); 4],
654            bg_window_2_area: [WindowAreaMode::default(); 4],
655            obj_window_1_area: WindowAreaMode::default(),
656            obj_window_2_area: WindowAreaMode::default(),
657            math_window_1_area: WindowAreaMode::default(),
658            math_window_2_area: WindowAreaMode::default(),
659            bg_window_mask_logic: [WindowMaskLogic::default(); 4],
660            obj_window_mask_logic: WindowMaskLogic::default(),
661            math_window_mask_logic: WindowMaskLogic::default(),
662            force_main_screen_black: ColorMathEnableMode::Never,
663            color_math_enabled: ColorMathEnableMode::Always,
664            sub_bg_obj_enabled: false,
665            direct_color_mode_enabled: false,
666            color_math_operation: ColorMathOperation::default(),
667            color_math_divide_enabled: false,
668            bg_color_math_enabled: [false; 4],
669            obj_color_math_enabled: false,
670            backdrop_color_math_enabled: false,
671            sub_backdrop_color: 0,
672            vram_address_increment_step: 1,
673            vram_address_translation: VramAddressTranslation::default(),
674            vram_address_increment_mode: VramIncrementMode::default(),
675            vram_address: 0,
676            vram_prefetch_buffer: 0,
677            oam_address: 0,
678            oam_address_reload_value: 0,
679            obj_priority_mode: ObjPriorityMode::default(),
680            oam_write_buffer: 0,
681            oam_data_flipflop: AccessFlipflop::default(),
682            cgram_address: 0,
683            cgram_write_buffer: 0,
684            cgram_flipflop: AccessFlipflop::default(),
685            mode_7: Mode7Registers::new(),
686            mode_7_latched: Mode7Registers::new(),
687            mode_7_write_buffer: 0,
688            multiply_operand_l: !0,
689            multiply_operand_r: !0,
690            latched_h_counter: 0,
691            latched_v_counter: 0,
692            new_hv_latched: false,
693            h_counter_flipflop: AccessFlipflop::default(),
694            v_counter_flipflop: AccessFlipflop::default(),
695            sprite_overflow: false,
696            sprite_pixel_overflow: false,
697            mid_line_update: None,
698            programmable_joypad_port: 0xFF,
699        }
700    }
701
702    pub fn write_inidisp(&mut self, value: u8, first_vblank_scanline: bool) {
703        // INIDISP: Display control 1
704        let prev_forced_blanking = self.forced_blanking;
705        let prev_brightness = self.brightness;
706
707        self.forced_blanking = value.bit(7);
708        self.brightness = value & 0x0F;
709
710        // Disabling forced blanking during the first VBlank scanline immediately reloads OAM address
711        if prev_forced_blanking && !self.forced_blanking && first_vblank_scanline {
712            self.reload_oam_address();
713        }
714
715        if prev_forced_blanking != self.forced_blanking
716            || (!self.forced_blanking && prev_brightness != self.brightness)
717        {
718            self.mid_line_update = Some(MidScanlineUpdate::Inidisp);
719        }
720
721        log::trace!("  Forced blanking: {}", self.forced_blanking);
722        log::trace!("  Brightness: {}", self.brightness);
723    }
724
725    pub fn write_setini(&mut self, value: u8) {
726        // SETINI: Display control 2
727        self.interlaced = value.bit(0);
728        self.pseudo_obj_hi_res = value.bit(1);
729        self.v_display_size = if value.bit(2) {
730            VerticalDisplaySize::TwoThirtyNine
731        } else {
732            VerticalDisplaySize::TwoTwentyFour
733        };
734        self.pseudo_h_hi_res = value.bit(3);
735        self.extbg_enabled = value.bit(6);
736
737        log::trace!("  Interlaced: {}", self.interlaced);
738        log::trace!("  Pseudo H hi-res: {}", self.pseudo_h_hi_res);
739        log::trace!("  Smaller OBJs: {}", self.pseudo_obj_hi_res);
740        log::trace!("  EXTBG enabled: {}", self.extbg_enabled);
741        log::trace!("  V display size: {:?}", self.v_display_size);
742    }
743
744    pub fn write_tm(&mut self, value: u8) {
745        // TM: Main screen designation
746        for (i, bg_enabled) in self.main_bg_enabled.iter_mut().enumerate() {
747            *bg_enabled = value.bit(i as u8);
748        }
749        self.main_obj_enabled = value.bit(4);
750
751        log::trace!("  Main screen BG enabled: {:?}", self.main_bg_enabled);
752        log::trace!("  Main screen OBJ enabled: {}", self.main_obj_enabled);
753    }
754
755    pub fn write_ts(&mut self, value: u8) {
756        // TS: Sub screen designation
757        for (i, bg_enabled) in self.sub_bg_enabled.iter_mut().enumerate() {
758            *bg_enabled = value.bit(i as u8);
759        }
760        self.sub_obj_enabled = value.bit(4);
761
762        log::trace!("  Sub screen BG enabled: {:?}", self.sub_bg_enabled);
763        log::trace!("  Sub screen OBJ enabled: {:?}", self.sub_obj_enabled);
764    }
765
766    pub fn write_bgmode(&mut self, value: u8) {
767        // BGMODE: BG mode and character size
768        self.bg_mode = BgMode::from_byte(value);
769        self.mode_1_bg3_priority = value.bit(3);
770
771        for (i, tile_size) in self.bg_tile_size.iter_mut().enumerate() {
772            *tile_size = TileSize::from_bit(value.bit(i as u8 + 4));
773        }
774
775        log::trace!("  BG mode: {:?}", self.bg_mode);
776        log::trace!("  Mode 1 BG3 priority: {}", self.mode_1_bg3_priority);
777        log::trace!("  BG tile sizes: {:?}", self.bg_tile_size);
778    }
779
780    pub fn write_mosaic(&mut self, value: u8) {
781        // MOSAIC: Mosaic size and enable
782        self.mosaic_size = value >> 4;
783
784        for (i, mosaic_enabled) in self.bg_mosaic_enabled.iter_mut().enumerate() {
785            *mosaic_enabled = value.bit(i as u8);
786        }
787
788        log::trace!("  Mosaic size: {}", self.mosaic_size);
789        log::trace!("  Mosaic enabled: {:?}", self.bg_mosaic_enabled);
790    }
791
792    pub fn write_bg1234sc(&mut self, bg: usize, value: u8) {
793        // BG1SC/BG2SC/BG3SC/BG4SC: BG1-4 screen base and size
794        self.bg_screen_size[bg] = BgScreenSize::from_byte(value);
795        self.bg_base_address[bg] = u16::from(value & 0xFC) << 8;
796
797        log::trace!("  BG{} screen size: {:?}", bg + 1, self.bg_screen_size[bg]);
798        log::trace!("  BG{} base address: {:04X}", bg + 1, self.bg_base_address[bg]);
799    }
800
801    pub fn write_bg1234nba(&mut self, base_bg: usize, value: u8) {
802        // BG12NBA/BG34NBA: BG 1/2/3/4 character data area designation
803        self.bg_tile_base_address[base_bg] = u16::from(value & 0x0F) << 12;
804        self.bg_tile_base_address[base_bg + 1] = u16::from(value & 0xF0) << 8;
805
806        log::trace!(
807            "  BG{} tile base address: {:04X}",
808            base_bg + 1,
809            self.bg_tile_base_address[base_bg]
810        );
811        log::trace!(
812            "  BG{} tile base address: {:04X}",
813            base_bg + 2,
814            self.bg_tile_base_address[base_bg + 1],
815        );
816    }
817
818    pub fn write_bg1hofs(&mut self, value: u8) {
819        // BG1HOFS: BG1 horizontal scroll / M7HOFS: Mode 7 horizontal scroll
820        self.write_bg_h_scroll(0, value);
821
822        self.mode_7.h_scroll = u16::from_le_bytes([self.mode_7_write_buffer, value]);
823        self.mode_7_write_buffer = value;
824
825        log::trace!("  Mode 7 H scroll: {:04X}", self.mode_7.h_scroll);
826    }
827
828    pub fn write_bg1vofs(&mut self, value: u8) {
829        // BG1VOFS: BG1 vertical scroll / M7VOFS: Mode 7 vertical scroll
830        self.write_bg_v_scroll(0, value);
831
832        self.mode_7.v_scroll = u16::from_le_bytes([self.mode_7_write_buffer, value]);
833        self.mode_7_write_buffer = value;
834
835        log::trace!("  Mode 7 V scroll: {:04X}", self.mode_7.v_scroll);
836    }
837
838    pub fn write_bg_h_scroll(&mut self, i: usize, value: u8) {
839        let current = self.bg_h_scroll[i];
840        let prev = self.bg_scroll_write_buffer;
841
842        // H scroll formula from https://wiki.superfamicom.org/backgrounds
843        self.bg_h_scroll[i] =
844            (u16::from(value) << 8) | u16::from(prev & !0x07) | ((current >> 8) & 0x07);
845        self.bg_scroll_write_buffer = value;
846
847        if self.bg_mode != BgMode::Seven {
848            self.mid_line_update = Some(MidScanlineUpdate::Scroll);
849        }
850
851        log::trace!("  BG{} H scroll: {:04X}", i + 1, self.bg_h_scroll[i]);
852    }
853
854    pub fn write_bg_v_scroll(&mut self, i: usize, value: u8) {
855        let prev = self.bg_scroll_write_buffer;
856
857        self.bg_v_scroll[i] = u16::from_le_bytes([prev, value]);
858        self.bg_scroll_write_buffer = value;
859
860        if self.bg_mode != BgMode::Seven {
861            self.mid_line_update = Some(MidScanlineUpdate::Scroll);
862        }
863
864        log::trace!("  BG{} V scroll: {:04X}", i + 1, self.bg_v_scroll[i]);
865    }
866
867    pub fn write_m7sel(&mut self, value: u8) {
868        // M7SEL: Mode 7 settings
869        self.mode_7.h_flip = value.bit(0);
870        self.mode_7.v_flip = value.bit(1);
871        self.mode_7.oob_behavior = Mode7OobBehavior::from_byte(value);
872
873        log::trace!("  Mode 7 H flip: {}", self.mode_7.h_flip);
874        log::trace!("  Mode 7 V flip: {}", self.mode_7.v_flip);
875        log::trace!("  Mode 7 OOB behavior: {:?}", self.mode_7.oob_behavior);
876    }
877
878    pub fn write_m7a(&mut self, value: u8) {
879        // M7A: Mode 7 parameter A / multiply 16-bit operand
880        self.mode_7.parameter_a = u16::from_le_bytes([self.mode_7_write_buffer, value]);
881        self.multiply_operand_l = i16::from_le_bytes([self.mode_7_write_buffer, value]);
882        self.mode_7_write_buffer = value;
883
884        log::trace!("  Mode 7 parameter A: {:04X}", self.mode_7.parameter_a);
885    }
886
887    pub fn write_m7b(&mut self, value: u8) {
888        // M7B: Mode 7 parameter B / multiply 8-bit operand
889        self.mode_7.parameter_b = u16::from_le_bytes([self.mode_7_write_buffer, value]);
890        self.multiply_operand_r = value as i8;
891        self.mode_7_write_buffer = value;
892
893        log::trace!("  Mode 7 parameter B: {:04X}", self.mode_7.parameter_b);
894    }
895
896    pub fn write_m7c(&mut self, value: u8) {
897        // M7C: Mode 7 parameter C
898        self.mode_7.parameter_c = u16::from_le_bytes([self.mode_7_write_buffer, value]);
899        self.mode_7_write_buffer = value;
900
901        log::trace!("  Mode 7 parameter C: {:04X}", self.mode_7.parameter_c);
902    }
903
904    pub fn write_m7d(&mut self, value: u8) {
905        // M7D: Mode 7 parameter D
906        self.mode_7.parameter_d = u16::from_le_bytes([self.mode_7_write_buffer, value]);
907        self.mode_7_write_buffer = value;
908
909        log::trace!("  Mode 7 parameter D: {:04X}", self.mode_7.parameter_d);
910    }
911
912    pub fn write_m7x(&mut self, value: u8) {
913        // M7X: Mode 7 center X coordinate
914        self.mode_7.center_x = u16::from_le_bytes([self.mode_7_write_buffer, value]) & 0x1FFF;
915        self.mode_7_write_buffer = value;
916
917        log::trace!("  Mode 7 center X: {:04X}", self.mode_7.center_x);
918    }
919
920    pub fn write_m7y(&mut self, value: u8) {
921        // M7Y: Mode 7 center Y coordinate
922        self.mode_7.center_y = u16::from_le_bytes([self.mode_7_write_buffer, value]) & 0x1FFF;
923        self.mode_7_write_buffer = value;
924
925        log::trace!("  Mode 7 center Y: {:04X}", self.mode_7.center_y);
926    }
927
928    pub fn write_obsel(&mut self, value: u8) {
929        // OBSEL: Object size and base
930        self.obj_tile_base_address = u16::from(value & 0x07) << 13;
931        self.obj_tile_gap_size = u16::from(value & 0x18) << 9;
932        self.obj_tile_size = ObjTileSize::from_byte(value);
933
934        log::trace!("  OBJ tile base address: {:04X}", self.obj_tile_base_address);
935        log::trace!("  OBJ tile gap size: {:04X}", self.obj_tile_gap_size);
936        log::trace!("  OBJ tile size: {:?}", self.obj_tile_size);
937    }
938
939    pub fn write_w1234sel(&mut self, base_bg: usize, value: u8) {
940        // W12SEL/W34SEL: Window BG1/2/3/4 mask settings
941        self.bg_window_1_area[base_bg] = WindowAreaMode::from_bits(value);
942        self.bg_window_2_area[base_bg] = WindowAreaMode::from_bits(value >> 2);
943        self.bg_window_1_area[base_bg + 1] = WindowAreaMode::from_bits(value >> 4);
944        self.bg_window_2_area[base_bg + 1] = WindowAreaMode::from_bits(value >> 6);
945
946        log::trace!("  BG{} window 1 mask: {:?}", base_bg + 1, self.bg_window_1_area[base_bg]);
947        log::trace!("  BG{} window 2 mask: {:?}", base_bg + 1, self.bg_window_2_area[base_bg]);
948        log::trace!("  BG{} window 1 mask: {:?}", base_bg + 2, self.bg_window_1_area[base_bg + 1]);
949        log::trace!("  BG{} window 2 mask: {:?}", base_bg + 2, self.bg_window_2_area[base_bg + 1]);
950    }
951
952    pub fn write_wobjsel(&mut self, value: u8) {
953        // WOBJSEL: Window OBJ/MATH mask settings
954        self.obj_window_1_area = WindowAreaMode::from_bits(value);
955        self.obj_window_2_area = WindowAreaMode::from_bits(value >> 2);
956        self.math_window_1_area = WindowAreaMode::from_bits(value >> 4);
957        self.math_window_2_area = WindowAreaMode::from_bits(value >> 6);
958
959        log::trace!("  OBJ window 1 mask: {:?}", self.obj_window_1_area);
960        log::trace!("  OBJ window 2 mask: {:?}", self.obj_window_2_area);
961        log::trace!("  MATH window 1 mask: {:?}", self.math_window_1_area);
962        log::trace!("  MATH window 2 mask: {:?}", self.math_window_2_area);
963    }
964
965    pub fn write_wh0(&mut self, value: u8) {
966        // WH0: Window 1 left position
967        self.window_1_left = value.into();
968        log::trace!("  Window 1 left: {value:02X}");
969    }
970
971    pub fn write_wh1(&mut self, value: u8) {
972        // WH1: Window 1 right position
973        self.window_1_right = value.into();
974        log::trace!("  Window 1 right: {value:02X}");
975    }
976
977    pub fn write_wh2(&mut self, value: u8) {
978        // WH2: Window 2 left position
979        self.window_2_left = value.into();
980        log::trace!("  Window 2 left: {value:02X}");
981    }
982
983    pub fn write_wh3(&mut self, value: u8) {
984        // WH3: Window 2 right position
985        self.window_2_right = value.into();
986        log::trace!("  Window 2 right: {value:02X}");
987    }
988
989    pub fn write_wbglog(&mut self, value: u8) {
990        // WBGLOG: Window BG mask logic
991        for (i, mask_logic) in self.bg_window_mask_logic.iter_mut().enumerate() {
992            *mask_logic = WindowMaskLogic::from_bits(value >> (2 * i));
993        }
994
995        log::trace!("  BG window mask logic: {:?}", self.bg_window_mask_logic);
996    }
997
998    pub fn write_wobjlog(&mut self, value: u8) {
999        // WOBJLOG: Window OBJ/MATH mask logic
1000        self.obj_window_mask_logic = WindowMaskLogic::from_bits(value);
1001        self.math_window_mask_logic = WindowMaskLogic::from_bits(value >> 2);
1002
1003        log::trace!("  OBJ window mask logic: {:?}", self.obj_window_mask_logic);
1004        log::trace!("  MATH window mask logic: {:?}", self.math_window_mask_logic);
1005    }
1006
1007    pub fn write_tmw(&mut self, value: u8) {
1008        // TMW: Window area main screen disable
1009        for (i, bg_disabled) in self.main_bg_disabled_in_window.iter_mut().enumerate() {
1010            *bg_disabled = value.bit(i as u8);
1011        }
1012        self.main_obj_disabled_in_window = value.bit(4);
1013
1014        log::trace!(
1015            "  Main screen BG disabled inside window: {:?}",
1016            self.main_bg_disabled_in_window
1017        );
1018        log::trace!(
1019            "  Main screen OBJ disabled inside window: {}",
1020            self.main_obj_disabled_in_window
1021        );
1022    }
1023
1024    pub fn write_tsw(&mut self, value: u8) {
1025        // TSW: Window area sub screen disable
1026        for (i, bg_disabled) in self.sub_bg_disabled_in_window.iter_mut().enumerate() {
1027            *bg_disabled = value.bit(i as u8);
1028        }
1029        self.sub_obj_disabled_in_window = value.bit(4);
1030
1031        log::trace!("  Sub screen BG disabled inside window: {:?}", self.sub_bg_disabled_in_window);
1032        log::trace!("  Sub screen OBJ disabled inside window: {}", self.sub_obj_disabled_in_window);
1033    }
1034
1035    pub fn write_cgwsel(&mut self, value: u8) {
1036        // CGWSEL: Color math control register 1
1037        self.direct_color_mode_enabled = value.bit(0);
1038        self.sub_bg_obj_enabled = value.bit(1);
1039
1040        self.color_math_enabled = match value & 0x30 {
1041            0x00 => ColorMathEnableMode::Always,
1042            0x10 => ColorMathEnableMode::InsideColorWindow,
1043            0x20 => ColorMathEnableMode::OutsideColorWindow,
1044            0x30 => ColorMathEnableMode::Never,
1045            _ => unreachable!("value & 0x30 is always one of the above values"),
1046        };
1047        self.force_main_screen_black = match value & 0xC0 {
1048            0x00 => ColorMathEnableMode::Never,
1049            0x40 => ColorMathEnableMode::OutsideColorWindow,
1050            0x80 => ColorMathEnableMode::InsideColorWindow,
1051            0xC0 => ColorMathEnableMode::Always,
1052            _ => unreachable!("value & 0xC0 is always one of the above values"),
1053        };
1054
1055        log::trace!("  Direct color mode enabled: {}", self.direct_color_mode_enabled);
1056        log::trace!("  Sub screen BG/OBJ enabled: {}", self.sub_bg_obj_enabled);
1057        log::trace!("  Color math enabled: {:?}", self.color_math_enabled);
1058        log::trace!("  Force main screen black: {:?}", self.force_main_screen_black);
1059    }
1060
1061    pub fn write_cgadsub(&mut self, value: u8) {
1062        // CGADSUB: Color math control register 2
1063        for (i, enabled) in self.bg_color_math_enabled.iter_mut().enumerate() {
1064            *enabled = value.bit(i as u8);
1065        }
1066
1067        self.obj_color_math_enabled = value.bit(4);
1068        self.backdrop_color_math_enabled = value.bit(5);
1069        self.color_math_divide_enabled = value.bit(6);
1070        self.color_math_operation =
1071            if value.bit(7) { ColorMathOperation::Subtract } else { ColorMathOperation::Add };
1072
1073        log::trace!("  Color math operation: {:?}", self.color_math_operation);
1074        log::trace!("  Color math divide: {}", self.color_math_divide_enabled);
1075        log::trace!("  BG color math enabled: {:?}", self.bg_color_math_enabled);
1076        log::trace!("  OBJ color math enabled: {}", self.obj_color_math_enabled);
1077        log::trace!("  Backdrop color math enabled: {}", self.backdrop_color_math_enabled);
1078    }
1079
1080    pub fn write_coldata(&mut self, value: u8) {
1081        // COLDATA: Sub screen backdrop color
1082        let intensity: u16 = (value & 0x1F).into();
1083
1084        let mut sub_backdrop_color = self.sub_backdrop_color;
1085
1086        if value.bit(7) {
1087            // Update B
1088            sub_backdrop_color = (sub_backdrop_color & 0x03FF) | (intensity << 10);
1089        }
1090
1091        if value.bit(6) {
1092            // Update G
1093            sub_backdrop_color = (sub_backdrop_color & 0xFC1F) | (intensity << 5);
1094        }
1095
1096        if value.bit(5) {
1097            // Update R
1098            sub_backdrop_color = (sub_backdrop_color & 0xFFE0) | intensity;
1099        }
1100
1101        self.sub_backdrop_color = sub_backdrop_color;
1102
1103        log::trace!("  Sub screen backdrop color: {sub_backdrop_color:04X}");
1104    }
1105
1106    pub fn write_oamaddl(&mut self, value: u8, in_active_display: bool) {
1107        // OAMADDL: OAM address, low byte
1108        self.oam_address_reload_value.set_lsb(value);
1109
1110        if !in_active_display {
1111            self.reload_oam_address();
1112        }
1113
1114        log::trace!("  OAM address reload value: {:04X}", self.oam_address_reload_value);
1115    }
1116
1117    pub fn write_oamaddh(&mut self, value: u8, in_active_display: bool) {
1118        // OAMADDH: OAM address, high byte
1119        self.oam_address_reload_value.set_msb(value & 0x01);
1120        self.obj_priority_mode = ObjPriorityMode::from_byte(value);
1121
1122        if !in_active_display {
1123            self.reload_oam_address();
1124        }
1125
1126        log::trace!("  OAM address reload value: {:04X}", self.oam_address_reload_value);
1127        log::trace!("  OBJ priority mode: {:?}", self.obj_priority_mode);
1128    }
1129
1130    pub fn write_vmain(&mut self, value: u8) {
1131        // VMAIN: VRAM address increment mode
1132        self.vram_address_increment_step = match value & 0x03 {
1133            0x00 => 1,
1134            0x01 => 32,
1135            0x02 | 0x03 => 128,
1136            _ => unreachable!("value & 0x03 is always <= 0x03"),
1137        };
1138        self.vram_address_translation = VramAddressTranslation::from_byte(value);
1139        self.vram_address_increment_mode = VramIncrementMode::from_byte(value);
1140
1141        log::trace!("  VRAM data port increment step: {}", self.vram_address_increment_step);
1142        log::trace!("  VRAM data port address translation: {:?}", self.vram_address_translation);
1143        log::trace!("  VRAM data port increment on byte: {:?}", self.vram_address_increment_mode);
1144    }
1145
1146    pub fn write_vmaddl(&mut self, value: u8, vram: &Vram) {
1147        // VMADDL: VRAM address, low byte
1148        self.vram_address.set_lsb(value);
1149        self.vram_prefetch_buffer = vram[(self.vram_address & ppu::VRAM_ADDRESS_MASK) as usize];
1150
1151        log::trace!("  VRAM data port address: {:04X}", self.vram_address);
1152    }
1153
1154    pub fn write_vmaddh(&mut self, value: u8, vram: &Vram) {
1155        // VMADDH: VRAM address, high byte
1156        self.vram_address.set_msb(value);
1157        self.vram_prefetch_buffer = vram[(self.vram_address & ppu::VRAM_ADDRESS_MASK) as usize];
1158
1159        log::trace!("  VRAM data port address: {:04X}", self.vram_address);
1160    }
1161
1162    pub fn write_cgadd(&mut self, value: u8) {
1163        // CGADD: CGRAM address
1164        self.cgram_address = value;
1165        self.cgram_flipflop = AccessFlipflop::First;
1166
1167        log::trace!("  CGRAM data port address: {value:02X}");
1168    }
1169
1170    pub fn read_mpyl(&self) -> u8 {
1171        // MPYL: PPU multiply result, low byte
1172        let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r);
1173        mpy_result.low_byte()
1174    }
1175
1176    pub fn read_mpym(&self) -> u8 {
1177        // MPYM: PPU multiply result, middle byte
1178        let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r);
1179        mpy_result.mid_byte()
1180    }
1181
1182    pub fn read_mpyh(&self) -> u8 {
1183        // MPYH: PPU multiply result, high byte
1184        let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r);
1185        mpy_result.high_byte()
1186    }
1187
1188    pub fn read_slhv(&mut self, h_counter: u16, v_counter: u16) {
1189        if self.programmable_joypad_port.bit(7) {
1190            self.latched_h_counter = h_counter;
1191            self.latched_v_counter = v_counter;
1192        }
1193    }
1194
1195    pub fn read_ophct(&mut self, ppu2_open_bus: u8) -> u8 {
1196        // Bits 1-7 of high byte are PPU2 open bus
1197        let value = match self.h_counter_flipflop {
1198            AccessFlipflop::First => self.latched_h_counter.lsb(),
1199            AccessFlipflop::Second => (ppu2_open_bus & 0xFE) | self.latched_h_counter.msb(),
1200        };
1201        self.h_counter_flipflop = self.h_counter_flipflop.toggle();
1202        value
1203    }
1204
1205    pub fn read_opvct(&mut self, ppu2_open_bus: u8) -> u8 {
1206        // Bits 1-7 of high byte are PPU2 open bus
1207        let value = match self.v_counter_flipflop {
1208            AccessFlipflop::First => self.latched_v_counter.lsb(),
1209            AccessFlipflop::Second => (ppu2_open_bus & 0xFE) | self.latched_v_counter.msb(),
1210        };
1211        self.v_counter_flipflop = self.v_counter_flipflop.toggle();
1212        value
1213    }
1214
1215    pub fn reset_hv_counter_flipflops(&mut self) {
1216        self.h_counter_flipflop = AccessFlipflop::First;
1217        self.v_counter_flipflop = AccessFlipflop::First;
1218    }
1219
1220    pub fn update_wrio(&mut self, wrio: u8, h_counter: u16, v_counter: u16) {
1221        if self.programmable_joypad_port.bit(7) & !wrio.bit(7) {
1222            self.latched_h_counter = h_counter;
1223            self.latched_v_counter = v_counter;
1224        }
1225        self.programmable_joypad_port = wrio;
1226    }
1227
1228    pub fn latch_mode_7(&mut self) {
1229        self.mode_7_latched = self.mode_7.clone();
1230    }
1231
1232    pub fn reload_oam_address(&mut self) {
1233        self.oam_address = self.oam_address_reload_value;
1234        self.oam_data_flipflop = AccessFlipflop::First;
1235    }
1236
1237    #[allow(clippy::range_plus_one)]
1238    pub fn is_inside_window_1(&self, pixel: u16) -> bool {
1239        (self.window_1_left..self.window_1_right + 1).contains(&pixel)
1240    }
1241
1242    #[allow(clippy::range_plus_one)]
1243    pub fn is_inside_window_2(&self, pixel: u16) -> bool {
1244        (self.window_2_left..self.window_2_right + 1).contains(&pixel)
1245    }
1246
1247    pub fn bg_in_window(&self, bg: usize, window_x: u16) -> bool {
1248        self.bg_window_mask_logic[bg].apply(
1249            self.bg_window_1_area[bg].to_optional_bool(self.is_inside_window_1(window_x)),
1250            self.bg_window_2_area[bg].to_optional_bool(self.is_inside_window_2(window_x)),
1251        )
1252    }
1253
1254    pub fn obj_in_window(&self, window_x: u16) -> bool {
1255        self.obj_window_mask_logic.apply(
1256            self.obj_window_1_area.to_optional_bool(self.is_inside_window_1(window_x)),
1257            self.obj_window_2_area.to_optional_bool(self.is_inside_window_2(window_x)),
1258        )
1259    }
1260
1261    pub fn in_math_window(&self, window_x: u16) -> bool {
1262        self.math_window_mask_logic.apply(
1263            self.math_window_1_area.to_optional_bool(self.is_inside_window_1(window_x)),
1264            self.math_window_2_area.to_optional_bool(self.is_inside_window_2(window_x)),
1265        )
1266    }
1267
1268    pub fn in_hi_res_mode(&self) -> bool {
1269        self.bg_mode.is_hi_res() || self.pseudo_h_hi_res
1270    }
1271
1272    pub fn color_math_enabled_for_any_layer(&self) -> bool {
1273        self.color_math_enabled != ColorMathEnableMode::Never
1274            && (self.backdrop_color_math_enabled
1275                || self.obj_color_math_enabled
1276                || self.bg_color_math_enabled.iter().any(|&b| b))
1277    }
1278}