V-Sync體系下的螢幕渲染機制
問題:view.backgroundColor = UIColor.red 後螢幕怎麼知道顏色改變了
也就是CPU執行完這句程式碼後,怎麼告知螢幕?
UI更新資訊傳達到螢幕採是顯示器定時取資料的方式,有個東西叫做vsync訊號,UI更新後資料會被放在一個緩衝區,vsync訊號來臨後,顯示器會把緩衝區的資料顯示出來。取資料的頻率(vsync訊號的頻率)就是顯示器的重新整理頻率,iOS中這個週期是16.67ms,也就是iOS的最大幀率60ps。值得一提的是,iOS中螢幕觸控掃描週期一般和螢幕重新整理週期是重疊的,也就是在兩個vsync訊號中間螢幕會進行掃描觸控點,然後在下次vsync訊號時傳遞給app觸控事件。
(tip:iOS每秒最多捕捉60個點)
問題:那螢幕沒有任何變化的時候呢,也會更新嗎
不知道大家有沒有想起一個繪製方法——draw rect,我們可以在draw rect中加入自己想要繪製的內容,然後就會被顯示出來,每秒重新整理60次是不是意味著draw rect每秒被呼叫60次?
並不是的,有變化的layer都會被做上標記,下次渲染只會渲染這些layer,如果沒有layer有變化,就沒有資料被渲染,顯示器顯示的就是上一幀的畫面
學習draw rect的時候,學習資料會告訴你,draw rect不能自己呼叫,它是系統呼叫的方法,只有系統知道它什麼時候該呼叫,那它什麼時候呼叫呢
等到主執行緒忙完的時候,也就是runloop將要休息的時候,Core Animation就會呼叫一遍被標記layer的view的layout subviews和draw rect方法,當然,是這兩個方法被重寫的情況下才會呼叫。那Core Animation怎麼知道runloop即將休息了呢,因為Core Animation註冊了runloop狀態的觀察者
在Core Animation做完了它自己的以及調完了我們重寫的方法後,更新了的layer的資料會被打包送到一個叫做render process 的程序等待下一步的工作
runloop即將休息時呼叫layout subviews、draw rect方法,還有做了其他的一些事,然後把layer資料送去render process程序,這整個動作叫做commit,在draw rect或者layout subviews方法打斷點,可以看到堆疊上有這樣的方法:CA::Transaction::commit()
哎,等等,剛才提到說“等到主執行緒忙完的時候”,那麼意思是說,如果主執行緒一直有事做,那麼layer的更新資料就一直不會被提交?那螢幕不就不動了?
對的,如果主執行緒做其他的事,或者commit的方法要做的事情太多,螢幕就不動了,就掉幀了
其實不止app有Core Animation,render process也有一個Core Animation,只不過跟app的Core Animation有些不同。render process 的 Core Animation 可以生成GPU指令,不確定app 的 Core Animation能不能,不過app的 Core Animation不需要這麼做,從配合上來看,app的 Core Animation和 render process的 Core Animation應該是互補的
V-Sync體系
來給大家看個圖

渲染流程圖1
這個是wwdc上的一個渲染流程圖,這個一格一格的就是vsync訊號間隔,可以看到render process的動作是從接受到vsync訊號才開始的,確實是這樣的,其實vsync訊號並不是為了讓螢幕知道資料變化了才出現的,螢幕有自己的穩定的重新整理頻率,把GPU輸出頻率調整和螢幕的重新整理頻率一樣,沒有vsync訊號,我們設定的背景顏色也會被螢幕的自己穩定的重新整理頻率顯示到螢幕上。vsync訊號的出現是為了解決GPU輸出頻率不穩定而導致上一幀和下一幀混合產生的撕裂畫面產生的。但由於這種改動,導致vsync現在在系統很多地方都有影響,vsync本身的影響已經超過了它的初衷,比如跟螢幕掃描週期重疊、在動畫實現的流程中起到關鍵作用,比原先預想的控制渲染開始時間,防止上一幀和下一幀混合影響範圍要大,已經形成自己的體系,說是vsync體系的渲染也不足為過
可以從圖上看出來,渲染和顯示都是踩著vsync訊號的點進行的
再給大家看一張圖

渲染流程圖2.png
從這張圖可以看出來,不止渲染和顯示,連螢幕捕捉點、app進行處理touch事件和commit都是踩著vsync訊號進行的
如果是動畫的話,這裡的app處理就只是vsync訊號喚起runloop,然後進行commit操作
如果是touch事件的話,這裡的app處理還會touch事件機制和相關我們app對點選的處理,如果螢幕有變化,會在runloop最後進行commit(注意,runloop最後這個時間點跟vsync訊號完全沒關係,app處理可以很快,只需要1ms就處理好,那commit就發生在上個vsync訊號後的1ms,如果app處理的很慢,20ms都沒處理好,此時下一個vsync訊號已經來了,這次的commit就會被丟棄,就發生了掉幀)
動畫的大致原理
app 的Core Animation會管理三種tree,layer tree、presentation tree 和 render tree,這三種層次結構是實現動畫的基礎,layer tree存放目標tree,presentation tree存放實時tree。動畫要進行下一幀時vsync會喚醒runloop,然後app更新presentation tree,再commit進行渲染。這樣渲染流程就像下面這樣

動畫渲染流程.png
總結:
動畫更新畫面,或者螢幕掃描到touch,app會在下一個vsync訊號來臨時接受到相關資訊,並進行處理。app更新的layer會被標記並通過CATransaction提交到一個彙總的地方,等到主執行緒沒事幹的時候(runloop將要睡眠或退出),Core Animation就會把這些提交匯總合並(比如runloop週期內先設定了color = .red,然後設定了 color = .black,最後合併後是color = .black)(Core Animation會註冊runloop狀態通知),並呼叫layout subviews、draw rect方法,進行圖片解壓,然後commit到render process。第二個vsync訊號來了的時候render process開始工作,工作完了交接給GPU,GPU進行渲染,render process和GPU的工作時間被限制在2 16.67ms內(16.67ms內完成,系統是雙緩衝模式,超過16.67ms,系統會開啟三緩衝模式,但是如果不能在2 16.67ms內完成工作,就會被丟棄),GPU渲染好後會放在back buffer中,第三次vsync訊號來臨就會轉移到front buffer中,然後顯示器會顯示出來