329 lines
8.9 KiB
C
Executable File
329 lines
8.9 KiB
C
Executable File
#include "gb-impl.h"
|
|
|
|
BYTE getColour(BYTE colourNum, BYTE palette) {
|
|
// use the palette to get the colour
|
|
return (palette >> (colourNum * 2)) & 0x3;
|
|
}
|
|
|
|
void renderTiles(gb *cpu) {
|
|
WORD tileData = 0;
|
|
WORD backgroundMemory = 0;
|
|
BYTE unsig = 1;
|
|
|
|
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
|
|
|
|
BYTE scrollY = readMemory(cpu, SCROLLY);
|
|
BYTE scrollX = readMemory(cpu, SCROLLX);
|
|
BYTE windowY = readMemory(cpu, WINDOWY);
|
|
BYTE windowX = readMemory(cpu, WINDOWX);
|
|
|
|
if (windowX < 7) {
|
|
windowX = 7;
|
|
}
|
|
windowX -= 7;
|
|
|
|
BYTE currentLine = readMemory(cpu, LCD_SCANLINE_ADRR);
|
|
BYTE usingWindow = 0;
|
|
|
|
/*The window is enabled and visible?*/
|
|
if ((lcd_control & 0x20) != 0 && windowY <= currentLine)
|
|
usingWindow = 1;
|
|
|
|
// tile area ?
|
|
if ((lcd_control & 0x10) != 0)
|
|
tileData = 0x8000;
|
|
else {
|
|
// This memory region uses signed bytes
|
|
tileData = 0x8800;
|
|
unsig = 0;
|
|
}
|
|
|
|
BYTE yPos = 0;
|
|
|
|
// yPos is used to calculate which of 32 vertical tiles the
|
|
// current scanline is drawing
|
|
if (usingWindow == 0)
|
|
yPos = scrollY + currentLine;
|
|
else {
|
|
yPos = currentLine - windowY;
|
|
}
|
|
|
|
// which line of the tile is being rendered ?
|
|
WORD tileRow = (yPos / 8) * 32;
|
|
|
|
BYTE palette = readMemory(cpu, 0xFF47);
|
|
BYTE colorMap[4] = {getColour(0, palette), getColour(1, palette),
|
|
getColour(2, palette), getColour(3, palette)};
|
|
|
|
// Now I have to render the line, pixel by pixel
|
|
for (BYTE pixel = 0; pixel < 160;) {
|
|
BYTE xPos = pixel + scrollX;
|
|
|
|
if ((lcd_control & 0x08) != 0)
|
|
backgroundMemory = 0x9C00;
|
|
else
|
|
backgroundMemory = 0x9800;
|
|
|
|
// translate the current x pos to window space if necessary
|
|
if (usingWindow == 1) {
|
|
if (pixel >= windowX) {
|
|
// This means I'm in the window
|
|
xPos = pixel - windowX;
|
|
if ((lcd_control & 0x40) != 0)
|
|
backgroundMemory = 0x9c00;
|
|
else
|
|
backgroundMemory = 0x9800;
|
|
}
|
|
}
|
|
|
|
// which of the 32 horizontal tiles does this xPos fall within?
|
|
WORD tileCol = (xPos / 8);
|
|
WORD tileNum;
|
|
|
|
WORD tileAddrss = backgroundMemory + tileRow + tileCol;
|
|
if (unsig == 1)
|
|
tileNum = (BYTE)readMemory(cpu, tileAddrss);
|
|
else {
|
|
tileNum =
|
|
((SIGNED_WORD)readMemory(cpu, tileAddrss) + (SIGNED_WORD)128) &
|
|
0xff;
|
|
}
|
|
|
|
// deduce where this tile identifier is in memory.
|
|
WORD tileLocation = tileData + (tileNum * 16);
|
|
|
|
BYTE line = yPos % 8;
|
|
line *= 2; // each line takes two bytes
|
|
BYTE data1 = readMemory(cpu, tileLocation + line);
|
|
BYTE data2 = readMemory(cpu, tileLocation + line + 1);
|
|
|
|
SIGNED_BYTE colourBit = (xPos & 0x7);
|
|
colourBit -= 7;
|
|
colourBit *= -1;
|
|
|
|
for(; colourBit >= 0; colourBit--) {
|
|
BYTE colourNum = (data2 >> (colourBit)) & 0x1;
|
|
colourNum <<= 1;
|
|
colourNum |= ((data1 >> (colourBit)) & 0x1);
|
|
|
|
BYTE col = colorMap[colourNum];
|
|
|
|
cpu->screenData[currentLine][pixel] = col;
|
|
pixel++;
|
|
if(pixel >= 160) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void renderSprites(gb *cpu) {
|
|
BYTE ysize = 8;
|
|
BYTE currentLine = readMemory(cpu, 0xFF44);
|
|
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
|
|
if ((lcd_control & 0x4) != 0)
|
|
ysize = 16;
|
|
|
|
/*Explore all the sprite table*/
|
|
/*Each sprite is formed by 4 bytes:
|
|
* 0 : y pos - 16
|
|
* 1 : x pos - 8
|
|
* 2 : data location in memory
|
|
* 3 : several flags
|
|
*/
|
|
for (BYTE index = 0; index < 40 * 4; index += 4) {
|
|
BYTE yPos = readMemory(cpu, 0xFE00 + index) - 16;
|
|
|
|
if ((currentLine < yPos) || (currentLine >= (yPos + ysize)))
|
|
continue;
|
|
|
|
BYTE xPos = readMemory(cpu, 0xFE00 + index + 1) - 8;
|
|
|
|
if (xPos > 160)
|
|
continue;
|
|
|
|
BYTE tileLocation = readMemory(cpu, 0xFE00 + index + 2);
|
|
BYTE attributes = readMemory(cpu, 0xFE00 + index + 3);
|
|
|
|
BYTE yFlip = attributes & 0x40;
|
|
BYTE xFlip = attributes & 0x20;
|
|
|
|
WORD colourAddress;
|
|
if ((attributes & 0x10) != 0)
|
|
colourAddress = 0xFF49;
|
|
else
|
|
colourAddress = 0xFF48;
|
|
|
|
// does this sprite intercept with the scanline?
|
|
BYTE line = currentLine - yPos;
|
|
|
|
// read the sprite in backwards in the y axis
|
|
if (yFlip != 0) {
|
|
line -= ysize;
|
|
line *= -1;
|
|
}
|
|
|
|
line *= 2; // same as for tiles
|
|
WORD dataAddress = (0x8000 + (tileLocation * 16)) + line;
|
|
BYTE data1 = readMemory(cpu, dataAddress);
|
|
BYTE data2 = readMemory(cpu, dataAddress + 1);
|
|
|
|
BYTE palette = readMemory(cpu, colourAddress);
|
|
|
|
// its easier to read in from right to left as pixel 0 is
|
|
// bit 7 in the colour data, pixel 1 is bit 6 etc...
|
|
for (SIGNED_BYTE tilePixel = 7; tilePixel >= 0; tilePixel--) {
|
|
SIGNED_BYTE xPix = 0 - tilePixel;
|
|
xPix += 7;
|
|
BYTE pixel = xPos + (BYTE)xPix;
|
|
|
|
if (pixel > 160 || currentLine > 144)
|
|
continue;
|
|
|
|
// check if pixel is hidden behind background
|
|
if ((attributes & 0x80) != 0 &&
|
|
cpu->screenData[currentLine][pixel] != 0) {
|
|
continue;
|
|
}
|
|
|
|
/*Should do some refactoring for avoiding this signed pixel*/
|
|
SIGNED_BYTE colourbit = tilePixel;
|
|
// read the sprite in backwards for the x axis
|
|
if (xFlip != 0) {
|
|
colourbit -= 7;
|
|
colourbit *= -1;
|
|
}
|
|
|
|
BYTE colourNum = (data2 >> (colourbit)) & 0x1;
|
|
colourNum <<= 1;
|
|
colourNum |= ((data1 >> (colourbit)) & 0x1);
|
|
|
|
/*Must ignore the alpha color*/
|
|
if (colourNum == 0)
|
|
continue;
|
|
|
|
BYTE col = getColour(colourNum, palette);
|
|
|
|
/*I can draw the pixel*/
|
|
cpu->screenData[currentLine][pixel] = col;
|
|
}
|
|
}
|
|
}
|
|
|
|
void drawScanline(gb *cpu) {
|
|
BYTE control = readMemory(cpu, LCD_REG_CONTROL);
|
|
if ((control & 0x1) != 0)
|
|
renderTiles(cpu);
|
|
|
|
if ((control & 0x2) != 0)
|
|
renderSprites(cpu);
|
|
}
|
|
|
|
void updateLCD(gb *cpu) {
|
|
BYTE status = readMemory(cpu, LCD_REG_STATUS);
|
|
BYTE lcd_control = readMemory(cpu, LCD_REG_CONTROL);
|
|
|
|
/*LCD is disabled*/
|
|
if ((lcd_control & 0x80) == 0) {
|
|
// set the mode to 1 during lcd disabled and reset scanline
|
|
cpu->clockScanline = 456;
|
|
cpu->memory[0xFF44] = 0;
|
|
status &= 252;
|
|
status |= 0x1;
|
|
writeMemory(cpu, LCD_REG_STATUS, status);
|
|
return;
|
|
}
|
|
|
|
BYTE currentline = readMemory(cpu, LCD_SCANLINE_ADRR);
|
|
BYTE mode = status & 0x3;
|
|
|
|
BYTE nextMode = 0;
|
|
BYTE reqInt = 0;
|
|
|
|
// in vblank so set mode to 1
|
|
if (currentline >= 144) {
|
|
nextMode = 1;
|
|
status &= ~(0x3);
|
|
status |= 0x1;
|
|
reqInt = status & 0x10;
|
|
} else {
|
|
// mode 2
|
|
if (cpu->clockScanline >= 376) {
|
|
nextMode = 2;
|
|
status &= ~(0x3);
|
|
status |= 0x2;
|
|
reqInt = status & 0x20;
|
|
}
|
|
// mode 3
|
|
else if (cpu->clockScanline >= 204) {
|
|
nextMode = 3;
|
|
status &= ~(0x3);
|
|
status |= 0x3;
|
|
}
|
|
// mode 0
|
|
else {
|
|
nextMode = 0;
|
|
status &= ~(0x3);
|
|
reqInt = status & 0x8;
|
|
}
|
|
}
|
|
|
|
if ((reqInt != 0) && (mode != nextMode))
|
|
raiseInterrupt(cpu, 1);
|
|
|
|
// check the conincidence flag
|
|
if (readMemory(cpu, 0xFF44) == readMemory(cpu, 0xFF45)) {
|
|
status |= 0x4;
|
|
if ((status & 0x40) != 0)
|
|
raiseInterrupt(cpu, 1);
|
|
} else {
|
|
status &= ~(0x4);
|
|
}
|
|
writeMemory(cpu, LCD_REG_STATUS, status);
|
|
}
|
|
|
|
void handleGraphic(gb *cpu, BYTE cycles) {
|
|
updateLCD(cpu);
|
|
BYTE lcd_control = cpu->memory[LCD_REG_CONTROL];
|
|
|
|
if ((lcd_control & 0x80) == 0) {
|
|
return;
|
|
}
|
|
cpu->clockScanline -= cycles;
|
|
if (cpu->clockScanline <= 0) {
|
|
BYTE currentline = cpu->memory[LCD_SCANLINE_ADRR];
|
|
cpu->clockScanline += 456;
|
|
|
|
/*V-blank interrupt*/
|
|
if (currentline == 144)
|
|
raiseInterrupt(cpu, 0);
|
|
|
|
else if (currentline > 153)
|
|
cpu->memory[LCD_SCANLINE_ADRR] = 0;
|
|
|
|
else if (currentline < 144)
|
|
drawScanline(cpu);
|
|
cpu->memory[LCD_SCANLINE_ADRR]++;
|
|
}
|
|
}
|
|
|
|
BYTE getPixelColor(gb *cpu, BYTE x, BYTE y) {
|
|
BYTE col = cpu->screenData[y][x];
|
|
BYTE color = 0;
|
|
switch (col) {
|
|
case 0:
|
|
color = BLACK_COL;
|
|
break;
|
|
case 1:
|
|
color = GRAY_COL;
|
|
break;
|
|
case 2:
|
|
color = LIGHTGRAY_COL;
|
|
break;
|
|
case 3:
|
|
color = WHITE_COL;
|
|
break;
|
|
}
|
|
return color;
|
|
}
|