BizHawk/BizHawk.MultiClient/GBAtools/GBAGPUView.cs

580 lines
16 KiB
C#
Raw Normal View History

2012-11-26 23:16:31 +00:00
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;
using BizHawk.MultiClient.GBtools;
2012-11-26 23:16:31 +00:00
namespace BizHawk.MultiClient.GBAtools
{
public partial class GBAGPUView : Form
{
Emulation.Consoles.Nintendo.GBA.GBA gba;
// emulator memory areas
IntPtr vram;
IntPtr oam;
IntPtr mmio;
IntPtr palram;
// color conversion to RGB888
int[] ColorConversion;
2012-11-28 20:33:07 +00:00
MobileBmpView bg0, bg1, bg2, bg3, bgpal, sppal, sprites;
2012-11-26 23:16:31 +00:00
public GBAGPUView()
{
InitializeComponent();
// TODO: hook up something
// we do this twice to avoid having to & 0x7fff with every color
int[] tmp = Emulation.Consoles.GB.GBColors.GetLut(Emulation.Consoles.GB.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);
GenerateWidgets();
}
#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();
}
2012-11-28 20:33:07 +00:00
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 tw, th;
int shape = attr0 >> 14;
if (shape == 3)
return;
int size = attr1 >> 14;
tw = spritesizes[shape, size, 0];
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;
if (!eightbit)
tiles += 32 * (attr2 & 1023);
else
tiles += 32 * (attr2 & 1022);
int tilestride = 0;
if (twodee)
tilestride = 1024 - tw * (eightbit ? 64 : 32);
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();
}
2012-11-27 21:13:13 +00:00
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();
}
#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);
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();
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);
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();
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));
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));
break;
}
2012-11-27 21:13:13 +00:00
if (bgpal.ShouldDraw) DrawPalette(bgpal, false);
if (sppal.ShouldDraw) DrawPalette(sppal, true);
2012-11-28 20:33:07 +00:00
if (sprites.ShouldDraw) DrawSprites(sprites);
}
MobileBmpView MakeWidget(string text, int w, int h)
{
var mbv = new MobileBmpView();
mbv.Text = text;
mbv.TopLevel = false;
mbv.ChangeViewSize(w, h);
mbv.bmpView.Clear();
mbv.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
listBoxWidgets.Items.Add(sender);
(sender as Form).Hide();
};
2012-11-28 20:33:07 +00:00
// hackish, and why doesn't winforms handle this directly?
mbv.bmpView.Click += (o, e) => (o as Control).Parent.BringToFront();
panel1.Controls.Add(mbv);
listBoxWidgets.Items.Add(mbv);
return mbv;
}
void GenerateWidgets()
{
listBoxWidgets.BeginUpdate();
bg0 = MakeWidget("Background 0", 256, 256);
bg1 = MakeWidget("Background 1", 256, 256);
bg2 = MakeWidget("Background 2", 256, 256);
bg3 = MakeWidget("Background 3", 256, 256);
2012-11-27 21:13:13 +00:00
bgpal = MakeWidget("Background Palettes", 256, 256);
sppal = MakeWidget("Sprite Palettes", 256, 256);
2012-11-28 20:33:07 +00:00
sprites = MakeWidget("Sprites", 1024, 512);
listBoxWidgets.EndUpdate();
2012-11-26 23:16:31 +00:00
}
public void Restart()
{
gba = Global.Emulator as Emulation.Consoles.Nintendo.GBA.GBA;
if (gba != null)
2012-11-26 23:16:31 +00:00
{
gba.GetGPUMemoryAreas(out vram, out palram, out oam, out mmio);
2012-11-26 23:16:31 +00:00
}
else
{
if (Visible)
Close();
}
}
/// <summary>belongs in ToolsBefore</summary>
public void UpdateValues()
{
if (!this.IsHandleCreated || this.IsDisposed)
return;
if (gba != null)
{
DrawEverything();
}
}
private void GBAGPUView_Load(object sender, EventArgs e)
{
Restart();
}
void ShowSelectedWidget()
{
if (listBoxWidgets.SelectedItem != null)
{
(listBoxWidgets.SelectedItem as MobileBmpView).Show();
listBoxWidgets.Items.RemoveAt(listBoxWidgets.SelectedIndex);
}
}
private void buttonShowWidget_Click(object sender, EventArgs e)
{
ShowSelectedWidget();
}
private void listBoxWidgets_DoubleClick(object sender, EventArgs e)
{
ShowSelectedWidget();
2012-11-26 23:16:31 +00:00
}
}
}