夢開始的地方——FC游戲開發(fā)指南(7)高性能的秘密:DMA
(本系列是一個(gè)回歸電子游戲原點(diǎn)的特別系列,作者 @goodorc_gamedev)
上期鏈接:https://www.bilibili.com/read/cv13722007
上次文章介紹了精靈的概念。按照我們玩FC游戲的的經(jīng)驗(yàn),F(xiàn)C同屏處理幾十個(gè)精靈應(yīng)該不是難事。比如《沙羅曼蛇》,在模擬器里將背景層關(guān)閉,就可以看到同屏有幾十個(gè)精靈同時(shí)在屏幕上運(yùn)動(dòng):(一個(gè)敵人是由4個(gè)左右的精靈組成的,所以下圖至少有30個(gè)以上的精靈)

但是當(dāng)我用上次視頻的方法,將精靈拷貝到顯卡,發(fā)現(xiàn)只要超過10個(gè)精靈,就無法正常顯示了。
這是為什么呢?
還是因?yàn)镕C的CPU太慢了,速度不到PPU的三分之一,如果每幀通過0x2004端口循環(huán)拷貝幾十個(gè)精靈,那么CPU會死給你看。
所以FC高性能的秘密,就在于——精靈數(shù)據(jù)能夠以極高的速度傳輸給PPU,每秒輕松傳遞幾十次,實(shí)現(xiàn)數(shù)十個(gè)精靈每秒幾十次的移動(dòng)、變換。
1、DMA(Direct Memory Access)
百度百科上DMA的介紹很直截了當(dāng):

DMA(Direct Memory Access,直接存儲器訪問) 是所有現(xiàn)代電腦的重要特色,它允許不同速度的硬件裝置來溝通,而不需要依賴于CPU的大量中斷負(fù)載。
簡單來說,就是有一個(gè)專用硬件,它受CPU控制。只要CPU告訴它,把內(nèi)存里的哪一段地址的數(shù)據(jù)搬到PPU,它就會以極高的速度將內(nèi)存數(shù)據(jù)搬進(jìn)PPU。而且它走的還是一條“高速公路”,叫做DMA總線。
在DMA傳輸階段,CPU還可以騰出手做其它事情。
2、基于DMA的渲染流程
我按照參考資料,基于DMA改善了FC渲染流程,如圖:

解釋:
很多精靈對象,用struct數(shù)組表示,數(shù)組做成固定長度的即可。
★ 注意精靈數(shù)組的起始地址是專門指定的,下面會提到。
每一幀更新每個(gè)精靈對象的tile、位置等等。不需要的精靈填0即可。
在原來的程序中,每一幀要通過load_SP函數(shù)將精靈拷貝到PPU。但這次改為直接控制DMA,作用一樣是把很多精靈對象更新到PPU。
一旦想清楚了,就是這么簡單。
DMA傳輸?shù)腃語言寫法:
參數(shù)page=2,start=0是怎么回事呢?
任天堂文檔里是這么說的:
$4014 寫
DMA 訪問精靈 RAM:通過寫一個(gè)值 xx 到這個(gè)端口,引起 CPU 內(nèi)存地址為$xx00-$xxFF 的區(qū)域傳送到精靈內(nèi)存
簡單解釋:端口0x4014是一個(gè)DMA控制端口,往里寫一個(gè)0~255的數(shù)字,就會啟動(dòng)DMA,將內(nèi)存中0xXX00 ~ 0xXXFF的數(shù)據(jù)傳輸?shù)絇PU的精靈區(qū)。比如寫上2,就會自動(dòng)拷貝0x0200 ~ 0x02FF這一段精靈數(shù)據(jù)。
內(nèi)存中一整塊256字節(jié)的區(qū)域,比如從0x1A00 ~ 0x1AFF ,稱之為一頁(page)。
另一個(gè)參數(shù)start應(yīng)該是偏移量,暫時(shí)填0即可。
3、C語言特性——直接指定變量的內(nèi)存地址
我們一般學(xué)習(xí)C語言都是用PC機(jī)學(xué)習(xí),內(nèi)存足夠用,內(nèi)存地址也是由操作系統(tǒng)分配。但到了FC這種單片機(jī)系統(tǒng),就要借用更高級的C技巧了。
既然DMA要求必須事先說定精靈數(shù)據(jù)的地址,而且說好是傳一整頁。這就要求必須把精靈放在指定的地方,比如從0x0200開始,一直放到0x02ff,共256字節(jié)。每個(gè)精靈對象4字節(jié)(tile, x, y, attr),一頁可以放64個(gè)精靈。
寫法像這樣:
如果這樣定義,初始化的寫法:
只要用到精靈數(shù)據(jù)的地方,都要用類似的寫法修改。也許有更好的寫法。
至此,大家既可以參考第一篇文章放出的《spitfire》射擊游戲項(xiàng)目,也可以在《移動(dòng)》簡單項(xiàng)目中修改。
4、實(shí)例
《移動(dòng)》的測試項(xiàng)目,加入DMA后代碼修改如下:
上面這段代碼與之前的效果相同。讀者可以給它加上發(fā)射子彈的功能,看看最多能同時(shí)發(fā)射多少子彈。
5、將DMA指令寫成匯編
像DMA這種每幀調(diào)用的必要操作,寫在C語言循環(huán)里仍然不是最佳選擇。最好的辦法是直接寫成匯編,連函數(shù)調(diào)用也省了。
修改之前看過的crt0.s文件,找到中間的標(biāo)簽nmi:
nmi是一個(gè)特殊的中斷,在電視機(jī)的每幀渲染完畢后觸發(fā),在這里做一些每幀都需要做的DMA工作再合適不過了。
在inc tickcount那句代碼之前,插入DMA操作代碼:
編寫完畢后,應(yīng)刪除C語言中的SP_DMA(2,0)函數(shù)調(diào)用,顯示效果不變。
至此關(guān)于精靈Sprite的討論告一段落,下一期將介紹FC的背景地圖與卷軸功能。
(本文作者?@goodorc_gamedev。歡迎加入游戲開發(fā)群歡樂攪基:1082025059
對游戲開發(fā)感興趣的童鞋可戳這里進(jìn)一步了解:http://www.levelpp.com/)