#ifdef DEBUG_PROCESS /* Debugging process code is difficult; where to write debug output? */ static void debugPrint(char *format, ...) { FILE *tty = fopen("/dev/tty", "w"); va_list ap; va_start(ap, format); vfprintf(tty, format, ap); va_end(ap); fclose(tty); } #endif /* DEBUG_PROCESS */
2018年10月2日 星期二
不定參數印 log
From the UNIXProcess_md.c
2018年4月21日 星期六
[Sikuli] 簡單的GBF腳本 (一) 工欲善其事
1. SikuliX 本體下載
首先你必須要下載SikuliX (廢話)
(撰寫本文時, SikuliX版本為 sikulixsetup-1.1.2.jar)
http://sikulix.com/quickstart/
由於SikuliX IDE是運行在Java的環境中, 請確保你的電腦至少有 JRE
可以透過Java的官方網站檢查 (請使用IE打開此連結)
安裝完成 JRE 後, 再開始安裝 SikuliX
0. 在你喜歡的位置新增一個資料夾,
把剛剛下載回來的sikulixsetup-xxx.jar放進去.
雙擊開始安裝
1. 諸位應該都沒有安裝過, 直接點選"是"即可
(有安裝過的記得先備份一下)
第三個 Tesseract 是 1.1.2 新整合的一項 Open Source 的 OCR 功能,
有辨識文字需求的朋友可以安裝看看.
如果有遇到問題再回到這步選"否"安裝2.5.4
我自己是沒遇到啥問題....
2. 簡單的使用範例
1. 點擊 runsikulix之後就會打開 SikuliX 的 IDE
2. 點選find之後, 自動進入截圖的模式, 把"我的電腦"框起來看看
3. 把上面那行複製貼上, 將第二行的 find 改為 doubleClick
4. 點選"慢動作執行", 應該可以看到"我的電腦"被點開了
3. 一些實用技巧
1. 強制中斷: ALT + SHIFT + C執行的時候需要強制中斷Sikuli, 可以使用此命令
2. 匯入自訂功能
像是需要random功能的話, 可以使用這個方法匯入:
from sikuli import * import random
當然也可以加入自己的Path
myScriptPath = "C:\\PATH\\TO\\YOUR\\FUNCTIO\\MyFunction.sikuli" addImportPath(myScriptPath) from sikuli import findAndClick
*** 加入自定義腳本後, 請重新執行SikuliX
3. 為了跟 GBF 扯上一點關係,
這裡順便提供一個簡單的隨機點擊腳本: (其實好像也不需要下一篇了 XD)
from sikuli import * import random def findAndClick(Pattern): match = find(Pattern) print match nW = match.getW() - 10 nH = match.getH() - 10 nX = match.getX() + random.randint(10, nW) nY = match.getY() + random.randint(10, nH) location = Location(nX, nY) print("Location at: ", location) click(location) sleep(2) def checkIfExist(Pattern): if exists(Pattern): match = find(Pattern) print match nW = match.getW() - 10 nH = match.getH() - 10 nX = match.getX() + random.randint(10, nW) nY = match.getY() + random.randint(10, nH) location = Location(nX, nY) print("Location at: ", location) click(location) sleep(2) def randomClick(position): nW = position.getW() - 10 nH = position.getH() - 10 nX = position.getX() + random.randint(10, nW) nY = position.getY() + random.randint(10, nH) location = Location(nX, nY) print("Location at: ", location) click(location) sleep(2)
使用方法如下:
I.) 開一個新的檔案, 貼上腳本, 另存為你喜歡的名稱 (例如: myFunction)
II.) 參照"加入自定義的腳本", 如下所示: (加入ImportPath, 請記得重新啟動 SikuliX)
# Include it myScriptPath = "C:\\Users\\Uorol\\Documents\\myFunction.sikuli" addImportPath(myScriptPath) from sikuli import myFunction # Use it myFunction.findAndClick("Picture")
2018年1月31日 星期三
[Windows] 使用 MinGw 編譯 protobuf 給 QT 使用
MinGW: A native Windows port of the GNU Compiler Collection (GCC)
下載地址:
ProtoBuf: https://github.com/google/protobuf/releases/tag/v3.5.1
MinGw: https://sourceforge.net/projects/mingw/
1.) 安裝MinGw, 預設路徑是 C:\MinGW
2.) 勾選 Basic Setup, 然後按下 Installation->Update Catalogue 就會開始安裝
3.) 將 MinGw 跟 msys 的 bin 目錄加入環境變數, 我自己的目錄是:
C:\MinGW\bin
C:\MinGW\msys\1.0\bin
4.) 執行 C:\MinGW\msys\1.0\msys.bat 進入 mingw 的 shell
5.) 解壓縮protobuf, shell cd 到 protobuf 資料夾下
6.) 依序執行
$ ./configure
$ make
$ make check
$ make install
7.) 完成後你可以在 C:\MinGW\msys\1.0\local 下面找到剛剛編譯出來的 library
同時這裡也有一個 protoc, 你可以用它來編譯你的 *.proto 檔案
8.) 複製一份 library 到你的QT project下, 並加入 INCLUDE_PATH 以及 LIBS
下載地址:
ProtoBuf: https://github.com/google/protobuf/releases/tag/v3.5.1
MinGw: https://sourceforge.net/projects/mingw/
1.) 安裝MinGw, 預設路徑是 C:\MinGW
2.) 勾選 Basic Setup, 然後按下 Installation->Update Catalogue 就會開始安裝
3.) 將 MinGw 跟 msys 的 bin 目錄加入環境變數, 我自己的目錄是:
C:\MinGW\bin
C:\MinGW\msys\1.0\bin
4.) 執行 C:\MinGW\msys\1.0\msys.bat 進入 mingw 的 shell
5.) 解壓縮protobuf, shell cd 到 protobuf 資料夾下
6.) 依序執行
$ ./configure
$ make
$ make check
$ make install
7.) 完成後你可以在 C:\MinGW\msys\1.0\local 下面找到剛剛編譯出來的 library
同時這裡也有一個 protoc, 你可以用它來編譯你的 *.proto 檔案
8.) 複製一份 library 到你的QT project下, 並加入 INCLUDE_PATH 以及 LIBS
INCLUDEPATH += protobuf/include
LIBS += -L$$PWD/libs -lprotobuf
^這邊的 $$PWD 表示搜尋當前目錄, QT對相對路徑的支援好像很差....
RS485小記
RS485 只是一種電氣特性的規範, 並沒有說明雙方如何交互訊息.
儘管它相較於 RS232 可以在佈線上支援多點的模式, 但仍沒有仲裁機制,
因此總線BUS上一次還是只有一個人可以發送訊息.
一般會搭配 Master-Slave (e.g. Modbus) or Token passing 等 protocol 使用
RS485 模組的Pin 腳定義 (以MAX485為例):
pin 1 :RO (receive out)
pin 2 RE (receive enable)
pin 3 :DE (data enable)
pin 4 :DI (data in)
pin 5, pin 8 : Gnd and Vcc onnected
pin 6,7 : A and B the RS485 pair
使用2-wire的半雙工模式下, 需要決定是在送還是在收的狀態
由於 DE 是 HIGH Enable, /RE 是 LOW Enable
可以把/RE and DE 短路, 然後MCU/RaspberryPi 用 GPIO 來決定收/送
儘管它相較於 RS232 可以在佈線上支援多點的模式, 但仍沒有仲裁機制,
因此總線BUS上一次還是只有一個人可以發送訊息.
一般會搭配 Master-Slave (e.g. Modbus) or Token passing 等 protocol 使用
RS485 模組的Pin 腳定義 (以MAX485為例):
pin 1 :RO (receive out)
pin 2 RE (receive enable)
pin 3 :DE (data enable)
pin 4 :DI (data in)
pin 5, pin 8 : Gnd and Vcc onnected
pin 6,7 : A and B the RS485 pair
使用2-wire的半雙工模式下, 需要決定是在送還是在收的狀態
由於 DE 是 HIGH Enable, /RE 是 LOW Enable
可以把/RE and DE 短路, 然後MCU/RaspberryPi 用 GPIO 來決定收/送
2018年1月15日 星期一
從 BootAnimation 探索 SurfaceFlinger (六) Composer, 多麼好聽的名字
前情提要
SurfaceFlinger 的旅程才正要開始, 讓我們繼續 init() 函數:
setPowerModeInternal 看似簡單, 不過也做了不少事情.
一開始我們的 currentMode 是 HWC_POWER_MODE_OFF
這裡將 DisplayDevice (primary) 的 powerMode 更新為 HWC_POWER_MODE_NORMAL,
並且呼叫了 mEventThread->onScreenAcquired(); 來喚醒 EventThread 並分發 VSyncEvent
最後最後呼叫了 repaintEverything 通知 SurfaceFlinger 更新畫面,
到這邊 SurfaceFlinger 的 init 就全部完成了...!!! d(`・∀・)b
看到最後一行的 startBootAnim() 就會發現實際上 BootAnimation 的啟動是在 SurfaceFlinger 完成後才帶起來的, 不然也沒地方可以給他畫嘛 XD
至此每個重要部件都差不多初始化完成了, 接下來進入畫畫篇.
キタ━━━━(゚∀゚)━━━━!!
SurfaceFlinger 的旅程才正要開始, 讓我們繼續 init() 函數:
void SurfaceFlinger::init() { ... // Initialize the H/W composer object. There may or may not be an // actual hardware composer underneath. mHwc = new HWComposer(this, *static_cast<HWComposer::EventHandler *>(this));
HWComposer, 又聽到了 Composer 這個名字.
前面 SurfaceComposerClient 並沒有真的去 compose, 這個傢伙會不會有所不同呢?
我們先看一下它的建構子, 這邊帶入的 flinger 跟 handler 其實都是 SurfaceFlinger
◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣就不能簡單一點么?
好的我們還是一行一行乖乖地分析:
前面的兩個初始化現在不知道功能是啥, 暫且跳過
needVSyncThread 表示需要開啟 VSync, 跟上一篇的 DispSync 有關!
接下來使用 loadFbHalModule() 開啟 FB HAL.
這個 FB (frameBuffer) 也和 GraphicBuffer 息息相關,
理論上所有繪圖的動作都是交給 GPU 去做的.
每個 APP 都會拿到自己的 Layer 去繪圖,
並且要把每一張圖都疊加 (compose) 在一起輸出到螢幕, 這塊顯示用的 buffer 就是 frameBuffer 了
因為這個特性, 在 HWComposer 去啟動 FrameBuffer HAL 也是相當合情合理的.
展開看一下:
這邊每個 vendor 的作法都不盡相同, 就不列上 HAL 的 code 了
loadFbHalModule 可以取得 HAL 的接口 mFbDev
再來是讀取 HWComposer 的 HAL:
IMX 的 HAL 行為:
1.) 再去開啟 FSL HW module, 把工作轉交給該 Module
2.) 啟動 HAL 的 VSyncThread, 這個 VSyncThread 會依據 primary display 是否連接上, 來決定是否要啟動 FakeVSync
如果沒有連上 Display, 則會使用 FakeVSync (睡一段時間起來)
反之則會呼叫 ioctl (MXCFB_WAIT_FOR_VSYNC)
無論哪兩者都會再去呼叫 mCtx->m_callback->vsync
這個 mCtx->m_callback 是透過 hwc_registerProcs 這個 API 註冊進來的
HWComposer 會呼叫 registerProcs 將 vsync call back 設置為 hook_vsync
hook_vsync 又會去呼叫到 mEventHandler.onVSyncReceived
最後 SurfaceFlinger 會依照螢幕是否開啟來決定是否要啟動 VSyncThread
從 log 上面可以看到在 vsync 有 enable 的時候每次來 vsync 的時間大概是 16 ms 左右, 也就是 FPS 大約是 60 HZ
回到 HWComposer 的建構子:
讀取完 FrameBuffer 跟 HWComposer 的 HAL module 之後,
針對 HWC API > 1.1 的版本會將 FrameBuffer HAL 關閉,
這應該是因為將它整合到 HWComposer 的相關操作了, 後面再找看看在哪裡!
這邊將 mAllocatedDisplayIDs 的 0, 1 bit 先標記起來, 定義為 primary & external 的 display
再往下進行 HWComposer HAL module 的 callback function 註冊:
一開始透過 eventControl 關閉自身的 VSync thread 與 HAL 的 VSync thread.
因為這裡把 needVSyncThread 設置為 false, 所以實際上是不會生成自身的 VSync thread 的.
順帶一提, 如果沒有 HWComposer HAL的支援時, 我們需要 Fake 一個 VSync 出來, 其實跟 HWComposer HAL 做的 Fake 是一樣的, 我們看一下:
如果 mEnabled 為 true, 則每隔 mRefreshPeriod 的時間後會發出一次 onVSyncReceived 給 SurfaceFlinger
threadLoop 內容如下:
中間的計算式意義如下:
1.) 當 next_vsync >= now, 則 sleep 到 next_vsync 時間, 更新下一次 VSync 到來時間 (+peroid)
2.) 當 next_vsync < now, 則先更新 next_vsync 到相鄰最近的一次 peroid time.
(假設原先的 mNextFakeVSync = 5, now = 17, period = 10,
我們知道最近一次的 Vsync 是 25, 計算方式如下:
Step 1.) 先算出最靠近 now 的 next_vsync 間隔所需要的sleep time
sleep = 10 - (17-5)%10 = 10 - 2 = 8.
Step 2.) next_vsync即為 now + sleep
next_vsync = 17 + 8 = 25.
接下來依據 HWComposer HAL 的可支援性設置 mNumDisplays,
這會在後面呼叫 HAL 時持續的使用到.
最後呼叫 queryDisplayProperties() 帶入 primary & external,
這個 API 在 hotplug 的時候也會呼叫到, 用來獲取 display 的參數,
並把 mDisplayData.connected 設置為 true
HWComposer 的初始化做完了, 但還沒開始做事呢.
再回到 SurfaceFlinger::init()
生成一個 RendorEngine, 這裡會依據你的 version 來生成 GLESXXRenderEngine()
跟 EGL 相關的操作暫且跳過, 往下進入 Display 的初始化:
HWComposer 前面在 queryDisplayProperties 時已經將 primary & external 都設置為 connected,
下面依序展開:
這邊的 mCurrentState 是一個 State 的結構, 叫做 State 的人非常多, 這邊的定義如下:
可以看到它由一個 LayerVector 跟 DefaultKeyedVector 所組成
在 createBuiltinDisplayLocked 中, 還生成了一個 DisplayDeviceState 的 info 變數, 拿來記錄 primary & external 的資訊:
接下來又看到了熟悉的 BufferQueue !
上一次分析它是在前面使用者透過 SurfaceComposerClient 來建立 Layer 的時候
這裡 createBufferQueue 的時候還自帶了一個 allocator
(不過實際上應該可以也帶NULL才對...
我的印象中 Binder 如果發現 IPC 的對象是自己的話不會去多占一個通訊位子 XD
也許只是為了簡化流程吧)
它把 consumer 轉交給了 FramebufferSurface, 從前面的經驗我們可以知道 FramebufferSurface 應該會繼承 ConsumerBase 並且去實作 onFrameAvailable
它的建構子如下:
帶入了 HWComposer, display_id, 以及 consumer
consumer 再透過 ConsumerBase 跟 BufferQueueCore 串在一起, 這邊不再贅述
繼續往下看,
int32_t hwcId = allocateHwcDisplayId(type); 對於 BUILT_IN_DISPLAY 來說就是再度回傳 type
這裡將剛剛還沒用上的 producer 轉交給了 DisplayDevice
還記得前面 BootAnimation 透過 SurfaceControl 建立了一個 Surface 嗎?
DisplayDevice 自身的 mNativeWindow 也同樣是一個 Surface 類型
幾個重要的成員變數如下:
mDisplayToken => 前面生成的 Binder 對象
mDisplaySurface => FramebufferSurface 對象 (consumer)
mNativeWindow => Surface 對象 (producer)
mSurface => EGLSurface, 會由它自己的 Surface 透過 producer 取得 GraphicBuffer
前面 SurfaceComposerClient 並沒有真的去 compose, 這個傢伙會不會有所不同呢?
我們先看一下它的建構子, 這邊帶入的 flinger 跟 handler 其實都是 SurfaceFlinger
HWComposer::HWComposer( const sp<SurfaceFlinger>& flinger, EventHandler& handler) : mFlinger(flinger), mFbDev(0), mHwc(0), mNumDisplays(1), mCBContext(new cb_context), mEventHandler(handler), mDebugForceFakeVSync(false) { for (size_t i =0 ; i<MAX_HWC_DISPLAYS ; i++) { mLists[i] = 0; } for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) { mLastHwVSync[i] = 0; mVSyncCounts[i] = 0; } bool needVSyncThread = true; // Note: some devices may insist that the FB HAL be opened before HWC. int fberr = loadFbHalModule(); loadHwcModule(); if (mFbDev && mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // close FB HAL if we don't needed it. // FIXME: this is temporary until we're not forced to open FB HAL // before HWC. framebuffer_close(mFbDev); mFbDev = NULL; } // these display IDs are always reserved for (size_t i=0 ; i<NUM_BUILTIN_DISPLAYS ; i++) { mAllocatedDisplayIDs.markBit(i); } if (mHwc) { ALOGI("Using %s version %u.%u", HWC_HARDWARE_COMPOSER, (hwcApiVersion(mHwc) >> 24) & 0xff, (hwcApiVersion(mHwc) >> 16) & 0xff); if (mHwc->registerProcs) { mCBContext->hwc = this; mCBContext->procs.invalidate = &hook_invalidate; mCBContext->procs.vsync = &hook_vsync; if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) mCBContext->procs.hotplug = &hook_hotplug; else mCBContext->procs.hotplug = NULL; memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero)); mHwc->registerProcs(mHwc, &mCBContext->procs); } // don't need a vsync thread if we have a hardware composer needVSyncThread = false; // always turn vsync off when we start eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0); // the number of displays we actually have depends on the // hw composer version if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) { // 1.3 adds support for virtual displays mNumDisplays = MAX_HWC_DISPLAYS; } else if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // 1.1 adds support for multiple displays mNumDisplays = NUM_BUILTIN_DISPLAYS; } else { mNumDisplays = 1; } } if (mFbDev) { ALOG_ASSERT(!(mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)), "should only have fbdev if no hwc or hwc is 1.0"); DisplayData& disp(mDisplayData[HWC_DISPLAY_PRIMARY]); disp.connected = true; disp.format = mFbDev->format; DisplayConfig config = DisplayConfig(); config.width = mFbDev->width; config.height = mFbDev->height; config.xdpi = mFbDev->xdpi; config.ydpi = mFbDev->ydpi; config.refresh = nsecs_t(1e9 / mFbDev->fps); disp.configs.push_back(config); disp.currentConfig = 0; } else if (mHwc) { // here we're guaranteed to have at least HWC 1.1 ALOGD() for (size_t i =0 ; i<NUM_BUILTIN_DISPLAYS ; i++) { queryDisplayProperties(i); } } if (needVSyncThread) { // we don't have VSYNC support, we need to fake it ALOGE("needVSyncThread, create VSyncThread"); mVSyncThread = new VSyncThread(*this); } }
◢▆▅▄▃崩╰(〒皿〒)╯潰▃▄▅▇◣
好的我們還是一行一行乖乖地分析:
前面的兩個初始化現在不知道功能是啥, 暫且跳過
needVSyncThread 表示需要開啟 VSync, 跟上一篇的 DispSync 有關!
接下來使用 loadFbHalModule() 開啟 FB HAL.
這個 FB (frameBuffer) 也和 GraphicBuffer 息息相關,
理論上所有繪圖的動作都是交給 GPU 去做的.
每個 APP 都會拿到自己的 Layer 去繪圖,
並且要把每一張圖都疊加 (compose) 在一起輸出到螢幕, 這塊顯示用的 buffer 就是 frameBuffer 了
因為這個特性, 在 HWComposer 去啟動 FrameBuffer HAL 也是相當合情合理的.
展開看一下:
// Load and prepare the FB HAL, which uses the gralloc module. Sets mFbDev. int HWComposer::loadFbHalModule() { hw_module_t const* module; int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module); if (err != 0) { ALOGE("%s module not found", GRALLOC_HARDWARE_MODULE_ID); return err; } return framebuffer_open(module, &mFbDev); } static inline int framebuffer_open(const struct hw_module_t* module, struct framebuffer_device_t** device) { return module->methods->open(module, GRALLOC_HARDWARE_FB0, (struct hw_device_t**)device); }
這邊每個 vendor 的作法都不盡相同, 就不列上 HAL 的 code 了
loadFbHalModule 可以取得 HAL 的接口 mFbDev
再來是讀取 HWComposer 的 HAL:
// Load and prepare the hardware composer module. Sets mHwc. void HWComposer::loadHwcModule() { hw_module_t const* module; if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) { ALOGE("%s module not found", HWC_HARDWARE_MODULE_ID); return; } int err = hwc_open_1(module, &mHwc); if (err) { ALOGE("%s device failed to initialize (%s)", HWC_HARDWARE_COMPOSER, strerror(-err)); return; } if (!hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_0) || hwcHeaderVersion(mHwc) < MIN_HWC_HEADER_VERSION || hwcHeaderVersion(mHwc) > HWC_HEADER_VERSION) { ALOGE("%s device version %#x unsupported, will not be used", HWC_HARDWARE_COMPOSER, mHwc->common.version); hwc_close_1(mHwc); mHwc = NULL; return; } } static inline int hwc_open_1(const struct hw_module_t* module, hwc_composer_device_1_t** device) { return module->methods->open(module, HWC_HARDWARE_COMPOSER, (struct hw_device_t**)device); }
IMX 的 HAL 行為:
1.) 再去開啟 FSL HW module, 把工作轉交給該 Module
2.) 啟動 HAL 的 VSyncThread, 這個 VSyncThread 會依據 primary display 是否連接上, 來決定是否要啟動 FakeVSync
如果沒有連上 Display, 則會使用 FakeVSync (睡一段時間起來)
反之則會呼叫 ioctl (MXCFB_WAIT_FOR_VSYNC)
無論哪兩者都會再去呼叫 mCtx->m_callback->vsync
這個 mCtx->m_callback 是透過 hwc_registerProcs 這個 API 註冊進來的
HWComposer 會呼叫 registerProcs 將 vsync call back 設置為 hook_vsync
hook_vsync 又會去呼叫到 mEventHandler.onVSyncReceived
最後 SurfaceFlinger 會依照螢幕是否開啟來決定是否要啟動 VSyncThread
從 log 上面可以看到在 vsync 有 enable 的時候每次來 vsync 的時間大概是 16 ms 左右, 也就是 FPS 大約是 60 HZ
回到 HWComposer 的建構子:
if (mFbDev && mHwc && hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // close FB HAL if we don't needed it. // FIXME: this is temporary until we're not forced to open FB HAL // before HWC. framebuffer_close(mFbDev); mFbDev = NULL; } // these display IDs are always reserved for (size_t i=0 ; i<NUM_BUILTIN_DISPLAYS ; i++) { mAllocatedDisplayIDs.markBit(i); }
讀取完 FrameBuffer 跟 HWComposer 的 HAL module 之後,
針對 HWC API > 1.1 的版本會將 FrameBuffer HAL 關閉,
這應該是因為將它整合到 HWComposer 的相關操作了, 後面再找看看在哪裡!
這邊將 mAllocatedDisplayIDs 的 0, 1 bit 先標記起來, 定義為 primary & external 的 display
/* Display types and associated mask bits. */ enum { HWC_DISPLAY_PRIMARY = 0, HWC_DISPLAY_EXTERNAL = 1, // HDMI, DP, etc. HWC_DISPLAY_VIRTUAL = 2, HWC_NUM_PHYSICAL_DISPLAY_TYPES = 2, HWC_NUM_DISPLAY_TYPES = 3, };
再往下進行 HWComposer HAL module 的 callback function 註冊:
if (mHwc) { ALOGI("Using %s version %u.%u", HWC_HARDWARE_COMPOSER, (hwcApiVersion(mHwc) >> 24) & 0xff, (hwcApiVersion(mHwc) >> 16) & 0xff); if (mHwc->registerProcs) { mCBContext->hwc = this; mCBContext->procs.invalidate = &hook_invalidate; mCBContext->procs.vsync = &hook_vsync; if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) mCBContext->procs.hotplug = &hook_hotplug; else mCBContext->procs.hotplug = NULL; memset(mCBContext->procs.zero, 0, sizeof(mCBContext->procs.zero)); mHwc->registerProcs(mHwc, &mCBContext->procs); } // don't need a vsync thread if we have a hardware composer needVSyncThread = false; // always turn vsync off when we start eventControl(HWC_DISPLAY_PRIMARY, HWC_EVENT_VSYNC, 0); // the number of displays we actually have depends on the // hw composer version if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) { // 1.3 adds support for virtual displays mNumDisplays = MAX_HWC_DISPLAYS; } else if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_1)) { // 1.1 adds support for multiple displays mNumDisplays = NUM_BUILTIN_DISPLAYS; } else { mNumDisplays = 1; } }
一開始透過 eventControl 關閉自身的 VSync thread 與 HAL 的 VSync thread.
因為這裡把 needVSyncThread 設置為 false, 所以實際上是不會生成自身的 VSync thread 的.
順帶一提, 如果沒有 HWComposer HAL的支援時, 我們需要 Fake 一個 VSync 出來, 其實跟 HWComposer HAL 做的 Fake 是一樣的, 我們看一下:
如果 mEnabled 為 true, 則每隔 mRefreshPeriod 的時間後會發出一次 onVSyncReceived 給 SurfaceFlinger
threadLoop 內容如下:
bool HWComposer::VSyncThread::threadLoop() { { // scope for lock Mutex::Autolock _l(mLock); while (!mEnabled) { mCondition.wait(mLock); } } 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) { // we missed, find where the next vsync should be sleep = (period - ((now - next_vsync) % period)); next_vsync = now + sleep; } mNextFakeVSync = next_vsync + period; 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; }
中間的計算式意義如下:
1.) 當 next_vsync >= now, 則 sleep 到 next_vsync 時間, 更新下一次 VSync 到來時間 (+peroid)
2.) 當 next_vsync < now, 則先更新 next_vsync 到相鄰最近的一次 peroid time.
(假設原先的 mNextFakeVSync = 5, now = 17, period = 10,
我們知道最近一次的 Vsync 是 25, 計算方式如下:
Step 1.) 先算出最靠近 now 的 next_vsync 間隔所需要的sleep time
sleep = 10 - (17-5)%10 = 10 - 2 = 8.
Step 2.) next_vsync即為 now + sleep
next_vsync = 17 + 8 = 25.
接下來依據 HWComposer HAL 的可支援性設置 mNumDisplays,
這會在後面呼叫 HAL 時持續的使用到.
最後呼叫 queryDisplayProperties() 帶入 primary & external,
這個 API 在 hotplug 的時候也會呼叫到, 用來獲取 display 的參數,
並把 mDisplayData.connected 設置為 true
HWComposer 的初始化做完了, 但還沒開始做事呢.
再回到 SurfaceFlinger::init()
生成一個 RendorEngine, 這裡會依據你的 version 來生成 GLESXXRenderEngine()
跟 EGL 相關的操作暫且跳過, 往下進入 Display 的初始化:
... // initialize our non-virtual displays for (size_t i=0 ; i<DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES ; i++) { DisplayDevice::DisplayType type((DisplayDevice::DisplayType)i); // set-up the displays that are already connected if (mHwc->isConnected(i) || type==DisplayDevice::DISPLAY_PRIMARY) { // All non-virtual displays are currently considered secure. bool isSecure = true; createBuiltinDisplayLocked(type); wp<IBinder> token = mBuiltinDisplays[i]; sp<IGraphicBufferProducer> producer; sp<IGraphicBufferConsumer> consumer; BufferQueue::createBufferQueue(&producer, &consumer, new GraphicBufferAlloc()); sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i, consumer); int32_t hwcId = allocateHwcDisplayId(type); sp<DisplayDevice> hw = new DisplayDevice(this, type, hwcId, mHwc->getFormat(hwcId), isSecure, token, fbs, producer, mRenderEngine->getEGLConfig()); if (i > DisplayDevice::DISPLAY_PRIMARY) { // FIXME: currently we don't get blank/unblank requests // for displays other than the main display, so we always // assume a connected display is unblanked. ALOGD("marking display %zu as acquired/unblanked", i); hw->setPowerMode(HWC_POWER_MODE_NORMAL); } mDisplays.add(token, hw); } } ...
HWComposer 前面在 queryDisplayProperties 時已經將 primary & external 都設置為 connected,
下面依序展開:
/* 生成 BBinder 並賦值給 mBuiltinDisplays 在 Binder 的設計架構下, BBinder 一方是 Service, 負責接收資訊, 後面一定會看到有人生成 BpBinder 來傳送資訊 */ void SurfaceFlinger::createBuiltinDisplayLocked(DisplayDevice::DisplayType type) { mBuiltinDisplays[type] = new BBinder(); DisplayDeviceState info(type); // All non-virtual displays are currently considered secure. info.isSecure = true; mCurrentState.displays.add(mBuiltinDisplays[type], info); }
這邊的 mCurrentState 是一個 State 的結構, 叫做 State 的人非常多, 這邊的定義如下:
struct State { LayerVector layersSortedByZ; DefaultKeyedVector< wp<IBinder>, DisplayDeviceState> displays; };
可以看到它由一個 LayerVector 跟 DefaultKeyedVector 所組成
在 createBuiltinDisplayLocked 中, 還生成了一個 DisplayDeviceState 的 info 變數, 拿來記錄 primary & external 的資訊:
struct DisplayDeviceState { DisplayDeviceState(); DisplayDeviceState(DisplayDevice::DisplayType type); bool isValid() const { return type >= 0; } bool isMainDisplay() const { return type == DisplayDevice::DISPLAY_PRIMARY; } bool isVirtualDisplay() const { return type >= DisplayDevice::DISPLAY_VIRTUAL; } DisplayDevice::DisplayType type; sp<IGraphicBufferProducer> surface; uint32_t layerStack; Rect viewport; Rect frame; uint8_t orientation; uint32_t width, height; String8 displayName; bool isSecure; }; SurfaceFlinger::DisplayDeviceState::DisplayDeviceState(DisplayDevice::DisplayType type) : type(type), layerStack(DisplayDevice::NO_LAYER_STACK), orientation(0), width(0), height(0) { viewport.makeInvalid(); frame.makeInvalid(); }
接下來又看到了熟悉的 BufferQueue !
上一次分析它是在前面使用者透過 SurfaceComposerClient 來建立 Layer 的時候
這裡 createBufferQueue 的時候還自帶了一個 allocator
(不過實際上應該可以也帶NULL才對...
我的印象中 Binder 如果發現 IPC 的對象是自己的話不會去多占一個通訊位子 XD
也許只是為了簡化流程吧)
它把 consumer 轉交給了 FramebufferSurface, 從前面的經驗我們可以知道 FramebufferSurface 應該會繼承 ConsumerBase 並且去實作 onFrameAvailable
它的建構子如下:
FramebufferSurface::FramebufferSurface(HWComposer& hwc, int disp, const sp<IGraphicBufferConsumer>& consumer) : ConsumerBase(consumer), mDisplayType(disp), mCurrentBufferSlot(-1), mCurrentBuffer(0), mHwc(hwc) { mName = "FramebufferSurface"; mConsumer->setConsumerName(mName); int usage = GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER; if(disp != 0) { usage |= GRALLOC_USAGE_HW_FBX; } mConsumer->setConsumerUsageBits(usage); mConsumer->setDefaultBufferFormat(mHwc.getFormat(disp)); mConsumer->setDefaultBufferSize(mHwc.getWidth(disp), mHwc.getHeight(disp)); mConsumer->setDefaultMaxBufferCount(NUM_FRAMEBUFFER_SURFACE_BUFFERS); }
帶入了 HWComposer, display_id, 以及 consumer
consumer 再透過 ConsumerBase 跟 BufferQueueCore 串在一起, 這邊不再贅述
繼續往下看,
int32_t hwcId = allocateHwcDisplayId(type); 對於 BUILT_IN_DISPLAY 來說就是再度回傳 type
這裡將剛剛還沒用上的 producer 轉交給了 DisplayDevice
... sp<DisplayDevice> hw = new DisplayDevice(this, type, hwcId, mHwc->getFormat(hwcId), isSecure, token, fbs, producer, mRenderEngine->getEGLConfig()); ... // 建構子如下: DisplayDevice::DisplayDevice( const sp<SurfaceFlinger>& flinger, DisplayType type, int32_t hwcId, int format, bool isSecure, const wp<IBinder>& displayToken, const sp<DisplaySurface>& displaySurface, const sp<IGraphicBufferProducer>& producer, EGLConfig config) : lastCompositionHadVisibleLayers(false), mFlinger(flinger), mType(type), mHwcDisplayId(hwcId), mDisplayToken(displayToken), mDisplaySurface(displaySurface), mDisplay(EGL_NO_DISPLAY), mSurface(EGL_NO_SURFACE), mDisplayWidth(), mDisplayHeight(), mFormat(), mFlags(), mPageFlipCount(), mIsSecure(isSecure), mSecureLayerVisible(false), mLayerStack(NO_LAYER_STACK), mOrientation(), mPowerMode(HWC_POWER_MODE_OFF), mActiveConfig(0) { mNativeWindow = new Surface(producer, false); ANativeWindow* const window = mNativeWindow.get(); /* * Create our display's surface */ EGLSurface surface; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (config == EGL_NO_CONFIG) { config = RenderEngine::chooseEglConfig(display, format); } surface = eglCreateWindowSurface(display, config, window, NULL); eglQuerySurface(display, surface, EGL_WIDTH, &mDisplayWidth); eglQuerySurface(display, surface, EGL_HEIGHT, &mDisplayHeight); if (mType >= DisplayDevice::DISPLAY_VIRTUAL) { // reset virtual display buffer format. // because it is changed by eglCreateWindowSurface(). native_window_set_buffers_format(window, format); window->setSwapInterval(window, 0); } mConfig = config; mDisplay = display; mSurface = surface; mFormat = format; mPageFlipCount = 0; mViewport.makeInvalid(); mFrame.makeInvalid(); // virtual displays are always considered enabled mPowerMode = (mType >= DisplayDevice::DISPLAY_VIRTUAL) ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF; // Name the display. The name will be replaced shortly if the display // was created with createDisplay(). switch (mType) { case DISPLAY_PRIMARY: mDisplayName = "Built-in Screen"; break; case DISPLAY_EXTERNAL: mDisplayName = "HDMI Screen"; break; default: mDisplayName = "Virtual Screen"; // e.g. Overlay #n break; } // initialize the display orientation transform. setProjection(DisplayState::eOrientationDefault, mViewport, mFrame); #ifdef EGL_ANDROID_swap_rectangle // only enable swap rectange in PRIMARY and EXTERNAL display, hwcomposer // has problem to support swap rectange in Virtual display. if (eglSetSwapRectangleANDROID(display, surface, 0, 0, mDisplayWidth, mDisplayHeight) == EGL_TRUE && mType <= DISPLAY_EXTERNAL) { // This could fail if this extension is not supported by this // specific surface (of config) mFlags |= SWAP_RECTANGLE; } // when we have the choice between PARTIAL_UPDATES and SWAP_RECTANGLE // choose PARTIAL_UPDATES, which should be more efficient if (mFlags & PARTIAL_UPDATES) mFlags &= ~SWAP_RECTANGLE; #endif }
還記得前面 BootAnimation 透過 SurfaceControl 建立了一個 Surface 嗎?
DisplayDevice 自身的 mNativeWindow 也同樣是一個 Surface 類型
幾個重要的成員變數如下:
mDisplayToken => 前面生成的 Binder 對象
mDisplaySurface => FramebufferSurface 對象 (consumer)
mNativeWindow => Surface 對象 (producer)
mSurface => EGLSurface, 會由它自己的 Surface 透過 producer 取得 GraphicBuffer
對於 BootAnimation 來說, producer 是它自己, consumer 是 SurfaceFlinger.
它繪製開機動畫提供給 SurfaceFlinger 使用.
對於 SurfaceFlinger 來說, producer 是 HWComposer, 它負責把屬於這個 Display 的圖形繪製成一張, 並呈現到對應的 FrameBuffer (螢幕)
再回到 SurfaceFlinger,
生成後的 DisplayDevice 被加到了 mDisplay 這個 KeyedVector 中, 方便後續管理使用.
接下來生成 EventControlThread, 將自己帶入
這個 EventControlThread 只有專心做一件事情, 就是來控制 VSync 是否要啟動.
最後將 mDrawingState 指向 mCurrentState, 然後呼叫 initializeDisplays()
void SurfaceFlinger::initializeDisplays() { class MessageScreenInitialized : public MessageBase { SurfaceFlinger* flinger; public: MessageScreenInitialized(SurfaceFlinger* flinger) : flinger(flinger) { } virtual bool handler() { flinger->onInitializeDisplays(); return true; } }; sp<MessageBase> msg = new MessageScreenInitialized(this); postMessageAsync(msg); // we may be called from main thread, use async message }
onInitializeDisplays() 如下:
它其實只是要送入 eDisplayProjectionChanged 跟 eLayerStackChanged 這兩個 flags
這兩個 flags 在後面談論繪圖功能時會再提到...
void SurfaceFlinger::onInitializeDisplays() { // reset screen orientation and use primary layer stack Vector<ComposerState> state; Vector<DisplayState> displays; DisplayState d; d.what = DisplayState::eDisplayProjectionChanged | DisplayState::eLayerStackChanged; d.token = mBuiltinDisplays[DisplayDevice::DISPLAY_PRIMARY]; d.layerStack = 0; d.orientation = DisplayState::eOrientationDefault; d.frame.makeInvalid(); d.viewport.makeInvalid(); d.width = 0; d.height = 0; displays.add(d); setTransactionState(state, displays, 0); setPowerModeInternal(getDisplayDevice(d.token), HWC_POWER_MODE_NORMAL); const nsecs_t period = getHwComposer().getRefreshPeriod(HWC_DISPLAY_PRIMARY); mAnimFrameTracker.setDisplayRefreshPeriod(period); }
它其實只是要送入 eDisplayProjectionChanged 跟 eLayerStackChanged 這兩個 flags
這兩個 flags 在後面談論繪圖功能時會再提到...
setPowerModeInternal 看似簡單, 不過也做了不少事情.
void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& hw, int mode) { ALOGD("Set power mode=%d, type=%d flinger=%p", mode, hw->getDisplayType(), this); int32_t type = hw->getDisplayType(); int currentMode = hw->getPowerMode(); if (mode == currentMode) { ALOGD("Screen type=%d is already mode=%d", hw->getDisplayType(), mode); return; } hw->setPowerMode(mode); if (type >= DisplayDevice::NUM_BUILTIN_DISPLAY_TYPES) { ALOGW("Trying to set power mode for virtual display"); return; } if (currentMode == HWC_POWER_MODE_OFF) { getHwComposer().setPowerMode(type, mode); if (type == DisplayDevice::DISPLAY_PRIMARY) { // FIXME: eventthread only knows about the main display right now #ifdef VSYNC_DIRECT_REFRESH mSFEventThread->onScreenAcquired(); #endif mEventThread->onScreenAcquired(); resyncToHardwareVsync(true); } mVisibleRegionsDirty = true; mHasPoweredOff = true; repaintEverything(); } else if (mode == HWC_POWER_MODE_OFF) { if (type == DisplayDevice::DISPLAY_PRIMARY) { disableHardwareVsync(true); // also cancels any in-progress resync // FIXME: eventthread only knows about the main display right now #ifdef VSYNC_DIRECT_REFRESH mSFEventThread->onScreenReleased(); #endif mEventThread->onScreenReleased(); } getHwComposer().setPowerMode(type, mode); mVisibleRegionsDirty = true; // from this point on, SF will stop drawing on this display } else { getHwComposer().setPowerMode(type, mode); } }
一開始我們的 currentMode 是 HWC_POWER_MODE_OFF
這裡將 DisplayDevice (primary) 的 powerMode 更新為 HWC_POWER_MODE_NORMAL,
並且呼叫了 mEventThread->onScreenAcquired(); 來喚醒 EventThread 並分發 VSyncEvent
最後最後呼叫了 repaintEverything 通知 SurfaceFlinger 更新畫面,
到這邊 SurfaceFlinger 的 init 就全部完成了...!!! d(`・∀・)b
看到最後一行的 startBootAnim() 就會發現實際上 BootAnimation 的啟動是在 SurfaceFlinger 完成後才帶起來的, 不然也沒地方可以給他畫嘛 XD
至此每個重要部件都差不多初始化完成了, 接下來進入畫畫篇.
キタ━━━━(゚∀゚)━━━━!!
2018年1月12日 星期五
從 BootAnimation 探索 SurfaceFlinger (五) 新章再開! SurfaceFlinger 與 DispSync 事件
前情提要
從 BootAnimation 的初始化到現在, 我們總共做了這幾件事情:
1.) 透過 SurfaceComposerClient 跟 SurfaceFlinger 建立關係 (createConnection)
2.) 透過 SurfaceComposerClient 請 SurfaceFlinger 建立了一個 Layer 對象,
在 Layer 的建構子生成了一個 BufferQueue, 它的消費者是 SurfaceFlinger,
而生產者則回傳給了 SurfaceComposerClient
3.) SurfaceComposerClient 創建了一個 SurfaceControl, 並把生產者的角色交給它.
4.) SurfaceControl 會生成一個 Surface 對象, 並把生產者帶給它
Surface 是本地的視窗介面,
作為一個生產者, EGL 透過會 Surface 對象調用 dequeueBuffer 取得 GraphicBuffer 來寫入資料,
作為一個生產者, EGL 透過會 Surface 對象調用 dequeueBuffer 取得 GraphicBuffer 來寫入資料,
完成寫入後再透過 queueBuffer 通知 SurfaceFlinger 有新的資料到來.
在前面的文章中, 我們只有簡單談到了 SurfaceFlinger 是作為一個 Service 被帶起, 還沒有深入的聊到它.
現在我們來看一下 SurfaceFlinger 的初始化流程:
// main_surfaceflinger.cpp int main(int, char**) { ... // instantiate surfaceflinger sp<SurfaceFlinger> flinger = new SurfaceFlinger(); ... // initialize before clients can connect flinger->init(); // publish surface flinger sp<IServiceManager> sm(defaultServiceManager()); sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false); // run in this thread flinger->run(); return 0; }
SurfaceFlinger 的建構子沒有做甚麼特別的處理,
不過不要忘記了 onFirstRef 函數, 這裡會初始化 MessageQueue (EventQueue )
前面在 createSurface 時, 我們有透過 mFlinger->postMessageSync(msg) 來傳遞 MessageCreateLayer 這個 message.
關於整個 MessageQueue 的 flow, 可以參考這張圖:
接下來我們看看 SurfaceFlinger 的 init 函數:
好的, 馬上又遇到了新朋友 DispSyncSource 跟 EventThread
這裡面帶入了一個 mPrimaryDispSync 參數
這是一個 DispSync 類型的變數, 建構子如下:
它又 new 了一個 DispSyncThread 並啟動之,
查看一下 threadLoop 的功能...
後面應該會看到有人透過 addEventListener 來註冊 Listener,
並且使用 updateModel 來啟動這個 thread.
比較重要的就是 mDispSync 與 mPhaseOffset 這兩個參數了
查看它的子函數, 果然 setPhaseOffset 會透過 mDispSync 來註冊 eventListener
另外還有一個函數 setVSyncEnabled 也可以來增加/刪除 eventListener
這裡帶入的 callback 是 static_cast<DispSync::Callback*>this,
更新了 mVSyncEvent 的 type 與 timestamp, 並觸發了一個 mCondition.
這表示另外一邊一定會有人在等待這個 event 到來.
所以 mDispSync 的作用就是在啟動後, 每隔一段時間發出一個 event 給它的 listener 們
而目前看到 listener 是從 EventThread::enableVSyncLocked 註冊來的
回到 SurfaceFlinger, 我們現在知道有兩個 EventThread 被帶起來了,
為什麼會分成兩個呢? 其實從他們的名稱來看可見一斑:
一個是給 apps 用的 (通知每個 Layers 去畫圖)
另一個則是給 surfaceflinger 自己更新畫面用的
在 EventThread::onFirstRef() 會啟動自身的 threadLoop(),
它在 threadLoop() 中持續等待 event (Vsync) 發生.
如果有 event 的話, 就將 event 發送給全部的 connection.
剛好在第二個 EventThread 之中有建立了一個 Connection,
我們看到 mSFEventThread 會被 mEventQueue 作為它的 mEventThread 使用.
簡單整理一下:
在裡面 Looper 除了處理 Message 以外, 也會負責處理這些 Events, 實在是功能拔群呀 ⁽⁽ ◟(∗ ˊωˋ ∗)◞ ⁾⁾
明明只有五行 code ...為什麼分析起來會如此龐大呢... (¦3[▓▓]
下一篇, 令人熟悉的 Composer 又出現了, 鳩竟這個 Composer 是不是真的會做 Composer 該做的事情呢?
讓我們繼續....看...下....去..........
不過不要忘記了 onFirstRef 函數, 這裡會初始化 MessageQueue (EventQueue )
前面在 createSurface 時, 我們有透過 mFlinger->postMessageSync(msg) 來傳遞 MessageCreateLayer 這個 message.
關於整個 MessageQueue 的 flow, 可以參考這張圖:
接下來我們看看 SurfaceFlinger 的 init 函數:
void SurfaceFlinger::init() { Mutex::Autolock _l(mStateLock); // initialize EGL for the default display mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(mEGLDisplay, NULL, NULL); // start the EventThread sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync, vsyncPhaseOffsetNs, true, "app"); mEventThread = new EventThread(vsyncSrc);
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, sfVsyncPhaseOffsetNs, true, "sf"); mSFEventThread = new EventThread(sfVsyncSrc); mEventQueue.setEventThread(mSFEventThread); ...
好的, 馬上又遇到了新朋友 DispSyncSource 跟 EventThread
這裡面帶入了一個 mPrimaryDispSync 參數
這是一個 DispSync 類型的變數, 建構子如下:
DispSync::DispSync() : mRefreshSkipCount(0), mThread(new DispSyncThread()) { mThread->run("DispSync", PRIORITY_URGENT_DISPLAY + PRIORITY_MORE_FAVORABLE); reset(); beginResync(); }
它又 new 了一個 DispSyncThread 並啟動之,
查看一下 threadLoop 的功能...
DispSyncThread(): mStop(false), mPeriod(0), mPhase(0), mWakeupLatency(0) { } virtual bool threadLoop() { status_t err; // 初始化 now 為系統時間 nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); nsecs_t nextEventTime = 0; while (true) { // 有個 callback 的 vector, 後面一定會從某處取得 callbacks 並呼叫 Vector<CallbackInvocation> callbackInvocations; nsecs_t targetTime = 0; { // Scope for lock Mutex::Autolock lock(mMutex); // 一開始是 false, 不管它 if (mStop) { return false; } // 一開始設為 0, 所以會進來等 condition // 看了一下會在 updateModel, addEventListener, removeEventListener 時進入 // 又只有 updateModel 會修改到 mPeriod 的值, 所以一定是透過 updateModel 來啟動 if (mPeriod == 0) { err = mCond.wait(mMutex); if (err != NO_ERROR) { ALOGE("error waiting for new events: %s (%d)", strerror(-err), err); return false; } continue; } // 遍歷所有連上的 listener, 依據他們的 phase 計算出下一次 Event 到來的時間 nextEventTime = computeNextEventTimeLocked(now); targetTime = nextEventTime; // Debug 用的參數, 可以不用管它 bool isWakeup = false; // 等待 timeOut 自動喚醒, 如果是 timeOut 則標記為 isWakeup if (now < targetTime) { err = mCond.waitRelative(mMutex, targetTime - now); if (err == TIMED_OUT) { isWakeup = true; } else if (err != NO_ERROR) { ALOGE("error waiting for next event: %s (%d)", strerror(-err), err); return false; } } // 再次 update now now = systemTime(SYSTEM_TIME_MONOTONIC); // 遍歷全部註冊的 EventListener, 並帶入現在時刻 // 如果他們的 NextEventTime 小於 now, 表示應該要觸發了 callbackInvocations = gatherCallbackInvocationsLocked(now); } // 如果有 callback, 則觸發之 if (callbackInvocations.size() > 0) { fireCallbackInvocations(callbackInvocations); } } return false; }
後面應該會看到有人透過 addEventListener 來註冊 Listener,
並且使用 updateModel 來啟動這個 thread.
這個 DispSync 被放入了 DispSyncSource 之中,
DispSyncSource(DispSync* dispSync, nsecs_t phaseOffset, bool traceVsync, const char* label) : mValue(0), mTraceVsync(traceVsync), mVsyncOnLabel(String8::format("VsyncOn-%s", label)), mVsyncEventLabel(String8::format("VSYNC-%s", label)), mDispSync(dispSync), mCallbackMutex(), mCallback(), mVsyncMutex(), mPhaseOffset(phaseOffset), mEnabled(false) {}
比較重要的就是 mDispSync 與 mPhaseOffset 這兩個參數了
查看它的子函數, 果然 setPhaseOffset 會透過 mDispSync 來註冊 eventListener
另外還有一個函數 setVSyncEnabled 也可以來增加/刪除 eventListener
這裡帶入的 callback 是 static_cast<DispSync::Callback*>this,
Callback觸發的事件是 onDispSyncEvent
這裡會呼叫 mCallback成員
查看 member variable 可以發現 mCallback 這個成員會透過 setCallback 更新.
virtual void onDispSyncEvent(nsecs_t when) { sp<VSyncSource::Callback> callback; { Mutex::Autolock lock(mCallbackMutex); callback = mCallback; if (mTraceVsync) { mValue = (mValue + 1) % 2; ATRACE_INT(mVsyncEventLabel.string(), mValue); } } if (callback != NULL) { callback->onVSyncEvent(when); } }
這裡會呼叫 mCallback成員
查看 member variable 可以發現 mCallback 這個成員會透過 setCallback 更新.
再查找之後可以發現是 EventThread::enableVSyncLocked 來設置的,
它設置 callback 之後還會註冊 listener 進去
這裡又把 this 給帶進來了, 我們看一下它的 onVSyncEvent:它設置 callback 之後還會註冊 listener 進去
void EventThread::enableVSyncLocked() { #ifndef VSYNC_DIRECT_REFRESH // 我們沒有設置, 所以會進入 // 當螢幕關閉時, mUserSoftware 才會是 true if (!mUseSoftwareVSync) { // never enable h/w VSYNC when screen is off if (!mVsyncEnabled) { mVsyncEnabled = true; mVSyncSource->setCallback(static_cast<VSyncSource::Callback*>(this)); mVSyncSource->setVSyncEnabled(true); } } #endif mDebugVsyncEnabled = true; sendVsyncHintOnLocked(); }
void EventThread::onVSyncEvent(nsecs_t timestamp) { Mutex::Autolock _l(mLock); mVSyncEvent[0].header.type = DisplayEventReceiver::DISPLAY_EVENT_VSYNC; mVSyncEvent[0].header.id = 0; mVSyncEvent[0].header.timestamp = timestamp; mVSyncEvent[0].vsync.count++; mCondition.broadcast(); }
更新了 mVSyncEvent 的 type 與 timestamp, 並觸發了一個 mCondition.
這表示另外一邊一定會有人在等待這個 event 到來.
所以 mDispSync 的作用就是在啟動後, 每隔一段時間發出一個 event 給它的 listener 們
而目前看到 listener 是從 EventThread::enableVSyncLocked 註冊來的
回到 SurfaceFlinger, 我們現在知道有兩個 EventThread 被帶起來了,
為什麼會分成兩個呢? 其實從他們的名稱來看可見一斑:
一個是給 apps 用的 (通知每個 Layers 去畫圖)
另一個則是給 surfaceflinger 自己更新畫面用的
sp<VSyncSource> vsyncSrc = new DispSyncSource(&mPrimaryDispSync, vsyncPhaseOffsetNs, true, "app");
sp<VSyncSource> sfVsyncSrc = new DispSyncSource(&mPrimaryDispSync, sfVsyncPhaseOffsetNs, true, "sf");
在 EventThread::onFirstRef() 會啟動自身的 threadLoop(),
它在 threadLoop() 中持續等待 event (Vsync) 發生.
如果有 event 的話, 就將 event 發送給全部的 connection.
bool EventThread::threadLoop() { DisplayEventReceiver::Event event; Vector< sp<EventThread::Connection> > signalConnections; signalConnections = waitForEvent(&event); // dispatch events to listeners... const size_t count = signalConnections.size(); for (size_t i=0 ; i<count ; i++) { const sp<Connection>& conn(signalConnections[i]); // now see if we still need to report this event status_t err = conn->postEvent(event); if (err == -EAGAIN || err == -EWOULDBLOCK) { // The destination doesn't accept events anymore, it's probably // full. For now, we just drop the events on the floor. // FIXME: Note that some events cannot be dropped and would have // to be re-sent later. // Right-now we don't have the ability to do this. ALOGW("EventThread: dropping event (%08x) for connection %p", event.header.type, conn.get()); } else if (err < 0) { // handle any other error on the pipe as fatal. the only // reasonable thing to do is to clean-up this connection. // The most common error we'll get here is -EPIPE. removeDisplayEventConnection(signalConnections[i]); } } return true; }
剛好在第二個 EventThread 之中有建立了一個 Connection,
我們看到 mSFEventThread 會被 mEventQueue 作為它的 mEventThread 使用.
void MessageQueue::setEventThread(const sp<EventThread>& eventThread) { mEventThread = eventThread; // 生成一個 Connection, 裡面會生成 DataChannel mEvents = eventThread->createEventConnection(); // 獲取 DataChannel, 並把它的 fd 加入 Looper 中 // 這樣當 eventThread 被觸發時, Message Queue 就會收到新的 event mEventTube = mEvents->getDataChannel(); mLooper->addFd(mEventTube->getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver, this); } sp<EventThread::Connection> EventThread::createEventConnection() const { return new Connection(const_cast<EventThread*>(this)); } // Connection 的建構子如下 EventThread::Connection::Connection( const sp<EventThread>& eventThread) : count(-1), mEventThread(eventThread), mChannel(new BitTube()) { } sp<BitTube> EventThread::Connection::getDataChannel() const { return mChannel; } // 在 onFirstRef 將自己註冊給 mDisplayEventConnections void EventThread::Connection::onFirstRef() { // NOTE: mEventThread doesn't hold a strong reference on us mEventThread->registerDisplayEventConnection(this); } // 這個 mDisplayEventConnections 將會在 EventThread 的 threadLoop 中使用 // 當 connection status_t EventThread::registerDisplayEventConnection( const sp<EventThread::Connection>& connection) { Mutex::Autolock _l(mLock); mDisplayEventConnections.add(connection); mCondition.broadcast(); return NO_ERROR; } int MessageQueue::cb_eventReceiver(int fd, int events, void* data) { MessageQueue* queue = reinterpret_cast<MessageQueue *>(data); return queue->eventReceiver(fd, events); } int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) { ssize_t n; DisplayEventReceiver::Event buffer[8]; while ((n = DisplayEventReceiver::getEvents(mEventTube, buffer, 8)) > 0) { for (int i=0 ; i<n ; i++) { if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { #if INVALIDATE_ON_VSYNC mHandler->dispatchInvalidate(); #else mHandler->dispatchRefresh(); #endif break; } } } return 1; }
簡單整理一下:
MessageQueue 呼叫 mSFEventThread 生成了一個 Connection,
這個 Connection 會自動註冊進 mSFEventThread 之中.
EventThread 在 threadLoop 中等待 event 到來,
當 DispSyncSource 產生的 event 到來時, 發送給每個 Connection
Connection 收到 Event 後, 往自己的 mChannel 寫,在上面貼了一張 MessageQueue 是如何處理 Message 的流程圖,
MessageQueue 的 Looper 就會被喚醒並且填入 Event, 並呼叫它的 callback function,
也就是從 addFd 帶進來的 MessageQueue::cb_eventReceiver (它現在被 SimpleLooperCallback 封裝起來)
在裡面 Looper 除了處理 Message 以外, 也會負責處理這些 Events, 實在是功能拔群呀 ⁽⁽ ◟(∗ ˊωˋ ∗)◞ ⁾⁾
明明只有五行 code ...為什麼分析起來會如此龐大呢... (¦3[▓▓]
下一篇, 令人熟悉的 Composer 又出現了, 鳩竟這個 Composer 是不是真的會做 Composer 該做的事情呢?
讓我們繼續....看...下....去..........
訂閱:
文章 (Atom)
不定參數印 log
From the UNIXProcess_md.c #ifdef DEBUG_PROCESS /* Debugging process code is difficult; where to write debug output? */ static void deb...
-
1. SikuliX 本體下載 首先你必須要下載SikuliX (廢話) (撰寫本文時, SikuliX版本為 sikulixsetup-1.1.2.jar) http://sikulix.com/quickstart/ 由於SikuliX IDE是運行在Ja...
-
最近在看CNN的文章的時候, 提到了Hierarchy的概念 目的是以 divide-and-conquer 的概念來將大問題切割為小問題 以image recognition / computer vision來說, "直覺上"每個pixels只會...