322 lines
10 KiB
C#
322 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using System.Runtime.InteropServices;
|
|
using Mesen.GUI.Controls;
|
|
using Mesen.GUI.Forms;
|
|
using Mesen.GUI.Config;
|
|
using System.Drawing.Imaging;
|
|
using Mesen.GUI.Debugger.Labels;
|
|
using System.Collections.ObjectModel;
|
|
|
|
namespace Mesen.GUI.Debugger
|
|
{
|
|
public partial class ctrlEventViewerPpuView : BaseControl
|
|
{
|
|
public const int HdmaChannelFlag = 0x40;
|
|
|
|
private int _baseWidth = 1364 / 2;
|
|
private double _xRatio = 2;
|
|
|
|
private Point _lastPos = new Point(-1, -1);
|
|
private bool _needUpdate = false;
|
|
private Bitmap _screenBitmap = null;
|
|
private Bitmap _overlayBitmap = null;
|
|
private Bitmap _displayBitmap = null;
|
|
private byte[] _pictureData = null;
|
|
private Font _overlayFont;
|
|
private Font _smallOverlayFont;
|
|
|
|
public UInt32 ScanlineCount { get; set; } = 262;
|
|
public int ImageScale { get { return picViewer.ImageScale; } set { picViewer.ImageScale = value; } }
|
|
|
|
public ctrlEventViewerPpuView()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
base.OnLoad(e);
|
|
|
|
if(!IsDesignMode) {
|
|
tmrOverlay.Start();
|
|
_overlayFont = new Font(BaseControl.MonospaceFontFamily, 10);
|
|
_smallOverlayFont = new Font(BaseControl.MonospaceFontFamily, 8);
|
|
}
|
|
}
|
|
|
|
public CpuType CpuType { get; set; }
|
|
|
|
public void RefreshViewer()
|
|
{
|
|
EventViewerDisplayOptions options = ConfigManager.Config.Debug.EventViewer.GetInteropOptions();
|
|
if(this.CpuType == CpuType.Gameboy) {
|
|
_baseWidth = 456 * 2;
|
|
_xRatio = 0.5;
|
|
} else {
|
|
_baseWidth = 1364 / 2;
|
|
_xRatio = 2;
|
|
}
|
|
_pictureData = DebugApi.GetEventViewerOutput(this.CpuType, _baseWidth, ScanlineCount, options);
|
|
|
|
int picHeight = (int)ScanlineCount*2;
|
|
if(_screenBitmap == null || _screenBitmap.Height != picHeight || _screenBitmap.Width != _baseWidth) {
|
|
_screenBitmap = new Bitmap(_baseWidth, picHeight, PixelFormat.Format32bppPArgb);
|
|
_overlayBitmap = new Bitmap(_baseWidth, picHeight, PixelFormat.Format32bppPArgb);
|
|
_displayBitmap = new Bitmap(_baseWidth, picHeight, PixelFormat.Format32bppPArgb);
|
|
}
|
|
|
|
GCHandle handle = GCHandle.Alloc(this._pictureData, GCHandleType.Pinned);
|
|
try {
|
|
Bitmap source = new Bitmap(_baseWidth, (int)ScanlineCount*2, _baseWidth*4, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject());
|
|
using(Graphics g = Graphics.FromImage(_screenBitmap)) {
|
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
|
|
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
|
|
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
|
|
g.DrawImageUnscaled(source, 0, 0);
|
|
}
|
|
} finally {
|
|
handle.Free();
|
|
}
|
|
|
|
UpdateDisplay(true);
|
|
}
|
|
|
|
private void UpdateDisplay(bool forceUpdate)
|
|
{
|
|
if(!_needUpdate && !forceUpdate) {
|
|
return;
|
|
}
|
|
|
|
using(Graphics g = Graphics.FromImage(_displayBitmap)) {
|
|
g.DrawImage(_screenBitmap, 0, 0);
|
|
g.DrawImage(_overlayBitmap, 0, 0);
|
|
|
|
if(_lastPos.X >= 0) {
|
|
string location = _lastPos.X + ", " + _lastPos.Y;
|
|
SizeF size = g.MeasureString(location, _overlayFont);
|
|
int x = (int)(_lastPos.X / _xRatio) + 5;
|
|
int y = _lastPos.Y - (int)size.Height / 2 - 5;
|
|
|
|
if(x - picViewer.ScrollOffsets.X / picViewer.ImageScale + size.Width > (picViewer.Width / picViewer.ImageScale) - 5) {
|
|
x -= (int)size.Width + 5;
|
|
}
|
|
if(y*2 - picViewer.ScrollOffsets.Y / picViewer.ImageScale < size.Height + 5) {
|
|
y = _lastPos.Y + 5;
|
|
}
|
|
|
|
g.DrawOutlinedString(location, _overlayFont, Brushes.Black, Brushes.White, x, y*2);
|
|
|
|
location = GetCycle(_lastPos.X).ToString();
|
|
g.DrawOutlinedString(location, _smallOverlayFont, Brushes.Black, Brushes.White, x, y*2 + (int)size.Height - 5);
|
|
}
|
|
}
|
|
|
|
picViewer.ImageSize = new Size(_baseWidth, (int)ScanlineCount*2);
|
|
picViewer.Image = _displayBitmap;
|
|
_needUpdate = false;
|
|
}
|
|
|
|
private void UpdateOverlay(Point p)
|
|
{
|
|
Point pos = GetHClockAndScanline(p);
|
|
|
|
if(_lastPos == pos) {
|
|
//Same x,y location, no need to update
|
|
return;
|
|
}
|
|
|
|
using(Graphics g = Graphics.FromImage(_overlayBitmap)) {
|
|
g.Clear(Color.Transparent);
|
|
using(Pen bg = new Pen(Color.FromArgb(128, Color.LightGray))) {
|
|
g.DrawRectangle(bg, (int)(pos.X / _xRatio) - 1, 0, 3, _overlayBitmap.Height);
|
|
g.DrawRectangle(bg, 0, pos.Y * 2 - 1, _overlayBitmap.Width, 3);
|
|
}
|
|
}
|
|
|
|
_needUpdate = true;
|
|
_lastPos = pos;
|
|
}
|
|
|
|
private void ClearOverlay()
|
|
{
|
|
using(Graphics g = Graphics.FromImage(_overlayBitmap)) {
|
|
g.Clear(Color.Transparent);
|
|
}
|
|
UpdateDisplay(false);
|
|
_lastPos = new Point(-1, -1);
|
|
}
|
|
|
|
private Point GetHClockAndScanline(Point location)
|
|
{
|
|
return new Point(
|
|
(int)((location.X / this.ImageScale) * _xRatio),
|
|
((location.Y & ~0x01) / this.ImageScale) / 2
|
|
);
|
|
}
|
|
|
|
private int GetCycle(int hClock)
|
|
{
|
|
if(hClock <= 1292) {
|
|
return hClock >> 2;
|
|
} else if(hClock <= 1310) {
|
|
return (hClock - 2) >> 2;
|
|
} else {
|
|
return (hClock - 4) >> 2;
|
|
}
|
|
}
|
|
|
|
private DebugEventInfo? GetEventAtPosition(Point location)
|
|
{
|
|
Point pos = GetHClockAndScanline(location);
|
|
|
|
EventViewerDisplayOptions options = ConfigManager.Config.Debug.EventViewer.GetInteropOptions();
|
|
DebugEventInfo evt = new DebugEventInfo();
|
|
DebugApi.GetEventViewerEvent(this.CpuType, ref evt, (UInt16)pos.Y, (UInt16)pos.X, options);
|
|
if(evt.ProgramCounter == 0xFFFFFFFF) {
|
|
int[] xOffsets = new int[] { 0, 1, -1, 2, -2, 3 };
|
|
int[] yOffsets = new int[] { 0, -1, 1 };
|
|
|
|
//Check for other events near the current mouse position
|
|
for(int j = 0; j < yOffsets.Length; j++) {
|
|
for(int i = 0; i < xOffsets.Length; i++) {
|
|
DebugApi.GetEventViewerEvent(this.CpuType, ref evt, (UInt16)(pos.Y + yOffsets[j]), (UInt16)(pos.X + xOffsets[i] * _xRatio), options);
|
|
if(evt.ProgramCounter != 0xFFFFFFFF) {
|
|
return evt;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
} else {
|
|
return evt;
|
|
}
|
|
}
|
|
|
|
private void picPicture_MouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
DebugEventInfo? result = GetEventAtPosition(e.Location);
|
|
if(result == null) {
|
|
ResetTooltip();
|
|
UpdateOverlay(e.Location);
|
|
return;
|
|
}
|
|
|
|
DebugEventInfo evt = result.Value;
|
|
Point newPos = new Point(evt.Cycle, evt.Scanline);
|
|
if(_lastPos == newPos) {
|
|
return;
|
|
}
|
|
|
|
Dictionary<string, string> values;
|
|
if(this.CpuType == CpuType.Gameboy) {
|
|
values = new Dictionary<string, string>() {
|
|
{ "Type", ResourceHelper.GetEnumText(evt.Type) },
|
|
{ "Scanline", evt.Scanline.ToString() },
|
|
{ "Cycle", evt.Cycle.ToString() },
|
|
{ "PC", "$" + evt.ProgramCounter.ToString("X4") },
|
|
};
|
|
} else {
|
|
values = new Dictionary<string, string>() {
|
|
{ "Type", ResourceHelper.GetEnumText(evt.Type) },
|
|
{ "Scanline", evt.Scanline.ToString() },
|
|
{ "H-Clock", evt.Cycle.ToString() + " (" + GetCycle(evt.Cycle) + ")" },
|
|
{ "PC", "$" + evt.ProgramCounter.ToString("X6") },
|
|
};
|
|
}
|
|
|
|
switch(evt.Type) {
|
|
case DebugEventType.Register:
|
|
bool isWrite = evt.Operation.Type == MemoryOperationType.Write || evt.Operation.Type == MemoryOperationType.DmaWrite;
|
|
bool isDma = evt.Operation.Type == MemoryOperationType.DmaWrite || evt.Operation.Type == MemoryOperationType.DmaRead;
|
|
|
|
CodeLabel label = LabelManager.GetLabel(new AddressInfo() { Address = (int)evt.Operation.Address, Type = SnesMemoryType.CpuMemory });
|
|
string registerText = "$" + evt.Operation.Address.ToString("X4");
|
|
if(label != null) {
|
|
registerText = label.Label + " (" + registerText + ")";
|
|
}
|
|
|
|
values["Register"] = registerText + (isWrite ? " (Write)" : " (Read)") + (isDma ? " (DMA)" : "");
|
|
values["Value"] = "$" + evt.Operation.Value.ToString("X2");
|
|
|
|
if(isDma && this.CpuType != CpuType.Gameboy) {
|
|
bool indirectHdma = false;
|
|
values["Channel"] = (evt.DmaChannel & 0x07).ToString();
|
|
|
|
if((evt.DmaChannel & ctrlEventViewerPpuView.HdmaChannelFlag) != 0) {
|
|
indirectHdma = evt.DmaChannelInfo.HdmaIndirectAddressing;
|
|
values["Channel"] += indirectHdma ? " (Indirect HDMA)" : " (HDMA)";
|
|
values["Line Counter"] = "$" + evt.DmaChannelInfo.HdmaLineCounterAndRepeat.ToString("X2");
|
|
}
|
|
|
|
values["Mode"] = evt.DmaChannelInfo.TransferMode.ToString();
|
|
|
|
int aBusAddress;
|
|
if(indirectHdma) {
|
|
aBusAddress = (evt.DmaChannelInfo.SrcBank << 16) | evt.DmaChannelInfo.TransferSize;
|
|
} else {
|
|
aBusAddress = (evt.DmaChannelInfo.SrcBank << 16) | evt.DmaChannelInfo.SrcAddress;
|
|
}
|
|
|
|
if(!evt.DmaChannelInfo.InvertDirection) {
|
|
values["Transfer"] = "$" + aBusAddress.ToString("X4") + " -> $" + evt.DmaChannelInfo.DestAddress.ToString("X2");
|
|
} else {
|
|
values["Transfer"] = "$" + aBusAddress.ToString("X4") + " <- $" + evt.DmaChannelInfo.DestAddress.ToString("X2");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DebugEventType.Breakpoint:
|
|
ReadOnlyCollection<Breakpoint> breakpoints = BreakpointManager.Breakpoints;
|
|
if(evt.BreakpointId >= 0 && evt.BreakpointId < breakpoints.Count) {
|
|
Breakpoint bp = breakpoints[evt.BreakpointId];
|
|
values["CPU Type"] = ResourceHelper.GetEnumText(bp.CpuType);
|
|
values["BP Type"] = bp.ToReadableType();
|
|
values["BP Addresses"] = bp.GetAddressString(true);
|
|
if(bp.Condition.Length > 0) {
|
|
values["BP Condition"] = bp.Condition;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
UpdateOverlay(new Point((int)(evt.Cycle / _xRatio * this.ImageScale), (int)(evt.Scanline * 2 * this.ImageScale)));
|
|
|
|
Form parentForm = this.FindForm();
|
|
Point location = parentForm.PointToClient(Control.MousePosition);
|
|
BaseForm.GetPopupTooltip(parentForm).SetTooltip(location, values);
|
|
}
|
|
|
|
private void ResetTooltip()
|
|
{
|
|
BaseForm.GetPopupTooltip(this.FindForm()).Visible = false;
|
|
}
|
|
|
|
private void picPicture_MouseLeave(object sender, EventArgs e)
|
|
{
|
|
ResetTooltip();
|
|
ClearOverlay();
|
|
}
|
|
|
|
private void tmrOverlay_Tick(object sender, EventArgs e)
|
|
{
|
|
UpdateDisplay(false);
|
|
}
|
|
|
|
public void ZoomIn()
|
|
{
|
|
picViewer.ZoomIn();
|
|
}
|
|
|
|
public void ZoomOut()
|
|
{
|
|
picViewer.ZoomOut();
|
|
}
|
|
}
|
|
}
|