mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Sprite viewer v1
This commit is contained in:
parent
13a5053462
commit
fd7be59a1f
18 changed files with 656 additions and 105 deletions
|
@ -14,6 +14,23 @@ struct ViewerRefreshConfig
|
|||
uint16_t Cycle;
|
||||
};
|
||||
|
||||
struct DebugSpriteInfo
|
||||
{
|
||||
uint16_t SpriteIndex;
|
||||
uint16_t TileIndex;
|
||||
int16_t X;
|
||||
int16_t Y;
|
||||
|
||||
uint8_t Palette;
|
||||
uint8_t Priority;
|
||||
uint8_t Width;
|
||||
uint8_t Height;
|
||||
bool HorizontalMirror;
|
||||
bool VerticalMirror;
|
||||
bool UseSecondTable;
|
||||
bool Visible;
|
||||
};
|
||||
|
||||
class PpuTools
|
||||
{
|
||||
protected:
|
||||
|
@ -37,6 +54,7 @@ public:
|
|||
|
||||
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;
|
||||
virtual uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[]) = 0;
|
||||
|
||||
void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle);
|
||||
void RemoveViewer(uint32_t viewerId);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#include "Gameboy/Debugger/GbPpuTools.h"
|
||||
#include "Debugger/DebugTypes.h"
|
||||
#include "Shared/SettingTypes.h"
|
||||
#include "SNES/SnesDefaultVideoFilter.h"
|
||||
#include "Gameboy/GbTypes.h"
|
||||
|
||||
GbPpuTools::GbPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugger, emu)
|
||||
|
@ -71,21 +70,14 @@ void GbPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& ba
|
|||
std::fill(filled, filled + 256, 0xFF);
|
||||
|
||||
for(int i = 0; i < 0xA0; i += 4) {
|
||||
uint8_t sprY = oamRam[i];
|
||||
if(sprY > row || sprY + height <= row) {
|
||||
DebugSpriteInfo sprite = GetSpriteInfo(i / 4, options, state, oamRam);
|
||||
if(sprite.Y > row || sprite.Y + height <= row) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int y = row - sprY;
|
||||
uint8_t sprX = oamRam[i + 1];
|
||||
uint8_t tileIndex = oamRam[i + 2];
|
||||
uint8_t attributes = oamRam[i + 3];
|
||||
|
||||
uint16_t tileBank = isCgb ? ((attributes & 0x08) ? 0x2000 : 0x0000) : 0;
|
||||
uint8_t palette = isCgb ? (attributes & 0x07) << 2 : 0;
|
||||
bool hMirror = (attributes & 0x20) != 0;
|
||||
bool vMirror = (attributes & 0x40) != 0;
|
||||
|
||||
int y = row - sprite.Y;
|
||||
uint8_t tileIndex = (uint8_t)sprite.TileIndex;
|
||||
uint16_t tileBank = sprite.UseSecondTable ? 0x2000 : 0x0000;
|
||||
if(largeSprites) {
|
||||
tileIndex &= 0xFE;
|
||||
}
|
||||
|
@ -93,25 +85,25 @@ void GbPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& ba
|
|||
uint16_t tileStart = tileIndex * 16;
|
||||
tileStart |= tileBank;
|
||||
|
||||
uint16_t pixelStart = tileStart + (vMirror ? (height - 1 - y) : y) * 2;
|
||||
uint16_t pixelStart = tileStart + (sprite.VerticalMirror ? (height - 1 - y) : y) * 2;
|
||||
for(int x = 0; x < width; x++) {
|
||||
if(sprX + x >= 256) {
|
||||
if(sprite.X + x >= 256) {
|
||||
break;
|
||||
} else if(filled[sprX + x] < sprX) {
|
||||
} else if(filled[sprite.X + x] < sprite.X) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t shift = hMirror ? (x & 0x07) : (7 - (x & 0x07));
|
||||
uint8_t shift = sprite.HorizontalMirror ? (x & 0x07) : (7 - (x & 0x07));
|
||||
uint8_t color = GetTilePixelColor(vram, 0x3FFF, 2, pixelStart, shift, 1);
|
||||
|
||||
if(color > 0) {
|
||||
uint32_t outOffset = (row * 256) + sprite.X + x;
|
||||
if(!isCgb) {
|
||||
color = (((attributes & 0x10) ? state.ObjPalette1 : state.ObjPalette0) >> (color * 2)) & 0x03;
|
||||
outBuffer[outOffset] = palette[4 + (sprite.Palette * 4) + color];
|
||||
} else {
|
||||
outBuffer[outOffset] = palette[32 + (sprite.Palette * 4) + color];
|
||||
}
|
||||
|
||||
uint32_t outOffset = (row * 256) + sprX + x;
|
||||
outBuffer[outOffset] = SnesDefaultVideoFilter::ToArgb(state.CgbObjPalettes[palette + color]);
|
||||
filled[sprX + x] = sprX;
|
||||
filled[sprite.X + x] = (uint8_t)sprite.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,3 +119,34 @@ FrameInfo GbPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, Base
|
|||
{
|
||||
return { 256, 256 };
|
||||
}
|
||||
|
||||
DebugSpriteInfo GbPpuTools::GetSpriteInfo(uint16_t i, GetSpritePreviewOptions& options, GbPpuState& state, uint8_t* oamRam)
|
||||
{
|
||||
DebugSpriteInfo sprite = {};
|
||||
|
||||
sprite.SpriteIndex = i;
|
||||
sprite.Y = oamRam[i*4];
|
||||
|
||||
sprite.X = oamRam[i * 4 + 1];
|
||||
sprite.TileIndex = oamRam[i * 4 + 2];
|
||||
uint8_t attributes = oamRam[i * 4 + 3];
|
||||
|
||||
sprite.UseSecondTable = (state.CgbEnabled && (attributes & 0x08)) ? true : false;
|
||||
sprite.Palette = state.CgbEnabled ? (attributes & 0x07) : ((attributes & 0x10) ? 1 : 0);
|
||||
sprite.HorizontalMirror = (attributes & 0x20) != 0;
|
||||
sprite.VerticalMirror = (attributes & 0x40) != 0;
|
||||
sprite.Visible = sprite.X > 0 && sprite.Y > 0 && sprite.Y < 160 && sprite.X < 168;
|
||||
sprite.Width = 8;
|
||||
sprite.Height = state.LargeSprites ? 16 : 8;
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
uint32_t GbPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[])
|
||||
{
|
||||
GbPpuState& state = (GbPpuState&)baseState;
|
||||
for(int i = 0; i < 40; i++) {
|
||||
outBuffer[i] = GetSpriteInfo(i, options, state, oamRam);
|
||||
}
|
||||
return 40;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@ class Emulator;
|
|||
|
||||
class GbPpuTools : public PpuTools
|
||||
{
|
||||
private:
|
||||
DebugSpriteInfo GetSpriteInfo(uint16_t spriteIndex, GetSpritePreviewOptions& options, GbPpuState& state, uint8_t* oamRam);
|
||||
|
||||
public:
|
||||
GbPpuTools(Debugger* debugger, Emulator *emu);
|
||||
|
||||
|
@ -15,4 +18,5 @@ public:
|
|||
|
||||
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;
|
||||
uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[]) override;
|
||||
};
|
|
@ -83,6 +83,54 @@ void NesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, ui
|
|||
|
||||
void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer)
|
||||
{
|
||||
NesPpuState& state = (NesPpuState&)baseState;
|
||||
|
||||
bool largeSprites = (state.ControlReg & 0x20) ? true : false;
|
||||
uint16_t sprAddr = (state.ControlReg & 0x08) ? 0x1000 : 0x0000;
|
||||
|
||||
std::fill(outBuffer, outBuffer + 256 * 240, 0xFF666666);
|
||||
std::fill(outBuffer + 256 * 240, outBuffer + 256 * 256, 0xFF333333);
|
||||
|
||||
for(int i = 0x100 - 4; i >= 0; i -= 4) {
|
||||
DebugSpriteInfo sprite = GetSpriteInfo(i / 4, options, state, oamRam);
|
||||
|
||||
uint16_t tileStart;
|
||||
if(largeSprites) {
|
||||
if(sprite.TileIndex & 0x01) {
|
||||
tileStart = 0x1000 | ((sprite.TileIndex & 0xFE) * 16);
|
||||
} else {
|
||||
tileStart = 0x0000 | (sprite.TileIndex * 16);
|
||||
}
|
||||
} else {
|
||||
tileStart = (sprite.TileIndex * 16) | sprAddr;
|
||||
}
|
||||
|
||||
for(int y = 0; y < sprite.Height; y++) {
|
||||
if(sprite.Y + y >= 256) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t lineOffset = sprite.VerticalMirror ? (sprite.Height - 1 - y) : y;
|
||||
uint16_t pixelStart = tileStart + lineOffset;
|
||||
if(largeSprites && lineOffset >= 8) {
|
||||
pixelStart += 8;
|
||||
}
|
||||
|
||||
for(int x = 0; x < sprite.Width; x++) {
|
||||
if(sprite.X + x >= 256) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t shift = sprite.HorizontalMirror ? (x & 0x07) : (7 - (x & 0x07));
|
||||
uint8_t color = GetTilePixelColor(vram, 0x3FFF, 2, pixelStart, shift, 8);
|
||||
|
||||
if(color > 0) {
|
||||
uint32_t outOffset = ((sprite.Y + y) * 256) + sprite.X + x;
|
||||
outBuffer[outOffset] = palette[16 + (sprite.Palette * 4) + color];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state)
|
||||
|
@ -90,7 +138,39 @@ FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& stat
|
|||
return { 512, 480 };
|
||||
}
|
||||
|
||||
DebugSpriteInfo NesPpuTools::GetSpriteInfo(uint32_t i, GetSpritePreviewOptions& options, NesPpuState& state, uint8_t* oamRam)
|
||||
{
|
||||
DebugSpriteInfo sprite = {};
|
||||
|
||||
sprite.SpriteIndex = i;
|
||||
sprite.Y = oamRam[i * 4] + 1;
|
||||
sprite.X = oamRam[i * 4 + 3];
|
||||
sprite.TileIndex = oamRam[i * 4 + 1];
|
||||
|
||||
uint8_t attributes = oamRam[i * 4 + 2];
|
||||
sprite.Palette = (attributes & 0x03);
|
||||
sprite.HorizontalMirror = (attributes & 0x40) != 0;
|
||||
sprite.VerticalMirror = (attributes & 0x80) != 0;
|
||||
sprite.Priority = (attributes & 0x20) ? 0 : 1;
|
||||
sprite.Visible = sprite.Y < 240;
|
||||
sprite.Width = 8;
|
||||
|
||||
bool largeSprites = (state.ControlReg & 0x20) ? true : false;
|
||||
sprite.Height = largeSprites ? 16 : 8;
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
uint32_t NesPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[])
|
||||
{
|
||||
NesPpuState& state = (NesPpuState&)baseState;
|
||||
for(int i = 0; i < 64; i++) {
|
||||
outBuffer[i] = GetSpriteInfo(i, options, state, oamRam);
|
||||
}
|
||||
return 64;
|
||||
}
|
||||
|
||||
FrameInfo NesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
|
||||
{
|
||||
return { 256, 240 };
|
||||
return { 256, 256 };
|
||||
}
|
||||
|
|
|
@ -6,11 +6,13 @@ class Debugger;
|
|||
class Emulator;
|
||||
class BaseMapper;
|
||||
class NesConsole;
|
||||
struct NesPpuState;
|
||||
|
||||
class NesPpuTools : public PpuTools
|
||||
{
|
||||
private:
|
||||
BaseMapper* _mapper = nullptr;
|
||||
DebugSpriteInfo GetSpriteInfo(uint32_t spriteIndex, GetSpritePreviewOptions& options, NesPpuState& state, uint8_t* oamRam);
|
||||
|
||||
public:
|
||||
NesPpuTools(Debugger* debugger, Emulator *emu, NesConsole* console);
|
||||
|
@ -20,4 +22,5 @@ public:
|
|||
|
||||
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;
|
||||
uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[]) override;
|
||||
};
|
|
@ -115,67 +115,43 @@ static constexpr uint8_t _oamSizes[8][2][2] = {
|
|||
void SnesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, uint8_t *vram, uint8_t *oamRam, uint32_t* palette, uint32_t* outBuffer)
|
||||
{
|
||||
PpuState& state = (PpuState&)baseState;
|
||||
|
||||
FrameInfo size = GetSpritePreviewSize(options, state);
|
||||
//TODO
|
||||
//uint16_t baseAddr = state.EnableOamPriority ? (_internalOamAddress & 0x1FC) : 0;
|
||||
uint16_t baseAddr = 0;
|
||||
|
||||
bool filled[256 * 240] = {};
|
||||
int lastScanline = state.OverscanMode ? 239 : 224;
|
||||
std::fill(outBuffer, outBuffer + 256 * lastScanline, 0xFF888888);
|
||||
std::fill(outBuffer + 256 * lastScanline, outBuffer + 256 * 240, 0xFF000000);
|
||||
bool filled[512*256] = {};
|
||||
std::fill(outBuffer, outBuffer + size.Width * size.Height, 0xFF333333);
|
||||
for(int i = 0; i < (state.OverscanMode ? 239 : 224); i++) {
|
||||
std::fill(outBuffer + size.Width * i + 256, outBuffer + size.Width * i + 512, 0xFF888888);
|
||||
}
|
||||
|
||||
for(int screenY = 0; screenY < lastScanline; screenY++) {
|
||||
for(int scanline = 0; scanline < (int)size.Height; scanline++) {
|
||||
for(int i = 508; i >= 0; i -= 4) {
|
||||
uint16_t addr = (baseAddr + i) & 0x1FF;
|
||||
uint8_t y = oamRam[addr + 1];
|
||||
|
||||
uint8_t highTableOffset = addr >> 4;
|
||||
uint8_t shift = ((addr >> 2) & 0x03) << 1;
|
||||
uint8_t highTableValue = oamRam[0x200 | highTableOffset] >> shift;
|
||||
uint8_t largeSprite = (highTableValue & 0x02) >> 1;
|
||||
uint8_t height = _oamSizes[state.OamMode][largeSprite][1] << 3;
|
||||
|
||||
uint8_t endY = (y + (state.ObjInterlace ? (height >> 1): height)) & 0xFF;
|
||||
|
||||
bool visible = (screenY >= y && screenY < endY) || (endY < y && screenY < endY);
|
||||
DebugSpriteInfo sprite = GetSpriteInfo(i / 4, options, state, oamRam);
|
||||
|
||||
uint8_t endY = (sprite.Y + (state.ObjInterlace ? (sprite.Height >> 1): sprite.Height)) & 0xFF;
|
||||
bool visible = (scanline >= sprite.Y && scanline < endY) || (endY < sprite.Y && scanline < endY);
|
||||
if(!visible) {
|
||||
//Not visible on this scanline
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t width = _oamSizes[state.OamMode][largeSprite][0] << 3;
|
||||
uint16_t sign = (highTableValue & 0x01) << 8;
|
||||
int16_t x = (int16_t)((sign | oamRam[addr]) << 7) >> 7;
|
||||
|
||||
if(x != -256 && (x + width <= 0 || x > 255)) {
|
||||
//Sprite is not visible (and must be ignored for time/range flag calculations)
|
||||
//Sprites at X=-256 are always used when considering Time/Range flag calculations, but not actually drawn.
|
||||
continue;
|
||||
}
|
||||
|
||||
int tileRow = (oamRam[addr + 2] & 0xF0) >> 4;
|
||||
int tileColumn = oamRam[addr + 2] & 0x0F;
|
||||
|
||||
uint8_t flags = oamRam[addr + 3];
|
||||
bool useSecondTable = (flags & 0x01) != 0;
|
||||
uint8_t paletteIndex = (flags >> 1) & 0x07;
|
||||
//uint8_t priority = (flags >> 4) & 0x03;
|
||||
bool horizontalMirror = (flags & 0x40) != 0;
|
||||
bool verticalMirror = (flags & 0x80) != 0;
|
||||
|
||||
int tileRow = (sprite.TileIndex & 0xF0) >> 4;
|
||||
int tileColumn = sprite.TileIndex & 0x0F;
|
||||
|
||||
uint8_t yOffset;
|
||||
int rowOffset;
|
||||
|
||||
int yGap = (screenY - y);
|
||||
int yGap = (scanline - sprite.Y);
|
||||
if(state.ObjInterlace) {
|
||||
yGap <<= 1;
|
||||
yGap |= (state.FrameCount & 0x01);
|
||||
}
|
||||
|
||||
if(verticalMirror) {
|
||||
yOffset = (height - 1 - yGap) & 0x07;
|
||||
rowOffset = (height - 1 - yGap) >> 3;
|
||||
if(sprite.VerticalMirror) {
|
||||
yOffset = (sprite.Height - 1 - yGap) & 0x07;
|
||||
rowOffset = (sprite.Height - 1 - yGap) >> 3;
|
||||
} else {
|
||||
yOffset = yGap & 0x07;
|
||||
rowOffset = yGap >> 3;
|
||||
|
@ -183,38 +159,90 @@ void SnesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState&
|
|||
|
||||
uint8_t row = (tileRow + rowOffset) & 0x0F;
|
||||
|
||||
for(int j = std::max<int16_t>(x, 0); j < x + width && j < 256; j++) {
|
||||
uint32_t outOffset = screenY * 256 + j;
|
||||
for(int j = sprite.X; j < sprite.X + sprite.Width && j < 256; j++) {
|
||||
uint32_t outOffset = scanline * size.Width + (256 + j);
|
||||
if(filled[outOffset]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t xOffset;
|
||||
int columnOffset;
|
||||
if(horizontalMirror) {
|
||||
xOffset = (width - (j - x) - 1) & 0x07;
|
||||
columnOffset = (width - (j - x) - 1) >> 3;
|
||||
if(sprite.HorizontalMirror) {
|
||||
xOffset = (sprite.Width - (j - sprite.X) - 1) & 0x07;
|
||||
columnOffset = (sprite.Width - (j - sprite.X) - 1) >> 3;
|
||||
} else {
|
||||
xOffset = (j - x) & 0x07;
|
||||
columnOffset = (j - x) >> 3;
|
||||
xOffset = (j - sprite.X) & 0x07;
|
||||
columnOffset = (j - sprite.X) >> 3;
|
||||
}
|
||||
|
||||
uint8_t column = (tileColumn + columnOffset) & 0x0F;
|
||||
uint8_t tileIndex = (row << 4) | column;
|
||||
uint16_t tileStart = ((state.OamBaseAddress + (tileIndex << 4) + (useSecondTable ? state.OamAddressOffset : 0)) & 0x7FFF) << 1;
|
||||
uint16_t tileStart = ((state.OamBaseAddress + (tileIndex << 4) + (sprite.UseSecondTable ? state.OamAddressOffset : 0)) & 0x7FFF) << 1;
|
||||
|
||||
uint8_t color = GetTilePixelColor(vram, Ppu::VideoRamSize - 1, 4, tileStart + yOffset * 2, 7 - xOffset, 1);
|
||||
if(color != 0) {
|
||||
if(options.SelectedSprite == i / 4) {
|
||||
filled[outOffset] = true;
|
||||
}
|
||||
outBuffer[outOffset] = GetRgbPixelColor(palette, color, paletteIndex, 4, false, 256);
|
||||
outBuffer[outOffset] = GetRgbPixelColor(palette, color, sprite.Palette + 8, 4, false, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugSpriteInfo SnesPpuTools::GetSpriteInfo(uint16_t spriteIndex, GetSpritePreviewOptions& options, PpuState& state, uint8_t* oamRam)
|
||||
{
|
||||
uint16_t addr = (spriteIndex * 4) & 0x1FF;
|
||||
|
||||
uint8_t highTableOffset = addr >> 4;
|
||||
uint8_t shift = ((addr >> 2) & 0x03) << 1;
|
||||
uint8_t highTableValue = oamRam[0x200 | highTableOffset] >> shift;
|
||||
uint8_t largeSprite = (highTableValue & 0x02) >> 1;
|
||||
uint8_t height = _oamSizes[state.OamMode][largeSprite][1] << 3;
|
||||
|
||||
uint8_t width = _oamSizes[state.OamMode][largeSprite][0] << 3;
|
||||
uint16_t sign = (highTableValue & 0x01) << 8;
|
||||
int16_t x = (int16_t)((sign | oamRam[addr]) << 7) >> 7;
|
||||
uint8_t y = oamRam[addr + 1];
|
||||
uint8_t flags = oamRam[addr + 3];
|
||||
|
||||
bool visible = true;
|
||||
if(x + width <= 0 || x > 255) {
|
||||
visible = false;
|
||||
} else {
|
||||
uint16_t scanlineCount = state.OverscanMode ? 239 : 224;
|
||||
uint8_t endY = (y + (state.ObjInterlace ? (height >> 1) : height)) & 0xFF;
|
||||
if(endY >= scanlineCount && y >= scanlineCount) {
|
||||
visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
DebugSpriteInfo sprite = {};
|
||||
sprite.SpriteIndex = spriteIndex;
|
||||
sprite.X = x;
|
||||
sprite.Y = y;
|
||||
sprite.Height = height;
|
||||
sprite.Width = width;
|
||||
sprite.TileIndex = oamRam[addr + 2];
|
||||
sprite.Palette = ((flags >> 1) & 0x07);
|
||||
sprite.Priority = (flags >> 4) & 0x03;
|
||||
sprite.HorizontalMirror = (flags & 0x40) != 0;
|
||||
sprite.VerticalMirror = (flags & 0x80) != 0;
|
||||
sprite.UseSecondTable = (flags & 0x01) != 0;
|
||||
sprite.Visible = visible;
|
||||
return sprite;
|
||||
}
|
||||
|
||||
uint32_t SnesPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[])
|
||||
{
|
||||
PpuState& state = (PpuState&)baseState;
|
||||
for(int i = 0; i < 128; i++) {
|
||||
outBuffer[i] = GetSpriteInfo(i, options, state, oamRam);
|
||||
}
|
||||
return 128;
|
||||
}
|
||||
|
||||
FrameInfo SnesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
FrameInfo size = { 256, 256 };
|
||||
|
@ -247,5 +275,5 @@ FrameInfo SnesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& bas
|
|||
|
||||
FrameInfo SnesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
|
||||
{
|
||||
return { 256,240 };
|
||||
return { 512, 256 };
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ struct BaseState;
|
|||
|
||||
class SnesPpuTools : public PpuTools
|
||||
{
|
||||
private:
|
||||
DebugSpriteInfo GetSpriteInfo(uint16_t spriteIndex, GetSpritePreviewOptions& options, PpuState& state, uint8_t* oamRam);
|
||||
|
||||
public:
|
||||
SnesPpuTools(Debugger* debugger, Emulator *emu);
|
||||
|
||||
|
@ -15,5 +18,6 @@ public:
|
|||
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;
|
||||
uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* oamRam, DebugSpriteInfo outBuffer[]) override;
|
||||
FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
};
|
|
@ -94,12 +94,14 @@ 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 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 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 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 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); }
|
||||
DllExport uint32_t __stdcall GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state, uint8_t* oamRam, DebugSpriteInfo sprites[]) { return GetDebugger()->GetPpuTools(cpuType)->GetSpriteList(options, state, oamRam, sprites); }
|
||||
|
||||
DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle, CpuType cpuType) { GetDebugger()->GetPpuTools(cpuType)->SetViewerUpdateTiming(viewerId, scanline, cycle); }
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace Mesen.Debugger.Controls
|
|||
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);
|
||||
|
||||
public static readonly StyledProperty<Rect> SelectionRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(SelectionRect), Rect.Empty);
|
||||
|
||||
private Stopwatch _stopWatch = Stopwatch.StartNew();
|
||||
private DispatcherTimer _timer = new DispatcherTimer();
|
||||
|
@ -78,12 +80,17 @@ namespace Mesen.Debugger.Controls
|
|||
set { SetValue(ShowAltGridProperty, value); }
|
||||
}
|
||||
|
||||
public Rect SelectionRect
|
||||
{
|
||||
get { return GetValue(SelectionRectProperty); }
|
||||
set { SetValue(SelectionRectProperty, value); }
|
||||
}
|
||||
|
||||
private Point? _mousePosition = null;
|
||||
private PixelPoint? _selectedTile = null;
|
||||
|
||||
static PictureViewer()
|
||||
{
|
||||
AffectsRender<PictureViewer>(SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty);
|
||||
AffectsRender<PictureViewer>(SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty, SelectionRectProperty);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
|
@ -148,8 +155,14 @@ namespace Mesen.Debugger.Controls
|
|||
base.OnPointerPressed(e);
|
||||
Point p = e.GetCurrentPoint(this).Position;
|
||||
p = new Point(Math.Min(p.X, MinWidth - 1), Math.Min(p.Y, MinHeight - 1));
|
||||
_selectedTile = new PixelPoint((int)(p.X / GridSizeX / Zoom), (int)(p.Y / GridSizeY / Zoom));
|
||||
InvalidateVisual();
|
||||
|
||||
Rect selection = new Rect(
|
||||
(int)p.X / GridSizeX * GridSizeX / Zoom,
|
||||
(int)p.Y / GridSizeY * GridSizeY / Zoom,
|
||||
GridSizeX,
|
||||
GridSizeY
|
||||
);
|
||||
SelectionRect = selection;
|
||||
}
|
||||
|
||||
private void DrawGrid(DrawingContext context, bool show, int gridX, int gridY, Color color)
|
||||
|
@ -183,7 +196,7 @@ namespace Mesen.Debugger.Controls
|
|||
int width = Source.PixelSize.Width * Zoom;
|
||||
int height = Source.PixelSize.Height * Zoom;
|
||||
|
||||
context.FillRectangle(new SolidColorBrush(0xFF333333), new Rect(Bounds.Size));
|
||||
context.FillRectangle(new SolidColorBrush(0xFFFFFFFF), new Rect(Bounds.Size));
|
||||
|
||||
context.DrawImage(
|
||||
Source,
|
||||
|
@ -195,15 +208,12 @@ namespace Mesen.Debugger.Controls
|
|||
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;
|
||||
|
||||
if(SelectionRect != Rect.Empty) {
|
||||
Rect rect = new Rect(
|
||||
_selectedTile.Value.X * gridSizeX - 0.5,
|
||||
_selectedTile.Value.Y * gridSizeY - 0.5,
|
||||
gridSizeX + 1,
|
||||
gridSizeY + 1
|
||||
SelectionRect.X * Zoom - 0.5,
|
||||
SelectionRect.Y * Zoom - 0.5,
|
||||
SelectionRect.Width * Zoom + 1,
|
||||
SelectionRect.Height * Zoom + 1
|
||||
);
|
||||
|
||||
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, (double)(_stopWatch.ElapsedMilliseconds / 50) % 100 / 5);
|
||||
|
|
81
NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs
Normal file
81
NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Mesen.Interop;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mesen.Debugger.ViewModels
|
||||
{
|
||||
public class SpriteViewerViewModel : ViewModelBase
|
||||
{
|
||||
public CpuType CpuType { get; }
|
||||
public ConsoleType ConsoleType { get; }
|
||||
|
||||
[Reactive] public SpriteRowModel[] Sprites { get; set; } = new SpriteRowModel[0];
|
||||
|
||||
//For designer
|
||||
public SpriteViewerViewModel() : this(CpuType.Cpu, ConsoleType.Snes) { }
|
||||
|
||||
public SpriteViewerViewModel(CpuType cpuType, ConsoleType consoleType)
|
||||
{
|
||||
CpuType = cpuType;
|
||||
ConsoleType = consoleType;
|
||||
}
|
||||
|
||||
public void UpdateSprites(DebugSpriteInfo[] newSprites)
|
||||
{
|
||||
if(Sprites.Length != newSprites.Length) {
|
||||
SpriteRowModel[] sprites = new SpriteRowModel[newSprites.Length];
|
||||
for(int i = 0; i < newSprites.Length; i++) {
|
||||
sprites[i] = new SpriteRowModel();
|
||||
}
|
||||
Sprites = sprites;
|
||||
}
|
||||
|
||||
for(int i = 0; i < newSprites.Length; i++) {
|
||||
Sprites[i].Init(ref newSprites[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public class SpriteRowModel : ViewModelBase
|
||||
{
|
||||
[Reactive] public int SpriteIndex { get; set; }
|
||||
[Reactive] public int X { get; set; }
|
||||
[Reactive] public int Y { get; set; }
|
||||
[Reactive] public int Width { get; set; }
|
||||
[Reactive] public int Height { get; set; }
|
||||
[Reactive] public int TileIndex { get; set; }
|
||||
[Reactive] public int Priority { get; set; }
|
||||
[Reactive] public int Palette { get; set; }
|
||||
[Reactive] public bool Visible { get; set; }
|
||||
[Reactive] public ISolidColorBrush? Foreground { get; set; }
|
||||
[Reactive] public FontStyle FontStyle { get; set; }
|
||||
[Reactive] public string Size { get; set; } = "";
|
||||
[Reactive] public string Flags { get; set; } = "";
|
||||
|
||||
public void Init(ref DebugSpriteInfo sprite)
|
||||
{
|
||||
SpriteIndex = sprite.SpriteIndex;
|
||||
X = sprite.X;
|
||||
Y = sprite.Y;
|
||||
Width = sprite.Width;
|
||||
Height = sprite.Height;
|
||||
TileIndex = sprite.TileIndex;
|
||||
Priority = sprite.Priority;
|
||||
Palette = sprite.Palette;
|
||||
Size = sprite.Width + "x" + sprite.Height;
|
||||
|
||||
Visible = sprite.Visible;
|
||||
FontStyle = FontStyle.Normal;
|
||||
Foreground = sprite.Visible ? Brushes.Black : Brushes.Gray;
|
||||
|
||||
string flags = sprite.HorizontalMirror ? "H" : "";
|
||||
flags += sprite.VerticalMirror ? "V" : "";
|
||||
flags += sprite.UseSecondTable ? "N" : "";
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@
|
|||
VerticalScrollBarVisibility="Auto"
|
||||
Sorting="OnGridSort"
|
||||
CanUserReorderColumns="False"
|
||||
>
|
||||
>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{l:Translate colFunction}" Binding="{Binding FunctionName}" Width="*" CanUserResize="True" />
|
||||
<DataGridTextColumn Header="{l:Translate colCallCount}" Binding="{Binding CallCount}" Width="*" CanUserResize="True" />
|
||||
|
|
76
NewUI/Debugger/Windows/SpriteViewerWindow.axaml
Normal file
76
NewUI/Debugger/Windows/SpriteViewerWindow.axaml
Normal file
|
@ -0,0 +1,76 @@
|
|||
<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.SpriteViewerWindow"
|
||||
x:DataType="dvm:SpriteViewerViewModel"
|
||||
Width="600" Height="600"
|
||||
Title="{l:Translate wndTitle}"
|
||||
Icon="/Assets/PerfTracker.png"
|
||||
>
|
||||
<Design.DataContext>
|
||||
<dvm:SpriteViewerViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TabControl:singleitem">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="DataGridCell.rowStyle">
|
||||
<Setter Property="Foreground" Value="{Binding Path=Foreground}"/>
|
||||
<Setter Property="FontStyle" Value="{Binding Path=FontStyle}"/>
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<Grid
|
||||
ColumnDefinitions="1*,1*"
|
||||
RowDefinitions="*"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
>
|
||||
<Border BorderBrush="Gray" BorderThickness="1" Margin="1">
|
||||
<ScrollViewer
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
AllowAutoHide="False"
|
||||
>
|
||||
<dc:PictureViewer
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
x:Name="picViewer"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Border BorderBrush="Gray" BorderThickness="1" Grid.Column="1" Margin="1">
|
||||
<dc:MesenDataGrid
|
||||
Items="{CompiledBinding Sprites}"
|
||||
HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
CanUserReorderColumns="False"
|
||||
SelectionChanged="OnGridSelectionChanged"
|
||||
>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="{l:Translate colSpriteIndex}" Binding="{Binding SpriteIndex}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colX}" Binding="{Binding X}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colY}" Binding="{Binding Y}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colSize}" Binding="{Binding Size}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colTileIndex}" Binding="{Binding TileIndex}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colPriority}" Binding="{Binding Priority}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colPalette}" Binding="{Binding Palette}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
<DataGridTextColumn Header="{l:Translate colFlags}" Binding="{Binding Flags}" Width="*" CanUserResize="True" CellStyleClasses="rowStyle" />
|
||||
</DataGrid.Columns>
|
||||
</dc:MesenDataGrid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
131
NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs
Normal file
131
NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
#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;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace Mesen.Debugger.Windows
|
||||
{
|
||||
public class SpriteViewerWindow : Window
|
||||
{
|
||||
private NotificationListener _listener;
|
||||
private SpriteViewerViewModel _model;
|
||||
private PictureViewer _picViewer;
|
||||
private WriteableBitmap _viewerBitmap;
|
||||
private int _updateCounter = 0;
|
||||
|
||||
public SpriteViewerWindow()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
_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 SpriteViewerViewModel model) {
|
||||
_model = model;
|
||||
} else {
|
||||
throw new Exception("Unexpected model");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if(e.AddedItems[0] is SpriteViewerViewModel.SpriteRowModel row) {
|
||||
int offset = 0;
|
||||
if(_model.CpuType == CpuType.Cpu) {
|
||||
offset = 256;
|
||||
}
|
||||
_picViewer.SelectionRect = new Rect(row.X + offset, row.Y, row.Width, row.Height);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSprites<T>(bool forceListUpdate) where T : struct, BaseState
|
||||
{
|
||||
T ppuState = DebugApi.GetPpuState<T>(_model.CpuType);
|
||||
byte[] vram = DebugApi.GetMemoryState(_model.CpuType.GetVramMemoryType());
|
||||
byte[] spriteRam = DebugApi.GetMemoryState(_model.CpuType.GetSpriteRamMemoryType());
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType);
|
||||
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
GetSpritePreviewOptions options = new GetSpritePreviewOptions() {
|
||||
SelectedSprite = -1
|
||||
};
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType);
|
||||
|
||||
FrameInfo size = DebugApi.GetSpritePreviewSize(_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.GetSpritePreview(_model.CpuType, options, ppuState, vram, spriteRam, palette, framebuffer.Address);
|
||||
}
|
||||
|
||||
_picViewer.Source = _viewerBitmap;
|
||||
_picViewer.InvalidateVisual();
|
||||
|
||||
if(forceListUpdate || _updateCounter % 4 == 0) { //15 fps
|
||||
DebugSpriteInfo[] sprites = DebugApi.GetSpriteList(_model.CpuType, options, ppuState, spriteRam);
|
||||
_model.UpdateSprites(sprites);
|
||||
}
|
||||
_updateCounter++;
|
||||
});
|
||||
}
|
||||
|
||||
private void listener_OnNotification(NotificationEventArgs e)
|
||||
{
|
||||
if(e.NotificationType == ConsoleNotificationType.PpuFrameDone || e.NotificationType == ConsoleNotificationType.CodeBreak) {
|
||||
bool forceListUpdate = e.NotificationType == ConsoleNotificationType.CodeBreak;
|
||||
switch(_model.CpuType) {
|
||||
case CpuType.Cpu: UpdateSprites<PpuState>(forceListUpdate); break;
|
||||
case CpuType.Nes: UpdateSprites<NesPpuState>(forceListUpdate); break;
|
||||
case CpuType.Gameboy: UpdateSprites<GbPpuState>(forceListUpdate); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -134,24 +134,22 @@ namespace Mesen.Interop
|
|||
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();
|
||||
IntPtr compareVramPtr = IntPtr.Zero;
|
||||
|
||||
if(options.CompareVram != null) {
|
||||
handle = GCHandle.Alloc(options.CompareVram, GCHandleType.Pinned);
|
||||
pointer = handle.Value.AddrOfPinnedObject();
|
||||
compareVramPtr = handle.Value.AddrOfPinnedObject();
|
||||
}
|
||||
|
||||
int len = Marshal.SizeOf(typeof(T));
|
||||
IntPtr ptr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, ptr, false);
|
||||
IntPtr statePtr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, statePtr, false);
|
||||
InteropGetTilemapOptions interopOptions = options.ToInterop();
|
||||
interopOptions.CompareVram = pointer;
|
||||
DebugApi.GetTilemap(cpuType, interopOptions, ptr, vram, palette, outputBuffer);
|
||||
interopOptions.CompareVram = compareVramPtr;
|
||||
DebugApi.GetTilemap(cpuType, interopOptions, statePtr, vram, palette, outputBuffer);
|
||||
|
||||
if(handle.HasValue) {
|
||||
handle.Value.Free();
|
||||
}
|
||||
Marshal.FreeHGlobal(statePtr);
|
||||
handle?.Free();
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern FrameInfo GetTilemapSize(CpuType cpuType, InteropGetTilemapOptions options, IntPtr state);
|
||||
|
@ -160,11 +158,46 @@ namespace Mesen.Interop
|
|||
int len = Marshal.SizeOf(typeof(T));
|
||||
IntPtr ptr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, ptr, false);
|
||||
return DebugApi.GetTilemapSize(cpuType, options.ToInterop(), ptr);
|
||||
FrameInfo size = DebugApi.GetTilemapSize(cpuType, options.ToInterop(), ptr);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
return size;
|
||||
}
|
||||
|
||||
[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)] private static extern void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr buffer);
|
||||
public static void GetSpritePreview<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr outputBuffer) where T : struct, BaseState
|
||||
{
|
||||
int len = Marshal.SizeOf(typeof(T));
|
||||
IntPtr statePtr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, statePtr, false);
|
||||
DebugApi.GetSpritePreview(cpuType, options, statePtr, vram, spriteRam, palette, outputBuffer);
|
||||
Marshal.FreeHGlobal(statePtr);
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern FrameInfo GetSpritePreviewSize(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state);
|
||||
public static FrameInfo GetSpritePreviewSize<T>(CpuType cpuType, GetSpritePreviewOptions options, T state) where T : struct, BaseState
|
||||
{
|
||||
int len = Marshal.SizeOf(typeof(T));
|
||||
IntPtr ptr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, ptr, false);
|
||||
FrameInfo size = DebugApi.GetSpritePreviewSize(cpuType, options, ptr);
|
||||
Marshal.FreeHGlobal(ptr);
|
||||
return size;
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern UInt32 GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] spriteRam, [In,Out]DebugSpriteInfo[] sprites);
|
||||
public static DebugSpriteInfo[] GetSpriteList<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] spriteRam) where T : struct, BaseState
|
||||
{
|
||||
DebugSpriteInfo[] sprites = new DebugSpriteInfo[256];
|
||||
int len = Marshal.SizeOf(typeof(T));
|
||||
IntPtr statePtr = Marshal.AllocHGlobal(len);
|
||||
Marshal.StructureToPtr(state, statePtr, false);
|
||||
UInt32 spriteCount = DebugApi.GetSpriteList(cpuType, options, statePtr, spriteRam, sprites);
|
||||
Array.Resize(ref sprites, (int)spriteCount);
|
||||
Marshal.FreeHGlobal(statePtr);
|
||||
return sprites;
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle, CpuType cpuType);
|
||||
|
||||
|
@ -720,6 +753,23 @@ namespace Mesen.Interop
|
|||
public Int32 SelectedSprite;
|
||||
}
|
||||
|
||||
public struct DebugSpriteInfo
|
||||
{
|
||||
public UInt16 SpriteIndex;
|
||||
public UInt16 TileIndex;
|
||||
public Int16 X;
|
||||
public Int16 Y;
|
||||
|
||||
public byte Palette;
|
||||
public byte Priority;
|
||||
public byte Width;
|
||||
public byte Height;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool HorizontalMirror;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool VerticalMirror;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool UseSecondTable;
|
||||
[MarshalAs(UnmanagedType.I1)] public bool Visible;
|
||||
}
|
||||
|
||||
public enum TileFormat
|
||||
{
|
||||
Bpp2,
|
||||
|
@ -820,6 +870,18 @@ namespace Mesen.Interop
|
|||
}
|
||||
}
|
||||
|
||||
public static SnesMemoryType GetSpriteRamMemoryType(this CpuType cpuType)
|
||||
{
|
||||
switch(cpuType) {
|
||||
case CpuType.Cpu: return SnesMemoryType.SpriteRam;
|
||||
case CpuType.Gameboy: return SnesMemoryType.GbSpriteRam;
|
||||
case CpuType.Nes: return SnesMemoryType.NesSpriteRam;
|
||||
|
||||
default:
|
||||
throw new Exception("Invalid CPU type");
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetAddressSize(this CpuType cpuType)
|
||||
{
|
||||
switch(cpuType) {
|
||||
|
|
|
@ -791,6 +791,19 @@
|
|||
<Control ID="chkHighlightAttributeChanges">Highlight attribute changes</Control>
|
||||
</Form>
|
||||
|
||||
<Form ID="SpriteViewerWindow">
|
||||
<Control ID="wndTitle">Sprite Viewer</Control>
|
||||
|
||||
<Control ID="colSpriteIndex">#</Control>
|
||||
<Control ID="colX">X</Control>
|
||||
<Control ID="colY">Y</Control>
|
||||
<Control ID="colSize">Size</Control>
|
||||
<Control ID="colTileIndex">Tile</Control>
|
||||
<Control ID="colPriority">Priority</Control>
|
||||
<Control ID="colPalette">Palette</Control>
|
||||
<Control ID="colFlags">Flags</Control>
|
||||
</Form>
|
||||
|
||||
<Form ID="AssemblerWindow">
|
||||
<Control ID="wndTitle">Assembler</Control>
|
||||
<Control ID="lblStartAddress">Start address: $</Control>
|
||||
|
|
|
@ -218,6 +218,9 @@
|
|||
<Compile Update="Debugger\Views\SnesPpuView.axaml.cs">
|
||||
<DependentUpon>SnesPpuView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Debugger\Windows\SpriteViewerWindow.axaml.cs">
|
||||
<DependentUpon>SpriteViewerWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Debugger\Windows\TilemapViewerWindow.axaml.cs">
|
||||
<DependentUpon>TilemapViewerWindow.axaml</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -395,6 +395,11 @@
|
|||
<Image Source="/Assets/VideoOptions.png" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Sprite Viewer" Click="OnSpriteViewerClick">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="/Assets/PerfTracker.png" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="_Assembler" Click="OnAssemblerClick">
|
||||
<MenuItem.Icon>
|
||||
<Image Source="/Assets/Chip.png" />
|
||||
|
|
|
@ -67,6 +67,14 @@ namespace Mesen.Views
|
|||
}.Show();
|
||||
}
|
||||
|
||||
private void OnSpriteViewerClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
RomInfo romInfo = EmuApi.GetRomInfo();
|
||||
new SpriteViewerWindow {
|
||||
DataContext = new SpriteViewerViewModel(romInfo.ConsoleType.GetMainCpuType(), romInfo.ConsoleType),
|
||||
}.Show();
|
||||
}
|
||||
|
||||
private void OnMemoryToolsClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
new MemoryToolsWindow {
|
||||
|
|
Loading…
Add table
Reference in a new issue