通常,為用戶界面應用動畫只不過是創建並配置正確的動畫和故事板對象。但在其他情況下,特別是同時發生多個動畫時,可能需要更加關註性能。特定的效果更可能導致這些問題——例如,那些涉及視頻、大點陣圖以及多層透明等的效果通常需要占用更多CPU開銷。如果不謹慎實現這類效果,運行它們使可能造成明顯抖動,或者會從其他 ...
通常,為用戶界面應用動畫只不過是創建並配置正確的動畫和故事板對象。但在其他情況下,特別是同時發生多個動畫時,可能需要更加關註性能。特定的效果更可能導致這些問題——例如,那些涉及視頻、大點陣圖以及多層透明等的效果通常需要占用更多CPU開銷。如果不謹慎實現這類效果,運行它們使可能造成明顯抖動,或者會從其他同時運行的應用程式搶占CPU時間。
幸運的是,WPF提供了幾個可提供幫助的技巧。接下來的幾節將學習降低最大幀率以及緩存電腦顯卡中的點陣圖,這兩種技術可以減輕CPU的負擔。
一、期望的幀率
正如前面所學習的,WPF試圖保持以60幀/秒的速度運動動畫。這樣可確保從開始到結束得到平滑流暢的動畫。當然,WPF可能達不到這個目標。如果同時運行多個複雜的動畫,並且CPU或顯卡不能承受的話,整個幀率可能會下降(最好的情形),甚至可能會跳躍以進行補償(最壞的情形)。
儘管很少提高幀率,但可能會選擇降低幀率,這可能是因為以下兩個原因之一:
- 動畫使用更低的幀率看起來也很好,所以不希望浪費額外的CPU周期。
- 應用程式運行在性能較差的CPU或顯卡上,並知道使用高的幀率時整個動畫的渲染效果還不如使用更低的幀率的渲染效果好。
調整幀率很容易。只需要為包含動畫的故事板使用Timeline.DesiredFrameRate附加屬性。下麵的示例將幀率減半:
<Storyboard Timeline.DesiredFrameRate="30">
下圖顯示了一個簡單的測試程式,該程式為一個小球應用動畫,使其在Canvas控制項上沿一條曲線運動。
這個應用程式開始在Canvas上繪製Ellipse對象。Canvas.ClipToBounds屬性被設置為true,所以圓的邊緣不會超出Canvas控制項的邊緣而進入視窗的其他部分。
<Canvas ClipToBounds="True"> <Ellipse Name="ellipse" Fill="Red" Width="10" Height="10"></Ellipse> </Canvas>
為在Canvas控制項上移動圓,需要同時進行兩個動畫——一個動畫用於更新Canva.Left屬性(從左向右移動圓),另一個動畫用於改變Canvas.Top屬性(使圓上升,然後下降)。Canvas.Top動畫是可反轉的——一旦圓達到最高點,就會下降。Canvas.Left動畫不是可反轉的,但持續時間是Canvas.Top動畫的兩倍,從而使得這兩個動畫可以同時移動圓。最後的技巧是為Canvas.Top動畫使用DeceleartionRatio屬性。這樣,當圓達到最高點是上升的速度會更慢,這會創建更逼真的效果。
下麵是動畫的完整標記:
<Window.Resources> <BeginStoryboard x:Key="beginStoryboard"> <Storyboard Timeline.DesiredFrameRate="{Binding ElementName=txtFrameRate,Path=Text}"> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Left)" From="0" To="300" Duration="0:0:5"> </DoubleAnimation> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Top)" From="300" To="0" AutoReverse="True" Duration="0:0:2.5" DecelerationRatio="1"> </DoubleAnimation> </Storyboard> </BeginStoryboard> </Window.Resources>
這個示例的真正目的是嘗試不同的幀率。為查看某個特定幀率的效果,只需要在文本框中輸入合適的數值,然後單擊Repeat按鈕即可。然後動畫就會使用新的幀率(通過數據綁定表達式獲取新的幀率)觸發,從而可以觀察動畫的效果。在更低的幀率下,橢圓不會均勻移動——而會在Cavans控制項中的跳躍。
也可使用代碼調整Timeline.DesiredFrame屬性。例如,可能希望讀取靜態屬性RenderCapability.Tier以確定顯卡支持的渲染級別。
二、點陣圖緩存
點陣圖緩存通知WPF獲取內容的當前點陣圖圖像,並將其複製到顯卡的記憶體中。這時,顯卡可以控制點陣圖的操作和顯示的刷新。這個處理過程比讓WPF完成所有工作要快很多,並且和顯卡不斷通信。
如果運用得當,點陣圖緩存可以改善應用程式的繪圖性能。但如果運用不當,就會浪費顯存並且實際上會降低性能。所以,在使用點陣圖緩存之前,需要確保真正合適。下麵列出一些指導原則:
- 如果正在繪製的內容需要頻繁地重新繪製,使用點陣圖緩存可能是合理的。因為每次後續的重新繪製將更快。一個例子是當其他一些具有動畫的對象浮動在形狀錶面上時,使用BitmapCacheBrush畫刷繪製形狀的錶面。儘管形狀沒有變化,但是形狀的不同部分被遮擋住或顯露出來,從而需要重新繪製。
- 如果元素的內容經常變化,使用點陣圖緩存可能不合理。因為可視化內容每次改變時,WPF需要重新渲染點陣圖將其發送到顯卡緩存,而這需要耗費時間。該規則有些晦澀,因為某些改變不會導致緩存無效。安全操作的例子包括使用變換旋轉以及重新縮放元素、剪裁元素、改變元素的透明度以及應用效果。另一方面,改變元素的內容、佈局以及各式將強制重新渲染點陣圖。
- 儘量少緩存內容。點陣圖越大,WPF存儲緩存副本所需的時間越長,需要的顯存越多。一旦耗盡顯存,WPF將被迫使用更慢的軟體渲染。
為更好地理解點陣圖緩存,使用一個簡單示例是有幫助的,下圖例舉一個示例,一個動畫推動一個簡單的圖像——正方形——在Canvas面板上移動,Canvas面板包含一條具有複雜集合圖形的路徑。但正方形在Canvas面板錶面上移動時,強制WPF重新計算路徑並填充丟失的部分。這會帶來極大的CPU負擔,並且動畫甚至可能開始變得斷斷續續。
可採用幾種方法解決該問題。一種選擇是使用一幅點陣圖替換背景,WPF能夠更高效地管理點陣圖。更靈活的選擇是使用點陣圖緩存,這種方法可繼續將存活的、可交互的元素作為背景。
為啟用點陣圖緩存功能,將相應元素的CacheMode屬性設置為BitmapCache。每個元素都提供了CacheMode屬性,這意味著可以精確選擇為哪個元素使用這一特征。
<Path CacheMode="BitmapCache" ...></Path>
通過這個簡單修改,可立即看到區別。首先,視窗顯示的事件要稍長一些。但動畫的運行將更平滑,並且CPU的負擔將顯著降低。可通過Windows任務管理器進行檢查——經常可以看到CPU的負擔從接近100%減少到20%一下。
通常,當啟用點陣圖緩存時,WPF採用元素當前尺寸的快照並將其點陣圖複製到顯卡中。如果之後使用ScaleTransform放大元素,這會變成一個問題。在這種情況下,將放大緩存的點陣圖,而不是實際的元素,當放大元素時這會導致模糊放大以及色塊。
例如,設想一個修訂過的示例。在這個示例中,第二個同步動畫擴展Path使其為原始尺寸的10倍,然後縮回原始尺寸。為確保具有良好的顯示質量,可使用5倍於Path原始尺寸的尺寸緩存其點陣圖:
<Path ...> <Path.CacheMode> <BitmapCache RenderAtScale="5"></BitmapCache> </Path.CacheMode> </Path>
這樣可解決像素化問題。雖然緩存的點陣圖仍比Path的最大動畫尺寸(最大尺寸達10倍於其原始尺寸)小,但顯卡能使點陣圖的尺寸加倍,從5倍到10倍,而不會有任何明顯的縮放問題。更重要的是,這可使應用避免過多地使用顯存。