2012年10月9日 星期二

skia 性能優化


Skia是一個 Android 中的一個 open source 向量圖型庫,

Android 2.2 skia 幾乎無所不在, 提供了 draw text, draw rectangle, draw bitmap 等等的功能. 許多 apk UI, 其具體實現就是用skia, 例如, launcher. skia Android 的原生代碼中, 提供了 skia 以及 skiaGL (OpenGL加速的版本). skia 的版本中,  skia 的運算仰賴 CPU, 雖然有提供了 fixed point 以及 floating point 兩套版本, 但筆者實際上透過 #define 調整時, 編譯上會產生錯誤. 有部分的代碼需要修改調整.(是否有其他同好有調試過這部分的代碼?)




因為skia 的運算仰賴 CPU, 因此, 有了優化代碼的空間. drawRect 為例, 我們選擇一個 blitter 來優化:
代碼位置 : \external\skia\src\core\SkBlitter_RGB16.cpp
我們選擇的 blitter SRGB16_Blitter, 要優化的是 blitRect 函式, 先來看原生的 SkRGB16_Blitter::blitRect 函式:

Source3.cpp
01 void SkRGB16_Blitter::blitRect(int x, int y, int width, int height) {
02     SkASSERT(x + width <= fDevice.width() && y + height <= fDevice.height());
03     uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
04     unsigned    deviceRB = fDevice.rowBytes();
05     SkPMColor src32 = fSrcColor32;        
06     while (--height >= 0)
07         {
08         blend32_16_row(src32, device, width);
09         device = (uint16_t*)((char*)device + deviceRB);
10     }
11 }
  
第8行的Blend32_16_row 函式的代碼如下:
Source3.cpp
01 static inline void blend32_16_row(SkPMColor src, uint16_t dst[], int count) {
02     SkASSERT(count > 0);
03     uint32_t src_expand = pmcolor_to_expand16(src);
04     unsigned scale = SkAlpha255To256(0xFF - SkGetPackedA32(src)) >> 3;
05     do {
06         uint32_t dst_expand = SkExpand_rgb_16(*dst) * scale;
07         *dst = SkCompact_rgb_16((src_expand + dst_expand) >> 5);
08         dst += 1;
09     } while (--count != 0);
10 }

SkRGB16_Blitter::blitRect 函式對 rectangle 由上到下, 計算每一條 line memory address. 然後調用blend32_16_row 函式處理line上的每個pixel.
我們要優化的是blend32_16_row 這個函式. 由於筆者使用的 CPU ARM 9,
store, load 的資料寬度為 32 bits, 但是 blend32_16_row 存取 dst 的方式
是以 16 bits 為單位存取. 這裡優化的方式就是一次讀取 32bits, 也就是 2 element. 優化後得程式碼如下:

Source3.cpp
01 static inline void blend32_16_row_32(SkPMColor src,uint16_t dst[],int count) {
02     SkASSERT(count > 0);
03     uint32_t src_expand = pmcolor_to_expand16(src);
04     unsigned scale = SkAlpha255To256(0xFF - SkGetPackedA32(src)) >> 3;
05     uint32_t *dst32 = (uint32_t*)dst;
06     uint32_t  dstH, dstL;
07     uint32_t dst_expand, dst_expandH, dst_expandL;
08     do {       
09         dstH = *dst32;
10         dstL = dstH&0xffff; 
11         dstH >>= 16;
12         dst_expandH = SkExpand_rgb_16(dstH) * scale;
13         dst_expandL = SkExpand_rgb_16(dstL) * scale;
14         dstH = (uint32_t)SkCompact_rgb_16((src_expand + dst_expandH) >> 5);
15         dstL = (uint32_t)SkCompact_rgb_16((src_expand + dst_expandL) >> 5);
16         *dst32++ = (dstH << 16) | (dstL&0xffff);
17         count -= 2;
18     } while (count != 0);
19 }

dst讀取 32bits , unpacked 成兩個 element, 分別運算完後, 再進行 packed 32bits寫回 buffer.

另外 dst memory address 不一定都是 32bits alignment 因此針對這種狀況, 筆者簡單使用 loop unroll 的優化方式. 優化後代碼如下:
Source3.cpp
01 static inline void blend32_16_row_5(SkPMColor src,uint16_t dst[],int count){
02     SkASSERT(count > 0);
03     uint32_t src_expand = pmcolor_to_expand16(src);
04     unsigned scale = SkAlpha255To256(0xFF - SkGetPackedA32(src)) >> 3;
05     uint32_t *dst32 = (uint32_t*)dst;
06     uint16_t  *dstH = dst;
07     uint32_t dst_expand, dst_expandH, dst_expandL;
08     do {
09         dst_expandH = SkExpand_rgb_16(*dst++) * scale;
10         dst_expandL = SkExpand_rgb_16(*dst++) * scale;
11         *dstH++ = SkCompact_rgb_16((src_expand + dst_expandH)>>5);
12         *dstH++ = SkCompact_rgb_16((src_expand + dst_expandL)>>5);
13         count -= 2;
14     } while (count != 0);
15 }
條件是 rect width 必須是 2 的倍數.
若不是 2 的倍數, 也有優化方式, 但這裡筆者就用原生的做法.
修改後的SkRGB16_Blitter::blitRect函式如下:
Source3.cpp
01 void SkRGB16_Blitter::blitRect(int x, int y, int width, int height) {
02     SkASSERT(x + width <= fDevice.width() && y + height <= fDevice.height());
03     uint16_t* SK_RESTRICT device = fDevice.getAddr16(x, y);
04     unsigned    deviceRB = fDevice.rowBytes();
05     SkPMColor src32 = fSrcColor32;
06     if( ((width&0x1)==0)&&(((unsigned int)device&0x3)==0) )
07     { // even width & 32bits alignment.
08       while (--height >= 0)
09       {
10         blend32_16_row_32(src32, device, width);
11         device = (uint16_t*)((char*)device + deviceRB);
12       }
13     }
14     else if((width&0x1)==0)
15     {// even width.
16       while (--height >= 0) 
17       {
18         blend32_16_row_5(src32, device, width);
19         device = (uint16_t*)((char*)device + deviceRB);
20       }
21     }
22     else  
23     { 
24       while (--height >= 0)
25       {
26         blend32_16_row(src32, device, width);
27         device = (uint16_t*)((char*)device + deviceRB);
28       }
29     }
30 }

0xBenchmark Draw Rect 選項對優化前後的 image 分別 benchmark 的結果如下.



優化前 benchmark 結果




優化後 benchmark 結果





沒有留言:

張貼留言