From d586876f40a99b8f299528bee256d5ff9676b25f Mon Sep 17 00:00:00 2001 From: beirich Date: Sat, 1 Sep 2012 18:40:52 +0000 Subject: [PATCH] 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 --- .../Consoles/Sega/Genesis/GenVDP.DMA.cs | 27 ++++- .../Consoles/Sega/Genesis/GenVDP.Render.cs | 21 +++- .../Consoles/Sega/Genesis/GenVDP.cs | 109 +++++++++++------- .../Consoles/Sega/Genesis/Genesis.cs | 41 +++++-- 4 files changed, 138 insertions(+), 60 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs index b47c35c7db..201bde56c3 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs @@ -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 + } } } \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs index 2fe8d3e2ae..697b4b383e 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs @@ -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) diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs index ae9497aca4..42233fd89b 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs @@ -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; } diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/Genesis.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/Genesis.cs index c1ec0dfff2..887e87faa1 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/Genesis.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/Genesis.cs @@ -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