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();
+ }
}
}