#include #include #include bool window_visible() { return LCDC_WIN_ENABLE && lcd_get_context()->win_x >= 0 && lcd_get_context()->win_x <= 166 && lcd_get_context()->win_y >= 0 && lcd_get_context()->win_y < YRES; } void pixel_fifo_push(u32 value) { fifo_entry *next = malloc(sizeof(fifo_entry)); next->next = NULL; next->value = value; if(!ppu_get_context()->pfc.pixel_fifo.head) { //first entry ppu_get_context()->pfc.pixel_fifo.head = ppu_get_context()->pfc.pixel_fifo.tail = next; } else { ppu_get_context()->pfc.pixel_fifo.tail->next = next; ppu_get_context()->pfc.pixel_fifo.tail = next; } ppu_get_context()->pfc.pixel_fifo.size++; } u32 pixel_fifo_pop() { if(ppu_get_context()->pfc.pixel_fifo.size <= 0) { fprintf(stderr, "ERR in pixel FIFO\n"); } fifo_entry *popped = ppu_get_context()->pfc.pixel_fifo.head; ppu_get_context()->pfc.pixel_fifo.head = popped->next; ppu_get_context()->pfc.pixel_fifo.size--; u32 val = popped->value; free(popped); return val; } u32 fetch_sprite_pixels(int bit, u32 color, u8 bg_color) { for (int i = 0; i < ppu_get_context()->fetched_entry_count; i++) { int sp_x = (ppu_get_context()->fetched_entries[i].x - 8) + ((lcd_get_context()->scroll_x % 8)); if (sp_x + 8 < ppu_get_context()->pfc.fifo_x) { //past this pixel already continue; } int offset = ppu_get_context()->pfc.fifo_x - sp_x; if (offset < 0 || offset > 7) { continue; } bit = 7 - offset; if (ppu_get_context()->fetched_entries[i].f_x_flip) { bit = offset; } u8 lo = !!(ppu_get_context()->pfc.fetch_entry_data[i * 2] & (1 << bit)); u8 hi = !!(ppu_get_context()->pfc.fetch_entry_data[(i * 2)+1] & (1 << bit)) << 1; bool bg_priority = ppu_get_context()->fetched_entries[i].f_bgp; if (!(hi|lo)) { //transparant continue; } if(!bg_priority || bg_color == 0) { color = (ppu_get_context()->fetched_entries[i].f_pn) ? lcd_get_context()->sp2_colors[hi|lo] : lcd_get_context()->sp1_colors[hi|lo]; if(hi | lo) { break; } } } return color; } bool pipeline_fifo_add() { if(ppu_get_context()->pfc.pixel_fifo.size > 8) { //fifo is full! return false; } int x = ppu_get_context()->pfc.fetch_x - (8 - lcd_get_context()->scroll_x % 8); for (int i=0; i<8; i++) { int bit = 7 - i; u8 lo = !!(ppu_get_context()->pfc.bgw_fetch_data[1] & (1 << bit)); u8 hi = !!(ppu_get_context()->pfc.bgw_fetch_data[2] & (1 << bit)) << 1; u32 color = lcd_get_context()->bg_colors[hi | lo]; if (!LCDC_BGW_ENABLE) { color = lcd_get_context()->bg_colors[0]; } if(LCDC_OBJ_ENABLE) { color = fetch_sprite_pixels(bit, color, hi | lo); } if (x >= 0) { pixel_fifo_push(color); ppu_get_context()->pfc.fifo_x++; } } return true; } void pipeline_load_sprite_tile() { oam_line_entry *le = ppu_get_context()->line_sprites; while(le) { int sp_x = (le->entry.x - 8) + (lcd_get_context()->scroll_x % 8); if ((sp_x >= ppu_get_context()->pfc.fetch_x && sp_x < ppu_get_context()->pfc.fetch_x + 8) || ((sp_x + 8) >= ppu_get_context()->pfc.fetch_x && (sp_x + 8) < ppu_get_context()->pfc.fetch_x + 8)) { ppu_get_context()->fetched_entries[ppu_get_context()->fetched_entry_count++] = le->entry; } le = le->next; if (!le || ppu_get_context()->fetched_entry_count >= 3) { //max checking 3 sprites on pixels break; } } } void pipeline_load_sprite_data(u8 offset) { int cur_y = lcd_get_context()->ly; u8 sprite_height = LCDC_OBJ_HEIGHT; for (int i = 0; i < ppu_get_context()->fetched_entry_count; i++) { u8 ty = ((cur_y + 16) - ppu_get_context()->fetched_entries[i].y) * 2; if (ppu_get_context()->fetched_entries[i].f_y_flip) { ty = ((sprite_height * 2) - 2) - ty; } u8 tile_index = ppu_get_context()->fetched_entries[i].tile; if (sprite_height == 16) { tile_index &= ~(1); } ppu_get_context()->pfc.fetch_entry_data[(i * 2) + offset] = bus_read(0x8000 + (tile_index * 16) + ty + offset); } } void pipeline_load_window_tile() { if(!window_visible()) return; u8 window_y = lcd_get_context()->win_y; if (ppu_get_context()->rendering_window) { u8 w_tile_y = ppu_get_context()->window_line / 8; ppu_get_context()->pfc.bgw_fetch_data[0] = bus_read(LCDC_WIN_MAP_AREA + ((ppu_get_context()->pfc.fetch_x + 7 - lcd_get_context()->win_x) / 8) + (w_tile_y * 32)); if(LCDC_BGW_DATA_AREA == 0x8800){ ppu_get_context()->pfc.bgw_fetch_data[0] += 128; } } } void pipeline_fetch() { switch(ppu_get_context()->pfc.cur_fetch_state) { case FS_TILE: { ppu_get_context()->fetched_entry_count = 0; if (LCDC_BGW_ENABLE) { ppu_get_context()->pfc.bgw_fetch_data[0] = bus_read(LCDC_BG_MAP_AREA + (ppu_get_context()->pfc.map_x / 8) + (((ppu_get_context()->pfc.map_y / 8)) * 32)); if(LCDC_BGW_DATA_AREA == 0x8800) { ppu_get_context()->pfc.bgw_fetch_data[0] += 128; } pipeline_load_window_tile(); } if(LCDC_OBJ_ENABLE && ppu_get_context()->line_sprite_count) { pipeline_load_sprite_tile(); } ppu_get_context()->pfc.cur_fetch_state = FS_DATA0; ppu_get_context()->pfc.fetch_x += 8; } break; case FS_DATA0: { if(ppu_get_context()->rendering_window){ ppu_get_context()->pfc.bgw_fetch_data[1] = bus_read(LCDC_BGW_DATA_AREA + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + (lcd_get_context()->ly % 8) * 2); } else { ppu_get_context()->pfc.bgw_fetch_data[1] = bus_read(LCDC_BGW_DATA_AREA + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + ppu_get_context()->pfc.tile_y); } pipeline_load_sprite_data(0); ppu_get_context()->pfc.cur_fetch_state = FS_DATA1; } break; case FS_DATA1: { if(ppu_get_context()->rendering_window){ ppu_get_context()->pfc.bgw_fetch_data[2] = bus_read(LCDC_BGW_DATA_AREA + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + 1 + (lcd_get_context()->ly % 8) * 2); } else { ppu_get_context()->pfc.bgw_fetch_data[2] = bus_read(LCDC_BGW_DATA_AREA + (ppu_get_context()->pfc.bgw_fetch_data[0] * 16) + ppu_get_context()->pfc.tile_y + 1); } pipeline_load_sprite_data(1); ppu_get_context()->pfc.cur_fetch_state = FS_IDLE; } break; case FS_IDLE: { ppu_get_context()->pfc.cur_fetch_state = FS_PUSH; } break; case FS_PUSH: { if (pipeline_fifo_add()) { ppu_get_context()->pfc.cur_fetch_state = FS_TILE; } } break; } } void pipeline_fifo_reset() { while(ppu_get_context()->pfc.pixel_fifo.size) { pixel_fifo_pop(); } ppu_get_context()->pfc.pixel_fifo.head = 0; } void pipeline_push_pixel() { if (ppu_get_context()->pfc.pixel_fifo.size > 8) { u32 pixel_data = pixel_fifo_pop(); if(ppu_get_context()->pfc.pushed_x+7 >= lcd_get_context()->win_x && lcd_get_context()->ly >= lcd_get_context()->win_y && !ppu_get_context()->rendering_window && window_visible()) { ppu_get_context()->rendering_window = true; pipeline_fifo_reset(); ppu_get_context()->pfc.cur_fetch_state = FS_TILE; printf("Window X: %d\n", lcd_get_context()->win_x); ppu_get_context()->pfc.fetch_x = 0; ppu_get_context()->pfc.fifo_x = 0; return; } if(ppu_get_context()->rendering_window) { ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x + (lcd_get_context()->ly * XRES)] = pixel_data; ppu_get_context()->pfc.pushed_x++; }else { if(ppu_get_context()->pfc.line_x >= (lcd_get_context()->scroll_x % 8)) { ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x + (lcd_get_context()->ly * XRES)] = pixel_data; ppu_get_context()->pfc.pushed_x++; } } ppu_get_context()->pfc.line_x++; } } void pipeline_process() { ppu_get_context()->pfc.map_y = (lcd_get_context()->ly + lcd_get_context()->scroll_y); ppu_get_context()->pfc.map_x = (ppu_get_context()->pfc.fetch_x + lcd_get_context()->scroll_x); ppu_get_context()->pfc.tile_y = ((lcd_get_context()->ly + lcd_get_context()->scroll_y) % 8) * 2; if(!(ppu_get_context()->line_ticks & 1)) { pipeline_fetch(); } pipeline_push_pixel(); }