2025-02-01 19:05:25 -07:00
|
|
|
#include <ppu.h>
|
|
|
|
#include <lcd.h>
|
|
|
|
#include <bus.h>
|
|
|
|
|
|
|
|
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));
|
2025-02-18 22:39:56 -07:00
|
|
|
if(ppu_get_context()->rendering_window) {
|
|
|
|
sp_x = (ppu_get_context()->fetched_entries[i].x - 8);
|
|
|
|
}
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
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) {
|
2025-02-18 22:39:56 -07:00
|
|
|
color = ((ppu_get_context()->fetched_entries[i].f_pn) ?
|
|
|
|
lcd_get_context()->sp2_colors[hi|lo] : lcd_get_context()->sp1_colors[hi|lo]);
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2025-02-18 22:39:56 -07:00
|
|
|
if(LCDC_OBJ_ENABLE && !ppu_get_context()->debug) {
|
2025-02-01 19:05:25 -07:00
|
|
|
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;
|
|
|
|
|
2025-02-01 20:53:13 -07:00
|
|
|
if (ppu_get_context()->rendering_window) {
|
|
|
|
u8 w_tile_y = ppu_get_context()->window_line / 8;
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
|
2025-02-01 20:53:13 -07:00
|
|
|
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));
|
2025-02-01 19:05:25 -07:00
|
|
|
|
2025-02-01 20:53:13 -07:00
|
|
|
if(LCDC_BGW_DATA_AREA == 0x8800){
|
|
|
|
ppu_get_context()->pfc.bgw_fetch_data[0] += 128;
|
2025-02-01 19:05:25 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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: {
|
2025-02-01 20:53:13 -07:00
|
|
|
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) +
|
2025-02-18 22:39:56 -07:00
|
|
|
(ppu_get_context()->window_line % 8) * 2);
|
2025-02-01 20:53:13 -07:00
|
|
|
} 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);
|
|
|
|
}
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
pipeline_load_sprite_data(0);
|
|
|
|
|
|
|
|
ppu_get_context()->pfc.cur_fetch_state = FS_DATA1;
|
|
|
|
} break;
|
|
|
|
|
|
|
|
case FS_DATA1: {
|
2025-02-01 20:53:13 -07:00
|
|
|
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 +
|
2025-02-18 22:39:56 -07:00
|
|
|
(ppu_get_context()->window_line % 8) * 2);
|
2025-02-01 20:53:13 -07:00
|
|
|
} 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);
|
|
|
|
}
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-01 20:53:13 -07:00
|
|
|
void pipeline_fifo_reset() {
|
|
|
|
while(ppu_get_context()->pfc.pixel_fifo.size) {
|
|
|
|
pixel_fifo_pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
ppu_get_context()->pfc.pixel_fifo.head = 0;
|
|
|
|
}
|
|
|
|
|
2025-02-19 10:20:45 -07:00
|
|
|
//Rate at wich the screen interpolates between the old and new colors
|
|
|
|
//Used to simulate the slow response time of the GB's LCD
|
|
|
|
//A value of zero will always use the exiting screen color, while a 1 will always use the new color
|
|
|
|
//any value in between will interpolate that percentage between the two
|
|
|
|
#define INTERPOLATION_RATE 0.5
|
|
|
|
|
|
|
|
u32 interpolate(u32 first, u32 second) {
|
|
|
|
u32 final = 0;
|
|
|
|
for(int c = 0; c < 4; c++) {
|
|
|
|
u8 c1 = (first >> (c*8)) & 0xFF;
|
|
|
|
u8 c2 = (second >> (c*8)) & 0xFF;
|
|
|
|
u8 cf = (u8)(((((float)c2/(float)0xFF) - ((float)c1/(float)0xFF)) * INTERPOLATION_RATE + ((float)c1/(float)0xFF)) * 0xFF);
|
|
|
|
final |= cf << (c*8);
|
|
|
|
}
|
|
|
|
return final;
|
|
|
|
}
|
|
|
|
|
2025-02-01 19:05:25 -07:00
|
|
|
void pipeline_push_pixel() {
|
|
|
|
if (ppu_get_context()->pfc.pixel_fifo.size > 8) {
|
|
|
|
u32 pixel_data = pixel_fifo_pop();
|
|
|
|
|
2025-02-18 22:39:56 -07:00
|
|
|
if(ppu_get_context()->pfc.line_x+7 >= lcd_get_context()->win_x && lcd_get_context()->ly >= lcd_get_context()->win_y && !ppu_get_context()->rendering_window && window_visible()) {
|
2025-02-01 20:53:13 -07:00
|
|
|
ppu_get_context()->rendering_window = true;
|
|
|
|
pipeline_fifo_reset();
|
|
|
|
ppu_get_context()->pfc.cur_fetch_state = FS_TILE;
|
2025-02-18 22:39:56 -07:00
|
|
|
ppu_get_context()->pfc.fetch_x = ppu_get_context()->pfc.line_x;
|
|
|
|
ppu_get_context()->pfc.fifo_x = ppu_get_context()->pfc.line_x;
|
|
|
|
ppu_get_context()->pfc.pushed_x = ppu_get_context()->pfc.line_x;
|
2025-02-01 20:53:13 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(ppu_get_context()->rendering_window) {
|
|
|
|
ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x +
|
2025-02-19 10:20:45 -07:00
|
|
|
(lcd_get_context()->ly * XRES)] = interpolate(ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x +
|
|
|
|
(lcd_get_context()->ly * XRES)], pixel_data);
|
2025-02-01 19:05:25 -07:00
|
|
|
|
|
|
|
ppu_get_context()->pfc.pushed_x++;
|
2025-02-01 20:53:13 -07:00
|
|
|
}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 +
|
2025-02-19 10:20:45 -07:00
|
|
|
(lcd_get_context()->ly * XRES)] = interpolate(ppu_get_context()->video_buffer[ppu_get_context()->pfc.pushed_x +
|
|
|
|
(lcd_get_context()->ly * XRES)], pixel_data);
|
2025-02-01 20:53:13 -07:00
|
|
|
|
|
|
|
ppu_get_context()->pfc.pushed_x++;
|
|
|
|
}
|
2025-02-01 19:05:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|