BizHawk/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SNESGraphicsDecoder.cs

642 lines
21 KiB
C#

using System.Buffers.Binary;
using System.Drawing;
using BizHawk.Emulation.Cores.Nintendo.SNES;
using static BizHawk.Emulation.Cores.Nintendo.BSNES.BsnesApi.SNES_REGISTER;
using static BizHawk.Emulation.Cores.Nintendo.SNES.SNESGraphicsDecoder;
namespace BizHawk.Emulation.Cores.Nintendo.BSNES
{
public sealed unsafe class SNESGraphicsDecoder : ISNESGraphicsDecoder
{
private readonly BsnesApi _api;
private readonly byte* vram; // waterbox pointer, ALWAYS access with EnterExit()
private readonly ushort* cgram; // waterbox pointer, ALWAYS access with EnterExit()
private readonly byte[][] cachedTiles = new byte[5][];
private readonly int[] bppArrayIndex = { 0, 0, 0, 0, 1, 0, 0, 0, 2 };
private bool useBackColor;
private int backColor;
private readonly int[] palette = new int[32768];
private readonly short[] directColorTable = new short[256];
private void generate_palette()
{
Span<byte> scratch = stackalloc byte[4];
scratch[0] = 0xFF;
for (int color = 0; color < 32768; color++) {
int r = (color >> 10) & 31;
int g = (color >> 5) & 31;
int b = (color >> 0) & 31;
r = r << 3 | r >> 2; r = r << 8 | r << 0;
g = g << 3 | g >> 2; g = g << 8 | g << 0;
b = b << 3 | b >> 2; b = b << 8 | b << 0;
unchecked
{
scratch[1] = (byte) (b >> 8);
scratch[2] = (byte) (g >> 8);
scratch[3] = (byte) (r >> 8);
}
palette[color] = BinaryPrimitives.ReadInt32BigEndian(scratch);
}
}
private void generate_directColorTable()
{
for (int i = 0; i < 256; i++)
{
directColorTable[i] = (short)
( (i << 2 & 0x001c) //R
+ (i << 4 & 0x0380) //G
+ (i << 7 & 0x6000) ); //B
}
}
public SNESGraphicsDecoder(BsnesApi api)
{
_api = api;
vram = (byte*)api.core.snes_get_memory_region((int)BsnesApi.SNES_MEMORY.VRAM, out _, out _);
cgram = (ushort*)api.core.snes_get_memory_region((int)BsnesApi.SNES_MEMORY.CGRAM, out _, out _);
generate_palette();
generate_directColorTable();
}
public void CacheTiles()
{
CacheTiles2Bpp();
CacheTiles_Merge(4);
CacheTiles_Merge(8);
CacheTilesMode7();
CacheTilesMode7ExtBg();
}
private void CacheTiles2Bpp()
{
const int tileCount = 65536 / 8 / 2;
cachedTiles[0] ??= new byte[8 * 8 * tileCount];
for (int i = 0; i < tileCount; i++)
{
int offset = 64 * i;
int addr = 16 * i;
const int stride = 8;
for (int y = 0; y < 8; y++)
{
byte val = vram[addr + 1];
for (int x = 0; x < 8; x++)
cachedTiles[0][offset + y * stride + x] = (byte)(val >> (7 - x) & 1);
val = vram[addr + 0];
for (int x = 0; x < 8; x++)
cachedTiles[0][offset + y * stride + x] = (byte)((cachedTiles[0][offset + y * stride + x] << 1) | (val >> (7 - x) & 1));
addr += 2;
}
}
}
private void CacheTiles_Merge(int toBpp)
{
int shift = toBpp / 2;
int tileCount = 8192 / toBpp;
int destinationIndex = bppArrayIndex[toBpp];
cachedTiles[destinationIndex] ??= new byte[8 * 8 * tileCount];
for (int i = 0; i < tileCount; i++)
{
int srcAddr = 128 * i;
int dstAddr = 64 * i;
for (int p = 0; p < 64; p++)
{
int tileA = cachedTiles[destinationIndex - 1][srcAddr + p];
int tileB = cachedTiles[destinationIndex - 1][srcAddr + p + 64];
cachedTiles[destinationIndex][dstAddr + p] = (byte)(tileA | (tileB << shift));
}
}
}
private void CacheTilesMode7()
{
const int tileCount = 256;
const int pixelCount = 8 * 8 * tileCount;
cachedTiles[3] ??= new byte[pixelCount];
for (int i = 0; i < pixelCount; i++)
cachedTiles[3][i] = vram[2 * i + 1];
}
private void CacheTilesMode7ExtBg()
{
const int tileCount = 256;
const int pixelCount = 8 * 8 * tileCount;
cachedTiles[4] ??= new byte[pixelCount];
for (int i = 0; i < pixelCount; i++)
cachedTiles[4][i] = (byte)(cachedTiles[3][i] & 0x7F);
}
public int Colorize(int rgb555)
{
return palette[rgb555];
}
public void Colorize(int* buf, int offset, int numpixels)
{
for (int i = 0; i < numpixels; i++)
{
buf[offset + i] = palette[buf[offset + i]];
}
}
public ISNESGraphicsDecoder.OAMInfo CreateOAMInfo(ScreenInfo si, int num) => new OAMInfo(_api.core.snes_read_oam, si, num);
public void DecodeBG(
int* screen,
int stride, TileEntry[] map,
int tiledataBaseAddr, ScreenSize size,
int bpp,
int tilesize,
int paletteStart)
{
//emergency backstop. this can only happen if we're displaying an unavailable BG or other similar such value
if (bpp == 0) return;
int ncolors = 1 << bpp;
var dims = SizeInTilesForBGSize(size);
int count8x8 = tilesize / 8;
int tileSizeBytes = 8 * bpp;
int baseTileNum = tiledataBaseAddr / tileSizeBytes;
byte[] tileCache = cachedTiles[bppArrayIndex[bpp]];
int tileCacheMask = tileCache.Length - 1;
for (int mty = 0; mty < dims.Height; mty++)
for (int mtx = 0; mtx < dims.Width; mtx++)
for (int tx = 0; tx < count8x8; tx++)
for (int ty = 0; ty < count8x8; ty++)
{
int mapIndex = mty * dims.Width + mtx;
var te = map[mapIndex];
//apply metatile flipping
int tnx = tx, tny = ty;
if (tilesize == 16)
{
if ((te.flags & TileEntryFlags.Horz) != 0) tnx = 1 - tnx;
if ((te.flags & TileEntryFlags.Vert) != 0) tny = 1 - tny;
}
int tileNum = te.tilenum + tnx + tny * 16 + baseTileNum;
int srcOfs = tileNum * 64;
for (int y = 0; y < 8; y++)
for (int x = 0; x < 8; x++)
{
int px = x;
int py = y;
if ((te.flags & TileEntryFlags.Horz) != 0) px = 7 - x;
if ((te.flags & TileEntryFlags.Vert) != 0) py = 7 - y;
int dstX = (mtx * count8x8 + tx) * 8 + px;
int dstY = (mty * count8x8 + ty) * 8 + py;
int dstOfs = dstY * stride + dstX;
int color = tileCache[srcOfs & tileCacheMask];
srcOfs++;
// if (color != 0)
// {
color += te.palette * ncolors;
color += paletteStart;
// }
screen[dstOfs] = color;
}
}
}
public void DecodeMode7BG(int* screen, int stride, bool extBg)
{
byte[] cachedTileBuffer = cachedTiles[extBg ? 4 : 3];
for (int ty = 0, tidx = 0; ty < 128; ty++)
for (int tx = 0; tx < 128; tx++, tidx++)
{
int tileEntry = vram[tidx * 2];
int src = tileEntry * 64;
for (int py = 0; py < 8; py++)
for (int px = 0; px < 8; px++)
{
int dst = (ty * 8 + py) * stride + (tx * 8 + px);
int srcData = cachedTileBuffer[src++];
screen[dst] = srcData;
}
}
}
public void DirectColorify(int* screen, int numPixels)
{
for (int i = 0; i < numPixels; i++)
{
screen[i] = directColorTable[screen[i]];
}
}
public void Enter()
=> _api.Enter();
public void Exit()
=> _api.Exit();
public TileEntry[] FetchMode7Tilemap()
{
var buf = new TileEntry[128 * 128];
for (int tidx = 0; tidx < 128 * 128; tidx++)
{
buf[tidx].address = tidx * 2;
buf[tidx].tilenum = vram[tidx * 2];
}
return buf;
}
public TileEntry[] FetchTilemap(int addr, ScreenSize size)
{
Dimensions blockDimensions = SizeInBlocksForBGSize(size);
int realWidth = blockDimensions.Width * 32;
int realHeight = blockDimensions.Height * 32;
TileEntry[] buf = new TileEntry[realWidth * realHeight];
for (int by = 0; by < blockDimensions.Height; by++)
for (int bx = 0; bx < blockDimensions.Width; bx++)
for (int y = 0; y < 32; y++)
for (int x = 0; x < 32; x++)
{
int idx = (by * 32 + y) * realWidth + bx * 32 + x;
ushort entry = *(ushort*)(vram + addr);
buf[idx].tilenum = (ushort)(entry & 0x3FF);
buf[idx].palette = (byte)((entry >> 10) & 7);
buf[idx].flags = (TileEntryFlags)((entry >> 13) & 7);
buf[idx].address = addr;
addr += 2;
}
return buf;
}
public int[] GetPalette()
{
int[] ret = new int[256];
for (int i = 0; i < 256; i++)
ret[i] = cgram[i] & 0x7FFF;
return ret;
}
public void Paletteize(int* buf, int offset, int startcolor, int numpixels)
{
for (int i = 0; i < numpixels; i++)
{
int entry = buf[offset + i];
int color = entry == 0 && useBackColor ? backColor : cgram[startcolor + entry] & 0x7FFF;
buf[offset + i] = color;
}
}
public void RenderMode7TilesToScreen(
int* screen,
int stride,
bool ext,
bool directColor,
int tilesWide,
int startTile,
int numTiles)
{
byte[] cachedTileBuffer = cachedTiles[ext ? 4 : 3];
for (int i = 0; i < numTiles; i++)
{
int targetYPos = i / tilesWide * stride * 8;
int targetXPos = i % tilesWide * 8;
int destinationOffset = targetYPos + targetXPos;
int sourceOffset = (startTile + i) * 64;
for (int y = 0; y < 8; y++)
for (int x = 0; x < 8; x++)
{
screen[destinationOffset + y * stride + x] = cachedTileBuffer[sourceOffset++];
}
}
int numPixels = numTiles * 8 * 8;
if (directColor) DirectColorify(screen, numPixels);
else Paletteize(screen, 0, 0, numPixels);
Colorize(screen, 0, numPixels);
}
public void RenderSpriteToScreen(
int* screen,
int stride,
int destx,
int desty, ScreenInfo si,
int spritenum,
ISNESGraphicsDecoder.OAMInfo oamInfo,
int xlimit,
int ylimit,
byte[,] spriteMap)
{
oamInfo ??= new OAMInfo(_api.core.snes_read_oam, si, spritenum);
Size dim = ObjSizes[si.OBSEL_Size, oamInfo.Size ? 1 : 0];
byte[] cachedTileBuffer = cachedTiles[bppArrayIndex[4]];
int baseaddr = oamInfo.Table ? si.OBJTable1Addr : si.OBJTable0Addr;
//TODO - flips of 'undocumented' rectangular oam settings are wrong. probably easy to do right, but we need a test
int bcol = oamInfo.Tile & 0xF;
int brow = (oamInfo.Tile >> 4) & 0xF;
for (int oy = 0; oy < dim.Height; oy++)
for (int ox = 0; ox < dim.Width; ox++)
{
int dy, dx;
if (oamInfo.HFlip)
dx = dim.Width - 1 - ox;
else dx = ox;
if (oamInfo.VFlip)
dy = dim.Height - 1 - oy;
else dy = oy;
dx += destx;
dy += desty;
if (dx >= xlimit || dy >= ylimit || dx < 0 || dy < 0)
continue;
int col = (bcol + (ox >> 3)) & 0xF;
int row = (brow + (oy >> 3)) & 0xF;
int sx = ox & 0x7;
int sy = oy & 0x7;
int addr = baseaddr * 2 + (row * 16 + col) * 64;
addr += sy * 8 + sx;
int dofs = stride * dy + dx;
int color = cachedTileBuffer[addr];
if (spriteMap != null && color == 0)
{
//skip transparent pixels
}
else
{
screen[dofs] = color;
Paletteize(screen, dofs, oamInfo.Palette * 16 + 128, 1);
Colorize(screen, dofs, 1);
if (spriteMap != null) spriteMap[dx, dy] = (byte)spritenum;
}
}
}
public void RenderTilesToScreen(
int* screen,
int stride,
int bpp,
int startcolor,
int startTile,
int numTiles)
{
if (numTiles == -1)
numTiles = 8192 / bpp;
byte[] cachedTileBuffer = cachedTiles[bppArrayIndex[bpp]];
int tilesPerRow = stride / 8;
for (int i = 0; i < numTiles; i++)
{
int targetYPos = i / tilesPerRow * stride * 8;
int targetXPos = i % tilesPerRow * 8;
int destinationOffset = targetYPos + targetXPos;
int sourceOffset = (startTile + i) * 64;
for (int y = 0; y < 8; y++)
for (int x = 0; x < 8; x++)
{
screen[destinationOffset + y * stride + x] = cachedTileBuffer[sourceOffset++];
}
}
int numPixels = numTiles * 8 * 8;
Paletteize(screen, 0, startcolor, numPixels);
Colorize(screen, 0, numPixels);
}
public ScreenInfo ScanScreenInfo()
{
int OBSEL_NameSel = _api.core.snes_peek_logical_register(OBSEL_NAMESEL);
int OBSEL_NameBase = _api.core.snes_peek_logical_register(OBSEL_NAMEBASE);
ScreenInfo screenInfo = new()
{
Mode = _api.core.snes_peek_logical_register(BG_MODE),
Mode1_BG3_Priority = _api.core.snes_peek_logical_register(BG3_PRIORITY) == 1,
SETINI_Mode7ExtBG = _api.core.snes_peek_logical_register(SETINI_MODE7_EXTBG) == 1,
SETINI_HiRes = _api.core.snes_peek_logical_register(SETINI_HIRES) == 1,
SETINI_Overscan = _api.core.snes_peek_logical_register(SETINI_OVERSCAN) == 1,
SETINI_ObjInterlace = _api.core.snes_peek_logical_register(SETINI_OBJ_INTERLACE) == 1,
SETINI_ScreenInterlace = _api.core.snes_peek_logical_register(SETINI_SCREEN_INTERLACE) == 1,
CGWSEL_ColorMask = _api.core.snes_peek_logical_register(CGWSEL_COLORMASK),
CGWSEL_ColorSubMask = _api.core.snes_peek_logical_register(CGWSEL_COLORSUBMASK),
CGWSEL_AddSubMode = _api.core.snes_peek_logical_register(CGWSEL_ADDSUBMODE),
CGWSEL_DirectColor = _api.core.snes_peek_logical_register(CGWSEL_DIRECTCOLOR) == 1,
CGADSUB_AddSub = _api.core.snes_peek_logical_register(CGADDSUB_MODE),
CGADSUB_Half = _api.core.snes_peek_logical_register(CGADDSUB_HALF) == 1,
OBSEL_Size = _api.core.snes_peek_logical_register(OBSEL_SIZE),
OBSEL_NameSel = OBSEL_NameSel,
OBSEL_NameBase = OBSEL_NameBase,
OBJTable0Addr = OBSEL_NameBase << 14,
OBJTable1Addr = ((OBSEL_NameBase << 14) + ((OBSEL_NameSel + 1) << 13)) & 0xFFFF,
OBJ_MainEnabled = _api.core.snes_peek_logical_register(TM_OBJ) == 1,
OBJ_SubEnabled = _api.core.snes_peek_logical_register(TS_OBJ) == 1,
OBJ_MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_OBJ) == 1,
BK_MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_BACKDROP) == 1,
M7HOFS = _api.core.snes_peek_logical_register(M7HOFS),
M7VOFS = _api.core.snes_peek_logical_register(M7VOFS),
M7A = _api.core.snes_peek_logical_register(M7A),
M7B = _api.core.snes_peek_logical_register(M7B),
M7C = _api.core.snes_peek_logical_register(M7C),
M7D = _api.core.snes_peek_logical_register(M7D),
M7X = _api.core.snes_peek_logical_register(M7X),
M7Y = _api.core.snes_peek_logical_register(M7Y),
M7SEL_REPEAT = _api.core.snes_peek_logical_register(M7SEL_REPEAT),
M7SEL_HFLIP = _api.core.snes_peek_logical_register(M7SEL_HFLIP) == 1,
M7SEL_VFLIP = _api.core.snes_peek_logical_register(M7SEL_VFLIP) == 1
};
screenInfo.ObjSizeBounds = ObjSizes[screenInfo.OBSEL_Size, 1];
int square = Math.Max(screenInfo.ObjSizeBounds.Width, screenInfo.ObjSizeBounds.Height);
screenInfo.ObjSizeBoundsSquare = new Size(square, square);
screenInfo.BG.BG1.Bpp = ModeBpps[screenInfo.Mode, 0];
screenInfo.BG.BG2.Bpp = ModeBpps[screenInfo.Mode, 1];
screenInfo.BG.BG3.Bpp = ModeBpps[screenInfo.Mode, 2];
screenInfo.BG.BG4.Bpp = ModeBpps[screenInfo.Mode, 3];
//initial setting of mode type (derived from bpp table.. mode7 bg types will be fixed up later)
for (int i = 1; i <= 4; i++)
screenInfo.BG[i].BGMode = screenInfo.BG[i].Bpp == 0 ? BGMode.Unavailable : BGMode.Text;
screenInfo.BG.BG1.TILESIZE = _api.core.snes_peek_logical_register(BG1_TILESIZE);
screenInfo.BG.BG2.TILESIZE = _api.core.snes_peek_logical_register(BG2_TILESIZE);
screenInfo.BG.BG3.TILESIZE = _api.core.snes_peek_logical_register(BG3_TILESIZE);
screenInfo.BG.BG4.TILESIZE = _api.core.snes_peek_logical_register(BG4_TILESIZE);
screenInfo.BG.BG1.SCSIZE = _api.core.snes_peek_logical_register(BG1_SCSIZE);
screenInfo.BG.BG2.SCSIZE = _api.core.snes_peek_logical_register(BG2_SCSIZE);
screenInfo.BG.BG3.SCSIZE = _api.core.snes_peek_logical_register(BG3_SCSIZE);
screenInfo.BG.BG4.SCSIZE = _api.core.snes_peek_logical_register(BG4_SCSIZE);
screenInfo.BG.BG1.SCADDR = _api.core.snes_peek_logical_register(BG1_SCADDR);
screenInfo.BG.BG2.SCADDR = _api.core.snes_peek_logical_register(BG2_SCADDR);
screenInfo.BG.BG3.SCADDR = _api.core.snes_peek_logical_register(BG3_SCADDR);
screenInfo.BG.BG4.SCADDR = _api.core.snes_peek_logical_register(BG4_SCADDR);
screenInfo.BG.BG1.TDADDR = _api.core.snes_peek_logical_register(BG1_TDADDR);
screenInfo.BG.BG2.TDADDR = _api.core.snes_peek_logical_register(BG2_TDADDR);
screenInfo.BG.BG3.TDADDR = _api.core.snes_peek_logical_register(BG3_TDADDR);
screenInfo.BG.BG4.TDADDR = _api.core.snes_peek_logical_register(BG4_TDADDR);
screenInfo.BG.BG1.MainEnabled = _api.core.snes_peek_logical_register(TM_BG1) == 1;
screenInfo.BG.BG2.MainEnabled = _api.core.snes_peek_logical_register(TM_BG2) == 1;
screenInfo.BG.BG3.MainEnabled = _api.core.snes_peek_logical_register(TM_BG3) == 1;
screenInfo.BG.BG4.MainEnabled = _api.core.snes_peek_logical_register(TM_BG4) == 1;
screenInfo.BG.BG1.SubEnabled = _api.core.snes_peek_logical_register(TS_BG1) == 1;
screenInfo.BG.BG2.SubEnabled = _api.core.snes_peek_logical_register(TS_BG2) == 1;
screenInfo.BG.BG3.SubEnabled = _api.core.snes_peek_logical_register(TS_BG3) == 1;
screenInfo.BG.BG4.SubEnabled = _api.core.snes_peek_logical_register(TS_BG4) == 1;
screenInfo.BG.BG1.MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_BG1) == 1;
screenInfo.BG.BG2.MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_BG2) == 1;
screenInfo.BG.BG3.MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_BG3) == 1;
screenInfo.BG.BG4.MathEnabled = _api.core.snes_peek_logical_register(CGADDSUB_BG4) == 1;
screenInfo.BG.BG1.HOFS = _api.core.snes_peek_logical_register(BG1HOFS);
screenInfo.BG.BG1.VOFS = _api.core.snes_peek_logical_register(BG1VOFS);
screenInfo.BG.BG2.HOFS = _api.core.snes_peek_logical_register(BG2HOFS);
screenInfo.BG.BG2.VOFS = _api.core.snes_peek_logical_register(BG2VOFS);
screenInfo.BG.BG3.HOFS = _api.core.snes_peek_logical_register(BG3HOFS);
screenInfo.BG.BG3.VOFS = _api.core.snes_peek_logical_register(BG3VOFS);
screenInfo.BG.BG4.HOFS = _api.core.snes_peek_logical_register(BG4HOFS);
screenInfo.BG.BG4.VOFS = _api.core.snes_peek_logical_register(BG4VOFS);
for (int i = 1; i <= 4; i++)
{
screenInfo.BG[i].Mode = screenInfo.Mode;
screenInfo.BG[i].TiledataAddr = screenInfo.BG[i].TDADDR << 13;
screenInfo.BG[i].ScreenAddr = screenInfo.BG[i].SCADDR << 9;
}
//fixup irregular things for mode 7
if (screenInfo.Mode == 7)
{
screenInfo.BG.BG1.TiledataAddr = 0;
screenInfo.BG.BG1.ScreenAddr = 0;
screenInfo.BG.BG1.BGMode = screenInfo.CGWSEL_DirectColor ? BGMode.Mode7DC : BGMode.Mode7;
if (screenInfo.SETINI_Mode7ExtBG)
{
screenInfo.BG.BG2.BGMode = BGMode.Mode7Ext;
screenInfo.BG.BG2.Bpp = 7;
screenInfo.BG.BG2.TiledataAddr = 0;
screenInfo.BG.BG2.ScreenAddr = 0;
}
}
//determine which colors each BG could use
switch (screenInfo.Mode)
{
case 0:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 32);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(32, 32);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(64, 32);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(96, 32);
break;
case 1:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(0, 32);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(0, 0);
break;
case 2:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(0, 0);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(0, 0);
break;
case 3:
case 7:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 256);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(0, 0);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(0, 0);
break;
case 4:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 256);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(0, 32);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(0, 0);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(0, 0);
break;
case 5:
case 6:
screenInfo.BG.BG1.PaletteSelection = new PaletteSelection(0, 128);
screenInfo.BG.BG2.PaletteSelection = new PaletteSelection(0, 32);
screenInfo.BG.BG3.PaletteSelection = new PaletteSelection(0, 0);
screenInfo.BG.BG4.PaletteSelection = new PaletteSelection(0, 0);
break;
}
return screenInfo;
}
public void SetBackColor(int snescol)
{
if (snescol == -1)
{
useBackColor = false;
}
else
{
useBackColor = true;
backColor = snescol;
}
}
}
internal class OAMInfo : ISNESGraphicsDecoder.OAMInfo
{
public ushort X { get; }
public byte Y { get; }
public int Tile { get; }
public bool Table { get; }
public int Palette { get; }
public byte Priority { get; }
public bool VFlip { get; }
public bool HFlip { get; }
public bool Size { get; }
public int Address { get; }
public OAMInfo(Func<ushort, byte> readOam, ScreenInfo si, int index)
{
ushort lowaddr = (ushort)(index * 4);
X = readOam(lowaddr++);
Y = readOam(lowaddr++);
byte character = readOam(lowaddr++);
Table = (readOam(lowaddr) & 1) == 1;
Palette = (readOam(lowaddr) >> 1) & 7;
Priority = (byte)((readOam(lowaddr) >> 4) & 3);
HFlip = ((readOam(lowaddr) >> 6) & 1) == 1;
VFlip = ((readOam(lowaddr) >> 7) & 1) == 1;
int highaddr = index / 4;
int shift = (index % 4) * 2;
byte high = readOam((ushort)(512 + highaddr));
bool highX = (high & (1 << shift++)) != 0;
Size = (high & (1 << shift)) != 0;
if (highX) X += 256;
Tile = character + (Table ? 256 : 0);
Address = character * 32 + (Table ? si.OBJTable1Addr : si.OBJTable0Addr);
Address &= 0xFFFF;
}
}
}