//http://wiki.superfamicom.org/snes/show/Backgrounds //http://board.zsnes.com/phpBB3/viewtopic.php?f=10&t=13029&start=75 yoshis island offset per tile demos. and other demos of advanced modes //but we wont worry about offset per tile modes here. //helpful detailed reg list //http://wiki.superfamicom.org/snes/show/Registers using System; namespace BizHawk.Emulation.Cores.Nintendo.SNES { public unsafe class SNESGraphicsDecoder : IDisposable { public class PaletteSelection { public PaletteSelection() { } public PaletteSelection(int start, int size) { this.start = start; this.size = size; } public int start, size; } public struct Dimensions { public Dimensions(int w, int h) { Width = w; Height = h; } public int Width, Height; public override string ToString() { return string.Format("{0}x{1}", Width, Height); } } public enum ScreenSize { AAAA_32x32 = 0, ABAB_64x32 = 1, AABB_32x64 = 2, ABCD_64x64 = 3, Hacky_1x1 = 4, } public static Dimensions SizeInTilesForBGSize(ScreenSize size) { if (size == ScreenSize.Hacky_1x1) return new Dimensions(1, 1); var ret = SizeInBlocksForBGSize(size); ret.Width *= 32; ret.Height *= 32; return ret; } public static Dimensions[,] ObjSizes = new Dimensions[,] { { new Dimensions(8,8), new Dimensions(16,16) }, { new Dimensions(8,8), new Dimensions(32,32) }, { new Dimensions(8,8), new Dimensions(64,64) }, { new Dimensions(16,16), new Dimensions(32,32) }, { new Dimensions(16,16), new Dimensions(64,64) }, { new Dimensions(32,32), new Dimensions(64,64) }, { new Dimensions(16,32), new Dimensions(32,64) }, { new Dimensions(16,32), new Dimensions(32,32) } }; public static Dimensions SizeInBlocksForBGSize(ScreenSize size) { switch (size) { case ScreenSize.AAAA_32x32: return new Dimensions(1, 1); case ScreenSize.ABAB_64x32: return new Dimensions(2, 1); case ScreenSize.AABB_32x64: return new Dimensions(1, 2); case ScreenSize.ABCD_64x64: return new Dimensions(2, 2); default: throw new Exception(); } } public enum BGMode { Unavailable, Text, Mode7, Mode7Ext, Mode7DC, OBJ } /// /// is a BGMode a mode7 type (mode7, mode7ext, mode7DC) /// public static bool BGModeIsMode7Type(BGMode BGMode) { return BGMode == SNESGraphicsDecoder.BGMode.Mode7 || BGMode == SNESGraphicsDecoder.BGMode.Mode7DC || BGMode == SNESGraphicsDecoder.BGMode.Mode7Ext; } /// /// this class is not 'smart' - it wont recompute values for you. it's meant to be read only (we should find some way to protect write access to make that clear) /// public class BGInfo { public BGInfo(int num) { } /// /// what type of BG is it? /// public BGMode BGMode; /// /// is this BGMode a mode7 type (mode7, mode7ext, mode7DC) /// public bool BGModeIsMode7Type { get { return BGModeIsMode7Type(BGMode); } } /// /// Is the layer even enabled? /// public bool Enabled { get { return Bpp != 0; } } /// /// screen and tiledata register values /// public int SCADDR, TDADDR; /// /// SCSIZE register /// public int SCSIZE; /// /// which Mode this BG came from /// public int Mode; /// /// the address of the screen data /// public int ScreenAddr; /// /// the address of the tile data /// public int TiledataAddr; /// /// Screen size (shape, really.) /// public ScreenSize ScreenSize { get { return (ScreenSize)SCSIZE; } } /// /// the BPP of the BG, as derived from the current mode /// public int Bpp; /// /// value of the tilesize register; 1 implies 16x16 tiles /// public int TILESIZE; /// /// enabled on MAIN Screen via $212C /// public bool MainEnabled; /// /// enabled on SUB Screen via $212D /// public bool SubEnabled; /// /// enabled for color math via $2131 /// public bool MathEnabled; /// /// scroll registers /// public int HOFS, VOFS; /// /// TileSize; 8 or 16 /// public int TileSize { get { return TILESIZE == 1 ? 16 : 8; } } /// /// The size of the layer, in tiles /// public Dimensions ScreenSizeInTiles { get { if (BGMode == SNESGraphicsDecoder.BGMode.Text) return SizeInTilesForBGSize(ScreenSize); else return new Dimensions(128, 128); } } /// /// The size of the layer, in pixels. This has factored in the selection of 8x8 or 16x16 tiles /// public Dimensions ScreenSizeInPixels { get { return new Dimensions(ScreenSizeInTiles.Width * TileSize, ScreenSizeInTiles.Height * TileSize); } } /// /// returns information about what colors could possibly be used for this bg /// public PaletteSelection PaletteSelection; } public class BGInfos { BGInfo[] bgs = new BGInfo[4] { new BGInfo(1), new BGInfo(2), new BGInfo(3), new BGInfo(4) }; public BGInfo BG1 { get { return bgs[0]; } } public BGInfo BG2 { get { return bgs[1]; } } public BGInfo BG3 { get { return bgs[2]; } } public BGInfo BG4 { get { return bgs[3]; } } public BGInfo this[int index] { get { return bgs[index - 1]; } } } public class ModeInfo { /// /// the mode number, i.e. Mode 7 /// public int MODE; } public class OAMInfo { public int Index { private set; get; } public int X { private set; get; } public int Y { private set; get; } public int Tile { private set; get; } public int Name { private set; get; } public int Table { private set; get; } public int Palette { private set; get; } public int Priority { private set; get; } public bool VFlip { private set; get; } public bool HFlip { private set; get; } public int Size { private set; get; } /// /// tiledata address /// public int Address { private set; get; } public OAMInfo(SNESGraphicsDecoder dec, ScreenInfo si, int num) { Index = num; int lowaddr = num*4; X = dec.oam[lowaddr++]; Y = dec.oam[lowaddr++]; Name = dec.oam[lowaddr++]; Table = dec.oam[lowaddr] & 1; Palette = (dec.oam[lowaddr]>>1) & 7; Priority = (dec.oam[lowaddr] >> 4) & 3; HFlip = ((dec.oam[lowaddr] >> 6) & 1)==1; VFlip = ((dec.oam[lowaddr] >> 7) & 1) == 1; int highaddr = num / 4; int shift = (num % 4) * 2; int high = dec.oam[512+highaddr]; high >>= shift; int x = high & 1; high >>= 1; Size = high & 1; X |= (x << 8); X = (X << 23) >> 23; Tile = Table*256 + Name; Address = 32 * Tile; if (Tile < 256) Address += si.OBJTable0Addr; else Address += si.OBJTable1Addr - (256 * 32); Address &= 0xFFFF; } } public class ScreenInfo { public Dimensions ObjSizeBounds; public Dimensions ObjSizeBoundsSquare; public BGInfos BG = new BGInfos(); public ModeInfo Mode = new ModeInfo(); public bool Mode1_BG3_Priority { private set; get; } public bool SETINI_Mode7ExtBG { private set; get; } public bool SETINI_HiRes { private set; get; } public bool SETINI_Overscan { private set; get; } public bool SETINI_ObjInterlace { private set; get; } public bool SETINI_ScreenInterlace { private set; get; } public int CGWSEL_ColorMask { private set; get; } public int CGWSEL_ColorSubMask { private set; get; } public int CGWSEL_AddSubMode { private set; get; } public bool CGWSEL_DirectColor { private set; get; } public int CGADSUB_AddSub { private set; get; } public bool CGADSUB_Half { private set; get; } public int OBSEL_Size { private set; get; } public int OBSEL_NameSel { private set; get; } public int OBSEL_NameBase { private set; get; } public int OBJTable0Addr { private set; get; } public int OBJTable1Addr { private set; get; } public bool OBJ_MainEnabled { private set; get; } public bool OBJ_SubEnabled { private set; get; } public bool OBJ_MathEnabled { private set; get; } public bool BK_MathEnabled { private set; get; } public int M7HOFS { private set; get; } public int M7VOFS { private set; get; } public int M7A { private set; get; } public int M7B { private set; get; } public int M7C { private set; get; } public int M7D { private set; get; } public int M7X { private set; get; } public int M7Y { private set; get; } public int M7SEL_REPEAT { private set; get; } public bool M7SEL_HFLIP { private set; get; } public bool M7SEL_VFLIP { private set; get; } public static ScreenInfo GetScreenInfo(LibsnesApi api) { var si = new ScreenInfo(); si.Mode1_BG3_Priority = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_PRIORITY) == 1; si.OBSEL_Size = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_SIZE); si.OBSEL_NameSel = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_NAMESEL); si.OBSEL_NameBase = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_NAMEBASE); si.ObjSizeBounds = ObjSizes[si.OBSEL_Size,1]; int square = Math.Max(si.ObjSizeBounds.Width, si.ObjSizeBounds.Height); si.ObjSizeBoundsSquare = new Dimensions(square, square); si.OBJTable0Addr = si.OBSEL_NameBase << 14; si.OBJTable1Addr = (si.OBJTable0Addr + ((si.OBSEL_NameSel + 1) << 13)) & 0xFFFF; si.SETINI_Mode7ExtBG = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_MODE7_EXTBG) == 1; si.SETINI_HiRes = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_HIRES) == 1; si.SETINI_Overscan = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_OVERSCAN) == 1; si.SETINI_ObjInterlace = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_OBJ_INTERLACE) == 1; si.SETINI_ScreenInterlace = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_SCREEN_INTERLACE) == 1; si.CGWSEL_ColorMask = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_COLORMASK); si.CGWSEL_ColorSubMask = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_COLORSUBMASK); si.CGWSEL_AddSubMode = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_ADDSUBMODE); si.CGWSEL_DirectColor = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_DIRECTCOLOR) == 1; si.CGADSUB_AddSub = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_MODE); si.CGADSUB_Half = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_HALF) == 1; si.OBJ_MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_OBJ) == 1; si.OBJ_SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_OBJ) == 1; si.OBJ_MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_OBJ) == 1; si.BK_MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BACKDROP) == 1; si.Mode.MODE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG_MODE); si.BG.BG1.Bpp = ModeBpps[si.Mode.MODE, 0]; si.BG.BG2.Bpp = ModeBpps[si.Mode.MODE, 1]; si.BG.BG3.Bpp = ModeBpps[si.Mode.MODE, 2]; si.BG.BG4.Bpp = ModeBpps[si.Mode.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++) si.BG[i].BGMode = si.BG[i].Bpp == 0 ? BGMode.Unavailable : BGMode.Text; si.BG.BG1.TILESIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1_TILESIZE); si.BG.BG2.TILESIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2_TILESIZE); si.BG.BG3.TILESIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_TILESIZE); si.BG.BG4.TILESIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4_TILESIZE); si.BG.BG1.SCSIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1_SCSIZE); si.BG.BG2.SCSIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2_SCSIZE); si.BG.BG3.SCSIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_SCSIZE); si.BG.BG4.SCSIZE = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4_SCSIZE); si.BG.BG1.SCADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1_SCADDR); si.BG.BG2.SCADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2_SCADDR); si.BG.BG3.SCADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_SCADDR); si.BG.BG4.SCADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4_SCADDR); si.BG.BG1.TDADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1_TDADDR); si.BG.BG2.TDADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2_TDADDR); si.BG.BG3.TDADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_TDADDR); si.BG.BG4.TDADDR = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4_TDADDR); si.BG.BG1.MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_BG1) == 1; si.BG.BG2.MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_BG2) == 1; si.BG.BG3.MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_BG3) == 1; si.BG.BG4.MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_BG4) == 1; si.BG.BG1.SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_BG1) == 1; si.BG.BG2.SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_BG2) == 1; si.BG.BG3.SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_BG3) == 1; si.BG.BG4.SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_BG4) == 1; si.BG.BG1.MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BG1) == 1; si.BG.BG2.MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BG2) == 1; si.BG.BG3.MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BG3) == 1; si.BG.BG4.MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BG4) == 1; si.BG.BG1.HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1HOFS); si.BG.BG1.VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG1VOFS); si.BG.BG2.HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2HOFS); si.BG.BG2.VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG2VOFS); si.BG.BG3.HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3HOFS); si.BG.BG3.VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3VOFS); si.BG.BG4.HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4HOFS); si.BG.BG4.VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG4VOFS); si.M7HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7HOFS); si.M7VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7VOFS); si.M7A = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7A); si.M7B = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7B); si.M7C = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7C); si.M7D = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7D); si.M7X = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7X); si.M7Y = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7Y); si.M7Y = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7Y); si.M7SEL_REPEAT = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_REPEAT); si.M7SEL_HFLIP = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_HFLIP)!=0; si.M7SEL_VFLIP = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_VFLIP)!=0; for (int i = 1; i <= 4; i++) { si.BG[i].Mode = si.Mode.MODE; si.BG[i].TiledataAddr = si.BG[i].TDADDR << 13; si.BG[i].ScreenAddr = si.BG[i].SCADDR << 9; } //fixup irregular things for mode 7 if (si.Mode.MODE == 7) { si.BG.BG1.TiledataAddr = 0; si.BG.BG1.ScreenAddr = 0; if (si.CGWSEL_DirectColor) { si.BG.BG1.BGMode = BGMode.Mode7DC; } else si.BG.BG1.BGMode = BGMode.Mode7; if (si.SETINI_Mode7ExtBG) { si.BG.BG2.BGMode = BGMode.Mode7Ext; si.BG.BG2.Bpp = 7; si.BG.BG2.TiledataAddr = 0; si.BG.BG2.ScreenAddr = 0; } } //determine which colors each BG could use switch (si.Mode.MODE) { case 0: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 32); si.BG.BG2.PaletteSelection = new PaletteSelection(32, 32); si.BG.BG3.PaletteSelection = new PaletteSelection(64, 32); si.BG.BG4.PaletteSelection = new PaletteSelection(96, 32); break; case 1: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 32); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 2: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 3: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 256); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 4: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 256); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 32); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 5: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 32); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 6: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 32); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; case 7: si.BG.BG1.PaletteSelection = new PaletteSelection(0, 256); si.BG.BG2.PaletteSelection = new PaletteSelection(0, 128); si.BG.BG3.PaletteSelection = new PaletteSelection(0, 0); si.BG.BG4.PaletteSelection = new PaletteSelection(0, 0); break; } return si; } } static int[,] ModeBpps = new[,] { {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}, {8,7,0,0} }; public ScreenInfo ScanScreenInfo() { return ScreenInfo.GetScreenInfo(api); } //the same basic color table that libsnes uses to convert from snes 555 to rgba32 static int[] directColorTable = new int[256]; //8bpp gfx -> rgb555 static SNESGraphicsDecoder() { //make directColorTable for (int i = 0; i < 256; i++) { int r = i & 7; int g = (i >> 3) & 7; int b = (i >> 6) & 3; r <<= 2; g <<= 2; b <<= 3; int color = (b << 10) | (g << 5) | r; directColorTable[i] = color; } } int[] colortable; public byte* vram, oam; public ushort* cgram, vram16; LibsnesApi api; public SNESGraphicsDecoder(LibsnesApi api, SnesColors.ColorType pal) { this.api = api; colortable = SnesColors.GetLUT(pal); IntPtr block = (IntPtr)api.QUERY_get_memory_data(LibsnesApi.SNES_MEMORY.VRAM); vram = (byte*)block; vram16 = (ushort*)block; block = (IntPtr)api.QUERY_get_memory_data(LibsnesApi.SNES_MEMORY.CGRAM); cgram = (ushort*)block; block = (IntPtr)api.QUERY_get_memory_data(LibsnesApi.SNES_MEMORY.OAM); oam = (byte*)block; } public void Dispose() { //todo - unhook from api? } public struct TileEntry { public ushort tilenum; public byte palette; public TileEntryFlags flags; public int address; } public enum TileEntryFlags : byte { None = 0, Priority = 1, Horz = 2, Vert = 4, } /// /// decodes a mode7 BG. youll still need to paletteize and colorize it. /// public void DecodeMode7BG(int* screen, int stride, bool extBg) { int[] tileCache = _tileCache[extBg?17:7]; 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, pix=src; py < 8; py++) { for (int px = 0; px < 8; px++, pix++) { int dst = (ty * 8 + py) * stride + (tx * 8 + px); int srcData = tileCache[pix]; screen[dst] = srcData; } } } } } /// /// returns a tilemap which might be resized into 8x8 physical tiles if the 16x16 logical tilesize is specified /// //TileEntry[] AdaptTilemap(TileEntry[] map8x8, int tilesWide, int tilesTall, int tilesize) //{ // if (tilesize == 8) return map8x8; // int numTiles = tilesWide * tilesTall; // var ret = new TileEntry[numTiles * 4]; // for(int y=0;y /// decodes a BG. youll still need to paletteize and colorize it. /// someone else has to take care of calculating the starting color from the mode and layer number. /// public void DecodeBG(int* screen, int stride, TileEntry[] map, int tiledataBaseAddr, ScreenSize size, int bpp, int tilesize, int paletteStart) { int ncolors = 1 << bpp; int[] tileBuf = new int[16*16]; var dims = SizeInTilesForBGSize(size); int count8x8 = tilesize / 8; int tileSizeBytes = 8 * bpp; int baseTileNum = tiledataBaseAddr / tileSizeBytes; int[] tileCache = _tileCache[bpp]; int tileCacheMask = tileCache.Length - 1; int screenWidth = dims.Width * count8x8 * 8; 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]; int tileNum = te.tilenum + tx + ty * 16 + baseTileNum; int srcOfs = tileNum * 64; for (int i = 0, y = 0; y < 8; y++) { for (int x = 0; x < 8; x++, i++) { 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 && usingUserBackColor) { } else { color += te.palette * ncolors; color += paletteStart; } screen[dstOfs] = color; } } } } } } } public TileEntry[] FetchMode7Tilemap() { TileEntry[] buf = new TileEntry[128*128]; for (int ty = 0, tidx = 0; ty < 128; ty++) { for (int tx = 0; tx < 128; tx++, tidx++) { int tileEntry = vram[tidx * 2]; buf[tidx].address = tidx * 2; buf[tidx].tilenum = (ushort)tileEntry; //palette and flags are ok defaulting to 0 } } return buf; } /// /// fetches a tilemap. this is simple; apparently only the screen size (shape) is a factor (not the tile size) /// public TileEntry[] FetchTilemap(int addr, ScreenSize size) { var blockDims = SizeInBlocksForBGSize(size); int blocksw = blockDims.Width; int blocksh = blockDims.Height; int width = blockDims.Width * 32; int height = blockDims.Height * 32; TileEntry[] buf = new TileEntry[width*height]; for (int by = 0; by < blocksh; by++) { for (int bx = 0; bx < blocksw; bx++) { for (int y = 0; y < 32; y++) { for (int x = 0; x < 32; x++) { int idx = (by * 32 + y) * width + 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; } //TODO - paletteize and colorize could be in one step, for more speed 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; if (entry == 0 && usingUserBackColor) color = userBackColor; else color = cgram[startcolor + entry] & 0x7FFF; //unfortunate that we have to mask this here.. maybe do it in a more optimal spot when we port it to c++ buf[offset + i] = color; } } public void Colorize(int* buf, int offset, int numpixels) { for (int i = 0; i < numpixels; i++) { buf[offset + i] = colortable[491520 + buf[offset + i]]; } } int[][] _tileCache = new int[18][]; bool usingUserBackColor = false; int userBackColor; public void SetBackColor(int snescol) { usingUserBackColor = true; userBackColor = snescol; } /// /// Caches all tiles at the 2bpp, 4bpp, and 8bpp decoded states. /// we COULD defer this til we need it, you know. sort of a cool idea, not too hard /// public void CacheTiles() { //generate 2bpp tiles int numtiles = 65536/8/2; int[] tiles = new int[8 * 8 * numtiles]; _tileCache[2] = tiles; for (int i = 0; i < numtiles; i++) { Decode8x8x2bpp(tiles, i * 64, 16 * i, 8); } //merge 2bpp tiles into 4bpp and 8bpp CacheTiles_Merge(2); CacheTiles_Merge(4); CacheTilesMode7(); CacheTilesMode7ExtBg(); } public void CacheTilesMode7() { int numtiles = 256; int[] tiles = new int[8 * 8 * numtiles]; _tileCache[7] = tiles; for (int i = 0, j=0; i < numtiles; i++) { for (int y = 0; y < 8; y++) for (int x = 0; x < 8; x++, j++) tiles[j] = vram[j * 2 + 1]; } } //not being used.. do we need it? public int[] GetCachedTile(int bpp, int tilenum) { int[] ret = new int[8 * 8]; int idx = tilenum * 64; for (int i = 0; i < 64; i++) ret[i] = _tileCache[bpp][idx + i]; return ret; } void CacheTilesMode7ExtBg() { int numtiles = 256; int[] tiles = new int[8 * 8 * numtiles]; _tileCache[17] = tiles; int[] mode7tiles = _tileCache[7]; int numPixels = numtiles*8*8; for (int i = 0; i < numPixels; i++) tiles[i] = mode7tiles[i] & 0x7F; } /// /// merges one type of tiles with another to create the higher-order bitdepth. /// TODO - templateize this when we change it to c++ /// void CacheTiles_Merge(int fromBpp) { int toBpp = fromBpp * 2; int shift = fromBpp; int numtiles = 8192 / toBpp; int[] tilesDst = new int[8 * 8 * numtiles]; _tileCache[toBpp] = tilesDst; int[] tilesSrc = _tileCache[fromBpp]; for (int i = 0; i < numtiles; i++) { int srcAddr = i * 128; int dstAddr = i * 64; for (int p = 0; p < 64; p++) { int tileA = tilesSrc[srcAddr + p]; int tileB = tilesSrc[srcAddr + p + 64]; tilesDst[dstAddr + p] = tileA | (tileB << shift); } } } /// /// decodes an 8x8 tile to a linear framebuffer type thing. fundamental unit of tile decoding. /// public void Decode8x8x2bpp(int[] buf, int offset, int addr, int stride=8) { for (int y = 0; y < 8; y++) { byte val = vram[addr + 1]; for (int x = 0; x < 8; x++) buf[offset + y * stride + x] = val >> (7 - x) & 1; val = vram[addr + 0]; for (int x = 0; x < 8; x++) buf[offset + y * stride + x] = (buf[offset + y * stride + x] << 1) | (val >> (7 - x) & 1); addr += 2; } } /// /// renders the mode7 tiles to a screen with the predefined size. /// public void RenderMode7TilesToScreen(int* screen, int stride, bool ext, bool directColor, int tilesWide = 16, int startTile = 0, int numTiles = 256) { int[] tilebuf = _tileCache[ext?17:7]; for (int i = 0; i < numTiles; i++) { int tnum = startTile + i; //TODO - mask by possible number of tiles? only in OBJ rendering mode? int ty = i / tilesWide; int tx = i % tilesWide; int dstOfs = (ty * 8) * stride + tx * 8; int srcOfs = tnum * 64; for (int y = 0, p = 0; y < 8; y++) { for (int x = 0; x < 8; x++, p++) { screen[dstOfs + y * stride + x] = tilebuf[srcOfs + p]; } } } int numPixels = numTiles * 8 * 8; if (directColor) DirectColorify(screen, numPixels); else Paletteize(screen, 0, 0, numPixels); Colorize(screen, 0, numPixels); } /// /// renders the tiles to a screen of the crudely specified size. /// we might need 16x16 unscrambling and some other perks here eventually. /// provide a start color to use as the basis for the palette /// public void RenderTilesToScreen(int* screen, int tilesWide, int tilesTall, int stride, int bpp, int startcolor, int startTile = 0, int numTiles = -1, bool descramble16 = false) { if (numTiles == -1) numTiles = 8192 / bpp; int[] tilebuf = _tileCache[bpp]; for (int i = 0; i < numTiles; i++) { int tnum = startTile + i; //TODO - mask by possible number of tiles? only in OBJ rendering mode? int ty = i / tilesWide; int tx = i % tilesWide; int dstOfs = (ty * 8) * stride + tx * 8; int srcOfs = tnum * 64; for (int y = 0, p = 0; y < 8; y++) for (int x = 0; x < 8; x++, p++) { screen[dstOfs + y * stride + x] = tilebuf[srcOfs + p]; } } int numPixels = numTiles * 8 * 8; Paletteize(screen, 0, startcolor, numPixels); Colorize(screen, 0, numPixels); } public void RenderSpriteToScreen(int* screen, int stride, int destx, int desty, ScreenInfo si, int spritenum, OAMInfo oam = null, int xlimit = 1024, int ylimit = 1024, byte[,] spriteMap = null) { var dims = new[] { SNESGraphicsDecoder.ObjSizes[si.OBSEL_Size, 0], SNESGraphicsDecoder.ObjSizes[si.OBSEL_Size, 1] }; if(oam == null) oam = new OAMInfo(this, si, spritenum); var dim = dims[oam.Size]; int[] tilebuf = _tileCache[4]; int baseaddr; if (oam.Table == 0) baseaddr = si.OBJTable0Addr; else baseaddr = si.OBJTable1Addr; //TODO - flips of 'undocumented' rectangular oam settings are wrong. probably easy to do right, but we need a test int bcol = oam.Tile & 0xF; int brow = (oam.Tile >> 4) & 0xF; for(int oy=0;oy=xlimit || dy>=ylimit || dx<0 || dy<0) continue; int col = (bcol + (x >> 3)) & 0xF; int row = (brow + (y >> 3)) & 0xF; int sx = x & 0x7; int sy = y & 0x7; int addr = baseaddr*2 + (row * 16 + col) * 64; addr += sy * 8 + sx; int dofs = stride*dy+dx; int color = tilebuf[addr]; if (spriteMap != null && color == 0) { //skip transparent pixels } else { screen[dofs] = color; Paletteize(screen, dofs, oam.Palette * 16 + 128, 1); Colorize(screen, dofs, 1); if (spriteMap != null) spriteMap[dx, dy] = (byte)spritenum; } } } public int Colorize(int rgb555) { //skip to max luminance in the palette table return colortable[491520 + rgb555]; } /// /// returns the current palette, transformed into an int array, for more convenience /// public int[] GetPalette() { var ret = new int[256]; for (int i = 0; i < 256; i++) ret[i] = cgram[i] & 0x7FFF; return ret; } public void DirectColorify(int* screen, int numPixels) { for (int i = 0; i < numPixels; i++) screen[i] = directColorTable[screen[i]]; } } //class SNESGraphicsDecoder } //namespace