diff --git a/Core/Debugger/DebugTypes.h b/Core/Debugger/DebugTypes.h index 9aa696e1..9c109f47 100644 --- a/Core/Debugger/DebugTypes.h +++ b/Core/Debugger/DebugTypes.h @@ -144,9 +144,20 @@ struct CodeLineData char Comment[1000]; }; +enum class TilemapDisplayMode +{ + Default, + Grayscale, + AttributeView +}; + struct GetTilemapOptions { uint8_t Layer; + uint8_t* CompareVram; + bool HighlightTileChanges; + bool HighlightAttributeChanges; + TilemapDisplayMode DisplayMode; }; enum class TileFormat diff --git a/Core/Debugger/DebugUtilities.h b/Core/Debugger/DebugUtilities.h index 83773d6e..f26cf6df 100644 --- a/Core/Debugger/DebugUtilities.h +++ b/Core/Debugger/DebugUtilities.h @@ -79,9 +79,11 @@ public: case SnesMemoryType::NesMemory: case SnesMemoryType::NesNametableRam: case SnesMemoryType::NesPaletteRam: + case SnesMemoryType::NesPpuMemory: case SnesMemoryType::NesPrgRom: case SnesMemoryType::NesSaveRam: case SnesMemoryType::NesSpriteRam: + case SnesMemoryType::NesSecondarySpriteRam: case SnesMemoryType::NesWorkRam: return CpuType::Nes; @@ -94,7 +96,8 @@ public: static constexpr SnesMemoryType GetLastCpuMemoryType() { - return SnesMemoryType::NesMemory; + //TODO refactor to "IsRelativeMemory"? + return SnesMemoryType::NesPpuMemory; } static constexpr bool IsPpuMemory(SnesMemoryType memType) @@ -111,6 +114,8 @@ public: case SnesMemoryType::NesSpriteRam: case SnesMemoryType::NesPaletteRam: case SnesMemoryType::NesNametableRam: + case SnesMemoryType::NesSecondarySpriteRam: + case SnesMemoryType::NesPpuMemory: return true; default: diff --git a/Core/Debugger/MemoryDumper.cpp b/Core/Debugger/MemoryDumper.cpp index c83bd113..26b96ba6 100644 --- a/Core/Debugger/MemoryDumper.cpp +++ b/Core/Debugger/MemoryDumper.cpp @@ -16,7 +16,6 @@ #include "Debugger/MemoryDumper.h" #include "SNES/BaseCartridge.h" #include "NES/NesConsole.h" -#include "NES/NesMemoryManager.h" #include "Shared/Video/VideoDecoder.h" #include "Debugger/DebugTypes.h" #include "Debugger/DebugBreakHelper.h" @@ -37,7 +36,7 @@ MemoryDumper::MemoryDumper(Debugger* debugger) _cartridge = c->GetCartridge().get(); _gameboy = c->GetCartridge()->GetGameboy(); } else if(NesConsole* c = dynamic_cast(console)) { - _nesMemoryManager = c->GetMemoryManager(); + _nesConsole = c; } else if(Gameboy* c = dynamic_cast(console)) { _gameboy = c; } @@ -76,6 +75,7 @@ uint32_t MemoryDumper::GetMemorySize(SnesMemoryType type) case SnesMemoryType::Cx4Memory: return 0x1000000; case SnesMemoryType::GameboyMemory: return 0x10000; case SnesMemoryType::NesMemory: return 0x10000; + case SnesMemoryType::NesPpuMemory: return 0x4000; case SnesMemoryType::Register: return 0x10000; default: return _emu->GetMemory(type).Size; } @@ -135,9 +135,18 @@ void MemoryDumper::GetMemoryState(SnesMemoryType type, uint8_t *buffer) } case SnesMemoryType::NesMemory: { - if(_nesMemoryManager) { + if(_nesConsole) { for(int i = 0; i <= 0xFFFF; i++) { - buffer[i] = _nesMemoryManager->DebugRead(i); + buffer[i] = _nesConsole->DebugRead(i); + } + } + break; + } + + case SnesMemoryType::NesPpuMemory: { + if(_nesConsole) { + for(int i = 0; i < 0x4000; i++) { + buffer[i] = _nesConsole->DebugReadVram(i); } } break; @@ -187,7 +196,8 @@ void MemoryDumper::SetMemoryValue(SnesMemoryType memoryType, uint32_t address, u case SnesMemoryType::GsuMemory: _cartridge->GetGsu()->GetMemoryMappings()->DebugWrite(address, value); break; case SnesMemoryType::Cx4Memory: _cartridge->GetCx4()->GetMemoryMappings()->DebugWrite(address, value); break; case SnesMemoryType::GameboyMemory: _gameboy->GetMemoryManager()->DebugWrite(address, value); break; - case SnesMemoryType::NesMemory: _nesMemoryManager->DebugWrite(address, value); break; + case SnesMemoryType::NesMemory: _nesConsole->DebugWrite(address, value); break; + case SnesMemoryType::NesPpuMemory: _nesConsole->DebugWriteVram(address, value); break; default: uint8_t* src = GetMemoryBuffer(memoryType); @@ -221,7 +231,8 @@ uint8_t MemoryDumper::GetMemoryValue(SnesMemoryType memoryType, uint32_t address case SnesMemoryType::GsuMemory: return _cartridge->GetGsu()->GetMemoryMappings()->Peek(address); case SnesMemoryType::Cx4Memory: return _cartridge->GetCx4()->GetMemoryMappings()->Peek(address); case SnesMemoryType::GameboyMemory: return _gameboy->GetMemoryManager()->DebugRead(address); - case SnesMemoryType::NesMemory: return _nesMemoryManager->DebugRead(address); + case SnesMemoryType::NesMemory: return _nesConsole->DebugRead(address); + case SnesMemoryType::NesPpuMemory: return _nesConsole->DebugReadVram(address); default: uint8_t* src = GetMemoryBuffer(memoryType); diff --git a/Core/Debugger/MemoryDumper.h b/Core/Debugger/MemoryDumper.h index e3013185..1e438a9a 100644 --- a/Core/Debugger/MemoryDumper.h +++ b/Core/Debugger/MemoryDumper.h @@ -4,7 +4,7 @@ #include "DebugTypes.h" class MemoryManager; -class NesMemoryManager; +class NesConsole; class BaseCartridge; class Ppu; class Spc; @@ -22,7 +22,7 @@ private: Spc* _spc = nullptr; Gameboy* _gameboy = nullptr; MemoryManager* _memoryManager = nullptr; - NesMemoryManager* _nesMemoryManager = nullptr; + NesConsole* _nesConsole = nullptr; BaseCartridge* _cartridge = nullptr; Debugger* _debugger = nullptr; Disassembler* _disassembler = nullptr; diff --git a/Core/Debugger/PpuTools.cpp b/Core/Debugger/PpuTools.cpp index e470664b..f3bbad9d 100644 --- a/Core/Debugger/PpuTools.cpp +++ b/Core/Debugger/PpuTools.cpp @@ -41,7 +41,7 @@ uint8_t PpuTools::GetTilePixelColor(const uint8_t* ram, const uint32_t ramMask, void PpuTools::BlendColors(uint8_t output[4], uint8_t input[4]) { - uint8_t alpha = input[3] + 1; + int alpha = input[3] + 1; uint8_t invertedAlpha = 256 - input[3]; output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8); output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8); diff --git a/Core/Debugger/PpuTools.h b/Core/Debugger/PpuTools.h index 99d6f6ac..bd01143b 100644 --- a/Core/Debugger/PpuTools.h +++ b/Core/Debugger/PpuTools.h @@ -31,7 +31,11 @@ public: PpuTools(Debugger* debugger, Emulator *emu); void GetTileView(GetTileViewOptions options, uint8_t *source, uint32_t srcSize, uint32_t* palette, uint32_t *outBuffer); + + virtual FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) = 0; virtual void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) = 0; + + virtual FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) = 0; virtual void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer) = 0; void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle); diff --git a/Core/Gameboy/Debugger/GbPpuTools.cpp b/Core/Gameboy/Debugger/GbPpuTools.cpp index 8dfc04fa..e77c7f17 100644 --- a/Core/Gameboy/Debugger/GbPpuTools.cpp +++ b/Core/Gameboy/Debugger/GbPpuTools.cpp @@ -17,7 +17,7 @@ void GbPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uin uint16_t baseTile = state.BgTileSelect ? 0 : 0x1000; - std::fill(outBuffer, outBuffer + 1024 * 256, 0xFFFFFFFF); + std::fill(outBuffer, outBuffer + 256 * 256, 0xFFFFFFFF); uint16_t vramMask = isCgb ? 0x3FFF : 0x1FFF; @@ -45,7 +45,7 @@ void GbPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uin uint8_t shift = hMirror ? (x & 0x07) : (7 - (x & 0x07)); uint8_t color = GetTilePixelColor(vram, vramMask, 2, pixelStart, shift, 1); - outBuffer[((row * 8) + y) * 1024 + column * 8 + x] = SnesDefaultVideoFilter::ToArgb(state.CgbBgPalettes[bgPalette + color]); + outBuffer[((row * 8) + y) * 256 + column * 8 + x] = palette[bgPalette + color]; } } } @@ -116,4 +116,14 @@ void GbPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& ba } } } -} \ No newline at end of file +} + +FrameInfo GbPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state) +{ + return { 256, 256 }; +} + +FrameInfo GbPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) +{ + return { 256, 256 }; +} diff --git a/Core/Gameboy/Debugger/GbPpuTools.h b/Core/Gameboy/Debugger/GbPpuTools.h index 91854575..4e069253 100644 --- a/Core/Gameboy/Debugger/GbPpuTools.h +++ b/Core/Gameboy/Debugger/GbPpuTools.h @@ -11,5 +11,8 @@ public: GbPpuTools(Debugger* debugger, Emulator *emu); void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t *outBuffer) override; + FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override; + void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override; + FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override; }; \ No newline at end of file diff --git a/Core/NES/BaseMapper.cpp b/Core/NES/BaseMapper.cpp index 2dc870e2..da58fde2 100644 --- a/Core/NES/BaseMapper.cpp +++ b/Core/NES/BaseMapper.cpp @@ -1062,7 +1062,7 @@ int32_t BaseMapper::GetRelativeAddress(AddressInfo& addr) case SnesMemoryType::NesSaveRam: ptrAddress = _saveRam; break; case SnesMemoryType::Register: return (addr.Address) & 0xFFFF; break; case SnesMemoryType::NesInternalRam: return (addr.Address) & 0x1FFF; break; - default: return -1; + default: return GetPpuRelativeAddress(addr); } ptrAddress += addr.Address; diff --git a/Core/NES/BaseNesPpu.cpp b/Core/NES/BaseNesPpu.cpp index bc980b4e..19041415 100644 --- a/Core/NES/BaseNesPpu.cpp +++ b/Core/NES/BaseNesPpu.cpp @@ -7,6 +7,7 @@ void BaseNesPpu::GetState(NesPpuState& state) { //TODO //state.ControlFlags = _flags; + state.ControlReg = _controlReg; state.StatusFlags = _statusFlags; //state.State = _state; state.Cycle = _cycle; @@ -55,4 +56,34 @@ uint16_t BaseNesPpu::GetCurrentBgColor() color = _paletteRAM[_videoRamAddr & 0x1F]; } return (color & _paletteRamMask) | _intensifyColorBits; -} \ No newline at end of file +} + +uint8_t BaseNesPpu::ReadPaletteRam(uint16_t addr) +{ + addr &= 0x1F; + if(addr == 0x10 || addr == 0x14 || addr == 0x18 || addr == 0x1C) { + addr &= ~0x10; + } + return _paletteRAM[addr]; +} + +void BaseNesPpu::WritePaletteRam(uint16_t addr, uint8_t value) +{ + addr &= 0x1F; + value &= 0x3F; + if(addr == 0x00 || addr == 0x10) { + _paletteRAM[0x00] = value; + _paletteRAM[0x10] = value; + } else if(addr == 0x04 || addr == 0x14) { + _paletteRAM[0x04] = value; + _paletteRAM[0x14] = value; + } else if(addr == 0x08 || addr == 0x18) { + _paletteRAM[0x08] = value; + _paletteRAM[0x18] = value; + } else if(addr == 0x0C || addr == 0x1C) { + _paletteRAM[0x0C] = value; + _paletteRAM[0x1C] = value; + } else { + _paletteRAM[addr] = value; + } +} diff --git a/Core/NES/BaseNesPpu.h b/Core/NES/BaseNesPpu.h index 6e6c8539..66576ab5 100644 --- a/Core/NES/BaseNesPpu.h +++ b/Core/NES/BaseNesPpu.h @@ -148,6 +148,9 @@ public: uint16_t GetCurrentBgColor(); + uint8_t ReadPaletteRam(uint16_t addr); + void WritePaletteRam(uint16_t addr, uint8_t value); + virtual PpuModel GetPpuModel() = 0; virtual uint32_t GetPixelBrightness(uint8_t x, uint8_t y) = 0; diff --git a/Core/NES/Debugger/NesDebugger.cpp b/Core/NES/Debugger/NesDebugger.cpp index fdeb7747..e6a92f5a 100644 --- a/Core/NES/Debugger/NesDebugger.cpp +++ b/Core/NES/Debugger/NesDebugger.cpp @@ -36,7 +36,7 @@ NesDebugger::NesDebugger(Debugger* debugger) _mapper = console->GetMapper(); _traceLogger.reset(new NesTraceLogger(debugger, _ppu)); - _ppuTools.reset(new NesPpuTools(debugger, debugger->GetEmulator())); + _ppuTools.reset(new NesPpuTools(debugger, debugger->GetEmulator(), console)); _disassembler = debugger->GetDisassembler().get(); _memoryAccessCounter = debugger->GetMemoryAccessCounter().get(); _settings = debugger->GetEmulator()->GetSettings(); diff --git a/Core/NES/Debugger/NesPpuTools.cpp b/Core/NES/Debugger/NesPpuTools.cpp index c88477d8..987d2d05 100644 --- a/Core/NES/Debugger/NesPpuTools.cpp +++ b/Core/NES/Debugger/NesPpuTools.cpp @@ -1,16 +1,96 @@ #include "stdafx.h" #include "NES/Debugger/NesPpuTools.h" +#include "NES/Mappers/MMC5.h" +#include "NES/NesConsole.h" #include "Debugger/DebugTypes.h" #include "Shared/SettingTypes.h" -NesPpuTools::NesPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugger, emu) +NesPpuTools::NesPpuTools(Debugger* debugger, Emulator *emu, NesConsole* console) : PpuTools(debugger, emu) { + _mapper = console->GetMapper(); } void NesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) { + MMC5* mmc5 = dynamic_cast(_mapper); + NesPpuState& state = (NesPpuState&)baseState; + uint16_t bgAddr = (state.ControlReg & 0x10) ? 0x1000 : 0; + + uint8_t* prevVram = options.CompareVram != nullptr ? options.CompareVram : vram; + constexpr uint32_t grayscalePalette[4] = { 0xFF000000, 0xFF808080, 0xFFC0C0C0, 0xFFFFFFFF }; + for(int nametableIndex = 0; nametableIndex < 4; nametableIndex++) { + uint16_t baseAddr = 0x2000 + nametableIndex * 0x400; + uint16_t baseAttributeAddr = baseAddr + 960; + uint32_t bufferOffset = ((nametableIndex & 0x01) ? 256 : 0) + ((nametableIndex & 0x02) ? 512 * 240 : 0); + for(uint8_t row = 0; row < 30; row++) { + for(uint8_t column = 0; column < 32; column++) { + uint16_t ntIndex = (row << 5) + column; + uint16_t attributeAddress = baseAttributeAddr + ((row & 0xFC) << 1) + (column >> 2); + uint8_t tileIndex = vram[baseAddr + ntIndex]; + uint8_t attribute = vram[attributeAddress]; + bool tileChanged = options.HighlightTileChanges && prevVram[baseAddr + ntIndex] != tileIndex; + bool attrChanged = options.HighlightAttributeChanges && prevVram[attributeAddress] != attribute; + uint8_t shift = (column & 0x02) | ((row & 0x02) << 1); + + uint8_t paletteBaseAddr; + if(mmc5 && mmc5->IsExtendedAttributes()) { + paletteBaseAddr = mmc5->GetExAttributeNtPalette(ntIndex) << 2; + } else { + paletteBaseAddr = ((attribute >> shift) & 0x03) << 2; + } + + uint16_t tileAddr = bgAddr + (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[bufferOffset + (row << 12) + (column << 3) + (y << 9) + x] = palette[color == 0 ? 0 : (paletteBaseAddr + color)]; + } + } + } else { + for(uint8_t y = 0; y < 8; y++) { + uint8_t lowByte, highByte; + if(mmc5 && mmc5->IsExtendedAttributes()) { + lowByte = mmc5->GetExAttributeTileData(ntIndex, tileAddr + y); + highByte = mmc5->GetExAttributeTileData(ntIndex, tileAddr + y + 8); + } else { + lowByte = vram[tileAddr + y]; + highByte = vram[tileAddr + y + 8]; + } + + for(uint8_t x = 0; x < 8; x++) { + uint8_t color = ((lowByte >> (7 - x)) & 0x01) | (((highByte >> (7 - x)) & 0x01) << 1); + uint32_t offset = bufferOffset + (row << 12) + (column << 3) + (y << 9) + x; + if(options.DisplayMode == TilemapDisplayMode::Grayscale) { + outBuffer[offset] = grayscalePalette[color]; + } else { + outBuffer[offset] = palette[color == 0 ? 0 : (paletteBaseAddr + color)]; + if(tileChanged) { + uint32_t tileChangedColor = 0x80FF0000; + BlendColors((uint8_t*)&outBuffer[offset], (uint8_t*)&tileChangedColor); + } else if(attrChanged) { + uint32_t attrChangedColor = 0x80FFFF00; + BlendColors((uint8_t*)&outBuffer[offset], (uint8_t*)&attrChangedColor); + } + } + } + } + } + } + } + } } void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer) { -} \ No newline at end of file +} + +FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state) +{ + return { 512, 480 }; +} + +FrameInfo NesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) +{ + return { 256, 240 }; +} diff --git a/Core/NES/Debugger/NesPpuTools.h b/Core/NES/Debugger/NesPpuTools.h index 86355b23..5be90daa 100644 --- a/Core/NES/Debugger/NesPpuTools.h +++ b/Core/NES/Debugger/NesPpuTools.h @@ -4,12 +4,20 @@ class Debugger; class Emulator; +class BaseMapper; +class NesConsole; class NesPpuTools : public PpuTools { +private: + BaseMapper* _mapper = nullptr; + public: - NesPpuTools(Debugger* debugger, Emulator *emu); + NesPpuTools(Debugger* debugger, Emulator *emu, NesConsole* console); void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t *outBuffer) override; + FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override; + void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override; + FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override; }; \ No newline at end of file diff --git a/Core/NES/HdPacks/HdNesPpu.cpp b/Core/NES/HdPacks/HdNesPpu.cpp index 81589e34..459dbbfb 100644 --- a/Core/NES/HdPacks/HdNesPpu.cpp +++ b/Core/NES/HdPacks/HdNesPpu.cpp @@ -30,7 +30,7 @@ void* HdNesPpu::OnBeforeSendFrame() for(uint32_t address : _hdData->WatchedMemoryAddresses) { if(address & HdPackBaseMemoryCondition::PpuMemoryMarker) { if((address & 0x3FFF) >= 0x3F00) { - info->WatchedAddressValues[address] = ReadPaletteRAM(address); + info->WatchedAddressValues[address] = ReadPaletteRam(address); } else { info->WatchedAddressValues[address] = _console->GetMapper()->DebugReadVram(address & 0x3FFF, true); } diff --git a/Core/NES/HdPacks/HdNesPpu.h b/Core/NES/HdPacks/HdNesPpu.h index 61508579..52eebf78 100644 --- a/Core/NES/HdPacks/HdNesPpu.h +++ b/Core/NES/HdPacks/HdNesPpu.h @@ -87,12 +87,12 @@ public: tileInfo.Grayscale = _paletteRamMask == 0x30; tileInfo.EmphasisBits = _intensifyColorBits >> 6; - tileInfo.Tile.PpuBackgroundColor = ReadPaletteRAM(0); + tileInfo.Tile.PpuBackgroundColor = ReadPaletteRam(0); tileInfo.Tile.BgColorIndex = backgroundColor; if(backgroundColor == 0) { tileInfo.Tile.BgColor = tileInfo.Tile.PpuBackgroundColor; } else { - tileInfo.Tile.BgColor = ReadPaletteRAM(tilePalette + backgroundColor); + tileInfo.Tile.BgColor = ReadPaletteRam(tilePalette + backgroundColor); } tileInfo.XScroll = _xScroll; @@ -133,9 +133,9 @@ public: } if(tileInfo.Sprite[j].SpriteColorIndex == 0) { - tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(0); + tileInfo.Sprite[j].SpriteColor = ReadPaletteRam(0); } else { - tileInfo.Sprite[j].SpriteColor = ReadPaletteRAM(sprite.PaletteOffset + tileInfo.Sprite[j].SpriteColorIndex); + tileInfo.Sprite[j].SpriteColor = ReadPaletteRam(sprite.PaletteOffset + tileInfo.Sprite[j].SpriteColorIndex); } tileInfo.Sprite[j].PpuBackgroundColor = tileInfo.Tile.PpuBackgroundColor; @@ -169,7 +169,7 @@ public: } } else { //"If the current VRAM address points in the range $3F00-$3FFF during forced blanking, the color indicated by this palette location will be shown on screen instead of the backdrop color." - pixel = ReadPaletteRAM(_videoRamAddr) | _intensifyColorBits; + pixel = ReadPaletteRam(_videoRamAddr) | _intensifyColorBits; _info->ScreenTiles[bufferOffset].Tile.TileIndex = HdPpuTileInfo::NoTile; _info->ScreenTiles[bufferOffset].SpriteCount = 0; } diff --git a/Core/NES/NesConsole.cpp b/Core/NES/NesConsole.cpp index 4bdffc94..4c88ecae 100644 --- a/Core/NES/NesConsole.cpp +++ b/Core/NES/NesConsole.cpp @@ -329,7 +329,11 @@ vector NesConsole::GetCpuTypes() AddressInfo NesConsole::GetAbsoluteAddress(AddressInfo& relAddress) { - return _mapper->GetAbsoluteAddress(relAddress.Address); + if(relAddress.Type == SnesMemoryType::NesMemory) { + return _mapper->GetAbsoluteAddress(relAddress.Address); + } else { + return _mapper->GetPpuAbsoluteAddress(relAddress.Address); + } } AddressInfo NesConsole::GetRelativeAddress(AddressInfo& absAddress, CpuType cpuType) @@ -401,3 +405,30 @@ void NesConsole::ProcessAudioPlayerAction(AudioPlayerActionParams p) } } +uint8_t NesConsole::DebugRead(uint16_t addr) +{ + return _memoryManager->DebugRead(addr); +} + +void NesConsole::DebugWrite(uint16_t addr, uint8_t value) +{ + _memoryManager->DebugWrite(addr, value); +} + +uint8_t NesConsole::DebugReadVram(uint16_t addr) +{ + if(addr >= 0x3F00) { + return _ppu->ReadPaletteRam(addr); + } else { + return _mapper->DebugReadVram(addr); + } +} + +void NesConsole::DebugWriteVram(uint16_t addr, uint8_t value) +{ + if(addr >= 0x3F00) { + _ppu->WritePaletteRam(addr, value); + } else { + _mapper->DebugWriteVram(addr, value); + } +} \ No newline at end of file diff --git a/Core/NES/NesConsole.h b/Core/NES/NesConsole.h index 762a41de..4edba371 100644 --- a/Core/NES/NesConsole.h +++ b/Core/NES/NesConsole.h @@ -101,4 +101,8 @@ public: RomFormat GetRomFormat() override; AudioTrackInfo GetAudioTrackInfo() override; void ProcessAudioPlayerAction(AudioPlayerActionParams p) override; + uint8_t DebugRead(uint16_t addr); + void DebugWrite(uint16_t addr, uint8_t value); + uint8_t DebugReadVram(uint16_t addr); + void DebugWriteVram(uint16_t addr, uint8_t value); }; diff --git a/Core/NES/NesMemoryManager.h b/Core/NES/NesMemoryManager.h index d64987f1..e58329d5 100644 --- a/Core/NES/NesMemoryManager.h +++ b/Core/NES/NesMemoryManager.h @@ -55,6 +55,9 @@ class NesMemoryManager : public ISerializable uint8_t* GetInternalRAM(); + uint8_t DebugReadVram(uint16_t addr); + void DebugWriteVram(uint16_t addr, uint8_t value); + uint8_t Read(uint16_t addr, MemoryOperationType operationType = MemoryOperationType::Read); void Write(uint16_t addr, uint8_t value, MemoryOperationType operationType); diff --git a/Core/NES/NesPpu.cpp b/Core/NES/NesPpu.cpp index 3cf707a3..29f2730c 100644 --- a/Core/NES/NesPpu.cpp +++ b/Core/NES/NesPpu.cpp @@ -318,7 +318,7 @@ template uint8_t NesPpu::PeekRam(uint16_t addr) returnValue = _memoryReadBuffer; if((_videoRamAddr & 0x3FFF) >= 0x3F00 && !_console->GetNesConfig().DisablePaletteRead) { - returnValue = ReadPaletteRAM(_videoRamAddr) | (_openBus & 0xC0); + returnValue = ReadPaletteRam(_videoRamAddr) | (_openBus & 0xC0); openBusMask = 0xC0; } else { openBusMask = 0x00; @@ -375,7 +375,7 @@ template uint8_t NesPpu::ReadRam(uint16_t addr) _memoryReadBuffer = ReadVram(_ppuBusAddress & 0x3FFF, MemoryOperationType::Read); if((_ppuBusAddress & 0x3FFF) >= 0x3F00 && !_console->GetNesConfig().DisablePaletteRead) { - returnValue = ReadPaletteRAM(_ppuBusAddress) | (_openBus & 0xC0); + returnValue = ReadPaletteRam(_ppuBusAddress) | (_openBus & 0xC0); //TODO //_emu->DebugProcessVramReadOperation(MemoryOperationType::Read, _ppuBusAddress & 0x3FFF, returnValue); openBusMask = 0xC0; @@ -461,7 +461,7 @@ template void NesPpu::WriteRam(uint16_t addr, uint8_t value) break; case PPURegisters::VideoMemoryData: if((_ppuBusAddress & 0x3FFF) >= 0x3F00) { - WritePaletteRAM(_ppuBusAddress, value); + WritePaletteRam(_ppuBusAddress, value); //TODO //_console->DebugProcessVramWriteOperation(_ppuBusAddress & 0x3FFF, value); } else { @@ -482,36 +482,6 @@ template void NesPpu::WriteRam(uint16_t addr, uint8_t value) } } -template uint8_t NesPpu::ReadPaletteRAM(uint16_t addr) -{ - addr &= 0x1F; - if(addr == 0x10 || addr == 0x14 || addr == 0x18 || addr == 0x1C) { - addr &= ~0x10; - } - return _paletteRAM[addr]; -} - -template void NesPpu::WritePaletteRAM(uint16_t addr, uint8_t value) -{ - addr &= 0x1F; - value &= 0x3F; - if(addr == 0x00 || addr == 0x10) { - _paletteRAM[0x00] = value; - _paletteRAM[0x10] = value; - } else if(addr == 0x04 || addr == 0x14) { - _paletteRAM[0x04] = value; - _paletteRAM[0x14] = value; - } else if(addr == 0x08 || addr == 0x18) { - _paletteRAM[0x08] = value; - _paletteRAM[0x18] = value; - } else if(addr == 0x0C || addr == 0x1C) { - _paletteRAM[0x0C] = value; - _paletteRAM[0x1C] = value; - } else { - _paletteRAM[addr] = value; - } -} - template void NesPpu::ProcessTmpAddrScrollGlitch(uint16_t normalAddr, uint16_t value, uint16_t mask) { _tmpVideoRamAddr = normalAddr; @@ -1577,5 +1547,4 @@ template uint32_t NesPpu::GetPixelBrightness(uint8_t x, uint8_t y); template NesPpu::NesPpu(NesConsole* console); template uint16_t* NesPpu::GetScreenBuffer(bool previousBuffer); template void NesPpu::Exec(); -template uint8_t NesPpu::ReadPaletteRAM(uint16_t addr); template uint32_t NesPpu::GetPixelBrightness(uint8_t x, uint8_t y); diff --git a/Core/NES/NesPpu.h b/Core/NES/NesPpu.h index e6cb5404..f6a5bdea 100644 --- a/Core/NES/NesPpu.h +++ b/Core/NES/NesPpu.h @@ -122,9 +122,6 @@ public: PpuModel GetPpuModel() override; - uint8_t ReadPaletteRAM(uint16_t addr); - void WritePaletteRAM(uint16_t addr, uint8_t value); - uint8_t ReadRam(uint16_t addr) override; uint8_t PeekRam(uint16_t addr) override; void WriteRam(uint16_t addr, uint8_t value) override; diff --git a/Core/NES/NesTypes.h b/Core/NES/NesTypes.h index fea58da2..f76c2e16 100644 --- a/Core/NES/NesTypes.h +++ b/Core/NES/NesTypes.h @@ -148,6 +148,7 @@ struct NesPpuState : public BaseState uint32_t SafeOamScanline; uint16_t BusAddress; uint8_t MemoryReadBuffer; + uint8_t ControlReg; }; struct ApuLengthCounterState diff --git a/Core/SNES/Debugger/SnesPpuTools.cpp b/Core/SNES/Debugger/SnesPpuTools.cpp index 1c0383e6..163b754a 100644 --- a/Core/SNES/Debugger/SnesPpuTools.cpp +++ b/Core/SNES/Debugger/SnesPpuTools.cpp @@ -12,6 +12,7 @@ SnesPpuTools::SnesPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugge void SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) { PpuState& state = (PpuState&)baseState; + FrameInfo outputSize = GetTilemapSize(options, state); static constexpr uint8_t layerBpp[8][4] = { { 2,2,2,2 }, { 4,4,2,0 }, { 4,4,0,0 }, { 8,4,0,0 }, { 8,2,0,0 }, { 4,2,0,0 }, { 4,0,0,0 }, { 8,0,0,0 } @@ -26,7 +27,7 @@ void SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, u LayerConfig layer = state.Layers[options.Layer]; - std::fill(outBuffer, outBuffer + 1024*1024, palette[0]); + std::fill(outBuffer, outBuffer + outputSize.Width*outputSize.Height, palette[0]); uint8_t bpp = layerBpp[state.BgMode][options.Layer]; if(bpp == 0) { @@ -55,7 +56,7 @@ void SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, u } else { rgbColor = GetRgbPixelColor(palette, color, 0, 8, false, 0); } - outBuffer[((row * 8) + y) * 1024 + column * 8 + x] = rgbColor; + outBuffer[((row * 8) + y) * outputSize.Width + column * 8 + x] = rgbColor; } } } @@ -91,7 +92,7 @@ void SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, u uint8_t color = GetTilePixelColor(vram, Ppu::VideoRamSize - 1, bpp, pixelStart, shift, 1); if(color != 0) { uint8_t paletteIndex = bpp == 8 ? 0 : (vram[addr + 1] >> 2) & 0x07; - outBuffer[((row * tileHeight) + y) * 1024 + column * tileWidth + x] = GetRgbPixelColor(palette, color, paletteIndex, bpp, directColor, basePaletteOffset); + outBuffer[((row * tileHeight) + y) * outputSize.Width + column * tileWidth + x] = GetRgbPixelColor(palette, color, paletteIndex, bpp, directColor, basePaletteOffset); } } } @@ -213,3 +214,38 @@ void SnesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& } } } + +FrameInfo SnesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& baseState) +{ + FrameInfo size = { 256, 256 }; + + PpuState& state = (PpuState&)baseState; + if(state.BgMode == 7) { + return { 1024, 1024 }; + } + + LayerConfig layer = state.Layers[options.Layer]; + bool largeTileWidth = layer.LargeTiles || state.BgMode == 5 || state.BgMode == 6; + bool largeTileHeight = layer.LargeTiles; + + if(largeTileHeight) { + size.Height *= 2; + } + if(layer.DoubleHeight) { + size.Height *= 2; + } + + if(largeTileWidth) { + size.Width *= 2; + } + if(layer.DoubleWidth) { + size.Width *= 2; + } + + return size; +} + +FrameInfo SnesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) +{ + return { 256,240 }; +} diff --git a/Core/SNES/Debugger/SnesPpuTools.h b/Core/SNES/Debugger/SnesPpuTools.h index e2625896..070ccd34 100644 --- a/Core/SNES/Debugger/SnesPpuTools.h +++ b/Core/SNES/Debugger/SnesPpuTools.h @@ -12,5 +12,8 @@ public: SnesPpuTools(Debugger* debugger, Emulator *emu); void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) override; + FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override; + void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer) override; + FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override; }; \ No newline at end of file diff --git a/Core/Shared/Audio/SoundMixer.cpp b/Core/Shared/Audio/SoundMixer.cpp index 5fb94abc..332e1877 100644 --- a/Core/Shared/Audio/SoundMixer.cpp +++ b/Core/Shared/Audio/SoundMixer.cpp @@ -129,13 +129,12 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount, uint32_ } if(_audioDevice) { - if(!cfg.EnableAudio) { + if(cfg.EnableAudio) { + _audioDevice->PlayBuffer(out, count, cfg.SampleRate, true); + _audioDevice->ProcessEndOfFrame(); + } else { _audioDevice->Stop(); - return; } - - _audioDevice->PlayBuffer(out, count, cfg.SampleRate, true); - _audioDevice->ProcessEndOfFrame(); } } } diff --git a/Core/SnesMemoryType.h b/Core/SnesMemoryType.h index b6a33edc..a6f7daa0 100644 --- a/Core/SnesMemoryType.h +++ b/Core/SnesMemoryType.h @@ -10,6 +10,7 @@ enum class SnesMemoryType Cx4Memory, GameboyMemory, NesMemory, + NesPpuMemory, PrgRom, WorkRam, diff --git a/InteropDLL/DebugApiWrapper.cpp b/InteropDLL/DebugApiWrapper.cpp index aa186de0..4f1491b7 100644 --- a/InteropDLL/DebugApiWrapper.cpp +++ b/InteropDLL/DebugApiWrapper.cpp @@ -94,7 +94,10 @@ extern "C" DllExport void __stdcall SetCdlData(CpuType cpuType, uint8_t* cdlData, uint32_t length) { GetDebugger()->SetCdlData(cpuType, cdlData, length); } DllExport void __stdcall MarkBytesAs(CpuType cpuType, uint32_t start, uint32_t end, uint8_t flags) { GetDebugger()->MarkBytesAs(cpuType, start, end, flags); } - DllExport void __stdcall GetTilemap(CpuType cpuType, GetTilemapOptions options, BaseState& state, uint8_t *vram, uint32_t* palette, uint32_t *buffer) { GetDebugger()->GetPpuTools(cpuType)->GetTilemap(options, state, vram, palette, buffer); } + DllExport FrameInfo __stdcall GetTilemapSize(CpuType cpuType, GetTilemapOptions options, BaseState& state) { return GetDebugger()->GetPpuTools(cpuType)->GetTilemapSize(options, state); } + DllExport FrameInfo __stdcall GetSpritePreviewSize(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state) { return GetDebugger()->GetPpuTools(cpuType)->GetSpritePreviewSize(options, state); } + + DllExport void __stdcall GetTilemap(CpuType cpuType, GetTilemapOptions options, BaseState& state, uint8_t *vram, uint32_t* palette, uint32_t *outputBuffer) { GetDebugger()->GetPpuTools(cpuType)->GetTilemap(options, state, vram, palette, outputBuffer); } DllExport void __stdcall GetTileView(CpuType cpuType, GetTileViewOptions options, uint8_t *source, uint32_t srcSize, uint32_t *colors, uint32_t *buffer) { GetDebugger()->GetPpuTools(cpuType)->GetTileView(options, source, srcSize, colors, buffer); } DllExport void __stdcall GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t *oamRam, uint32_t* palette, uint32_t *buffer) { GetDebugger()->GetPpuTools(cpuType)->GetSpritePreview(options, state, vram, oamRam, palette, buffer); } diff --git a/NewUI/Debugger/Controls/PictureViewer.cs b/NewUI/Debugger/Controls/PictureViewer.cs index 4e1ecfe8..dc3ccacd 100644 --- a/NewUI/Debugger/Controls/PictureViewer.cs +++ b/NewUI/Debugger/Controls/PictureViewer.cs @@ -23,6 +23,10 @@ namespace Mesen.Debugger.Controls public static readonly StyledProperty GridSizeYProperty = AvaloniaProperty.Register(nameof(GridSizeY), 8); public static readonly StyledProperty ShowGridProperty = AvaloniaProperty.Register(nameof(ShowGrid), false); + public static readonly StyledProperty AltGridSizeXProperty = AvaloniaProperty.Register(nameof(AltGridSizeX), 8); + public static readonly StyledProperty AltGridSizeYProperty = AvaloniaProperty.Register(nameof(AltGridSizeY), 8); + public static readonly StyledProperty ShowAltGridProperty = AvaloniaProperty.Register(nameof(ShowAltGrid), false); + private Stopwatch _stopWatch = Stopwatch.StartNew(); private DispatcherTimer _timer = new DispatcherTimer(); @@ -56,6 +60,24 @@ namespace Mesen.Debugger.Controls set { SetValue(ShowGridProperty, value); } } + public int AltGridSizeX + { + get { return GetValue(AltGridSizeXProperty); } + set { SetValue(AltGridSizeXProperty, value); } + } + + public int AltGridSizeY + { + get { return GetValue(AltGridSizeYProperty); } + set { SetValue(AltGridSizeYProperty, value); } + } + + public bool ShowAltGrid + { + get { return GetValue(ShowAltGridProperty); } + set { SetValue(ShowAltGridProperty, value); } + } + private Point? _mousePosition = null; private PixelPoint? _selectedTile = null; @@ -130,6 +152,28 @@ namespace Mesen.Debugger.Controls InvalidateVisual(); } + private void DrawGrid(DrawingContext context, bool show, int gridX, int gridY, Color color) + { + if(show) { + int width = Source.PixelSize.Width * Zoom; + int height = Source.PixelSize.Height * Zoom; + int gridSizeX = gridX * Zoom; + int gridSizeY = gridY * Zoom; + + Pen pen = new Pen(color.ToUint32(), 1 + (Zoom / 2)); + double offset = 0; + if(Zoom % 2 == 1) { + offset = 0.5; + } + for(int i = 1; i < width / gridSizeX; i++) { + context.DrawLine(pen, new Point(i * gridSizeX + offset, 0), new Point(i * gridSizeX + offset, height)); + } + for(int i = 1; i < height / gridSizeY; i++) { + context.DrawLine(pen, new Point(0, i * gridSizeY + offset), new Point(width, i * gridSizeY + offset)); + } + } + } + public override void Render(DrawingContext context) { if(Source == null) { @@ -148,26 +192,19 @@ namespace Mesen.Debugger.Controls Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.Default ); - int gridSizeX = GridSizeX * Zoom; - int gridSizeY = GridSizeY * Zoom; - if(ShowGrid) { - Pen pen = new Pen(Color.FromArgb(128, Colors.LightBlue.R, Colors.LightBlue.G, Colors.LightBlue.B).ToUint32()); - for(int i = 1; i < width / gridSizeX; i++) { - context.DrawLine(pen, new Point(i * gridSizeX + 0.5, 0), new Point(i* gridSizeX + 0.5, height)); - } - for(int i = 1; i < height / gridSizeY; i++) { - context.DrawLine(pen, new Point(0, i * gridSizeY + 0.5), new Point(width, i * gridSizeY + 0.5)); - } - } + DrawGrid(context, ShowGrid, GridSizeX, GridSizeY, Color.FromArgb(128, Colors.LightBlue.R, Colors.LightBlue.G, Colors.LightBlue.B)); + DrawGrid(context, ShowAltGrid, AltGridSizeX, AltGridSizeY, Color.FromArgb(128, Colors.Red.R, Colors.Red.G, Colors.Red.B)); if(_selectedTile.HasValue) { + int gridSizeX = GridSizeX * Zoom; + int gridSizeY = GridSizeY * Zoom; + Rect rect = new Rect( _selectedTile.Value.X * gridSizeX - 0.5, _selectedTile.Value.Y * gridSizeY - 0.5, gridSizeX + 1, gridSizeY + 1 ); - DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, (double)(_stopWatch.ElapsedMilliseconds / 50) % 100 / 5); context.DrawRectangle(new Pen(0x40000000, 2), rect.Inflate(0.5)); diff --git a/NewUI/Debugger/PaletteHelper.cs b/NewUI/Debugger/PaletteHelper.cs new file mode 100644 index 00000000..833a2878 --- /dev/null +++ b/NewUI/Debugger/PaletteHelper.cs @@ -0,0 +1,82 @@ +using Mesen.Config; +using Mesen.Interop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mesen.Debugger +{ + public static class PaletteHelper + { + private static uint To8Bit(int color) + { + return (uint)((color << 3) + (color >> 2)); + } + + public static uint ToArgb(int rgb555) + { + uint b = To8Bit(rgb555 >> 10); + uint g = To8Bit((rgb555 >> 5) & 0x1F); + uint r = To8Bit(rgb555 & 0x1F); + + return (0xFF000000 | (r << 16) | (g << 8) | b); + } + + public static UInt32[] GetConvertedPalette(CpuType cpuType, ConsoleType consoleType) + { + switch(cpuType) { + case CpuType.Cpu: { + byte[] cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); + UInt32[] colors = new UInt32[256]; + for(int i = 0; i < 256; i++) { + colors[i] = ToArgb(cgram[i * 2] | cgram[i * 2 + 1] << 8); + } + return colors; + } + + case CpuType.Nes: { + byte[] cgram = DebugApi.GetMemoryState(SnesMemoryType.NesPaletteRam); + UInt32[] colors = new UInt32[32]; + for(int i = 0; i < 32; i++) { + colors[i] = ConfigManager.Config.Nes.UserPalette[cgram[i]]; + } + return colors; + } + + case CpuType.Gameboy: { + GbPpuState ppu = DebugApi.GetPpuState(CpuType.Gameboy); + if(consoleType == ConsoleType.GameboyColor) { + UInt32[] colors = new UInt32[64]; + for(int i = 0; i < 32; i++) { + colors[i] = ToArgb(ppu.CgbBgPalettes[i]); + } + + for(int i = 0; i < 32; i++) { + colors[i + 32] = ToArgb(ppu.CgbObjPalettes[i]); + } + return colors; + } else { + UInt32[] colors = new UInt32[16]; + GameboyConfig cfg = ConfigManager.Config.Gameboy; + + for(int i = 0; i < 4; i++) { + colors[i] = cfg.BgColors[(ppu.BgPalette >> (i * 2)) & 0x03]; + colors[i + 4] = cfg.Obj0Colors[(ppu.ObjPalette0 >> (i * 2)) & 0x03]; + colors[i + 8] = cfg.Obj1Colors[(ppu.ObjPalette1 >> (i * 2)) & 0x03]; + } + + colors[12] = 0xFFFFFFFF; + colors[13] = 0xFFB0B0B0; + colors[14] = 0xFF606060; + colors[15] = 0xFF000000; + return colors; + } + } + } + + throw new Exception("Invalid cpu type"); + } + } +} diff --git a/NewUI/Debugger/ViewModels/MemoryToolsViewModel.cs b/NewUI/Debugger/ViewModels/MemoryToolsViewModel.cs index c4136672..4c4c3bc5 100644 --- a/NewUI/Debugger/ViewModels/MemoryToolsViewModel.cs +++ b/NewUI/Debugger/ViewModels/MemoryToolsViewModel.cs @@ -20,6 +20,8 @@ namespace Mesen.Debugger.ViewModels [Reactive] public int ScrollPosition { get; set; } [Reactive] public HexEditorDataProvider? DataProvider { get; set; } + [Reactive] public Enum[] AvailableMemoryTypes { get; set; } = Array.Empty(); + [ObservableAsProperty] public int MaxScrollValue { get; } public int[] AvailableWidths { get => new int[] { 4, 8, 16, 32, 48, 64, 80, 96, 112, 128 }; } @@ -34,6 +36,8 @@ namespace Mesen.Debugger.ViewModels return; } + AvailableMemoryTypes = Enum.GetValues().Where(t => DebugApi.GetMemorySize(t) > 0).Cast().ToArray(); + this.WhenAnyValue(x => x.MemoryType).Subscribe(x => DataProvider = new HexEditorDataProvider( x, true, diff --git a/NewUI/Debugger/ViewModels/SnesPpuViewModel.cs b/NewUI/Debugger/ViewModels/SnesPpuViewModel.cs index cc01ea8d..21bf7741 100644 --- a/NewUI/Debugger/ViewModels/SnesPpuViewModel.cs +++ b/NewUI/Debugger/ViewModels/SnesPpuViewModel.cs @@ -12,12 +12,7 @@ namespace Mesen.Debugger.ViewModels [ObservableAsProperty] public string? Scanline { get; } [ObservableAsProperty] public string? HClock { get; } - private PpuState _state; - public PpuState State - { - get => _state; - set => this.RaiseAndSetIfChanged(ref _state, value); - } + [Reactive] private PpuState State { get; set; } public SnesPpuViewModel() { diff --git a/NewUI/Debugger/ViewModels/TileViewerViewModel.cs b/NewUI/Debugger/ViewModels/TileViewerViewModel.cs index b41fac3d..e35a5a89 100644 --- a/NewUI/Debugger/ViewModels/TileViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/TileViewerViewModel.cs @@ -16,8 +16,8 @@ namespace Mesen.Debugger.ViewModels { public class TileViewerViewModel : ViewModelBase { - public CpuType CpuType { get; } - public ConsoleType ConsoleType { get; } + private CpuType _cpuType { get; } + private ConsoleType _consoleType { get; } [Reactive] public SnesMemoryType MemoryType { get; set; } [Reactive] public TileFormat TileFormat { get; set; } @@ -43,8 +43,8 @@ namespace Mesen.Debugger.ViewModels public TileViewerViewModel(CpuType cpuType, ConsoleType consoleType) { - CpuType = cpuType; - ConsoleType = consoleType; + _cpuType = cpuType; + _consoleType = consoleType; TileFormat = TileFormat.Bpp2; TileLayout = TileLayout.Normal; @@ -60,7 +60,7 @@ namespace Mesen.Debugger.ViewModels } AvailableMemoryTypes = Enum.GetValues().Where(t => DebugApi.GetMemorySize(t) > 0).Cast().ToArray(); - switch(CpuType) { + switch(_cpuType) { case CpuType.Cpu: MemoryType = SnesMemoryType.VideoRam; AvailableFormats = new Enum[] { TileFormat.Bpp2, TileFormat.Bpp4, TileFormat.Bpp8, TileFormat.DirectColor, TileFormat.Mode7, TileFormat.Mode7DirectColor }; @@ -107,75 +107,9 @@ namespace Mesen.Debugger.ViewModels }).ToPropertyEx(this, x => x.MaximumAddress); } - private uint To8Bit(int color) - { - return (uint)((color << 3) + (color >> 2)); - } - - public uint ToArgb(int rgb555) - { - uint b = To8Bit(rgb555 >> 10); - uint g = To8Bit((rgb555 >> 5) & 0x1F); - uint r = To8Bit(rgb555 & 0x1F); - - return (0xFF000000 | (r << 16) | (g << 8) | b); - } - public void UpdatePaletteColors() { - switch(CpuType) { - case CpuType.Cpu: { - byte[] cgram = DebugApi.GetMemoryState(SnesMemoryType.CGRam); - UInt32[] colors = new UInt32[256]; - for(int i = 0; i < 256; i++) { - colors[i] = ToArgb(cgram[i * 2] | cgram[i * 2 + 1] << 8); - } - PaletteColors = colors; - break; - } - - case CpuType.Nes: { - byte[] cgram = DebugApi.GetMemoryState(SnesMemoryType.NesPaletteRam); - UInt32[] colors = new UInt32[32]; - for(int i = 0; i < 32; i++) { - colors[i] = ConfigManager.Config.Nes.UserPalette[cgram[i]]; - } - PaletteColors = colors; - break; - } - - case CpuType.Gameboy: { - GbPpuState ppu = DebugApi.GetPpuState(CpuType.Gameboy); - if(ConsoleType == ConsoleType.GameboyColor) { - UInt32[] colors = new UInt32[64]; - for(int i = 0; i < 32; i++) { - colors[i] = ToArgb(ppu.CgbBgPalettes[i]); - } - - for(int i = 0; i < 32; i++) { - colors[i+32] = ToArgb(ppu.CgbObjPalettes[i]); - } - PaletteColors = colors; - } else { - UInt32[] colors = new UInt32[16]; - GameboyConfig cfg = ConfigManager.Config.Gameboy; - - for(int i = 0; i < 4; i++) { - colors[i] = cfg.BgColors[(ppu.BgPalette >> (i*2)) & 0x03]; - colors[i+4] = cfg.Obj0Colors[(ppu.ObjPalette0 >> (i * 2)) & 0x03]; - colors[i+8] = cfg.Obj1Colors[(ppu.ObjPalette1 >> (i * 2)) & 0x03]; - } - - colors[12] = 0xFFFFFFFF; - colors[13] = 0xFFB0B0B0; - colors[14] = 0xFF606060; - colors[15] = 0xFF000000; - PaletteColors = colors; - } - - break; - } - } + PaletteColors = PaletteHelper.GetConvertedPalette(_cpuType, _consoleType); } } } diff --git a/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs new file mode 100644 index 00000000..f1ca797d --- /dev/null +++ b/NewUI/Debugger/ViewModels/TilemapViewerViewModel.cs @@ -0,0 +1,62 @@ +using Avalonia.Controls; +using Mesen.Interop; +using Mesen.ViewModels; +using ReactiveUI.Fody.Helpers; +using System; +using System.Collections.Generic; + +namespace Mesen.Debugger.ViewModels +{ + public class TilemapViewerViewModel : ViewModelBase + { + public CpuType CpuType { get; } + public ConsoleType ConsoleType { get; } + + [Reactive] public bool ShowGrid { get; set; } + [Reactive] public bool ShowAltGrid { get; set; } + [Reactive] public bool HighlightTileChanges { get; set; } + [Reactive] public bool HighlightAttributeChanges { get; set; } + [Reactive] public TilemapDisplayMode DisplayMode { get; set; } + + [Reactive] public List Tabs { get; set; } = new List(); + + //For designer + public TilemapViewerViewModel() : this(CpuType.Cpu, ConsoleType.Snes) { } + + public TilemapViewerViewModel(CpuType cpuType, ConsoleType consoleType) + { + CpuType = cpuType; + ConsoleType = consoleType; + + switch(CpuType) { + case CpuType.Cpu: + Tabs = new List() { + new() { Title = "Layer 1", Layer = 0 }, + new() { Title = "Layer 2", Layer = 1 }, + new() { Title = "Layer 3", Layer = 2 }, + new() { Title = "Layer 4", Layer = 3 }, + }; + break; + + case CpuType.Nes: + Tabs = new List() { + new() { Title = "", Layer = 0 } + }; + break; + + case CpuType.Gameboy: + Tabs = new List() { + new() { Title = "Layer 1", Layer = 0 }, + new() { Title = "Layer 2", Layer = 1 } + }; + break; + } + } + } + + public class TilemapViewerTab + { + public string Title { get; set; } = ""; + public int Layer { get; set; } = 0; + } +} diff --git a/NewUI/Debugger/Windows/EventViewerWindow.axaml.cs b/NewUI/Debugger/Windows/EventViewerWindow.axaml.cs index f1d1dd36..c0457965 100644 --- a/NewUI/Debugger/Windows/EventViewerWindow.axaml.cs +++ b/NewUI/Debugger/Windows/EventViewerWindow.axaml.cs @@ -88,7 +88,7 @@ namespace Mesen.Debugger.Windows if(this.DataContext is EventViewerViewModel model) { _model = model; } else { - throw new Exception("Unexception model"); + throw new Exception("Unexpected model"); } } diff --git a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml index d0f7780d..3d0c6576 100644 --- a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml +++ b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml @@ -29,6 +29,7 @@ DockPanel.Dock="Left" EnumType="{x:Type i:SnesMemoryType}" SelectedItem="{CompiledBinding MemoryType}" + AvailableValues="{CompiledBinding AvailableMemoryTypes}" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NewUI/Debugger/Windows/TilemapViewerWindow.axaml.cs b/NewUI/Debugger/Windows/TilemapViewerWindow.axaml.cs new file mode 100644 index 00000000..c6cc418f --- /dev/null +++ b/NewUI/Debugger/Windows/TilemapViewerWindow.axaml.cs @@ -0,0 +1,128 @@ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using System; +using Mesen.Debugger.Controls; +using Mesen.Debugger.ViewModels; +using Avalonia.Platform; +using Mesen.Interop; +using System.ComponentModel; + +namespace Mesen.Debugger.Windows +{ + public class TilemapViewerWindow : Window + { + private NotificationListener _listener; + private TilemapViewerViewModel _model; + private PictureViewer _picViewer; + private WriteableBitmap _viewerBitmap; + private byte[]? _prevVram = null; + private TilemapViewerTab _selectedTab = new TilemapViewerTab(); + + public TilemapViewerWindow() + { + InitializeComponent(); +#if DEBUG + this.AttachDevTools(); +#endif + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + + if(Design.IsDesignMode) { + return; + } + + //Renderer.DrawFps = true; + _picViewer = this.FindControl("picViewer"); + _picViewer.Source = _viewerBitmap; + InitBitmap(256, 256); + + _listener = new NotificationListener(); + _listener.OnNotification += listener_OnNotification; + } + + protected override void OnClosing(CancelEventArgs e) + { + _listener?.Dispose(); + base.OnClosing(e); + } + + private void InitBitmap(int width, int height) + { + _viewerBitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul); + } + + protected override void OnDataContextChanged(EventArgs e) + { + if(this.DataContext is TilemapViewerViewModel model) { + _model = model; + } else { + throw new Exception("Unexpected model"); + } + } + + private void OnTabSelected(object? sender, SelectionChangedEventArgs e) + { + if(e.AddedItems.Count > 0) { + _selectedTab = (TilemapViewerTab)e.AddedItems[0]!; + } + } + + private void UpdateTilemap() where T : struct, BaseState + { + T ppuState = DebugApi.GetPpuState(_model.CpuType); + byte[] vram = DebugApi.GetMemoryState(_model.CpuType.GetVramMemoryType()); + byte[]? prevVram = _prevVram; + _prevVram = vram; + UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType); + + Dispatcher.UIThread.Post(() => { + GetTilemapOptions options = new GetTilemapOptions() { + Layer = (byte)_selectedTab.Layer, + CompareVram = prevVram, + HighlightTileChanges = _model.HighlightTileChanges, + HighlightAttributeChanges = _model.HighlightAttributeChanges, + DisplayMode = _model.DisplayMode + }; + UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType); + + FrameInfo size = DebugApi.GetTilemapSize(_model.CpuType, options, ppuState); + if(_viewerBitmap.PixelSize.Width != size.Width || _viewerBitmap.PixelSize.Height != size.Height) { + InitBitmap((int)size.Width, (int)size.Height); + } + + using(var framebuffer = _viewerBitmap.Lock()) { + DebugApi.GetTilemap(_model.CpuType, options, ppuState, vram, palette, framebuffer.Address); + } + + _picViewer.Source = _viewerBitmap; + _picViewer.InvalidateVisual(); + }); + } + + private void listener_OnNotification(NotificationEventArgs e) + { + if(e.NotificationType != ConsoleNotificationType.PpuFrameDone) { + return; + } + + switch(_model.CpuType) { + case CpuType.Cpu: UpdateTilemap(); break; + case CpuType.Nes: UpdateTilemap(); break; + case CpuType.Gameboy: UpdateTilemap(); break; + } + } + } +} diff --git a/NewUI/Interop/DebugApi.cs b/NewUI/Interop/DebugApi.cs index 0e5fc3f4..6b7665aa 100644 --- a/NewUI/Interop/DebugApi.cs +++ b/NewUI/Interop/DebugApi.cs @@ -130,13 +130,42 @@ namespace Mesen.Interop return buffer; } - [DllImport(DllPath)] public static extern void GetTilemap(CpuType cpuType, GetTilemapOptions options, PpuState state, byte[] vram, UInt32[] palette, IntPtr buffer); + [DllImport(DllPath)] private static extern void GetTilemap(CpuType cpuType, InteropGetTilemapOptions options, IntPtr state, byte[] vram, UInt32[] palette, IntPtr outputBuffer); + public static void GetTilemap(CpuType cpuType, GetTilemapOptions options, T state, byte[] vram, UInt32[] palette, IntPtr outputBuffer) where T : struct, BaseState + { + GCHandle? handle = null; + IntPtr pointer = IntPtr.Zero; + handle?.Free(); + + if(options.CompareVram != null) { + handle = GCHandle.Alloc(options.CompareVram, GCHandleType.Pinned); + pointer = handle.Value.AddrOfPinnedObject(); + } + + int len = Marshal.SizeOf(typeof(T)); + IntPtr ptr = Marshal.AllocHGlobal(len); + Marshal.StructureToPtr(state, ptr, false); + InteropGetTilemapOptions interopOptions = options.ToInterop(); + interopOptions.CompareVram = pointer; + DebugApi.GetTilemap(cpuType, interopOptions, ptr, vram, palette, outputBuffer); + + if(handle.HasValue) { + handle.Value.Free(); + } + } + + [DllImport(DllPath)] private static extern FrameInfo GetTilemapSize(CpuType cpuType, InteropGetTilemapOptions options, IntPtr state); + public static FrameInfo GetTilemapSize(CpuType cpuType, GetTilemapOptions options, T state) where T : struct, BaseState + { + int len = Marshal.SizeOf(typeof(T)); + IntPtr ptr = Marshal.AllocHGlobal(len); + Marshal.StructureToPtr(state, ptr, false); + return DebugApi.GetTilemapSize(cpuType, options.ToInterop(), ptr); + } + [DllImport(DllPath)] public static extern void GetTileView(CpuType cpuType, GetTileViewOptions options, byte[] source, int srcSize, UInt32[] palette, IntPtr buffer); [DllImport(DllPath)] public static extern void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, PpuState state, byte[] vram, byte[] oamRam, UInt32[] palette, IntPtr buffer); - [DllImport(DllPath)] public static extern void GetGameboyTilemap(byte[] vram, GbPpuState state, UInt16 offset, [In, Out] byte[] buffer); - [DllImport(DllPath)] public static extern void GetGameboySpritePreview(GetSpritePreviewOptions options, GbPpuState state, byte[] vram, byte[] oamRam, [In, Out] byte[] buffer); - [DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle, CpuType cpuType); [DllImport(DllPath)] private static extern UInt32 GetDebugEventCount(CpuType cpuType); @@ -242,6 +271,7 @@ namespace Mesen.Interop Cx4Memory, GameboyMemory, NesMemory, + NesPpuMemory, PrgRom, WorkRam, @@ -630,9 +660,39 @@ namespace Mesen.Interop [MarshalAs(UnmanagedType.I1)] public bool ShowPreviousFrameEvents; } - public struct GetTilemapOptions + public enum TilemapDisplayMode + { + Default, + Grayscale, + AttributeView + } + + public class GetTilemapOptions { public byte Layer; + public byte[]? CompareVram; + public bool HighlightTileChanges; + public bool HighlightAttributeChanges; + public TilemapDisplayMode DisplayMode; + + public InteropGetTilemapOptions ToInterop() + { + return new InteropGetTilemapOptions() { + Layer = Layer, + HighlightTileChanges = HighlightTileChanges, + HighlightAttributeChanges = HighlightAttributeChanges, + DisplayMode = DisplayMode + }; + } + } + + public struct InteropGetTilemapOptions + { + public byte Layer; + public IntPtr CompareVram; + [MarshalAs(UnmanagedType.I1)] public bool HighlightTileChanges; + [MarshalAs(UnmanagedType.I1)] public bool HighlightAttributeChanges; + public TilemapDisplayMode DisplayMode; } public enum TileBackground @@ -748,6 +808,18 @@ namespace Mesen.Interop } } + public static SnesMemoryType GetVramMemoryType(this CpuType cpuType) + { + switch(cpuType) { + case CpuType.Cpu: return SnesMemoryType.VideoRam; + case CpuType.Gameboy: return SnesMemoryType.GbVideoRam; + case CpuType.Nes: return SnesMemoryType.NesPpuMemory; + + default: + throw new Exception("Invalid CPU type"); + } + } + public static int GetAddressSize(this CpuType cpuType) { switch(cpuType) { diff --git a/NewUI/Interop/DebugState.cs b/NewUI/Interop/DebugState.cs index 7531a64c..b8f6475b 100644 --- a/NewUI/Interop/DebugState.cs +++ b/NewUI/Interop/DebugState.cs @@ -75,7 +75,7 @@ namespace Mesen.Interop public CpuStopState StopState; } - public struct PpuState + public struct PpuState : BaseState { public UInt16 Cycle; public UInt16 Scanline; @@ -941,6 +941,27 @@ namespace Mesen.Interop public AluState Alu; } + public struct NesPpuStatusFlags + { + [MarshalAs(UnmanagedType.I1)] public bool SpriteOverflow; + [MarshalAs(UnmanagedType.I1)] public bool Sprite0Hit; + [MarshalAs(UnmanagedType.I1)] public bool VerticalBlank; + } + + public struct NesPpuState : BaseState + { + public NesPpuStatusFlags StatusFlags; + public Int32 Scanline; + public UInt32 Cycle; + public UInt32 FrameCount; + public UInt32 NmiScanline; + public UInt32 ScanlineCount; + public UInt32 SafeOamScanline; + public UInt16 BusAddress; + public byte MemoryReadBuffer; + public byte ControlReg; + }; + [Flags] public enum NesCpuFlags { diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index dabdade2..4d9a6dcb 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -782,6 +782,15 @@ (not recommended) +
+ Tilemap Viewer + Display mode: + Show tile grid + Show attribute grid + Highlight tile changes + Highlight attribute changes +
+
Assembler Start address: $ @@ -1332,13 +1341,14 @@ x == [$150] || y == [10] GB - Sprite RAM CPU Memory + PPU Memory PRG ROM NES RAM (2kb) Work RAM Save RAM Nametable RAM - Sprite RAM - Sprite RAM + Sprite / OAM RAM + Secondary OAM RAM CHR ROM CHR RAM Palette RAM @@ -1371,6 +1381,11 @@ x == [$150] || y == [10] White Magenta + + Tilemap + Tilemap (grayscale) + Attribute view + Hexadecimal Text diff --git a/NewUI/NewUI.csproj b/NewUI/NewUI.csproj index cb6c68f3..0c30158c 100644 --- a/NewUI/NewUI.csproj +++ b/NewUI/NewUI.csproj @@ -215,6 +215,9 @@ SnesPpuView.axaml + + TilemapViewerWindow.axaml + TraceLoggerWindow.axaml diff --git a/NewUI/Views/MainMenuView.axaml b/NewUI/Views/MainMenuView.axaml index 8a6d58fc..da18779c 100644 --- a/NewUI/Views/MainMenuView.axaml +++ b/NewUI/Views/MainMenuView.axaml @@ -390,6 +390,11 @@ + + + + + diff --git a/NewUI/Views/MainMenuView.axaml.cs b/NewUI/Views/MainMenuView.axaml.cs index b9cb4129..54ac554a 100644 --- a/NewUI/Views/MainMenuView.axaml.cs +++ b/NewUI/Views/MainMenuView.axaml.cs @@ -59,6 +59,14 @@ namespace Mesen.Views }.Show(); } + private void OnTilemapViewerClick(object sender, RoutedEventArgs e) + { + RomInfo romInfo = EmuApi.GetRomInfo(); + new TilemapViewerWindow { + DataContext = new TilemapViewerViewModel(romInfo.ConsoleType.GetMainCpuType(), romInfo.ConsoleType), + }.Show(); + } + private void OnMemoryToolsClick(object sender, RoutedEventArgs e) { new MemoryToolsWindow { diff --git a/Windows/SoundManager.cpp b/Windows/SoundManager.cpp index 095ddc03..a811d788 100644 --- a/Windows/SoundManager.cpp +++ b/Windows/SoundManager.cpp @@ -236,6 +236,10 @@ void SoundManager::Pause() void SoundManager::Stop() { + if(!_playing) { + return; + } + if(_secondaryBuffer) { _secondaryBuffer->Stop(); ClearSecondaryBuffer();