Mesen2/Core/GBA/Debugger/GbaPpuTools.cpp

630 lines
19 KiB
C++

#include "pch.h"
#include "GBA/Debugger/GbaPpuTools.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/MemoryDumper.h"
#include "Shared/SettingTypes.h"
#include "GBA/GbaTypes.h"
#include "Shared/ColorUtilities.h"
GbaPpuTools::GbaPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugger, emu)
{
}
void GbaPpuTools::SetMemoryAccessData(uint16_t scanline, uint8_t* data)
{
memcpy(_memoryAccess + scanline * 308 * 4, data, 308 * 4);
}
DebugTilemapInfo GbaPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer)
{
GbaPpuState& state = (GbaPpuState&)baseState;
FrameInfo outputSize = GetTilemapSize(options, state);
if(options.Layer == 4) {
DebugTilemapInfo result = {};
result.TileHeight = 1;
result.TileWidth = 1;
result.RowCount = 228;
result.ColumnCount = 308*4;
result.Bpp = 8;
result.Format = TileFormat::DirectColor;
for(int i = 0; i < sizeof(_memoryAccess); i++) {
outBuffer[i] = (_memoryAccess[i] & GbaPpuMemAccess::Vram) ? 0xFFA00000 : 0xFF000000;
outBuffer[i] |= (_memoryAccess[i] & GbaPpuMemAccess::VramObj) ? 0xFF5F0000 : 0xFF000000;
outBuffer[i] |= (_memoryAccess[i] & GbaPpuMemAccess::Oam) ? 0xFF00FF00 : 0xFF000000;
outBuffer[i] |= (_memoryAccess[i] & GbaPpuMemAccess::Palette) ? 0xFF0000FF : 0xFF000000;
}
return result;
}
uint8_t colorMask = 0xFF;
bool grayscale = options.DisplayMode == TilemapDisplayMode::Grayscale;
if(grayscale) {
palette = (uint32_t*)_grayscaleColorsBpp4;
colorMask = 0x0F;
}
std::fill(outBuffer, outBuffer + outputSize.Width * outputSize.Height, palette[0]);
DebugTilemapInfo result = {};
switch(state.BgMode) {
case 0: case 1: case 2:
{
GbaBgConfig& layer = state.BgLayers[options.Layer];
bool transformEnabled = (state.BgMode != 0 && options.Layer >= 2);
bool bpp8Mode = layer.Bpp8Mode || transformEnabled;
result.ScrollX = transformEnabled ? 0 : layer.ScrollX;
result.ScrollY = transformEnabled ? 0 : layer.ScrollY;
result.ScrollWidth = transformEnabled ? 0 : 240;
result.ScrollHeight = transformEnabled ? 0 : 160;
result.Bpp = bpp8Mode ? 8 : 4;
result.Format = bpp8Mode ? TileFormat::GbaBpp8 : TileFormat::GbaBpp4;
result.TileWidth = 8;
result.TileHeight = 8;
if(transformEnabled) {
uint16_t screenSize = 128 << layer.ScreenSize;
result.ColumnCount = screenSize / 8;
result.RowCount = screenSize / 8;
} else {
result.ColumnCount = layer.DoubleWidth ? 64 : 32;
result.RowCount = layer.DoubleHeight ? 64 : 32;
}
result.TilemapAddress = layer.TilemapAddr;
result.TilesetAddress = layer.TilesetAddr;
result.Priority = layer.Priority;
for(uint32_t row = 0; row < result.RowCount; row++) {
for(uint32_t column = 0; column < result.ColumnCount; column++) {
uint16_t vramAddr;
if(transformEnabled) {
vramAddr = layer.TilemapAddr + row * result.ColumnCount + column;
} else {
vramAddr = layer.TilemapAddr + (((row & 0x1F) * 32 + (column & 0x1F)) * 2);
if(column >= 32) {
vramAddr += 0x800;
}
if(row >= 32) {
vramAddr += layer.DoubleWidth ? 0x1000 : 0x800;
}
}
uint16_t tileData;
if(transformEnabled) {
tileData = vram[vramAddr];
} else {
tileData = vram[vramAddr] | (vram[vramAddr + 1] << 8);
}
uint16_t tileIndex = tileData & 0x3FF;
bool hMirror = tileData & (1 << 10);
bool vMirror = tileData & (1 << 11);
uint8_t paletteIndex = (tileData >> 12) & 0x0F;
for(int y = 0; y < 8; y++) {
uint8_t tileRow = vMirror ? 7 - y : y;
for(int x = 0; x < 8; x++) {
uint8_t tileColumn = hMirror ? 7 - x : x;
uint8_t color = 0;
if(bpp8Mode) {
color = vram[(layer.TilesetAddr + tileIndex * 64 + tileRow * 8 + tileColumn) & 0xFFFF];
paletteIndex = 0;
} else {
color = vram[(layer.TilesetAddr + tileIndex * 32 + tileRow * 4 + (tileColumn >> 1)) & 0xFFFF];
if(tileColumn & 0x01) {
color >>= 4;
} else {
color &= 0x0F;
}
}
if(color) {
int pos = ((row * 8) + y) * outputSize.Width + column * 8 + x;
outBuffer[pos] = palette[((paletteIndex * 16) + color) & colorMask];
}
}
}
}
}
break;
}
case 3:
{
if(options.Layer != 2) {
break;
}
result.ScrollX = 0;
result.ScrollY = 0;
result.Bpp = 16;
result.Format = TileFormat::DirectColor;
result.TileWidth = 1;
result.TileHeight = 1;
result.ColumnCount = 240;
result.RowCount = 160 * 2;
result.TilemapAddress = 0;
result.TilesetAddress = 0;
result.ScrollWidth = 0;
result.ScrollHeight = 0;
for(int i = 0; i < GbaConstants::PixelCount; i++) {
outBuffer[i] = ColorUtilities::Rgb555ToArgb(vram[i * 2] | (vram[i * 2 + 1] << 8));
}
break;
}
case 4:
{
if(options.Layer != 2) {
break;
}
result.ScrollX = 0;
result.ScrollY = state.DisplayFrameSelect ? 160 : 0;
result.Bpp = 8;
result.Format = TileFormat::GbaBpp8;
result.TileWidth = 1;
result.TileHeight = 1;
result.ColumnCount = 240;
result.RowCount = 160*2;
result.TilemapAddress = 0;
result.TilesetAddress = 0;
result.ScrollWidth = 240;
result.ScrollHeight = 160;
for(int i = 0; i < GbaConstants::PixelCount * 2; i++) {
outBuffer[i] = palette[vram[i]];
}
break;
}
case 5:
{
if(options.Layer != 2) {
break;
}
result.ScrollX = 0;
result.ScrollY = state.DisplayFrameSelect ? 128 : 0;
result.Bpp = 16;
result.Format = TileFormat::DirectColor;
result.TileWidth = 1;
result.TileHeight = 1;
result.ColumnCount = 160;
result.RowCount = 128*2;
result.TilemapAddress = 0;
result.TilesetAddress = 0;
result.ScrollWidth = 160;
result.ScrollHeight = 128;
for(int i = 0; i < 160*128*2; i++) {
outBuffer[i] = ColorUtilities::Rgb555ToArgb(vram[i * 2] | (vram[i * 2 + 1] << 8));
}
break;
}
default:
break;
}
return result;
}
FrameInfo GbaPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& baseState)
{
if(options.Layer == 4) {
return { 308 * 4, 228 };
}
GbaPpuState& state = (GbaPpuState&)baseState;
switch(state.BgMode) {
case 0: case 1: case 2: {
GbaBgConfig& layer = state.BgLayers[options.Layer];
bool transformEnabled = (state.BgMode != 0 && options.Layer >= 2);
if(transformEnabled) {
uint32_t screenSize = 128 << layer.ScreenSize;
return { screenSize, screenSize };
} else {
return { 256u * (layer.DoubleWidth ? 2 : 1), 256u * (layer.DoubleHeight ? 2 : 1) };
}
}
case 3:
if(options.Layer == 2) {
return { 240, 160 };
}
break;
case 4:
if(options.Layer == 2) {
return { 240, 160 * 2 };
}
break;
case 5:
if(options.Layer == 2) {
return { 160, 128 * 2 };
}
break;
}
return { 0, 0 };
}
DebugTilemapTileInfo GbaPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState, BaseState& ppuToolsState)
{
DebugTilemapTileInfo result = {};
FrameInfo size = GetTilemapSize(options, baseState);
if(x >= size.Width || y >= size.Height || options.Layer == 4) {
return result;
}
GbaPpuState& state = (GbaPpuState&)baseState;
bool transformEnabled = (state.BgMode != 0 && options.Layer >= 2);
int32_t width = -1;
int32_t height = -1;
int32_t column = -1;
int32_t row = -1;
int32_t tilemapAddr = -1;
int32_t pixelData = -1;
int32_t tileIndex = -1;
int32_t tileStart = -1;
int32_t paletteIndex = -1;
NullableBoolean hMirror = NullableBoolean::Undefined;
NullableBoolean vMirror = NullableBoolean::Undefined;
switch(state.BgMode) {
case 0:
case 1:
case 2: {
row = y / 8;
column = x / 8;
width = 8;
height = 8;
int offset = state.BgLayers[options.Layer].TilemapAddr;
if(transformEnabled) {
uint16_t screenSize = 128 << state.BgLayers[options.Layer].ScreenSize;
uint16_t columnCount = screenSize >> 3;
tilemapAddr = offset + row * columnCount + column;
} else {
tilemapAddr = offset + (((row & 0x1F) << 5) + column) * 2;
if(column >= 32) {
tilemapAddr += 0x800;
}
if(row >= 32) {
tilemapAddr += size.Width > 256 ? 0x1000 : 0x800;
}
}
uint16_t tileData;
if(transformEnabled) {
tileData = vram[tilemapAddr];
} else {
tileData = vram[tilemapAddr] | (vram[tilemapAddr + 1] << 8);
}
bool bpp8Mode = transformEnabled || state.BgLayers[options.Layer].Bpp8Mode;
tileIndex = tileData & 0x3FF;
if(!transformEnabled) {
paletteIndex = (tileData >> 12) & 0x0F;
hMirror = (NullableBoolean)(tileData & (1 << 10));
vMirror = (NullableBoolean)(tileData & (1 << 11));
}
tileStart = state.BgLayers[options.Layer].TilesetAddr + (tileIndex * (bpp8Mode ? 64 : 32));
break;
}
case 3:
case 4:
case 5: {
uint16_t screenWidth = state.BgMode == 5 ? 160 : 240;
column = x;
row = y;
width = 1;
height = 1;
if(state.BgMode == 3 || state.BgMode == 5) {
tilemapAddr = (y * screenWidth + x) * 2;
pixelData = vram[tilemapAddr] | (vram[tilemapAddr + 1] << 8);
} else {
tilemapAddr = (y * screenWidth + x);
pixelData = vram[tilemapAddr];
}
break;
}
}
result.Column = column;
result.Row = row;
result.Height = height;
result.Width = width;
result.TileMapAddress = tilemapAddr;
result.TileIndex = tileIndex;
result.TileAddress = tileStart;
result.PixelData = pixelData;
result.PaletteIndex = paletteIndex;
result.PaletteAddress = paletteIndex << 5;
result.HorizontalMirroring = hMirror;
result.VerticalMirroring = vMirror;
return result;
}
void GbaPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& baseState, DebugSpriteInfo* sprites, uint32_t* spritePreviews, uint32_t* palette, uint32_t* outBuffer)
{
uint32_t bgColor = GetSpriteBackgroundColor(options.Background, palette, false);
vector<uint8_t> priority(512 * 256);
std::fill(priority.begin(), priority.end(), 4);
std::fill(outBuffer, outBuffer + 512 * 256, GetSpriteBackgroundColor(options.Background, palette, true));
for(int i = 0; i < 160; i++) {
std::fill(outBuffer + i * 512, outBuffer + i * 512 + 240, bgColor);
}
for(int i = 0; i < 128; i++) {
DebugSpriteInfo& sprite = sprites[i];
uint32_t* spritePreview = spritePreviews + i * _spritePreviewSize;
for(int y = 0; y < sprite.Height; y++) {
uint8_t outputRow = (uint8_t)(sprite.Y + y);
for(int x = 0; x < sprite.Width; x++) {
uint16_t drawPos = (sprite.X + x) & 0x1FF;
uint32_t pos = (outputRow * 512) + drawPos;
uint32_t color = spritePreview[y * sprite.Width + x];
if(color != 0) {
if((uint8_t)sprite.Priority < priority[pos]) {
priority[pos] = (uint8_t)sprite.Priority;
outBuffer[pos] = color;
}
} else {
spritePreview[y * sprite.Width + x] = bgColor;
}
}
}
}
}
DebugSpritePreviewInfo GbaPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions options, BaseState& state, BaseState& ppuToolsState)
{
DebugSpritePreviewInfo info = {};
info.Height = 256;
info.Width = 512;
info.SpriteCount = 128;
info.CoordOffsetX = 0;
info.CoordOffsetY = 0;
info.VisibleX = 0;
info.VisibleY = 0;
info.VisibleWidth = 240;
info.VisibleHeight = 160;
info.WrapRightToLeft = true;
info.WrapBottomToTop = true;
return info;
}
void GbaPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t* spritePreview, uint16_t i, GetSpritePreviewOptions& options, GbaPpuState& state, uint8_t* vram, uint8_t* oam, uint32_t* palette)
{
uint32_t addr = i * 8;
sprite.Y = oam[addr];
sprite.X = oam[addr + 2] | ((oam[addr + 3] & 0x01) << 8);
sprite.RawY = sprite.Y;
sprite.RawX = sprite.X;
bool bpp8Mode = oam[addr + 1] & 0x20;
sprite.Bpp = bpp8Mode ? 8 : 4;
sprite.Format = bpp8Mode ? TileFormat::GbaBpp8 : TileFormat::GbaBpp4;
bool hMirror = false;
bool vMirror = false;
uint8_t shape = (oam[addr + 1] >> 6) & 0x03;
bool transformEnabled = oam[addr + 1] & 0x01;
bool doubleSize = transformEnabled && (oam[addr + 1] & 0x02);
bool spriteHidden = !transformEnabled && (oam[addr + 1] & 0x02);
sprite.TransformEnabled = transformEnabled ? NullableBoolean::True : NullableBoolean::False;
if(transformEnabled) {
sprite.TransformParamIndex = (oam[addr + 3] >> 1) & 0x1F;
sprite.DoubleSize = doubleSize ? NullableBoolean::True : NullableBoolean::False;
//Clear all pixels first, since the drawing logic won't always draw on each pixel
std::fill(spritePreview, spritePreview + _spritePreviewSize, 0);
} else {
hMirror = oam[addr + 3] & 0x10;
vMirror = oam[addr + 3] & 0x20;
sprite.HorizontalMirror = hMirror ? NullableBoolean::True : NullableBoolean::False;
sprite.VerticalMirror = vMirror ? NullableBoolean::True : NullableBoolean::False;
}
sprite.MosaicEnabled = oam[addr + 1] & 0x10 ? NullableBoolean::True : NullableBoolean::False;
GbaPpuObjMode mode = (GbaPpuObjMode)((oam[addr + 1] >> 2) & 0x03);
sprite.BlendingEnabled = mode == GbaPpuObjMode::Blending ? NullableBoolean::True : NullableBoolean::False;
sprite.WindowMode = mode == GbaPpuObjMode::Window ? NullableBoolean::True : NullableBoolean::False;
uint8_t size = (oam[addr + 3] >> 6) & 0x03;
sprite.TileIndex = oam[addr + 4] | ((oam[addr + 5] & 0x03) << 8);
sprite.Priority = (DebugSpritePriority)((oam[addr + 5] >> 2) & 0x03);
sprite.Palette = bpp8Mode ? 0 : (oam[addr + 5] >> 4) & 0x0F;
sprite.PaletteAddress = bpp8Mode ? -1 : (sprite.Palette << 5);
static constexpr uint8_t sprSize[4][4][2] = {
{ { 8, 8 }, { 16, 8 }, { 8, 16 }, { 8, 8 } },
{ { 16, 16 }, { 32, 8 }, { 8, 32 }, { 16, 16 } },
{ { 32, 32 }, { 32, 16 }, { 16, 32 }, { 32, 32 } },
{ { 64, 64 }, { 64, 32 }, { 32, 64 }, { 64, 64 } }
};
uint8_t width = sprSize[size][shape][0];
uint8_t height = sprSize[size][shape][1];
sprite.Width = width << (doubleSize ? 1 : 0);
sprite.Height = height << (doubleSize ? 1 : 0);
sprite.SpriteIndex = i;
sprite.UseExtendedVram = false;
if(spriteHidden || mode == GbaPpuObjMode::Invalid) {
sprite.Visibility = SpriteVisibility::Disabled;
} else {
uint16_t x1 = sprite.X;
uint16_t x2 = (sprite.X + sprite.Width - 1) & 0x1FF;
uint8_t y1 = sprite.Y;
uint8_t y2 = (sprite.Y + sprite.Height - 1) & 0xFF;
sprite.Visibility = ((x1 < 240 || x2 < 240) && (y1 < 160 || y2 < 160)) ? SpriteVisibility::Visible : SpriteVisibility::Offscreen;
}
sprite.TileCount = (height / 8) * (width / 8);
if(state.ObjVramMappingOneDimension) {
for(uint32_t j = 0; j < sprite.TileCount; j++) {
sprite.TileAddresses[j] = 0x10000 | (((sprite.TileIndex * 32) + (j * 32 * (bpp8Mode ? 2 : 1))) & 0x7FFF);
}
} else {
int count = 0;
for(int y = 0; y < height / 8; y++) {
for(int x = 0; x < width / 8; x++) {
uint32_t tileIndex = (sprite.TileIndex & ~0x1F) + y * 0x20 + (((sprite.TileIndex & 0x1F) + (bpp8Mode ? x * 2 : x)) & 0x1F);
sprite.TileAddresses[count++] = 0x10000 | ((tileIndex * 32) & 0x7FFF);
}
}
}
sprite.TileAddress = sprite.TileAddresses[0];
int16_t pa = 0;
int16_t pc = 0;
int32_t xValue = 0;
int32_t yValue = 0;
int16_t centerX = 0;
int16_t centerY = 0;
for(int y = 0; y < sprite.Height; y++) {
uint8_t yOffset = vMirror ? (sprite.Height - y - 1) : y;
if(transformEnabled) {
uint16_t transformAddr = (sprite.TransformParamIndex << 5) + 6;
pa = oam[transformAddr] | (oam[transformAddr + 1] << 8);
int16_t pb = oam[transformAddr + 8] | (oam[transformAddr + 9] << 8);
pc = oam[transformAddr + 16] | (oam[transformAddr + 17] << 8);
int16_t pd = oam[transformAddr + 24] | (oam[transformAddr + 25] << 8);
centerX = width / 2;
centerY = height / 2;
int32_t originX = -(centerX << (uint8_t)doubleSize);
int32_t originY = -(centerY << (uint8_t)doubleSize);
xValue = originX * pa + (originY + yOffset) * pb;
yValue = originX * pc + (originY + yOffset) * pd;
}
uint8_t tilesPerRow = (width / 8) * (bpp8Mode ? 2 : 1);
uint32_t xRender = 0;
uint32_t yRender = 0;
for(int x = 0; x < sprite.Width; x++) {
if(transformEnabled) {
xRender = (xValue >> 8) + centerX;
yRender = (yValue >> 8) + centerY;
xValue += pa;
yValue += pc;
} else {
xRender = hMirror ? (width - x - 1) : x;
yRender = vMirror ? (height - yOffset - 1) : yOffset;
}
if(xRender >= width || yRender >= height) {
continue;
}
uint8_t tileColumn = xRender / 8;
uint8_t tileRow = yRender / 8;
uint16_t index;
if(state.ObjVramMappingOneDimension) {
index = sprite.TileIndex + tileRow * tilesPerRow + (bpp8Mode ? tileColumn * 2 : tileColumn);
} else {
index = (sprite.TileIndex & ~0x1F) + tileRow * 0x20 + (((sprite.TileIndex & 0x1F) + (bpp8Mode ? tileColumn * 2 : tileColumn)) & 0x1F);
}
uint8_t color = 0;
if(bpp8Mode) {
uint32_t tileAddr = (index * 32 + (yRender & 0x07) * 8 + (xRender & 0x07)) & 0x7FFF;
color = vram[0x10000 + tileAddr];
} else {
uint32_t tileAddr = (index * 32 + (yRender & 0x07) * 4 + ((xRender & 0x07) >> 1)) & 0x7FFF;
color = vram[0x10000 + tileAddr];
if(xRender & 0x01) {
color >>= 4;
} else {
color &= 0x0F;
}
}
uint32_t outOffset = (y * sprite.Width) + x;
if(color != 0) {
spritePreview[outOffset] = palette[0x100 + (sprite.Palette * 16) + color];
} else {
spritePreview[outOffset] = 0;
}
}
}
}
void GbaPpuTools::GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, BaseState& ppuToolsState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[], uint32_t* spritePreviews, uint32_t* screenPreview)
{
GbaPpuState& state = (GbaPpuState&)baseState;
for(int i = 0; i < 128; i++) {
outBuffer[i].Init();
GetSpriteInfo(outBuffer[i], spritePreviews+i*_spritePreviewSize, i, options, state, vram, oamRam, palette);
}
GetSpritePreview(options, baseState, outBuffer, spritePreviews, palette, screenPreview);
}
DebugPaletteInfo GbaPpuTools::GetPaletteInfo(GetPaletteInfoOptions options)
{
DebugPaletteInfo info = {};
info.PaletteMemType = MemoryType::GbaPaletteRam;
info.HasMemType = true;
info.RawFormat = RawPaletteFormat::Rgb555;
info.ColorsPerPalette = 16;
info.BgColorCount = 256;
info.SpritePaletteOffset = info.BgColorCount;
info.SpriteColorCount = 256;
info.ColorCount = info.BgColorCount + info.SpriteColorCount;
uint8_t* palette = _debugger->GetMemoryDumper()->GetMemoryBuffer(MemoryType::GbaPaletteRam);
for(int i = 0; i < 512; i++) {
info.RawPalette[i] = palette[i * 2] | (palette[i * 2 + 1] << 8);
info.RgbPalette[i] = ColorUtilities::Rgb555ToArgb(info.RawPalette[i]);
}
return info;
}
void GbaPpuTools::SetPaletteColor(int32_t colorIndex, uint32_t color)
{
uint8_t r = (color >> 19) & 0x1F;
uint8_t g = (color >> 11) & 0x1F;
uint8_t b = (color >> 3) & 0x1F;
uint16_t rgb555 = (b << 10) | (g << 5) | r;
_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::GbaPaletteRam, colorIndex * 2, rgb555 & 0xFF);
_debugger->GetMemoryDumper()->SetMemoryValue(MemoryType::GbaPaletteRam, colorIndex * 2 + 1, (rgb555 >> 8));
}