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

相關文章
相關標籤/搜索