OAM search and OAM timing in mode 3
This commit is contained in:
parent
476133abd0
commit
3d1c8b50c4
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
/* FIFO functions */
|
/* FIFO functions */
|
||||||
|
|
||||||
static inline unsigned fifo_size(GB_fifo_t *fifo)
|
static inline unsigned __attribute__((unused)) fifo_size(GB_fifo_t *fifo)
|
||||||
{
|
{
|
||||||
return (fifo->write_end - fifo->read_end) & 0xF;
|
return (fifo->write_end - fifo->read_end) & 0xF;
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ typedef struct __attribute__((packed)) {
|
|||||||
uint8_t x;
|
uint8_t x;
|
||||||
uint8_t tile;
|
uint8_t tile;
|
||||||
uint8_t flags;
|
uint8_t flags;
|
||||||
} GB_sprite_t;
|
} GB_object_t;
|
||||||
|
|
||||||
static bool window_enabled(GB_gameboy_t *gb)
|
static bool window_enabled(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
@ -97,7 +97,7 @@ static uint32_t __attribute__((unused)) get_pixel(GB_gameboy_t *gb, uint8_t x, u
|
|||||||
uint8_t sprite_palette = 0;
|
uint8_t sprite_palette = 0;
|
||||||
uint16_t tile_address = 0;
|
uint16_t tile_address = 0;
|
||||||
uint8_t background_pixel = 0, sprite_pixel = 0;
|
uint8_t background_pixel = 0, sprite_pixel = 0;
|
||||||
GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam;
|
GB_object_t *sprite = (GB_object_t *) &gb->oam;
|
||||||
uint8_t sprites_in_line = 0;
|
uint8_t sprites_in_line = 0;
|
||||||
bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
|
bool lcd_8_16_mode = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
|
||||||
bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0;
|
bool sprites_enabled = (gb->io_registers[GB_IO_LCDC] & 2) != 0;
|
||||||
@ -407,6 +407,40 @@ void GB_lcd_off(GB_gameboy_t *gb)
|
|||||||
gb->ly_for_comparison = 0;
|
gb->ly_for_comparison = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void search_oam(GB_gameboy_t *gb)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
We have very little means to test how the OAM search timing actually
|
||||||
|
works so we currently implement it atomically.
|
||||||
|
|
||||||
|
This is enough for most cases except (All are TODOs):
|
||||||
|
- The OAM bug on the DMG (Not emulated at all)
|
||||||
|
- Changing object height via LCDC during mode 2
|
||||||
|
- What about changing during mode 3?
|
||||||
|
- Enabling and disabling sprites during mode 2
|
||||||
|
- Does this flag actually do anything in mode 2? Or only during mode 3?
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This reverse sorts the visible objects by location and priority */
|
||||||
|
|
||||||
|
GB_object_t *objects = (GB_object_t *) &gb->oam;
|
||||||
|
bool height_16 = (gb->io_registers[GB_IO_LCDC] & 4) != 0;
|
||||||
|
gb->n_visible_objs = 0;
|
||||||
|
for (uint8_t i = 0; i < 40; i++) {
|
||||||
|
signed y = objects[i].y - 16;
|
||||||
|
if (y <= gb->current_line && y + (height_16? 16 : 8) > gb->current_line) {
|
||||||
|
unsigned j = 0;
|
||||||
|
for (; j < gb->n_visible_objs; j++) {
|
||||||
|
if (objects[gb->visible_objs[j]].x <= objects[i].x) break;
|
||||||
|
}
|
||||||
|
memmove(gb->visible_objs + j + 1, gb->visible_objs + j, gb->n_visible_objs - j);
|
||||||
|
gb->visible_objs[j] = i;
|
||||||
|
gb->n_visible_objs++;
|
||||||
|
if (gb->n_visible_objs == 10) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void render_pixel_if_possible(GB_gameboy_t *gb)
|
static void render_pixel_if_possible(GB_gameboy_t *gb)
|
||||||
{
|
{
|
||||||
GB_fifo_item_t *fifo_item = NULL;
|
GB_fifo_item_t *fifo_item = NULL;
|
||||||
@ -444,8 +478,10 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
|
|||||||
}
|
}
|
||||||
gb->position_in_line++;
|
gb->position_in_line++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
||||||
{
|
{
|
||||||
|
GB_object_t *objects = (GB_object_t *) &gb->oam;
|
||||||
|
|
||||||
GB_STATE_MACHINE(gb, display, cycles, 2) {
|
GB_STATE_MACHINE(gb, display, cycles, 2) {
|
||||||
GB_STATE(gb, display, 1);
|
GB_STATE(gb, display, 1);
|
||||||
@ -465,9 +501,10 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
GB_STATE(gb, display, 15);
|
GB_STATE(gb, display, 15);
|
||||||
GB_STATE(gb, display, 16);
|
GB_STATE(gb, display, 16);
|
||||||
GB_STATE(gb, display, 17);
|
GB_STATE(gb, display, 17);
|
||||||
|
GB_STATE(gb, display, 19);
|
||||||
GB_STATE(gb, display, 20);
|
GB_STATE(gb, display, 20);
|
||||||
|
GB_STATE(gb, display, 21);
|
||||||
|
GB_STATE(gb, display, 22);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
if (!(gb->io_registers[GB_IO_LCDC] & 0x80)) {
|
||||||
@ -529,6 +566,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->io_registers[GB_IO_STAT] |= 2;
|
gb->io_registers[GB_IO_STAT] |= 2;
|
||||||
GB_STAT_update(gb);
|
GB_STAT_update(gb);
|
||||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||||
|
search_oam(gb);
|
||||||
}
|
}
|
||||||
GB_SLEEP(gb, display, 7, 1);
|
GB_SLEEP(gb, display, 7, 1);
|
||||||
|
|
||||||
@ -556,8 +594,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
|
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
|
||||||
gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
|
gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
|
||||||
|
|
||||||
gb->cycles_for_line += 6;
|
gb->cycles_for_line += 5;
|
||||||
GB_SLEEP(gb, display, 10, 6);
|
GB_SLEEP(gb, display, 10, 5);
|
||||||
|
|
||||||
/* The actual rendering cycle */
|
/* The actual rendering cycle */
|
||||||
gb->fetcher_divisor = false;
|
gb->fetcher_divisor = false;
|
||||||
@ -565,6 +603,26 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->fifo_paused = true;
|
gb->fifo_paused = true;
|
||||||
gb->in_window = false;
|
gb->in_window = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
/* Handle objects */
|
||||||
|
while (gb->n_visible_objs != 0 &&
|
||||||
|
(gb->io_registers[GB_IO_LCDC] & 2) != 0 &&
|
||||||
|
objects[gb->visible_objs[gb->n_visible_objs - 1]].x == (uint8_t)(gb->position_in_line + 8)) {
|
||||||
|
|
||||||
|
if (!gb->fetching_objects) {
|
||||||
|
/* Penalty for interrupting the fetcher */
|
||||||
|
uint8_t penalty = (uint8_t[]){5, 4, 3, 2, 1, 0, 0, 0}[gb->fetcher_state * 2 + gb->fetcher_divisor];
|
||||||
|
gb->cycles_for_line += penalty;
|
||||||
|
GB_SLEEP(gb, display, 19, penalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
gb->fetching_objects = true;
|
||||||
|
gb->cycles_for_line += 6;
|
||||||
|
GB_SLEEP(gb, display, 20, 6);
|
||||||
|
gb->n_visible_objs--;
|
||||||
|
}
|
||||||
|
gb->fetching_objects = false;
|
||||||
|
|
||||||
|
|
||||||
/* Handle window */
|
/* Handle window */
|
||||||
/* Todo: Timing not verified by test ROM */
|
/* Todo: Timing not verified by test ROM */
|
||||||
if (!gb->in_window && window_enabled(gb) &&
|
if (!gb->in_window && window_enabled(gb) &&
|
||||||
@ -628,7 +686,9 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->fifo_paused = false;
|
gb->fifo_paused = false;
|
||||||
}
|
}
|
||||||
if (gb->position_in_line == 160) break;
|
if (gb->position_in_line == 160) break;
|
||||||
else if (gb->position_in_line == 159) {
|
gb->cycles_for_line++;
|
||||||
|
GB_SLEEP(gb, display, 21, 1);
|
||||||
|
}
|
||||||
gb->io_registers[GB_IO_STAT] &= ~3;
|
gb->io_registers[GB_IO_STAT] &= ~3;
|
||||||
gb->oam_read_blocked = false;
|
gb->oam_read_blocked = false;
|
||||||
gb->vram_read_blocked = false;
|
gb->vram_read_blocked = false;
|
||||||
@ -638,10 +698,8 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
|
|||||||
gb->hdma_on = true;
|
gb->hdma_on = true;
|
||||||
gb->hdma_cycles = 0;
|
gb->hdma_cycles = 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
gb->cycles_for_line++;
|
gb->cycles_for_line++;
|
||||||
GB_SLEEP(gb, display, 20, 1);
|
GB_SLEEP(gb, display, 22, 1);
|
||||||
}
|
|
||||||
GB_STAT_update(gb);
|
GB_STAT_update(gb);
|
||||||
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
|
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
|
||||||
}
|
}
|
||||||
@ -836,7 +894,7 @@ uint8_t GB_get_oam_info(GB_gameboy_t *gb, GB_oam_info_t *dest, uint8_t *sprite_h
|
|||||||
*sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
|
*sprite_height = (gb->io_registers[GB_IO_LCDC] & 4) ? 16:8;
|
||||||
uint8_t oam_to_dest_index[40] = {0,};
|
uint8_t oam_to_dest_index[40] = {0,};
|
||||||
for (unsigned y = 0; y < LINES; y++) {
|
for (unsigned y = 0; y < LINES; y++) {
|
||||||
GB_sprite_t *sprite = (GB_sprite_t *) &gb->oam;
|
GB_object_t *sprite = (GB_object_t *) &gb->oam;
|
||||||
uint8_t sprites_in_line = 0;
|
uint8_t sprites_in_line = 0;
|
||||||
for (uint8_t i = 0; i < 40; i++, sprite++) {
|
for (uint8_t i = 0; i < 40; i++, sprite++) {
|
||||||
int sprite_y = sprite->y - 16;
|
int sprite_y = sprite->y - 16;
|
||||||
|
@ -421,6 +421,9 @@ struct GB_gameboy_internal_s {
|
|||||||
bool fetcher_divisor; // The fetcher runs at 2MHz
|
bool fetcher_divisor; // The fetcher runs at 2MHz
|
||||||
bool fifo_paused;
|
bool fifo_paused;
|
||||||
bool in_window;
|
bool in_window;
|
||||||
|
uint8_t visible_objs[10];
|
||||||
|
uint8_t n_visible_objs;
|
||||||
|
bool fetching_objects;
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
|
/* Unsaved data. This includes all pointers, as well as everything that shouldn't be on a save state */
|
||||||
|
Loading…
Reference in New Issue
Block a user