#include "ppu.h" #include "mem.h" #include "interrupts.h" #include "display.h" namespace ppu { struct oam_entry_t { uint8_t y, x, tile, attr; }; uint32_t t_states_total = 70224; uint32_t t_states_per_scanline = 456; uint32_t vsync_lines = 10; uint32_t t_screen = 0; uint8_t pixels[160*144]; uint16_t dots_in_scanline = 0; uint8_t line_buffer[160]; uint8_t *_LCDC = nullptr; uint8_t *_STAT = nullptr; uint8_t *_SCY = nullptr; uint8_t *_SCX = nullptr; uint8_t *_LY = nullptr; uint8_t *_LYC = nullptr; uint8_t *_BGP = nullptr; uint8_t *_WY = nullptr; uint8_t *_WX = nullptr; uint8_t *OBP = nullptr; oam_entry_t *oam = nullptr; uint8_t *vram = nullptr; #define LCDC (*_LCDC) #define STAT (*_STAT) #define SCY (*_SCY) #define SCX (*_SCX) #define LY (*_LY) #define LYC (*_LYC) #define BGP (*_BGP) #define WY (*_WY) #define WX (*_WX) #define IF 0xff0f bool last_interrupt_lcd_state = false; void init() { _LCDC = mem::rawHram(0xff40); _STAT = mem::rawHram(0xff41); _SCY = mem::rawHram(0xff42); _SCX = mem::rawHram(0xff43); _LY = mem::rawHram(0xff44); _LYC = mem::rawHram(0xff45); _BGP = mem::rawHram(0xff47); _WY = mem::rawHram(0xff4a); _WX = mem::rawHram(0xff4b); OBP = mem::rawHram(0xff48); oam = (oam_entry_t*)mem::rawHram(0xfe00); vram = mem::rawVram(); } void fill_line_buffer_bkg() { if ((LCDC & 0x1) == 0) { for (int i=0; i<160; ++i) line_buffer[i]=0; return; } const uint16_t ty = uint8_t(SCY+LY) >> 3; const uint8_t ly = uint8_t(SCY+LY) & 0x7; uint16_t tx = SCX >> 3; uint8_t ox = SCX & 0x7; uint16_t base_tilemap_address = LCDC&0x8 ? 0x1c00 : 0x1800; int pi = 0; while(true) { uint16_t tilemap_address = base_tilemap_address + tx + (ty<<5); uint16_t tile = vram[tilemap_address]; uint16_t base_tile_address = 0x0000; if ( ((LCDC&0x10)==0) && (tile<128) ) base_tile_address = 0x1000; uint16_t tile_address = base_tile_address + (tile<<4) + (ly*2); uint8_t a = vram[tile_address]; uint8_t b = vram[tile_address+1]; for (int i=0; i<8; ++i) { if (ox==0) { uint8_t index = (a&0x80 ? 1 : 0) + (b&0x80 ? 2 : 0 ); line_buffer[pi++] = (BGP >> (index*2)) & 0x3; } else { ox--; } a=a<<1; b=b<<1; if (pi==160) return; } tx = (tx+1)&0x1f; } } void fill_line_buffer_win() { if ((LCDC & 0x21) != 0x21) return; if (LY> 3; const uint8_t ly = uint8_t(LY-WY) & 0x7; uint8_t ox = WX<7 ? 7-WX : 0; uint16_t tx = 0; uint16_t base_tilemap_address = LCDC&0x40 ? 0x1c00 : 0x1800; int pi = WX<7 ? 0 : WX-7; while(true) { uint16_t tilemap_address = base_tilemap_address + tx + (ty<<5); uint16_t tile = vram[tilemap_address]; uint16_t base_tile_address = 0x0000; if ( ((LCDC&0x10)==0) && (tile<128) ) base_tile_address = 0x1000; uint16_t tile_address = base_tile_address + (tile<<4) + (ly*2); uint8_t a = vram[tile_address]; uint8_t b = vram[tile_address+1]; for (int i=0; i<8; ++i) { if (ox==0) { uint8_t index = (a&0x80 ? 1 : 0) + (b&0x80 ? 2 : 0 ); line_buffer[pi++] = (BGP >> (index*2)) & 0x3; } else { ox--; } a=a<<1; b=b<<1; if (pi==160) return; } tx = (tx+1)&0x1f; } } void fill_line_buffer_obj() { if ((LCDC & 0x2) == 0) return; const uint8_t height = (LCDC & 0x4) ? 16 : 8; uint8_t obj_list[10] = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }; int num_obj_found=0; int obj=0; // Revisem els 40 posibles sprites o fins a trobar 10... while (obj<40 && num_obj_found<10) { // Si el sprite està tocant la linea actual l'afegim a la llista if ( (LY+16 >= (oam[obj].y)) && (LY+16 < (oam[obj].y+height)) ) { obj_list[num_obj_found]=obj; num_obj_found++; } obj++; } // Pintem els sprites en el buffer de sprites uint8_t pixels[160]; uint8_t x_pos[160]; for (int i=0;i<160;++i) { pixels[i] = 255; x_pos[i] = 255; } obj=0; while (obj_list[obj] != 255) { oam_entry_t *o = &oam[obj_list[obj]]; const uint8_t ly = uint8_t(LY-o->y) & 0xf; uint16_t tile = height==8 ? o->tile : o->tile & 0xFE; // si es dos tiles de alt, el primer sempre comença en numero parell uint8_t yflip = o->attr&0x40 ? (height-1)-ly : ly; // està invertit verticalment? uint16_t tile_address = 0x0000 + (tile<<4) + (yflip*2); uint8_t a = vram[tile_address]; uint8_t b = vram[tile_address+1]; for (int i=0; i<8; ++i) { // Per a cada pixel de la linea del tile... if (o->x+i>=168) break; // Si ja estem fora de la pantalla per la dreta, eixim del bucle if (o->x+i>=8) { // Si està dins de la pantalla... if (x_pos[o->x+i-8]>o->x) { // Si te una x menor que la que tenía //uint8_t xflip = o->attr&0x20 ? 8 : 0; // està invertit horitzontalment? const uint8_t ppos = 1 << ( o->attr&0x20 ? i : 7-i); const uint8_t val = (a&ppos ? 1 : 0) + (b&ppos ? 2 : 0 ); // agafem el pixel que toca if (val) { // Si el pixel no es transparent... const uint8_t color = (OBP[(o->attr>>4)&1] >> (val*2)) & 0x3; pixels[o->x+i-8] = color | o->attr&0x80;; // el pintem al buffer, amb el flag de prioritat respecte al BKG x_pos[o->x+i-8] = o->x; // I apuntem la seua x per a comparar després } } } } obj++; } // Per últim, volquem els pixels que toque al buffer de linea for (int i=0; i<160; ++i) { if (pixels[i]!=255) { // si el pixel no es transparent... if ( !(pixels[i]&0x80) || (line_buffer[i]==0) ) { // Si te prioritat o el color de fondo es 0... line_buffer[i] = pixels[i]&0x03; // pintem el pixel (llevant el flag de prioritat) } } } } void refresh(const uint32_t dt, const bool full) { if ((LCDC&0x80)==0) return; for (int i=0;i=t_states_total) { t_screen-=t_states_total; display::redraw(); } } } const uint8_t *getpixels() { return pixels; } }