Mesen2/Core/NES/Debugger/NesPpuTools.cpp

501 lines
17 KiB
C++

#include "pch.h"
#include "NES/Debugger/NesPpuTools.h"
#include "NES/Debugger/IExtModeMapperDebug.h"
#include "NES/BaseMapper.h"
#include "NES/BaseNesPpu.h"
#include "NES/NesConsole.h"
#include "NES/NesConstants.h"
#include "NES/NesTypes.h"
#include "NES/NesDefaultVideoFilter.h"
#include "NES/Mappers/Homebrew/Rainbow.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/MemoryDumper.h"
#include "Debugger/MemoryAccessCounter.h"
#include "Shared/SettingTypes.h"
static constexpr uint32_t grayscalePalette[4] = { 0xFF000000, 0xFF808080, 0xFFC0C0C0, 0xFFFFFFFF };
NesPpuTools::NesPpuTools(Debugger* debugger, Emulator *emu, NesConsole* console) : PpuTools(debugger, emu)
{
_console = console;
_mapper = console->GetMapper();
}
void NesPpuTools::GetPpuToolsState(BaseState& state)
{
NesPpuToolsState nesState = {};
IExtModeMapperDebug* exMode = dynamic_cast<IExtModeMapperDebug*>(_mapper);
if(exMode) {
nesState.ExtConfig = exMode->GetExModeConfig();
}
(NesPpuToolsState&)state = nesState;
}
void NesPpuTools::DrawNametable(uint8_t* ntSource, uint32_t ntBaseAddr, uint8_t ntIndex, GetTilemapOptions options, NesPpuState& state, IExtModeMapperDebug* exMode, ExtModeConfig& extCfg, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer, uint32_t bufferWidth)
{
uint16_t baseAttributeAddr = ntBaseAddr + 960;
for(uint8_t row = 0; row < 30; row++) {
for(uint8_t column = 0; column < 32; column++) {
uint16_t ntOffset = (row << 5) + column;
uint16_t attributeAddress = baseAttributeAddr + ((row & 0xFC) << 1) + (column >> 2);
uint8_t tileIndex = ntSource[ntBaseAddr + ntOffset];
uint8_t attribute = ntSource[attributeAddress];
uint8_t shift = (column & 0x02) | ((row & 0x02) << 1);
uint8_t paletteBaseAddr;
if(exMode && exMode->HasExtendedAttributes(extCfg, ntIndex)) {
paletteBaseAddr = exMode->GetExAttributePalette(extCfg, ntIndex, ntOffset) << 2;
} else {
paletteBaseAddr = ((attribute >> shift) & 0x03) << 2;
}
uint16_t tileAddr = state.Control.BackgroundPatternAddr + (tileIndex << 4);
if(options.DisplayMode == TilemapDisplayMode::AttributeView) {
for(uint8_t y = 0; y < 8; y++) {
for(uint8_t x = 0; x < 8; x++) {
uint8_t color = ((x & 0x04) >> 2) + ((y & 0x04) >> 1);
outBuffer[(row*bufferWidth*8) + (column << 3) + (y*bufferWidth) + x] = palette[paletteBaseAddr + color];
}
}
} else {
for(uint8_t y = 0; y < 8; y++) {
uint8_t lowByte, highByte;
if(exMode && exMode->HasExtendedBackground(extCfg, ntIndex)) {
lowByte = exMode->GetExBackgroundChrData(extCfg, ntIndex, ntOffset, tileAddr + y);
highByte = exMode->GetExBackgroundChrData(extCfg, ntIndex, ntOffset, tileAddr + y + 8);
} else {
lowByte = vram[tileAddr + y];
highByte = vram[tileAddr + y + 8];
}
uint32_t offset = (row*bufferWidth*8) + (column << 3) + (y*bufferWidth);
for(uint8_t x = 0; x < 8; x++) {
uint8_t color = ((lowByte >> (7 - x)) & 0x01) | (((highByte >> (7 - x)) & 0x01) << 1);
if(options.DisplayMode == TilemapDisplayMode::Grayscale) {
outBuffer[offset + x] = grayscalePalette[color];
} else {
outBuffer[offset + x] = palette[paletteBaseAddr + color];
}
}
}
}
}
}
}
DebugTilemapInfo NesPpuTools::GetWindowTilemap(GetTilemapOptions options, NesPpuState& state, IExtModeMapperDebug* exMode, ExtModeConfig& extCfg, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer)
{
uint16_t baseAddr = extCfg.WindowBank * 0x400;
DrawNametable(extCfg.ExtRam, baseAddr, 4, options, state, exMode, extCfg, vram, palette, outBuffer, 256);
DebugTilemapInfo result = {};
result.Bpp = 2;
result.Format = TileFormat::NesBpp2;
result.TileWidth = 8;
result.TileHeight = 8;
result.ColumnCount = 32;
result.RowCount = 30;
result.TilemapAddress = extCfg.WindowBank * 0x400;
result.TilesetAddress = state.Control.BackgroundPatternAddr;
result.ScrollWidth = NesConstants::ScreenWidth;
result.ScrollHeight = NesConstants::ScreenHeight;
result.ScrollX = extCfg.WindowScrollX;
result.ScrollY = extCfg.WindowScrollY;
return result;
}
DebugTilemapInfo NesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer)
{
IExtModeMapperDebug* exMode = dynamic_cast<IExtModeMapperDebug*>(_mapper);
NesPpuState& state = (NesPpuState&)baseState;
ExtModeConfig& extCfg = ((NesPpuToolsState&)ppuToolsState).ExtConfig;
for(int i = 0; i < 32; i+=4) {
palette[i] = palette[0];
}
if(options.Layer == 1 && exMode) {
return GetWindowTilemap(options, state, exMode, extCfg, vram, palette, outBuffer);
}
for(int nametableIndex = 0; nametableIndex < 4; nametableIndex++) {
uint16_t baseAddr = 0x2000 + nametableIndex * 0x400;
uint32_t bufferOffset = ((nametableIndex & 0x01) ? 256 : 0) + ((nametableIndex & 0x02) ? 512 * 240 : 0);
DrawNametable(vram, baseAddr, nametableIndex, options, state, exMode, extCfg, vram, palette, outBuffer+bufferOffset, 512);
if(options.DisplayMode != TilemapDisplayMode::AttributeView) {
if(options.TileHighlightMode != TilemapHighlightMode::None || options.AttributeHighlightMode != TilemapHighlightMode::None) {
ApplyHighlights(options, nametableIndex, vram, outBuffer);
}
}
}
DebugTilemapInfo result = {};
result.Bpp = 2;
result.Format = TileFormat::NesBpp2;
result.TileWidth = 8;
result.TileHeight = 8;
result.ColumnCount = 64;
result.RowCount = 60;
result.TilemapAddress = 0x2000;
result.TilesetAddress = state.Control.BackgroundPatternAddr;
result.ScrollWidth = NesConstants::ScreenWidth;
result.ScrollHeight = NesConstants::ScreenHeight;
switch(_mapper->GetState().Mirroring) {
case MirroringType::Horizontal: result.Mirroring = TilemapMirroring::Horizontal; break;
case MirroringType::Vertical: result.Mirroring = TilemapMirroring::Vertical; break;
case MirroringType::ScreenAOnly: result.Mirroring = TilemapMirroring::SingleScreenA; break;
case MirroringType::ScreenBOnly: result.Mirroring = TilemapMirroring::SingleScreenB; break;
case MirroringType::FourScreens: result.Mirroring = TilemapMirroring::FourScreens; break;
}
if(state.Scanline >= 240 || (state.Scanline == 239 && state.Cycle >= 256) || (state.Scanline == -1 && state.Cycle < 328)) {
//During vblank, use T instead of V
uint16_t t = state.TmpVideoRamAddr;
result.ScrollX = ((t & 0x1F) << 3) | ((t & 0x400) ? 256 : 0) | state.ScrollX;
result.ScrollY = (((t & 0x3E0) >> 2) | ((t & 0x7000) >> 12)) + ((t & 0x800) ? 240 : 0);
} else {
//During rendering, use V and subtract according to the current scanline/cycle
uint16_t v = state.VideoRamAddr;
int32_t scrollX = ((v & 0x1F) << 3) | ((v & 0x400) ? 256 : 0);
if(state.Cycle <= 256) {
if(state.Cycle >= 8) {
scrollX -= (state.Cycle & ~0x07);
}
//Adjust for the 2 x increments at the end of the previous scanline
scrollX -= 16;
} else if(state.Cycle >= 328) {
scrollX -= 8;
if(state.Cycle >= 336) {
scrollX -= 8;
}
}
if(scrollX < 0) {
scrollX += 512;
}
scrollX += state.ScrollX;
int32_t scrollY = (((v & 0x3E0) >> 2) | ((v & 0x7000) >> 12)) + ((v & 0x800) ? 240 : 0);
if(state.Scanline >= 0) {
if(state.Cycle < 256) {
scrollY -= state.Scanline;
} else {
scrollY -= state.Scanline + 1;
}
}
if(scrollY < 0) {
scrollY += 480;
}
result.ScrollX = (uint32_t)scrollX;
result.ScrollY = (uint32_t)scrollY;
}
return result;
}
void NesPpuTools::ApplyHighlights(GetTilemapOptions options, uint8_t nametableIndex, uint8_t* vram, uint32_t* outBuffer)
{
uint16_t baseAddr = 0x2000 + nametableIndex * 0x400;
uint16_t baseAttributeAddr = baseAddr + 960;
uint32_t bufferOffset = ((nametableIndex & 0x01) ? 256 : 0) + ((nametableIndex & 0x02) ? 512 * 240 : 0);
AddressCounters* accessCounters = options.AccessCounters;
uint8_t* prevVram = options.CompareVram != nullptr ? options.CompareVram : vram;
uint64_t masterClock = options.MasterClock;
uint32_t clockRate = _console->GetMasterClockRate() / _console->GetFps();
auto isHighlighted = [&](uint16_t addr, TilemapHighlightMode mode) -> bool {
switch(mode) {
default:
case TilemapHighlightMode::None: return false;
//Highlight if modified since the last update
case TilemapHighlightMode::Changes: return prevVram[addr] != vram[addr];
//Highlight if modified in the last frame
case TilemapHighlightMode::Writes: return accessCounters && masterClock - accessCounters[addr].WriteStamp < clockRate;
}
};
for(uint8_t row = 0; row < 30; row++) {
for(uint8_t column = 0; column < 32; column++) {
uint16_t ntOffset = (row << 5) + column;
uint16_t attributeAddress = baseAttributeAddr + ((row & 0xFC) << 1) + (column >> 2);
bool tileHighlighted = isHighlighted(baseAddr + ntOffset, options.TileHighlightMode);
bool attrHighlighted = isHighlighted(attributeAddress, options.AttributeHighlightMode);
if(!tileHighlighted && !attrHighlighted) {
continue;
}
for(uint8_t y = 0; y < 8; y++) {
uint32_t offset = bufferOffset + (row << 12) + (column << 3) + (y << 9);
for(uint8_t x = 0; x < 8; x++) {
if(tileHighlighted) {
static constexpr uint32_t tileChangedColor = 0x80FF0000;
if(x == 0 || y == 0 || x == 7 || y == 7) {
outBuffer[offset + x] = 0xFF000000 | tileChangedColor;
} else {
BlendColors((uint8_t*)&outBuffer[offset + x], (uint8_t*)&tileChangedColor);
}
}
if(attrHighlighted) {
static constexpr uint32_t attrChangedColor = 0x80FFFF00;
bool isEdge = (
((column & 3) == 0 && x == 0) ||
((row & 3) == 0 && y == 0) ||
((column & 3) == 3 && x == 7) ||
((row & 3) == 3 && y == 7)
);
if(isEdge) {
outBuffer[offset + x] = 0xFF000000 | attrChangedColor;
} else {
BlendColors((uint8_t*)&outBuffer[offset + x], (uint8_t*)&attrChangedColor);
}
}
}
}
}
}
}
void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, DebugSpriteInfo* sprites, uint32_t* spritePreviews, uint32_t* palette, uint32_t* outBuffer)
{
uint32_t bgColor = GetSpriteBackgroundColor(options.Background, palette, false);
std::fill(outBuffer, outBuffer + 256 * 240, bgColor);
std::fill(outBuffer + 256 * 240, outBuffer + 256 * 256, GetSpriteBackgroundColor(options.Background, palette, true));
for(int i = 63; i >= 0; i--) {
DebugSpriteInfo& sprite = sprites[i];
uint32_t* spritePreview = spritePreviews + i * _spritePreviewSize;
int spritePosY = sprite.Y + 1;
for(int y = 0; y < sprite.Height; y++) {
for(int x = 0; x < sprite.Width; x++) {
uint32_t color = spritePreview[y * sprite.Width + x];
if(color != 0) {
if(spritePosY + y >= 256 || sprite.X + x >= 256) {
continue;
}
outBuffer[((spritePosY + y) * 256) + sprite.X + x] = color;
} else {
spritePreview[y * sprite.Width + x] = bgColor;
}
}
}
}
}
FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state)
{
if(options.Layer == 0) {
return { 512, 480 };
} else if(options.Layer == 1 && dynamic_cast<IExtModeMapperDebug*>(_mapper)) {
return { 256, 240 };
}
return { 0,0 };
}
DebugTilemapTileInfo NesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState)
{
DebugTilemapTileInfo result = {};
FrameInfo size = GetTilemapSize(options, baseState);
if(x >= size.Width || y >= size.Height) {
return result;
}
uint8_t row = y / 8;
uint8_t column = x / 8;
uint8_t nametableIndex = (column >= 32 ? 1 : 0) | (row >= 30 ? 2 : 0);
column &= 0x1F;
if(row >= 30) {
row -= 30;
}
IExtModeMapperDebug* exMode = dynamic_cast<IExtModeMapperDebug*>(_mapper);
ExtModeConfig& extCfg = ((NesPpuToolsState&)ppuToolsState).ExtConfig;
NesPpuState& state = (NesPpuState&)baseState;
uint16_t bgAddr = state.Control.BackgroundPatternAddr;
uint16_t baseAddr = 0x2000 + nametableIndex * 0x400;
uint16_t baseAttributeAddr = baseAddr + 960;
uint16_t ntOffset = (row << 5) + column;
uint16_t attributeAddress = baseAttributeAddr + ((row & 0xFC) << 1) + (column >> 2);
uint8_t attribute = vram[attributeAddress];
uint8_t shift = (column & 0x02) | ((row & 0x02) << 1);
uint8_t paletteBaseAddr;
if(exMode && exMode->HasExtendedAttributes(extCfg, nametableIndex)) {
paletteBaseAddr = exMode->GetExAttributePalette(extCfg, nametableIndex, ntOffset) << 2;
} else {
paletteBaseAddr = ((attribute >> shift) & 0x03) << 2;
}
result.Row = row;
result.Column = column;
result.Width = 8;
result.Height = 8;
result.TileMapAddress = baseAddr + ntOffset;
result.TileIndex = vram[result.TileMapAddress];
result.TileAddress = bgAddr + (result.TileIndex << 4);
result.PaletteIndex = paletteBaseAddr >> 2;
result.PaletteAddress = 0x3F00 | paletteBaseAddr;
result.AttributeAddress = attributeAddress;
result.AttributeData = vram[attributeAddress];
return result;
}
void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t* spritePreview, uint32_t i, GetSpritePreviewOptions& options, NesPpuState& state, NesPpuToolsState& ppuToolsState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
{
IExtModeMapperDebug* exMode = dynamic_cast<IExtModeMapperDebug*>(_mapper);
ExtModeConfig& extCfg = ppuToolsState.ExtConfig;
sprite.Bpp = 2;
sprite.Format = TileFormat::NesBpp2;
sprite.SpriteIndex = i;
sprite.UseExtendedVram = false;
sprite.Y = oamRam[i * 4];
sprite.X = oamRam[i * 4 + 3];
sprite.RawY = sprite.Y;
sprite.RawX = sprite.X;
sprite.TileIndex = oamRam[i * 4 + 1];
sprite.UseSecondTable = NullableBoolean::Undefined;
uint8_t attributes = oamRam[i * 4 + 2];
sprite.Palette = (attributes & 0x03);
sprite.PaletteAddress = 0x3F00 | ((attributes & 0x03) << 2);
bool horizontalMirror = (attributes & 0x40) != 0;
bool verticalMirror = (attributes & 0x80) != 0;
sprite.HorizontalMirror = horizontalMirror ? NullableBoolean::True : NullableBoolean::False;
sprite.VerticalMirror = verticalMirror ? NullableBoolean::True : NullableBoolean::False;
sprite.Priority = (attributes & 0x20) ? DebugSpritePriority::Background : DebugSpritePriority::Foreground;
sprite.Visibility = sprite.Y < 239 ? SpriteVisibility::Visible : SpriteVisibility::Offscreen;
sprite.Width = 8;
bool largeSprites = state.Control.LargeSprites;
sprite.Height = largeSprites ? 16 : 8;
uint16_t sprAddr = state.Control.SpritePatternAddr;
uint16_t tileStart;
if(largeSprites) {
if(sprite.TileIndex & 0x01) {
tileStart = 0x1000 | ((sprite.TileIndex & 0xFE) * 16);
} else {
tileStart = 0x0000 | (sprite.TileIndex * 16);
}
sprite.TileAddresses[0] = tileStart;
sprite.TileAddresses[1] = tileStart + 16;
sprite.TileCount = 2;
} else {
tileStart = (sprite.TileIndex * 16) | sprAddr;
sprite.TileAddresses[0] = tileStart;
sprite.TileCount = 1;
}
sprite.TileAddress = tileStart;
for(int y = 0; y < sprite.Height; y++) {
uint8_t lineOffset = verticalMirror ? (sprite.Height - 1 - y) : y;
uint16_t pixelStart = tileStart + lineOffset;
if(largeSprites && lineOffset >= 8) {
pixelStart += 8;
}
for(int x = 0; x < 8; x++) {
uint8_t lowByte, highByte;
if(exMode && exMode->HasExtendedSprites(extCfg)) {
lowByte = exMode->GetExSpriteChrData(extCfg, i, pixelStart);
highByte = exMode->GetExSpriteChrData(extCfg, i, pixelStart + 8);
} else {
lowByte = vram[pixelStart];
highByte = vram[pixelStart + 8];
}
uint8_t shift = horizontalMirror ? x : (7 - x);
uint8_t color = ((lowByte >> shift) & 0x01) | (((highByte >> shift) & 0x01) << 1);
uint32_t outOffset = (y * 8) + x;
if(color > 0) {
spritePreview[outOffset] = palette[16 + (sprite.Palette * 4) + color];
} else {
spritePreview[outOffset] = 0;
}
}
}
}
void NesPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[], uint32_t* spritePreviews, uint32_t* screenPreview)
{
NesPpuState& state = (NesPpuState&)baseState;
NesPpuToolsState& nesToolsState = (NesPpuToolsState&)ppuToolsState;
for(int i = 0; i < 64; i++) {
outBuffer[i].Init();
GetSpriteInfo(outBuffer[i], spritePreviews+i*_spritePreviewSize, i, options, state, nesToolsState, vram, oamRam, palette);
}
GetSpritePreview(options, baseState, outBuffer, spritePreviews, palette, screenPreview);
}
DebugSpritePreviewInfo NesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& state, BaseState& ppuToolsState)
{
DebugSpritePreviewInfo info = {};
info.Height = 256;
info.Width = 256;
info.SpriteCount = 64;
info.CoordOffsetX = 0;
info.CoordOffsetY = 1;
info.VisibleX = 0;
info.VisibleY = 0;
info.VisibleWidth = 256;
info.VisibleHeight = 240;
return info;
}
DebugPaletteInfo NesPpuTools::GetPaletteInfo(GetPaletteInfoOptions options)
{
DebugPaletteInfo info = {};
info.PaletteMemType = MemoryType::NesPaletteRam;
info.HasMemType = true;
info.RawFormat = RawPaletteFormat::Indexed;
info.ColorsPerPalette = 4;
info.BgColorCount = 4 * 4;
info.SpritePaletteOffset = info.BgColorCount;
info.SpriteColorCount = 4 * 4;
info.ColorCount = info.BgColorCount + info.SpriteColorCount;
uint32_t rgbPalette[512];
NesDefaultVideoFilter::GetFullPalette(rgbPalette, _console->GetNesConfig(), _console->GetPpu()->GetPpuModel());
uint8_t* paletteRam = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::NesPaletteRam);
for(int i = 0; i < 32; i++) {
info.RawPalette[i] = paletteRam[i];
info.RgbPalette[i] = rgbPalette[paletteRam[i]];
}
return info;
}
void NesPpuTools::SetPaletteColor(int32_t colorIndex, uint32_t color)
{
if(color < 0x3F) {
_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::NesPaletteRam, colorIndex, (uint8_t)color);
}
}