diff --git a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs index a199bd82c4..41f6bfac7c 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -145,6 +145,16 @@ namespace BizHawk.Emulation.Consoles.GB tracecb = null; LibGambatte.gambatte_settracecallback(GambatteState, tracecb); + // todo: have the gambatte core actually call this at an appropriate time + if (scanlinecallback != null) + { + IntPtr vram = IntPtr.Zero; + int vramlength = 0; + if (!LibGambatte.gambatte_getmemoryarea(GambatteState, LibGambatte.MemoryAreas.vram, ref vram, ref vramlength)) + throw new Exception(); + scanlinecallback(vram, vramlength, LibGambatte.gambatte_cpuread(GambatteState, 0xff40)); + } + LibGambatte.gambatte_runfor(GambatteState, VideoBuffer, 160, soundbuff, ref nsamp); // upload any modified data to the memory domains @@ -575,6 +585,34 @@ namespace BizHawk.Emulation.Consoles.GB #endregion + #region ppudebug + /// + /// a callback to be registered at a particular scanline + /// + /// + /// length of vram in bytes + /// current LCDC status + public delegate void ScanlineCallback(IntPtr vram, int vramlength, int lcdc); + + ScanlineCallback scanlinecallback; + + /// + /// set up callback + /// + /// + /// scanline + public void SetScanlineCallback(ScanlineCallback callback, int line) + { + if (callback == null) + this.scanlinecallback = null; + else if (line < 0 || line > 153) + throw new ArgumentOutOfRangeException("line must be in [0, 153]"); + else + this.scanlinecallback = callback; + } + + #endregion + public void Dispose() { LibGambatte.gambatte_destroy(GambatteState); diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index 99e23705de..2e46e42621 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -217,12 +217,21 @@ + + Component + Form ColorChooserForm.cs + + Form + + + GBGPUView.cs + @@ -457,6 +466,9 @@ ColorChooserForm.cs + + GBGPUView.cs + MainForm.cs Designer diff --git a/BizHawk.MultiClient/GBtools/BmpView.cs b/BizHawk.MultiClient/GBtools/BmpView.cs new file mode 100644 index 0000000000..5390ff5aba --- /dev/null +++ b/BizHawk.MultiClient/GBtools/BmpView.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Drawing; +using System.Windows.Forms; + +namespace BizHawk.MultiClient.GBtools +{ + public partial class BmpView : Control + { + public Bitmap bmp; + + public BmpView() + { + SetStyle(ControlStyles.AllPaintingInWmPaint, true); + SetStyle(ControlStyles.UserPaint, true); + SetStyle(ControlStyles.DoubleBuffer, true); + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + SetStyle(ControlStyles.Opaque, true); + this.BackColor = Color.Transparent; + this.Paint += new PaintEventHandler(BmpView_Paint); + this.SizeChanged += new EventHandler(BmpView_SizeChanged); + } + + void BmpView_Paint(object sender, PaintEventArgs e) + { + if (bmp != null) + { + e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor; + e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half; + e.Graphics.DrawImageUnscaled(bmp, 0, 0); + } + } + + void BmpView_SizeChanged(object sender, EventArgs e) + { + if (bmp != null) + { + bmp.Dispose(); + bmp = null; + } + if (Width == 0 || Height == 0) + return; + bmp = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + } + + } +} diff --git a/BizHawk.MultiClient/GBtools/GBGPUView.Designer.cs b/BizHawk.MultiClient/GBtools/GBGPUView.Designer.cs new file mode 100644 index 0000000000..2f4c4c4a2f --- /dev/null +++ b/BizHawk.MultiClient/GBtools/GBGPUView.Designer.cs @@ -0,0 +1,98 @@ +namespace BizHawk.MultiClient.GBtools +{ + partial class GBGPUView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.bmpViewWin = new BizHawk.MultiClient.GBtools.BmpView(); + this.bmpViewBG = new BizHawk.MultiClient.GBtools.BmpView(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(17, 21); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(22, 13); + this.label1.TabIndex = 2; + this.label1.Text = "BG"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(275, 24); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(26, 13); + this.label2.TabIndex = 3; + this.label2.Text = "Win"; + // + // bmpViewWin + // + this.bmpViewWin.BackColor = System.Drawing.Color.Transparent; + this.bmpViewWin.Location = new System.Drawing.Point(278, 37); + this.bmpViewWin.Name = "bmpViewWin"; + this.bmpViewWin.Size = new System.Drawing.Size(256, 256); + this.bmpViewWin.TabIndex = 5; + this.bmpViewWin.Text = "bmpView2"; + // + // bmpViewBG + // + this.bmpViewBG.BackColor = System.Drawing.Color.Transparent; + this.bmpViewBG.Location = new System.Drawing.Point(12, 37); + this.bmpViewBG.Name = "bmpViewBG"; + this.bmpViewBG.Size = new System.Drawing.Size(256, 256); + this.bmpViewBG.TabIndex = 4; + this.bmpViewBG.Text = "bmpView1"; + // + // GBGPUView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(564, 404); + this.Controls.Add(this.bmpViewWin); + this.Controls.Add(this.bmpViewBG); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Name = "GBGPUView"; + this.Text = "GB GPU Viewer"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.GBGPUView_FormClosed); + this.Load += new System.EventHandler(this.GBGPUView_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private BmpView bmpViewBG; + private BmpView bmpViewWin; + } +} \ No newline at end of file diff --git a/BizHawk.MultiClient/GBtools/GBGPUView.cs b/BizHawk.MultiClient/GBtools/GBGPUView.cs new file mode 100644 index 0000000000..9d72d3e6f2 --- /dev/null +++ b/BizHawk.MultiClient/GBtools/GBGPUView.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BizHawk.MultiClient.GBtools +{ + public partial class GBGPUView : Form + { + Emulation.Consoles.GB.Gameboy gb; + + public GBGPUView() + { + InitializeComponent(); + + } + + public void Restart() + { + if (Global.Emulator is Emulation.Consoles.GB.Gameboy) + { + gb = Global.Emulator as Emulation.Consoles.GB.Gameboy; + } + else + { + this.Close(); + } + } + + /// + /// put me in ToolsBefore + /// + public void UpdateValues() + { + if (!this.IsHandleCreated || this.IsDisposed) + return; + if (gb != null) + if (this.Visible) + gb.SetScanlineCallback(ScanlineCallback, 0); + else + gb.SetScanlineCallback(null, 0); + } + + static unsafe void DrawTile(byte* tile, int* dest, int pitch) + { + for (int y = 0; y < 8; y++) + { + int loplane = *tile++; + int hiplane = *tile++; + hiplane <<= 1; // msb + dest += 7; + for (int x = 0; x < 8; x++) // right to left + { + int palcolor = loplane & 1 | hiplane & 2; + // todo: palette transformation + int color = palcolor * 0x555555 | unchecked((int)0xff000000); + *dest-- = color; + loplane >>= 1; + hiplane >>= 1; + } + dest++; + dest += pitch; + } + } + + static unsafe void DrawBG(Bitmap b, IntPtr _map, IntPtr _tiles, bool wrap) + { + if (b.Width != 256 || b.Height != 256) + throw new Exception("GPUView screwed up."); + + var lockdata = b.LockBits(new Rectangle(0, 0, 256, 256), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + byte* map = (byte*)_map; + + int* dest = (int*)lockdata.Scan0; + + int pitch = lockdata.Stride / sizeof(int); // in int*s, not bytes + + for (int ty = 0; ty < 32; ty++) + { + for (int tx = 0; tx < 32; tx++) + { + int tileindex = map[0]; + if (wrap && tileindex >= 128) + tileindex -= 256; + byte* tile = (byte*)(_tiles + tileindex * 16); + DrawTile(tile, dest, pitch); + map++; + dest += 8; + } + dest -= 256; + dest += pitch * 8; + } + b.UnlockBits(lockdata); + } + + /// + /// core calls this on scanlines + /// + /// + /// + /// + void ScanlineCallback(IntPtr vram, int vramlength, int lcdc) + { + + DrawBG( + bmpViewBG.bmp, + vram + (lcdc.Bit(3) ? 0x1c00 : 0x1800), + vram + (lcdc.Bit(4) ? 0x0000 : 0x1000), + !lcdc.Bit(4)); + bmpViewBG.Refresh(); + + DrawBG( + bmpViewWin.bmp, + vram + (lcdc.Bit(6) ? 0x1c00 : 0x1800), + vram + 0x1000, // force win to second tile bank??? + true); + bmpViewWin.Refresh(); + + } + + private void GBGPUView_FormClosed(object sender, FormClosedEventArgs e) + { + if (gb != null) + { + gb.SetScanlineCallback(null, 0); + gb = null; + } + } + + private void GBGPUView_Load(object sender, EventArgs e) + { + gb = Global.Emulator as Emulation.Consoles.GB.Gameboy; + } + } +} diff --git a/BizHawk.MultiClient/GBtools/GBGPUView.resx b/BizHawk.MultiClient/GBtools/GBGPUView.resx new file mode 100644 index 0000000000..29dcb1b3a3 --- /dev/null +++ b/BizHawk.MultiClient/GBtools/GBGPUView.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BizHawk.MultiClient/MainForm.Designer.cs b/BizHawk.MultiClient/MainForm.Designer.cs index d7afa15fe1..e0fa15094b 100644 --- a/BizHawk.MultiClient/MainForm.Designer.cs +++ b/BizHawk.MultiClient/MainForm.Designer.cs @@ -306,6 +306,7 @@ this.cmiScreenshotClipboard = new System.Windows.Forms.ToolStripMenuItem(); this.cmiCloseRom = new System.Windows.Forms.ToolStripMenuItem(); this.cmiShowMenu = new System.Windows.Forms.ToolStripMenuItem(); + this.gPUViewerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.StatusSlot0.SuspendLayout(); this.contextMenuStrip1.SuspendLayout(); @@ -2118,7 +2119,8 @@ this.multicartCompatibilityToolStripMenuItem, this.toolStripSeparator8, this.changeDMGPalettesToolStripMenuItem, - this.loadGBInSGBToolStripMenuItem1}); + this.loadGBInSGBToolStripMenuItem1, + this.gPUViewerToolStripMenuItem}); this.gBToolStripMenuItem.Name = "gBToolStripMenuItem"; this.gBToolStripMenuItem.Size = new System.Drawing.Size(32, 17); this.gBToolStripMenuItem.Text = "&GB"; @@ -2645,6 +2647,13 @@ this.cmiShowMenu.Text = "Show Menu"; this.cmiShowMenu.Click += new System.EventHandler(this.showMenuToolStripMenuItem_Click); // + // gPUViewerToolStripMenuItem + // + this.gPUViewerToolStripMenuItem.Name = "gPUViewerToolStripMenuItem"; + this.gPUViewerToolStripMenuItem.Size = new System.Drawing.Size(190, 22); + this.gPUViewerToolStripMenuItem.Text = "GPU Viewer"; + this.gPUViewerToolStripMenuItem.Click += new System.EventHandler(this.gPUViewerToolStripMenuItem_Click); + // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 14F); @@ -2962,6 +2971,7 @@ private System.Windows.Forms.ToolStripMenuItem showMissle2ToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem showBallToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem showPlayfieldToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem gPUViewerToolStripMenuItem; } } diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 716bdcd905..8c9bb06b91 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -77,6 +77,7 @@ namespace BizHawk.MultiClient public NESNameTableViewer NESNameTableViewer1 = new NESNameTableViewer(); public NESPPU NESPPU1 = new NESPPU(); public NESDebugger NESDebug1 = new NESDebugger(); + public GBtools.GBGPUView GBGPUView1 = new GBtools.GBGPUView(); public PCEBGViewer PCEBGViewer1 = new PCEBGViewer(); public Cheats Cheats1 = new Cheats(); public ToolBox ToolBox1 = new ToolBox(); @@ -2497,6 +2498,7 @@ namespace BizHawk.MultiClient NESNameTableViewer1.UpdateValues(); NESPPU1.UpdateValues(); PCEBGViewer1.UpdateValues(); + GBGPUView1.UpdateValues(); } public void UpdateToolsLoadstate() @@ -2866,6 +2868,17 @@ namespace BizHawk.MultiClient PCEBGViewer1.Focus(); } + public void LoadGBGPUView() + { + if (!GBGPUView1.IsHandleCreated || GBGPUView1.IsDisposed) + { + GBGPUView1 = new GBtools.GBGPUView(); + GBGPUView1.Show(); + } + else + GBGPUView1.Focus(); + } + public void LoadTI83KeyPad() { if (!TI83KeyPad1.IsHandleCreated || TI83KeyPad1.IsDisposed) @@ -3099,6 +3112,7 @@ namespace BizHawk.MultiClient NESPPU1.Restart(); NESNameTableViewer1.Restart(); NESDebug1.Restart(); + GBGPUView1.Restart(); PCEBGViewer1.Restart(); TI83KeyPad1.Restart(); Cheats1.Restart(); @@ -3137,6 +3151,7 @@ namespace BizHawk.MultiClient CloseForm(NESNameTableViewer1); CloseForm(NESPPU1); CloseForm(NESDebug1); + CloseForm(GBGPUView1); CloseForm(PCEBGViewer1); CloseForm(Cheats1); CloseForm(TI83KeyPad1); @@ -4249,5 +4264,10 @@ namespace BizHawk.MultiClient Global.Config.Atari2600_ShowPlayfield ^= true; SyncCoreInputComm(); } + + private void gPUViewerToolStripMenuItem_Click(object sender, EventArgs e) + { + LoadGBGPUView(); + } } }