using System; namespace BizHawk.Emulation.Consoles.Sega { public partial class GenVDP { byte[] PriorityBuffer = new byte[320]; // 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) { RenderScrollA(); RenderScrollB(); RenderSpritesScanline(); } if (ScanLine == 223) // shrug RenderPalette(); } void RenderPalette() { for (int p = 0; p < 4; p++) for (int i = 0; i < 16; i++) FrameBuffer[(p*FrameWidth) + i] = Palette[(p*16) + i]; } void RenderBackgroundScanline(int xScroll, int yScroll, int nameTableBase, int lowPriority, int highPriority) { int yTile = ((ScanLine + yScroll) / 8) % NameTableHeight; int yOfs = (ScanLine + yScroll) % 8; // 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 - xScroll) / 8) % NameTableWidth); int xOfs = Math.Abs(x - xScroll) & 7; 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; bool vFlip = ((nameTableEntry >> 12) & 1) != 0; bool priority = ((nameTableEntry >> 15) & 1) != 0; int palette = (nameTableEntry >> 13) & 3; if (priority && PriorityBuffer[x] >= highPriority) continue; if (PriorityBuffer[x] >= lowPriority) continue; if (vFlip) yOfs = 7 - yOfs; if (hFlip) xOfs = 7 - xOfs; int texel = PatternBuffer[(patternNo * 64) + (yOfs * 8) + (xOfs)]; if (texel == 0) continue; int pixel = Palette[(palette * 16) + texel]; FrameBuffer[(ScanLine * FrameWidth) + x] = pixel; PriorityBuffer[x] = (byte) (priority ? highPriority : lowPriority); } } void RenderScrollA() { // todo scroll values int hscroll = VRAM[HScrollTableAddr + 0] | (VRAM[HScrollTableAddr + 1] << 8); hscroll &= 0x3FFF; //hscroll = 24; int vscroll = VSRAM[0] & 0x3FFF; RenderBackgroundScanline(hscroll, vscroll, NameTableAddrA, 2, 5); } void RenderScrollB() { int hscroll = VRAM[HScrollTableAddr + 2] | (VRAM[HScrollTableAddr + 3] << 8); hscroll &= 0x3FFF; //hscroll = 24; int vscroll = VSRAM[1] & 0x3FFF; RenderBackgroundScanline(hscroll, vscroll, NameTableAddrB, 1, 4); } static readonly int[] SpriteSizeTable = { 8, 16, 24, 32 }; Sprite sprite; void RenderSpritesScanline() { int scanLineBase = ScanLine * FrameWidth; int processedSprites = 0; // This is incredibly unoptimized. TODO... FetchSprite(0); while (true) { if (sprite.Y > ScanLine || sprite.Y+sprite.HeightPixels <= ScanLine) goto nextSprite; if (sprite.X + sprite.WidthPixels <= 0) goto nextSprite; if (sprite.HeightCells == 2) sprite.HeightCells = 2; int yline = ScanLine - sprite.Y; if (sprite.VFlip) yline = sprite.HeightPixels - 1 - yline; int paletteBase = sprite.Palette * 16; if (sprite.HFlip == false) { int pattern = sprite.PatternIndex + ((yline / 8)); for (int xi = 0; xi < sprite.WidthPixels; xi++) { if (sprite.X + xi < 0 || sprite.X + xi >= FrameWidth) continue; if (sprite.Priority && PriorityBuffer[sprite.X + xi] >= 6) continue; if (PriorityBuffer[sprite.X + xi] >= 3) continue; int pixel = PatternBuffer[((pattern + ((xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (xi & 7)]; if (pixel != 0) { FrameBuffer[scanLineBase + sprite.X + xi] = Palette[paletteBase + pixel]; PriorityBuffer[sprite.X + xi] = sprite.Priority ? (byte) 6 : (byte) 3; } } } else { // HFlip int pattern = sprite.PatternIndex + ((yline / 8)) + (sprite.HeightCells * (sprite.WidthCells - 1)); for (int xi = 0; xi < sprite.WidthPixels; xi++) { if (sprite.X + xi < 0 || sprite.X + xi >= FrameWidth) continue; if (sprite.Priority && PriorityBuffer[sprite.X + xi] >= 6) continue; if (PriorityBuffer[sprite.X + xi] >= 3) continue; int pixel = PatternBuffer[((pattern + ((-xi / 8) * sprite.HeightCells)) * 64) + ((yline & 7) * 8) + (7 - (xi & 7))]; if (pixel != 0) { FrameBuffer[scanLineBase + sprite.X + xi] = Palette[paletteBase + pixel]; PriorityBuffer[sprite.X + xi] = sprite.Priority ? (byte) 6 : (byte) 3; } } } nextSprite: if (sprite.Link == 0) break; if (++processedSprites > 80) break; FetchSprite(sprite.Link); } } void FetchSprite(int spriteNo) { int satbase = SpriteAttributeTableAddr + (spriteNo*8); sprite.Y = (VRAM[satbase + 0] | (VRAM[satbase + 1] << 8) & 0x3FF) - 128; sprite.X = (VRAM[satbase + 6] | (VRAM[satbase + 7] << 8) & 0x3FF) - 128; sprite.WidthPixels = SpriteSizeTable[(VRAM[satbase + 3] >> 2) & 3]; sprite.HeightPixels = SpriteSizeTable[VRAM[satbase + 3] & 3]; sprite.WidthCells = ((VRAM[satbase + 3] >> 2) & 3) + 1; sprite.HeightCells = (VRAM[satbase + 3] & 3) + 1; sprite.Link = VRAM[satbase + 2] & 0x7F; sprite.PatternIndex = (VRAM[satbase + 4] | (VRAM[satbase + 5] << 8)) & 0x7FF; sprite.HFlip = ((VRAM[satbase + 5] >> 3) & 1) != 0; sprite.VFlip = ((VRAM[satbase + 5] >> 4) & 1) != 0; sprite.Palette = (VRAM[satbase + 5] >> 5) & 3; sprite.Priority = ((VRAM[satbase + 5] >> 7) & 1) != 0; } struct Sprite { public int X, Y; public int WidthPixels, HeightPixels; public int WidthCells, HeightCells; public int Link; public int Palette; public int PatternIndex; public bool Priority; public bool HFlip; public bool VFlip; } } }