gen: implement H-ints
gen: implement Vram/Vram DMA copy (badly) gen: fix dumb sprite rendering bug gen: fix crash bug with certain WINDOW settings
This commit is contained in:
parent
35ec42073f
commit
d586876f40
|
@ -24,9 +24,9 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
bool DmaFillModePending;
|
||||
|
||||
void ExecuteDmaFill(ushort data)
|
||||
void ExecuteVramFill(ushort data)
|
||||
{
|
||||
//Log.Error("VDP","DMA FILL REQD, WRITE {0:X4}, {1:X4} times, at {2:X4}", data, DmaLength, VdpDataAddr);
|
||||
Log.Note("VDP", "DMA VRAM 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.Error("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
|
||||
Log.Note("VDP", "DMA 68000 -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
|
||||
|
||||
int length = DmaLength;
|
||||
if (length == 0)
|
||||
|
@ -68,5 +68,26 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
// TODO: find correct number of 68k cycles to burn
|
||||
}
|
||||
|
||||
void ExecuteVramVramCopy()
|
||||
{
|
||||
Log.Note("VDP", "DMA VRAM -> VRAM COPY REQ'D. LENGTH {0:X4}, SOURCE {1:X4}", DmaLength, DmaSource);
|
||||
|
||||
int length = DmaLength;
|
||||
if (length == 0)
|
||||
length = 0x10000;
|
||||
|
||||
int source = DmaSource;
|
||||
|
||||
do
|
||||
{
|
||||
ushort value = (ushort)ReadVdpData();
|
||||
source += 2;
|
||||
// TODO funky source behavior
|
||||
WriteVdpData(value);
|
||||
} while (--length > 0);
|
||||
|
||||
// TODO: find correct number of 68k cycles to burn
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,17 +18,22 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
public void RenderLine()
|
||||
{
|
||||
Array.Clear(PriorityBuffer, 0, 320);
|
||||
|
||||
if (DisplayEnabled)
|
||||
{
|
||||
Array.Clear(PriorityBuffer, 0, 320);
|
||||
RenderScrollA();
|
||||
RenderScrollB();
|
||||
RenderSpritesScanline();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If display is disabled, fill in with background color.
|
||||
for (int i = 0; i < FrameWidth; i++)
|
||||
FrameBuffer[(ScanLine * FrameWidth) + i] = BackgroundColor;
|
||||
}
|
||||
|
||||
//if (ScanLine == 223) // shrug
|
||||
//RenderPalette();
|
||||
// RenderPalette();
|
||||
}
|
||||
|
||||
void RenderPalette()
|
||||
|
@ -106,7 +111,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
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;
|
||||
|
@ -118,11 +123,18 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
{
|
||||
startPixel = 0;
|
||||
endPixel = (windowHPosition * 8);
|
||||
if (endPixel > FrameWidth)
|
||||
endPixel = FrameWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPixel = windowHPosition * 8;
|
||||
endPixel = FrameWidth;
|
||||
if (startPixel > FrameWidth)
|
||||
{
|
||||
startPixel = -1;
|
||||
endPixel = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,6 +275,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
continue;
|
||||
|
||||
if (sprite.Priority == false && PriorityBuffer[sprite.X + xi] >= 3) continue;
|
||||
if (PriorityBuffer[sprite.X + xi] == 9) continue;
|
||||
|
||||
int pixel = PatternBuffer[((pattern + ((-xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (7 - (xi & 7))];
|
||||
if (pixel != 0)
|
||||
|
|
|
@ -57,6 +57,8 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
public const int StatusSpriteOverflow = 0x40;
|
||||
public const int StatusVerticalInterruptPending = 0x80;
|
||||
|
||||
public bool VdpDebug = false;
|
||||
|
||||
public GenVDP()
|
||||
{
|
||||
WriteVdpRegister(00, 0x04);
|
||||
|
@ -140,14 +142,13 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
switch (Registers[23] >> 6)
|
||||
{
|
||||
case 2:
|
||||
Log.Note("VDP", "VRAM FILL");
|
||||
DmaFillModePending = true;
|
||||
break;
|
||||
case 3:
|
||||
Log.Error("VDP", "VRAM COPY **** UNIMPLEMENTED ***");
|
||||
Log.Error("VDP", "VRAM COPY **** UNIMPLEMENTED ***");
|
||||
ExecuteVramVramCopy();
|
||||
break;
|
||||
default:
|
||||
Log.Note("VDP", "68k->VRAM COPY");
|
||||
Execute68000VramCopy();
|
||||
break;
|
||||
}
|
||||
|
@ -179,7 +180,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
if (DmaFillModePending)
|
||||
{
|
||||
ExecuteDmaFill(data);
|
||||
ExecuteVramFill(data);
|
||||
}
|
||||
|
||||
switch (VdpDataCode & 7)
|
||||
|
@ -187,21 +188,23 @@ 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);
|
||||
if (VdpDebug)
|
||||
Log.Note("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);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
case CommandCramWrite: // CRAM write
|
||||
CRAM[(VdpDataAddr / 2) % 64] = data;
|
||||
//Console.WriteLine("Wrote CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "Wrote CRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 64, data);
|
||||
ProcessPalette((VdpDataAddr/2)%64);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
case CommandVsramWrite: // VSRAM write
|
||||
VSRAM[(VdpDataAddr / 2) % 40] = data;
|
||||
//Console.WriteLine("Wrote VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "Wrote VSRAM[{0:X2}] = {1:X4}", (VdpDataAddr / 2) % 40, data);
|
||||
VdpDataAddr += Registers[0x0F];
|
||||
break;
|
||||
default:
|
||||
|
@ -243,7 +246,8 @@ 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;
|
||||
int hcounter = (488 - Native68000.Musashi.GetCyclesRemaining()) * 255 / 488;
|
||||
// FIXME: totally utterly wrong.
|
||||
|
||||
ushort res = (ushort) ((vcounter << 8) | (hcounter & 0xFF));
|
||||
//Console.WriteLine("READ HVC: V={0:X2} H={1:X2} ret={2:X4}", vcounter, hcounter, res);
|
||||
|
@ -253,59 +257,72 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
|
||||
public void WriteVdpRegister(int register, byte data)
|
||||
{
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "Register {0}: {1:X2}", register, data);
|
||||
switch (register)
|
||||
{
|
||||
case 0x00: // Mode Set Register 1
|
||||
Registers[register] = data;
|
||||
Log.Error("VDP", "HINT enabled: " + HInterruptsEnabled);
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "HINT enabled: " + HInterruptsEnabled);
|
||||
break;
|
||||
|
||||
case 0x01: // Mode Set Register 2
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled);
|
||||
//Log.Note("VDP", "DmaEnabled: " + DmaEnabled);
|
||||
//Log.Note("VDP", "VINT enabled: " + VInterruptEnabled);
|
||||
if (VdpDebug)
|
||||
{
|
||||
Registers[register] = data;
|
||||
Log.Note("VDP", "DisplayEnabled: " + DisplayEnabled);
|
||||
Log.Note("VDP", "DmaEnabled: " + DmaEnabled);
|
||||
Log.Note("VDP", "VINT enabled: " + VInterruptEnabled);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x02: // Name Table Address for Layer A
|
||||
NameTableAddrA = (ushort) ((data & 0x38) << 10);
|
||||
//Log.Error("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "SET NTa A = {0:X4}", NameTableAddrA);
|
||||
break;
|
||||
|
||||
case 0x03: // Name Table Address for Window
|
||||
NameTableAddrWindow = (ushort) ((data & 0x3E) << 10);
|
||||
//Log.Error("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "SET NTa W = {0:X4}", NameTableAddrWindow);
|
||||
break;
|
||||
|
||||
case 0x04: // Name Table Address for Layer B
|
||||
NameTableAddrB = (ushort) (data << 13);
|
||||
//Log.Error("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "SET NTa B = {0:X4}", NameTableAddrB);
|
||||
break;
|
||||
|
||||
case 0x05: // Sprite Attribute Table Address
|
||||
SpriteAttributeTableAddr = (ushort) (data << 9);
|
||||
//Log.Error("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "SET SAT attr = {0:X4}", SpriteAttributeTableAddr);
|
||||
break;
|
||||
|
||||
case 0x0A: // H Interrupt Register
|
||||
//Log.Note("VDP", "HInt occurs every {0} lines.", data);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "HInt occurs every {0} lines.", data);
|
||||
break;
|
||||
|
||||
case 0x0B: // VScroll/HScroll modes
|
||||
/*if ((data & 4) != 0)
|
||||
Log.Note("VDP", "VSCroll Every 2 Cells Enabled");
|
||||
else
|
||||
Log.Note("VDP", "Full Screen VScroll");*/
|
||||
if (VdpDebug)
|
||||
{
|
||||
if ((data & 4) != 0)
|
||||
Log.Note("VDP", "VSCroll Every 2 Cells Enabled");
|
||||
else
|
||||
Log.Note("VDP", "Full Screen VScroll");
|
||||
|
||||
int hscrollmode = data & 3;
|
||||
//switch (hscrollmode)
|
||||
//{
|
||||
// case 0: Log.Note("VDP", "Full Screen HScroll"); break;
|
||||
// case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); break;
|
||||
// case 2: Log.Note("VDP", "HScroll every 1 cell"); break;
|
||||
// case 3: Log.Note("VDP", "HScroll every 2 cell"); break;
|
||||
//}
|
||||
int hscrollmode = data & 3;
|
||||
switch (hscrollmode)
|
||||
{
|
||||
case 0: Log.Note("VDP", "Full Screen HScroll"); break;
|
||||
case 1: Log.Note("VDP", "Prohibited HSCROLL mode!!! But it'll work."); break;
|
||||
case 2: Log.Note("VDP", "HScroll every 1 cell"); break;
|
||||
case 3: Log.Note("VDP", "HScroll every line"); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0C: // Mode Set #4
|
||||
|
@ -317,7 +334,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
{
|
||||
FrameBuffer = new int[256*224];
|
||||
FrameWidth = 256;
|
||||
//Log.Note("VDP", "SWITCH TO 32 CELL WIDE MODE");
|
||||
Log.Note("VDP", "SWITCH TO 32 CELL WIDE MODE");
|
||||
}
|
||||
} else {
|
||||
// Display is 40 cells wide
|
||||
|
@ -325,18 +342,20 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
{
|
||||
FrameBuffer = new int[320*224];
|
||||
FrameWidth = 320;
|
||||
//Log.Note("VDP", "SWITCH TO 40 CELL WIDE MODE");
|
||||
Log.Note("VDP", "SWITCH TO 40 CELL WIDE MODE");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x0D: // H Scroll Table Address
|
||||
HScrollTableAddr = (ushort) (data << 10);
|
||||
//Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr);
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "SET HScrollTab attr = {0:X4}", HScrollTableAddr);
|
||||
break;
|
||||
|
||||
case 0x0F: // Auto Address Register Increment
|
||||
//Log.Note("VDP", "Set Data Increment to " + data);
|
||||
//if (VdpDebug)
|
||||
Log.Note("VDP", "Set Data Increment to " + data);
|
||||
break;
|
||||
|
||||
case 0x10: // Nametable Dimensions
|
||||
|
@ -359,36 +378,38 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
case 0x11: // Window H Position
|
||||
int whp = data & 31;
|
||||
bool fromright = (data & 0x80) != 0;
|
||||
//Log.Error("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left");
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "Window H is {0} units from {1}", whp, fromright ? "right" : "left");
|
||||
break;
|
||||
|
||||
case 0x12: // Window V
|
||||
//whp = data & 31;
|
||||
//fromright = (data & 0x80) != 0;
|
||||
//Log.Error("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
|
||||
whp = data & 31;
|
||||
fromright = (data & 0x80) != 0;
|
||||
if (VdpDebug)
|
||||
Log.Note("VDP", "Window V is {0} units from {1}", whp, fromright ? "lower" : "upper");
|
||||
break;
|
||||
|
||||
case 0x13: // DMA Length Low
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
break;
|
||||
|
||||
case 0x14: // DMA Length High
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
Log.Note("VDP", "DMA Length = {0:X4}", DmaLength);
|
||||
break;
|
||||
|
||||
case 0x15: // DMA Source Low
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
case 0x16: // DMA Source Mid
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
case 0x17: // DMA Source High
|
||||
Registers[register] = data;
|
||||
//Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
Log.Note("VDP", "DMA Source = {0:X6}", DmaSource);
|
||||
break;
|
||||
|
||||
}
|
||||
|
|
|
@ -147,18 +147,31 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
{
|
||||
//Log.Error("VDP","FRAME {0}, SCANLINE {1}", Frame, VDP.ScanLine);
|
||||
|
||||
if (VDP.ScanLine < 224)
|
||||
if (VDP.ScanLine < VDP.FrameHeight)
|
||||
VDP.RenderLine();
|
||||
|
||||
Exec68k(487);
|
||||
if (Z80Runnable)
|
||||
Exec68k(365);
|
||||
RunZ80(171);
|
||||
|
||||
// H-Int now?
|
||||
|
||||
VDP.HIntLineCounter--;
|
||||
if (VDP.HIntLineCounter < 0)
|
||||
{
|
||||
SoundCPU.ExecuteCycles(228);
|
||||
SoundCPU.Interrupt = false;
|
||||
} else {
|
||||
SoundCPU.TotalExecutedCycles += 228; // I emulate the YM2612 synced to Z80 clock, for better or worse. Keep the timer going even if Z80 isn't running.
|
||||
VDP.HIntLineCounter = VDP.Registers[10];
|
||||
VDP.VdpStatusWord |= GenVDP.StatusHorizBlanking;
|
||||
|
||||
if (VDP.HInterruptsEnabled)
|
||||
{
|
||||
Set68kIrq(4);
|
||||
//Console.WriteLine("Fire hint!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Exec68k(488 - 365);
|
||||
RunZ80(228 - 171);
|
||||
|
||||
if (VDP.ScanLine == 224)
|
||||
{
|
||||
VDP.VdpStatusWord |= GenVDP.StatusVerticalInterruptPending;
|
||||
|
@ -168,8 +181,7 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
if (VDP.VInterruptEnabled)
|
||||
Set68kIrq(6);
|
||||
|
||||
if (Z80Runnable)
|
||||
SoundCPU.Interrupt = true;
|
||||
SoundCPU.Interrupt = true;
|
||||
//The INT output is asserted every frame for exactly one scanline, and it can't be disabled. A very short Z80 interrupt routine would be triggered multiple times if it finishes within 228 Z80 clock cycles. I think (but cannot recall the specifics) that some games have delay loops in the interrupt handler for this very reason.
|
||||
}
|
||||
}
|
||||
|
@ -196,6 +208,17 @@ namespace BizHawk.Emulation.Consoles.Sega
|
|||
#endif
|
||||
}
|
||||
|
||||
void RunZ80(int cycles)
|
||||
{
|
||||
// I emulate the YM2612 synced to Z80 clock, for better or worse.
|
||||
// So we still need to keep the Z80 cycle count accurate even if the Z80 isn't running.
|
||||
|
||||
if (Z80Runnable)
|
||||
SoundCPU.ExecuteCycles(cycles);
|
||||
else
|
||||
SoundCPU.TotalExecutedCycles += cycles;
|
||||
}
|
||||
|
||||
void Set68kIrq(int irq)
|
||||
{
|
||||
#if MUSASHI
|
||||
|
|
Loading…
Reference in New Issue