WindowsPhone-GameBoy模拟器开发五--使用XNA初略实现Gameboy显示系统

开篇前,最近弄了个空间,你们不嫌弃的话能够上去讨论讨论Jwindows

http://www.lihengzhe.cn缓存

这一次,就来简单地实现gameboy的实现机制。先说一下本次内容涉及到的技术,其实也就一项—XNA,用XNA来完成咱们最后的显示(windows phone的开发嘛,也只能用XNA了)。dom

思路大概是这样的:首先经过gameboy的内存取出图像的图块映射数据,经过映射来获取像素的数据,因为像素的数据实际上又是一个颜色值在调色板寄存器上的索引,因此再获取到调色板寄存器的数据,结合该像素的颜色索引值,最终肯定该像素的颜色数据。ide

思路清楚了以后就要说一下此次要用到的技术了,首先决定使用一个2D贴图来看成Gb的背景来显示,2D贴图的数据就是从GB中获取的颜色数据。在XNA中,颜色是用32位数来表示的,看下图测试

image

经过上一篇文章知道,GB中模拟的颜色总共有4种ui

3d

模拟的颜色指针

0code

[255, 255, 255]orm

1

[192, 192, 192]

2

[96, 96, 96]

3

[0, 0, 0]

用对应的代码表示成

Dictionary<int,UInt32> ColorMap =new Dictionary<int,uint>(){{0, 0xFFFFFFFF},{1, 0xFFC0C0C0},{2, 0xFF606060},{3, 0xFF000000}};

这里最开始的8位永远是FF,由于在Gb的显示系统中没有实现alpha通道(就是透明度),好,接下来先看看运行的结果:

image

能够看到“内存”上方的框内有些黑黑白白的东西,其实这整个一个框是一个picturebox控件,里面这些黑黑白白的东西就是从Gb内存中显示出来的图像数据,固然,如今里面都是些测试数据,并且Gb的Cpu指令也都尚未实现,不过这不是重点了,重点是终于实现了从GameBoy内存中读取出图像数据并显示了。

下面进一步说明下是怎么实现的,供你们一块儿交流指点:

public Form1()
{
InitializeComponent();
PresentationParameters mPP = new PresentationParameters();
mPP.BackBufferHeight = pcbBackground.Height;
mPP.BackBufferWidth = pcbBackground.Width;
mPP.DeviceWindowHandle = pcbBackground.Handle;
mPP.DepthStencilFormat = DepthFormat.Depth24;
mPP.PresentationInterval = PresentInterval.Immediate;
mPP.IsFullScreen = false;
mGraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, mPP);
mTexture = new Texture2D(mGraphicsDevice, 256, 256);
mSpriteBatch = new SpriteBatch(mGraphicsDevice);
mGBConsole.GPU.SetTheMapData(mLogo);
}

由于此次是在窗体程序中用XNA来显示,因此在初始化XNA中使用的图像设备(GraphicsDevice)对象时要告诉该对象在什么地方上进行画图,把picturebox变量的对象指针传给它的初始化参数中mPP.DeviceWindowHandle = pcbBackground.Handle;其中,这一句 mGBConsole.GPU.SetTheMapData(mLogo);是用来初始化映射数据的,固然这只是测试数据,之后会删去。

而后在窗体中放置一个timer控件,用来定时刷新咱们的GB“屏幕”。

private void timer1_Tick(object sender, EventArgs e)
{
mGraphicsDevice.Clear(Framework.Color.White);
mGraphicsDevice.Textures[0] = null;
mFrameData = mGBConsole.OutputFrame();
mTexture.SetData(mFrameData);
mSpriteBatch.Begin();
mSpriteBatch.Draw(mTexture, new Framework.Vector2(0, 0), Framework.Color.White);
mSpriteBatch.End();
mGraphicsDevice.Present();
}

 

这都是很常规的XNA的绘图代码了,其中mFrameData = mGBConsole.OutputFrame();这一句是从GB的内存中获取到视屏图像的数据。跟进到代码中,最后能够去到一个GameboyGPU的类,这个就是用来处理视频数据的类了,详细的请看代码中的注释:

public class GameboyGPU
{
readonly int TileSize = 8 * 2;//16个地址,每一个地址存8位数据,共16KB
byte[] mVRAM = new byte[8 * 1024];//8KB video ram
byte PalleteRegister = 0;

//for test,给内存中的图像块的数据区随机的放上一些数据,该方法会在之后删除 public void InitialData() { Random mRandom = new Random(); for (int i = 0; i < 0x17FF;i++ ) { mVRAM[i] = (byte)(mRandom.Next() % 0xFF); } for (int i = 0x1800; i < 0x1FFF; i++) { mVRAM[i] = (byte)(mRandom.Next() % 0xFF); } } public byte[] Tiles1Data { get { return mVRAM.Take(0xFFF).ToArray(); }} public byte[] Tiles1Map { get { return mVRAM.Skip(0x1800).Take(0x3FF).ToArray(); } } public byte[] Tiles2Map { get { return mVRAM.Skip(0x1C00).Take(0x3FF).ToArray(); } } //设置图像映射数据(总感受这个方法不是很好,之后要改改) public void SetTheMapData(byte[] aMapData) { Buffer.BlockCopy(aMapData, 0, mVRAM, 0x1800, aMapData.Length); } public byte[] GetSingleTileData(int aTileIndex) { return Tiles1Data.Skip(aTileIndex * TileSize).Take(TileSize).ToArray(); } //从这个方法开始处理图像数据,传入内存中视频缓存部分的数据和调色板的数据 public UInt32[] OuputFrameData(byte[] aMemoryData, byte aPalleteRegister) { mVRAM = aMemoryData; PalleteRegister = aPalleteRegister; InitialData();//为测试初始化一些数据 UInt32[] mResult = new UInt32[256 * 256];//每帧图像的大小为256*256个像素 int mResultCount = 0; foreach (byte tileIndex in Tiles1Map) { //根据映射获取到图块数据,再根据图块数据获取到每一个像素的颜色值 uint[] mTileColorData = GetColorData( GetSingleTileData(tileIndex)); Buffer.BlockCopy(mTileColorData, 0, mResult, mResultCount, mTileColorData.Length); mResultCount += mTileColorData.Length; } return mResult; } //获取每一个图块的颜色数据 public UInt32[] GetColorData(byte[] aTileData) { if (aTileData.Length % 2 != 0) throw new Exception("VRAM data error"); UInt32[] mResultData = new UInt32[8*8]; int mColorCounter=0; for (int i = 0; i < aTileData.Length - 1; i++) { //由上一篇文章分析得,图块的每行有8个像素,使用两个字节来表示,一个字节表示每一个像素的高位,另外一个字节用来表示每一个像素的低位,两个位能表示的数据范围为0—3共4个数,这个数是一个调色板的索引代码,用这个代码能够去到调色板中查出实际的颜色值。 uint mHigh = aTileData[i]; uint mLow = aTileData[++i]; //caculate the color index for (int j = 7; j >=0; j--) { int mHighValue = (mHigh & (16*(j+1)))==(16*(j+1))?1:0; int mLowValue = (mLow & (16 * (j + 1))) == (16 * (j + 1)) ? 1 : 0; mResultData[mColorCounter] = GetColorValue( (uint)mHighValue*2+ (uint)mLowValue);//表示高位的数据要乘以2至关于左移一位,从个位变成十位 if (mColorCounter > 8*8) throw new Exception("Color data error"); mColorCounter++; } } return mResultData; } //查询调色板中的颜色获取实际的颜色值 public UInt32 GetColorValue(uint aColorIndex) { try { //调色板共八位,由右到左每两位划分为一组,共4组,传进来的索引值就是用来索引取哪一组的数据。可是没个组中存放的其实仍是一个索引值,用来到颜色表中查找实际的颜色数据,由于每2位划分为一组,索引每组能表示4个索引值,正好对应着Gb能表示的4种颜色 switch (aColorIndex) { case 0://获取第1组的颜色索引 return ColorConfig.ColorMap[PalleteRegister&3]; case 1://第2组 return ColorConfig.ColorMap[(PalleteRegister&12)>>2]; case 2: /第3组 return ColorConfig.ColorMap[(PalleteRegister&48)>>4]; case 3: /第4组 return ColorConfig.ColorMap[(PalleteRegister&192) >> 6]; default: return 0; } } catch { return 0; } } }

 

这里说一下,为何从图块中映射过来的数据不直接映射到颜色表而是映射到调色板上

经过一幅图来表示一下这个家伙的映射关系:

我的以为,其实在硬件中,3中的数据应该是直接固化在硬件上的,由于gameboy中模拟的颜色都是固定了的,因此这部分数据是没法修改的。在这个前提下,若是不使用调色板,在要对画面进行修改的时候,就须要修改1中的数据,可是,有些游戏特效,好比画面的反色,只是修改像素的颜色,也须要从新刷新内存中的每一个像素点的数据,而若是使用了调色板的话,只须要修改调色板中的数据便可。对于gameboy而言,整版数据有256*256个点的数据须要修改,最坏状况下整个图块区域的数据都要修改,共4KB的数据,而使用了调色板的话,最多只须要修改1Byte的数据,差了4000倍,在速度上快了不少。

好了,这就是比较粗鲁的Gb显示系统的代码了,此次的代码还有很是多的,如显示的画面会不停地闪烁,可是对于观察指令的运行状况应该已经足够了,接下来就是实现cpu的指令了。

代码已上传到codeplex,不嫌弃的话,欢迎你们指点:https://emulatorwp.codeplex.com/SourceControl/list/changesets

相关文章
相关标签/搜索