天天看點

Skia深入分析3——skia圖檔繪制的實作(2)

此篇講圖像采樣

一、采樣流程

在上一節裡的流程圖有寫到,圖像繪制的實際渲染發生在某個blitter的blitrect函數中,我們先看一個具體的blitrect實作。

其中shadespan用來将shader中x,y坐标處的值取n個到dst的buffer中。

對于圖像繪制時,它是 skbitmapprocshader,這裡是其實作:

流程如下:

1、存在 shaderproc,直接用

2、計算一次能處理的像素數count

3、mproc計算count個坐标,sproc根據坐标值去取色

注意到之前三個函數指針:

state.getshaderproc32

mproc = state.getmatrixproc

sproc = state.getshaderproc32

這三個函數指針在一開始建立blitter時設定:

skblitter::choose -> skshader::createcontext -> skbitmapprocshader::oncreatecontext -> skbitmapprocstate::chooseprocs

這是一個相當長的函數,它做的事情如下:

1、(優化步驟)在大于skpaint::klow_filterlevel的品質要求下,試圖做預縮放。

2、選擇matrix函數:choosematrixproc。

3、選擇sample函數:

(1)高品質:setbitmapfilterprocs

(2)klow_filterlevel或knone_filterlevel:采取flags計算的方法,根據x,y變化矩陣情況和采樣要求選擇函數

4、(優化步驟)在滿足條件時,選取shader函數,此函數替代matrix和sample函數

5、(優化步驟)platformprocs(),進一步選擇優化版本的sample函數

對于rgb565格式的目标,使用的是skshader的 shadespan16 方法。shadespan16的代碼邏輯類似,不再說明。

二、matrixproc和sampleproc

matrixproc的使命是生成坐标集。sampleproc則根據坐标集取像素,采樣合成

我們先倒過來看 sampleproc 看這個坐标集是怎麼使用的:

nofilter_dx系列:

nofilter_dxdy系列:

這兩個系列是直接取了x,y坐标處的圖像像素

filter_dx系列:

filter_dxdy系列:

将四個相鄰像素取出來之後,作filter處理

看暈了麼,其實總結一下是這樣:

nofilter_dx,第一個32位數表示y,其餘的32位數包含兩個x坐标。

nofilter_dxdy,用16位表示x,16位表示y。這種情況就是取的最近值,直接到x,y坐标處取值就可以了。

filter_dxdy系列,每個32位數分别表示x和y坐标(14:4:14),交錯排列,中間的內插補點部分是相差的小數擴大16倍而得的近似整數。

filter_dx系列,第一個數為y坐标用14:4:14的方式存儲,後面的數為x坐标,也用14:4:14的方式存儲,前後為對應坐标,中間為放大16倍的距離,這個情況是一行之内y坐标相同(隻做縮放或小數平移的情況),一樣是作雙線性插值。

Skia深入分析3——skia圖檔繪制的實作(2)

下面我們來看matrixproc的實作,

先跟進 choosematrixproc的代碼:

有些函數是找符号找不到的,我們注意到skbitmapprocstate.cpp 中包含了多次 skbitmapprocstate_matrix.h 頭檔案:

頭檔案代碼如下:

然後我們就清楚了,這些函數名是用宏組合出來的。(神一般的代碼。。。。。)

怎麼算坐标的不詳述了,主要按原理去推就可以了,坐标計算有三種模式:clamp(越界時限制在邊界)、repeat(越界時從開頭取起)、mirror(越界時取樣方向倒轉去取)。

sampleproc函數也是類似的方法組合出來的,不詳述。

三、進階插值算法

雙線性插值雖然在一般情況下夠用了,但在放大圖檔時,效果還是不夠好。需要更好的效果,可以用進階插值算法,代價是性能的大幅消耗。

進階插值算法目前在android的java代碼處是走不進去的,不知道chromium是否用到。

幾個要點:

1、在 setbitmapfilterprocs 時判斷進階插值是否支援,若支援,設定 shaderproc 為 highqualityfilter32/highqualityfilter16(也就是獨立計算坐标和采樣像素)

2、highqualityfilter先通過變換矩陣計算原始點。

3、highqualityfilter根據 skbitmapfilter  的采樣視窗,将這個視窗中的所有點按其與原始點矩離,查詢對應權重值,然後相加,得到最終像素點。

4、skbitmapfilter 采用查表法去給出權重值,預計算由子類完成。

5、目前skia庫用的是雙三次插值 mitchell 法。

sk_conf_declare(const char *, c_bitmapfilter, "bitmap.filter", "mitchell", "which scanline bitmap filter to use [mitchell, lanczos, hamming, gaussian, triangle, box]");

詳細代碼見 external/skia/src/core/skbitmapfilter.cpp,盡量這部分代碼幾乎無用武之地,但裡面的公式很值得借鑒,随便改改就能做成 glsl shader 用。

看完這段代碼,可以作不負責任的猜想:skia設計之初,隻考慮了近鄰插值和雙線性插值兩種情況,是以采用這種模闆方法,可以最小化代碼量。而且matrixproc和sampleproc可以後續分别作simd優化(intel的sse和arm的neon),以提高性能。

但是對于線性插值,兩步法(取值——采樣)在算法實作上本來就不是最優的,後面又不得不引入shader函數,應對一些場景做優化。高階插值無法在這個設計下實作,是以又像更新檔一樣打上去。

四、總結

看完這一部分代碼,有幾個感受。

第一:繪張圖檔看上去一件簡單的事,在渲染執行時,真心不容易,如果追求效果,還會有各種各樣的花樣。

第二:在性能有要求的場景下,用模闆真是災難:函數改寫時,遇到模闆,就不得不重新定義函數,并替換之,弄得代碼看上去一下子混亂不少。

第三:從圖像繪制這個角度上看,skia渲染性能雖然确實很好了,但遠沒有達到極限,仍然是有一定的優化空間的,如果這部分出現了性能問題,還是能做一定的優化的。關于skia性能的讨論将放到介紹skia系列的最後一章。

第四:opengl+glsl确實是輕松且高效多了,軟體渲染在複雜場景上性能很有限。

繼續閱讀