Re: How do GPU/PPU timings work on the Gameboy?
Posted: Sun Jan 20, 2019 5:19 pm
I managed to run the bootrom, the Nintendo logo appeared. but ... does not roll from top to bottom, jumping on the screen. I could not find the GPU error. Needless to say, the CPU and the memory did not have much difficulty, but the GPU is my achilles canyon.
Some ideia where is the error on renderTiles, on main loop i call gpu.updateGraphics();
thx!
Code: Select all
private static final int MODE0 = 3;
private static final int MODE1 = 4;
private static final int MODE2 = 5;
private static final int LCDC_BGDISPLAY = 0;
private static final int LCDC_OBJENABLE = 1;
private static final int LCDC_OBJ_SPRIT = 2; //Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16)
private static final int LCDC_BG_TILE_M = 3; //Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
private static final int LCDC_BG_TILE_S = 4; //Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF)
private static final int LCDC_WINDOW_EN = 5; //Bit 5 - Window Display Enable (0=Off, 1=On)
private static final int LCDC_WINDOW_SE = 6; //Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF)
public void updateGraphics(int cycles){
setLCDStatus();
if(isLCDEnabled())
scanlineCounter -= cycles;
else
return;
if(scanlineCounter <= 0){
int currentLine = read_reg(0xFF44); // LY
scanlineCounter = 456;
if(currentLine == 144){ // V-Blank periodo
MMU.getInstance().requestInterrupter(0);
}else if(currentLine > 153){ // se passou de 153, reset to 0
gpu_registros[4] = 0;
}else if( currentLine < 144){
drawScanLine() ;
}
gpu_registros[4] += 1;
}
}
///////////////////////////////////////////////////////////////////////////////////////
private void drawScanLine() {
int control = read_reg(0xFF40); // 0xFF40
if(testBit(control, LCDC_BGDISPLAY))
renderTiles(control);
if(testBit(control, LCDC_OBJENABLE))
renderSprites();
}
//////////////////////////////////////////////////////////////////////
private void renderTiles(int c){
int tileData = 0;
int backgroundMemory = 0;
boolean using = true;
int lcdControl = c;
// onde vou desenhar o visual area no window
int scrollY = read_reg(0xFF42); // SCY
int scrollX = read_reg(0xFF43); // SCX
int windowY = read_reg(0xff4A); // WY
int windowX = read_reg(0xFF4B) - 7; // WX
boolean usingWindow = false;
// background enable
if(testBit(lcdControl, LCDC_WINDOW_EN)){ // Bit 5 - Window Display Enable (0=Off, 1=On)
// is the current scanline we're drawing within the windows Y pos?,
if(windowY <= read_reg(0xFF44))
usingWindow = true;
}
// qual tile data estamos usando?
if(testBit(lcdControl, LCDC_BG_TILE_S)){
tileData = 0x8000;
}else{
// IMPORTANT: This memory region uses signed bytes as tile identifiers
tileData = 0x8800;
using = false;
}
// qual background regiao da memoria?
if(false == usingWindow){
if(testBit(lcdControl, LCDC_BG_TILE_M))
backgroundMemory = 0x9C00;
else
backgroundMemory = 0x9800;
}else{
// qual window memory
if(testBit(lcdControl, LCDC_WINDOW_SE))
backgroundMemory = 0x9C00;
else
backgroundMemory = 0x9800;
}
int yPos = 0;
//yPos é usado para calcular qual de 32 vertcal tiles o corrente scanline esta desenhando
if(!usingWindow)
yPos = scrollY + read_reg(0xFF44);
else
yPos = read_reg(0xFF44) - windowY;
// qual dos 8 vertical pixels do corrente tile esta no scanline
int tileRow = (yPos / 8)*32;
//hora de iniciar a desenhar a 160 horizontal pixels para este scanline
for(int pixel = 0; pixel < 160; pixel++){
int xPos = pixel + scrollX;
// traduz o corrente X pos para a janela de space se necessario
if(usingWindow){
if(pixel >= windowX){
xPos = pixel - windowX;
}
}
// qual dos 32 horizontais tiles este xPos esta dentro
int tileCol = (xPos / 8);
int tileNumUnsigned=0;
byte tileNumSigned=0;
// get the tile identity number. Remember it can be signed or unsigned
int tileAddress = backgroundMemory + tileRow + tileCol;
if(using){
tileNumUnsigned = MMU.getInstance().readByte(tileAddress); // signed
}else{
tileNumSigned = (byte)MMU.getInstance().readByte(tileAddress); // unsigned
}
// deduce where this tile identifier is in memory.
int tileLocation = tileData;
if(using)
tileLocation += (tileNumUnsigned * 16);
else
tileLocation += (tileNumSigned + 128) * 16;
// find the correct vertical line we're on of the tile to get the tile data from in memory
int line = yPos % 8;
line *= 2;// each vertical line takes up two bytes of memory
int data1 = MMU.getInstance().readByte(tileLocation + line) ;
int data2 = MMU.getInstance().readByte(tileLocation + line + 1) ;
// pixel 0 in the tile is it 7 of data 1 and data2. Pixel 1 is bit 6 etc..
int colourBit = xPos % 8 ;
colourBit -= 7 ;
colourBit *= -1 ;
// combine data 2 and data 1 to get the colour id for this pixel in the tile
int colourNum = testBit(data2,colourBit) ? 1:0 ;
colourNum <<= 1;
colourNum |= testBit(data1,colourBit) ? 1:0 ;
// now we have the colour id get the actual colour from palette 0xFF47
int col = GetColour(colourNum, 0xFF47) ;
int red = 0;
int green = 0;
int blue = 0;
switch(col){
case 0: red = 0xFF; green = 0xFF ; blue = 0xFF; break ;
case 1: red = 0xCC; green = 0xCC ; blue = 0xCC; break ;
case 2: red = 0x77; green = 0x77 ; blue = 0x77; break ;
}
int finaly = read_reg(0xFF44);
if ((finaly<0)||(finaly>143)||(pixel<0)||(pixel>159))
{
return;
}
screenData[pixel][finaly][0] = red;
screenData[pixel][finaly][1] = green;
screenData[pixel][finaly][2] = blue;
}
}
/////////////////////////////////////////////////////////////////////////
public void setLCDStatus(){
int status = read_reg(0xFF41);
if(isLCDEnabled() == false){
// set o modo para 1 durante lcd disable. e reset scanline
scanlineCounter = 456; // reset scanline
gpu_registros[4] = 0; // scanline = 0;
status &= 252;
status = setBit(status, 0);
write_reg(0xFF41, status); // set interrupter H-Blank
}
int currentLine = read_reg(0xFF44);
int currentMode = status & 0x3;
int mode = 0;
boolean reqInt = false;
// se estiver em V-Blank, então set MODO para 1
if(currentLine >= 144){
mode = 1;
status = setBit(status, 0);
status = resetBit(status, 1);
reqInt = testBit(status, MODE1);
}else{
int mode2bounds = 456-80;
int mode3bounds = mode2bounds - 172;
// MODO 2
if(scanlineCounter >= mode2bounds){
mode = 2;
status = setBit(status, 1); // enable V-Blank
status = resetBit(status, 0);// disable H-Blank
reqInt = testBit(status, MODE2);
}// MODO 3
else if(scanlineCounter >= mode3bounds){
mode = 3;
status = setBit(status, 1);
status = setBit(status, 0);
}//MODO 0
else{
mode = 0;
status = resetBit(status, 1);
status = resetBit(status, 0);
reqInt = testBit(status, MODE0);
}
}
// entrada em um novo modo, então solicita Interrupt
if(reqInt && (mode != currentMode))
MMU.getInstance().requestInterrupter(1); // Bit 1: LCD Interupt
if(currentLine == read_reg(0xFF45)){
status = setBit(status, 2);
if(testBit(status, 6))
MMU.getInstance().requestInterrupter(1);
}else{
status = resetBit(status, 2);
}
write_reg(0xFF41, status);
}
thx!