850 lines
24 KiB
C#
850 lines
24 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Windows.Forms;
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
|
using BizHawk.Client.Common;
|
|
using BizHawk.Emulation.Cores.Nintendo.Gameboy;
|
|
using BizHawk.Emulation.Cores.Nintendo.GBA;
|
|
using System.Collections.Generic;
|
|
using BizHawk.Emulation.Common;
|
|
|
|
namespace BizHawk.Client.EmuHawk
|
|
{
|
|
public partial class GBAGPUView : Form, IToolFormAutoConfig
|
|
{
|
|
[RequiredService]
|
|
IGBAGPUViewable gba { get; set; }
|
|
|
|
// emulator memory areas
|
|
private IntPtr vram;
|
|
private IntPtr oam;
|
|
private IntPtr mmio;
|
|
private IntPtr palram;
|
|
// color conversion to RGB888
|
|
private readonly int[] ColorConversion;
|
|
|
|
MobileBmpView bg0, bg1, bg2, bg3, bgpal, sppal, sprites, bgtiles16, bgtiles256, sptiles16, sptiles256;
|
|
|
|
// MobileDetailView memory;
|
|
|
|
public bool AskSaveChanges() { return true; }
|
|
public bool UpdateBefore { get { return true; } }
|
|
|
|
public GBAGPUView()
|
|
{
|
|
InitializeComponent();
|
|
// TODO: hook up something
|
|
// we do this twice to avoid having to & 0x7fff with every color
|
|
int[] tmp = GBColors.GetLut(GBColors.ColorType.vivid);
|
|
ColorConversion = new int[65536];
|
|
Buffer.BlockCopy(tmp, 0, ColorConversion, 0, sizeof(int) * tmp.Length);
|
|
Buffer.BlockCopy(tmp, 0, ColorConversion, sizeof(int) * tmp.Length, sizeof(int) * tmp.Length);
|
|
radioButtonManual.Checked = true;
|
|
GenerateWidgets();
|
|
hScrollBar1_ValueChanged(null, null);
|
|
RecomputeRefresh();
|
|
}
|
|
|
|
#region drawing primitives
|
|
|
|
unsafe void DrawTile256(int* dest, int pitch, byte* tile, ushort* palette, bool hflip, bool vflip)
|
|
{
|
|
if (vflip)
|
|
{
|
|
dest += pitch * 7;
|
|
pitch = -pitch;
|
|
}
|
|
|
|
if (hflip)
|
|
{
|
|
dest += 7;
|
|
for (int y = 0; y < 8; y++)
|
|
{
|
|
for (int x = 0; x < 8; x++)
|
|
{
|
|
*dest-- = ColorConversion[palette[*tile++]];
|
|
}
|
|
dest += 8;
|
|
dest += pitch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y = 0; y < 8; y++)
|
|
{
|
|
for (int x = 0; x < 8; x++)
|
|
{
|
|
*dest++ = ColorConversion[palette[*tile++]];
|
|
}
|
|
dest -= 8;
|
|
dest += pitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe void DrawTile16(int* dest, int pitch, byte* tile, ushort* palette, bool hflip, bool vflip)
|
|
{
|
|
if (vflip)
|
|
{
|
|
dest += pitch * 7;
|
|
pitch = -pitch;
|
|
}
|
|
if (hflip)
|
|
{
|
|
dest += 7;
|
|
for (int y = 0; y < 8; y++)
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
*dest-- = ColorConversion[palette[*tile & 15]];
|
|
*dest-- = ColorConversion[palette[*tile >> 4]];
|
|
tile++;
|
|
}
|
|
dest += 8;
|
|
dest += pitch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int y = 0; y < 8; y++)
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
*dest++ = ColorConversion[palette[*tile & 15]];
|
|
*dest++ = ColorConversion[palette[*tile >> 4]];
|
|
tile++;
|
|
}
|
|
dest -= 8;
|
|
dest += pitch;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe void DrawTextNameTable16(int* dest, int pitch, ushort* nametable, byte* tiles)
|
|
{
|
|
for (int ty = 0; ty < 32; ty++)
|
|
{
|
|
for (int tx = 0; tx < 32; tx++)
|
|
{
|
|
ushort ntent = *nametable++;
|
|
DrawTile16(dest, pitch, tiles + (ntent & 1023) * 32, (ushort*)palram + (ntent >> 12 << 4), ntent.Bit(10), ntent.Bit(11));
|
|
dest += 8;
|
|
}
|
|
dest -= 256;
|
|
dest += 8 * pitch;
|
|
}
|
|
}
|
|
|
|
unsafe void DrawTextNameTable256(int* dest, int pitch, ushort* nametable, byte* tiles)
|
|
{
|
|
for (int ty = 0; ty < 32; ty++)
|
|
{
|
|
for (int tx = 0; tx < 32; tx++)
|
|
{
|
|
ushort ntent = *nametable++;
|
|
DrawTile256(dest, pitch, tiles + (ntent & 1023) * 64, (ushort*)palram, ntent.Bit(10), ntent.Bit(11));
|
|
dest += 8;
|
|
}
|
|
dest -= 256;
|
|
dest += 8 * pitch;
|
|
}
|
|
}
|
|
|
|
unsafe void DrawTextNameTable(int* dest, int pitch, ushort* nametable, byte* tiles, bool eightbit)
|
|
{
|
|
if (eightbit)
|
|
DrawTextNameTable256(dest, pitch, nametable, tiles);
|
|
else
|
|
DrawTextNameTable16(dest, pitch, nametable, tiles);
|
|
}
|
|
|
|
unsafe void DrawTextBG(int n, MobileBmpView mbv)
|
|
{
|
|
ushort bgcnt = ((ushort*)mmio)[4 + n];
|
|
int ssize = bgcnt >> 14;
|
|
switch (ssize)
|
|
{
|
|
case 0: mbv.ChangeAllSizes(256, 256); break;
|
|
case 1: mbv.ChangeAllSizes(512, 256); break;
|
|
case 2: mbv.ChangeAllSizes(256, 512); break;
|
|
case 3: mbv.ChangeAllSizes(512, 512); break;
|
|
}
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
byte* tiles = (byte*)vram + ((bgcnt & 0xc) << 12);
|
|
|
|
ushort* nametable = (ushort*)vram + ((bgcnt & 0x1f00) << 2);
|
|
|
|
bool eightbit = bgcnt.Bit(7);
|
|
|
|
switch (ssize)
|
|
{
|
|
case 0:
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
break;
|
|
case 1:
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
pixels += 256;
|
|
nametable += 1024;
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
break;
|
|
case 2:
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
pixels += pitch * 256;
|
|
nametable += 1024;
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
break;
|
|
case 3:
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
pixels += 256;
|
|
nametable += 1024;
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
pixels -= 256;
|
|
pixels += pitch * 256;
|
|
nametable += 1024;
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
pixels += 256;
|
|
nametable += 1024;
|
|
DrawTextNameTable(pixels, pitch, nametable, tiles, eightbit);
|
|
break;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawAffineBG(int n, MobileBmpView mbv)
|
|
{
|
|
ushort bgcnt = ((ushort*)mmio)[4 + n];
|
|
int ssize = bgcnt >> 14;
|
|
switch (ssize)
|
|
{
|
|
case 0: mbv.ChangeAllSizes(128, 128); break;
|
|
case 1: mbv.ChangeAllSizes(256, 256); break;
|
|
case 2: mbv.ChangeAllSizes(512, 512); break;
|
|
case 3: mbv.ChangeAllSizes(1024, 1024); break;
|
|
}
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
byte* tiles = (byte*)vram + ((bgcnt & 0xc) << 12);
|
|
|
|
byte* nametable = (byte*)vram + ((bgcnt & 0x1f00) << 3);
|
|
|
|
for (int ty = 0; ty < bmp.Height / 8; ty++)
|
|
{
|
|
for (int tx = 0; tx < bmp.Width / 8; tx++)
|
|
{
|
|
DrawTile256(pixels, pitch, tiles + *nametable++ * 64, (ushort*)palram, false, false);
|
|
pixels += 8;
|
|
}
|
|
pixels -= bmp.Width;
|
|
pixels += 8 * pitch;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawM3BG(MobileBmpView mbv)
|
|
{
|
|
mbv.ChangeAllSizes(240, 160);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
ushort* frame = (ushort*)vram;
|
|
|
|
for (int y = 0; y < 160; y++)
|
|
{
|
|
for (int x = 0; x < 240; x++)
|
|
*pixels++ = ColorConversion[*frame++];
|
|
pixels -= 240;
|
|
pixels += pitch;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawM4BG(MobileBmpView mbv, bool secondframe)
|
|
{
|
|
mbv.ChangeAllSizes(240, 160);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
byte* frame = (byte*)vram + (secondframe ? 40960 : 0);
|
|
ushort* palette = (ushort*)palram;
|
|
|
|
for (int y = 0; y < 160; y++)
|
|
{
|
|
for (int x = 0; x < 240; x++)
|
|
*pixels++ = ColorConversion[palette[*frame++]];
|
|
pixels -= 240;
|
|
pixels += pitch;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawM5BG(MobileBmpView mbv, bool secondframe)
|
|
{
|
|
mbv.ChangeAllSizes(160, 128);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
ushort* frame = (ushort*)vram + (secondframe ? 20480 : 0);
|
|
|
|
for (int y = 0; y < 128; y++)
|
|
{
|
|
for (int x = 0; x < 160; x++)
|
|
*pixels++ = ColorConversion[*frame++];
|
|
pixels -= 160;
|
|
pixels += pitch;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
static readonly int[, ,] spritesizes = { { { 1, 1 }, { 2, 2 }, { 4, 4 }, { 8, 8 } }, { { 2, 1 }, { 4, 1 }, { 4, 2 }, { 8, 4 } }, { { 1, 2 }, { 1, 4 }, { 2, 4 }, { 4, 8 } } };
|
|
|
|
unsafe void DrawSprite(int* dest, int pitch, ushort* sprite, byte* tiles, bool twodee)
|
|
{
|
|
ushort attr0 = sprite[0];
|
|
ushort attr1 = sprite[1];
|
|
ushort attr2 = sprite[2];
|
|
|
|
if (!attr0.Bit(8) && attr0.Bit(9))
|
|
return; // 2x with affine off
|
|
|
|
int shape = attr0 >> 14;
|
|
if (shape == 3)
|
|
return;
|
|
int size = attr1 >> 14;
|
|
int tw = spritesizes[shape, size, 0];
|
|
int th = spritesizes[shape, size, 1];
|
|
|
|
bool eightbit = attr0.Bit(13);
|
|
bool hflip = attr1.Bit(12);
|
|
bool vflip = attr1.Bit(13);
|
|
|
|
ushort* palette = (ushort*)palram + 256;
|
|
if (!eightbit)
|
|
palette += attr2 >> 12 << 4;
|
|
|
|
int tileindex = eightbit ? attr2 & 1022 : attr2 & 1023;
|
|
int tilestride = twodee ? 1024 - tw * (eightbit ? 64 : 32) : 0;
|
|
|
|
// see if the sprite would read past the end of vram, and skip it if it would
|
|
{
|
|
int tileend;
|
|
|
|
if (!twodee)
|
|
tileend = tileindex + tw * th * (eightbit ? 2 : 1);
|
|
else
|
|
tileend = tileindex + tw * (eightbit ? 2 : 1) + (th - 1) * 32;
|
|
|
|
if (tileend > 1024)
|
|
return;
|
|
}
|
|
|
|
tiles += 32 * tileindex;
|
|
|
|
if (vflip)
|
|
dest += pitch * 8 * (th - 1);
|
|
if (hflip)
|
|
dest += 8 * (tw - 1);
|
|
for (int ty = 0; ty < th; ty++)
|
|
{
|
|
for (int tx = 0; tx < tw; tx++)
|
|
{
|
|
if (eightbit)
|
|
{
|
|
DrawTile256(dest, pitch, tiles, palette, hflip, vflip);
|
|
tiles += 64;
|
|
}
|
|
else
|
|
{
|
|
DrawTile16(dest, pitch, tiles, palette, hflip, vflip);
|
|
tiles += 32;
|
|
}
|
|
if (hflip)
|
|
dest -= 8;
|
|
else
|
|
dest += 8;
|
|
}
|
|
if (hflip)
|
|
dest += tw * 8;
|
|
else
|
|
dest -= tw * 8;
|
|
if (vflip)
|
|
dest -= pitch * 8;
|
|
else
|
|
dest += pitch * 8;
|
|
tiles += tilestride;
|
|
}
|
|
}
|
|
|
|
unsafe void DrawSprites(MobileBmpView mbv)
|
|
{
|
|
mbv.BmpView.ChangeBitmapSize(1024, 512);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
// Clear()
|
|
Win32.MemSet(lockdata.Scan0, 0xff, (uint)(lockdata.Height * lockdata.Stride));
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
ushort* sprites = (ushort*)oam;
|
|
byte* tiles = (byte*)vram + 65536;
|
|
|
|
ushort dispcnt = ((ushort*)mmio)[0];
|
|
bool twodee = !dispcnt.Bit(6);
|
|
|
|
for (int sy = 0; sy < 8; sy++)
|
|
{
|
|
for (int sx = 0; sx < 16; sx++)
|
|
{
|
|
DrawSprite(pixels, pitch, sprites, tiles, twodee);
|
|
pixels += 64;
|
|
sprites += 4;
|
|
}
|
|
pixels -= 1024;
|
|
pixels += pitch * 64;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawPalette(MobileBmpView mbv, bool sprite)
|
|
{
|
|
mbv.BmpView.ChangeBitmapSize(16, 16);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
ushort* palette = (ushort*)palram + (sprite ? 256 : 0);
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
{
|
|
for (int i = 0; i < 16; i++)
|
|
*pixels++ = ColorConversion[*palette++];
|
|
pixels -= 16;
|
|
pixels += pitch;
|
|
}
|
|
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawTileRange(int* dest, int pitch, byte* tiles, ushort* palette, int tw, int th, bool eightbit)
|
|
{
|
|
for (int ty = 0; ty < th; ty++)
|
|
{
|
|
for (int tx = 0; tx < tw; tx++)
|
|
{
|
|
if (eightbit)
|
|
{
|
|
DrawTile256(dest, pitch, tiles, palette, false, false);
|
|
dest += 8;
|
|
tiles += 64;
|
|
}
|
|
else
|
|
{
|
|
DrawTile16(dest, pitch, tiles, palette, false, false);
|
|
dest += 8;
|
|
tiles += 32;
|
|
}
|
|
}
|
|
dest -= tw * 8;
|
|
dest += pitch * 8;
|
|
}
|
|
}
|
|
|
|
unsafe void DrawSpriteTiles(MobileBmpView mbv, bool tophalfonly, bool eightbit)
|
|
{
|
|
int tw = eightbit ? 16 : 32;
|
|
int th = tophalfonly ? 16 : 32;
|
|
|
|
mbv.BmpView.ChangeBitmapSize(tw * 8, 256);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
byte* tiles = (byte*)vram + 65536;
|
|
// TODO: palette changing (in 4 bit mode anyway)
|
|
ushort* palette = (ushort*)palram + 256;
|
|
|
|
if (tophalfonly)
|
|
{
|
|
Win32.MemSet(lockdata.Scan0, 0xff, (uint)(128 * lockdata.Stride));
|
|
pixels += 128 * pitch;
|
|
tiles += 16384;
|
|
}
|
|
DrawTileRange(pixels, pitch, tiles, palette, tw, th, eightbit);
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
unsafe void DrawBGTiles(MobileBmpView mbv, bool eightbit)
|
|
{
|
|
int tw = eightbit ? 32 : 64;
|
|
int th = 32;
|
|
|
|
mbv.BmpView.ChangeBitmapSize(tw * 8, th * 8);
|
|
Bitmap bmp = mbv.BmpView.BMP;
|
|
var lockdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
|
|
|
int* pixels = (int*)lockdata.Scan0;
|
|
int pitch = lockdata.Stride / sizeof(int);
|
|
|
|
byte* tiles = (byte*)vram;
|
|
// TODO: palette changing (in 4 bit mode anyway)
|
|
ushort* palette = (ushort*)palram;
|
|
DrawTileRange(pixels, pitch, tiles, palette, tw, th, eightbit);
|
|
bmp.UnlockBits(lockdata);
|
|
mbv.BmpView.Refresh();
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
unsafe void DrawEverything()
|
|
{
|
|
ushort dispcnt = ((ushort*)mmio)[0];
|
|
|
|
int bgmode = dispcnt & 7;
|
|
switch (bgmode)
|
|
{
|
|
case 0:
|
|
if (bg0.ShouldDraw) DrawTextBG(0, bg0);
|
|
if (bg1.ShouldDraw) DrawTextBG(1, bg1);
|
|
if (bg2.ShouldDraw) DrawTextBG(2, bg2);
|
|
if (bg3.ShouldDraw) DrawTextBG(3, bg3);
|
|
if (bgtiles16.ShouldDraw) DrawBGTiles(bgtiles16, false);
|
|
if (bgtiles256.ShouldDraw) DrawBGTiles(bgtiles256, true);
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, false, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, false, true);
|
|
break;
|
|
case 1:
|
|
if (bg0.ShouldDraw) DrawTextBG(0, bg0);
|
|
if (bg1.ShouldDraw) DrawTextBG(1, bg1);
|
|
if (bg2.ShouldDraw) DrawAffineBG(2, bg2);
|
|
if (bg3.ShouldDraw) bg3.BmpView.Clear();
|
|
if (bgtiles16.ShouldDraw) DrawBGTiles(bgtiles16, false);
|
|
if (bgtiles256.ShouldDraw) DrawBGTiles(bgtiles256, true);
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, false, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, false, true);
|
|
break;
|
|
case 2:
|
|
if (bg0.ShouldDraw) bg0.BmpView.Clear();
|
|
if (bg1.ShouldDraw) bg1.BmpView.Clear();
|
|
if (bg2.ShouldDraw) DrawAffineBG(2, bg2);
|
|
if (bg3.ShouldDraw) DrawAffineBG(3, bg3);
|
|
// while there are no 4bpp tiles possible in mode 2, there might be some in memory
|
|
// due to midframe mode switching. no real reason not to display them if that's
|
|
// what the user wants to see
|
|
if (bgtiles16.ShouldDraw) DrawBGTiles(bgtiles16, false);
|
|
if (bgtiles256.ShouldDraw) DrawBGTiles(bgtiles256, true);
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, false, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, false, true);
|
|
break;
|
|
case 3:
|
|
if (bg0.ShouldDraw) bg0.BmpView.Clear();
|
|
if (bg1.ShouldDraw) bg1.BmpView.Clear();
|
|
if (bg2.ShouldDraw) DrawM3BG(bg2);
|
|
if (bg3.ShouldDraw) bg3.BmpView.Clear();
|
|
if (bgtiles16.ShouldDraw) bgtiles16.BmpView.Clear();
|
|
if (bgtiles256.ShouldDraw) bgtiles256.BmpView.Clear();
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, true, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, true, true);
|
|
break;
|
|
//in modes 4, 5, bg3 is repurposed as bg2 invisible frame
|
|
case 4:
|
|
if (bg0.ShouldDraw) bg0.BmpView.Clear();
|
|
if (bg1.ShouldDraw) bg1.BmpView.Clear();
|
|
if (bg2.ShouldDraw) DrawM4BG(bg2, dispcnt.Bit(4));
|
|
if (bg3.ShouldDraw) DrawM4BG(bg3, !dispcnt.Bit(4));
|
|
if (bgtiles16.ShouldDraw) bgtiles16.BmpView.Clear();
|
|
if (bgtiles256.ShouldDraw) bgtiles256.BmpView.Clear();
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, true, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, true, true);
|
|
break;
|
|
case 5:
|
|
if (bg0.ShouldDraw) bg0.BmpView.Clear();
|
|
if (bg1.ShouldDraw) bg1.BmpView.Clear();
|
|
if (bg2.ShouldDraw) DrawM5BG(bg2, dispcnt.Bit(4));
|
|
if (bg3.ShouldDraw) DrawM5BG(bg3, !dispcnt.Bit(4));
|
|
if (bgtiles16.ShouldDraw) bgtiles16.BmpView.Clear();
|
|
if (bgtiles256.ShouldDraw) bgtiles256.BmpView.Clear();
|
|
if (sptiles16.ShouldDraw) DrawSpriteTiles(sptiles16, true, false);
|
|
if (sptiles256.ShouldDraw) DrawSpriteTiles(sptiles256, true, true);
|
|
break;
|
|
default:
|
|
// shouldn't happen, but shouldn't be our problem either
|
|
if (bg0.ShouldDraw) bg0.BmpView.Clear();
|
|
if (bg1.ShouldDraw) bg1.BmpView.Clear();
|
|
if (bg2.ShouldDraw) bg2.BmpView.Clear();
|
|
if (bg3.ShouldDraw) bg3.BmpView.Clear();
|
|
if (bgtiles16.ShouldDraw) bgtiles16.BmpView.Clear();
|
|
if (bgtiles256.ShouldDraw) bgtiles256.BmpView.Clear();
|
|
if (sptiles16.ShouldDraw) sptiles16.BmpView.Clear();
|
|
if (sptiles256.ShouldDraw) sptiles256.BmpView.Clear();
|
|
break;
|
|
}
|
|
|
|
if (bgpal.ShouldDraw) DrawPalette(bgpal, false);
|
|
if (sppal.ShouldDraw) DrawPalette(sppal, true);
|
|
|
|
if (sprites.ShouldDraw) DrawSprites(sprites);
|
|
}
|
|
|
|
MobileBmpView MakeMBVWidget(string text, int w, int h)
|
|
{
|
|
var mbv = new MobileBmpView();
|
|
mbv.Text = text;
|
|
mbv.BmpView.Text = text;
|
|
mbv.TopLevel = false;
|
|
mbv.ChangeViewSize(w, h);
|
|
mbv.BmpView.Clear();
|
|
panel1.Controls.Add(mbv);
|
|
listBoxWidgets.Items.Add(mbv);
|
|
return mbv;
|
|
}
|
|
|
|
MobileDetailView MakeMDVWidget(string text, int w, int h)
|
|
{
|
|
var mdv = new MobileDetailView();
|
|
mdv.Text = text;
|
|
mdv.BmpView.Text = text;
|
|
mdv.TopLevel = false;
|
|
mdv.ClientSize = new Size(w, h);
|
|
mdv.BmpView.Clear();
|
|
panel1.Controls.Add(mdv);
|
|
listBoxWidgets.Items.Add(mdv);
|
|
return mdv;
|
|
}
|
|
|
|
void GenerateWidgets()
|
|
{
|
|
listBoxWidgets.BeginUpdate();
|
|
bg0 = MakeMBVWidget("Background 0", 256, 256);
|
|
bg1 = MakeMBVWidget("Background 1", 256, 256);
|
|
bg2 = MakeMBVWidget("Background 2", 256, 256);
|
|
bg3 = MakeMBVWidget("Background 3", 256, 256);
|
|
bgpal = MakeMBVWidget("Background Palettes", 256, 256);
|
|
sppal = MakeMBVWidget("Sprite Palettes", 256, 256);
|
|
sprites = MakeMBVWidget("Sprites", 1024, 512);
|
|
sptiles16 = MakeMBVWidget("Sprite Tiles (4bpp)", 256, 256);
|
|
sptiles256 = MakeMBVWidget("Sprite Tiles (8bpp)", 128, 256);
|
|
bgtiles16 = MakeMBVWidget("Background Tiles (4bpp)", 512, 256);
|
|
bgtiles256 = MakeMBVWidget("Background Tiles (8bpp)", 256, 256);
|
|
// todo: finish these
|
|
// MakeMDVWidget("Details", 128, 192);
|
|
// memory = MakeMDVWidget("Details - Memory", 128, 192);
|
|
listBoxWidgets.EndUpdate();
|
|
|
|
foreach (var f in listBoxWidgets.Items)
|
|
{
|
|
Form form = (Form)f;
|
|
// close becomes hide
|
|
form.FormClosing += delegate(object sender, FormClosingEventArgs e)
|
|
{
|
|
e.Cancel = true;
|
|
listBoxWidgets.Items.Add(sender);
|
|
(sender as Form).Hide();
|
|
};
|
|
// hackish, and why doesn't winforms handle this directly?
|
|
BringToFrontHack(form, form);
|
|
}
|
|
}
|
|
|
|
static void BringToFrontHack(Control c, Control top)
|
|
{
|
|
c.Click += (o, e) => top.BringToFront();
|
|
if (c.HasChildren)
|
|
foreach (Control cc in c.Controls)
|
|
BringToFrontHack(cc, top);
|
|
}
|
|
|
|
|
|
public void Restart()
|
|
{
|
|
var mem = gba.GetMemoryAreas();
|
|
vram = mem.vram;
|
|
palram = mem.palram;
|
|
oam = mem.oam;
|
|
mmio = mem.mmio;
|
|
|
|
_cbscanlineEmu = 500; // force an update
|
|
UpdateValues();
|
|
}
|
|
|
|
public void NewUpdate(ToolFormUpdateType type) { }
|
|
|
|
/// <summary>belongs in ToolsBefore</summary>
|
|
public void UpdateValues()
|
|
{
|
|
if (!IsHandleCreated || IsDisposed)
|
|
return;
|
|
|
|
if (_cbscanlineEmu != _cbscanline)
|
|
{
|
|
_cbscanlineEmu = _cbscanline;
|
|
if (!_cbscanline.HasValue) // manual, deactivate callback
|
|
{
|
|
gba.SetScanlineCallback(null, 0);
|
|
}
|
|
else
|
|
{
|
|
gba.SetScanlineCallback(DrawEverything, _cbscanline.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void FastUpdate()
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
private void GBAGPUView_Load(object sender, EventArgs e)
|
|
{
|
|
}
|
|
|
|
void ShowSelectedWidget()
|
|
{
|
|
if (listBoxWidgets.SelectedItem != null)
|
|
{
|
|
var form = listBoxWidgets.SelectedItem as Form;
|
|
form.Show();
|
|
form.BringToFront();
|
|
listBoxWidgets.Items.RemoveAt(listBoxWidgets.SelectedIndex);
|
|
}
|
|
}
|
|
|
|
private void buttonShowWidget_Click(object sender, EventArgs e)
|
|
{
|
|
ShowSelectedWidget();
|
|
}
|
|
|
|
private void listBoxWidgets_DoubleClick(object sender, EventArgs e)
|
|
{
|
|
ShowSelectedWidget();
|
|
}
|
|
|
|
#region refresh control
|
|
|
|
private int? _cbscanline = null;
|
|
private int? _cbscanlineEmu = 500;
|
|
|
|
private void RecomputeRefresh()
|
|
{
|
|
if (radioButtonScanline.Checked)
|
|
{
|
|
hScrollBar1.Enabled = true;
|
|
buttonRefresh.Enabled = false;
|
|
_cbscanline = (hScrollBar1.Value + 160) % 228;
|
|
}
|
|
else if (radioButtonManual.Checked)
|
|
{
|
|
hScrollBar1.Enabled = false;
|
|
buttonRefresh.Enabled = true;
|
|
_cbscanline = null;
|
|
}
|
|
}
|
|
|
|
private void radioButtonFrame_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
RecomputeRefresh();
|
|
}
|
|
|
|
private void radioButtonScanline_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
RecomputeRefresh();
|
|
}
|
|
|
|
private void hScrollBar1_ValueChanged(object sender, EventArgs e)
|
|
{
|
|
_cbscanline = (hScrollBar1.Value + 160) % 228;
|
|
radioButtonScanline.Text = $"Scanline {_cbscanline}";
|
|
}
|
|
|
|
private void radioButtonManual_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
RecomputeRefresh();
|
|
}
|
|
|
|
private void buttonRefresh_Click(object sender, EventArgs e)
|
|
{
|
|
DrawEverything();
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void GBAGPUView_FormClosed(object sender, FormClosedEventArgs e)
|
|
{
|
|
if (gba != null)
|
|
{
|
|
gba.SetScanlineCallback(null, 0);
|
|
}
|
|
}
|
|
|
|
#region copy to clipboard
|
|
|
|
private void timerMessage_Tick(object sender, EventArgs e)
|
|
{
|
|
timerMessage.Stop();
|
|
labelClipboard.Text = "CTRL + C: Copy under mouse to clipboard.";
|
|
}
|
|
|
|
private void GBAGPUView_KeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if (ModifierKeys.HasFlag(Keys.Control) && e.KeyCode == Keys.C)
|
|
{
|
|
// find the control under the mouse
|
|
Point m = Cursor.Position;
|
|
Control top = this;
|
|
Control found;
|
|
do
|
|
{
|
|
found = top.GetChildAtPoint(top.PointToClient(m));
|
|
top = found;
|
|
} while (found != null && found.HasChildren);
|
|
|
|
if (found is BmpView)
|
|
{
|
|
Clipboard.SetImage((found as BmpView).BMP);
|
|
labelClipboard.Text = $"{found.Text} copied to clipboard.";
|
|
timerMessage.Stop();
|
|
timerMessage.Start();
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|