199 lines
6.1 KiB
C#
199 lines
6.1 KiB
C#
namespace GameBoy.Video
|
|
{
|
|
public sealed partial class Ppu
|
|
{
|
|
private readonly byte[] lineBgColor = new byte[ScreenWidth];
|
|
|
|
private void RenderScanline()
|
|
{
|
|
RenderBackground();
|
|
RenderWindow();
|
|
RenderSprites();
|
|
}
|
|
|
|
private void RenderBackground()
|
|
{
|
|
int rowBase = ly * ScreenWidth;
|
|
bool bgEnabled = (lcdc & 0x01) != 0;
|
|
|
|
if (!bgEnabled)
|
|
{
|
|
for (int x = 0; x < ScreenWidth; x++)
|
|
{
|
|
lineBgColor[x] = 0;
|
|
Framebuffer[rowBase + x] = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
int tileMapBase = (lcdc & 0x08) != 0 ? 0x1C00 : 0x1800;
|
|
bool signed = (lcdc & 0x10) == 0;
|
|
int y = (scy + ly) & 0xFF;
|
|
int tileRow = y >> 3;
|
|
int rowInTile = y & 7;
|
|
|
|
for (int x = 0; x < ScreenWidth; x++)
|
|
{
|
|
int mapX = (scx + x) & 0xFF;
|
|
int tileCol = mapX >> 3;
|
|
int bit = 7 - (mapX & 7);
|
|
|
|
int tileIndex = vram[tileMapBase + tileRow * 32 + tileCol];
|
|
int tileAddr = signed
|
|
? 0x1000 + ((sbyte)tileIndex) * 16 + rowInTile * 2
|
|
: tileIndex * 16 + rowInTile * 2;
|
|
|
|
int low = vram[tileAddr];
|
|
int high = vram[tileAddr + 1];
|
|
int color = (((high >> bit) & 1) << 1) | ((low >> bit) & 1);
|
|
|
|
lineBgColor[x] = (byte)color;
|
|
Framebuffer[rowBase + x] = Shade(bgp, color);
|
|
}
|
|
}
|
|
|
|
private void RenderWindow()
|
|
{
|
|
if ((lcdc & 0x20) == 0)
|
|
return;
|
|
if (wy > ly)
|
|
return;
|
|
if (wx > 166)
|
|
return;
|
|
|
|
int rowBase = ly * ScreenWidth;
|
|
int tileMapBase = (lcdc & 0x40) != 0 ? 0x1C00 : 0x1800;
|
|
bool signed = (lcdc & 0x10) == 0;
|
|
int winY = windowLine;
|
|
int tileRow = winY >> 3;
|
|
int rowInTile = winY & 7;
|
|
int start = wx - 7;
|
|
bool drawn = false;
|
|
|
|
for (int x = 0; x < ScreenWidth; x++)
|
|
{
|
|
if (x < start)
|
|
continue;
|
|
drawn = true;
|
|
|
|
int winX = x - start;
|
|
int tileCol = winX >> 3;
|
|
int bit = 7 - (winX & 7);
|
|
|
|
int tileIndex = vram[tileMapBase + tileRow * 32 + tileCol];
|
|
int tileAddr = signed
|
|
? 0x1000 + ((sbyte)tileIndex) * 16 + rowInTile * 2
|
|
: tileIndex * 16 + rowInTile * 2;
|
|
|
|
int low = vram[tileAddr];
|
|
int high = vram[tileAddr + 1];
|
|
int color = (((high >> bit) & 1) << 1) | ((low >> bit) & 1);
|
|
|
|
lineBgColor[x] = (byte)color;
|
|
Framebuffer[rowBase + x] = Shade(bgp, color);
|
|
}
|
|
|
|
if (drawn)
|
|
windowLine++;
|
|
}
|
|
|
|
private readonly int[] spriteCandidates = new int[10];
|
|
|
|
private void RenderSprites()
|
|
{
|
|
if ((lcdc & 0x02) == 0)
|
|
return;
|
|
|
|
int height = (lcdc & 0x04) != 0 ? 16 : 8;
|
|
int count = 0;
|
|
|
|
for (int i = 0; i < 40 && count < 10; i++)
|
|
{
|
|
int spriteY = oam[i * 4] - 16;
|
|
if (ly >= spriteY && ly < spriteY + height)
|
|
spriteCandidates[count++] = i;
|
|
}
|
|
|
|
SortByPriority(count);
|
|
|
|
int rowBase = ly * ScreenWidth;
|
|
for (int x = 0; x < ScreenWidth; x++)
|
|
{
|
|
for (int c = 0; c < count; c++)
|
|
{
|
|
int i = spriteCandidates[c];
|
|
int spriteX = oam[i * 4 + 1] - 8;
|
|
if (x < spriteX || x >= spriteX + 8)
|
|
continue;
|
|
|
|
int attr = oam[i * 4 + 3];
|
|
int spriteY = oam[i * 4] - 16;
|
|
int row = ly - spriteY;
|
|
if ((attr & 0x40) != 0)
|
|
row = height - 1 - row;
|
|
|
|
int tile = oam[i * 4 + 2];
|
|
if (height == 16)
|
|
{
|
|
tile &= 0xFE;
|
|
if (row >= 8)
|
|
{
|
|
tile += 1;
|
|
row -= 8;
|
|
}
|
|
}
|
|
|
|
int pixelCol = x - spriteX;
|
|
int col = (attr & 0x20) != 0 ? 7 - pixelCol : pixelCol;
|
|
int bit = 7 - col;
|
|
|
|
int tileAddr = tile * 16 + row * 2;
|
|
int low = vram[tileAddr];
|
|
int high = vram[tileAddr + 1];
|
|
int color = (((high >> bit) & 1) << 1) | ((low >> bit) & 1);
|
|
|
|
if (color == 0)
|
|
continue;
|
|
|
|
bool behindBg = (attr & 0x80) != 0 && lineBgColor[x] != 0;
|
|
if (!behindBg)
|
|
{
|
|
byte palette = (attr & 0x10) != 0 ? obp1 : obp0;
|
|
Framebuffer[rowBase + x] = Shade(palette, color);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SortByPriority(int count)
|
|
{
|
|
for (int a = 1; a < count; a++)
|
|
{
|
|
int key = spriteCandidates[a];
|
|
int keyX = oam[key * 4 + 1];
|
|
int b = a - 1;
|
|
while (b >= 0 && ShouldSwap(spriteCandidates[b], keyX, key))
|
|
{
|
|
spriteCandidates[b + 1] = spriteCandidates[b];
|
|
b--;
|
|
}
|
|
spriteCandidates[b + 1] = key;
|
|
}
|
|
}
|
|
|
|
private bool ShouldSwap(int current, int keyX, int key)
|
|
{
|
|
int currentX = oam[current * 4 + 1];
|
|
if (currentX != keyX)
|
|
return currentX > keyX;
|
|
return current > key;
|
|
}
|
|
|
|
private static byte Shade(byte palette, int color)
|
|
{
|
|
return (byte)((palette >> (color * 2)) & 0x03);
|
|
}
|
|
}
|
|
}
|