/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ #ifndef _PPU_H_ #define _PPU_H_ #define FIRST_VISIBLE_LINE 1 #define TILE_2BIT 0 #define TILE_4BIT 1 #define TILE_8BIT 2 #define TILE_2BIT_EVEN 3 #define TILE_2BIT_ODD 4 #define TILE_4BIT_EVEN 5 #define TILE_4BIT_ODD 6 #define TILE_CACHE_VALID (1 << 7) #define TILE_CACHE_FLIP (1 << 6) #define TILE_CACHE_BLANK (1 << 5) #define MAX_2BIT_TILES 4096 #define MAX_4BIT_TILES 2048 #define MAX_8BIT_TILES 1024 #define CLIP_OR 0 #define CLIP_AND 1 #define CLIP_XOR 2 #define CLIP_XNOR 3 struct ClipData { uint8 Count; uint8 DrawMode[6]; uint16 Left[6]; uint16 Right[6]; }; struct InternalPPU { struct ClipData Clip[2][6]; bool8 OBJChanged; uint8 *TileCacheData; uint8 TileCache[4096]; bool8 Interlace; bool8 InterlaceOBJ; bool8 PseudoHires; int CurrentLine; int PreviousLine; uint8 *XB; SNESPixel ScreenColors[256]; uint8 MaxBrightness; bool8 RenderThisFrame; int RenderedScreenWidth; int RenderedScreenHeight; uint32 FrameCount; uint32 RenderedFramesCount; uint32 DisplayedRenderedFrameCount; uint32 TotalEmulatedFrames; uint32 SkippedFrames; uint32 FrameSkip; }; struct SOBJ { int16 HPos; uint16 VPos; uint8 HFlip; uint8 VFlip; uint16 Name; uint8 Priority; uint8 Palette; uint8 Size; }; struct SPPU { struct { bool8 High; uint32 Increment; uint32 Address; uint32 Mask1; uint32 FullGraphicCount; uint32 Shift; } VMA; uint32 WRAM; struct { uint16 SCBase; uint16 HOffset; uint16 VOffset; uint8 BGSize; uint16 NameBase; uint16 SCSize; } BG[4]; uint8 BGMode; uint8 BG3Priority; bool8 CGFLIP; uint8 CGFLIPRead; uint8 CGADD; uint8 CGSavedByte; uint16 CGDATA[256]; struct SOBJ OBJ[128]; uint16 OBJNameBase; uint16 OBJNameSelect; uint8 OBJSizeSelect; uint16 OAMAddr; uint16 SavedOAMAddr; uint8 OAMPriorityRotation; uint8 OAMFlip; uint8 OAMReadFlip; uint16 OAMTileAddress; uint16 OAMWriteRegister; uint8 OAMData[512 + 32]; uint8 FirstSprite; uint8 LastSprite; uint8 RangeTimeOver; bool8 HTimerEnabled; bool8 VTimerEnabled; short HTimerPosition; short VTimerPosition; uint16 IRQHBeamPos; uint16 IRQVBeamPos; uint8 HBeamFlip; uint8 VBeamFlip; uint16 HBeamPosLatched; uint16 VBeamPosLatched; uint8 HVBeamCounterLatched; bool8 Mode7HFlip; bool8 Mode7VFlip; uint8 Mode7Repeat; int16 MatrixA; int16 MatrixB; int16 MatrixC; int16 MatrixD; int16 CentreX; int16 CentreY; int16 M7HOFS; int16 M7VOFS; uint8 Mosaic; uint8 MosaicStart; bool8 BGMosaic[4]; uint8 Window1Left; uint8 Window1Right; uint8 Window2Left; uint8 Window2Right; bool8 RecomputeClipWindows; uint8 ClipCounts[6]; uint8 ClipWindowOverlapLogic[6]; uint8 ClipWindow1Enable[6]; uint8 ClipWindow2Enable[6]; bool8 ClipWindow1Inside[6]; bool8 ClipWindow2Inside[6]; bool8 ForcedBlanking; uint8 FixedColourRed; uint8 FixedColourGreen; uint8 FixedColourBlue; uint8 Brightness; uint16 ScreenHeight; bool8 Need16x8Mulitply; uint8 BGnxOFSbyte; uint8 M7byte; uint8 HDMA; uint8 HDMAEnded; uint8 OpenBus1; uint8 OpenBus2; uint16 VRAMReadBuffer; }; extern struct SPPU PPU; extern struct InternalPPU IPPU; void S9xResetPPU (void); void S9xResetPPUFast (void); void S9xSoftResetPPU (void); void S9xSetPPU (uint8, uint32); uint8 S9xGetPPU (uint32); void S9xSetCPU (uint8, uint32); uint8 S9xGetCPU (uint32); void S9xUpdateIRQPositions (bool initial); void S9xDoAutoJoypad (void); #include "gfx.h" #include "tile.h" #include "memory.h" static inline void FLUSH_REDRAW (void) { if (IPPU.PreviousLine != IPPU.CurrentLine) S9xUpdateScreen(); } static inline void S9xUpdateVRAMReadBuffer() { if (PPU.VMA.FullGraphicCount) { uint32 addr = PPU.VMA.Address; uint32 rem = addr & PPU.VMA.Mask1; uint32 address = (addr & ~PPU.VMA.Mask1) + (rem >> PPU.VMA.Shift) + ((rem & (PPU.VMA.FullGraphicCount - 1)) << 3); PPU.VRAMReadBuffer = READ_WORD(Memory.VRAM + ((address << 1) & 0xffff)); } else PPU.VRAMReadBuffer = READ_WORD(Memory.VRAM + ((PPU.VMA.Address << 1) & 0xffff)); } static inline void REGISTER_2104 (uint8 Byte) { const uint32 SignExtend[2] = {0x0000, 0xff00}; if (!(PPU.OAMFlip & 1)) { PPU.OAMWriteRegister &= 0xff00; PPU.OAMWriteRegister |= Byte; } if (PPU.OAMAddr & 0x100) { uint32 addr = ((PPU.OAMAddr & 0x10f) << 1) + (PPU.OAMFlip & 1); if (Byte != PPU.OAMData[addr]) { FLUSH_REDRAW(); PPU.OAMData[addr] = Byte; IPPU.OBJChanged = TRUE; // X position high bit, and sprite size (x4) struct SOBJ *pObj = &PPU.OBJ[(addr & 0x1f) * 4]; pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(Byte >> 0) & 1]; pObj++->Size = Byte & 2; pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(Byte >> 2) & 1]; pObj++->Size = Byte & 8; pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(Byte >> 4) & 1]; pObj++->Size = Byte & 32; pObj->HPos = (pObj->HPos & 0xFF) | SignExtend[(Byte >> 6) & 1]; pObj->Size = Byte & 128; } } else if (PPU.OAMFlip & 1) { PPU.OAMWriteRegister &= 0x00ff; uint32 lowbyte = (uint8) (PPU.OAMWriteRegister); uint32 highbyte = Byte; PPU.OAMWriteRegister |= Byte << 8; int addr = (PPU.OAMAddr << 1); if (lowbyte != PPU.OAMData[addr] || highbyte != PPU.OAMData[addr + 1]) { FLUSH_REDRAW(); PPU.OAMData[addr] = lowbyte; PPU.OAMData[addr + 1] = highbyte; IPPU.OBJChanged = TRUE; if (addr & 2) { // Tile PPU.OBJ[addr = PPU.OAMAddr >> 1].Name = PPU.OAMWriteRegister & 0x1ff; // priority, h and v flip. PPU.OBJ[addr].Palette = (highbyte >> 1) & 7; PPU.OBJ[addr].Priority = (highbyte >> 4) & 3; PPU.OBJ[addr].HFlip = (highbyte >> 6) & 1; PPU.OBJ[addr].VFlip = (highbyte >> 7) & 1; } else { // X position (low) PPU.OBJ[addr = PPU.OAMAddr >> 1].HPos &= 0xff00; PPU.OBJ[addr].HPos |= lowbyte; // Sprite Y position PPU.OBJ[addr].VPos = highbyte; } } } PPU.OAMFlip ^= 1; if (!(PPU.OAMFlip & 1)) { ++PPU.OAMAddr; PPU.OAMAddr &= 0x1ff; if (PPU.OAMPriorityRotation && PPU.FirstSprite != (PPU.OAMAddr >> 1)) { PPU.FirstSprite = (PPU.OAMAddr & 0xfe) >> 1; IPPU.OBJChanged = TRUE; } } else { if (PPU.OAMPriorityRotation && (PPU.OAMAddr & 1)) IPPU.OBJChanged = TRUE; } } // This code is correct, however due to Snes9x's inaccurate timings, some games might be broken by this chage. :( #ifdef DEBUGGER #define CHECK_INBLANK() \ if (!PPU.ForcedBlanking && CPU.V_Counter < PPU.ScreenHeight + FIRST_VISIBLE_LINE) \ { \ printf("Invalid VRAM acess at (%04d, %04d) blank:%d\n", CPU.Cycles, CPU.V_Counter, PPU.ForcedBlanking); \ PPU.VMA.Address += !PPU.VMA.High ? PPU.VMA.Increment : 0; \ return; \ } #else #define CHECK_INBLANK() \ if (!PPU.ForcedBlanking && CPU.V_Counter < PPU.ScreenHeight + FIRST_VISIBLE_LINE) \ { \ PPU.VMA.Address += !PPU.VMA.High ? PPU.VMA.Increment : 0; \ return; \ } #endif #define INVALIDATE_TILE_CACHE() \ IPPU.TileCache[address >> 4] = 0; \ IPPU.TileCache[address >> 5] = 0; \ IPPU.TileCache[address >> 6] = 0; \ IPPU.TileCache[((address >> 4) - 1) & (MAX_2BIT_TILES - 1)] = 0; \ IPPU.TileCache[((address >> 5) - 1) & (MAX_4BIT_TILES - 1)] = 0; static inline void REGISTER_2118 (uint8 Byte) { CHECK_INBLANK(); uint32 address; if (PPU.VMA.FullGraphicCount) { uint32 rem = PPU.VMA.Address & PPU.VMA.Mask1; address = (((PPU.VMA.Address & ~PPU.VMA.Mask1) + (rem >> PPU.VMA.Shift) + ((rem & (PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) & 0xffff; Memory.VRAM[address] = Byte; } else Memory.VRAM[address = (PPU.VMA.Address << 1) & 0xffff] = Byte; INVALIDATE_TILE_CACHE(); if (!PPU.VMA.High) { #ifdef DEBUGGER if (Settings.TraceVRAM && !CPU.InDMAorHDMA) printf("VRAM write byte: $%04X (%d, %d)\n", PPU.VMA.Address, Memory.PPU_IO[0x115] & 3, (Memory.PPU_IO[0x115] & 0x0c) >> 2); #endif PPU.VMA.Address += PPU.VMA.Increment; } } static inline void REGISTER_2118_tile (uint8 Byte) { CHECK_INBLANK(); uint32 rem = PPU.VMA.Address & PPU.VMA.Mask1; uint32 address = (((PPU.VMA.Address & ~PPU.VMA.Mask1) + (rem >> PPU.VMA.Shift) + ((rem & (PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) & 0xffff; Memory.VRAM[address] = Byte; INVALIDATE_TILE_CACHE(); if (!PPU.VMA.High) PPU.VMA.Address += PPU.VMA.Increment; } static inline void REGISTER_2118_linear (uint8 Byte) { CHECK_INBLANK(); uint32 address; Memory.VRAM[address = (PPU.VMA.Address << 1) & 0xffff] = Byte; INVALIDATE_TILE_CACHE(); if (!PPU.VMA.High) PPU.VMA.Address += PPU.VMA.Increment; } #undef CHECK_INBLANK #ifdef DEBUGGER #define CHECK_INBLANK() \ if (!PPU.ForcedBlanking && CPU.V_Counter < PPU.ScreenHeight + FIRST_VISIBLE_LINE) \ { \ printf("Invalid VRAM acess at (%04d, %04d) blank:%d\n", CPU.Cycles, CPU.V_Counter, PPU.ForcedBlanking); \ PPU.VMA.Address += PPU.VMA.High ? PPU.VMA.Increment : 0; \ return; \ } #else #define CHECK_INBLANK() \ if (!PPU.ForcedBlanking && CPU.V_Counter < PPU.ScreenHeight + FIRST_VISIBLE_LINE) \ { \ PPU.VMA.Address += PPU.VMA.High ? PPU.VMA.Increment : 0; \ return; \ } #endif static inline void REGISTER_2119 (uint8 Byte) { CHECK_INBLANK(); uint32 address; if (PPU.VMA.FullGraphicCount) { uint32 rem = PPU.VMA.Address & PPU.VMA.Mask1; address = ((((PPU.VMA.Address & ~PPU.VMA.Mask1) + (rem >> PPU.VMA.Shift) + ((rem & (PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) + 1) & 0xffff; Memory.VRAM[address] = Byte; } else Memory.VRAM[address = ((PPU.VMA.Address << 1) + 1) & 0xffff] = Byte; INVALIDATE_TILE_CACHE(); if (PPU.VMA.High) { #ifdef DEBUGGER if (Settings.TraceVRAM && !CPU.InDMAorHDMA) printf("VRAM write word: $%04X (%d, %d)\n", PPU.VMA.Address, Memory.PPU_IO[0x115] & 3, (Memory.PPU_IO[0x115] & 0x0c) >> 2); #endif PPU.VMA.Address += PPU.VMA.Increment; } } static inline void REGISTER_2119_tile (uint8 Byte) { CHECK_INBLANK(); uint32 rem = PPU.VMA.Address & PPU.VMA.Mask1; uint32 address = ((((PPU.VMA.Address & ~PPU.VMA.Mask1) + (rem >> PPU.VMA.Shift) + ((rem & (PPU.VMA.FullGraphicCount - 1)) << 3)) << 1) + 1) & 0xffff; Memory.VRAM[address] = Byte; INVALIDATE_TILE_CACHE(); if (PPU.VMA.High) PPU.VMA.Address += PPU.VMA.Increment; } static inline void REGISTER_2119_linear (uint8 Byte) { CHECK_INBLANK(); uint32 address; Memory.VRAM[address = ((PPU.VMA.Address << 1) + 1) & 0xffff] = Byte; INVALIDATE_TILE_CACHE(); if (PPU.VMA.High) PPU.VMA.Address += PPU.VMA.Increment; } static inline void REGISTER_2122 (uint8 Byte) { if (PPU.CGFLIP) { if ((Byte & 0x7f) != (PPU.CGDATA[PPU.CGADD] >> 8) || PPU.CGSavedByte != (uint8) (PPU.CGDATA[PPU.CGADD] & 0xff)) { FLUSH_REDRAW(); PPU.CGDATA[PPU.CGADD] = (Byte & 0x7f) << 8 | PPU.CGSavedByte; uint8 r = IPPU.XB[PPU.CGSavedByte & 0x1f]; uint8 g = IPPU.XB[(PPU.CGDATA[PPU.CGADD] >> 5) & 0x1f]; uint8 b = IPPU.XB[(Byte >> 2) & 0x1f]; IPPU.ScreenColors[PPU.CGADD] = BUILD_PIXEL(r, g, b); } PPU.CGADD++; } else { PPU.CGSavedByte = Byte; } PPU.CGFLIP ^= 1; } static inline void REGISTER_2180 (uint8 Byte) { Memory.RAM[PPU.WRAM++] = Byte; PPU.WRAM &= 0x1ffff; } static inline uint8 REGISTER_4212 (void) { uint8 byte = 0; if ((CPU.V_Counter >= PPU.ScreenHeight + FIRST_VISIBLE_LINE) && (CPU.V_Counter < PPU.ScreenHeight + FIRST_VISIBLE_LINE + 3)) byte = 1; if ((CPU.Cycles < SNES_HBLANK_END_HC) || (CPU.Cycles >= SNES_HBLANK_START_HC)) byte |= 0x40; if (CPU.V_Counter >= PPU.ScreenHeight + FIRST_VISIBLE_LINE) byte |= 0x80; return (byte); } #endif