GUI系統之SurfaceFlinger(12)VSync訊號的產生和處理
轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/
GUI系統之SurfaceFlinger章節目錄:
blog.csdn.net/uiop78uiop78/article/details/8954508
前面小節ProjectButter中我們學習了Android 4.1顯示系統中的新特性,其中一個就是加入了VSync同步。我們從理論的角度分析了採用這一機制的必要性和運作機理,那麼SurfaceFlinger具體是如何實施的呢?
先來想一下有哪些東西要考慮:
· VSync訊號的產生和分發
如果有硬體主動發出這一訊號,那是最好的了;否則就得通過軟體定時模擬來產生
· VSync訊號的處理
當訊號產生後,SurfaceFlinger如何在最短的時間內響應,具體處理流程是怎麼樣子的
在Android原始碼surfaceflinger目錄下有一個displayhardware資料夾,其中HWComposer的主要職責之一,就是用於產生VSync訊號。
/*frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp*/
HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger,EventHandler& handler, nsecs_t refreshPeriod)
: mFlinger(flinger), mModule(0), mHwc(0), mList(0), mCapacity(0),mNumOVLayers(0),
mNumFBLayers(0), mDpy(EGL_NO_DISPLAY),mSur(EGL_NO_SURFACE),
mEventHandler(handler),mRefreshPeriod(refreshPeriod),
mVSyncCount(0),mDebugForceFakeVSync(false)
{
charvalue[PROPERTY_VALUE_MAX];
property_get("debug.sf.no_hw_vsync", value, "0"); //系統屬性
mDebugForceFakeVSync =atoi(value);
bool needVSyncThread =false;//是否需要軟體模擬VSync
int err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);//載入HAL模組
if (err == 0) {
err = hwc_open(mModule, &mHwc);//開啟module
if (err == 0) {
if(mHwc->registerProcs) { //註冊硬體裝置事件回撥
mCBContext.hwc= this;
mCBContext.procs.invalidate = &hook_invalidate;
mCBContext.procs.vsync = &hook_vsync;
mHwc->registerProcs(mHwc, &mCBContext.procs);
memset(mCBContext.procs.zero, 0, sizeof(mCBContext.procs.zero));
}
if(mHwc->common.version >= HWC_DEVICE_API_VERSION_0_3) {
if(mDebugForceFakeVSync) {//用於除錯
mHwc->methods->eventControl(mHwc, HWC_EVENT_VSYNC, 0);
}
} else {//有可能支援VSync的硬體模組是這個版本以後才加入的,老版本仍然需要軟體模擬
needVSyncThread = true;
}
}
} else {
needVSyncThread =true; //硬體模組開啟失敗,只能用軟體模擬
}
if (needVSyncThread) {
mVSyncThread = new VSyncThread(*this);//建立一個產生VSync訊號的執行緒
}
}
這個函式的核心就是決定VSync的“訊號發生源”——硬體或者軟體模擬。
假如當前系統可以成功載入HWC_HARDWARE_MODULE_ID=“hwcomposer”,並且通過這個庫模組能順利開啟裝置(hwc_composer_device_t),其版本號又大於HWC_DEVICE_API_VERSION_0_3的話,我們就採用“硬體源”(此時needVSyncThread為false),否則需要建立一個新的VSync執行緒來模擬產生訊號。
(1)硬體源
如果mHwc->registerProcs不為空的話,我們註冊硬體回撥mCBContext.procs。定義如下:
struct cb_context{
callbacksprocs;
HWComposer*hwc;
};
呼叫registerProcs()時,傳入的引數是&mCBContext.procs。後期當有事件產生時,比如vsync或者invalidate,硬體模組將分別通過procs.vsync和procs.invalidate來通知HWComposer。
void HWComposer::hook_vsync(struct hwc_procs* procs, int dpy,int64_t timestamp) {
reinterpret_cast<cb_context *>(procs)->hwc->vsync(dpy,timestamp);
}
上面這個函式中,procs即前面的&mCBContext.procs,從指標地址上看它和&mCBContext是一致的,因而我們可以強制型別轉換為cb_context來進行操作,並由此訪問到hwc中的vsync實現:
void HWComposer::vsync(int dpy, int64_t timestamp) {
mEventHandler.onVSyncReceived(dpy, timestamp);
}
HWComposer將VSync訊號直接通知給mEventHandler,這個Handler由HWComposer構造時傳入,換句話說,我們需要看下是誰建立了HWComposer。
/*frameworks/native/services/surfaceflinger/displayhardware/DisplayHardware.cpp*/
void DisplayHardware::init(uint32_t dpy)
{…
mHwc = newHWComposer(mFlinger, *this, mRefreshPeriod);
從這裡可以看出來,HWComposer中的mEventHandler就是DisplayHardware物件,所以後者必須要繼承自HWComposer::EventHandler,以此處理產生的onVSyncReceived事件。
(2)軟體源
軟體源和硬體源的最大區別是它需要啟動一個新執行緒VSyncThread,其執行優先順序與SurfaceFlinger的工作執行緒是一樣的,都是-9。從理論的角度講,任何通過軟體定時來實現的機制都不可能是100%可靠的,即使優先順序再高也可能出現延遲和意外。不過如果“不可靠”的機率很小,而且就算出現意外時不至於是致命錯誤,那麼還是可以接受的。所以說VSyncThread從實踐的角度來講,的確起到了很好的作用。
bool HWComposer::VSyncThread::threadLoop() {
/*Step1. 系統是否使能了VSync訊號發生機制*/
{ // 自動鎖控制範圍
Mutex::Autolock_l(mLock);
while (!mEnabled) {//VSync訊號開關
mCondition.wait(mLock);
}
}
/*Step2. 計算產生VSync訊號的時間*/
const nsecs_t period = mRefreshPeriod;//訊號的產生間隔
const nsecs_t now =systemTime(CLOCK_MONOTONIC);
nsecs_t next_vsync =mNextFakeVSync;//產生訊號的時間
nsecs_t sleep = next_vsync- now; //需要休眠的時長
if (sleep < 0) {//已經過了時間點
sleep = (period - ((now - next_vsync) %period));
next_vsync = now +sleep;
}
mNextFakeVSync =next_vsync + period; //再下一次的VSync時間
struct timespec spec;
spec.tv_sec = next_vsync / 1000000000;
spec.tv_nsec = next_vsync% 1000000000;
int err;
do {
err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);//進入休眠
} while (err<0&& errno == EINTR);
if (err == 0) {
mHwc.mEventHandler.onVSyncReceived(0, next_vsync);//和硬體源是一樣的回撥
}
return true;
}
[email protected] VSyncThread::threadLoop. 關於自動鎖的使用我們已經分析過很多次了,不再贅述。這裡要注意的是mEnabled這個變數,它是用於控制是否產生VSync訊號的一個使能變數。當系統希望關閉VSync訊號發生源時,呼叫VSyncThread::setEnabled(false),否則傳入true。假如mEnabled為false時,VSyncThread就處於等待狀態,直到有人再次使能這個執行緒。
[email protected] VSyncThread::threadLoop. 接下來的程式碼用於真正產生一個VSync訊號。可以想象一下,無非就是這些步驟:
· 計算下一次產生VSync訊號的時間
· 進入休眠
· 休眠時間到了後,就代表應該發出VSync訊號了,通知感興趣的人
· 迴圈往復
變數mRefreshPeriod指定了產生VSync訊號的間隔。它是在DisplayHardware::init中計算出來的:
mRefreshPeriod = nsecs_t(1e9 / mRefreshRate);
如果mRefreshRate為60Hz的話,mRefreshPeriod就差不多是16ms。
因為mNextFakeVSync代表的是“下一次”產生訊號的時間點,所以首先將next_vsync=mNextFakeVSync。接著計算sleep,也就是離產生訊號的時間點還有多長(同時也是需要休眠的時間)。那麼如果sleep的結果小於0呢?代表我們已經錯過了這一次產生訊號的最佳時間點,這是有可能發生的。在這種情況下,就計算下一次最近的VSync離現在還剩多少時間,公式如下:
sleep = (period - ((now - next_vsync) % period));
我們以下圖來表述下采用這個公式的依據:
圖 11‑33 休眠時間推算簡圖
這個圖的前提是now超時時間不超過一個period。因而公式中還要加上%period。
計算完成sleep後,mNextFakeVSync= next_vsync +period。這是因為mNextFakeVSync代表的是下一次threadLoop需要用到的時間點,而next_vsync是指下一次(最近一次)產生VSync的時間點。
如何在指定的時間點再產生訊號呢?有兩種方法,其一是採用定時器回撥,其二就是採用休眠的形式主動等待,這裡使用的是後一種。
可想而知這裡的時間要儘可能精準,單位是nanosecond,即納秒級。函式clock_nanosleep的第一個入參是CLOCK_MONOTONIC,這種時鐘更加穩定,且不受系統時間的影響。
當休眠時間到了後,表示產生訊號的時刻到了。根據前面的分析,就是通過mEventHandler.onVSyncReceived()回撥來通知對訊息感興趣的人,這個做法軟硬體都一樣。
一次訊號產生完成後,函式直接返回true,似乎沒有看到迴圈的地方?這是因為當threadLoop返回值為“真”時,它將被系統再一次呼叫,從而迴圈起來。不清楚的可以參閱一下Thread類的實現。
接下來看下DisplayHardware如何處理這個VSync訊號的。
中間過程很簡單,我們就不一一解釋。在DisplayHardware::onVSyncReceived中,它又再次呼叫內部mVSyncHandler的onVSyncReceived(),將訊息向上一層傳遞。這個變數由EventThread在onFirstRef時通過DisplayHardware::setVSyncHandler()設定,代表的是EventThread物件本身,如下:
void EventThread::onFirstRef() {
mHw.setVSyncHandler(this);//this指標代表EventThread物件
所以VSync訊號被進一步遞交到了EventThread中。顯然,它也不是終點。
void EventThread::onVSyncReceived(int, nsecs_t timestamp) {
Mutex::Autolock _l(mLock);
mVSyncTimestamp =timestamp;
mCondition.broadcast();//有人在等待事件的到來
}
等待VSync事件的地方很多,其中最重要的是EventThread::threadLoop(),這個函式將負責對VSync進行分發,決定誰有權利來最終處理這一事件。
這個函式的主體邏輯還是比較簡單的,不過因為很長,內部又夾雜著多個迴圈體,顯得不好理解,因此我們只摘選最重要的一部分來加快大家的閱讀。
bool EventThread::threadLoop() {
nsecs_t timestamp;
DisplayEventReceiver::Event vsync;
Vector<wp<EventThread::Connection> > displayEventConnections;
do {//Step1. 第一個迴圈體
Mutex::Autolock_l(mLock);
do {…//Step2. 第二個迴圈體,決定是否上報VSync
} while(true);
//跳出迴圈,接下來就要準備分發VSync了
mDeliveredEvents++;
mLastVSyncTimestamp =timestamp;
const size_t count =mDisplayEventConnections.size();
for (size_t i=0 ;i<count ; i++) {
…//Step3. 第三個迴圈中逐個判斷各connection是否需要上報
if (reportVsync) {
displayEventConnections.add(connection);
}
}
} while(!displayEventConnections.size());//只要size不等於0就可以退出迴圈了
// 終於開始分發了。。。
vsync.header.type =DisplayEventReceiver::DISPLAY_EVENT_VSYNC;
vsync.header.timestamp =timestamp;
vsync.vsync.count =mDeliveredEvents;
const size_t count =displayEventConnections.size();
for (size_t i=0 ;i<count ; i++) {//Step4. 第四個迴圈體,分發事件
sp<Connection>conn(displayEventConnections[i].promote());
if (conn != NULL) {
status_t err =conn->postEvent(vsync);//通知connection發起者
if (err == -EAGAIN|| err == -EWOULDBLOCK) {
//這兩個錯誤是指對方當前不接受事件,有可能是暫時性的
} else if (err< 0) {
//發生了致命錯誤,一律移除
removeDisplayEventConnection(displayEventConnections[i]);
}
} else {//connection已經死了,將它移除
removeDisplayEventConnection(displayEventConnections[i]);
}
}
…
return true;
}
一共有四個迴圈體,看起來很亂,我們先以虛擬碼的形式來重新表述一遍:
do {//第一個迴圈體 do { //第二個迴圈體,判斷當前系統是否允許上報VSync } while(true); for (size_t i=0 ; i<count ; i++) { //第三個迴圈體,逐個計算需要上報的connection個數 } } while (!displayEventConnections.size());/*一旦需要上報的連線數超過0, 就可以退出迴圈了*/ for (size_t i=0 ; i<count ; i++) { /*第四個迴圈,開始實際的分發。這時要先考慮connection是否死亡,然後就是判斷分發後是否有異常返回,比如EWOULDBLOCK等等。對於暫態的錯誤,理論上是要再重發的,不過當前系統還沒有這麼做。的確,一方面這將使程式邏輯變得複雜,另一方面,即便丟一兩個VSYNC,也無傷大局。所以從原始碼註釋來看,將來這部分也不會改善。*/ } |
相信大家結合這段虛擬碼再來對照原始碼,就比較清楚了。
對VSYNC訊號感興趣的人,可以通過registerDisplayEventConnection()來與EventThread建立一個連線。搜尋程式碼可以發現,當前系統中建立了連線的物件是MessageQueue,具體程式碼在MessageQueue::setEventThread()中。
我們以下圖來總結本小節的內容:
圖 11‑34 VSYNC訊號的產生與分發
整體邏輯關係相對複雜,建議大家在做原始碼分析時,以下面兩條線索進行:
l VSync訊號的傳遞流向
l 各個類的靜態依賴關係。比如DisplayHardware持有一個HWComposer物件,同時這個物件的mEventHandler成員變數又指向DisplayHardware
相關推薦
GUI系統之SurfaceFlinger(12)VSync訊號的產生和處理
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
android Gui系統之SurfaceFlinger(5)---Vsync(2)
9.Vsync第二部分 在上一篇中我們講到,檢視的重新整理需要很多步驟, void SurfaceFlinger::handleMessageRefresh() { ATRACE_CALL(); preComposition(); //合成前的準備 rebui
android Gui系統之SurfaceFlinger(4)---Vsync(1)
8.Vsync 回到頂部 8.1概論 VSYNC(Vertical Synchronization)是一個相當古老的概念,對於遊戲玩家,它有一個更加大名鼎鼎的中文名字—-垂直同步。 “垂直同步(vsync)”指的是顯示卡的輸出幀數和螢幕的垂直重新整理率相同,這完全是一個CRT顯示器上
android Gui系統之SurfaceFlinger(3)---SurfaceFlinger
7.SurfaceFlinger SurfaceFlinger在前面的篇幅了,多有涉及。 SurfaceFlinger是GUI重新整理UI的核心,所以任何關於SurfaceFlinger的改進都會對android UI系統有重大影響。 SurfaceFlinger主要分為4個部分 1
android Gui系統之SurfaceFlinger(2)---BufferQueue
6 BufferQueue 上一篇已經說到,BufferQueue是SurfaceFlinger管理和消費surface的中介,我們就開始分析bufferqueue。 每個應用 可以由幾個BufferQueue? 應用繪製UI 所需的記憶體從何而來? 應用和SurfaceFlinge
android Gui系統之SurfaceFlinger(1)---SurfaceFlinger概論
GUI 是任何系統都很重要的一塊。 android GUI大體分為4大塊。 1)SurfaceFlinger 2)WMS 3)View機制 4)InputMethod 這塊內容非常之多,但是理解後,可以觸類旁通,其實現在主流的系統,包括andorid,ios
android Gui系統之SurfaceFlinger(1)---SurfaceFlinger概論【轉】
轉自:https://www.cnblogs.com/deman/p/5584198.html 閱讀目錄 1.OpenGL & OpenGL ES 2.Android的硬體介面HAL 3.Android顯示裝置:Gralloc & FrameBuff
GUI系統之SurfaceFlinger(18)postFramebuffer
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
GUI系統之SurfaceFlinger(15)handlePageFlip
文章都是通過閱讀原始碼分析出來的,還在不斷完善與改進中,其中難免有些地方理解得不對,歡迎大家批評指正。轉載請註明:From LXS. http://blog.csdn.net/uiop78uiop78/GUI系統之SurfaceFlinger章節目錄:blog.csdn.ne
【Linux】程序間通訊之訊息佇列、訊號量和共享儲存
訊息佇列、訊號量和共享儲存是IPC(程序間通訊)的三種形式,它們功能不同,但有相似之處,下面先介紹它們的相似點,然後再逐一說明。 1、相似點 每個核心中的IPC結構(訊息佇列、訊號量和共享儲存)都用一個非負整數的識別符號加以引用,與檔案描述符不同,當一個
IPC通訊之訊息佇列、訊號量和共享記憶體
有三種IPC我們稱作XSI IPC,即訊息佇列,訊號量以及共享儲存器。XSI IPC源自System V的IPC功能。由於XSI IPC不使用檔案系統的名稱空間,而是構造了它們自己的名字空間,
linux系統之網路防火牆(firewalld服務和iptables服務)
一.對於防火牆的理解 防火牆,其實就是用於實現Linux下訪問控制的功能的,它分為硬體的和軟體的兩種。 無論是在哪個網路中,防火牆工作的地方一定是在網路的邊緣。 而我們的任務就是需要去定義到底防火牆如何工作,這就是防火牆的策略,規則, 以達到讓它對出入網路的IP、資料進行檢
GUI開發之AWT、SWING、SWT和JFACE的比較
AWT Abstract Windows Toolkit(AWT)是最原始的 Java GUI 工具包。在任何一個 Java 執行環境中都可以使用它。 AWT 是一個非常簡單的具有有限 GUI 元件、佈局管理器和事件的工具包.有些經常使用的元件,例如表、樹、進度條等
複習筆記11 異常的產生和處理
1 異常體系&異常處理 1.1 異常概述 我們在寫程式碼的時候,經常的出現一些小問題,那麼為了方便我們處理這些問題,java為我們提供了異常機制 在Java中,把異常資訊封裝成了一個類。當出現了問題時,就會建立異常類物件並丟擲異常相關的資訊(如異常出現的位置、原因等)。 在Java中使用E
監控系統Nagios系列(八) 抖動(falpping)檢測和處理
所謂抖動,是指狀態在一定時間內變化過於頻繁。如果某個物件處於抖動狀態,那麼這時候的狀態變化也是無意義的。 Nagios提供了抖動狀態的檢查以及針對抖動狀態的處理。 1.判定抖動狀態 抖動是由於狀態變化過於頻繁導致,但是該如何定義“頻繁”?每次檢查得到的狀態都與上次不一樣?還是一定時間內狀態變化達到某
Linux學習之路-Linux自動化系統安裝【12】---20171230
repos exec auth bdc u盤啟動 eth 本地 ner 微型linux 安裝程序 CentOS系統安裝 系統啟動流程:bootloader-->kernel(initramfs)-->rootfs-->/sbin/initanaco
2018-10-18讀文獻總結之DCB分碼多重進接、零基線、訊號產生
---恢復內容開始--- 今天心血來潮,想開始把自己讀文獻的過程和每篇文獻的收穫總結一下,不知道CSDN怎麼回事,一直登陸不進去,搞得我註冊了一個部落格園的賬戶,部落格園新註冊的還需要認證,但是很快,所以我就來這邊了。文筆不好,主要是一些流水賬,用來自己看看。 前兩天一直搞不清DCB怎麼消除,看了一些文
【 MATLAB 】訊號處理工具箱的訊號產生函式之 sawtooth 函式簡記
sawtooth 函式 x = sawtooth(t) generates a sawtooth wave with period 2π for the elements of the time
ZR高速寬頻訊號產生回放系統
西安慕雷電子釋出全球頂級高速資料採集卡,取樣率4GSPS,模擬頻寬2GHZ,記錄儲存頻寬高達 6GB/S! 慕雷電子圍繞雷達、通訊、導航、電子對抗與偵查等領域,提供國內領先的訊號測試產 品與服務:訊號採集、處理、記錄與回放,用於試驗資料實時記錄、事後分析與外場
數字訊號產生之泊松分佈的隨機數
uniform.h #pragma once class uniform { private: double a, b, generate_num; int * seed; int s; int M, N, i, j; public: uniform() {