diff --git a/Assets/dll/bsnes.wbx.zst b/Assets/dll/bsnes.wbx.zst index 3c077e995c..f301e84e75 100644 Binary files a/Assets/dll/bsnes.wbx.zst and b/Assets/dll/bsnes.wbx.zst differ diff --git a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs index d46ce26929..10160052db 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1708,7 +1708,6 @@ namespace BizHawk.Client.EmuHawk private void SnesSubMenu_DropDownOpened(object sender, EventArgs e) { SNESControllerConfigurationMenuItem.Enabled = MovieSession.Movie.NotActive(); - SnesGfxDebuggerMenuItem.Enabled = true; } private DialogResult OpenOldBSNESGamepadSettingsDialog(ISettingsAdapter settable) diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 81424884ef..01fe35b1d8 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -2042,21 +2042,17 @@ namespace BizHawk.Client.EmuHawk case VSystemID.Raw.SNES when Emulator is LibsnesCore { IsSGB: true }: // doesn't use "SGB" sysID SNESSubMenu.Text = "&SGB"; SNESSubMenu.Visible = true; - SnesGfxDebuggerMenuItem.Visible = true; break; case VSystemID.Raw.SNES when Emulator is LibsnesCore { IsSGB: false }: SNESSubMenu.Text = "&SNES"; SNESSubMenu.Visible = true; - SnesGfxDebuggerMenuItem.Visible = true; break; case VSystemID.Raw.SNES when Emulator is BsnesCore bsnesCore: SNESSubMenu.Text = bsnesCore.IsSGB ? "&SGB" : "&SNES"; - SnesGfxDebuggerMenuItem.Visible = false; SNESSubMenu.Visible = true; break; case VSystemID.Raw.SNES when Emulator is SubBsnesCore subBsnesCore: SNESSubMenu.Text = subBsnesCore.IsSGB ? "&SGB" : "&SNES"; - SnesGfxDebuggerMenuItem.Visible = false; SNESSubMenu.Visible = true; break; default: diff --git a/src/BizHawk.Client.EmuHawk/tools/SNES/SNESGraphicsDebugger.cs b/src/BizHawk.Client.EmuHawk/tools/SNES/SNESGraphicsDebugger.cs index 8509c5b0b4..9068363414 100644 --- a/src/BizHawk.Client.EmuHawk/tools/SNES/SNESGraphicsDebugger.cs +++ b/src/BizHawk.Client.EmuHawk/tools/SNES/SNESGraphicsDebugger.cs @@ -41,7 +41,7 @@ namespace BizHawk.Client.EmuHawk private readonly List displayTypeItems = new List(); [RequiredService] - private LibsnesCore Emulator { get; set; } + private IBSNESForGfxDebugger Emulator { get; set; } [ConfigPersist] public bool UseUserBackdropColor @@ -63,7 +63,7 @@ namespace BizHawk.Client.EmuHawk displayTypeItems.Add(new DisplayTypeItem("Sprites", eDisplayType.Sprites)); displayTypeItems.Add(new DisplayTypeItem("OBJ", eDisplayType.OBJ)); - + displayTypeItems.Add(new DisplayTypeItem("BG1 Screen", eDisplayType.BG1)); displayTypeItems.Add(new DisplayTypeItem("BG2 Screen", eDisplayType.BG2)); displayTypeItems.Add(new DisplayTypeItem("BG3 Screen", eDisplayType.BG3)); @@ -85,6 +85,7 @@ namespace BizHawk.Client.EmuHawk var paletteTypeItems = new List { + // must be in same order as enum new PaletteTypeItem("BizHawk", SnesColors.ColorType.BizHawk), new PaletteTypeItem("bsnes", SnesColors.ColorType.BSNES), new PaletteTypeItem("Snes9X", SnesColors.ColorType.Snes9x) @@ -104,12 +105,14 @@ namespace BizHawk.Client.EmuHawk UserBackdropColor = -1; } - private LibsnesCore currentSnesCore; + private IBSNESForGfxDebugger currentSnesCore; + protected override void OnClosed(EventArgs e) { base.OnClosed(e); - currentSnesCore?.ScanlineHookManager.Unregister(this); + currentSnesCore?.ScanlineHookManager?.Unregister(this); currentSnesCore = null; + gd = null; } private string FormatBpp(int bpp) @@ -168,11 +171,12 @@ namespace BizHawk.Client.EmuHawk { if (currentSnesCore != Emulator) { - currentSnesCore?.ScanlineHookManager.Unregister(this); + currentSnesCore?.ScanlineHookManager?.Unregister(this); } if (currentSnesCore != Emulator && Emulator != null) { + gd = null; suppression = true; comboPalette.SelectedValue = Emulator.CurrPalette; RefreshBGENCheckStatesFromConfig(); @@ -184,9 +188,9 @@ namespace BizHawk.Client.EmuHawk if (currentSnesCore != null) { if (Visible && checkScanlineControl.Checked) - currentSnesCore.ScanlineHookManager.Register(this, ScanlineHook); + currentSnesCore.ScanlineHookManager?.Register(this, ScanlineHook); else - currentSnesCore.ScanlineHookManager.Unregister(this); + currentSnesCore.ScanlineHookManager?.Unregister(this); } } @@ -200,7 +204,7 @@ namespace BizHawk.Client.EmuHawk } } - private SNESGraphicsDecoder gd; + private ISNESGraphicsDecoder gd; private SNESGraphicsDecoder.ScreenInfo si; private SNESGraphicsDecoder.TileEntry[] map; private readonly byte[,] spriteMap = new byte[256, 224]; @@ -208,14 +212,10 @@ namespace BizHawk.Client.EmuHawk private void RegenerateData() { - gd?.Dispose(); - gd = null; if (currentSnesCore == null) return; - gd = NewDecoder(); + gd ??= NewDecoder(); using (gd.EnterExit()) { - if (checkBackdropColor.Checked) - gd.SetBackColor(DecodeWinformsColorToSNES(pnBackdropColor.BackColor)); gd.CacheTiles(); si = gd.ScanScreenInfo(); } @@ -247,7 +247,7 @@ namespace BizHawk.Client.EmuHawk txtScreenCGADSUB_AddSub_Descr.Text = si.CGADSUB_AddSub == 1 ? "SUB" : "ADD"; txtScreenCGADSUB_Half.Checked = si.CGADSUB_Half; - txtModeBits.Text = si.Mode.MODE.ToString(); + txtModeBits.Text = si.Mode.ToString(); txtScreenBG1Bpp.Text = FormatBpp(si.BG.BG1.Bpp); txtScreenBG2Bpp.Text = FormatBpp(si.BG.BG2.Bpp); txtScreenBG3Bpp.Text = FormatBpp(si.BG.BG3.Bpp); @@ -303,7 +303,7 @@ namespace BizHawk.Client.EmuHawk checkMathBG3.Checked = si.BG.BG3.MathEnabled; checkMathBG4.Checked = si.BG.BG4.MathEnabled; - if (si.Mode.MODE == 1 && si.Mode1_BG3_Priority) + if (si.Mode == 1 && si.Mode1_BG3_Priority) { lblBG3.ForeColor = Color.Red; if (toolTip1.GetToolTip(lblBG3) != "Mode 1 BG3 priority toggle bit of $2105 is SET") @@ -370,7 +370,7 @@ namespace BizHawk.Client.EmuHawk for (int y = 0; y < 224; y++) for (int x = 0; x < 256; x++) spriteMap[x, y] = 0xFF; for(int i=127;i>=0;i--) { - var oam = new SNESGraphicsDecoder.OAMInfo(gd, si, i); + var oam = gd.CreateOAMInfo(si, i); gd.RenderSpriteToScreen(pixelptr, stride / 4, oam.X, oam.Y, si, i, oam, 256, 224, spriteMap); } } @@ -379,24 +379,24 @@ namespace BizHawk.Client.EmuHawk allocate(128, 256); int startTile; startTile = si.OBJTable0Addr / 32; - gd.RenderTilesToScreen(pixelptr, 16, 16, stride / 4, 4, currPaletteSelection.start, startTile, 256, true); + gd.RenderTilesToScreen(pixelptr, stride / 4, 4, currPaletteSelection.start, startTile, 256); startTile = si.OBJTable1Addr / 32; - gd.RenderTilesToScreen(pixelptr + (stride/4*8*16), 16, 16, stride / 4, 4, currPaletteSelection.start, startTile, 256, true); + gd.RenderTilesToScreen(pixelptr + (stride/4*8*16), stride / 4, 4, currPaletteSelection.start, startTile, 256); } if (selection == eDisplayType.Tiles2bpp) { allocate(512, 512); - gd.RenderTilesToScreen(pixelptr, 64, 64, stride / 4, 2, currPaletteSelection.start); + gd.RenderTilesToScreen(pixelptr, stride / 4, 2, currPaletteSelection.start); } if (selection == eDisplayType.Tiles4bpp) { allocate(512, 512); - gd.RenderTilesToScreen(pixelptr, 64, 32, stride / 4, 4, currPaletteSelection.start); + gd.RenderTilesToScreen(pixelptr, stride / 4, 4, currPaletteSelection.start); } if (selection == eDisplayType.Tiles8bpp) { allocate(256, 256); - gd.RenderTilesToScreen(pixelptr, 32, 32, stride / 4, 8, currPaletteSelection.start); + gd.RenderTilesToScreen(pixelptr, stride / 4, 8, currPaletteSelection.start); } if (selection == eDisplayType.TilesMode7) { @@ -431,7 +431,7 @@ namespace BizHawk.Client.EmuHawk bool DirectColor = si.CGWSEL_DirectColor && bg.Bpp == 8; //any exceptions? int numPixels = 0; //TODO - could use BGMode property on BG... too much chaos to deal with it now - if (si.Mode.MODE == 7) + if (si.Mode == 7) { bool mode7 = bgnum == 1; bool mode7extbg = (bgnum == 2 && si.SETINI_Mode7ExtBG); @@ -499,7 +499,7 @@ namespace BizHawk.Client.EmuHawk default: throw new InvalidOperationException(); } } - + private class DisplayTypeItem { public eDisplayType Type { get; } @@ -533,6 +533,9 @@ namespace BizHawk.Client.EmuHawk private void SNESGraphicsDebugger_Load(object sender, EventArgs e) { + currentSnesCore = Emulator; + gd = NewDecoder(); + if (UserBackdropColor != -1) { pnBackdropColor.BackColor = Color.FromArgb(UserBackdropColor); @@ -540,6 +543,7 @@ namespace BizHawk.Client.EmuHawk if (checkBackdropColor.Checked) { SyncBackdropColor(); + gd.SetBackColor(DecodeWinformsColorToSNES(pnBackdropColor.BackColor)); } UpdateToolsLoadstate(); @@ -639,16 +643,14 @@ namespace BizHawk.Client.EmuHawk ret.start = 0; return ret; } - + ret.size = 1 << bpp; ret.start = colorSelection & (~(ret.size - 1)); return ret; } - private SNESGraphicsDecoder NewDecoder() - { - return currentSnesCore != null ? new SNESGraphicsDecoder(currentSnesCore.Api, currentSnesCore.CurrPalette) : null; - } + private ISNESGraphicsDecoder NewDecoder() + => currentSnesCore?.CreateGraphicsDecoder(); private void RenderPalette() { @@ -713,13 +715,13 @@ namespace BizHawk.Client.EmuHawk private void UpdateOBJDetails() { if (currObjDataState == null) return; - var oam = new SNESGraphicsDecoder.OAMInfo(gd, si, currObjDataState.Number); + var oam = gd.CreateOAMInfo(si, currObjDataState.Number); txtObjNumber.Text = $"#${currObjDataState.Number:X2}"; txtObjCoord.Text = $"({oam.X}, {oam.Y})"; cbObjHFlip.Checked = oam.HFlip; cbObjVFlip.Checked = oam.VFlip; - cbObjLarge.Checked = oam.Size == 1; - txtObjSize.Text = SNESGraphicsDecoder.ObjSizes[si.OBSEL_Size, oam.Size].ToString(); + cbObjLarge.Checked = oam.Size; + txtObjSize.Text = SNESGraphicsDecoder.ObjSizes[si.OBSEL_Size, oam.Size ? 1 : 0].ToString(); txtObjPriority.Text = oam.Priority.ToString(); txtObjPalette.Text = oam.Palette.ToString(); txtObjPaletteMemo.Text = $"${oam.Palette * 16 + 128:X2}"; @@ -874,7 +876,7 @@ namespace BizHawk.Client.EmuHawk if (tp == tpPalette) groupFreeze.Text = "Freeze - Color"; if (tp == tpTile) groupFreeze.Text = "Freeze - Tile"; if (tp == tpOBJ) groupFreeze.Text = "Freeze - OBJ"; - + groupFreeze.ResumeLayout(); Win32Imports.SendMessage(groupFreeze.Handle, 11, (IntPtr)1, IntPtr.Zero); //WM_SETREDRAW true @@ -981,9 +983,9 @@ namespace BizHawk.Client.EmuHawk { //render an obj tile int tile = currTileDataState.Address / 32; - gd.RenderTilesToScreen((int*)bmpdata.Scan0, 1, 1, bmpdata.Stride / 4, bpp, currPaletteSelection.start, tile, 1); + gd.RenderTilesToScreen((int*)bmpdata.Scan0, bmpdata.Stride / 4, bpp, currPaletteSelection.start, tile, 1); } - else gd.RenderTilesToScreen((int*)bmpdata.Scan0, 1, 1, bmpdata.Stride / 4, bpp, currPaletteSelection.start, currTileDataState.Tile, 1); + else gd.RenderTilesToScreen((int*)bmpdata.Scan0, bmpdata.Stride / 4, bpp, currPaletteSelection.start, currTileDataState.Tile, 1); bmp.UnlockBits(bmpdata); viewerTile.SetBitmap(bmp); @@ -1056,7 +1058,7 @@ namespace BizHawk.Client.EmuHawk { int ox = px / si.ObjSizeBounds.Width; int oy = py / si.ObjSizeBounds.Height; - + if (!0.RangeTo(7).Contains(ox) || !0.RangeTo(15).Contains(oy)) return; int objNum = oy * 8 + ox; @@ -1195,6 +1197,10 @@ namespace BizHawk.Client.EmuHawk private void checkBackdropColor_CheckedChanged(object sender, EventArgs e) { SyncBackdropColor(); + if (checkBackdropColor.Checked) + gd?.SetBackColor(DecodeWinformsColorToSNES(pnBackdropColor.BackColor)); + else + gd?.SetBackColor(); RegenerateData(); } @@ -1265,13 +1271,21 @@ namespace BizHawk.Client.EmuHawk if (suppression) return; var pal = (SnesColors.ColorType)comboPalette.SelectedValue; Console.WriteLine("set {0}", pal); - var s = Emulator.GetSettings(); - s.Palette = pal.ToString(); - currentSnesCore?.PutSettings(s); + try + { + currentSnesCore?.SetPalette(pal); + } + catch (NotImplementedException) + { + comboPalette.Enabled = false; + } RegenerateData(); - RenderView(); - RenderPalette(); - RenderTileView(); + using (gd.EnterExit()) + { + RenderView(); + RenderPalette(); + RenderTileView(); + } } private void RefreshBGENCheckStatesFromConfig() diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs index 02329db4af..f79206e66a 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs @@ -33,6 +33,8 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES [BizImport(CallingConvention.Cdecl)] public abstract void* snes_get_memory_region(int id, out int size, out int wordSize); [BizImport(CallingConvention.Cdecl)] + public abstract int snes_peek_logical_register(BsnesApi.SNES_REGISTER register); + [BizImport(CallingConvention.Cdecl)] public abstract byte snes_bus_read(uint address); [BizImport(CallingConvention.Cdecl)] public abstract void snes_bus_write(uint address, byte value); diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs index 08d294f94d..70b07219a7 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi_Enums.cs @@ -2,6 +2,91 @@ { public partial class BsnesApi { + public enum SNES_REGISTER { + //$2105 + BG_MODE, + BG3_PRIORITY, + BG1_TILESIZE, + BG2_TILESIZE, + BG3_TILESIZE, + BG4_TILESIZE, + //$2107 + BG1_SCADDR, + BG1_SCSIZE, + //$2108 + BG2_SCADDR, + BG2_SCSIZE, + //$2109, + BG3_SCADDR, + BG3_SCSIZE, + //$210A + BG4_SCADDR, + BG4_SCSIZE, + //$210B + BG1_TDADDR, + BG2_TDADDR, + //$210C + BG3_TDADDR, + BG4_TDADDR, + //$2133 SETINI + SETINI_MODE7_EXTBG, + SETINI_HIRES, + SETINI_OVERSCAN, + SETINI_OBJ_INTERLACE, + SETINI_SCREEN_INTERLACE, + //$2130 CGWSEL + CGWSEL_COLORMASK, + CGWSEL_COLORSUBMASK, + CGWSEL_ADDSUBMODE, + CGWSEL_DIRECTCOLOR, + //$2101 OBSEL + OBSEL_NAMEBASE, + OBSEL_NAMESEL, + OBSEL_SIZE, + //$2131 CGADSUB + CGADDSUB_MODE, + CGADDSUB_HALF, + CGADDSUB_BG4, + CGADDSUB_BG3, + CGADDSUB_BG2, + CGADDSUB_BG1, + CGADDSUB_OBJ, + CGADDSUB_BACKDROP, + //$212C TM + TM_BG1, + TM_BG2, + TM_BG3, + TM_BG4, + TM_OBJ, + //$212D TM + TS_BG1, + TS_BG2, + TS_BG3, + TS_BG4, + TS_OBJ, + //Mode7 regs + M7SEL_REPEAT, + M7SEL_HFLIP, + M7SEL_VFLIP, + M7A, + M7B, + M7C, + M7D, + M7X, + M7Y, + //BG scroll regs + BG1HOFS, + BG1VOFS, + BG2HOFS, + BG2VOFS, + BG3HOFS, + BG3VOFS, + BG4HOFS, + BG4VOFS, + M7HOFS, + M7VOFS + } + public enum SNES_MEMORY { CARTRIDGE_RAM, @@ -18,7 +103,7 @@ WRAM, APURAM, VRAM, - // OAM, // needs some work in the core probably? or we return an objects pointer + OBJECTS, CGRAM } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs index e905981f58..9ea3abea55 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.ISettable.cs @@ -9,6 +9,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES return _settings.Clone(); } + SNES.IBSNESForGfxDebugger.SettingsObj SNES.IBSNESForGfxDebugger.GetSettings() + => GetSettings(); + public SnesSyncSettings GetSyncSettings() { return _syncSettings.Clone(); @@ -21,6 +24,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES return PutSettingsDirtyBits.None; } + void SNES.IBSNESForGfxDebugger.PutSettings(SNES.IBSNESForGfxDebugger.SettingsObj s) + => PutSettings((SnesSettings) s); + public PutSettingsDirtyBits PutSyncSettings(SnesSyncSettings o) { bool ret = o.LeftPort != _syncSettings.LeftPort @@ -41,7 +47,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES private SnesSettings _settings; private SnesSyncSettings _syncSettings; - public class SnesSettings + public class SnesSettings : SNES.IBSNESForGfxDebugger.SettingsObj { public bool ShowBG1_0 { get; set; } = true; public bool ShowBG2_0 { get; set; } = true; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index 687166192b..9e6e8b7d08 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -4,6 +4,7 @@ using BizHawk.Common; using BizHawk.Emulation.Common; using BizHawk.Emulation.Common.Base_Implementations; using BizHawk.Emulation.Cores.Components.W65816; +using BizHawk.Emulation.Cores.Nintendo.SNES; // http://wiki.superfamicom.org/snes/show/Backgrounds @@ -11,7 +12,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES { [PortedCore(CoreNames.Bsnes115, "bsnes team", "v115+", "https://github.com/bsnes-emu/bsnes")] [ServiceNotApplicable(new[] { typeof(IDriveLight) })] - public unsafe partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable + public unsafe partial class BsnesCore : IEmulator, IDebuggable, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ISettable, IBSNESForGfxDebugger { [CoreConstructor(VSystemID.Raw.SGB)] [CoreConstructor(VSystemID.Raw.SNES)] @@ -232,6 +233,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES } } + public SnesColors.ColorType CurrPalette => SnesColors.ColorType.BSNES; + + public void SetPalette(SnesColors.ColorType colorType) + { + if (colorType != SnesColors.ColorType.BSNES) + throw new NotImplementedException("This core does not currently support different palettes."); + } + + public ISNESGraphicsDecoder CreateGraphicsDecoder() => new SNESGraphicsDecoder(Api); + + public ScanlineHookManager ScanlineHookManager => null; + private void snes_video_refresh(ushort* data, int width, int height, int pitch) { int widthMultiplier = 1; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SNESGraphicsDecoder.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SNESGraphicsDecoder.cs new file mode 100644 index 0000000000..2eec906e1d --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SNESGraphicsDecoder.cs @@ -0,0 +1,642 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +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 + { + [StructLayout(LayoutKind.Sequential)] + public struct Object // size: 10 bytes; equivalent to the c++ version + { + public readonly ushort x; + public readonly byte y; + public readonly byte character; + public readonly bool nameSelect; + public readonly bool vflip; + public readonly bool hflip; + public readonly byte priority; + public readonly byte palette; + public readonly bool size; + } + + private readonly BsnesApi _api; + private readonly byte* vram; // waterbox pointer, ALWAYS access with EnterExit() + private readonly Object* objects; // 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() + { + const int a = 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; + + palette[color] = a << 24 | b >> 8 << 16 | g >> 8 << 8 | r >> 8 << 0; + } + } + + 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 _); + objects = (Object*)api.core.snes_get_memory_region((int)BsnesApi.SNES_MEMORY.OBJECTS, 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(objects, 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 oam, + int xlimit, + int ylimit, + byte[,] spriteMap) + { + oam ??= new OAMInfo(objects, si, spritenum); + Size dim = ObjSizes[si.OBSEL_Size, oam.Size ? 1 : 0]; + + byte[] cachedTileBuffer = cachedTiles[bppArrayIndex[4]]; + + int baseaddr = oam.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 = 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 + (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, oam.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 unsafe OAMInfo(SNESGraphicsDecoder.Object* objects, ScreenInfo si, int index) + { + X = objects[index].x; + Y = objects[index].y; + Table = objects[index].nameSelect; + Palette = objects[index].palette; + Priority = objects[index].priority; + HFlip = objects[index].hflip; + VFlip = objects[index].vflip; + Size = objects[index].size; + byte character = objects[index].character; + Tile = character + (Table ? 256 : 0); + Address = character * 32 + (Table ? si.OBJTable1Addr : si.OBJTable0Addr); + Address &= 0xFFFF; + } + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SubBsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SubBsnesCore.cs index 74769a5477..8b0622b466 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SubBsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/SubBsnesCore.cs @@ -1,4 +1,5 @@ using BizHawk.Emulation.Common; +using BizHawk.Emulation.Cores.Nintendo.SNES; namespace BizHawk.Emulation.Cores.Nintendo.BSNES { @@ -28,18 +29,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES ser.Register(_bsnesCore.ServiceProvider.GetService()); ser.Register(_bsnesCore.ServiceProvider.GetService()); ser.Register(_bsnesCore.ServiceProvider.GetService()); + ser.Register(_bsnesCore.ServiceProvider.GetService()); if (IsSGB) - { - // board info is only set in SGB mode - ser.Register(_bsnesCore.ServiceProvider.GetService()); + { + // board info is only set in SGB mode + ser.Register(_bsnesCore.ServiceProvider.GetService()); } ServiceProvider = ser; } private readonly BsnesCore _bsnesCore; - public bool IsSGB => _bsnesCore.IsSGB; - + public bool IsSGB => _bsnesCore.IsSGB; + public IEmulatorServiceProvider ServiceProvider { get; } public ControllerDefinition ControllerDefinition => _bsnesCore.ControllerDefinition; @@ -64,20 +66,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES } if (resetSignal) - { - _bsnesCore.Api.core.snes_reset(); - } - - if (powerSignal) - { - _bsnesCore.Api.core.snes_power(); + { + _bsnesCore.Api.core.snes_reset(); + } + + if (powerSignal) + { + _bsnesCore.Api.core.snes_power(); } } else - { - // run the core for one (sub-)frame - bool subFrameRequested = controller.IsPressed("Subframe"); - framePassed = _bsnesCore.Api.core.snes_run(subFrameRequested); + { + // run the core for one (sub-)frame + bool subFrameRequested = controller.IsPressed("Subframe"); + framePassed = _bsnesCore.Api.core.snes_run(subFrameRequested); } if (!framePassed) _bsnesCore.IsLagFrame = false; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/IBSNESForGfxDebugger.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/IBSNESForGfxDebugger.cs new file mode 100644 index 0000000000..5ae9d94a92 --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/IBSNESForGfxDebugger.cs @@ -0,0 +1,48 @@ +#nullable enable + +using BizHawk.Emulation.Common; + +namespace BizHawk.Emulation.Cores.Nintendo.SNES +{ + public interface IBSNESForGfxDebugger : ISpecializedEmulatorService + { + public interface SettingsObj + { + bool ShowBG1_0 { get; set; } + + bool ShowBG1_1 { get; set; } + + bool ShowBG2_0 { get; set; } + + bool ShowBG2_1 { get; set; } + + bool ShowBG3_0 { get; set; } + + bool ShowBG3_1 { get; set; } + + bool ShowBG4_0 { get; set; } + + bool ShowBG4_1 { get; set; } + + bool ShowOBJ_0 { get; set; } + + bool ShowOBJ_1 { get; set; } + + bool ShowOBJ_2 { get; set; } + + bool ShowOBJ_3 { get; set; } + } + + SnesColors.ColorType CurrPalette { get; } + + ScanlineHookManager? ScanlineHookManager { get; } + + ISNESGraphicsDecoder CreateGraphicsDecoder(); + + SettingsObj GetSettings(); + + void PutSettings(SettingsObj s); + + void SetPalette(SnesColors.ColorType palette); + } +} diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.ISettable.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.ISettable.cs index 83f3ad070a..55544c18ab 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.ISettable.cs @@ -9,6 +9,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES return _settings.Clone(); } + IBSNESForGfxDebugger.SettingsObj IBSNESForGfxDebugger.GetSettings() + => GetSettings(); + public SnesSyncSettings GetSyncSettings() { return _syncSettings.Clone(); @@ -26,6 +29,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES return PutSettingsDirtyBits.None; } + void IBSNESForGfxDebugger.PutSettings(IBSNESForGfxDebugger.SettingsObj s) + => PutSettings((SnesSettings) s); + public PutSettingsDirtyBits PutSyncSettings(SnesSyncSettings o) { bool ret = o.LeftPort != _syncSettings.LeftPort @@ -40,7 +46,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES private SnesSettings _settings; private SnesSyncSettings _syncSettings; - public class SnesSettings + public class SnesSettings : IBSNESForGfxDebugger.SettingsObj { public bool ShowBG1_0 { get; set; } = true; public bool ShowBG2_0 { get; set; } = true; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs index 408ffa0bac..59196d6ee4 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/LibsnesCore.cs @@ -20,7 +20,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES [PortedCore(CoreNames.Bsnes, "byuu", "v87", "https://github.com/bsnes-emu/bsnes/tree/386ac87d21d14fafd15162d480a111209c9955ba")] [ServiceNotApplicable(new[] { typeof(IDriveLight) })] public unsafe partial class LibsnesCore : IEmulator, IVideoProvider, ISaveRam, IStatable, IInputPollable, IRegionable, ICodeDataLogger, - IDebuggable, ISettable + IDebuggable, ISettable, IBSNESForGfxDebugger { [CoreConstructor(VSystemID.Raw.SGB)] [CoreConstructor(VSystemID.Raw.SNES)] @@ -235,7 +235,17 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public SnesColors.ColorType CurrPalette { get; private set; } - public MyScanlineHookManager ScanlineHookManager { get; } + public void SetPalette(SnesColors.ColorType palette) + { + var s = GetSettings(); + s.Palette = Enum.GetName(typeof(SnesColors.ColorType), palette); + PutSettings(s); + } + + public ISNESGraphicsDecoder CreateGraphicsDecoder() + => new SNESGraphicsDecoder(Api, CurrPalette); + + public ScanlineHookManager ScanlineHookManager { get; } public class MyScanlineHookManager : ScanlineHookManager { @@ -364,14 +374,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES } } - private void SetPalette(SnesColors.ColorType pal) - { - CurrPalette = pal; - int[] tmp = SnesColors.GetLUT(pal); - fixed (int* p = &tmp[0]) - Api.QUERY_set_color_lut((IntPtr)p); - } - private void ReadHook(uint addr) { if (MemoryCallbacks.HasReads) @@ -633,7 +635,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES private void RefreshPalette() { - SetPalette((SnesColors.ColorType)Enum.Parse(typeof(SnesColors.ColorType), _settings.Palette, false)); + CurrPalette = (SnesColors.ColorType)Enum.Parse(typeof(SnesColors.ColorType), _settings.Palette, false); + int[] tmp = SnesColors.GetLUT(CurrPalette); + fixed (int* p = &tmp[0]) + Api.QUERY_set_color_lut((IntPtr)p); } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/SNESGraphicsDecoder.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/SNESGraphicsDecoder.cs index 238fddf312..098c952a24 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/SNESGraphicsDecoder.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES/SNESGraphicsDecoder.cs @@ -10,10 +10,100 @@ using BizHawk.Common; using System; +using System.Drawing; namespace BizHawk.Emulation.Cores.Nintendo.SNES { - public unsafe class SNESGraphicsDecoder : IDisposable, IMonitor + public unsafe interface ISNESGraphicsDecoder : IMonitor + { + public interface OAMInfo + { + ushort X { get; } + + byte Y { get; } + + int Tile { get; } + + bool Table { get; } + + int Palette { get; } + + byte Priority { get; } + + bool VFlip { get; } + + bool HFlip { get; } + + bool Size { get; } + + int Address { get; } + } + + void CacheTiles(); + + int Colorize(int rgb555); + + void Colorize(int* buf, int offset, int numpixels); + + OAMInfo CreateOAMInfo(SNESGraphicsDecoder.ScreenInfo si, int num); + + void DecodeBG( + int* screen, + int stride, + SNESGraphicsDecoder.TileEntry[] map, + int tiledataBaseAddr, + SNESGraphicsDecoder.ScreenSize size, + int bpp, + int tilesize, + int paletteStart); + + void DecodeMode7BG(int* screen, int stride, bool extBg); + + void DirectColorify(int* screen, int numPixels); + + SNESGraphicsDecoder.TileEntry[] FetchMode7Tilemap(); + + SNESGraphicsDecoder.TileEntry[] FetchTilemap(int addr, SNESGraphicsDecoder.ScreenSize size); + + int[] GetPalette(); + + void Paletteize(int* buf, int offset, int startcolor, int numpixels); + + void RenderMode7TilesToScreen( + int* screen, + int stride, + bool ext, + bool directColor, + int tilesWide = 16, + int startTile = 0, + int numTiles = 256); + + void RenderSpriteToScreen( + int* screen, + int stride, + int destx, + int desty, + SNESGraphicsDecoder.ScreenInfo si, + int spritenum, + OAMInfo oam = null, + int xlimit = 1024, + int ylimit = 1024, + byte[,] spriteMap = null); + + void RenderTilesToScreen( + int* screen, + int stride, + int bpp, + int startcolor, + int startTile = 0, + int numTiles = -1); + + SNESGraphicsDecoder.ScreenInfo ScanScreenInfo(); + + void SetBackColor(int snescol = -1); + } + + public unsafe class SNESGraphicsDecoder : ISNESGraphicsDecoder { public class PaletteSelection { @@ -48,16 +138,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES return ret; } - public static Dimensions[,] ObjSizes = + public static Size[,] ObjSizes = { - { 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) } + { new(8,8), new(16,16) }, + { new(8,8), new(32,32) }, + { new(8,8), new(64,64) }, + { new(16,16), new(32,32) }, + { new(16,16), new(64,64) }, + { new(32,32), new(64,64) }, + { new(16,32), new(32,64) }, + { new(16,32), new(32,32) } }; public static Dimensions SizeInBlocksForBGSize(ScreenSize size) @@ -199,27 +289,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public BGInfo this[int index] => bgs[index - 1]; } - public class ModeInfo - { - /// - /// the mode number, i.e. Mode 7 - /// - public int MODE; - } - - public class OAMInfo + public class OAMInfo : ISNESGraphicsDecoder.OAMInfo { public int Index { get; } - public int X { get; } - public int Y { get; } + public ushort X { get; } + public byte Y { get; } public int Tile { get; } - public int Name { get; } - public int Table { get; } + public bool Table { get; } public int Palette { get; } - public int Priority { get; } + public byte Priority { get; } public bool VFlip { get; } public bool HFlip { get; } - public int Size { get; } + public bool Size { get; } /// /// tiledata address @@ -229,15 +310,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES 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; + byte name = dec.oam[lowaddr++]; + Table = (dec.oam[lowaddr] & 1) == 1; Palette = (dec.oam[lowaddr]>>1) & 7; - Priority = (dec.oam[lowaddr] >> 4) & 3; - HFlip = ((dec.oam[lowaddr] >> 6) & 1)==1; + Priority = (byte)((dec.oam[lowaddr] >> 4) & 3); + HFlip = ((dec.oam[lowaddr] >> 6) & 1) == 1; VFlip = ((dec.oam[lowaddr] >> 7) & 1) == 1; int highaddr = num / 4; @@ -246,13 +327,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES high >>= shift; int x = high & 1; high >>= 1; - Size = high & 1; - X |= (x << 8); - X = (X << 23) >> 23; + Size = (high & 1) != 0; + X = (ushort)(X | (x << 8)); - Tile = Table*256 + Name; + Tile = name + (Table ? 256 : 0); Address = 32 * Tile; - + if (Tile < 256) Address += si.OBJTable0Addr; else @@ -264,240 +344,53 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public class ScreenInfo { - public Dimensions ObjSizeBounds; - public Dimensions ObjSizeBoundsSquare; + public Size ObjSizeBounds; + public Size ObjSizeBoundsSquare; public BGInfos BG = new BGInfos(); - public ModeInfo Mode = new ModeInfo(); + public int Mode { get; init; } + public bool Mode1_BG3_Priority { get; init; } - public bool Mode1_BG3_Priority { get; private set; } + public bool SETINI_Mode7ExtBG { get; init; } + public bool SETINI_HiRes { get; init; } + public bool SETINI_Overscan { get; init; } + public bool SETINI_ObjInterlace { get; init; } + public bool SETINI_ScreenInterlace { get; init; } - public bool SETINI_Mode7ExtBG { get; private set; } - public bool SETINI_HiRes { get; private set; } - public bool SETINI_Overscan { get; private set; } - public bool SETINI_ObjInterlace { get; private set; } - public bool SETINI_ScreenInterlace { get; private set; } + public int CGWSEL_ColorMask { get; init; } + public int CGWSEL_ColorSubMask { get; init; } + public int CGWSEL_AddSubMode { get; init; } + public bool CGWSEL_DirectColor { get; init; } + public int CGADSUB_AddSub { get; init; } + public bool CGADSUB_Half { get; init; } - public int CGWSEL_ColorMask { get; private set; } - public int CGWSEL_ColorSubMask { get; private set; } - public int CGWSEL_AddSubMode { get; private set; } - public bool CGWSEL_DirectColor { get; private set; } - public int CGADSUB_AddSub { get; private set; } - public bool CGADSUB_Half { get; private set; } + public int OBSEL_Size { get; init; } + public int OBSEL_NameSel { get; init; } + public int OBSEL_NameBase { get; init; } - public int OBSEL_Size { get; private set; } - public int OBSEL_NameSel { get; private set; } - public int OBSEL_NameBase { get; private set; } + public int OBJTable0Addr { get; init; } + public int OBJTable1Addr { get; init; } - public int OBJTable0Addr { get; private set; } - public int OBJTable1Addr { get; private set; } + public bool OBJ_MainEnabled { get; init; } + public bool OBJ_SubEnabled { get; init; } + public bool OBJ_MathEnabled { get; init; } + public bool BK_MathEnabled { get; init; } - public bool OBJ_MainEnabled { get; private set; } - public bool OBJ_SubEnabled { get; private set; } - public bool OBJ_MathEnabled { get; private set; } - public bool BK_MathEnabled { get; private set; } - - public int M7HOFS { get; private set; } - public int M7VOFS { get; private set; } - public int M7A { get; private set; } - public int M7B { get; private set; } - public int M7C { get; private set; } - public int M7D { get; private set; } - public int M7X { get; private set; } - public int M7Y { get; private set; } - public int M7SEL_REPEAT { get; private set; } - public bool M7SEL_HFLIP { get; private set; } - public bool M7SEL_VFLIP { get; private set; } - - 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; - } + public int M7HOFS { get; init; } + public int M7VOFS { get; init; } + public int M7A { get; init; } + public int M7B { get; init; } + public int M7C { get; init; } + public int M7D { get; init; } + public int M7X { get; init; } + public int M7Y { get; init; } + public int M7SEL_REPEAT { get; init; } + public bool M7SEL_HFLIP { get; init; } + public bool M7SEL_VFLIP { get; init; } } - private static readonly int[,] ModeBpps = { + public static readonly int[,] ModeBpps = { {2,2,2,2}, {4,4,2,0}, {4,4,0,0}, @@ -512,7 +405,182 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public ScreenInfo ScanScreenInfo() { - return ScreenInfo.GetScreenInfo(api); + int OBSEL_NameSel = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_NAMESEL); + int OBSEL_NameBase = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_NAMEBASE); + + var si = new ScreenInfo + { + Mode = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG_MODE), + Mode1_BG3_Priority = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.BG3_PRIORITY) == 1, + OBSEL_Size = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.OBSEL_SIZE), + OBSEL_NameSel = OBSEL_NameSel, + OBSEL_NameBase = OBSEL_NameBase, + OBJTable0Addr = OBSEL_NameBase << 14, + OBJTable1Addr = ((OBSEL_NameBase << 14) + ((OBSEL_NameSel + 1) << 13)) & 0xFFFF, + SETINI_Mode7ExtBG = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_MODE7_EXTBG) == 1, + SETINI_HiRes = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_HIRES) == 1, + SETINI_Overscan = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_OVERSCAN) == 1, + SETINI_ObjInterlace = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_OBJ_INTERLACE) == 1, + SETINI_ScreenInterlace = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.SETINI_SCREEN_INTERLACE) == 1, + CGWSEL_ColorMask = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_COLORMASK), + CGWSEL_ColorSubMask = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_COLORSUBMASK), + CGWSEL_AddSubMode = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_ADDSUBMODE), + CGWSEL_DirectColor = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGWSEL_DIRECTCOLOR) == 1, + CGADSUB_AddSub = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_MODE), + CGADSUB_Half = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_HALF) == 1, + OBJ_MainEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TM_OBJ) == 1, + OBJ_SubEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.TS_OBJ) == 1, + OBJ_MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_OBJ) == 1, + BK_MathEnabled = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.CGADSUB_BACKDROP) == 1, + M7HOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7HOFS), + M7VOFS = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7VOFS), + M7A = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7A), + M7B = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7B), + M7C = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7C), + M7D = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7D), + M7X = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7X), + M7Y = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7Y), + M7SEL_REPEAT = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_REPEAT), + M7SEL_HFLIP = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_HFLIP)!=0, + M7SEL_VFLIP = api.QUERY_peek_logical_register(LibsnesApi.SNES_REG.M7SEL_VFLIP)!=0, + }; + + si.ObjSizeBounds = ObjSizes[si.OBSEL_Size,1]; + int square = Math.Max(si.ObjSizeBounds.Width, si.ObjSizeBounds.Height); + si.ObjSizeBoundsSquare = new Size(square, square); + + si.BG.BG1.Bpp = ModeBpps[si.Mode, 0]; + si.BG.BG2.Bpp = ModeBpps[si.Mode, 1]; + si.BG.BG3.Bpp = ModeBpps[si.Mode, 2]; + si.BG.BG4.Bpp = ModeBpps[si.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); + + for (int i = 1; i <= 4; i++) + { + si.BG[i].Mode = si.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 == 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) + { + 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; } //the same basic color table that libsnes uses to convert from snes 555 to rgba32 @@ -565,11 +633,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES public int address; } + [Flags] public enum TileEntryFlags : byte { None = 0, Priority = 1, Horz = 2, Vert = 4, } + public ISNESGraphicsDecoder.OAMInfo CreateOAMInfo(ScreenInfo si, int num) + => new OAMInfo(this, si, num); + /// /// decodes a mode7 BG. youll still need to paletteize and colorize it. /// @@ -650,7 +722,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES { int mapIndex = mty * dims.Width + mtx; var te = map[mapIndex]; - + //apply metatile flipping int tnx = tx, tny = ty; if (tilesize == 16) @@ -766,13 +838,20 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES private readonly int[][] _tileCache = new int[18][]; - private bool usingUserBackColor = false; + private bool usingUserBackColor; private int userBackColor; public void SetBackColor(int snescol) { - usingUserBackColor = true; - userBackColor = snescol; + if (snescol == -1) + { + usingUserBackColor = false; + } + else + { + usingUserBackColor = true; + userBackColor = snescol; + } } /// @@ -909,11 +988,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES /// 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) + public void RenderTilesToScreen(int* screen, int stride, int bpp, int startcolor, int startTile = 0, int numTiles = -1) { if (numTiles == -1) numTiles = 8192 / bpp; int[] tilebuf = _tileCache[bpp]; + int tilesWide = stride / 8; for (int i = 0; i < numTiles; i++) { int tnum = startTile + i; @@ -935,20 +1015,14 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES } - 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) + public void RenderSpriteToScreen(int* screen, int stride, int destx, int desty, ScreenInfo si, int spritenum, ISNESGraphicsDecoder.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]; + oam ??= new OAMInfo(this, si, spritenum); + var dim = ObjSizes[si.OBSEL_Size, oam.Size ? 1 : 0]; int[] tilebuf = _tileCache[4]; - int baseaddr; - if (oam.Table == 0) - baseaddr = si.OBJTable0Addr; - else - baseaddr = si.OBJTable1Addr; + int baseaddr = oam.Table ? si.OBJTable1Addr : si.OBJTable0Addr; //TODO - flips of 'undocumented' rectangular oam settings are wrong. probably easy to do right, but we need a test diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp index acebe87e44..cc3ffd533e 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp @@ -11,8 +11,7 @@ using namespace SuperFamicom; //zero 05-sep-2012 -// currently unused; was only used in the graphics debugger as far as i can see -int snes_peek_logical_register(int reg) +EXPORT int snes_peek_logical_register(SNES_REGISTER reg) { if (SuperFamicom::system.fastPPU()) switch(reg) @@ -21,88 +20,88 @@ int snes_peek_logical_register(int reg) // 3-may-2021 above timestamp left for reference because i like it //$2105 - case SNES_REG_BG_MODE: return ppufast.io.bgMode; - case SNES_REG_BG3_PRIORITY: return ppufast.io.bgPriority; - case SNES_REG_BG1_TILESIZE: return ppufast.io.bg1.tileSize; - case SNES_REG_BG2_TILESIZE: return ppufast.io.bg2.tileSize; - case SNES_REG_BG3_TILESIZE: return ppufast.io.bg3.tileSize; - case SNES_REG_BG4_TILESIZE: return ppufast.io.bg4.tileSize; + case BG_MODE: return ppufast.io.bgMode; + case BG3_PRIORITY: return ppufast.io.bgPriority; + case BG1_TILESIZE: return ppufast.io.bg1.tileSize; + case BG2_TILESIZE: return ppufast.io.bg2.tileSize; + case BG3_TILESIZE: return ppufast.io.bg3.tileSize; + case BG4_TILESIZE: return ppufast.io.bg4.tileSize; //$2107 - case SNES_REG_BG1_SCADDR: return ppufast.io.bg1.screenAddress >> 8; - case SNES_REG_BG1_SCSIZE: return ppufast.io.bg1.screenSize; + case BG1_SCADDR: return ppufast.io.bg1.screenAddress >> 8; + case BG1_SCSIZE: return ppufast.io.bg1.screenSize; //$2108 - case SNES_REG_BG2_SCADDR: return ppufast.io.bg2.screenAddress >> 8; - case SNES_REG_BG2_SCSIZE: return ppufast.io.bg2.screenSize; + case BG2_SCADDR: return ppufast.io.bg2.screenAddress >> 8; + case BG2_SCSIZE: return ppufast.io.bg2.screenSize; //$2109 - case SNES_REG_BG3_SCADDR: return ppufast.io.bg3.screenAddress >> 8; - case SNES_REG_BG3_SCSIZE: return ppufast.io.bg3.screenSize; + case BG3_SCADDR: return ppufast.io.bg3.screenAddress >> 8; + case BG3_SCSIZE: return ppufast.io.bg3.screenSize; //$210A - case SNES_REG_BG4_SCADDR: return ppufast.io.bg4.screenAddress >> 8; - case SNES_REG_BG4_SCSIZE: return ppufast.io.bg4.screenSize; + case BG4_SCADDR: return ppufast.io.bg4.screenAddress >> 8; + case BG4_SCSIZE: return ppufast.io.bg4.screenSize; //$210B - case SNES_REG_BG1_TDADDR: return ppufast.io.bg1.tiledataAddress >> 12; - case SNES_REG_BG2_TDADDR: return ppufast.io.bg2.tiledataAddress >> 12; + case BG1_TDADDR: return ppufast.io.bg1.tiledataAddress >> 12; + case BG2_TDADDR: return ppufast.io.bg2.tiledataAddress >> 12; //$210C - case SNES_REG_BG3_TDADDR: return ppufast.io.bg3.tiledataAddress >> 12; - case SNES_REG_BG4_TDADDR: return ppufast.io.bg4.tiledataAddress >> 12; + case BG3_TDADDR: return ppufast.io.bg3.tiledataAddress >> 12; + case BG4_TDADDR: return ppufast.io.bg4.tiledataAddress >> 12; //$2133 SETINI - case SNES_REG_SETINI_MODE7_EXTBG: return ppufast.io.extbg; - case SNES_REG_SETINI_HIRES: return ppufast.io.pseudoHires; - case SNES_REG_SETINI_OVERSCAN: return ppufast.io.overscan; - case SNES_REG_SETINI_OBJ_INTERLACE: return ppufast.io.obj.interlace; - case SNES_REG_SETINI_SCREEN_INTERLACE: return ppufast.io.interlace; + case SETINI_MODE7_EXTBG: return ppufast.io.extbg; + case SETINI_HIRES: return ppufast.io.pseudoHires; + case SETINI_OVERSCAN: return ppufast.io.overscan; + case SETINI_OBJ_INTERLACE: return ppufast.io.obj.interlace; + case SETINI_SCREEN_INTERLACE: return ppufast.io.interlace; //$2130 CGWSEL - case SNES_REG_CGWSEL_COLORMASK: return ppufast.io.col.window.aboveMask; - case SNES_REG_CGWSEL_COLORSUBMASK: return ppufast.io.col.window.belowMask; - case SNES_REG_CGWSEL_ADDSUBMODE: return ppufast.io.col.blendMode; - case SNES_REG_CGWSEL_DIRECTCOLOR: return ppufast.io.col.directColor; + case CGWSEL_COLORMASK: return ppufast.io.col.window.aboveMask; + case CGWSEL_COLORSUBMASK: return ppufast.io.col.window.belowMask; + case CGWSEL_ADDSUBMODE: return ppufast.io.col.blendMode; + case CGWSEL_DIRECTCOLOR: return ppufast.io.col.directColor; //$2101 OBSEL - case SNES_REG_OBSEL_NAMEBASE: return ppufast.io.obj.tiledataAddress >> 13; // TODO: figure out why these shifts are only in specific places - case SNES_REG_OBSEL_NAMESEL: return ppufast.io.obj.nameselect; - case SNES_REG_OBSEL_SIZE: return ppufast.io.obj.baseSize; + case OBSEL_NAMEBASE: return ppufast.io.obj.tiledataAddress >> 13; + case OBSEL_NAMESEL: return ppufast.io.obj.nameselect; + case OBSEL_SIZE: return ppufast.io.obj.baseSize; //$2131 CGADDSUB //enum { BG1 = 0, BG2 = 1, BG3 = 2, BG4 = 3, OAM = 4, BACK = 5, COL = 5 }; - case SNES_REG_CGADDSUB_BG1: return ppufast.io.col.enable[PPUfast::Source::BG1]; - case SNES_REG_CGADDSUB_BG2: return ppufast.io.col.enable[PPUfast::Source::BG2]; - case SNES_REG_CGADDSUB_BG3: return ppufast.io.col.enable[PPUfast::Source::BG3]; - case SNES_REG_CGADDSUB_BG4: return ppufast.io.col.enable[PPUfast::Source::BG4]; - case SNES_REG_CGADDSUB_OBJ: return ppufast.io.col.enable[PPUfast::Source::OBJ2]; - case SNES_REG_CGADDSUB_BACKDROP: return ppufast.io.col.enable[PPUfast::Source::COL]; - case SNES_REG_CGADDSUB_HALF: return ppufast.io.col.halve; - case SNES_REG_CGADDSUB_MODE: return ppufast.io.col.mathMode; + case CGADDSUB_BG1: return ppufast.io.col.enable[PPUfast::Source::BG1]; + case CGADDSUB_BG2: return ppufast.io.col.enable[PPUfast::Source::BG2]; + case CGADDSUB_BG3: return ppufast.io.col.enable[PPUfast::Source::BG3]; + case CGADDSUB_BG4: return ppufast.io.col.enable[PPUfast::Source::BG4]; + case CGADDSUB_OBJ: return ppufast.io.col.enable[PPUfast::Source::OBJ2]; + case CGADDSUB_BACKDROP: return ppufast.io.col.enable[PPUfast::Source::COL]; + case CGADDSUB_HALF: return ppufast.io.col.halve; + case CGADDSUB_MODE: return ppufast.io.col.mathMode; //$212C TM - case SNES_REG_TM_BG1: return ppufast.io.bg1.aboveEnable; - case SNES_REG_TM_BG2: return ppufast.io.bg2.aboveEnable; - case SNES_REG_TM_BG3: return ppufast.io.bg3.aboveEnable; - case SNES_REG_TM_BG4: return ppufast.io.bg4.aboveEnable; - case SNES_REG_TM_OBJ: return ppufast.io.obj.aboveEnable; + case TM_BG1: return ppufast.io.bg1.aboveEnable; + case TM_BG2: return ppufast.io.bg2.aboveEnable; + case TM_BG3: return ppufast.io.bg3.aboveEnable; + case TM_BG4: return ppufast.io.bg4.aboveEnable; + case TM_OBJ: return ppufast.io.obj.aboveEnable; //$212D TS - case SNES_REG_TS_BG1: return ppufast.io.bg1.belowEnable; - case SNES_REG_TS_BG2: return ppufast.io.bg2.belowEnable; - case SNES_REG_TS_BG3: return ppufast.io.bg3.belowEnable; - case SNES_REG_TS_BG4: return ppufast.io.bg4.belowEnable; - case SNES_REG_TS_OBJ: return ppufast.io.obj.belowEnable; + case TS_BG1: return ppufast.io.bg1.belowEnable; + case TS_BG2: return ppufast.io.bg2.belowEnable; + case TS_BG3: return ppufast.io.bg3.belowEnable; + case TS_BG4: return ppufast.io.bg4.belowEnable; + case TS_OBJ: return ppufast.io.obj.belowEnable; //Mode7 regs - case SNES_REG_M7SEL_HFLIP: return ppufast.io.mode7.hflip; - case SNES_REG_M7SEL_VFLIP: return ppufast.io.mode7.vflip; - case SNES_REG_M7SEL_REPEAT: return ppufast.io.mode7.repeat; - case SNES_REG_M7A: return ppufast.io.mode7.a; - case SNES_REG_M7B: return ppufast.io.mode7.b; - case SNES_REG_M7C: return ppufast.io.mode7.c; - case SNES_REG_M7D: return ppufast.io.mode7.d; - case SNES_REG_M7X: return ppufast.io.mode7.x; - case SNES_REG_M7Y: return ppufast.io.mode7.y; + case M7SEL_HFLIP: return ppufast.io.mode7.hflip; + case M7SEL_VFLIP: return ppufast.io.mode7.vflip; + case M7SEL_REPEAT: return ppufast.io.mode7.repeat; + case M7A: return ppufast.io.mode7.a; + case M7B: return ppufast.io.mode7.b; + case M7C: return ppufast.io.mode7.c; + case M7D: return ppufast.io.mode7.d; + case M7X: return ppufast.io.mode7.x; + case M7Y: return ppufast.io.mode7.y; //BG scroll regs - case SNES_REG_BG1HOFS: return ppufast.io.bg1.hoffset; - case SNES_REG_BG1VOFS: return ppufast.io.bg1.voffset; - case SNES_REG_BG2HOFS: return ppufast.io.bg2.hoffset; - case SNES_REG_BG2VOFS: return ppufast.io.bg2.voffset; - case SNES_REG_BG3HOFS: return ppufast.io.bg3.hoffset; - case SNES_REG_BG3VOFS: return ppufast.io.bg3.voffset; - case SNES_REG_BG4HOFS: return ppufast.io.bg4.hoffset; - case SNES_REG_BG4VOFS: return ppufast.io.bg4.voffset; - case SNES_REG_M7HOFS: return ppufast.io.mode7.hoffset; // TODO figure out what that comment means .regs.m7_hofs & 0x1FFF; //rememebr to make these signed with <<19>>19 - case SNES_REG_M7VOFS: return ppufast.io.mode7.voffset; //rememebr to make these signed with <<19>>19 + case BG1HOFS: return ppufast.io.bg1.hoffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG1VOFS: return ppufast.io.bg1.voffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG2HOFS: return ppufast.io.bg2.hoffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG2VOFS: return ppufast.io.bg2.voffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG3HOFS: return ppufast.io.bg3.hoffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG3VOFS: return ppufast.io.bg3.voffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG4HOFS: return ppufast.io.bg4.hoffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case BG4VOFS: return ppufast.io.bg4.voffset & 0x3FF; // copied from old bsnes impl; not sure why this & exists + case M7HOFS: return ppufast.io.mode7.hoffset & 0x1FFF; + case M7VOFS: return ppufast.io.mode7.voffset & 0x1FFF; } else; // no fast ppu // TODO: potentially provide register values even in this case? currently all those are private in ppu.hpp @@ -386,8 +385,11 @@ EXPORT void* snes_get_memory_region(int id, int* size, int* word_size) *size = sizeof(ppufast.vram); *word_size = sizeof(*ppufast.vram); return ppufast.vram; - // case SNES_MEMORY::OAM: // probably weird since bsnes uses "object"s instead of bytes for oam rn - // return (uint8_t*) ppufast.objects; + case SNES_MEMORY::OBJECTS: // returns a pointer to an array of "objects", not raw OAM memory + if (!fast_ppu) break; + *size = sizeof(ppufast.objects); + *word_size = sizeof(*ppufast.objects); + return (void*) ppufast.objects; case SNES_MEMORY::CGRAM: if (!fast_ppu) break; *size = sizeof(ppufast.cgram); diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp index 9862cd77ca..5703666236 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.hpp @@ -22,6 +22,7 @@ enum SNES_MEMORY { WRAM, APURAM, VRAM, + OBJECTS, CGRAM }; @@ -56,92 +57,89 @@ struct SnesRegisters }; -// below code unused; would be useful for the graphics debugger -//$2105 -#define SNES_REG_BG_MODE 0 -#define SNES_REG_BG3_PRIORITY 1 -#define SNES_REG_BG1_TILESIZE 2 -#define SNES_REG_BG2_TILESIZE 3 -#define SNES_REG_BG3_TILESIZE 4 -#define SNES_REG_BG4_TILESIZE 5 -//$2107 -#define SNES_REG_BG1_SCADDR 10 -#define SNES_REG_BG1_SCSIZE 11 -//$2108 -#define SNES_REG_BG2_SCADDR 12 -#define SNES_REG_BG2_SCSIZE 13 -//$2109 -#define SNES_REG_BG3_SCADDR 14 -#define SNES_REG_BG3_SCSIZE 15 -//$210A -#define SNES_REG_BG4_SCADDR 16 -#define SNES_REG_BG4_SCSIZE 17 -//$210B -#define SNES_REG_BG1_TDADDR 20 -#define SNES_REG_BG2_TDADDR 21 -//$210C -#define SNES_REG_BG3_TDADDR 22 -#define SNES_REG_BG4_TDADDR 23 -//$2133 SETINI -#define SNES_REG_SETINI_MODE7_EXTBG 30 -#define SNES_REG_SETINI_HIRES 31 -#define SNES_REG_SETINI_OVERSCAN 32 -#define SNES_REG_SETINI_OBJ_INTERLACE 33 -#define SNES_REG_SETINI_SCREEN_INTERLACE 34 -//$2130 CGWSEL -#define SNES_REG_CGWSEL_COLORMASK 40 -#define SNES_REG_CGWSEL_COLORSUBMASK 41 -#define SNES_REG_CGWSEL_ADDSUBMODE 42 -#define SNES_REG_CGWSEL_DIRECTCOLOR 43 -//$2101 OBSEL -#define SNES_REG_OBSEL_NAMEBASE 50 -#define SNES_REG_OBSEL_NAMESEL 51 -#define SNES_REG_OBSEL_SIZE 52 -//$2131 CGADSUB -#define SNES_REG_CGADDSUB_MODE 60 -#define SNES_REG_CGADDSUB_HALF 61 -#define SNES_REG_CGADDSUB_BG4 62 -#define SNES_REG_CGADDSUB_BG3 63 -#define SNES_REG_CGADDSUB_BG2 64 -#define SNES_REG_CGADDSUB_BG1 65 -#define SNES_REG_CGADDSUB_OBJ 66 -#define SNES_REG_CGADDSUB_BACKDROP 67 -//$212C TM -#define SNES_REG_TM_BG1 70 -#define SNES_REG_TM_BG2 71 -#define SNES_REG_TM_BG3 72 -#define SNES_REG_TM_BG4 73 -#define SNES_REG_TM_OBJ 74 -//$212D TM -#define SNES_REG_TS_BG1 80 -#define SNES_REG_TS_BG2 81 -#define SNES_REG_TS_BG3 82 -#define SNES_REG_TS_BG4 83 -#define SNES_REG_TS_OBJ 84 -//Mode7 regs -#define SNES_REG_M7SEL_REPEAT 90 -#define SNES_REG_M7SEL_HFLIP 91 -#define SNES_REG_M7SEL_VFLIP 92 -#define SNES_REG_M7A 93 -#define SNES_REG_M7B 94 -#define SNES_REG_M7C 95 -#define SNES_REG_M7D 96 -#define SNES_REG_M7X 97 -#define SNES_REG_M7Y 98 -//BG scroll regs -#define SNES_REG_BG1HOFS 100 -#define SNES_REG_BG1VOFS 101 -#define SNES_REG_BG2HOFS 102 -#define SNES_REG_BG2VOFS 103 -#define SNES_REG_BG3HOFS 104 -#define SNES_REG_BG3VOFS 105 -#define SNES_REG_BG4HOFS 106 -#define SNES_REG_BG4VOFS 107 -#define SNES_REG_M7HOFS 108 -#define SNES_REG_M7VOFS 109 - - -int snes_peek_logical_register(int reg); - +enum SNES_REGISTER { + //$2105 + BG_MODE, + BG3_PRIORITY, + BG1_TILESIZE, + BG2_TILESIZE, + BG3_TILESIZE, + BG4_TILESIZE, + //$2107 + BG1_SCADDR, + BG1_SCSIZE, + //$2108 + BG2_SCADDR, + BG2_SCSIZE, + //$2109, + BG3_SCADDR, + BG3_SCSIZE, + //$210A + BG4_SCADDR, + BG4_SCSIZE, + //$210B + BG1_TDADDR, + BG2_TDADDR, + //$210C + BG3_TDADDR, + BG4_TDADDR, + //$2133 SETINI + SETINI_MODE7_EXTBG, + SETINI_HIRES, + SETINI_OVERSCAN, + SETINI_OBJ_INTERLACE, + SETINI_SCREEN_INTERLACE, + //$2130 CGWSEL + CGWSEL_COLORMASK, + CGWSEL_COLORSUBMASK, + CGWSEL_ADDSUBMODE, + CGWSEL_DIRECTCOLOR, + //$2101 OBSEL + OBSEL_NAMEBASE, + OBSEL_NAMESEL, + OBSEL_SIZE, + //$2131 CGADSUB + CGADDSUB_MODE, + CGADDSUB_HALF, + CGADDSUB_BG4, + CGADDSUB_BG3, + CGADDSUB_BG2, + CGADDSUB_BG1, + CGADDSUB_OBJ, + CGADDSUB_BACKDROP, + //$212C TM + TM_BG1, + TM_BG2, + TM_BG3, + TM_BG4, + TM_OBJ, + //$212D TM + TS_BG1, + TS_BG2, + TS_BG3, + TS_BG4, + TS_OBJ, + //Mode7 regs + M7SEL_REPEAT, + M7SEL_HFLIP, + M7SEL_VFLIP, + M7A, + M7B, + M7C, + M7D, + M7X, + M7Y, + //BG scroll regs + BG1HOFS, + BG1VOFS, + BG2HOFS, + BG2VOFS, + BG3HOFS, + BG3VOFS, + BG4HOFS, + BG4VOFS, + M7HOFS, + M7VOFS +}; #endif