[翻轉后臺緩沖區(qū)和屏幕緩沖區(qū)]
}
對于2D游戲或UI系統(tǒng)來說,渲染流程主要就分為以上2個階段。(這里假設游戲是全屏獨占的,對于非全屏獨占情況類似,但底層實現有較大差別)
事實上,第一階段和第二階段均可以被臟矩形算法優(yōu)化,并且臟矩形算法必須要求double buffer支持。
1. 使用臟矩形算法優(yōu)化第二階段:
翻轉前后臺緩沖區(qū),有兩種方法。一種采用把內存數據直接Blt到顯存中,另一種則是采用頁面翻轉。
之所以不采用頁面翻轉的原因在于:不是所有適配器支持頁面翻轉,且頁面翻轉需要渲染代碼直接對VGA硬件顯存讀寫,而VGA硬件總線一般會比較慢。
如果我們把物體繪制到后臺緩沖(內存),然后再通過臟矩形Blt到顯存,則可以大幅降低對VGA內存的訪問。
算法流程(假設背景不變):
設置前臺緩沖區(qū)背景(因為背景不變)
DirtyList初始化為空
while(1)
{
//邏輯代碼:
....
//渲染代碼:
后臺緩沖區(qū)清屏,并繪制后臺緩沖區(qū)背景
繪制后臺緩沖區(qū)物體
Add每個物體的矩形到DirtyList,同時對DirtyList進行切割合并
把后臺緩沖區(qū)中對應的臟矩形部分Blt到前臺緩沖
清空DirtyList,并按按當前物體重建DirtyList
}
以上算法的缺點是,每次必須清空整個后臺緩沖區(qū)然后重新繪制背景和所有物體。
但我們已假定背景不變,其實只要重畫所有物體即可(不需要重新畫背景。注:這里并不關心物體本身是否是臟的),于是得到進一步的優(yōu)化:
設置前臺緩沖區(qū)背景
設置后臺緩沖區(qū)背景
DirtyList初始化為空
while(1)
{
//邏輯代碼:
....
//渲染代碼:
Add每個物體的矩形到DirtyList,同時對DirtyList進行切割合并
把這個DirtyList中的所有矩形作為后臺緩存的剪裁區(qū)域
后臺緩沖區(qū)清屏,并繪制后臺緩沖區(qū)背景(實際上只操作了剪裁區(qū)域)
繪制后臺緩沖區(qū)物體(實際上只操作了剪裁區(qū)域)
把后臺緩沖區(qū)中對應的臟矩形部分Blt到前臺緩沖
清空DirtyList,并按按當前物體重建DirtyList
}
以上算法,在Android里的實現方式如下:
public void run() {
int frameCount = 0;
Rect dirtyRect = new Rect(0,0,0,0);
while(1){
//邏輯代碼:
//渲染代碼:
if(frameCount++==0){
canvas = Holder.lockCanvas(); //設置全屏刷新,相當于設置臟矩形區(qū)域為整個屏幕
}else{
把所有物體的矩形加入dirtyRect,得到并集 //注:Android API并不支持臟矩形鏈,但其實是可以用臟矩形鏈實現不規(guī)則的dirtyRects的
canvas = Holder.lockCanvas(dirtyRect); //設置部分刷新
}
...//清空并繪制背景
...//繪制所有物體
dirtyRect.set(0,0,0,0); //清空矩形
把所有物體的矩形加入dirtyRect,重建dirtyRect
Holder.unlockCanvasAndPost(canvas);
}
}
注1:一般來說,游戲的背景不會一成不變,但很少每一幀都在變,在這種我們只要在背景變化前重設臟矩形區(qū)域為整個屏幕區(qū)域即可。
注2:把所有物體的矩形加入dirtyRect,重建dirtyRect 這一步也可以放到下一幀的邏輯代碼前面。(因為只有在邏輯代碼里修改物體狀態(tài)才會導致臟矩形改變)
注3:Android里Holder.lockCanvas(dirtyRect)的意思是說,后面的繪圖操作使用dirtyRect作為clip區(qū)域,并且在最后swap前后臺緩沖時只刷新dirtyRect區(qū)域,并且刷新完了之后下一幀lock到的Canvas上的buffer數據除了dirtyRect以外都是上一幀畫完時的數據。(dirtyRect因為本來就要重畫,所以這塊區(qū)域Android不會保存)
2. 使用臟矩形算法優(yōu)化第一階段:
在(1.)中的算法必須要重畫所有物體,并且每幀的臟區(qū)的總和涉及了至少所有的物體(節(jié)省了第二階段的時間以及第一階段中繪制背景的時間)。而事實上游戲中的物體大多都是不變的,比如:TileGame中的樹木、房子等,
在這種情況下,我們希望這些物體只繪制到后臺緩存一次,以后除非物體狀態(tài)被改變(或者與該物體重疊的區(qū)域上的物體狀態(tài)被改變),否則就不再把這些物體再次繪制到后臺緩存上。
由于一個物體是否要繪制到后臺緩存取決于邏輯代碼(物體狀態(tài)),比如:游戲的上層邏輯 (游戲開發(fā)者)調用引擎的API接口去控制物體的位置、大小、動畫幀索引,那么游戲的下層邏輯(引擎)就需要在這些API的實現里標記這些物體是臟的(MarkDirty)。
MarkDirty()函數:
i. 標記該物體的是臟的(mDirty=true),
ii. 把該物體的矩形加入DirtyList
iii. 檢查是否有重疊的物體并標記這些重疊物體是臟的(但不需要care這些重疊區(qū)域,也不要擴大臟矩形區(qū)域)
iv. 對DirtyList進行切割合并。
注意:物體狀態(tài)的改變主要分兩種:一種只是Sprite本身圖片改變,如:按鈕被按下(image),另一種是位置、大小或可見性等屬性改變(resize),這兩種最好分開處理。
public void run() {
初始化1個背景單位到ObjectList,并call MarkDirty() //背景可以作為一個物體處理
初始化9個單位到ObjectList,并call MarkDirty() //ObjectList是Z-Order排序的
Rect dirtyRect = new Rect(0,0,0,0);
while(1)
{
//邏輯代碼(內部可能處理InputEvent):
for(all Object in ObjectList){
thisObject.Update();
//Update函數內部:
// if(某個物體只是image需要改變){
// thisObject.MarkDirty();
// }else if(某個物體resize了){
// thisObject.MarkDirty();
// thisObject.scale = xxx;
// thisObject.position = xxx;
// thisObject.image = xxx;
// thisObject.MarkDirty();
// }else{
//