主迴圈
在本節中,您將學習更多關於TouchGFX中圖形引擎的工作方式特別是主迴圈的內容。 回想一下,圖形引擎的主要任務是將應用的圖形(ui模型)渲染到影像緩衝。 此過程反復發生,以便在顯示器上產生新的影像。
圖形引擎採集外部事件,例如顯示器觸摸或按鈕按下事件。 這些事件經過篩選後被傳遞到應用。 應用可使用這些事件更新UI模型。 例如 在使用者觸控螢幕上的按鈕時將按鈕狀態更改為按下狀態,然後在使用者不再觸控螢幕時將按鈕狀態改回釋放狀態。
最後,圖形引擎將更新後的模型渲染到影像緩衝。 此過程無限輪迴。
在渲染影像後,影像緩衝被傳輸到顯示器,用戶可從顯示器上看到圖形。 為避免顯示器上的短時脈衝波干擾,傳輸過程必須與顯示器同步。 對於某些顯示器,必須以最小時間間隔定期進行傳輸。 對於其餘顯示器,必須在顯示器發出信號時進行傳輸。
圖形引擎通過等待硬體抽象層發出的“開始”信號實現此同步。 點擊此處閱讀關於硬體抽象層的更多內容
在程式碼中,TouchGFX圖形引擎內部的主迴圈大致是這樣的:
while(true) {
collect(); // Collect events from outside
update(); // Update the application ui model
render(); // Render new updated graphics to the framebuffer
wait(); // Wait for 'go' from display
}
程式碼更多地涉及實際實現,但上述程式碼有助於理解引擎的主要部分。
下面我們將詳細討論這四個階段。
採集
在該階段,圖形引擎從外部環境採集事件。 這些事件通常是觸摸事件和按鈕。
TouchGFX對事件進行採樣並傳遞到應用。 原始觸摸事件會被轉換為更具體的觸摸事件:
- 點擊:使用者用手指在螢幕上按下或鬆開
- 拖曳:使用者在螢幕上移動其手指(在觸控式螢幕的同時)。
- 手勢:用戶沿某個方向快速移動其手指然後釋放。 這被稱為滑動,由圖形引擎識別。
事件被傳遞到當前活動的UI元素(如小工具)。
引擎還傳遞tick事件。 此事件表示新的幀(或時間節拍),會一直發送,在沒有其他外部輸入時也會發送。 應用使用此事件驅動動畫或其他基於時間的操作,例如在特定時間結束後切換到暫停介面。
更新
圖形引擎與應用一起更新UI,以便反映採集的事件。 圖形引擎知曉當前活動的介面,並將事件傳遞給該物件。
基本原理是引擎將事件通知給應用(即UI模型中的Screen和Widget物件)。 作為回應,應用請求重繪介面的特定部分。 應用不以直接繪製的方式回應事件,而是更改Widget的屬性並請求重繪。
如果發生諸如Click這樣的事件,圖形引擎將搜索Screen物件的場景模型,以便找到應接收事件的Widget。 一些Widget(如Image和TextArea)不希望接收Click事件。 它們還有一個空的事件處理器,因此不會發生任何事件。
其他Widget(如Button)會回應Click事件(按下或釋放)。 Button小工具在被按下時更改其狀態並顯示另一幅圖像,並在觸摸再次釋放時變回原始狀態。
當Widget(如Button)改變其狀態時,也必須在影像緩衝中重繪它。 作為對事件的回應,Widget負責將此資訊傳回圖形引擎。 圖形引擎本身不基於採集的事件重繪任何Widget。 Widget持續跟蹤自身的內部狀態(對於Button,為要繪製的圖像),並指示圖形引擎重繪Widget覆蓋的介面部分(矩形)。
應用本身也能對事件做出回應。 通常使用以下兩種方式中的一種:
- 在TouchGFX Designer中為Widget配置交互 例如,我們可以配置交互,讓另一個Widget在Button被按下時可見。 此交互在Button更改其狀態並從圖形引擎請求重繪自身之後執行。 如果使用此交互顯示另一個(不可見)Widget,應用還應從圖形引擎請求重繪。
- 在介面上回應事件 也可以直接在介面上回應事件。 事件處理器是Screen類上的虛擬函數。 這些函數可在應用的Screen中重複實現。 例如,可用於在用戶觸控式螢幕時執行操作,無論哪裡,只要觸摸的是Widget。
Screen類具有下列事件處理器。 在採集到相應外部事件後,圖形引擎會呼叫這些函數:
framework/include/touchgfx/Screen.hpp
virtual void handleClickEvent(const ClickEvent& evt);
virtual void handleDragEvent(const DragEvent& evt);
virtual void handleGestureEvent(const GestureEvent& evt);
virtual void handleTickEvent();
virtual void handleKeyEvent(uint8_t key);
可以在這些事件處理器中插入任何C++程式碼。 應用通常會更新一些Widget的狀態和/或呼叫一些應用特定的函數。
基於時間的更新
handleTickEvent事件處理器在每一幀都會被呼叫。 這使得應用能夠基於時間更新使用者介面。 例如,在10秒後Widget漸隱。 假設一秒有60幀,程式碼可以是這樣:
void handleTickEvent() {
tickCounter += 1;
if (tickCounter == 600) {
myWidget.startFadeAnimation(0, 20); // Fade to 0 = invisible in 20 frames
}
}
圖形引擎還將呼叫Model類上的事件處理器。 此事件處理器通常用於執行重複操作,如檢查訊息佇列或進行GPIO採樣:
void Model::tick() {
bool b = sampleGPIO_Input1(); // Sample polled IO
if (b) {
...
}
}
請求重繪
正如上文所述,以Button為例,Widget負責在其狀態改變時請求重繪。 這背後的機制稱為無效區域。
當Button改變狀態(如從釋放變為按下)並需要重繪時,Button Widget覆蓋的區域即為無效區域。 圖形引擎保留了為幀請求的這些無效區域的清單。 採集的所有事件(觸摸、按鈕和tick)可能導致一個或多個無效區域,因此每一幀可能有許多個無效區域。
Screen類上的事件處理器也可以請求區域重繪。 下面我們更改第10幀的Box小工具box1的色彩,並通過呼叫Box上的Invalidate方法請求重繪:
void handleTickEvent() {
tickCounter += 1;
if (tickCounter == 10) {
box1.setColor(Color::getColorFrom24BitRGB(0xFF, 0x00, 0x00)); // Set color to red
box1.invalidate(); // Request redraw
}
}
在本例中,圖形引擎將在每一幀中呼叫handleTickEvent處理器。 在第10幀,應用程式碼請求重繪box1覆蓋的區域。 作為對該請求的回應,圖形引擎將在影像緩衝中用box1小工具中保存的色彩重繪該區域。
在下面的使用者介面中,背景圖像上方有一個Button Widget和一個Box Widget。 如果我們在Button上插入一個交互,以便在Button被點擊時更改Box的色彩,那麼當用戶點擊Button時,我們會得到兩個無效區域(用紅色表示):
為了獲得影像緩衝中繪製的新色彩,Box的區域無效化。 為了重新繪製釋放狀態,Button還將自身無效化。
渲染
如前文所述,更新階段的結果是待重繪區域(無效區域)的清單。 渲染階段的任務實際上是遍歷此列表,並將覆蓋這些區域的Widget繪製到影像緩衝。
此階段由圖形引擎自動處理。 應用已經定義的場景模型(ui中的Widget)並使一些區域無效化。 其餘的工作由引擎來處理。
圖形引擎逐一處理無效區域。 引擎掃描每個區域的場景模型,並採集區域覆蓋(部分或全部)的Widget的清單。
根據此Widget清單,圖形引擎呼叫Widget上的繪製方法。 從背景中的Widget開始,到最前面的Widget結束。
在繪製到影像緩衝時,Widget的繪製方法會用到Widget的狀態(如色彩)。 在更新階段,繪製Widget所需的任何資訊都必須保存到Widget。 否則,在渲染階段將無法獲取此資訊。
Wait
TouchGFX圖形引擎在更新和渲染下一幀之前等待一個信號。 之所以在幀之間等待而不是儘快地繼續渲染幀,原因有兩個:
渲染與顯示器同步。 如上文所述,一些顯示器需要反復發送影像緩衝。 在發送進行時,隨意地將幀渲染到影像緩衝是不可取的。 因此,圖形引擎會在發送開始後等待一小段時間,然後再開始渲染。 在應發送影像緩衝時,其他顯示器向微控制器發送信號。 圖形引擎等待該信號。
按固定速率渲染幀。 對於應用而言,按固定速率渲染影像的好處是更容易創建持續特定時間的動畫。 例如,如果顯示器頻率為60 Hz,則應將兩秒鐘的動畫設定為在120幀內完成。
圖形引擎的等待時間通常被應用中其他優先順序較低的程序利用。 在這種情況下,時間不會被浪費,優先順序較低的程序反正都應在某些時間點運行。
處理影像緩衝
如前文所述,圖形引擎會在更新影像緩衝之前與顯示器同步。 在渲染到影像緩衝後,引擎還需確保顯示器顯示更新後的影像緩衝。
兩個影像緩衝
在最簡單的設置中,有兩個影像緩衝可供使用。 圖形引擎在兩個影像緩衝之間切換。 在將影像繪製到一個影像緩衝的同時,將另一個影像緩衝傳輸到(並顯示在)顯示器上。
在此次繪製中,假設並行RGB顯示器連接了LTDC控制器。 這意味著在每一幀中都必須將影像緩衝發送到顯示器。 由於有兩個影像緩衝,圖形引擎可以在發送一個影像緩衝的同時將影像繪製到另一個影像緩衝。 此方案效果很好,如可能,應作為首選方案。
由於圖形引擎在每一幀都進行繪製,在上面的繪製中,我們也在所有影像發送新的影像緩衝。
常常會有應用不更新任何內容的幀。 這表示不進行任何渲染。 因此,在下一幀會再次發送相同的影像緩衝。
應用在2號幀未繪製任何內容,因此圖形引擎在3號幀再次重發2號幀緩衝。
典型的平行RGB顯示器的刷新率約為60 Hz。 此更新頻率必須由微控制器來維護。 此更新頻率意味著在再次開始發送前,有16 ms的時間可用來渲染新幀。 在某些情況下,渲染新幀的時間超過16 ms。 此時,圖形引擎只再次重發相同的幀(同之前一樣):
1號幀的渲染時間超過16 ms,因此重發之前渲染到1號影像緩衝的0號幀。 在3號幀發送2號影像緩衝中的新幀。 當有兩個影像緩衝可供使用時,渲染時間可能會非常長。 在有新幀可用之前,會一直重發上一幀。
一個影像緩衝
在某些系統中,只有一個影像緩衝的存儲空間。 如果使用平行RGB顯示器,則必須在每一幀發送1號影像緩衝。
由於圖形引擎不得不在向顯示器發送影像緩衝的同時將影像繪製到同一個影像緩衝,因此會產生問題。 如果不加注意就這樣做,會有一個極大的風險,即顯示器會顯示上一影像與新影像的混合影像。
一種解決方案是在傳輸完成前不進行繪製,只在傳輸再次開始前的時間間隙內繪製。 由於傳輸佔用了整個影像時間的很大一部分,因此可用於繪製影像的時間極少。 另一個缺點是如果在下一次傳輸開始時繪製未完成,則仍可能出現不完整的影像(撕裂)。
一種更有潛力的解決方案是監測影像緩衝已發送的量,然後將渲染次數限制在影像緩衝的合適範圍內。 雖然傳輸的進行,影像緩衝有越來越多的部分可供渲染演算法使用。
圖形引擎包含協助工程師確保繪製正確執行的演算法。
應用在每一幀更新並渲染影像緩衝:
如果幀沒有更新任何內容,則重發影像緩衝,不做任何更改。
如果渲染時間超過16 ms,當再次開始重發時,渲染尚未結束:
在這種情況下,圖形引擎必須確保正在發送的部分已完成渲染。 否則,顯示器將顯示未完成的影像緩衝。
在下一節中,我們將討論各個Widget的渲染時間。 這將有助於工程師開發出高性能的應用。