Tilemap viewer

This commit is contained in:
Sour 2021-06-22 20:12:37 -04:00
parent 811696f06f
commit ff825fc4de
45 changed files with 834 additions and 172 deletions

View file

@ -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

View file

@ -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:

View file

@ -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<NesConsole*>(console)) {
_nesMemoryManager = c->GetMemoryManager();
_nesConsole = c;
} else if(Gameboy* c = dynamic_cast<Gameboy*>(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);

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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
}
}
}
}
}
FrameInfo GbPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state)
{
return { 256, 256 };
}
FrameInfo GbPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
{
return { 256, 256 };
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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;
}
}
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;
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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<MMC5*>(_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)
{
}
}
FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state)
{
return { 512, 480 };
}
FrameInfo NesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
{
return { 256, 240 };
}

View file

@ -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;
};

View file

@ -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);
}

View file

@ -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;
}

View file

@ -329,7 +329,11 @@ vector<CpuType> 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);
}
}

View file

@ -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);
};

View file

@ -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);

View file

@ -318,7 +318,7 @@ template<class T> uint8_t NesPpu<T>::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<class T> uint8_t NesPpu<T>::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<class T> void NesPpu<T>::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<class T> void NesPpu<T>::WriteRam(uint16_t addr, uint8_t value)
}
}
template<class T> uint8_t NesPpu<T>::ReadPaletteRAM(uint16_t addr)
{
addr &= 0x1F;
if(addr == 0x10 || addr == 0x14 || addr == 0x18 || addr == 0x1C) {
addr &= ~0x10;
}
return _paletteRAM[addr];
}
template<class T> void NesPpu<T>::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<class T> void NesPpu<T>::ProcessTmpAddrScrollGlitch(uint16_t normalAddr, uint16_t value, uint16_t mask)
{
_tmpVideoRamAddr = normalAddr;
@ -1577,5 +1547,4 @@ template uint32_t NesPpu<NsfPpu>::GetPixelBrightness(uint8_t x, uint8_t y);
template NesPpu<HdNesPpu>::NesPpu(NesConsole* console);
template uint16_t* NesPpu<HdNesPpu>::GetScreenBuffer(bool previousBuffer);
template void NesPpu<HdNesPpu>::Exec();
template uint8_t NesPpu<HdNesPpu>::ReadPaletteRAM(uint16_t addr);
template uint32_t NesPpu<HdNesPpu>::GetPixelBrightness(uint8_t x, uint8_t y);

View file

@ -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;

View file

@ -148,6 +148,7 @@ struct NesPpuState : public BaseState
uint32_t SafeOamScanline;
uint16_t BusAddress;
uint8_t MemoryReadBuffer;
uint8_t ControlReg;
};
struct ApuLengthCounterState

View file

@ -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 };
}

View file

@ -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;
};

View file

@ -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();
}
}
}

View file

@ -10,6 +10,7 @@ enum class SnesMemoryType
Cx4Memory,
GameboyMemory,
NesMemory,
NesPpuMemory,
PrgRom,
WorkRam,

View file

@ -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); }

View file

@ -23,6 +23,10 @@ namespace Mesen.Debugger.Controls
public static readonly StyledProperty<int> GridSizeYProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(GridSizeY), 8);
public static readonly StyledProperty<bool> ShowGridProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(ShowGrid), false);
public static readonly StyledProperty<int> AltGridSizeXProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(AltGridSizeX), 8);
public static readonly StyledProperty<int> AltGridSizeYProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(AltGridSizeY), 8);
public static readonly StyledProperty<bool> ShowAltGridProperty = AvaloniaProperty.Register<PictureViewer, bool>(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));

View file

@ -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<GbPpuState>(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");
}
}
}

View file

@ -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<Enum>();
[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<SnesMemoryType>().Where(t => DebugApi.GetMemorySize(t) > 0).Cast<Enum>().ToArray();
this.WhenAnyValue(x => x.MemoryType).Subscribe(x => DataProvider = new HexEditorDataProvider(
x,
true,

View file

@ -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()
{

View file

@ -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<SnesMemoryType>().Where(t => DebugApi.GetMemorySize(t) > 0).Cast<Enum>().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<GbPpuState>(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);
}
}
}

View file

@ -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<TilemapViewerTab> Tabs { get; set; } = new List<TilemapViewerTab>();
//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<TilemapViewerTab>() {
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<TilemapViewerTab>() {
new() { Title = "", Layer = 0 }
};
break;
case CpuType.Gameboy:
Tabs = new List<TilemapViewerTab>() {
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;
}
}

View file

@ -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");
}
}

View file

@ -29,6 +29,7 @@
DockPanel.Dock="Left"
EnumType="{x:Type i:SnesMemoryType}"
SelectedItem="{CompiledBinding MemoryType}"
AvailableValues="{CompiledBinding AvailableMemoryTypes}"
/>
<ComboBox
DockPanel.Dock="Right"

View file

@ -67,7 +67,7 @@ namespace Mesen.Debugger.Windows
if(this.DataContext is TileViewerViewModel model) {
_model = model;
} else {
throw new Exception("Unexception model");
throw new Exception("Unexpected model");
}
}

View file

@ -0,0 +1,78 @@
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:m="clr-namespace:Mesen"
xmlns:vm="using:Mesen.ViewModels"
xmlns:sys="using:System"
xmlns:v="using:Mesen.Views"
xmlns:c="using:Mesen.Controls"
xmlns:i="using:Mesen.Interop"
xmlns:l="using:Mesen.Localization"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dvm="using:Mesen.Debugger.ViewModels"
xmlns:dc="using:Mesen.Debugger.Controls"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="600"
x:Class="Mesen.Debugger.Windows.TilemapViewerWindow"
x:DataType="dvm:TilemapViewerViewModel"
Width="600" Height="600"
Title="{l:Translate wndTitle}"
Icon="/Assets/VideoOptions.png"
>
<Design.DataContext>
<dvm:TilemapViewerViewModel />
</Design.DataContext>
<Window.Styles>
<Style Selector="TabControl:singleitem">
<Setter Property="IsVisible" Value="False" />
</Style>
</Window.Styles>
<DockPanel>
<Grid
DockPanel.Dock="Right"
ColumnDefinitions="Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
Margin="5 0 5 0"
Width="180"
>
<TextBlock Text="{l:Translate lblDisplayMode}" />
<c:EnumComboBox Grid.Row="0" Grid.Column="1" EnumType="{x:Type i:TilemapDisplayMode}" SelectedItem="{CompiledBinding DisplayMode}" />
<CheckBox Grid.Row="1" Grid.ColumnSpan="2" Content="{l:Translate chkShowGrid}" IsChecked="{CompiledBinding ShowGrid}" />
<CheckBox Grid.Row="2" Grid.ColumnSpan="2" Content="{l:Translate chkShowAltGrid}" IsChecked="{CompiledBinding ShowAltGrid}" />
<CheckBox Grid.Row="3" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightTileChanges}" IsChecked="{CompiledBinding HighlightTileChanges}" />
<CheckBox Grid.Row="4" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightAttributeChanges}" IsChecked="{CompiledBinding HighlightAttributeChanges}" />
</Grid>
<TabControl Items="{CompiledBinding Tabs}" SelectionChanged="OnTabSelected" Padding="1" DockPanel.Dock="Top">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.DataTemplates>
<DataTemplate></DataTemplate>
</TabControl.DataTemplates>
</TabControl>
<Border BorderBrush="Gray" BorderThickness="1">
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
AllowAutoHide="False"
>
<dc:PictureViewer
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
x:Name="picViewer"
ShowGrid="{CompiledBinding ShowGrid}"
AltGridSizeX="16"
AltGridSizeY="16"
ShowAltGrid="{CompiledBinding ShowAltGrid}"
/>
</ScrollViewer>
</Border>
</DockPanel>
</Window>

View file

@ -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<PictureViewer>("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<T>() where T : struct, BaseState
{
T ppuState = DebugApi.GetPpuState<T>(_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<PpuState>(); break;
case CpuType.Nes: UpdateTilemap<NesPpuState>(); break;
case CpuType.Gameboy: UpdateTilemap<GbPpuState>(); break;
}
}
}
}

View file

@ -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<T>(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<T>(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) {

View file

@ -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
{

View file

@ -782,6 +782,15 @@
<Control ID="Hint">(not recommended)</Control>
</Form>
<Form ID="TilemapViewerWindow">
<Control ID="wndTitle">Tilemap Viewer</Control>
<Control ID="lblDisplayMode">Display mode: </Control>
<Control ID="chkShowGrid">Show tile grid</Control>
<Control ID="chkShowAltGrid">Show attribute grid</Control>
<Control ID="chkHighlightTileChanges">Highlight tile changes</Control>
<Control ID="chkHighlightAttributeChanges">Highlight attribute changes</Control>
</Form>
<Form ID="AssemblerWindow">
<Control ID="wndTitle">Assembler</Control>
<Control ID="lblStartAddress">Start address: $</Control>
@ -1332,13 +1341,14 @@ x == [$150] || y == [10]
<Value ID="GbSpriteRam">GB - Sprite RAM</Value>
<Value ID="NesMemory">CPU Memory</Value>
<Value ID="NesPpuMemory">PPU Memory</Value>
<Value ID="NesPrgRom">PRG ROM</Value>
<Value ID="NesInternalRam">NES RAM (2kb)</Value>
<Value ID="NesWorkRam">Work RAM</Value>
<Value ID="NesSaveRam">Save RAM</Value>
<Value ID="NesNametableRam">Nametable RAM</Value>
<Value ID="NesSpriteRam">Sprite RAM</Value>
<Value ID="NesSecondarySpriteRam">Sprite RAM</Value>
<Value ID="NesSpriteRam">Sprite / OAM RAM</Value>
<Value ID="NesSecondarySpriteRam">Secondary OAM RAM</Value>
<Value ID="NesChrRom">CHR ROM</Value>
<Value ID="NesChrRam">CHR RAM</Value>
<Value ID="NesPaletteRam">Palette RAM</Value>
@ -1371,6 +1381,11 @@ x == [$150] || y == [10]
<Value ID="White">White</Value>
<Value ID="Magenta">Magenta</Value>
</Enum>
<Enum ID="TilemapDisplayMode">
<Value ID="Default">Tilemap</Value>
<Value ID="Grayscale">Tilemap (grayscale)</Value>
<Value ID="AttributeView">Attribute view</Value>
</Enum>
<Enum ID="StatusFlagFormat">
<Value ID="Hexadecimal">Hexadecimal</Value>
<Value ID="Text">Text</Value>

View file

@ -215,6 +215,9 @@
<Compile Update="Debugger\Views\SnesPpuView.axaml.cs">
<DependentUpon>SnesPpuView.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\TilemapViewerWindow.axaml.cs">
<DependentUpon>TilemapViewerWindow.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\TraceLoggerWindow.axaml.cs">
<DependentUpon>TraceLoggerWindow.axaml</DependentUpon>
</Compile>

View file

@ -390,6 +390,11 @@
<Image Source="/Assets/VerticalLayout.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Tile_map Viewer" Click="OnTilemapViewerClick">
<MenuItem.Icon>
<Image Source="/Assets/VideoOptions.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="_Assembler" Click="OnAssemblerClick">
<MenuItem.Icon>
<Image Source="/Assets/Chip.png" />

View file

@ -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 {

View file

@ -236,6 +236,10 @@ void SoundManager::Pause()
void SoundManager::Stop()
{
if(!_playing) {
return;
}
if(_secondaryBuffer) {
_secondaryBuffer->Stop();
ClearSecondaryBuffer();