Files
gameboy-sharp/src/GameBoy.Core/Video/Ppu.Render.cs
T

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);
}
}
}