gen: implement WINDOW rendering
gen: initialize VDP registers to power-on values
This commit is contained in:
parent
816435ad2f
commit
f21429b996
|
@ -7,25 +7,50 @@ Timings:
|
|||
- How many cycles does it take to accept an interrupt?
|
||||
- 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.
|
||||
Quackshot doesn't boot.
|
||||
Moonwalker doesn't boot.
|
||||
Altered Beast: start with 0 health, 0 lives???
|
||||
Contra Hard Corps: Scrolling is messed up in level 1... used to work.
|
||||
After Burner 2: No music
|
||||
MUSHA: Intro music starts too soon
|
||||
MUSHA: uses unimplemented VRAM copy
|
||||
MUSHA: Some sprites have messed up left/right symmetry
|
||||
Landstalker: freezes during new game sequence, very early
|
||||
Monster World 4 - Music is messed up now. Timing is all off. It used to work.
|
||||
Quackshot doesn't boot.
|
||||
Moonwalker doesn't boot.
|
||||
Altered Beast: start with 0 health, 0 lives???
|
||||
Contra Hard Corps: Scrolling is messed up in level 1... used to work. Window seemed to mess things up.
|
||||
After Burner 2: No music
|
||||
MUSHA: Intro music starts too soon
|
||||
MUSHA: uses unimplemented VRAM copy
|
||||
MUSHA: Some sprites have messed up left/right symmetry
|
||||
Landstalker: freezes during new game sequence, very early
|
||||
Arcus Odyssey does UNHANDLED Z80 READs... is this a problem?
|
||||
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
|
||||
|
||||
Things that read from VRAM work like 50%-90%, but not 100%. It's frustrating. Kid Chameleon and Eternal Champions are examples.
|
||||
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.
|
||||
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: Add 68000/VDP interrupt enable delay (one instruction, similar to After Burner/PCE)
|
||||
TODO: freaking H-interrupts
|
||||
TODO: Test DMA/ VDP command words.... I'm not at all convinced that VRAM is always correct
|
||||
TODO: non-instant DMA emulation
|
||||
TODO: Add 68000/VDP interrupt enable delay (one instruction, similar to After Burner/PCE)
|
||||
TODO: freaking H-interrupts
|
||||
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.
|
|
@ -26,7 +26,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
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: DMA can go to places besides just VRAM (eg CRAM, VSRAM) ??? can it?
|
||||
|
@ -50,7 +50,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
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;
|
||||
if (length == 0)
|
||||
|
|
|
@ -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 };
|
||||
|
||||
// TODO, should provide startup register values.
|
||||
public void RenderLine()
|
||||
{
|
||||
Array.Clear(PriorityBuffer, 0, 320);
|
||||
int bgcolor = BackgroundColor;
|
||||
for (int ofs = ScanLine * FrameWidth, i = 0; i < FrameWidth; i++, ofs++)
|
||||
FrameBuffer[ofs] = bgcolor;
|
||||
|
||||
if (DisplayEnabled)
|
||||
{
|
||||
|
@ -42,20 +38,25 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
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 nameTableWidth = NameTableWidth;
|
||||
if (nameTableBase == NameTableAddrWindow)
|
||||
nameTableWidth = Display40Mode ? 64 : 32;
|
||||
|
||||
// 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++)
|
||||
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 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 patternNo = nameTableEntry & 0x7FF;
|
||||
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()
|
||||
{
|
||||
// todo scroll values
|
||||
// Calculate scroll offsets
|
||||
|
||||
int hscroll = CalcHScrollPlaneA(ScanLine);
|
||||
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()
|
||||
{
|
||||
int hscroll = CalcHScrollPlaneB(ScanLine);
|
||||
int vscroll = VSRAM[1] & 0x3FF;
|
||||
RenderBackgroundScanline(hscroll, vscroll, NameTableAddrB, 1, 4);
|
||||
int bgColor = BackgroundColor;
|
||||
int xScroll = CalcHScrollPlaneB(ScanLine);
|
||||
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 };
|
||||
|
|
|
@ -14,8 +14,9 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
public byte[] PatternBuffer = new byte[0x20000];
|
||||
public int[] Palette = new int[64];
|
||||
public int[] FrameBuffer = new int[256*224];
|
||||
public int FrameWidth = 256;
|
||||
public int[] FrameBuffer = new int[320*224];
|
||||
public int FrameWidth = 320;
|
||||
public int FrameHeight = 224;
|
||||
|
||||
public int ScanLine;
|
||||
public int HIntLineCounter;
|
||||
|
@ -56,6 +57,19 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
public const int StatusSpriteOverflow = 0x40;
|
||||
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)
|
||||
{
|
||||
switch (addr)
|
||||
|
@ -173,6 +187,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
case CommandVramWrite: // VRAM Write
|
||||
VRAM[VdpDataAddr & 0xFFFE] = (byte) data;
|
||||
VRAM[(VdpDataAddr & 0xFFFE) + 1] = (byte) (data >> 8);
|
||||
//Log.Error("VDP", "VRAM[{0:X4}] = {1:X4}", VdpDataAddr, data);
|
||||
UpdatePatternBuffer(VdpDataAddr & 0xFFFE);
|
||||
UpdatePatternBuffer((VdpDataAddr & 0xFFFE) + 1);
|
||||
//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.
|
||||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -254,22 +270,22 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
case 0x02: // Name Table Address for Layer A
|
||||
NameTableAddrA = (ushort) ((data & 0x38) << 10);
|
||||
//Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
||||
//Log.Error("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
||||
break;
|
||||
|
||||
case 0x03: // Name Table Address for Window
|
||||
NameTableAddrWindow = (ushort) ((data & 0x3E) << 10);
|
||||
//Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
||||
//Log.Error("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
||||
break;
|
||||
|
||||
case 0x04: // Name Table Address for Layer B
|
||||
NameTableAddrB = (ushort) (data << 13);
|
||||
//Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
||||
//Log.Error("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
||||
break;
|
||||
|
||||
case 0x05: // Sprite Attribute Table Address
|
||||
SpriteAttributeTableAddr = (ushort) (data << 9);
|
||||
//Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
||||
//Log.Error("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
||||
break;
|
||||
|
||||
case 0x0A: // H Interrupt Register
|
||||
|
@ -338,7 +354,6 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
case 2: NameTableHeight = 32; break; // invalid setting
|
||||
case 3: NameTableHeight = 128; break;
|
||||
}
|
||||
//Log.Note("VDP", "Name Table Dimensions set to {0}x{1}", NameTableWidth, NameTableHeight);
|
||||
break;
|
||||
|
||||
case 0x11: // Window H Position
|
||||
|
@ -348,8 +363,8 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
break;
|
||||
|
||||
case 0x12: // Window V
|
||||
whp = data & 31;
|
||||
fromright = (data & 0x80) != 0;
|
||||
//whp = data & 31;
|
||||
//fromright = (data & 0x80) != 0;
|
||||
//Log.Error("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
|
||||
break;
|
||||
|
||||
|
@ -408,7 +423,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
public int BufferHeight
|
||||
{
|
||||
get { return 224; }
|
||||
get { return FrameHeight; }
|
||||
}
|
||||
|
||||
public int BackgroundColor
|
||||
|
|
Loading…
Reference in New Issue