1use crate::ppu::{BgAffineLatch, State};
2use bincode::{Decode, Encode};
3use jgenesis_common::define_bit_enum;
4use jgenesis_common::num::{GetBit, U16Ext};
5use std::{array, iter};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
8pub enum BgMode {
9    #[default]
10    Zero, // 4 text mode BGs
11    One,   // 2 text mode BGs + 1 affine BG
12    Two,   // 2 affine BGs
13    Three, // 15bpp bitmap, one frame buffer
14    Four,  // 8bpp bitmap, two frame buffers
15    Five,  // 15bpp bitmap, two frame buffers (reduced resolution)
16    Invalid(u8),
17}
18
19impl BgMode {
20    fn to_bits(self) -> u8 {
21        match self {
22            Self::Zero => 0,
23            Self::One => 1,
24            Self::Two => 2,
25            Self::Three => 3,
26            Self::Four => 4,
27            Self::Five => 5,
28            Self::Invalid(bits) => bits,
29        }
30    }
31
32    fn from_bits(bits: u16) -> Self {
33        match bits & 7 {
34            0 => Self::Zero,
35            1 => Self::One,
36            2 => Self::Two,
37            3 => Self::Three,
38            4 => Self::Four,
39            5 => Self::Five,
40            b @ (6 | 7) => Self::Invalid(b as u8),
41            _ => unreachable!("value & 7 is always <= 7"),
42        }
43    }
44
45    #[allow(clippy::manual_range_patterns)]
46    pub fn bg_active_in_mode(self, bg: usize) -> bool {
47        matches!(
48            (self, bg),
49            (BgMode::Zero, _)
50                | (BgMode::One, 0 | 1 | 2)
51                | (BgMode::Two, 2 | 3)
52                | (BgMode::Three | BgMode::Four | BgMode::Five, 2)
53        )
54    }
55
56    pub fn is_bitmap(self) -> bool {
57        matches!(self, Self::Three | Self::Four | Self::Five)
58    }
59}
60
61define_bit_enum!(BitmapFrameBuffer, [Zero, One]);
62
63impl BitmapFrameBuffer {
64    pub fn vram_address(self) -> u32 {
65        match self {
66            Self::Zero => 0x00000,
67            Self::One => 0x0A000,
68        }
69    }
70}
71
72define_bit_enum!(ObjVramMapDimensions, [Two, One]);
73define_bit_enum!(BitsPerPixel, [Four, Eight]);
74
75impl BitsPerPixel {
76    pub fn tile_size_bytes(self) -> u32 {
77        match self {
78            Self::Four => 32,
79            Self::Eight => 64,
80        }
81    }
82}
83
84define_bit_enum!(AffineOverflowBehavior, [Transparent, Wrap]);
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
87pub enum ScreenSize {
88    #[default]
89    Zero = 0, // 256x256 text / 128x128 affine
90    One = 1,   // 512x256 text / 256x256 affine
91    Two = 2,   // 256x512 text / 512x512 affine
92    Three = 3, // 512x512 text / 1024x1024 affine
93}
94
95impl ScreenSize {
96    fn from_bits(bits: u16) -> Self {
97        match bits & 3 {
98            0 => Self::Zero,
99            1 => Self::One,
100            2 => Self::Two,
101            3 => Self::Three,
102            _ => unreachable!("value & 3 is always <= 3"),
103        }
104    }
105
106    pub fn text_width_pixels(self) -> u32 {
107        match self {
108            Self::Zero | Self::Two => 256,
109            Self::One | Self::Three => 512,
110        }
111    }
112
113    pub fn text_width_tiles(self) -> u32 {
114        self.text_width_pixels() / 8
115    }
116
117    pub fn text_height_pixels(self) -> u32 {
118        match self {
119            Self::Zero | Self::One => 256,
120            Self::Two | Self::Three => 512,
121        }
122    }
123
124    pub fn text_height_tiles(self) -> u32 {
125        self.text_height_pixels() / 8
126    }
127
128    pub fn affine_dimension_tiles(self) -> u32 {
129        match self {
130            Self::Zero => 16,
131            Self::One => 32,
132            Self::Two => 64,
133            Self::Three => 128,
134        }
135    }
136}
137
138#[derive(Debug, Clone, Copy, Default, Encode, Decode)]
139pub struct BgControl {
140    pub priority: u8,
141    pub tile_data_addr: u32,
142    pub mosaic: bool,
143    pub bpp: BitsPerPixel,
144    pub tile_map_addr: u32,
145    pub affine_overflow: AffineOverflowBehavior,
146    pub size: ScreenSize,
147    // Bits 4-5: Unused but R/W
148    pub tile_data_extra_bits: u16,
149}
150
151impl BgControl {
152    fn read(&self) -> u16 {
153        u16::from(self.priority)
154            | (((self.tile_data_addr as u16) >> 14) << 2)
155            | (self.tile_data_extra_bits << 4)
156            | (u16::from(self.mosaic) << 6)
157            | ((self.bpp as u16) << 7)
158            | (((self.tile_map_addr as u16) >> 11) << 8)
159            | ((self.affine_overflow as u16) << 13)
160            | ((self.size as u16) << 14)
161    }
162
163    fn write(&mut self, value: u16, supports_affine: bool) {
164        self.priority = (value & 3) as u8;
165
166        let tile_data_addr_16kb = (value >> 2) & 3;
167        self.tile_data_addr = (tile_data_addr_16kb << 14).into();
168
169        self.tile_data_extra_bits = (value >> 4) & 3;
170
171        self.mosaic = value.bit(6);
172        self.bpp = BitsPerPixel::from_bit(value.bit(7));
173
174        let tile_map_addr_2kb = (value >> 8) & 0x1F;
175        self.tile_map_addr = (tile_map_addr_2kb << 11).into();
176
177        if supports_affine {
178            self.affine_overflow = AffineOverflowBehavior::from_bit(value.bit(13));
179        }
180
181        self.size = ScreenSize::from_bits(value >> 14);
182    }
183}
184
185#[derive(Debug, Clone, Copy, Encode, Decode)]
186pub struct BgAffineParameters {
187    // BG2X / BG3X
188    pub reference_x: i32,
189    // BG2Y / BG3Y
190    pub reference_y: i32,
191    // BG2PA / BG3PA
192    pub a: i32,
193    // BG2PB / BG3PB
194    pub b: i32,
195    // BG2PC / BG3PC
196    pub c: i32,
197    // BG2PD / BG3PD
198    pub d: i32,
199}
200
201impl Default for BgAffineParameters {
202    fn default() -> Self {
203        Self { reference_x: 0, reference_y: 0, a: 1 << 8, b: 0, c: 0, d: 1 << 8 }
204    }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
208pub enum BlendMode {
209    #[default]
210    None = 0,
211    AlphaBlending = 1,
212    BrightnessIncrease = 2,
213    BrightnessDecrease = 3,
214}
215
216impl BlendMode {
217    fn from_bits(bits: u16) -> Self {
218        match bits & 3 {
219            0 => Self::None,
220            1 => Self::AlphaBlending,
221            2 => Self::BrightnessIncrease,
222            3 => Self::BrightnessDecrease,
223            _ => unreachable!("value & 3 is always <= 3"),
224        }
225    }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum Window {
230    Inside0,
231    Inside1,
232    InsideObj,
233    Outside,
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub struct WindowEnabled {
238    pub bg: [bool; 4],
239    pub obj: bool,
240    pub blend: bool,
241}
242
243impl WindowEnabled {
244    pub const ALL: Self = Self { bg: [true; 4], obj: true, blend: true };
245}
246
247#[derive(Debug, Clone, Encode, Decode)]
248pub struct Registers {
249    // DISPCNT (Display control)
250    pub bg_mode: BgMode,
251    pub bitmap_frame_buffer: BitmapFrameBuffer,
252    pub oam_free_during_hblank: bool,
253    pub obj_vram_map_dimensions: ObjVramMapDimensions,
254    pub forced_blanking: bool,
255    pub bg_enabled: [bool; 4],
256    pub obj_enabled: bool,
257    pub window_enabled: [bool; 2],
258    pub obj_window_enabled: bool,
259    // Undocumented green swap register ($4000002)
260    pub green_swap: bool,
261    // DISPSTAT (Display status)
262    pub vblank_irq_enabled: bool,
263    pub hblank_irq_enabled: bool,
264    pub v_counter_irq_enabled: bool,
265    pub v_counter_match: u8,
266    // BGxCNT (BG0-3 control)
267    pub bg_control: [BgControl; 4],
268    // BGxHOFS (BG0-3 horizontal offset)
269    pub bg_h_scroll: [u32; 4],
270    // BGxVOFS (BG0-3 vertical offset)
271    pub bg_v_scroll: [u32; 4],
272    // BG2/3 affine registers
273    pub bg_affine_parameters: [BgAffineParameters; 2],
274    // WINxH (Window horizontal coordinates)
275    pub window_x1: [u32; 2],
276    pub window_x2: [u32; 2],
277    // WINxV (Window vertical coordinates)
278    pub window_y1: [u32; 2],
279    pub window_y2: [u32; 2],
280    // WININ (Window inside control)
281    pub window_in_bg_enabled: [[bool; 4]; 2],
282    pub window_in_obj_enabled: [bool; 2],
283    pub window_in_blend_enabled: [bool; 2],
284    // WINOUT (Window outside control)
285    pub window_out_bg_enabled: [bool; 4],
286    pub window_out_obj_enabled: bool,
287    pub window_out_blend_enabled: bool,
288    pub obj_window_bg_enabled: [bool; 4],
289    pub obj_window_obj_enabled: bool,
290    pub obj_window_blend_enabled: bool,
291    // MOSAIC (Mosaic size)
292    pub bg_mosaic_h_size: u8,
293    pub bg_mosaic_v_size: u8,
294    pub obj_mosaic_h_size: u8,
295    pub obj_mosaic_v_size: u8,
296    // BLDCNT (Blending control)
297    pub bg_blend_1st_target: [bool; 4],
298    pub obj_blend_1st_target: bool,
299    pub backdrop_blend_1st_target: bool,
300    pub blend_mode: BlendMode,
301    pub bg_blend_2nd_target: [bool; 4],
302    pub obj_blend_2nd_target: bool,
303    pub backdrop_blend_2nd_target: bool,
304    // BLDALPHA (Alpha blending coefficients)
305    pub blend_alpha_a: u8,
306    pub blend_alpha_b: u8,
307    // BLDY (Blending brightness coefficient)
308    pub blend_brightness: u8,
309}
310
311impl Registers {
312    pub fn new() -> Self {
313        Self {
314            bg_mode: BgMode::default(),
315            bitmap_frame_buffer: BitmapFrameBuffer::default(),
316            oam_free_during_hblank: false,
317            obj_vram_map_dimensions: ObjVramMapDimensions::default(),
318            forced_blanking: true,
319            bg_enabled: [false; 4],
320            obj_enabled: false,
321            window_enabled: [false; 2],
322            obj_window_enabled: false,
323            green_swap: false,
324            vblank_irq_enabled: false,
325            hblank_irq_enabled: false,
326            v_counter_irq_enabled: false,
327            v_counter_match: 0,
328            bg_control: [BgControl::default(); 4],
329            bg_h_scroll: [0; 4],
330            bg_v_scroll: [0; 4],
331            bg_affine_parameters: array::from_fn(|_| BgAffineParameters::default()),
332            window_x1: [0; 2],
333            window_x2: [0; 2],
334            window_y1: [0; 2],
335            window_y2: [0; 2],
336            window_in_bg_enabled: [[false; 4]; 2],
337            window_in_obj_enabled: [false; 2],
338            window_in_blend_enabled: [false; 2],
339            window_out_bg_enabled: [false; 4],
340            window_out_obj_enabled: false,
341            window_out_blend_enabled: false,
342            obj_window_bg_enabled: [false; 4],
343            obj_window_obj_enabled: false,
344            obj_window_blend_enabled: false,
345            bg_mosaic_h_size: 0,
346            bg_mosaic_v_size: 0,
347            obj_mosaic_h_size: 0,
348            obj_mosaic_v_size: 0,
349            bg_blend_1st_target: [false; 4],
350            obj_blend_1st_target: false,
351            backdrop_blend_1st_target: false,
352            blend_mode: BlendMode::default(),
353            bg_blend_2nd_target: [false; 4],
354            obj_blend_2nd_target: false,
355            backdrop_blend_2nd_target: false,
356            blend_alpha_a: 0,
357            blend_alpha_b: 0,
358            blend_brightness: 0,
359        }
360    }
361
362    // $4000000: DISPCNT (Display control)
363    pub fn write_dispcnt(&mut self, value: u16, state: &mut State) {
364        let prev_bg_enabled = self.bg_enabled;
365        let prev_obj_enabled = self.obj_enabled;
366        let prev_forced_blanking = self.forced_blanking;
367
368        self.bg_mode = BgMode::from_bits(value);
369        self.bitmap_frame_buffer = BitmapFrameBuffer::from_bit(value.bit(4));
370        self.oam_free_during_hblank = value.bit(5);
371        self.obj_vram_map_dimensions = ObjVramMapDimensions::from_bit(value.bit(6));
372        self.forced_blanking = value.bit(7);
373        self.bg_enabled = array::from_fn(|i| value.bit((8 + i) as u8));
374        self.obj_enabled = value.bit(12);
375        self.window_enabled = [value.bit(13), value.bit(14)];
376        self.obj_window_enabled = value.bit(15);
377
378        // Somewhat arbitrary threshold - produces closest-to-correct results in mgba test
379        // suite's video layout 2 test and Lady Sia
380        // Pretty sure there's more going on here than just a dot threshold though
381        //
382        // Enable latency also shouldn't apply to bitmap modes (e.g. Pinball Tycoon)
383        let layer_enable_latency = if state.dot < 30 { 2 } else { 3 };
384
385        for (i, (prev_enabled, enabled)) in iter::zip(prev_bg_enabled, self.bg_enabled).enumerate()
386        {
387            if !prev_enabled && enabled {
388                state.bg_enabled_latency[i] = layer_enable_latency;
389            }
390        }
391
392        if !prev_obj_enabled && self.obj_enabled {
393            state.obj_enabled_latency = layer_enable_latency;
394        }
395
396        if prev_forced_blanking && !self.forced_blanking {
397            state.forced_blanking_latency = layer_enable_latency;
398        }
399
400        log::debug!("DISPCNT write (line {} dot {}): {value:04X}", state.scanline, state.dot);
401        log::debug!("  BG mode: {:?}", self.bg_mode);
402        log::debug!("  Bitmap frame buffer: {:?}", self.bitmap_frame_buffer);
403        log::debug!("  OAM accessible during HBlank: {}", self.oam_free_during_hblank);
404        log::debug!("  OBJ VRAM map dimensions: {:?}", self.obj_vram_map_dimensions);
405        log::debug!("  Forced blanking enabled: {}", self.forced_blanking);
406        log::debug!("  BGs enabled: {:?}", self.bg_enabled);
407        log::debug!("  OBJ enabled: {}", self.obj_enabled);
408        log::debug!("  Window 0 enabled: {}", self.window_enabled[0]);
409        log::debug!("  Window 1 enabled: {}", self.window_enabled[1]);
410        log::debug!("  OBJ window enabled: {}", self.obj_window_enabled);
411    }
412
413    // $4000000: DISPCNT (Display control)
414    pub fn read_dispcnt(&self) -> u16 {
415        let bg_enabled_bits = bool_array_to_bits(self.bg_enabled);
416
417        u16::from(self.bg_mode.to_bits())
418            | ((self.bitmap_frame_buffer as u16) << 4)
419            | (u16::from(self.oam_free_during_hblank) << 5)
420            | ((self.obj_vram_map_dimensions as u16) << 6)
421            | (u16::from(self.forced_blanking) << 7)
422            | (bg_enabled_bits << 8)
423            | (u16::from(self.obj_enabled) << 12)
424            | (u16::from(self.window_enabled[0]) << 13)
425            | (u16::from(self.window_enabled[1]) << 14)
426            | (u16::from(self.obj_window_enabled) << 15)
427    }
428
429    pub fn write_green_swap(&mut self, value: u16) {
430        self.green_swap = value.bit(0);
431
432        log::debug!("Green swap write: {value:04X}");
433    }
434
435    pub fn read_green_swap(&self) -> u16 {
436        self.green_swap.into()
437    }
438
439    // $4000004: DISPSTAT (Display status)
440    pub fn write_dispstat(&mut self, value: u16) {
441        self.vblank_irq_enabled = value.bit(3);
442        self.hblank_irq_enabled = value.bit(4);
443        self.v_counter_irq_enabled = value.bit(5);
444        self.v_counter_match = value.msb();
445
446        log::debug!("DISPSTAT write: {value:04X}");
447        log::debug!("  VBlank IRQs enabled: {}", self.vblank_irq_enabled);
448        log::debug!("  HBlank IRQs enabled: {}", self.hblank_irq_enabled);
449        log::debug!("  V counter IRQs enabled: {}", self.v_counter_irq_enabled);
450        log::debug!("  V counter match target: {}", self.v_counter_match);
451    }
452
453    // $4000008-$400000E: BG0CNT/BG1CNT/BG2CNT/BG3CNT (BG0-3 control)
454    pub fn read_bgcnt(&self, index: usize) -> u16 {
455        self.bg_control[index].read()
456    }
457
458    // $4000008-$400000E: BG0CNT/BG1CNT/BG2CNT/BG3CNT (BG0-3 control)
459    pub fn write_bgcnt(&mut self, index: usize, value: u16) {
460        let supports_affine = index >= 2;
461        self.bg_control[index].write(value, supports_affine);
462
463        log::debug!("BG{index}CNT write: {value:04X}");
464        log::debug!("  Priority: {}", self.bg_control[index].priority);
465        log::debug!("  Tile data base address: {:04X}", self.bg_control[index].tile_data_addr);
466        log::debug!("  Mosaic enabled: {}", self.bg_control[index].mosaic);
467        log::debug!("  Bits per pixel: {:?}", self.bg_control[index].bpp);
468        log::debug!("  Tile map base address: {:04X}", self.bg_control[index].tile_map_addr);
469        log::debug!("  Affine overflow behavior: {:?}", self.bg_control[index].affine_overflow);
470        log::debug!("  Screen size: {}", self.bg_control[index].size as u8);
471    }
472
473    // $4000010/$4000014/$4000018/$400001C: BG1HOFS/BG2HOFS/BG3HOFS/BG4HOFS (BG0-3 horizontal offset)
474    pub fn write_bghofs(&mut self, index: usize, value: u16) {
475        self.bg_h_scroll[index] = (value & 0x1FF).into();
476
477        log::debug!("BG{index}HOFS write: {value:04X}");
478    }
479
480    // $4000012/$4000016/$400001A/$400001E: BG1VOFS/BG2VOFS/BG3VOFS/BG4VOFS (BG0-3 vertical offset)
481    pub fn write_bgvofs(&mut self, index: usize, value: u16) {
482        self.bg_v_scroll[index] = (value & 0x1FF).into();
483
484        log::debug!("BG{index}VOFS write: {value:04X}");
485    }
486
487    // $4000020-$400003E: BG2/3 affine parameter registers
488    pub fn write_bg_affine_register(
489        &mut self,
490        address: u32,
491        value: u16,
492        latch: &mut BgAffineLatch,
493    ) {
494        fn cast_parameter(parameter: u16) -> i32 {
495            // Parameters are signed 16-bit; sign extend to 32-bit
496            i32::from(parameter as i16)
497        }
498
499        let bg_idx = ((address >> 4) & 1) as usize;
500        let affine_parameters = &mut self.bg_affine_parameters[bg_idx];
501
502        match address & 0xE {
503            0x0 => {
504                // BG2PA / BG3PA
505                affine_parameters.a = cast_parameter(value);
506                log::debug!("BG{}PA write: {value:04X}", bg_idx + 2);
507            }
508            0x2 => {
509                // BG2PB // BG3PB
510                affine_parameters.b = cast_parameter(value);
511                log::debug!("BG{}PB write: {value:04X}", bg_idx + 2);
512            }
513            0x4 => {
514                // BG2PC // BG3PC
515                affine_parameters.c = cast_parameter(value);
516                log::debug!("BG{}PC write: {value:04X}", bg_idx + 2);
517            }
518            0x6 => {
519                // BG2PD // BG3PD
520                affine_parameters.d = cast_parameter(value);
521                log::debug!("BG{}PD write: {value:04X}", bg_idx + 2);
522            }
523            0x8 => {
524                // BG2X_L / BG3X_L
525                affine_parameters.reference_x =
526                    (affine_parameters.reference_x & !0xFFFF) | i32::from(value);
527
528                // Update X latch on register writes
529                latch.x_written[bg_idx] = true;
530
531                log::debug!("BG{}X_L write: {value:04X}", bg_idx + 2);
532                log::debug!("  Reference point X: {:07X}", affine_parameters.reference_x);
533            }
534            0xA => {
535                // BG2X_H / BG3X_H
536                // Truncate highest 4 bits (sign extend)
537                affine_parameters.reference_x =
538                    (affine_parameters.reference_x & 0xFFFF) | ((i32::from(value) << 20) >> 4);
539
540                // Update X latch on register writes
541                latch.x_written[bg_idx] = true;
542
543                log::debug!("BG{}X_H write: {value:04X}", bg_idx + 2);
544                log::debug!("  Reference point X: {:07X}", affine_parameters.reference_x);
545            }
546            0xC => {
547                // BG2Y_L / BG3Y_L
548                affine_parameters.reference_y =
549                    (affine_parameters.reference_y & !0xFFFF) | i32::from(value);
550
551                // Update Y latch on register writes
552                latch.y_written[bg_idx] = true;
553
554                log::debug!("BG{}Y_L write: {value:04X}", bg_idx + 2);
555                log::debug!("  Reference point Y: {:07X}", affine_parameters.reference_y);
556            }
557            0xE => {
558                // BG2Y_H / BG3Y_H
559                // Truncate highest 4 bits (sign extend)
560                affine_parameters.reference_y =
561                    (affine_parameters.reference_y & 0xFFFF) | ((i32::from(value) << 20) >> 4);
562
563                // Update Y latch on register writes
564                latch.y_written[bg_idx] = true;
565
566                log::debug!("BG{}X_H write: {value:04X}", bg_idx + 2);
567                log::debug!("  Reference point X: {:07X}", affine_parameters.reference_y);
568            }
569            _ => unreachable!("value & 0xE is always one of the above 8 values"),
570        }
571    }
572
573    pub fn read_bg_affine_register(&self, address: u32) -> u16 {
574        let bg_idx = ((address >> 4) & 1) as usize;
575        let affine_parameters = &self.bg_affine_parameters[bg_idx];
576
577        match address & 0xE {
578            0x0 => affine_parameters.a as u16,
579            0x2 => affine_parameters.b as u16,
580            0x4 => affine_parameters.c as u16,
581            0x6 => affine_parameters.d as u16,
582            0x8 => affine_parameters.reference_x as u16,
583            0xA => (affine_parameters.reference_x >> 16) as u16,
584            0xC => affine_parameters.reference_y as u16,
585            0xE => (affine_parameters.reference_y >> 16) as u16,
586            _ => unreachable!("value & 0xE is always one of the above values"),
587        }
588    }
589
590    // $4000040/$4000042: WIN0H/WIN1H (Window 0/1 horizontal coordinates)
591    pub fn write_winh(&mut self, window: usize, value: u16) {
592        [self.window_x1[window], self.window_x2[window]] = value.to_be_bytes().map(u32::from);
593
594        log::debug!("WIN{window}H write: {value:04X}");
595        log::debug!("  X1: {}", self.window_x1[window]);
596        log::debug!("  X2: {}", self.window_x2[window]);
597    }
598
599    pub fn write_winh_low(&mut self, window: usize, value: u8) {
600        self.window_x2[window] = value.into();
601
602        log::debug!("WIN{window}H_L write: {value:02X}");
603    }
604
605    pub fn write_winh_high(&mut self, window: usize, value: u8) {
606        self.window_x1[window] = value.into();
607
608        log::debug!("WIN{window}H_H write: {value:02X}");
609    }
610
611    // $4000044/$4000046: WIN0V/WIN1V (Window 0/1 vertical coordinates)
612    pub fn write_winv(&mut self, window: usize, value: u16) {
613        [self.window_y1[window], self.window_y2[window]] = value.to_be_bytes().map(u32::from);
614
615        log::debug!("WIN{window}V write: {value:04X}");
616        log::debug!("  Y1: {}", self.window_y1[window]);
617        log::debug!("  Y2: {}", self.window_y2[window]);
618    }
619
620    pub fn write_winv_low(&mut self, window: usize, value: u8) {
621        self.window_y2[window] = value.into();
622
623        log::debug!("WIN{window}V_L write: {value:02X}");
624    }
625
626    pub fn write_winv_high(&mut self, window: usize, value: u8) {
627        self.window_y1[window] = value.into();
628
629        log::debug!("WIN{window}V_H write: {value:02X}");
630    }
631
632    // $4000048: WININ (Window inside control)
633    pub fn read_winin(&self) -> u16 {
634        let win0_in_bg_enabled = bool_array_to_bits(self.window_in_bg_enabled[0]);
635        let win1_in_bg_enabled = bool_array_to_bits(self.window_in_bg_enabled[1]);
636
637        win0_in_bg_enabled
638            | (u16::from(self.window_in_obj_enabled[0]) << 4)
639            | (u16::from(self.window_in_blend_enabled[0]) << 5)
640            | (win1_in_bg_enabled << 8)
641            | (u16::from(self.window_in_obj_enabled[1]) << 12)
642            | (u16::from(self.window_in_blend_enabled[1]) << 13)
643    }
644
645    // $4000048: WININ (Window inside control)
646    pub fn write_winin(&mut self, value: u16) {
647        self.window_in_bg_enabled[0] = array::from_fn(|i| value.bit(i as u8));
648        self.window_in_obj_enabled[0] = value.bit(4);
649        self.window_in_blend_enabled[0] = value.bit(5);
650        self.window_in_bg_enabled[1] = array::from_fn(|i| value.bit((8 + i) as u8));
651        self.window_in_obj_enabled[1] = value.bit(12);
652        self.window_in_blend_enabled[1] = value.bit(13);
653
654        log::debug!("WININ write: {value:04X}");
655        log::debug!("  Window 0 BG enabled: {:?}", self.window_in_bg_enabled[0]);
656        log::debug!("  Window 0 OBJ enabled: {}", self.window_in_obj_enabled[0]);
657        log::debug!("  Window 0 blending enabled: {}", self.window_in_blend_enabled[0]);
658        log::debug!("  Window 1 BG enabled: {:?}", self.window_in_bg_enabled[1]);
659        log::debug!("  Window 1 OBJ enabled: {}", self.window_in_obj_enabled[1]);
660        log::debug!("  Window 1 blending enabled: {}", self.window_in_blend_enabled[1]);
661    }
662
663    // $400004A: WINOUT (Window outside control)
664    pub fn read_winout(&self) -> u16 {
665        let window_out_bg_enabled = bool_array_to_bits(self.window_out_bg_enabled);
666        let obj_window_bg_enabled = bool_array_to_bits(self.obj_window_bg_enabled);
667
668        window_out_bg_enabled
669            | (u16::from(self.window_out_obj_enabled) << 4)
670            | (u16::from(self.window_out_blend_enabled) << 5)
671            | (obj_window_bg_enabled << 8)
672            | (u16::from(self.obj_window_obj_enabled) << 12)
673            | (u16::from(self.obj_window_blend_enabled) << 13)
674    }
675
676    // $400004A: WINOUT (Window outside control)
677    pub fn write_winout(&mut self, value: u16) {
678        self.window_out_bg_enabled = array::from_fn(|i| value.bit(i as u8));
679        self.window_out_obj_enabled = value.bit(4);
680        self.window_out_blend_enabled = value.bit(5);
681        self.obj_window_bg_enabled = array::from_fn(|i| value.bit((8 + i) as u8));
682        self.obj_window_obj_enabled = value.bit(12);
683        self.obj_window_blend_enabled = value.bit(13);
684
685        log::debug!("WINOUT write: {value:04X}");
686        log::debug!("  Window outside BG enabled: {:?}", self.window_out_bg_enabled);
687        log::debug!("  Window outside OBJ enabled: {}", self.window_out_obj_enabled);
688        log::debug!("  Window outside blending enabled: {}", self.window_out_blend_enabled);
689        log::debug!("  OBJ window BG enabled: {:?}", self.obj_window_bg_enabled);
690        log::debug!("  OBJ window OBJ enabled: {}", self.obj_window_obj_enabled);
691        log::debug!("  OBJ window blending enabled: {}", self.obj_window_blend_enabled);
692    }
693
694    // $4000004C: MOSAIC (Mosaic size)
695    pub fn write_mosaic(&mut self, value: u16) {
696        log::debug!("MOSAIC write: {value:04X}");
697
698        let [bg_mosaic, obj_mosaic] = value.to_le_bytes();
699        self.write_bg_mosaic(bg_mosaic);
700        self.write_obj_mosaic(obj_mosaic);
701    }
702
703    pub fn write_bg_mosaic(&mut self, value: u8) {
704        self.bg_mosaic_h_size = value & 0xF;
705        self.bg_mosaic_v_size = value >> 4;
706
707        log::debug!("MOSAIC low write: {value:02X}");
708        log::debug!("  BG H size: {}", self.bg_mosaic_h_size);
709        log::debug!("  BG V size: {}", self.bg_mosaic_v_size);
710    }
711
712    pub fn write_obj_mosaic(&mut self, value: u8) {
713        self.obj_mosaic_h_size = value & 0xF;
714        self.obj_mosaic_v_size = value >> 4;
715
716        log::debug!("MOSAIC high write: {value:02X}");
717        log::debug!("  OBJ H size: {}", self.obj_mosaic_h_size);
718        log::debug!("  OBJ V size: {}", self.obj_mosaic_v_size);
719    }
720
721    // $4000050: BLDCNT (Blending control)
722    pub fn read_bldcnt(&self) -> u16 {
723        let bg_1st_target = bool_array_to_bits(self.bg_blend_1st_target);
724        let bg_2nd_target = bool_array_to_bits(self.bg_blend_2nd_target);
725
726        bg_1st_target
727            | (u16::from(self.obj_blend_1st_target) << 4)
728            | (u16::from(self.backdrop_blend_1st_target) << 5)
729            | ((self.blend_mode as u16) << 6)
730            | (bg_2nd_target << 8)
731            | (u16::from(self.obj_blend_2nd_target) << 12)
732            | (u16::from(self.backdrop_blend_2nd_target) << 13)
733    }
734
735    // $4000050: BLDCNT (Blending control)
736    pub fn write_bldcnt(&mut self, value: u16) {
737        self.bg_blend_1st_target = array::from_fn(|i| value.bit(i as u8));
738        self.obj_blend_1st_target = value.bit(4);
739        self.backdrop_blend_1st_target = value.bit(5);
740        self.blend_mode = BlendMode::from_bits(value >> 6);
741        self.bg_blend_2nd_target = array::from_fn(|i| value.bit((8 + i) as u8));
742        self.obj_blend_2nd_target = value.bit(12);
743        self.backdrop_blend_2nd_target = value.bit(13);
744
745        log::debug!("BLDCNT write: {value:04X}");
746        log::debug!("  Blend mode: {:?}", self.blend_mode);
747        log::debug!("  BG 1st target: {:?}", self.bg_blend_1st_target);
748        log::debug!("  OBJ 1st target: {:?}", self.obj_blend_1st_target);
749        log::debug!("  Backdrop 1st target: {:?}", self.backdrop_blend_1st_target);
750        log::debug!("  BG 2nd target: {:?}", self.bg_blend_2nd_target);
751        log::debug!("  OBJ 2nd target: {}", self.obj_blend_2nd_target);
752        log::debug!("  Backdrop 2nd target: {}", self.backdrop_blend_2nd_target);
753    }
754
755    // $4000052: BLDALPHA (Alpha blending coefficients)
756    pub fn read_bldalpha(&self) -> u16 {
757        u16::from_le_bytes([self.blend_alpha_a, self.blend_alpha_b])
758    }
759
760    // $4000052: BLDALPHA (Alpha blending coefficients)
761    pub fn write_bldalpha(&mut self, value: u16) {
762        self.blend_alpha_a = (value & 0x1F) as u8;
763        self.blend_alpha_b = ((value >> 8) & 0x1F) as u8;
764
765        log::debug!("BLDALPHA write: {value:04X}");
766        log::debug!("  A: {}", self.blend_alpha_a);
767        log::debug!("  B: {}", self.blend_alpha_b);
768    }
769
770    // $4000054: BLDY (Blending brightness coefficient)
771    pub fn write_bldy(&mut self, value: u16) {
772        self.blend_brightness = (value & 0x1F) as u8;
773
774        log::debug!("BLDY write: {value:04X} (coefficient = {})", self.blend_brightness);
775    }
776
777    pub fn window_layers_enabled(&self, window: Window) -> WindowEnabled {
778        match window {
779            Window::Inside0 => WindowEnabled {
780                bg: self.window_in_bg_enabled[0],
781                obj: self.window_in_obj_enabled[0],
782                blend: self.window_in_blend_enabled[0],
783            },
784            Window::Inside1 => WindowEnabled {
785                bg: self.window_in_bg_enabled[1],
786                obj: self.window_in_obj_enabled[1],
787                blend: self.window_in_blend_enabled[1],
788            },
789            Window::InsideObj => WindowEnabled {
790                bg: self.obj_window_bg_enabled,
791                obj: self.obj_window_obj_enabled,
792                blend: self.obj_window_blend_enabled,
793            },
794            Window::Outside => WindowEnabled {
795                bg: self.window_out_bg_enabled,
796                obj: self.window_out_obj_enabled,
797                blend: self.window_out_blend_enabled,
798            },
799        }
800    }
801}
802
803fn bool_array_to_bits(arr: [bool; 4]) -> u16 {
804    (0..4).map(|i| u16::from(arr[i]) << i).reduce(|a, b| a | b).unwrap()
805}