編輯:關於Android編程
在任何一個系統中,無可避免的都會跟各種buffers打交道,最經典的模式就是消費-生產者模式,一個獨立的buffer在它們之間的交換等操作都需要一個機制來控制每個buffer的“生命周期”,即ALLOCATION 和 RELEASE ,此外還要考慮到同步性問題,什麼時候可以read buffer和write buffer都需要聽從調遣。
在android中的fence就是這樣一個為了解決同步性而出現的機制。首先從fence的語義角度來分析一下它的基本原理:
Fence即柵欄,柵欄的角色與它的名字非常類似.一組線程可以使用柵欄來集體進行相互同步;在本質上,每個線程在到達某種周知的狀態時調用柵欄的wait()方法,阻塞起來,以等待其它所有參與線程調用wait()方法表明它們也到達了這個狀態.一旦所有的線程都到達柵欄,它們就會集體解除阻塞,並一起繼續執行;引起程序調用柵欄的wait()方法進行阻塞的那個狀態叫做柵欄狀態。
接下來分析fence在android中的應用,這裡主要涉及SurfaceFlinger中繪制buffer及顯示中的相關方面。
確切的說fence在producer和consumer對buffer處理的過程中是如何協調他們同步的工作,從而保證buffer內容的准確性,而不會被篡改。
首先我們知道一個buffer有以下幾種狀態:
FREE->DEQUEUED->QUEUED->ACQUIRED-FREE
FREE狀態時,producer就可以申請他了嗎?答案是錯的,他需要等一個signal,也就是NO_FENCE這個信號,因為有可能上一次申請的buffer正在被consumer作業中,所以要等待consumer發出finish的信號,而此時FREE狀態下的buffer就好像被柵欄攔住了,這裡是用Fence中wait()或者waitForever()方法,等一個NO_FENCCE信號,柵欄就會打開。進入到下一流程。
DEQUEUED是指producer已經申請了一個buffer從隊列中出來了,還沒有入隊列或者取消buffer,這個狀態下的buffer,producer想對其進行修改也就是填入UI數據時,必須等一個NO_FENCE信號,因為有可能其他owner正在對它進行操作。當信號一到,poducer就可以對其進行操作,操作完成後發出一個NO_FENCE信號。
QUEUED狀態下,也就是把buffer入隊列,不過在這個操作前需要等一個NO_FENCE信號,就比如上一步dequeueBuffer完成之後發的NO_FENCE.收到信號後才進行入隊列操作或者取消buffer操作。這個時候它的owner就變成BufferQueue了。
ACQUIRED狀態也就是producer已經對buffer填充完畢,與前面一樣它也要等到一個NO_FENCE信號,然後consumer才能對其進行操作。操作完成後會釋放buffer,然後發出一個NO_FENCE 信號。
所有的fence都是在kernel層實現的,androidHAL層只是把底層的一些接口的封裝及擴展。
Surfaceflinger在繪制surface過程主要是以下流程:
Surfaceflinger將計算好的layers交由給HWC,HWC根據具體情況選擇對應的繪制路徑。
因為openGL實現代碼沒有開源,所以也就不知道Z喎?/kf/ware/vc/" target="_blank" class="keylink">vcGVuR0zEx7HfttRmZW5jZcrHyOe6zrXE06bTw8HLo6zL+dLUtNNod2NvbXBvc2VyyOvK1qOsxuTKtbW91+6687eiz9a7+tbGysfSu9H5tcSjrNa7yse/tLK7tb3L/Mq1z9a1xLK/t9aw1cHLoaM8L3A+CjxwPkZlbmNltb2118rH1PXDtNOm08O1xMTYo6zL/LrNYnVmZmVyyseyu8/gudi1xKOssrvE3LDRZmVuY2W/tLPJYnVmZmVytcTSu7K/t9ajrLzytaXLtcv8vs3Kx9K7uPbUyrK71MrQ7bXEzsrM4qGjPC9wPgo8cD7V4sDvztLPyLTTtPO3vcPmt9bO9tK7z8LO0rbUZmVuY2W7+tbGwfezzLXEwO294qOsytfPyGZlbmNl09C6w7y4wOCjrMv8w8fT0LK7zay1xNf308OjrLWrvLi69ba8ysezybbUtObU2rXEoaPV4sDvt9bO9tK7z8JhY3F1aXJlRmVuY2Ugus0gcmVsZWFzZUZlbmNlo6y7udPQcmV0aXJlIGZlbmNloaM8L3A+CjxwPjxpbWcgc3JjPQ=="/uploadfile/Collfiles/20140913/2014091308422543.png" alt="\">
每一個layer都有一個acquire 和release fence,每一個系列layes都有一個retirefence,注意這邊的是layers!多個layer。
acquireFence:
禁止顯示一個buffer的內容直到該fence被觸發,而它是在H/W 被set up 前被發送的。
releaseFence:
這個意味著屬於這個layer的buffer已經不在被讀取了,在一個buffer不在被讀取的時候將會觸發這個fence。
Retire fence:
這個 scene或者 一系列的layers不再被顯示到顯示器上,當完成了一個frame的顯示後觸發這個fence。
到這裡可以知道acquireFence, releaseFence是屬於單個layer的,而Retire fence是屬於多個layer即一個scene.那麼在layer和layers對應的結構體必定有它們的影子:
在hardware/libhardware/include/hardware/hwcomposer.h中:
typedef struct hwc_layer_1 {
………
int acquireFenceFd;
int releaseFenceFd;
………
} hwc_layer_1_t;
可知在定義的一個layer中它們分別是兩個整型變量,變量後都以Fd結尾,可想而知這將描述一個文件描述符。
同樣:
typedef struct hwc_display_contents_1 {
…………
int retireFenceFd;
} hwc_display_contents_1_t;
介紹完上面的各種fence之後(當然還有其他種類的fence),我用一張圖來描述下fence應用的機制:
分析到這裡都是從宏觀上分析fence的,大概對fence機制框架有個清楚的認識,接下來看他到底是怎麼實現的。
之前說fence實現都是在kernel層,其實觀其HAL層代碼,Fence::wait() and waitForever(),mege()都是對kernel層的封裝。Kernel層的fence相對來說比較復雜些,畢竟是實現原理,但是究其本質fence其實就是一個文件描述符,這也響應了linux中一切皆文件的說法。
在kernel層有三個跟fence有關的結構體:
Sync_timeline , sync_pt , sync_fence.下面簡單說一下它們的作用和定義:
Sync_timeline:
顧名思義,是個時間軸,每個流程都有自己的timeline,代表著一個自動增加的計數器。
用圖形形象的來描述它如下:
Sync_pt:
其實就是sync point,同步點的概念,代表timeline上的一個特別的值。它有三種狀態:active signalederror。
Sync_fence
它是一系列sync_pt的集合,實際上是個文件描述符可以被傳到用戶空間,也就是這一個特性,讓hal層fence和kernel扯上聯系。
上面就是這三個結構體的基本介紹,還有跟fence相關的API這裡就不詳細介紹,後面分析LCD時在細究。
一開始就給出SF合成圖像到顯示的兩個流程,這裡重點分析hwc這條路徑:
因為android一旦啟動後,繪制圖像就是一個循環的狀態,所以為了方便研究,從android系統開機動畫開始:
第一步就是客戶端請求一個buffer(這裡暫不說成app),因為是剛開始所以一切的fence都屬於初始化狀態或者還沒被創造,(從理論上來講這個時候一切都是空閒的,無論是buffer還是其他什麼的,所以我按照這種假設模式繼續下去分析,事實是怎樣有待考究)因此第一次dequeue一個Buffer的時候就不需要等待display來觸發fence了,也不會擔心SF是不是在對這個buffer進行計算合成,就這樣一步步走向SF計算合成前,開始准備分派hwc渲染的時候,第一次對acquireFenceFd 和 releaseFenceFd還有retireFenceFd進行初始化,在setUpHWComposer中的createWorkList完成的:
關鍵代結構碼如下
其中hwc_layer_1 framebufferTarget;
hwc_display_contents_1 list;
For(;dpy { For(;numLayers;) disp.framebufferTarget->acquireFenceFd =-1; disp.framebufferTarget->releaseFenceFd= -1; } disp.list->retireFenceFd = -1; } 這樣的初始化印證了之前所說的acq,rel分別對應每個layer,而retire對應的是layers。 Set up之後,開始進行計算合成。最後走到postFramebuffer中的HWComposer::commit()---》set(…)---》hwc_set() 在hwc_set中完成了渲染工作,然後通過ioctl交給了fb去顯示,這裡貼出hwc_set中: 一直運行到hwc_sync 會堵塞在這個函數中的wait裡: voidhwc_sync(hwc_display_contents_1_t *list) { for (int i=0; i { if(list->hwLayers[i].acquireFenceFd>0) { sync_wait(list->hwLayers[i].acquireFenceFd,500); ALOGV("fenceFd=%d,name=%s",list->hwLayers[i].acquireFenceFd,list->hwLayers[i].LayerName); } } } 由上面的紅色代碼行可知他在等acquireFence這個信號。 if (layer->acquireFenceFd>0) { g_sync.acq_fence_fd[k] =layer->acquireFenceFd; } ioctl(context->fbFd,RK_FBIOSET_CONFIG_DONE, &g_sync); list->hwLayers[0].releaseFenceFd= g_sync.rel_fence_fd[0]; list->hwLayers[1].releaseFenceFd= g_sync.rel_fence_fd[1]; //list->retireFenceFd =g_sync.ret_fence_fd; close(g_sync.ret_fence_fd); list->retireFenceFd = -1; 首先這裡有兩個數組 acq_fence_fd和rel_fence_fd,看名字就能猜出這是存放對應兩個fence的fd,第一步是把之前初始化的每個layer的acqFenfd保存到數組中,接著display就開始顯示了,ioctl映射到內核中fd驅動程序的ioctl。 接下來分析fb驅動中跟fence相關的代碼: 首先定義了跟fence相關的一些變量: struct sync_fence *release_fence; structsync_fence *retire_fence; structsync_pt *release_sync_pt; structsync_pt *retire_sync_pt; structsync_fence *layer2_fence; structsync_pt *layer2_pt; 其中fence有三類 releaseretire
和layer2 。 接著尋找沒有被用過的fd保存到rel_fence_fd中: dev_drv->win_data.rel_fence_fd[0]= get_unused_fd(); dev_drv->win_data.rel_fence_fd[1]= get_unused_fd(); 然後開始創建fence: release_sync_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max); release_fence= sync_fence_create("rel_fence", release_sync_pt); sync_fence_install(release_fence,dev_drv->win_data.rel_fence_fd[0]); layer2_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max); layer2_fence=sync_fence_create("rel2_fence", layer2_pt); sync_fence_install(layer2_fence,dev_drv->win_data.rel_fence_fd[1]); retire_sync_pt= sw_sync_pt_create(dev_drv->timeline, dev_drv->timeline_max); retire_fence= sync_fence_create("ret_fence", retire_sync_pt); sync_fence_install(retire_fence,dev_drv->win_data.ret_fence_fd); 創建過程這裡省略掉,fence在這裡被創建完之後就阻塞觸發了(等待一個條件:當buffer被顯示後馬上觸發),觸發的函數在sync_fence_create中的sync_fence_signal_pt(pt);在這裡是一整個過程fence第一次被觸發。 觸發的是releaseFence
和retiredfence,接著往下走: 程序下一步會運行: if (dev_drv->wait_fs == 1) { //wait for new frame start in kernel
rk_fb_update_reg(dev_drv,regs); kfree(regs); mutex_unlock(&dev_drv->update_regs_list_lock); } 接著看rk_fb_update_reg(dev_drv,regs)中的關鍵代碼: sw_sync_timeline_inc(dev_drv->timeline,1); if(dev_drv->win_data.acq_fence_fd[0]>= 0) { for(i=0;i if(dev_drv->win_data.acq_fence_fd[i]> 0){ put_unused_fd(dev_drv->win_data.acq_fence_fd[i]); printk("acq_fd=%d\n",dev_drv->win_data.acq_fence_fd[i]); }
rk_fb_free_dma_buf(®s->dma_buf_data[i]); } } 核心功能大概就是讓之前保存在acq_fence_fd數組中的fd無效,看似簡單的一個操作,好像對acqFenceFd只是單純的賦值為-1,但是從源代碼中定義acqFenceFd的說明: /*Sync fence object that will be signaled when the buffer's * contents are available. May be -1 if the contents are already * available.*/ 上面是源代碼中的解釋,由此可以看出當fd為-1時acqFenceFd會被觸發。 當程序運行到這裡的時候,由於只是當中的一個線程,所以前面客戶端請求buffer的操作早已經開始了,而且已經在等待相關的fence了。觸發了releasefence之後用戶那邊收到之後就開始dequeue一個buffer進行填充surface了。 用一張圖來表示下這個過程:
在使用studio開發的項目過程中有時候我們想將項目發布到github上,以前都是用一種比較麻煩的方式(cmd)進行提交,最近發現studio其實是自帶這種功能的,終於可
前提本教程默認以下幾點你已經完全滿足:開通了認證後的服務號 服務號開通的微信支付的認證 騰訊給你的郵件中有商戶登錄的賬號和密碼 擁有一個可供上傳代碼和設置回調域名的網站或
本實例通過TimePickerDialog時間選擇對話框讓用戶設置鬧鐘,並通過AlarmManager全局定時器在指定的時間啟動鬧鐘Activity 。 程序運行效果圖
人物移動地圖的平滑滾動處理 玩過rpg游戲的朋友應該都知道RPG的游戲地圖一般都比較大 今天我和大家分享一下在RPG游戲