mirror of https://github.com/AxioDL/metaforce.git
1431 lines
48 KiB
C
1431 lines
48 KiB
C
/*
|
|
* Copyright (C) 2017 FIX94
|
|
*
|
|
* This software may be modified and distributed under the terms
|
|
* of the MIT license. See the LICENSE file for details.
|
|
*/
|
|
|
|
/*
|
|
* URDE modifications to generate RGBA8 framebuffer rather than BGR565
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <inttypes.h>
|
|
#include <string.h>
|
|
#include "fixNES/mapper.h"
|
|
#include "fixNES/ppu.h"
|
|
|
|
//certain optimizations were taken from nestopias ppu code,
|
|
//thanks to the people from there for all that
|
|
|
|
//sprite byte 2
|
|
#define PPU_SPRITE_PRIO (1<<5)
|
|
#define PPU_SPRITE_FLIP_X (1<<6)
|
|
#define PPU_SPRITE_FLIP_Y (1<<7)
|
|
|
|
//2000
|
|
#define PPU_INC_AMOUNT (1<<2)
|
|
#define PPU_SPRITE_ADDR (1<<3)
|
|
#define PPU_BACKGROUND_ADDR (1<<4)
|
|
#define PPU_SPRITE_8_16 (1<<5)
|
|
#define PPU_FLAG_NMI (1<<7)
|
|
|
|
//2001
|
|
#define PPU_GRAY (1<<0)
|
|
#define PPU_BG_8PX (1<<1)
|
|
#define PPU_SPRITE_8PX (1<<2)
|
|
#define PPU_BG_ENABLE (1<<3)
|
|
#define PPU_SPRITE_ENABLE (1<<4)
|
|
|
|
//2002
|
|
#define PPU_FLAG_OVERFLOW (1<<5)
|
|
#define PPU_FLAG_SPRITEZERO (1<<6)
|
|
#define PPU_FLAG_VBLANK (1<<7)
|
|
|
|
#define DOTS 341
|
|
|
|
#define VISIBLE_DOTS 256
|
|
#define VISIBLE_LINES 240
|
|
|
|
#define PPU_VRAM_HORIZONTAL_MASK 0x41F
|
|
#define PPU_VRAM_VERTICAL_MASK (~PPU_VRAM_HORIZONTAL_MASK)
|
|
|
|
#define PPU_DEBUG_ULTRA 0
|
|
|
|
#define PPU_DEBUG_VSYNC 0
|
|
|
|
//set or used externally
|
|
bool ppu4Screen = false;
|
|
bool ppu816Sprite = false;
|
|
bool ppuInFrame = false;
|
|
bool ppuScanlineDone = false;
|
|
uint8_t ppuDrawnXTile = 0;
|
|
|
|
//from main.c
|
|
extern uint32_t textureImage[0xF000];
|
|
extern bool nesPause;
|
|
extern bool ppuDebugPauseFrame;
|
|
extern bool doOverscan;
|
|
|
|
static uint8_t ppuDoSprites(uint8_t color, uint16_t dot);
|
|
|
|
// BMF Final 2
|
|
static const uint8_t PPU_Pal[192] =
|
|
{
|
|
0x52, 0x52, 0x52, 0x00, 0x00, 0x80, 0x08, 0x00, 0x8A, 0x2C, 0x00, 0x7E, 0x4A, 0x00, 0x4E, 0x50, 0x00, 0x06, 0x44, 0x00, 0x00, 0x26, 0x08, 0x00,
|
|
0x0A, 0x20, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x32, 0x00, 0x00, 0x26, 0x0A, 0x00, 0x1C, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xA4, 0xA4, 0xA4, 0x00, 0x38, 0xCE, 0x34, 0x16, 0xEC, 0x5E, 0x04, 0xDC, 0x8C, 0x00, 0xB0, 0x9A, 0x00, 0x4C, 0x90, 0x18, 0x00, 0x70, 0x36, 0x00,
|
|
0x4C, 0x54, 0x00, 0x0E, 0x6C, 0x00, 0x00, 0x74, 0x00, 0x00, 0x6C, 0x2C, 0x00, 0x5E, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0xFF, 0x4C, 0x9C, 0xFF, 0x7C, 0x78, 0xFF, 0xA6, 0x64, 0xFF, 0xDA, 0x5A, 0xFF, 0xF0, 0x54, 0xC0, 0xF0, 0x6A, 0x56, 0xD6, 0x86, 0x10,
|
|
0xBA, 0xA4, 0x00, 0x76, 0xC0, 0x00, 0x46, 0xCC, 0x1A, 0x2E, 0xC8, 0x66, 0x34, 0xC2, 0xBE, 0x3A, 0x3A, 0x3A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xFF, 0xFF, 0xB6, 0xDA, 0xFF, 0xC8, 0xCA, 0xFF, 0xDA, 0xC2, 0xFF, 0xF0, 0xBE, 0xFF, 0xFC, 0xBC, 0xEE, 0xFA, 0xC2, 0xC0, 0xF2, 0xCC, 0xA2,
|
|
0xE6, 0xDA, 0x92, 0xCC, 0xE6, 0x8E, 0xB8, 0xEE, 0xA2, 0xAE, 0xEA, 0xBE, 0xAE, 0xE8, 0xE2, 0xB0, 0xB0, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
} ;
|
|
|
|
static const uint8_t ppuRunCyclesNTSC[5] = { 3, 3, 3, 3, 3 };
|
|
static const uint8_t ppuRunCyclesPAL[5] = { 3, 3, 3, 3, 4 };
|
|
static const uint8_t ppuOddArrNTSC[2] = { 0, 1 };
|
|
static const uint8_t ppuOddArrPAL[2] = { 0, 0 };
|
|
|
|
static struct
|
|
{
|
|
const uint8_t *Pal;
|
|
const uint8_t *RunCycles;
|
|
const uint8_t *OddArr;
|
|
uint32_t BGRLUT[0x200];
|
|
uint8_t TILELUT[0x400][4];
|
|
uint16_t PALRAM2[0x20];
|
|
uint16_t NameTbl[4];
|
|
uint16_t NameTblBak[4];
|
|
uint8_t VRAM[0x1000];
|
|
uint8_t OAM[0x100];
|
|
uint8_t OAM2[0x20];
|
|
uint8_t PALRAM[0x20];
|
|
uint8_t Sprites[8][12];
|
|
uint8_t BGTiles[16];
|
|
uint8_t Reg[8];
|
|
uint8_t Count;
|
|
uint8_t OddNum;
|
|
uint16_t curLine;
|
|
uint16_t LinesTotal;
|
|
uint16_t PreRenderLine;
|
|
uint16_t curDot;
|
|
uint16_t VramAddr;
|
|
uint16_t TmpVramAddr;
|
|
uint8_t ToDraw;
|
|
uint8_t VramReadBuf;
|
|
uint8_t OAMpos;
|
|
uint8_t OAM2pos;
|
|
uint8_t OAMcpPos;
|
|
uint8_t OAMzSpritePos;
|
|
uint8_t FineXScroll;
|
|
uint8_t SpriteOAM2Pos;
|
|
uint8_t SpriteTilePos;
|
|
uint8_t BGRegA;
|
|
uint8_t BGRegB;
|
|
uint8_t BGAttribReg;
|
|
uint8_t BGIndex;
|
|
uint8_t BytesCopied;
|
|
uint8_t lastVal;
|
|
uint8_t CurSpriteLn;
|
|
uint8_t CurSpriteIndex;
|
|
uint8_t CurSpriteByte2;
|
|
uint8_t CurSpriteByte3;
|
|
uint8_t TmpOAMVal;
|
|
bool NextHasZSprite;
|
|
bool FrameDone;
|
|
bool TmpWrite;
|
|
bool NMIallowed;
|
|
bool NMITriggered;
|
|
bool VBlankFlagCleared;
|
|
bool VBlankClearCycle;
|
|
bool CurNMIStat;
|
|
bool OddFrame;
|
|
bool ReadReg2;
|
|
bool OAMoverflow;
|
|
bool SearchY;
|
|
bool LineOverflow;
|
|
bool DoOverscan;
|
|
bool BGEnable;
|
|
bool SprEnable;
|
|
} ppu;
|
|
|
|
extern bool nesPAL;
|
|
void ppuInit()
|
|
{
|
|
memset(ppu.PALRAM2,0,0x40);
|
|
memset(ppu.NameTbl,0,8);
|
|
memset(ppu.NameTblBak,0,8);
|
|
memset(ppu.VRAM,0,0x1000);
|
|
memset(ppu.OAM,0,0x100);
|
|
memset(ppu.OAM2,0xFF,0x20);
|
|
memset(ppu.PALRAM,0,0x20);
|
|
int32_t i;
|
|
for(i = 0; i < 8; i++)
|
|
memset(ppu.Sprites[i],0,12);
|
|
memset(ppu.BGTiles,0,0x10);
|
|
memset(ppu.Reg,0,8);
|
|
ppu.Pal = PPU_Pal;
|
|
//ppuCycles = 0;
|
|
//start out being in vblank
|
|
ppu.Reg[2] |= PPU_FLAG_VBLANK;
|
|
ppu.LinesTotal = nesPAL ? 312 : 262;
|
|
ppu.PreRenderLine = ppu.LinesTotal - 1;
|
|
ppu.curLine = ppu.LinesTotal - 11;
|
|
ppu.curDot = 0;
|
|
ppu.VramAddr = 0;
|
|
ppu.TmpVramAddr = 0;
|
|
ppu.ToDraw = 0;
|
|
ppu.VramReadBuf = 0;
|
|
ppu.OAMpos = 0;
|
|
ppu.OAM2pos = 0;
|
|
ppu.OAMcpPos = 0;
|
|
ppu.OAMzSpritePos = 0;
|
|
ppu.FineXScroll = 0;
|
|
ppu.SpriteOAM2Pos = 0;
|
|
ppu.SpriteTilePos = 0;
|
|
ppu.BGRegA = 0;
|
|
ppu.BGRegB = 0;
|
|
ppu.BGAttribReg = 0;
|
|
ppu.BGIndex = 0;
|
|
ppu.BytesCopied = 0;
|
|
ppu.lastVal = 0;
|
|
ppu.CurSpriteLn = 0;
|
|
ppu.CurSpriteIndex = 0;
|
|
ppu.CurSpriteByte2 = 0;
|
|
ppu.CurSpriteByte3 = 0;
|
|
ppu.TmpOAMVal = 0xFF;
|
|
ppu.NextHasZSprite = false;
|
|
ppu.FrameDone = false;
|
|
ppu.TmpWrite = false;
|
|
ppu.NMIallowed = false;
|
|
ppu.NMITriggered = false;
|
|
ppu.VBlankFlagCleared = false;
|
|
ppu.VBlankClearCycle = false;
|
|
ppu.CurNMIStat = false;
|
|
ppu.OddFrame = false;
|
|
ppu.ReadReg2 = false;
|
|
ppu.OAMoverflow = false;
|
|
ppu.SearchY = false;
|
|
ppu.LineOverflow = false;
|
|
ppu.DoOverscan = false;
|
|
ppu.BGEnable = false;
|
|
ppu.SprEnable = false;
|
|
//generate full BGR LUT
|
|
uint8_t rtint = nesPAL ? (1<<6) : (1<<5);
|
|
uint8_t gtint = nesPAL ? (1<<5) : (1<<6);
|
|
uint8_t btint = (1<<7);
|
|
for(i = 0; i < 0x200; i++)
|
|
{
|
|
uint8_t palpos = (i&0x3F)*3;
|
|
uint8_t r = ppu.Pal[palpos];
|
|
uint8_t g = ppu.Pal[palpos+1];
|
|
uint8_t b = ppu.Pal[palpos+2];
|
|
if((i & 0xF) <= 0xD)
|
|
{
|
|
//reduce red
|
|
if((i>>1) & btint) r = (uint8_t)(((float)r)*0.75f);
|
|
if((i>>1) & gtint) r = (uint8_t)(((float)r)*0.75f);
|
|
//reduce green
|
|
if((i>>1) & rtint) g = (uint8_t)(((float)g)*0.75f);
|
|
if((i>>1) & btint) g = (uint8_t)(((float)g)*0.75f);
|
|
//reduce blue
|
|
if((i>>1) & rtint) b = (uint8_t)(((float)b)*0.75f);
|
|
if((i>>1) & gtint) b = (uint8_t)(((float)b)*0.75f);
|
|
}
|
|
//save new color into LUT
|
|
ppu.BGRLUT[i] =
|
|
(0xFFu<<24) //Alpha
|
|
| (b<<16) //Blue
|
|
| (g<<8) //Green
|
|
| r; //Red
|
|
}
|
|
//tile LUT from nestopia
|
|
for (i = 0; i < 0x400; ++i)
|
|
{
|
|
ppu.TILELUT[i][0] = (i & 0xC0) ? (i >> 6 & 0xC) | (i >> 6 & 0x3) : 0;
|
|
ppu.TILELUT[i][1] = (i & 0x30) ? (i >> 6 & 0xC) | (i >> 4 & 0x3) : 0;
|
|
ppu.TILELUT[i][2] = (i & 0x0C) ? (i >> 6 & 0xC) | (i >> 2 & 0x3) : 0;
|
|
ppu.TILELUT[i][3] = (i & 0x03) ? (i >> 6 & 0xC) | (i >> 0 & 0x3) : 0;
|
|
}
|
|
ppu.Count = 0;
|
|
ppu.RunCycles = nesPAL ? ppuRunCyclesPAL : ppuRunCyclesNTSC;
|
|
ppu.OddNum = 0;
|
|
ppu.OddArr = nesPAL ? ppuOddArrPAL : ppuOddArrNTSC;
|
|
}
|
|
|
|
extern uint8_t m5_exMode;
|
|
extern uint8_t m5exGetAttrib(uint16_t addr);
|
|
static inline uint16_t ppuGetVramTbl(uint16_t tblStart)
|
|
{
|
|
return ppu.NameTbl[(tblStart>>10)&3];
|
|
}
|
|
extern bool nesEmuNSFPlayback;
|
|
|
|
//tile load from nestopia
|
|
static void loadTiles()
|
|
{
|
|
uint8_t* src[] =
|
|
{
|
|
ppu.TILELUT[ppu.BGRegA | (ppu.BGAttribReg & 3) << 8],
|
|
ppu.TILELUT[ppu.BGRegB | (ppu.BGAttribReg & 3) << 8]
|
|
};
|
|
|
|
uint8_t* dst = ppu.BGTiles+ppu.BGIndex;
|
|
ppu.BGIndex ^= 8;
|
|
|
|
dst[0] = src[0][0];
|
|
dst[1] = src[1][0];
|
|
dst[2] = src[0][1];
|
|
dst[3] = src[1][1];
|
|
dst[4] = src[0][2];
|
|
dst[5] = src[1][2];
|
|
dst[6] = src[0][3];
|
|
dst[7] = src[1][3];
|
|
}
|
|
|
|
static void grabNextSpriteTilesP1()
|
|
{
|
|
ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++];
|
|
ppu.CurSpriteLn = ppu.TmpOAMVal;
|
|
}
|
|
static void grabNextSpriteTilesP2()
|
|
{
|
|
ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++];
|
|
ppu.CurSpriteIndex = ppu.TmpOAMVal;
|
|
}
|
|
static void grabNextSpriteTilesP3()
|
|
{
|
|
ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++];
|
|
ppu.CurSpriteByte2 = ppu.TmpOAMVal;
|
|
}
|
|
static void grabNextSpriteTilesP4()
|
|
{
|
|
ppu.TmpOAMVal = ppu.OAM2[ppu.SpriteOAM2Pos++];
|
|
ppu.CurSpriteByte3 = ppu.TmpOAMVal;
|
|
}
|
|
static void grabNextSpriteTilesP5(uint16_t line)
|
|
{
|
|
uint8_t cSprLn = ppu.CurSpriteLn, cSprInd = ppu.CurSpriteIndex, cSprB2 = ppu.CurSpriteByte2;
|
|
uint8_t cSpriteAdd = 0; //used to select which 8 by 16 tile
|
|
bool spr816 = ppu.Reg[0] & PPU_SPRITE_8_16;
|
|
uint8_t cSpriteY = (line - cSprLn)&(spr816 ? 15 : 7);
|
|
if(cSpriteY > 7) //8 by 16 select
|
|
{
|
|
cSpriteAdd = 16;
|
|
cSpriteY &= 7;
|
|
}
|
|
uint16_t chrROMSpriteAdd = 0;
|
|
if(spr816)
|
|
{
|
|
chrROMSpriteAdd = ((cSprInd & 1) << 12);
|
|
cSprInd &= ~1;
|
|
}
|
|
else if(ppu.Reg[0] & PPU_SPRITE_ADDR)
|
|
chrROMSpriteAdd = 0x1000;
|
|
if(cSprB2 & PPU_SPRITE_FLIP_Y)
|
|
{
|
|
cSpriteY ^= 7;
|
|
if(spr816)
|
|
cSpriteAdd ^= 16; //8 by 16 select
|
|
}
|
|
/* write processed values into internal draw buffer */
|
|
mapperChrMode = 1;
|
|
uint8_t p0 = mapperChrGet8(((chrROMSpriteAdd+(cSprInd<<4)+cSpriteY+cSpriteAdd)&0xFFF) | chrROMSpriteAdd);
|
|
uint8_t p1 = mapperChrGet8(((chrROMSpriteAdd+(cSprInd<<4)+cSpriteY+8+cSpriteAdd)&0xFFF) | chrROMSpriteAdd);
|
|
if ((p0 | p1) && (cSprLn < VISIBLE_LINES)) //sprite contains data and is on a valid line, so process
|
|
{
|
|
//pixels
|
|
uint8_t sprTilePos = ppu.SpriteTilePos;
|
|
uint8_t a = (cSprB2 & PPU_SPRITE_FLIP_X) ? 7 : 0;
|
|
uint16_t p = (p0 >> 1 & 0x0055) | (p1 << 0 & 0x00AA) | (p0 << 8 & 0x5500) | (p1 << 9 & 0xAA00);
|
|
ppu.Sprites[sprTilePos][( a^=6 )] = ( p ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=6 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=7 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=6 )] = ( p >>= 2 ) & 0x3;
|
|
ppu.Sprites[sprTilePos][( a^=2 )] = ( p >>= 2 );
|
|
//x position
|
|
ppu.Sprites[sprTilePos][8] = ppu.CurSpriteByte3;
|
|
//is zero sprite (compare to 4 because OAM2 was just read, 4 is next position)
|
|
ppu.Sprites[sprTilePos][9] = (ppu.SpriteOAM2Pos == 4 && ppu.NextHasZSprite) ? 0x3 : 0x0;
|
|
//priority
|
|
ppu.Sprites[sprTilePos][10] = (cSprB2 & PPU_SPRITE_PRIO) ? 0x3 : 0x0;
|
|
//palette
|
|
ppu.Sprites[sprTilePos][11] = (cSprB2&3)<<2 | 0x10;
|
|
//increase pos
|
|
ppu.SpriteTilePos = sprTilePos+1;
|
|
}
|
|
}
|
|
|
|
static void updateBGTileAddress()
|
|
{
|
|
if((ppu.VramAddr & 0x1F) == 0x1F)
|
|
ppu.VramAddr ^= 0x41F;
|
|
else
|
|
ppu.VramAddr++;
|
|
}
|
|
static void updateBGHoriAddress()
|
|
{
|
|
ppu.VramAddr = (ppu.VramAddr & (~PPU_VRAM_HORIZONTAL_MASK)) | (ppu.TmpVramAddr & PPU_VRAM_HORIZONTAL_MASK);
|
|
}
|
|
static void updateBGVertAddress()
|
|
{
|
|
ppu.VramAddr = (ppu.VramAddr & (~PPU_VRAM_VERTICAL_MASK)) | (ppu.TmpVramAddr & PPU_VRAM_VERTICAL_MASK);
|
|
}
|
|
static void updateBGYAddress()
|
|
{
|
|
/* update Y position for writes */
|
|
if((ppu.VramAddr & 0x7000) != (7<<12))
|
|
ppu.VramAddr += (1<<12);
|
|
else switch(ppu.VramAddr & 0x3E0)
|
|
{
|
|
default: ppu.VramAddr = (ppu.VramAddr & 0xFFF) + (1 << 5); break;
|
|
case (29<<5): ppu.VramAddr ^= 0x800;
|
|
case (31<<5): ppu.VramAddr &= 0xC1F; break;
|
|
}
|
|
}
|
|
|
|
static void updateBGRegsA(uint16_t dot)
|
|
{
|
|
/* MMC5 Scroll Related */
|
|
if(dot == 320)
|
|
ppuDrawnXTile = 0;
|
|
else
|
|
ppuDrawnXTile++;
|
|
if(m5_exMode == 1)
|
|
{
|
|
/* MMC5 Ex Mode 1 has different Attribute for every Tile */
|
|
ppu.BGAttribReg = m5exGetAttrib(ppu.VramAddr);
|
|
}
|
|
else
|
|
{
|
|
uint16_t cPpuTbl = ppuGetVramTbl(ppu.VramAddr);
|
|
/* Select new BG Background Attribute */
|
|
uint8_t cAttrib = ((ppu.VramAddr>>4)&0x38) | ((ppu.VramAddr>>2)&7);
|
|
uint16_t attributeAddr = cPpuTbl | (0x3C0 | cAttrib);
|
|
ppu.BGAttribReg = mapperVramGet8(attributeAddr) >> ((ppu.VramAddr & 0x2) | (ppu.VramAddr >> 4 & 0x4));
|
|
}
|
|
}
|
|
static void updateBGRegsB()
|
|
{
|
|
uint16_t cPpuTbl = ppuGetVramTbl(ppu.VramAddr);
|
|
/* Select new BG Tiles */
|
|
uint16_t chrROMBG = (ppu.Reg[0] & PPU_BACKGROUND_ADDR) ? 0x1000 : 0;
|
|
uint16_t workAddr = cPpuTbl | (ppu.VramAddr & 0x3FF);
|
|
uint8_t curBGtileReg = mapperVramGet8(workAddr);
|
|
uint8_t curTileY = (ppu.VramAddr>>12)&7;
|
|
uint16_t curBGTile = chrROMBG+(curBGtileReg<<4)+curTileY;
|
|
mapperChrMode = 0;
|
|
uint8_t tmp = mapperChrGet8(curBGTile);
|
|
ppu.BGRegB = tmp >> 0 & 0x55; ppu.BGRegA = tmp >> 1 & 0x55;
|
|
tmp = mapperChrGet8(curBGTile+8);
|
|
ppu.BGRegA |= tmp << 0 & 0xAA; ppu.BGRegB |= tmp << 1 & 0xAA;
|
|
}
|
|
|
|
static void spriteEvalInit()
|
|
{
|
|
ppu.OAMzSpritePos = ppu.OAMpos;
|
|
//OAM Bug in 2C02
|
|
if(ppu.OAMpos & 0xF8)
|
|
memcpy(ppu.OAM,ppu.OAM + (ppu.OAMpos & 0xF8), 8);
|
|
}
|
|
|
|
static void spriteEvalA()
|
|
{
|
|
ppu.TmpOAMVal = ppu.OAM[(ppu.OAMpos+ppu.OAMcpPos)&0xFF];
|
|
//printf("%i %i %i %02x\n", ppu.OAMpos, ppu.OAMcpPos, (ppu.OAMpos+ppu.OAMcpPos)&0xFF, ppu.TmpOAMVal);
|
|
}
|
|
|
|
static void spriteEvalB(uint16_t line)
|
|
{
|
|
if(!ppu.OAMoverflow)
|
|
{
|
|
if(ppu.SearchY)
|
|
{
|
|
uint8_t cSpriteLn = ppu.TmpOAMVal;
|
|
uint8_t cSpriteAdd = (ppu.Reg[0] & PPU_SPRITE_8_16) ? 16 : 8;
|
|
if(cSpriteLn <= line && (cSpriteLn+cSpriteAdd) > line)
|
|
{
|
|
ppu.SearchY = false;
|
|
if(ppu.OAM2pos == 0 && ppu.OAMpos == ppu.OAMzSpritePos)
|
|
ppu.NextHasZSprite = true;
|
|
if(ppu.OAM2pos != 0x20)
|
|
{
|
|
ppu.OAM2[ppu.OAM2pos+ppu.OAMcpPos] = ppu.TmpOAMVal;
|
|
//printf("Copying sprite with line %i at line %i pos oam %i oam2 %i\n", cSpriteLn, ppu.curLine, ppu.OAMpos, ppu.OAM2pos);
|
|
}
|
|
else
|
|
{
|
|
//if(!(PPU_Reg[2] & PPU_FLAG_OVERFLOW) && !ppu.SpriteOverflow)
|
|
// printf("Overflow with line %i at line %i pos oam %i oam2 %i\n", cSpriteLn, ppu.curLine, ppu.OAMpos, ppu.OAM2pos);
|
|
ppu.Reg[2] |= PPU_FLAG_OVERFLOW;
|
|
ppu.LineOverflow = true;
|
|
}
|
|
ppu.BytesCopied++;
|
|
if(ppu.OAMcpPos < 3)
|
|
ppu.OAMcpPos++;
|
|
else
|
|
{
|
|
ppu.OAMcpPos = 0;
|
|
ppu.OAMpos += 4;
|
|
if(ppu.OAMpos == 0)
|
|
ppu.OAMoverflow = true;
|
|
}
|
|
}
|
|
else //no matching sprite, increase N
|
|
{
|
|
ppu.OAMpos += 4;
|
|
if(ppu.OAM2pos == 0x20 && !ppu.LineOverflow) /* nes sprite overflow bug */
|
|
{
|
|
if(ppu.OAMcpPos < 3)
|
|
ppu.OAMcpPos++;
|
|
else
|
|
ppu.OAMcpPos = 0;
|
|
}
|
|
if(ppu.OAMpos == 0)
|
|
ppu.OAMoverflow = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(ppu.OAM2pos != 0x20)
|
|
ppu.OAM2[ppu.OAM2pos+ppu.OAMcpPos] = ppu.TmpOAMVal;
|
|
if(ppu.OAMcpPos < 3)
|
|
ppu.OAMcpPos++;
|
|
else
|
|
{
|
|
ppu.OAMcpPos = 0;
|
|
ppu.OAMpos += 4;
|
|
if(ppu.OAMpos == 0)
|
|
ppu.OAMoverflow = true;
|
|
}
|
|
if(ppu.BytesCopied < 3) //we still have to read from this
|
|
ppu.BytesCopied++;
|
|
else //Back to next sprite
|
|
{
|
|
if(ppu.OAM2pos != 0x20)
|
|
ppu.OAM2pos += 4;
|
|
ppu.BytesCopied = 0;
|
|
ppu.OAMcpPos = 0;
|
|
ppu.SearchY = true;
|
|
if(ppu.LineOverflow) //overflow sprite copied, end
|
|
ppu.OAMoverflow = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ppu.OAMpos += 4;
|
|
if(ppu.OAM2pos == 0x20)
|
|
ppu.TmpOAMVal = ppu.OAM2[0];
|
|
}
|
|
|
|
static void clearSpriteWorkRegs()
|
|
{
|
|
ppu.OAMcpPos = 0;
|
|
ppu.BytesCopied = 0;
|
|
ppu.OAMoverflow = false;
|
|
ppu.SearchY = true;
|
|
ppu.LineOverflow = false;
|
|
}
|
|
|
|
static void resetOAMPos()
|
|
{
|
|
ppu.OAMpos = 0;
|
|
}
|
|
|
|
static void ppuCheckLine241(uint16_t dot)
|
|
{
|
|
/* VBlank start at first dot after post-render line */
|
|
/* Though results are better when starting it a bit later */
|
|
if(dot == 2)
|
|
{
|
|
ppu.NMITriggered = false;
|
|
if(!ppu.ReadReg2)
|
|
ppu.Reg[2] |= PPU_FLAG_VBLANK;
|
|
#if PPU_DEBUG_VSYNC
|
|
printf("PPU Start VBlank\n");
|
|
#endif
|
|
}
|
|
else if(dot == 4 && ppu.Reg[2] & PPU_FLAG_VBLANK)
|
|
ppu.NMIallowed = true;
|
|
ppu.ReadReg2 = false;
|
|
}
|
|
|
|
static void ppuLastDot(uint16_t *dot, uint16_t *line, bool curPicOutStat)
|
|
{
|
|
*dot = 0;
|
|
(*line)++;
|
|
if(*line == 241) /* done drawing this frame */
|
|
ppu.FrameDone = true;
|
|
else if(*line == ppu.LinesTotal) /* Wrap back down after pre-render line */
|
|
*line = 0;
|
|
ppu.curLine = *line;
|
|
/* For MMC5 Scanline Detect */
|
|
if((*line <= VISIBLE_LINES) && curPicOutStat)
|
|
{
|
|
ppuInFrame = true;
|
|
ppuScanlineDone = true;
|
|
}
|
|
else
|
|
ppuInFrame = false;
|
|
ppu.DoOverscan = (doOverscan && (*line < 8 || *line >= 232));
|
|
ppu.NextHasZSprite = false; //reset
|
|
ppu.OAMzSpritePos = 0; // reset
|
|
ppu.SpriteOAM2Pos = 0; //reset
|
|
ppu.ToDraw = ppu.SpriteTilePos;
|
|
ppu.SpriteTilePos = 0; //reset
|
|
ppu.OAM2pos = 0; //reset
|
|
ppu.TmpOAMVal = 0xFF;
|
|
memset(ppu.OAM2, ppu.TmpOAMVal, 0x20);
|
|
//printf("Line done\n");
|
|
}
|
|
|
|
void ppuCycle()
|
|
{
|
|
//try and make the pic out stat, cur dot and cur line local variables to reduce the time it gets pulled from bss!
|
|
size_t drawPos;
|
|
uint16_t dot = ppu.curDot, line = ppu.curLine, cPalIdx;
|
|
uint8_t curCol;
|
|
//these 2 stats are set by cpu, no need to have them in loop
|
|
ppu.CurNMIStat = !!(ppu.Reg[0] & PPU_FLAG_NMI);
|
|
bool picOutStat = (ppu.Reg[1] & (PPU_BG_ENABLE | PPU_SPRITE_ENABLE)) != 0;
|
|
ppu.VBlankClearCycle = false;
|
|
//get value of how often ppu has to run
|
|
uint8_t ppuLoop = ppu.RunCycles[ppu.Count];
|
|
ppu.Count = (ppu.Count+1)%5;
|
|
if(nesEmuNSFPlayback)
|
|
{
|
|
while(ppuLoop--)
|
|
{
|
|
if(line == ppu.PreRenderLine)
|
|
{
|
|
/* Mini-switch for basic vblank handle */
|
|
switch(dot)
|
|
{
|
|
case 0: /* VBlank ends at first dot of the pre-render line */
|
|
ppu.Reg[2] &= ~(PPU_FLAG_SPRITEZERO | PPU_FLAG_OVERFLOW);
|
|
goto add_dot_nsf;
|
|
case 2: /* Though results are better when clearing it a bit later */
|
|
#if PPU_DEBUG_VSYNC
|
|
printf("PPU End VBlank\n");
|
|
#endif
|
|
ppu.Reg[2] &= ~(PPU_FLAG_VBLANK);
|
|
goto add_dot_nsf;
|
|
case 7:
|
|
ppu.NMIallowed = false;
|
|
goto add_dot_nsf;
|
|
case 339:
|
|
ppu.OddFrame = ppu.OddArr[ppu.OddNum^=1];
|
|
if(ppu.OddFrame && (ppu.Reg[1] & PPU_BG_ENABLE))
|
|
{
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
break;
|
|
}
|
|
goto add_dot_nsf;
|
|
case 340:
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
break;
|
|
default:
|
|
add_dot_nsf:
|
|
dot++;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(line == 241)
|
|
ppuCheckLine241(dot);
|
|
if(dot == 340)
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
else
|
|
dot++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while(ppuLoop--)
|
|
{
|
|
if(line == ppu.PreRenderLine || line < VISIBLE_LINES)
|
|
{
|
|
switch(dot)
|
|
{
|
|
case 0: /* VBlank ends at first dot of the pre-render line */
|
|
if(line == ppu.PreRenderLine)
|
|
{
|
|
ppu.Reg[2] &= ~(PPU_FLAG_SPRITEZERO | PPU_FLAG_OVERFLOW);
|
|
goto add_dot;
|
|
}
|
|
//else if line < VISIBLE_LINES
|
|
ppu.BGEnable = (ppu.Reg[1] & PPU_BG_8PX) && (ppu.Reg[1] & PPU_BG_ENABLE);
|
|
ppu.SprEnable = (ppu.Reg[1] & PPU_SPRITE_8PX) && (ppu.Reg[1] & PPU_SPRITE_ENABLE);
|
|
goto do_render_pixel;
|
|
case 2: /* Though results are better when clearing it a bit later */
|
|
if(line == ppu.PreRenderLine)
|
|
{
|
|
#if PPU_DEBUG_VSYNC
|
|
printf("PPU End VBlank\n");
|
|
#endif
|
|
ppu.Reg[2] &= ~(PPU_FLAG_VBLANK);
|
|
goto add_dot;
|
|
}
|
|
//else if line < VISIBLE_LINES
|
|
goto do_render_pixel;
|
|
case 7:
|
|
if(line == ppu.PreRenderLine)
|
|
{
|
|
ppu.NMIallowed = false;
|
|
goto add_dot;
|
|
}
|
|
//else if line < VISIBLE_LINES
|
|
goto do_render_pixel;
|
|
case 1: case 3: case 5:
|
|
case 6: case 9: case 10:
|
|
case 11: case 13: case 14: case 15:
|
|
case 17: case 18: case 19: case 21:
|
|
case 22: case 23: case 25: case 26:
|
|
case 27: case 29: case 30: case 31:
|
|
case 33: case 34: case 35: case 37:
|
|
case 38: case 39: case 41: case 42:
|
|
case 43: case 45: case 46: case 47:
|
|
case 49: case 50: case 51: case 53:
|
|
case 54: case 55: case 57: case 58:
|
|
case 59: case 61: case 62: case 63:
|
|
if(line < VISIBLE_LINES) //needs a lot of optimization, takes a lot of cpu atm
|
|
{
|
|
do_render_pixel:
|
|
/* Grab color to render from BG and Sprites */
|
|
curCol = ppu.BGEnable ? ppu.BGTiles[(dot + ppu.FineXScroll) & 15] : 0;
|
|
if(ppu.SprEnable) curCol = ppuDoSprites(curCol, dot);
|
|
/* Draw current dot on screen */
|
|
drawPos = (dot)+(line<<8);
|
|
if(ppu.DoOverscan) /* Draw clipped area as black */
|
|
textureImage[drawPos] = 0xFF000000;
|
|
else
|
|
{
|
|
if(picOutStat) //use color from bg or sprite input
|
|
cPalIdx = ppu.PALRAM2[curCol&0x1F];
|
|
else if((ppu.VramAddr & 0x3F00) == 0x3F00) //bg and sprite disabled but address within PALRAM
|
|
cPalIdx = ppu.PALRAM2[ppu.VramAddr&0x1F];
|
|
else //bg and sprite disabled and address not within PALRAM
|
|
cPalIdx = ppu.PALRAM[0];
|
|
textureImage[drawPos] = ppu.BGRLUT[cPalIdx];
|
|
}
|
|
}
|
|
goto add_dot;
|
|
case 4: case 12: case 20: case 28:
|
|
case 36: case 44: case 52: case 60:
|
|
updateBGRegsB();
|
|
if(line < VISIBLE_LINES)
|
|
goto do_render_pixel;
|
|
goto add_dot;
|
|
case 8:
|
|
if(picOutStat) updateBGTileAddress();
|
|
loadTiles(); updateBGRegsA(dot);
|
|
if(line < VISIBLE_LINES)
|
|
{
|
|
ppu.BGEnable = (ppu.Reg[1] & PPU_BG_ENABLE);
|
|
ppu.SprEnable = (ppu.Reg[1] & PPU_SPRITE_ENABLE);
|
|
goto do_render_pixel;
|
|
}
|
|
goto add_dot;
|
|
case 16: case 24: case 32:
|
|
case 40: case 48: case 56:
|
|
if(picOutStat) updateBGTileAddress();
|
|
loadTiles(); updateBGRegsA(dot);
|
|
if(line < VISIBLE_LINES)
|
|
goto do_render_pixel;
|
|
goto add_dot;
|
|
case 64:
|
|
if(picOutStat) updateBGTileAddress();
|
|
loadTiles(); updateBGRegsA(dot);
|
|
if(line < VISIBLE_LINES)
|
|
{
|
|
if(picOutStat)
|
|
{
|
|
spriteEvalInit();
|
|
spriteEvalA();
|
|
}
|
|
goto do_render_pixel;
|
|
}
|
|
goto add_dot;
|
|
case 65: case 67: case 69: case 71:
|
|
case 73: case 75: case 77: case 79:
|
|
case 81: case 83: case 85: case 87:
|
|
case 89: case 91: case 93: case 95:
|
|
case 97: case 99: case 101: case 103:
|
|
case 105: case 107: case 109: case 111:
|
|
case 113: case 115: case 117: case 119:
|
|
case 121: case 123: case 125: case 127:
|
|
case 129: case 131: case 133: case 135:
|
|
case 137: case 139: case 141: case 143:
|
|
case 145: case 147: case 149: case 151:
|
|
case 153: case 155: case 157: case 159:
|
|
case 161: case 163: case 165: case 167:
|
|
case 169: case 171: case 173: case 175:
|
|
case 177: case 179: case 181: case 183:
|
|
case 185: case 187: case 189: case 191:
|
|
case 193: case 195: case 197: case 199:
|
|
case 201: case 203: case 205: case 207:
|
|
case 209: case 211: case 213: case 215:
|
|
case 217: case 219: case 221: case 223:
|
|
case 225: case 227: case 229: case 231:
|
|
case 233: case 235: case 237: case 239:
|
|
case 241: case 243: case 245: case 247:
|
|
case 249: case 253: case 255:
|
|
do_sprite_eval_b:
|
|
if(line < VISIBLE_LINES)
|
|
{
|
|
if(picOutStat) spriteEvalB(line);
|
|
goto do_render_pixel;
|
|
}
|
|
goto add_dot;
|
|
case 66: case 70: case 74: case 78:
|
|
case 82: case 86: case 90: case 94:
|
|
case 98: case 102: case 106: case 110:
|
|
case 114: case 118: case 122: case 126:
|
|
case 130: case 134: case 138: case 142:
|
|
case 146: case 150: case 154: case 158:
|
|
case 162: case 166: case 170: case 174:
|
|
case 178: case 182: case 186: case 190:
|
|
case 194: case 198: case 202: case 206:
|
|
case 210: case 214: case 218: case 222:
|
|
case 226: case 230: case 234: case 238:
|
|
case 242: case 246: case 250: case 254:
|
|
do_sprite_eval_a:
|
|
if(line < VISIBLE_LINES)
|
|
{
|
|
if(picOutStat) spriteEvalA();
|
|
goto do_render_pixel;
|
|
}
|
|
goto add_dot;
|
|
case 68: case 76: case 84: case 92:
|
|
case 100: case 108: case 116: case 124:
|
|
case 132: case 140: case 148: case 156:
|
|
case 164: case 172: case 180: case 188:
|
|
case 196: case 204: case 212: case 220:
|
|
case 228: case 236: case 244: case 252:
|
|
updateBGRegsB();
|
|
goto do_sprite_eval_a;
|
|
case 72: case 80: case 88: case 96:
|
|
case 104: case 112: case 120: case 128:
|
|
case 136: case 144: case 152: case 160:
|
|
case 168: case 176: case 184: case 192:
|
|
case 200: case 208: case 216: case 224:
|
|
case 232: case 240: case 248:
|
|
if(picOutStat) updateBGTileAddress();
|
|
loadTiles(); updateBGRegsA(dot);
|
|
goto do_sprite_eval_a;
|
|
case 251:
|
|
if(picOutStat) updateBGYAddress();
|
|
goto do_sprite_eval_b;
|
|
case 256:
|
|
if(line < VISIBLE_LINES)
|
|
clearSpriteWorkRegs();
|
|
loadTiles(); updateBGRegsA(dot);
|
|
if(picOutStat)
|
|
{
|
|
updateBGTileAddress();
|
|
goto do_spritetiles_P1;
|
|
}
|
|
goto add_dot;
|
|
case 257:
|
|
if(picOutStat)
|
|
{
|
|
updateBGHoriAddress();
|
|
goto do_spritetiles_P2;
|
|
}
|
|
goto add_dot;
|
|
case 258: case 266: case 274: case 306:
|
|
case 314:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
do_spritetiles_P3:
|
|
grabNextSpriteTilesP3();
|
|
}
|
|
goto add_dot;
|
|
case 259: case 267: case 275: case 307:
|
|
case 315:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
do_spritetiles_P4:
|
|
grabNextSpriteTilesP4();
|
|
}
|
|
goto add_dot;
|
|
case 260: case 268: case 276: case 308:
|
|
case 316:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
do_spritetiles_P5:
|
|
grabNextSpriteTilesP5(line);
|
|
}
|
|
goto add_dot;
|
|
case 261: case 262: case 263: case 269:
|
|
case 270: case 271: case 277: case 278:
|
|
case 279: case 309: case 310: case 311:
|
|
case 317: case 318: case 321: case 322:
|
|
case 323: case 325: case 326: case 327:
|
|
case 329: case 330: case 331: case 333:
|
|
case 334: case 335: case 337: case 338:
|
|
if(picOutStat && line < VISIBLE_LINES) resetOAMPos();
|
|
goto add_dot;
|
|
case 264: case 272: case 312:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
do_spritetiles_P1:
|
|
grabNextSpriteTilesP1();
|
|
}
|
|
goto add_dot;
|
|
case 265: case 273: case 305: case 313:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
do_spritetiles_P2:
|
|
grabNextSpriteTilesP2();
|
|
}
|
|
goto add_dot;
|
|
case 280: case 288: case 296: case 304:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
grabNextSpriteTilesP1();
|
|
}
|
|
goto add_dot;
|
|
case 281: case 289: case 297:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
grabNextSpriteTilesP2();
|
|
}
|
|
goto add_dot;
|
|
case 282: case 290: case 298:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
goto do_spritetiles_P3;
|
|
}
|
|
goto add_dot;
|
|
case 283: case 291: case 299:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
goto do_spritetiles_P4;
|
|
}
|
|
goto add_dot;
|
|
case 284: case 292: case 300:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
goto do_spritetiles_P5;
|
|
}
|
|
goto add_dot;
|
|
case 285: case 286: case 287: case 293:
|
|
case 294: case 295: case 301: case 302:
|
|
case 303:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
else if(line == ppu.PreRenderLine) updateBGVertAddress();
|
|
}
|
|
goto add_dot;
|
|
case 319:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
ppu.TmpOAMVal = ppu.OAM2[0];
|
|
}
|
|
goto add_dot;
|
|
case 320:
|
|
if(picOutStat && line < VISIBLE_LINES) resetOAMPos();
|
|
updateBGRegsA(dot);
|
|
goto add_dot;
|
|
case 324:
|
|
case 332:
|
|
if(picOutStat && line < VISIBLE_LINES) resetOAMPos();
|
|
updateBGRegsB();
|
|
goto add_dot;
|
|
case 328:
|
|
ppu.BGIndex = 0;
|
|
case 336:
|
|
if(picOutStat)
|
|
{
|
|
if(line < VISIBLE_LINES) resetOAMPos();
|
|
updateBGTileAddress();
|
|
}
|
|
loadTiles(); updateBGRegsA(dot);
|
|
goto add_dot;
|
|
case 339:
|
|
if(line == ppu.PreRenderLine)
|
|
{
|
|
ppu.OddFrame = ppu.OddArr[ppu.OddNum^=1];
|
|
if(ppu.OddFrame && (ppu.Reg[1] & PPU_BG_ENABLE))
|
|
{
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
break;
|
|
}
|
|
}
|
|
else if(picOutStat && line < VISIBLE_LINES)
|
|
resetOAMPos();
|
|
goto add_dot;
|
|
case 340:
|
|
if(picOutStat && line < VISIBLE_LINES) resetOAMPos();
|
|
updateBGRegsB();
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
break;
|
|
default:
|
|
add_dot:
|
|
dot++;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(line == 241)
|
|
ppuCheckLine241(dot);
|
|
if(dot == 340)
|
|
ppuLastDot(&dot,&line,picOutStat);
|
|
else
|
|
dot++;
|
|
}
|
|
}
|
|
}
|
|
ppu.curDot = dot;
|
|
}
|
|
|
|
void ppuPrintcurLineDot()
|
|
{
|
|
printf("%i %i %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos);
|
|
}
|
|
|
|
static uint8_t ppuDoSprites(uint8_t color, uint16_t dot)
|
|
{
|
|
uint8_t i;
|
|
for(i = 0; i < ppu.ToDraw; i++)
|
|
{
|
|
uint16_t x = dot - ppu.Sprites[i][8];
|
|
if (x > 7) continue;
|
|
//required pixel
|
|
x = ppu.Sprites[i][x];
|
|
if (x)
|
|
{
|
|
if ((color & ppu.Sprites[i][9]) && dot < 255)
|
|
{
|
|
ppu.Reg[2] |= PPU_FLAG_SPRITEZERO;
|
|
#if PPU_DEBUG_ULTRA
|
|
printf("Zero sprite hit at x %i y %i cSpriteDot %i "
|
|
"table %04x color %02x sprCol %02x\n", dot, ppu.curLine, cSpriteDot, ppuGetVramTbl((ppu.Reg[0]&3)<<10), color, sprCol);
|
|
#endif
|
|
//if(ppu.curLine < 224)
|
|
// ppuDebugPauseFrame = true;
|
|
}
|
|
if (!(color & ppu.Sprites[i][10]))
|
|
color = ppu.Sprites[i][11] | x;
|
|
|
|
break;
|
|
}
|
|
}
|
|
return color;
|
|
}
|
|
|
|
bool ppuDrawDone()
|
|
{
|
|
if(ppu.FrameDone)
|
|
{
|
|
//printf("%i\n",ppuCycles);
|
|
//ppuCycles = 0;
|
|
ppu.FrameDone = false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ppuSet8(uint8_t reg, uint8_t val)
|
|
{
|
|
ppu.lastVal = val;
|
|
if(reg == 0)
|
|
{
|
|
ppu.Reg[0] = val;
|
|
ppu.TmpVramAddr &= ~0xC00;
|
|
ppu.TmpVramAddr |= ((val&3)<<10);
|
|
ppu816Sprite = (val & PPU_SPRITE_8_16) != 0;
|
|
//printf("%d %d %d\n", (PPU_Reg[0] & PPU_BACKGROUND_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_ADDR) != 0, (PPU_Reg[0] & PPU_SPRITE_8_16) != 0);
|
|
}
|
|
else if(reg == 1)
|
|
{
|
|
if((ppu.Reg[1]^val)&(PPU_GRAY|0xE0))
|
|
{
|
|
uint8_t i;
|
|
for(i = 0; i < 0x20; i++)
|
|
ppu.PALRAM2[i] = (ppu.PALRAM[i]&((val&PPU_GRAY)?0x30:0x3F))|((val&0xE0)<<1);
|
|
}
|
|
ppu.BGEnable = (ppu.curDot >= 8 || (val & PPU_BG_8PX)) && (val & PPU_BG_ENABLE);
|
|
ppu.SprEnable = (ppu.curDot >= 8 || (val & PPU_SPRITE_8PX)) && (val & PPU_SPRITE_ENABLE);
|
|
ppu.Reg[1] = val;
|
|
}
|
|
else if(reg == 3)
|
|
{
|
|
#if PPU_DEBUG_ULTRA
|
|
printf("ppu.OAMpos at line %i dot %i was %02x set to %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos, val);
|
|
#endif
|
|
ppu.OAMpos = val;
|
|
}
|
|
else if(reg == 4)
|
|
{
|
|
#if PPU_DEBUG_ULTRA
|
|
printf("Setting OAM at line %i dot %i addr %02x to %02x\n", ppu.curLine, ppu.curDot, ppu.OAMpos, val);
|
|
#endif
|
|
ppu.OAM[ppu.OAMpos++] = val;
|
|
}
|
|
else if(reg == 5)
|
|
{
|
|
#if PPU_DEBUG_ULTRA
|
|
printf("ppuScrollWrite (%d) %02x pc %04x\n", ppu.TmpWrite, val, cpuGetPc());
|
|
#endif
|
|
if(!ppu.TmpWrite)
|
|
{
|
|
ppu.TmpWrite = true;
|
|
ppu.FineXScroll = val&7;
|
|
ppu.TmpVramAddr &= ~0x1F;
|
|
ppu.TmpVramAddr |= ((val>>3)&0x1F);
|
|
}
|
|
else
|
|
{
|
|
ppu.TmpWrite = false;
|
|
ppu.TmpVramAddr &= ~0x73E0;
|
|
ppu.TmpVramAddr |= ((val&7)<<12) | ((val>>3)<<5);
|
|
}
|
|
}
|
|
else if(reg == 6)
|
|
{
|
|
#if PPU_DEBUG_ULTRA
|
|
printf("ppu.VramAddrWrite (%d) %02x pc %04x\n", ppu.TmpWrite, val, cpuGetPc());
|
|
#endif
|
|
if(!ppu.TmpWrite)
|
|
{
|
|
ppu.TmpWrite = true;
|
|
ppu.TmpVramAddr &= 0xFF;
|
|
ppu.TmpVramAddr |= ((val&0x3F)<<8);
|
|
}
|
|
else
|
|
{
|
|
ppu.TmpWrite = false;
|
|
ppu.TmpVramAddr &= ~0xFF;
|
|
ppu.TmpVramAddr |= val;
|
|
ppu.VramAddr = ppu.TmpVramAddr;
|
|
//For MMC3 IRQ (Shadow Read)
|
|
if((ppu.VramAddr&0x3FFF) < 0x2000)
|
|
mapperChrGet8(ppu.VramAddr&0x3FFF);
|
|
}
|
|
}
|
|
else if(reg == 7)
|
|
{
|
|
uint16_t writeAddr = (ppu.VramAddr & 0x3FFF);
|
|
if(writeAddr < 0x2000)
|
|
{
|
|
mapperChrSet8(writeAddr, val);
|
|
}
|
|
else if(writeAddr < 0x3F00)
|
|
{
|
|
uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF);
|
|
//printf("ppuVRAMwrite %04x %02x\n", workAddr, val);
|
|
mapperVramSet8(workAddr, val);
|
|
}
|
|
else
|
|
{
|
|
uint8_t palRamAddr = (writeAddr&0x1F);
|
|
if((palRamAddr&3) == 0)
|
|
{
|
|
ppu.PALRAM[palRamAddr^0x10] = val;
|
|
ppu.PALRAM2[palRamAddr^0x10] = (val&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F))|((ppu.Reg[1]&0xE0)<<1);
|
|
}
|
|
ppu.PALRAM[palRamAddr] = val;
|
|
ppu.PALRAM2[palRamAddr] = (val&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F))|((ppu.Reg[1]&0xE0)<<1);
|
|
}
|
|
ppu.VramAddr += (ppu.Reg[0] & PPU_INC_AMOUNT) ? 32 : 1;
|
|
//For MMC3 IRQ (Shadow Read)
|
|
if((ppu.VramAddr&0x3FFF) < 0x2000)
|
|
mapperChrGet8(ppu.VramAddr&0x3FFF);
|
|
}
|
|
else if(reg != 2)
|
|
{
|
|
ppu.Reg[reg] = val;
|
|
//printf("ppuSet8 odd %d ppu.curDot %i ppu.curLine %i %04x %02x\n", ppu.OddFrame, ppu.curDot, ppu.curLine, reg, val);
|
|
}
|
|
}
|
|
|
|
uint8_t ppuGet8(uint8_t reg)
|
|
{
|
|
uint8_t ret = ppu.lastVal;
|
|
if(reg == 2)
|
|
{
|
|
ret = ppu.Reg[reg];
|
|
ppu.Reg[reg] &= ~PPU_FLAG_VBLANK;
|
|
if(ret & PPU_FLAG_VBLANK)
|
|
{
|
|
ppu.VBlankFlagCleared = true;
|
|
ppu.VBlankClearCycle = true;
|
|
}
|
|
ppu.TmpWrite = false;
|
|
ppu.ReadReg2 = true;
|
|
}
|
|
else if(reg == 4)
|
|
{
|
|
if(ppu.Reg[2] & PPU_FLAG_VBLANK || (ppu.Reg[1] & (PPU_BG_ENABLE | PPU_SPRITE_ENABLE)) == 0)
|
|
ret = ppu.OAM[ppu.OAMpos];
|
|
else
|
|
ret = ppu.TmpOAMVal;
|
|
//printf("Cycle %i line %i val %02x\n", ppu.curDot, ppu.curLine, ret);
|
|
}
|
|
else if(reg == 7)
|
|
{
|
|
uint16_t writeAddr = (ppu.VramAddr & 0x3FFF);
|
|
if(writeAddr < 0x2000)
|
|
{
|
|
ret = ppu.VramReadBuf;
|
|
mapperChrMode = 2;
|
|
ppu.VramReadBuf = mapperChrGet8(writeAddr);
|
|
}
|
|
else if(writeAddr < 0x3F00)
|
|
{
|
|
ret = ppu.VramReadBuf;
|
|
uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF);
|
|
ppu.VramReadBuf = mapperVramGet8(workAddr);
|
|
//printf("ppuVRAMread pc %04x addr %04x ret %02x\n", cpuGetPc(), workAddr, ret);
|
|
}
|
|
else
|
|
{
|
|
uint8_t palRamAddr = (writeAddr&0x1F);
|
|
if((palRamAddr&3) == 0)
|
|
palRamAddr &= ~0x10;
|
|
ret = ppu.PALRAM[palRamAddr]&((ppu.Reg[1]&PPU_GRAY)?0x30:0x3F);
|
|
//shadow read
|
|
uint16_t workAddr = ppuGetVramTbl(writeAddr) | (writeAddr & 0x3FF);
|
|
ppu.VramReadBuf = mapperVramGet8(workAddr);
|
|
}
|
|
ppu.VramAddr += (ppu.Reg[0] & PPU_INC_AMOUNT) ? 32 : 1;
|
|
//For MMC3 IRQ (Shadow Read)
|
|
if((ppu.VramAddr&0x3FFF) < 0x2000)
|
|
mapperChrGet8(ppu.VramAddr&0x3FFF);
|
|
}
|
|
//if(ret & PPU_FLAG_VBLANK)
|
|
//printf("ppuGet8 %04x:%02x\n",reg,ret);
|
|
ppu.lastVal = ret;
|
|
return ret;
|
|
}
|
|
|
|
bool ppuNMI()
|
|
{
|
|
if(ppu.VBlankFlagCleared && !ppu.VBlankClearCycle)
|
|
{
|
|
ppu.VBlankFlagCleared = false;
|
|
ppu.NMIallowed = false;
|
|
}
|
|
if(ppu.CurNMIStat && ppu.NMIallowed)
|
|
{
|
|
if(ppu.NMITriggered == false)
|
|
{
|
|
ppu.NMITriggered = true;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
ppu.NMITriggered = false;
|
|
return false;
|
|
}
|
|
|
|
void ppuDumpMem()
|
|
{
|
|
FILE *f = fopen("PPU_VRAM.bin","wb");
|
|
if(f)
|
|
{
|
|
fwrite(ppu.VRAM,1,0x1000,f);
|
|
fclose(f);
|
|
}
|
|
f = fopen("PPU_OAM.bin","wb");
|
|
if(f)
|
|
{
|
|
fwrite(ppu.OAM,1,0x100,f);
|
|
fclose(f);
|
|
}
|
|
f = fopen("PPU_Sprites.bin","wb");
|
|
if(f)
|
|
{
|
|
fwrite(ppu.Sprites,1,0x20,f);
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
uint16_t ppuGetCurVramAddr()
|
|
{
|
|
return ppu.VramAddr;
|
|
}
|
|
|
|
void ppuSetNameTblSingleLower()
|
|
{
|
|
ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0; ppu.NameTbl[2] = 0; ppu.NameTbl[3] = 0;
|
|
}
|
|
|
|
void ppuSetNameTblSingleUpper()
|
|
{
|
|
ppu.NameTbl[0] = 0x400; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0x400; ppu.NameTbl[3] = 0x400;
|
|
}
|
|
|
|
void ppuSetNameTblVertical()
|
|
{
|
|
ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0; ppu.NameTbl[3] = 0x400;
|
|
}
|
|
|
|
void ppuSetNameTblHorizontal()
|
|
{
|
|
ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0; ppu.NameTbl[2] = 0x400; ppu.NameTbl[3] = 0x400;
|
|
}
|
|
|
|
void ppuSetNameTbl4Screen()
|
|
{
|
|
ppu4Screen = true;
|
|
ppu.NameTbl[0] = 0; ppu.NameTbl[1] = 0x400; ppu.NameTbl[2] = 0x800; ppu.NameTbl[3] = 0xC00;
|
|
}
|
|
|
|
void ppuSetNameTblCustom(uint16_t addrA, uint16_t addrB, uint16_t addrC, uint16_t addrD)
|
|
{
|
|
//printf("%04x %04x %04x %04x\n", addrA, addrB, addrC, addrD);
|
|
//ppuPrintppu.curLineDot();
|
|
ppu.NameTbl[0] = addrA; ppu.NameTbl[1] = addrB; ppu.NameTbl[2] = addrC; ppu.NameTbl[3] = addrD;
|
|
}
|
|
|
|
void ppuBackUpTbl()
|
|
{
|
|
ppu.NameTblBak[0] = ppu.NameTbl[0]; ppu.NameTblBak[1] = ppu.NameTbl[1];
|
|
ppu.NameTblBak[2] = ppu.NameTbl[2]; ppu.NameTblBak[3] = ppu.NameTbl[3];
|
|
}
|
|
|
|
void ppuRestoreTbl()
|
|
{
|
|
ppu.NameTbl[0] = ppu.NameTblBak[0]; ppu.NameTbl[1] = ppu.NameTblBak[1];
|
|
ppu.NameTbl[2] = ppu.NameTblBak[2]; ppu.NameTbl[3] = ppu.NameTblBak[3];
|
|
}
|
|
|
|
//64x12 1BPP "Track"
|
|
static const uint8_t ppuNSFTextTrack[96] =
|
|
{
|
|
0x0C, 0x1C, 0x03, 0xD8, 0x7C, 0x71, 0xC0, 0x00, 0x0C, 0x1C, 0x07, 0xF8, 0xFE, 0x73, 0x80, 0x00,
|
|
0x0C, 0x1C, 0x06, 0x39, 0xE2, 0x77, 0x00, 0x00, 0x0C, 0x1C, 0x07, 0x39, 0xC0, 0x7E, 0x00, 0x00,
|
|
0x0C, 0x1C, 0x07, 0xF9, 0xC0, 0x7C, 0x00, 0x00, 0x0C, 0x1C, 0x01, 0xF9, 0xC0, 0x7C, 0x00, 0x00,
|
|
0x0C, 0x1E, 0x60, 0x38, 0xE2, 0x7E, 0x00, 0x00, 0x0C, 0x1F, 0xE3, 0xF8, 0xFE, 0x77, 0x00, 0x00,
|
|
0x0C, 0x1D, 0xC3, 0xF0, 0x3C, 0x73, 0xC0, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
|
0xFF, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
|
|
};
|
|
|
|
//128x12 1BPP "0123456789/"
|
|
static const uint8_t ppuNsfTextRest[192] =
|
|
{
|
|
0x0E, 0x1F, 0xF7, 0xF8, 0xF8, 0x03, 0x9F, 0x01, 0xF0, 0x60, 0x1F, 0x0F, 0x83, 0x00, 0x00, 0x00,
|
|
0x3F, 0x9F, 0xF7, 0xF9, 0xFC, 0x03, 0x9F, 0xC3, 0xF8, 0x70, 0x3F, 0x8F, 0xC3, 0x00, 0x00, 0x00,
|
|
0x3B, 0x83, 0x83, 0x80, 0x0E, 0x7F, 0xC1, 0xE7, 0x1C, 0x70, 0x71, 0xC0, 0xE1, 0x80, 0x00, 0x00,
|
|
0x71, 0xC3, 0x81, 0xC0, 0x0E, 0x7F, 0xC0, 0xE7, 0x1C, 0x30, 0x71, 0xC0, 0x71, 0x80, 0x00, 0x00,
|
|
0x79, 0xC3, 0x80, 0xE0, 0x0E, 0x63, 0x80, 0xE7, 0x1C, 0x38, 0x71, 0xC7, 0x70, 0xC0, 0x00, 0x00,
|
|
0x7D, 0xC3, 0x80, 0x70, 0x7E, 0x33, 0x81, 0xE7, 0x1C, 0x18, 0x3F, 0x8F, 0xF0, 0xC0, 0x00, 0x00,
|
|
0x77, 0xC3, 0x80, 0x70, 0x7C, 0x13, 0x9F, 0xC7, 0xF8, 0x1C, 0x1F, 0x1C, 0x70, 0x60, 0x00, 0x00,
|
|
0x73, 0xC3, 0x80, 0x38, 0x0E, 0x1B, 0x9F, 0x87, 0x70, 0x1C, 0x31, 0x9C, 0x70, 0x60, 0x00, 0x00,
|
|
0x71, 0xC3, 0x80, 0x38, 0x0E, 0x0B, 0x9C, 0x07, 0x00, 0x0C, 0x71, 0xDC, 0x70, 0x30, 0x00, 0x00,
|
|
0x3B, 0x9F, 0x80, 0x38, 0x0E, 0x0F, 0x9C, 0x03, 0x80, 0x0E, 0x71, 0xDC, 0x70, 0x30, 0x00, 0x00,
|
|
0x3F, 0x8F, 0x83, 0xF1, 0xFC, 0x07, 0x9F, 0xC1, 0xF9, 0xFE, 0x3F, 0x8F, 0xE0, 0x18, 0x00, 0x00,
|
|
0x0E, 0x03, 0x81, 0xE0, 0xF8, 0x03, 0x9F, 0xC0, 0xF9, 0xFE, 0x1F, 0x07, 0xC0, 0x18, 0x00, 0x00,
|
|
};
|
|
|
|
static void ppuDrawRest(uint8_t curX, uint8_t sym)
|
|
{
|
|
uint8_t i, j;
|
|
for(i = 0; i < 12; i++)
|
|
{
|
|
for(j = 0; j < 10; j++)
|
|
{
|
|
size_t drawPos = (j+curX)+((i+9)*256);
|
|
uint8_t xSel = (j+(sym*10));
|
|
if(ppuNsfTextRest[((11-i)<<4)+(xSel>>3)]&(0x80>>(xSel&7)))
|
|
textureImage[drawPos] = 0xFFFFFFFF; //White
|
|
else
|
|
textureImage[drawPos] = 0xFF000000; //Black
|
|
}
|
|
}
|
|
}
|
|
|
|
void ppuDrawNSFTrackNum(uint8_t cTrack, uint8_t trackTotal)
|
|
{
|
|
memset(textureImage,0,0xB400);
|
|
uint8_t curX = 4;
|
|
//draw "Track"
|
|
uint8_t i, j;
|
|
for(i = 0; i < 12; i++)
|
|
{
|
|
for(j = 0; j < 50; j++)
|
|
{
|
|
size_t drawPos = (j+curX)+((i+9)*256);
|
|
if(ppuNSFTextTrack[((11-i)<<3)+(j>>3)]&(0x80>>(j&7)))
|
|
textureImage[drawPos] = 0xFFFFFFFF; //White
|
|
else
|
|
textureImage[drawPos] = 0xFF000000; //Black
|
|
}
|
|
}
|
|
//"Track" len+space
|
|
curX+=60;
|
|
//draw current num
|
|
if(cTrack > 99)
|
|
{
|
|
ppuDrawRest(curX, (cTrack/100)%10);
|
|
curX+=10;
|
|
}
|
|
if(cTrack > 9)
|
|
{
|
|
ppuDrawRest(curX, (cTrack/10)%10);
|
|
curX+=10;
|
|
}
|
|
ppuDrawRest(curX, cTrack%10);
|
|
curX+=10;
|
|
//draw the "/"
|
|
ppuDrawRest(curX, 10);
|
|
curX+=10;
|
|
//draw total num
|
|
if(trackTotal > 99)
|
|
{
|
|
ppuDrawRest(curX, (trackTotal/100)%10);
|
|
curX+=10;
|
|
}
|
|
if(trackTotal > 9)
|
|
{
|
|
ppuDrawRest(curX, (trackTotal/10)%10);
|
|
curX+=10;
|
|
}
|
|
ppuDrawRest(curX, trackTotal%10);
|
|
curX+=10;
|
|
}
|
|
|
|
uint8_t ppuVRAMGet8(uint16_t addr)
|
|
{
|
|
return ppu.VRAM[addr&0xFFF];
|
|
}
|
|
|
|
void ppuVRAMSet8(uint16_t addr, uint8_t val)
|
|
{
|
|
ppu.VRAM[addr&0xFFF] = val;
|
|
}
|
|
|
|
uint8_t* ppuGetVRAM()
|
|
{
|
|
return ppu.VRAM;
|
|
}
|