Mesen-SX/UI/Debugger/EventViewer/ctrlEventViewerPpuView.cs

302 lines
9.3 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 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 void RefreshViewer()
{
EventViewerDisplayOptions options = ConfigManager.Config.Debug.EventViewer.GetInteropOptions();
_pictureData = DebugApi.GetEventViewerOutput(ScanlineCount, options);
int picHeight = (int)ScanlineCount*2;
if(_screenBitmap == null || _screenBitmap.Height != picHeight) {
_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 = _lastPos.X + 5;
int y = _lastPos.Y - (int)size.Height / 2 - 5;
if(x/2 - picViewer.ScrollOffsets.X / picViewer.ImageScale + size.Width > (picViewer.Width / picViewer.ImageScale) - 5) {
x -= (int)size.Width * 2 + 10;
}
if(y*2 - picViewer.ScrollOffsets.Y / picViewer.ImageScale < size.Height + 5) {
y = _lastPos.Y + 5;
}
g.DrawOutlinedString(location, _overlayFont, Brushes.Black, Brushes.White, x/2, y*2);
location = GetCycle(_lastPos.X).ToString();
g.DrawOutlinedString(location, _smallOverlayFont, Brushes.Black, Brushes.White, x/2, 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, pos.X / 2 - 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(
(location.X / this.ImageScale) * 2,
((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(ref evt, (UInt16)pos.Y, (UInt16)pos.X, options);
if(evt.ProgramCounter == 0xFFFFFFFF) {
int[] xOffsets = new int[] { 0, 2, -2, 4, -4, 6 };
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(ref evt, (UInt16)(pos.Y + yOffsets[j]), (UInt16)(pos.X + xOffsets[i]), 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 = 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) {
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 / 2 * 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();
}
}
}