From f21429b9964427d76d82878ff9d9e1c73c05fdee Mon Sep 17 00:00:00 2001 From: beirich Date: Sat, 1 Sep 2012 05:02:27 +0000 Subject: [PATCH] gen: implement WINDOW rendering gen: initialize VDP registers to power-on values --- .../Consoles/Sega/Genesis/Compat.txt | 61 +++++--- .../Consoles/Sega/Genesis/GenVDP.DMA.cs | 4 +- .../Consoles/Sega/Genesis/GenVDP.Render.cs | 142 ++++++++++++++++-- .../Consoles/Sega/Genesis/GenVDP.cs | 39 +++-- 4 files changed, 201 insertions(+), 45 deletions(-) diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/Compat.txt b/BizHawk.Emulation/Consoles/Sega/Genesis/Compat.txt index d755ed3058..f2e5690d21 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/Compat.txt +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/Compat.txt @@ -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 - - Things that read from VRAM work like 50%-90%, but not 100%. It's frustrating. Kid Chameleon and Eternal Champions are examples. +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 - 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: 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 \ No newline at end of file +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. \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs index 546f629189..b47c35c7db 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.DMA.cs @@ -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) diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs index 8234307b01..2fe8d3e2ae 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.Render.cs @@ -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 }; diff --git a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs index dd835a56b5..ae9497aca4 100644 --- a/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs +++ b/BizHawk.Emulation/Consoles/Sega/Genesis/GenVDP.cs @@ -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) @@ -157,7 +171,7 @@ namespace BizHawk.Emulation.Consoles.Sega ControlWordPending = false; // byte-swap incoming data when A0 is set - if ((VdpDataAddr & 1) != 0) + if ((VdpDataAddr & 1) != 0) { data = (ushort)((data >> 8) | (data << 8)); 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 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