gen: implement WINDOW rendering

gen: initialize VDP registers to power-on values
This commit is contained in:
beirich 2012-09-01 05:02:27 +00:00
parent 816435ad2f
commit f21429b996
4 changed files with 201 additions and 45 deletions

View File

@ -7,25 +7,50 @@ Timings:
- How many cycles does it take to accept an interrupt? - How many cycles does it take to accept an interrupt?
- AND has some funky timings when it comes to immediates? - AND has some funky timings when it comes to immediates?
GAMES: GAMES:
Monster World 4 - Music is messed up now. Timing is all off. It used to work. Monster World 4 - Music is messed up now. Timing is all off. It used to work.
Quackshot doesn't boot. Quackshot doesn't boot.
Moonwalker doesn't boot. Moonwalker doesn't boot.
Altered Beast: start with 0 health, 0 lives??? Altered Beast: start with 0 health, 0 lives???
Contra Hard Corps: Scrolling is messed up in level 1... used to work. Contra Hard Corps: Scrolling is messed up in level 1... used to work. Window seemed to mess things up.
After Burner 2: No music After Burner 2: No music
MUSHA: Intro music starts too soon MUSHA: Intro music starts too soon
MUSHA: uses unimplemented VRAM copy MUSHA: uses unimplemented VRAM copy
MUSHA: Some sprites have messed up left/right symmetry MUSHA: Some sprites have messed up left/right symmetry
Landstalker: freezes during new game sequence, very early Landstalker: freezes during new game sequence, very early
Arcus Odyssey does UNHANDLED Z80 READs... is this a problem?
Things that read from VRAM work like 50%-90%, but not 100%. It's frustrating. Kid Chameleon and Eternal Champions are examples. ahhh! real monsters - no sound
Alien Storm.... Controls all messed up. Same with Aero the Acrobat.
Devilish/Bad Omen - uses VRAM copy (unimpl)
Blood Shot - FPS game - some texture corruption
Some games flicker in the rightmost columns. Is this caused by mid-frame mode shifting(32/40 col modes?) Alisia Dragoon is one example. Things that read from VRAM work like 50%-90%, but not 100%. It's frustrating. Kid Chameleon and Eternal Champions are examples.
Some games flicker in the rightmost columns. Is this caused by mid-frame mode shifting(32/40 col modes?) Alisia Dragoon is one example.
TODO: non-instant DMA emulation TODO: non-instant DMA emulation
TODO: Add 68000/VDP interrupt enable delay (one instruction, similar to After Burner/PCE) TODO: Add 68000/VDP interrupt enable delay (one instruction, similar to After Burner/PCE)
TODO: freaking H-interrupts TODO: freaking H-interrupts
TODO: Test DMA/ VDP command words.... I'm not at all convinced that VRAM is always correct TODO: Test DMA/ VDP command words.... I'm not at all convinced that VRAM is always correct
==============
Notable games:
==============
Ghouls n Ghosts sets up the graphics planes backwards from normal, by setting the plane A to be low priority and Plane B to be high priority.
If you have a bug in your priority code this may find it.
Revenge of Shinobi will not play DAC sounds if YM2612 registers are not initialized to L/R channels enabled.
Ballz doesnt really initialize hardly any VDP registers, relies on VDP registers powered-on to the correct values
Contra appears to use VDP A0 set = byte-swap. Not sure if its important in anyway in that game, but the byte swap happens.

View File

@ -26,7 +26,7 @@ namespace BizHawk.Emulation.Consoles.Sega
void ExecuteDmaFill(ushort data) void ExecuteDmaFill(ushort data)
{ {
Log.Note("VDP","DMA FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr); //Log.Error("VDP","DMA FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr);
// TODO: It should spread this out, not do it all at once. // TODO: It should spread this out, not do it all at once.
// TODO: DMA can go to places besides just VRAM (eg CRAM, VSRAM) ??? can it? // TODO: DMA can go to places besides just VRAM (eg CRAM, VSRAM) ??? can it?
@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Consoles.Sega
void Execute68000VramCopy() void Execute68000VramCopy()
{ {
Log.Note("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource); //Log.Error("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
int length = DmaLength; int length = DmaLength;
if (length == 0) if (length == 0)

View File

@ -16,13 +16,9 @@ namespace BizHawk.Emulation.Consoles.Sega
static readonly byte[] PalXlatTable = { 0, 0, 36, 36, 73, 73, 109, 109, 145, 145, 182, 182, 219, 219, 255, 255 }; static readonly byte[] PalXlatTable = { 0, 0, 36, 36, 73, 73, 109, 109, 145, 145, 182, 182, 219, 219, 255, 255 };
// TODO, should provide startup register values.
public void RenderLine() public void RenderLine()
{ {
Array.Clear(PriorityBuffer, 0, 320); Array.Clear(PriorityBuffer, 0, 320);
int bgcolor = BackgroundColor;
for (int ofs = ScanLine * FrameWidth, i = 0; i < FrameWidth; i++, ofs++)
FrameBuffer[ofs] = bgcolor;
if (DisplayEnabled) if (DisplayEnabled)
{ {
@ -42,20 +38,25 @@ namespace BizHawk.Emulation.Consoles.Sega
FrameBuffer[(p*FrameWidth) + i] = Palette[(p*16) + i]; FrameBuffer[(p*FrameWidth) + i] = Palette[(p*16) + i];
} }
void RenderBackgroundScanline(int xScroll, int yScroll, int nameTableBase, int lowPriority, int highPriority) void RenderScrollAScanline(int xScroll, int yScroll, int nameTableBase, int startPixel, int endPixel)
{ {
const int lowPriority = 2;
const int highPriority = 5;
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight; int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
int nameTableWidth = NameTableWidth;
if (nameTableBase == NameTableAddrWindow)
nameTableWidth = Display40Mode ? 64 : 32;
// this is hellllla slow. but not optimizing until we implement & understand // this is hellllla slow. but not optimizing until we implement & understand
// all scrolling modes, shadow & hilight, etc. // all scrolling modes, shadow & hilight, etc.
// in thinking about this, you could convince me to optimize the PCE background renderer now. // in thinking about this, you could convince me to optimize the PCE background renderer now.
// Its way simple in comparison. But the PCE sprite renderer is way worse than gen. // Its way simple in comparison. But the PCE sprite renderer is way worse than gen.
for (int x = 0; x < FrameWidth; x++) for (int x = startPixel; x < endPixel; x++)
{ {
int xTile = Math.Abs(((x + (1024-xScroll)) / 8) % NameTableWidth); int xTile = Math.Abs(((x + (1024-xScroll)) / 8) % nameTableWidth);
int xOfs = Math.Abs((x + (1024-xScroll)) & 7); int xOfs = Math.Abs((x + (1024-xScroll)) & 7);
int yOfs = (ScanLine + yScroll) % 8; int yOfs = (ScanLine + yScroll) % 8;
int cellOfs = nameTableBase + (yTile * NameTableWidth * 2) + (xTile * 2); int cellOfs = nameTableBase + (yTile * nameTableWidth * 2) + (xTile * 2);
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs+1] << 8); int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs+1] << 8);
int patternNo = nameTableEntry & 0x7FF; int patternNo = nameTableEntry & 0x7FF;
bool hFlip = ((nameTableEntry >> 11) & 1) != 0; bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
@ -77,19 +78,134 @@ namespace BizHawk.Emulation.Consoles.Sega
} }
} }
void CalculateWindowScanlines(out int startScanline, out int endScanline)
{
int data = Registers[0x12];
int windowVPosition = data & 31;
bool fromTop = (data & 0x80) == 0;
if (windowVPosition == 0)
{
startScanline = -1;
endScanline = -1;
return;
}
if (fromTop)
{
startScanline = 0;
endScanline = (windowVPosition * 8);
} else {
startScanline = windowVPosition * 8;
endScanline = FrameHeight;
}
}
void CalculateWindowPosition(out int startPixel, out int endPixel)
{
int data = Registers[0x11];
int windowHPosition = (data & 31) * 2; // Window H position is set in 2-cell increments
bool fromLeft = (data & 0x80) == 0;
if (windowHPosition == 0)
{
startPixel = -1;
endPixel = -1;
return;
}
if (fromLeft)
{
startPixel = 0;
endPixel = (windowHPosition * 8);
}
else
{
startPixel = windowHPosition * 8;
endPixel = FrameWidth;
}
}
void RenderScrollA() void RenderScrollA()
{ {
// todo scroll values // Calculate scroll offsets
int hscroll = CalcHScrollPlaneA(ScanLine); int hscroll = CalcHScrollPlaneA(ScanLine);
int vscroll = VSRAM[0] & 0x3FF; int vscroll = VSRAM[0] & 0x3FF;
RenderBackgroundScanline(hscroll, vscroll, NameTableAddrA, 2, 5);
// Calculate window dimensions
int startWindowScanline, endWindowScanline;
int startWindowPixel, endWindowPixel;
CalculateWindowScanlines(out startWindowScanline, out endWindowScanline);
CalculateWindowPosition(out startWindowPixel, out endWindowPixel);
// Render scanline
if (ScanLine >= startWindowScanline && ScanLine < endWindowScanline) // Window takes up whole scanline
{
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, FrameWidth);
}
else if (startWindowPixel != -1) // Window takes up partial scanline
{
if (startWindowPixel == 0) // Window grows from left side
{
RenderScrollAScanline(0, 0, NameTableAddrWindow, 0, endWindowPixel);
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, endWindowPixel, FrameWidth);
}
else // Window grows from right side
{
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, 0, startWindowPixel);
RenderScrollAScanline(0, 0, NameTableAddrWindow, startWindowPixel, FrameWidth);
}
}
else // No window
{
RenderScrollAScanline(hscroll, vscroll, NameTableAddrA, 0, FrameWidth);
}
} }
void RenderScrollB() void RenderScrollB()
{ {
int hscroll = CalcHScrollPlaneB(ScanLine); int bgColor = BackgroundColor;
int vscroll = VSRAM[1] & 0x3FF; int xScroll = CalcHScrollPlaneB(ScanLine);
RenderBackgroundScanline(hscroll, vscroll, NameTableAddrB, 1, 4); int yScroll = VSRAM[1] & 0x3FF;
const int lowPriority = 1;
const int highPriority = 4;
int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight;
// this is hellllla slow. but not optimizing until we implement & understand
// all scrolling modes, shadow & hilight, etc.
// in thinking about this, you could convince me to optimize the PCE background renderer now.
// Its way simple in comparison. But the PCE sprite renderer is way worse than gen.
for (int x = 0; x < FrameWidth; x++)
{
int xTile = Math.Abs(((x + (1024 - xScroll)) / 8) % NameTableWidth);
int xOfs = Math.Abs((x + (1024 - xScroll)) & 7);
int yOfs = (ScanLine + yScroll) % 8;
int cellOfs = NameTableAddrB + (yTile * NameTableWidth * 2) + (xTile * 2);
int nameTableEntry = VRAM[cellOfs] | (VRAM[cellOfs + 1] << 8);
int patternNo = nameTableEntry & 0x7FF;
bool hFlip = ((nameTableEntry >> 11) & 1) != 0;
bool vFlip = ((nameTableEntry >> 12) & 1) != 0;
bool priority = ((nameTableEntry >> 15) & 1) != 0;
int palette = (nameTableEntry >> 13) & 3;
if (priority && PriorityBuffer[x] >= highPriority) continue;
if (!priority && PriorityBuffer[x] >= lowPriority) continue;
if (vFlip) yOfs = 7 - yOfs;
if (hFlip) xOfs = 7 - xOfs;
int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)];
int pixel = Palette[(palette * 16) + texel];
if (texel == 0)
pixel = bgColor;
FrameBuffer[(ScanLine * FrameWidth) + x] = pixel;
PriorityBuffer[x] = (byte)(priority ? highPriority : lowPriority);
}
} }
static readonly int[] SpriteSizeTable = { 8, 16, 24, 32 }; static readonly int[] SpriteSizeTable = { 8, 16, 24, 32 };

View File

@ -14,8 +14,9 @@ namespace BizHawk.Emulation.Consoles.Sega
public byte[] PatternBuffer = new byte[0x20000]; public byte[] PatternBuffer = new byte[0x20000];
public int[] Palette = new int[64]; public int[] Palette = new int[64];
public int[] FrameBuffer = new int[256*224]; public int[] FrameBuffer = new int[320*224];
public int FrameWidth = 256; public int FrameWidth = 320;
public int FrameHeight = 224;
public int ScanLine; public int ScanLine;
public int HIntLineCounter; public int HIntLineCounter;
@ -56,6 +57,19 @@ namespace BizHawk.Emulation.Consoles.Sega
public const int StatusSpriteOverflow = 0x40; public const int StatusSpriteOverflow = 0x40;
public const int StatusVerticalInterruptPending = 0x80; public const int StatusVerticalInterruptPending = 0x80;
public GenVDP()
{
WriteVdpRegister(00, 0x04);
WriteVdpRegister(01, 0x04);
WriteVdpRegister(02, 0x30);
WriteVdpRegister(03, 0x3C);
WriteVdpRegister(04, 0x07);
WriteVdpRegister(05, 0x67);
WriteVdpRegister(10, 0xFF);
WriteVdpRegister(12, 0x81);
WriteVdpRegister(15, 0x02);
}
public ushort ReadVdp(int addr) public ushort ReadVdp(int addr)
{ {
switch (addr) switch (addr)
@ -157,7 +171,7 @@ namespace BizHawk.Emulation.Consoles.Sega
ControlWordPending = false; ControlWordPending = false;
// byte-swap incoming data when A0 is set // byte-swap incoming data when A0 is set
if ((VdpDataAddr & 1) != 0) if ((VdpDataAddr & 1) != 0)
{ {
data = (ushort)((data >> 8) | (data << 8)); data = (ushort)((data >> 8) | (data << 8));
Console.WriteLine("VRAM byte-swap is happening because A0 is not 0"); Console.WriteLine("VRAM byte-swap is happening because A0 is not 0");
@ -173,6 +187,7 @@ namespace BizHawk.Emulation.Consoles.Sega
case CommandVramWrite: // VRAM Write case CommandVramWrite: // VRAM Write
VRAM[VdpDataAddr & 0xFFFE] = (byte) data; VRAM[VdpDataAddr & 0xFFFE] = (byte) data;
VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte) (data >> 8); VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte) (data >> 8);
//Log.Error("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
UpdatePatternBuffer(VdpDataAddr & 0xFFFE); UpdatePatternBuffer(VdpDataAddr & 0xFFFE);
UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1); UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1);
//Console.WriteLine("Wrote VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data); //Console.WriteLine("Wrote VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
@ -227,10 +242,11 @@ namespace BizHawk.Emulation.Consoles.Sega
// TODO dont tie this to musashi cycle count. // TODO dont tie this to musashi cycle count.
// Figure out a "clean" way to get cycle counter information available to VDP. // Figure out a "clean" way to get cycle counter information available to VDP.
// Oh screw that. The VDP and the cpu cycle counters are going to be intertwined pretty tightly.
int hcounter = (487 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 487; int hcounter = (487 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 487;
ushort res = (ushort) ((vcounter << 8) | (hcounter & 0xFF)); ushort res = (ushort) ((vcounter << 8) | (hcounter & 0xFF));
Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res); //Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res);
return res; return res;
} }
@ -254,22 +270,22 @@ namespace BizHawk.Emulation.Consoles.Sega
case 0x02: // Name Table Address for Layer A case 0x02: // Name Table Address for Layer A
NameTableAddrA = (ushort) ((data & 0x38) << 10); NameTableAddrA = (ushort) ((data & 0x38) << 10);
//Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA); //Log.Error("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
break; break;
case 0x03: // Name Table Address for Window case 0x03: // Name Table Address for Window
NameTableAddrWindow = (ushort) ((data & 0x3E) << 10); NameTableAddrWindow = (ushort) ((data & 0x3E) << 10);
//Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow); //Log.Error("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
break; break;
case 0x04: // Name Table Address for Layer B case 0x04: // Name Table Address for Layer B
NameTableAddrB = (ushort) (data << 13); NameTableAddrB = (ushort) (data << 13);
//Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB); //Log.Error("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
break; break;
case 0x05: // Sprite Attribute Table Address case 0x05: // Sprite Attribute Table Address
SpriteAttributeTableAddr = (ushort) (data << 9); SpriteAttributeTableAddr = (ushort) (data << 9);
//Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr); //Log.Error("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
break; break;
case 0x0A: // H Interrupt Register case 0x0A: // H Interrupt Register
@ -338,7 +354,6 @@ namespace BizHawk.Emulation.Consoles.Sega
case 2: NameTableHeight = 32; break; // invalid setting case 2: NameTableHeight = 32; break; // invalid setting
case 3: NameTableHeight = 128; break; case 3: NameTableHeight = 128; break;
} }
//Log.Note("VDP", "Name Table Dimensions set to {0}x{1}", NameTableWidth, NameTableHeight);
break; break;
case 0x11: // Window H Position case 0x11: // Window H Position
@ -348,8 +363,8 @@ namespace BizHawk.Emulation.Consoles.Sega
break; break;
case 0x12: // Window V case 0x12: // Window V
whp = data & 31; //whp = data & 31;
fromright = (data & 0x80) != 0; //fromright = (data & 0x80) != 0;
//Log.Error("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper"); //Log.Error("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
break; break;
@ -408,7 +423,7 @@ namespace BizHawk.Emulation.Consoles.Sega
public int BufferHeight public int BufferHeight
{ {
get { return 224; } get { return FrameHeight; }
} }
public int BackgroundColor public int BackgroundColor