953 lines
34 KiB
C#
953 lines
34 KiB
C#
namespace GarboDev
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
public partial class Renderer : IRenderer
|
|
{
|
|
private Memory memory;
|
|
private uint[] scanline = new uint[240];
|
|
private byte[] blend = new byte[240];
|
|
private byte[] windowCover = new byte[240];
|
|
private uint[] back = new uint[240 * 160];
|
|
//private uint[] front = new uint[240 * 160];
|
|
private const uint pitch = 240;
|
|
|
|
// Convenience variable as I use it everywhere, set once in RenderLine
|
|
private ushort dispCnt;
|
|
|
|
// Window helper variables
|
|
private byte win0x1, win0x2, win0y1, win0y2;
|
|
private byte win1x1, win1x2, win1y1, win1y2;
|
|
private byte win0Enabled, win1Enabled, winObjEnabled, winOutEnabled;
|
|
private bool winEnabled;
|
|
|
|
private byte blendSource, blendTarget;
|
|
private byte blendA, blendB, blendY;
|
|
private int blendType;
|
|
|
|
private int curLine = 0;
|
|
|
|
private static uint[] colorLUT;
|
|
|
|
static Renderer()
|
|
{
|
|
colorLUT = new uint[0x10000];
|
|
// Pre-calculate the color LUT
|
|
for (uint i = 0; i <= 0xFFFF; i++)
|
|
{
|
|
uint r = (i & 0x1FU);
|
|
uint g = (i & 0x3E0U) >> 5;
|
|
uint b = (i & 0x7C00U) >> 10;
|
|
r = (r << 3) | (r >> 2);
|
|
g = (g << 3) | (g >> 2);
|
|
b = (b << 3) | (b >> 2);
|
|
colorLUT[i] = (r << 16) | (g << 8) | b;
|
|
}
|
|
}
|
|
|
|
public Memory Memory
|
|
{
|
|
set { this.memory = value; }
|
|
}
|
|
|
|
public void Initialize(object data)
|
|
{
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
}
|
|
|
|
public uint[] ShowFrame()
|
|
{
|
|
//Array.Copy(this.back, this.front, this.front.Length);
|
|
|
|
//return this.front;
|
|
return this.back;
|
|
}
|
|
|
|
public void RenderLine(int line)
|
|
{
|
|
this.curLine = line;
|
|
|
|
// Render the line
|
|
this.dispCnt = Memory.ReadU16(this.memory.IORam, Memory.DISPCNT);
|
|
|
|
if ((this.dispCnt & (1 << 7)) != 0)
|
|
{
|
|
uint bgColor = Renderer.GbaTo32((ushort)0x7FFF);
|
|
for (int i = 0; i < 240; i++) this.scanline[i] = bgColor;
|
|
}
|
|
else
|
|
{
|
|
this.winEnabled = false;
|
|
|
|
if ((this.dispCnt & (1 << 13)) != 0)
|
|
{
|
|
// Calculate window 0 information
|
|
ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN0V);
|
|
this.win0y1 = (byte)(winy >> 8);
|
|
this.win0y2 = (byte)(winy & 0xff);
|
|
ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN0H);
|
|
this.win0x1 = (byte)(winx >> 8);
|
|
this.win0x2 = (byte)(winx & 0xff);
|
|
|
|
if (this.win0x2 > 240 || this.win0x1 > this.win0x2)
|
|
{
|
|
this.win0x2 = 240;
|
|
}
|
|
|
|
if (this.win0y2 > 160 || this.win0y1 > this.win0y2)
|
|
{
|
|
this.win0y2 = 160;
|
|
}
|
|
|
|
this.win0Enabled = this.memory.IORam[Memory.WININ];
|
|
this.winEnabled = true;
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 14)) != 0)
|
|
{
|
|
// Calculate window 1 information
|
|
ushort winy = Memory.ReadU16(this.memory.IORam, Memory.WIN1V);
|
|
this.win1y1 = (byte)(winy >> 8);
|
|
this.win1y2 = (byte)(winy & 0xff);
|
|
ushort winx = Memory.ReadU16(this.memory.IORam, Memory.WIN1H);
|
|
this.win1x1 = (byte)(winx >> 8);
|
|
this.win1x2 = (byte)(winx & 0xff);
|
|
|
|
if (this.win1x2 > 240 || this.win1x1 > this.win1x2)
|
|
{
|
|
this.win1x2 = 240;
|
|
}
|
|
|
|
if (this.win1y2 > 160 || this.win1y1 > this.win1y2)
|
|
{
|
|
this.win1y2 = 160;
|
|
}
|
|
|
|
this.win1Enabled = this.memory.IORam[Memory.WININ + 1];
|
|
this.winEnabled = true;
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 15)) != 0 && (this.dispCnt & (1 << 12)) != 0)
|
|
{
|
|
// Object windows are enabled
|
|
this.winObjEnabled = this.memory.IORam[Memory.WINOUT + 1];
|
|
this.winEnabled = true;
|
|
}
|
|
|
|
if (this.winEnabled)
|
|
{
|
|
this.winOutEnabled = this.memory.IORam[Memory.WINOUT];
|
|
}
|
|
|
|
// Calculate blending information
|
|
ushort bldcnt = Memory.ReadU16(this.memory.IORam, Memory.BLDCNT);
|
|
this.blendType = (bldcnt >> 6) & 0x3;
|
|
this.blendSource = (byte)(bldcnt & 0x3F);
|
|
this.blendTarget = (byte)((bldcnt >> 8) & 0x3F);
|
|
|
|
ushort bldalpha = Memory.ReadU16(this.memory.IORam, Memory.BLDALPHA);
|
|
this.blendA = (byte)(bldalpha & 0x1F);
|
|
if (this.blendA > 0x10) this.blendA = 0x10;
|
|
this.blendB = (byte)((bldalpha >> 8) & 0x1F);
|
|
if (this.blendB > 0x10) this.blendB = 0x10;
|
|
|
|
this.blendY = (byte)(this.memory.IORam[Memory.BLDY] & 0x1F);
|
|
if (this.blendY > 0x10) this.blendY = 0x10;
|
|
|
|
switch (this.dispCnt & 0x7)
|
|
{
|
|
case 0: this.RenderMode0Line(); break;
|
|
case 1: this.RenderMode1Line(); break;
|
|
case 2: this.RenderMode2Line(); break;
|
|
case 3: this.RenderMode3Line(); break;
|
|
case 4: this.RenderMode4Line(); break;
|
|
case 5: this.RenderMode5Line(); break;
|
|
}
|
|
}
|
|
|
|
Array.Copy(this.scanline, 0, this.back, this.curLine * Renderer.pitch, Renderer.pitch);
|
|
}
|
|
|
|
private void DrawBackdrop()
|
|
{
|
|
byte[] palette = this.memory.PaletteRam;
|
|
|
|
// Initialize window coverage buffer if neccesary
|
|
if (this.winEnabled)
|
|
{
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
this.windowCover[i] = this.winOutEnabled;
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 15)) != 0)
|
|
{
|
|
// Sprite window
|
|
this.DrawSpriteWindows();
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 14)) != 0)
|
|
{
|
|
// Window 1
|
|
if (this.curLine >= this.win1y1 && this.curLine < this.win1y2)
|
|
{
|
|
for (int i = this.win1x1; i < this.win1x2; i++)
|
|
{
|
|
this.windowCover[i] = this.win1Enabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 13)) != 0)
|
|
{
|
|
// Window 0
|
|
if (this.curLine >= this.win0y1 && this.curLine < this.win0y2)
|
|
{
|
|
for (int i = this.win0x1; i < this.win0x2; i++)
|
|
{
|
|
this.windowCover[i] = this.win0Enabled;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw backdrop first
|
|
uint bgColor = Renderer.GbaTo32((ushort)(palette[0] | (palette[1] << 8)));
|
|
uint modColor = bgColor;
|
|
|
|
if (this.blendType == 2 && (this.blendSource & (1 << 5)) != 0)
|
|
{
|
|
// Brightness increase
|
|
uint r = bgColor & 0xFF;
|
|
uint g = (bgColor >> 8) & 0xFF;
|
|
uint b = (bgColor >> 16) & 0xFF;
|
|
r = r + (((0xFF - r) * this.blendY) >> 4);
|
|
g = g + (((0xFF - g) * this.blendY) >> 4);
|
|
b = b + (((0xFF - b) * this.blendY) >> 4);
|
|
modColor = r | (g << 8) | (b << 16);
|
|
}
|
|
else if (this.blendType == 3 && (this.blendSource & (1 << 5)) != 0)
|
|
{
|
|
// Brightness decrease
|
|
uint r = bgColor & 0xFF;
|
|
uint g = (bgColor >> 8) & 0xFF;
|
|
uint b = (bgColor >> 16) & 0xFF;
|
|
r = r - ((r * this.blendY) >> 4);
|
|
g = g - ((g * this.blendY) >> 4);
|
|
b = b - ((b * this.blendY) >> 4);
|
|
modColor = r | (g << 8) | (b << 16);
|
|
}
|
|
|
|
if (this.winEnabled)
|
|
{
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
if ((this.windowCover[i] & (1 << 5)) != 0)
|
|
{
|
|
this.scanline[i] = modColor;
|
|
}
|
|
else
|
|
{
|
|
this.scanline[i] = bgColor;
|
|
}
|
|
this.blend[i] = 1 << 5;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
this.scanline[i] = modColor;
|
|
this.blend[i] = 1 << 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderTextBg(int bg)
|
|
{
|
|
if (this.winEnabled)
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.RenderTextBgWindow(bg);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgWindowBlend(bg);
|
|
else
|
|
this.RenderTextBgWindow(bg);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgWindowBrightInc(bg);
|
|
else
|
|
this.RenderTextBgWindow(bg);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgWindowBrightDec(bg);
|
|
else
|
|
this.RenderTextBgWindow(bg);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.RenderTextBgNormal(bg);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgBlend(bg);
|
|
else
|
|
this.RenderTextBgNormal(bg);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgBrightInc(bg);
|
|
else
|
|
this.RenderTextBgNormal(bg);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderTextBgBrightDec(bg);
|
|
else
|
|
this.RenderTextBgNormal(bg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderRotScaleBg(int bg)
|
|
{
|
|
if (this.winEnabled)
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.RenderRotScaleBgWindow(bg);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgWindowBlend(bg);
|
|
else
|
|
this.RenderRotScaleBgWindow(bg);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgWindowBrightInc(bg);
|
|
else
|
|
this.RenderRotScaleBgWindow(bg);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgWindowBrightDec(bg);
|
|
else
|
|
this.RenderRotScaleBgWindow(bg);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.RenderRotScaleBgNormal(bg);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgBlend(bg);
|
|
else
|
|
this.RenderRotScaleBgNormal(bg);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgBrightInc(bg);
|
|
else
|
|
this.RenderRotScaleBgNormal(bg);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << bg)) != 0)
|
|
this.RenderRotScaleBgBrightDec(bg);
|
|
else
|
|
this.RenderRotScaleBgNormal(bg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawSprites(int pri)
|
|
{
|
|
if (this.winEnabled)
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.DrawSpritesWindow(pri);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesWindowBlend(pri);
|
|
else
|
|
this.DrawSpritesWindow(pri);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesWindowBrightInc(pri);
|
|
else
|
|
this.DrawSpritesWindow(pri);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesWindowBrightDec(pri);
|
|
else
|
|
this.DrawSpritesWindow(pri);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (this.blendType)
|
|
{
|
|
case 0:
|
|
this.DrawSpritesNormal(pri);
|
|
break;
|
|
case 1:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesBlend(pri);
|
|
else
|
|
this.DrawSpritesNormal(pri);
|
|
break;
|
|
case 2:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesBrightInc(pri);
|
|
else
|
|
this.DrawSpritesNormal(pri);
|
|
break;
|
|
case 3:
|
|
if ((this.blendSource & (1 << 4)) != 0)
|
|
this.DrawSpritesBrightDec(pri);
|
|
else
|
|
this.DrawSpritesNormal(pri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderMode0Line()
|
|
{
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
for (int pri = 3; pri >= 0; pri--)
|
|
{
|
|
for (int i = 3; i >= 0; i--)
|
|
{
|
|
if ((this.dispCnt & (1 << (8 + i))) != 0)
|
|
{
|
|
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
|
|
|
|
if ((bgcnt & 0x3) == pri)
|
|
{
|
|
this.RenderTextBg(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void RenderMode1Line()
|
|
{
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
for (int pri = 3; pri >= 0; pri--)
|
|
{
|
|
if ((this.dispCnt & (1 << (8 + 2))) != 0)
|
|
{
|
|
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
|
|
|
|
if ((bgcnt & 0x3) == pri)
|
|
{
|
|
this.RenderRotScaleBg(2);
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i >= 0; i--)
|
|
{
|
|
if ((this.dispCnt & (1 << (8 + i))) != 0)
|
|
{
|
|
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
|
|
|
|
if ((bgcnt & 0x3) == pri)
|
|
{
|
|
this.RenderTextBg(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void RenderMode2Line()
|
|
{
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
for (int pri = 3; pri >= 0; pri--)
|
|
{
|
|
for (int i = 3; i >= 2; i--)
|
|
{
|
|
if ((this.dispCnt & (1 << (8 + i))) != 0)
|
|
{
|
|
ushort bgcnt = Memory.ReadU16(this.memory.IORam, Memory.BG0CNT + 0x2 * (uint)i);
|
|
|
|
if ((bgcnt & 0x3) == pri)
|
|
{
|
|
this.RenderRotScaleBg(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void RenderMode3Line()
|
|
{
|
|
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
|
|
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
byte blendMaskType = (byte)(1 << 2);
|
|
|
|
int bgPri = bg2Cnt & 0x3;
|
|
for (int pri = 3; pri > bgPri; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 10)) != 0)
|
|
{
|
|
// Background enabled, render it
|
|
int x = this.memory.Bgx[0];
|
|
int y = this.memory.Bgy[0];
|
|
|
|
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
|
|
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
|
|
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
int ax = ((int)x) >> 8;
|
|
int ay = ((int)y) >> 8;
|
|
|
|
if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
|
|
{
|
|
int curIdx = ((ay * 240) + ax) * 2;
|
|
this.scanline[i] = Renderer.GbaTo32((ushort)(vram[curIdx] | (vram[curIdx + 1] << 8)));
|
|
this.blend[i] = blendMaskType;
|
|
}
|
|
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
}
|
|
|
|
for (int pri = bgPri; pri >= 0; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void RenderMode4Line()
|
|
{
|
|
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
|
|
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
byte blendMaskType = (byte)(1 << 2);
|
|
|
|
int bgPri = bg2Cnt & 0x3;
|
|
for (int pri = 3; pri > bgPri; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 10)) != 0)
|
|
{
|
|
// Background enabled, render it
|
|
int baseIdx = 0;
|
|
if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx = 0xA000;
|
|
|
|
int x = this.memory.Bgx[0];
|
|
int y = this.memory.Bgy[0];
|
|
|
|
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
|
|
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
|
|
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
int ax = ((int)x) >> 8;
|
|
int ay = ((int)y) >> 8;
|
|
|
|
if (ax >= 0 && ax < 240 && ay >= 0 && ay < 160)
|
|
{
|
|
int lookup = vram[baseIdx + (ay * 240) + ax];
|
|
if (lookup != 0)
|
|
{
|
|
this.scanline[i] = Renderer.GbaTo32((ushort)(palette[lookup * 2] | (palette[lookup * 2 + 1] << 8)));
|
|
this.blend[i] = blendMaskType;
|
|
}
|
|
}
|
|
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
}
|
|
|
|
for (int pri = bgPri; pri >= 0; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void RenderMode5Line()
|
|
{
|
|
ushort bg2Cnt = Memory.ReadU16(this.memory.IORam, Memory.BG2CNT);
|
|
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
this.DrawBackdrop();
|
|
|
|
byte blendMaskType = (byte)(1 << 2);
|
|
|
|
int bgPri = bg2Cnt & 0x3;
|
|
for (int pri = 3; pri > bgPri; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
|
|
if ((this.dispCnt & (1 << 10)) != 0)
|
|
{
|
|
// Background enabled, render it
|
|
int baseIdx = 0;
|
|
if ((this.dispCnt & (1 << 4)) == 1 << 4) baseIdx += 160 * 128 * 2;
|
|
|
|
int x = this.memory.Bgx[0];
|
|
int y = this.memory.Bgy[0];
|
|
|
|
short dx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PA);
|
|
short dy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PC);
|
|
|
|
for (int i = 0; i < 240; i++)
|
|
{
|
|
int ax = ((int)x) >> 8;
|
|
int ay = ((int)y) >> 8;
|
|
|
|
if (ax >= 0 && ax < 160 && ay >= 0 && ay < 128)
|
|
{
|
|
int curIdx = (int)(ay * 160 + ax) * 2;
|
|
|
|
this.scanline[i] = Renderer.GbaTo32((ushort)(vram[baseIdx + curIdx] | (vram[baseIdx + curIdx + 1] << 8)));
|
|
this.blend[i] = blendMaskType;
|
|
}
|
|
|
|
x += dx;
|
|
y += dy;
|
|
}
|
|
}
|
|
|
|
for (int pri = bgPri; pri >= 0; pri--)
|
|
{
|
|
this.DrawSprites(pri);
|
|
}
|
|
}
|
|
|
|
private void DrawSpriteWindows()
|
|
{
|
|
byte[] palette = this.memory.PaletteRam;
|
|
byte[] vram = this.memory.VideoRam;
|
|
|
|
// OBJ must be enabled in this.dispCnt
|
|
if ((this.dispCnt & (1 << 12)) == 0) return;
|
|
|
|
for (int oamNum = 127; oamNum >= 0; oamNum--)
|
|
{
|
|
ushort attr0 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 0);
|
|
|
|
// Not an object window, so continue
|
|
if (((attr0 >> 10) & 3) != 2) continue;
|
|
|
|
ushort attr1 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 2);
|
|
ushort attr2 = this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(oamNum * 8) + 4);
|
|
|
|
int x = attr1 & 0x1FF;
|
|
int y = attr0 & 0xFF;
|
|
|
|
int width = -1, height = -1;
|
|
switch ((attr0 >> 14) & 3)
|
|
{
|
|
case 0:
|
|
// Square
|
|
switch ((attr1 >> 14) & 3)
|
|
{
|
|
case 0: width = 8; height = 8; break;
|
|
case 1: width = 16; height = 16; break;
|
|
case 2: width = 32; height = 32; break;
|
|
case 3: width = 64; height = 64; break;
|
|
}
|
|
break;
|
|
case 1:
|
|
// Horizontal Rectangle
|
|
switch ((attr1 >> 14) & 3)
|
|
{
|
|
case 0: width = 16; height = 8; break;
|
|
case 1: width = 32; height = 8; break;
|
|
case 2: width = 32; height = 16; break;
|
|
case 3: width = 64; height = 32; break;
|
|
}
|
|
break;
|
|
case 2:
|
|
// Vertical Rectangle
|
|
switch ((attr1 >> 14) & 3)
|
|
{
|
|
case 0: width = 8; height = 16; break;
|
|
case 1: width = 8; height = 32; break;
|
|
case 2: width = 16; height = 32; break;
|
|
case 3: width = 32; height = 64; break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Check double size flag here
|
|
|
|
int rwidth = width, rheight = height;
|
|
if ((attr0 & (1 << 8)) != 0)
|
|
{
|
|
// Rot-scale on
|
|
if ((attr0 & (1 << 9)) != 0)
|
|
{
|
|
rwidth *= 2;
|
|
rheight *= 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Invalid sprite
|
|
if ((attr0 & (1 << 9)) != 0)
|
|
width = -1;
|
|
}
|
|
|
|
if (width == -1)
|
|
{
|
|
// Invalid sprite
|
|
continue;
|
|
}
|
|
|
|
// Y clipping
|
|
if (y > ((y + rheight) & 0xff))
|
|
{
|
|
if (this.curLine >= ((y + rheight) & 0xff) && !(y < this.curLine)) continue;
|
|
}
|
|
else
|
|
{
|
|
if (this.curLine < y || this.curLine >= ((y + rheight) & 0xff)) continue;
|
|
}
|
|
|
|
int scale = 1;
|
|
if ((attr0 & (1 << 13)) != 0) scale = 2;
|
|
|
|
int spritey = this.curLine - y;
|
|
if (spritey < 0) spritey += 256;
|
|
|
|
if ((attr0 & (1 << 8)) == 0)
|
|
{
|
|
if ((attr1 & (1 << 13)) != 0) spritey = (height - 1) - spritey;
|
|
|
|
int baseSprite;
|
|
if ((this.dispCnt & (1 << 6)) != 0)
|
|
{
|
|
// 1 dimensional
|
|
baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * (width / 8)) * scale;
|
|
}
|
|
else
|
|
{
|
|
// 2 dimensional
|
|
baseSprite = (attr2 & 0x3FF) + ((spritey / 8) * 0x20);
|
|
}
|
|
|
|
int baseInc = scale;
|
|
if ((attr1 & (1 << 12)) != 0)
|
|
{
|
|
baseSprite += ((width / 8) * scale) - scale;
|
|
baseInc = -baseInc;
|
|
}
|
|
|
|
if ((attr0 & (1 << 13)) != 0)
|
|
{
|
|
// 256 colors
|
|
for (int i = x; i < x + width; i++)
|
|
{
|
|
if ((i & 0x1ff) < 240)
|
|
{
|
|
int tx = (i - x) & 7;
|
|
if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
|
|
int curIdx = baseSprite * 32 + ((spritey & 7) * 8) + tx;
|
|
int lookup = vram[0x10000 + curIdx];
|
|
if (lookup != 0)
|
|
{
|
|
this.windowCover[i & 0x1ff] = this.winObjEnabled;
|
|
}
|
|
}
|
|
if (((i - x) & 7) == 7) baseSprite += baseInc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 16 colors
|
|
int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
|
|
for (int i = x; i < x + width; i++)
|
|
{
|
|
if ((i & 0x1ff) < 240)
|
|
{
|
|
int tx = (i - x) & 7;
|
|
if ((attr1 & (1 << 12)) != 0) tx = 7 - tx;
|
|
int curIdx = baseSprite * 32 + ((spritey & 7) * 4) + (tx / 2);
|
|
int lookup = vram[0x10000 + curIdx];
|
|
if ((tx & 1) == 0)
|
|
{
|
|
lookup &= 0xf;
|
|
}
|
|
else
|
|
{
|
|
lookup >>= 4;
|
|
}
|
|
if (lookup != 0)
|
|
{
|
|
this.windowCover[i & 0x1ff] = this.winObjEnabled;
|
|
}
|
|
}
|
|
if (((i - x) & 7) == 7) baseSprite += baseInc;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int rotScaleParam = (attr1 >> 9) & 0x1F;
|
|
|
|
short dx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x6);
|
|
short dmx = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0xE);
|
|
short dy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x16);
|
|
short dmy = (short)this.memory.ReadU16Debug(Memory.OAM_BASE + (uint)(rotScaleParam * 8 * 4) + 0x1E);
|
|
|
|
int cx = rwidth / 2;
|
|
int cy = rheight / 2;
|
|
|
|
int baseSprite = attr2 & 0x3FF;
|
|
int pitch;
|
|
|
|
if ((this.dispCnt & (1 << 6)) != 0)
|
|
{
|
|
// 1 dimensional
|
|
pitch = (width / 8) * scale;
|
|
}
|
|
else
|
|
{
|
|
// 2 dimensional
|
|
pitch = 0x20;
|
|
}
|
|
|
|
short rx = (short)((dmx * (spritey - cy)) - (cx * dx) + (width << 7));
|
|
short ry = (short)((dmy * (spritey - cy)) - (cx * dy) + (height << 7));
|
|
|
|
// Draw a rot/scale sprite
|
|
if ((attr0 & (1 << 13)) != 0)
|
|
{
|
|
// 256 colors
|
|
for (int i = x; i < x + rwidth; i++)
|
|
{
|
|
int tx = rx >> 8;
|
|
int ty = ry >> 8;
|
|
|
|
if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
|
|
{
|
|
int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 8) + (tx & 7);
|
|
int lookup = vram[0x10000 + curIdx];
|
|
if (lookup != 0)
|
|
{
|
|
this.windowCover[i & 0x1ff] = this.winObjEnabled;
|
|
}
|
|
}
|
|
|
|
rx += dx;
|
|
ry += dy;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 16 colors
|
|
int palIdx = 0x200 + (((attr2 >> 12) & 0xF) * 16 * 2);
|
|
for (int i = x; i < x + rwidth; i++)
|
|
{
|
|
int tx = rx >> 8;
|
|
int ty = ry >> 8;
|
|
|
|
if ((i & 0x1ff) < 240 && tx >= 0 && tx < width && ty >= 0 && ty < height)
|
|
{
|
|
int curIdx = (baseSprite + ((ty / 8) * pitch) + ((tx / 8) * scale)) * 32 + ((ty & 7) * 4) + ((tx & 7) / 2);
|
|
int lookup = vram[0x10000 + curIdx];
|
|
if ((tx & 1) == 0)
|
|
{
|
|
lookup &= 0xf;
|
|
}
|
|
else
|
|
{
|
|
lookup >>= 4;
|
|
}
|
|
if (lookup != 0)
|
|
{
|
|
this.windowCover[i & 0x1ff] = this.winObjEnabled;
|
|
}
|
|
}
|
|
|
|
rx += dx;
|
|
ry += dy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public static uint GbaTo32(ushort color)
|
|
{
|
|
// more accurate, but slower :(
|
|
// return colorLUT[color];
|
|
return ((color & 0x1FU) << 19) | ((color & 0x3E0U) << 6) | ((color & 0x7C00U) >> 7);
|
|
}
|
|
}
|
|
}
|