diff --git a/Core/display.c b/Core/display.c index 0573b9cb..aa6e6815 100755 --- a/Core/display.c +++ b/Core/display.c @@ -75,7 +75,8 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) bg_enabled = false; } } - if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] && x + 7 >= gb->io_registers[GB_IO_WX] && gb->current_window_line != 0xFF) { + + if (window_enabled(gb) && y >= gb->io_registers[GB_IO_WY] + gb->wy_diff && x + 7 >= gb->io_registers[GB_IO_WX]) { in_window = true; } @@ -128,7 +129,7 @@ static uint32_t get_pixel(GB_gameboy_t *gb, uint8_t x, uint8_t y) if (in_window) { x -= gb->io_registers[GB_IO_WX] - 7; // Todo: This value is probably latched - y = gb->current_window_line; + y -= gb->io_registers[GB_IO_WY] + gb->wy_diff; } else { x += gb->effective_scx; @@ -282,7 +283,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) } /* Reset window rendering state */ - gb->current_window_line = 0xFF; + gb->wy_diff = 0; + gb->window_disabled_while_active = false; return; } @@ -330,7 +332,8 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) /* Reset window rendering state */ - gb->current_window_line = 0xFF; + gb->wy_diff = 0; + gb->window_disabled_while_active = false; } /* Entered VBlank state, update STAT and IF */ @@ -418,9 +421,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) if (position_in_line == stat_delay) { gb->io_registers[GB_IO_STAT] &= ~3; gb->io_registers[GB_IO_STAT] |= 2; - if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH >= gb->io_registers[GB_IO_WY]) { - gb->current_window_line++; - } } else if (position_in_line == 0 && gb->display_cycles != 0) { should_compare_ly = gb->is_cgb; @@ -431,11 +431,6 @@ static void update_display_state(GB_gameboy_t *gb, uint8_t cycles) gb->io_registers[GB_IO_STAT] |= 3; gb->effective_scx = gb->io_registers[GB_IO_SCX]; gb->previous_lcdc_x = - (gb->effective_scx & 0x7); - - /* Todo: This works on both 007 - The World Is Not Enough and Donkey Kong 94, but should be verified better */ - if (window_enabled(gb) && gb->display_cycles / LINE_LENGTH == gb->io_registers[GB_IO_WY] && gb->current_window_line == 0xFF) { - gb->current_window_line = 0; - } } else if (position_in_line == MODE2_LENGTH + MODE3_LENGTH + stat_delay + scx_delay) { gb->io_registers[GB_IO_STAT] &= ~3; @@ -800,3 +795,32 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h } return count; } + +/* Called when a write might enable or disable the window */ +void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value) +{ + bool before = window_enabled(gb); + gb->io_registers[addr] = value; + bool after = window_enabled(gb); + + if (before != after && gb->display_cycles < LINES * LINE_LENGTH) { + /* Window was disabled or enabled outside of vblank */ + uint8_t current_line = gb->display_cycles / LINE_LENGTH; + if (current_line >= gb->io_registers[GB_IO_WY]) { + if (after) { + if (!gb->window_disabled_while_active) { + /* Window was turned on for the first time this frame while LY > WY, + should start window in the next line */ + gb->wy_diff = current_line + 1 - gb->io_registers[GB_IO_WY]; + } + else { + gb->wy_diff += current_line; + } + } + else { + gb->wy_diff -= current_line; + gb->window_disabled_while_active = true; + } + } + } +} diff --git a/Core/display.h b/Core/display.h index 39677872..a6229232 100644 --- a/Core/display.h +++ b/Core/display.h @@ -5,6 +5,7 @@ #ifdef GB_INTERNAL void GB_display_run(GB_gameboy_t *gb, uint8_t cycles); void GB_palette_changed(GB_gameboy_t *gb, bool background_palette, uint8_t index); +void GB_window_related_write(GB_gameboy_t *gb, uint8_t addr, uint8_t value); #endif typedef enum { diff --git a/Core/gb.h b/Core/gb.h index 976fea01..660186a3 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -366,7 +366,7 @@ struct GB_gameboy_internal_s { int16_t previous_lcdc_x; bool stat_interrupt_line; uint8_t effective_scx; - uint8_t current_window_line; + uint8_t wy_diff; /* The LCDC will skip the first frame it renders after turning it on. On the CGB, a frame is not skipped if the previous frame was skipped as well. See https://www.reddit.com/r/EmuDev/comments/6exyxu/ */ @@ -382,6 +382,7 @@ struct GB_gameboy_internal_s { bool vram_read_blocked; bool oam_write_blocked; bool vram_write_blocked; + bool window_disabled_while_active; ); /* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */ diff --git a/Core/memory.c b/Core/memory.c index 21db2493..31354b31 100644 --- a/Core/memory.c +++ b/Core/memory.c @@ -406,10 +406,14 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) return; } + /* Todo: Clean this code up: use a function table and move relevant code to display.c and timing.c + (APU read and writes are already at apu.c) */ if (addr < 0xFF80) { /* Hardware registers */ switch (addr & 0xFF) { - + case GB_IO_WX: + GB_window_related_write(gb, addr & 0xFF, value); + break; case GB_IO_SCX: case GB_IO_IF: case GB_IO_SCY: @@ -418,7 +422,6 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) case GB_IO_OBP0: case GB_IO_OBP1: case GB_IO_WY: - case GB_IO_WX: case GB_IO_SB: case GB_IO_DMG_EMULATION_INDICATION: case GB_IO_UNKNOWN2: @@ -461,7 +464,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value) /* Sync after turning off LCD */ GB_timing_sync(gb); } - gb->io_registers[GB_IO_LCDC] = value; + /* Writing to LCDC might enable to disable the window, so we write it via GB_window_related_write */ + GB_window_related_write(gb, addr & 0xFF, value); return; case GB_IO_STAT: