283 lines
9.7 KiB
C++
283 lines
9.7 KiB
C++
#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<WY) return;
|
|
const uint16_t ty = uint8_t(LY-WY) >> 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<dt;++i)
|
|
{
|
|
// Açò va volcant els pixels del line_buffer en pantalla
|
|
if ( (STAT&0x3)==3) {
|
|
uint16_t current_pixel = dots_in_scanline-80;
|
|
if (current_pixel<160) {
|
|
pixels[current_pixel+LY*160] = line_buffer[current_pixel];
|
|
}
|
|
}
|
|
|
|
// gestió de en quin dot i linea estem, i tot el que ha de passar
|
|
//uint8_t interrupts = 0x00;
|
|
bool current_interrupt_lcd_state = false;
|
|
dots_in_scanline++;
|
|
if ( (dots_in_scanline==80) && (LY<144) )
|
|
{
|
|
STAT = (STAT & 0xFC) | 0x3; // Set mode 3
|
|
}
|
|
else if ( (dots_in_scanline==252) && (LY<144) )
|
|
{
|
|
STAT = (STAT & 0xFC); // Set mode 0
|
|
if (STAT&0x08) current_interrupt_lcd_state = true; // interrupts |= INTERRUPT_LCD;
|
|
}
|
|
else if (dots_in_scanline==456)
|
|
{
|
|
dots_in_scanline = 0;
|
|
LY++;
|
|
if (LY==154) LY=0;
|
|
if (LY==144)
|
|
{
|
|
// Set vblank interrupt
|
|
mem::writeMem(IF, mem::readMem(IF) | INTERRUPT_VBLANK);
|
|
STAT = (STAT & 0xFC) | 0x01; // Set mode 1
|
|
//interrupts |= INTERRUPT_VBLANK;
|
|
if (STAT&0x10) current_interrupt_lcd_state = true; //interrupts |= INTERRUPT_LCD;
|
|
}
|
|
else
|
|
{
|
|
if (LY<144)
|
|
{
|
|
STAT = (STAT & 0xFC) | 0x02; // Set mode 2
|
|
if (STAT&0x20) current_interrupt_lcd_state = true; // interrupts |= INTERRUPT_LCD;
|
|
fill_line_buffer_bkg();
|
|
fill_line_buffer_win();
|
|
fill_line_buffer_obj();
|
|
}
|
|
}
|
|
if (LY==LYC)
|
|
{
|
|
STAT = (STAT & 0xFB) | 0x04;
|
|
if (STAT&0x40) current_interrupt_lcd_state = true; // interrupts |= INTERRUPT_LCD;
|
|
}
|
|
else
|
|
{
|
|
STAT = (STAT & 0xFB);
|
|
}
|
|
}
|
|
|
|
if (!last_interrupt_lcd_state && current_interrupt_lcd_state)
|
|
{
|
|
mem::writeMem(IF, mem::readMem(IF) | INTERRUPT_LCD);
|
|
//sm83::interrupt(interrupts);
|
|
}
|
|
last_interrupt_lcd_state = current_interrupt_lcd_state;
|
|
|
|
t_screen++;
|
|
if (t_screen>=t_states_total)
|
|
{
|
|
t_screen-=t_states_total;
|
|
display::redraw();
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint8_t *getpixels()
|
|
{
|
|
return pixels;
|
|
}
|
|
}
|