Skia深入分析9——延遲渲染和顯示列表

目錄(?)[+]

概念

Android的硬件加速,是先將繪製命令存儲起來,然後回放,作爲軟件繪製的引擎Skia中同樣有這樣的機制。在Android 4.4的版本中又加入了延遲渲染的Canvas,它相當於默認使用顯示列表的Canvas。 
先得到顯示列表,再進行渲染,便有機會基於繪製API的整體情況做優化調度。比如使用GPU加速,裁剪過度繪製等。從原理上看,很可能在這一層級做比較大的效率提升,不過,由於Android既定的渲染框架限制,儘管Google在這方面做的東西很多,生效場景很少,收益也很有限。

顯示列表——SkPicture

用法

SkPicture的用法如下:

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">const int w = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">720</span>;
const int h = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1280</span>;
SkPictureRecorder recoder;
SkCanvas* displayCanvas =recoder.beginRecording(w, h, <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NULL</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);//這裏得到一個專門用來記錄的SkCanvas
/*調用SkCanvas的API,但起的是記錄命令的作用*/
displayCanvas->drawRect(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
displayCanvas->drawSprite(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
displayCanvas->clipRect(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
displayCanvas->drawText(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
/*終止繪製API的調用,得到SkPicture*/
SkPicture* picture = recoder.endRecording();

SkBitmap dst;
dst.allocN32Pixels(w, h);
SkCanvas canvas(dst);
c.drawPicture(picture);//用 picture->draw(&canvas)也可以,但最好用前面一種方法
/*此時內容已經在 dst 上面了*/
/*也可以用GPU的方式創建Canvas渲染,怎麼用可參考上一篇,*/
picture->unref();//釋放 picture</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li></ul>

顯示列表的記錄

記錄方案

我們先看一下beginRecording函數:

<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">SkCanvas* SkPictureRecorder::beginRecording(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> width, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> height,
                                            SkBBHFactory* bbhFactory <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* = NULL */</span>,
                                            uint32_t recordFlags <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* = 0 */</span>) {
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>->reset();  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// terminate any prior recording(s)</span>
    fWidth = width;
    fHeight = height;

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> SkISize size = SkISize::Make(width, height);

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (<span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NULL</span> != bbhFactory) {
        SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height));
        SkASSERT(<span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">NULL</span> != tree);
        fPictureRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, tree<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">.get</span>()));
    } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
        fPictureRecord = SkNEW_ARGS(SkPictureRecord, (size, recordFlags));
    }

    fPictureRecord->beginRecording();
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>->getRecordingCanvas();
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>

如上述代碼所看到的,主要有兩種記錄方法,一種是SkBBoxHierarchyRecord,另一種是SkPictureRecord。 
SkBBoxHierarchyRecord:以 R-Tree 算法組織繪製任務,這種方式需要計算繪製區域的包圍盒,在記錄文本繪製命令時這個計算還是相當耗時的,採用R-Tree組織的好處是繪製指定區域時可以快速找出相關的繪製任務。 
SkPictureRecord:順序記錄方式,不做處理

任務記錄的一些注意點

  1. 圖片存儲時需要判斷該圖片是否是可變的(immutable),若不是可變的,需要複製圖片到緩存。
  2. SkPaint的Shader及各種特效只存儲指針/引用。需要應用自行保證不做更改。
  3. SkPath也是複製了整個SkPath及SkPathRef中所有點到緩存,注意到需要記錄和恢復的比較複雜的類,需要實現 writeToMemory 和 readFromMemory 兩個方法
  4. 使用SkWriter32寫入,在結束記錄後將其內容全部複製到 SkPicture

回放過程

SkCanvas::drawPictureGPUDeviceEXPERIMENTAL_drawPictureSkPicture::drawSkPicturePlayBack::drawyesno

鑑於GPU繪圖時還需要把之前存儲在緩存區的bitmap、path等上傳爲紋理或vbo,直接以紋理/vbo建立緩存機制效率更高,所以有EXPERIMENTAL_drawPicture一條分支,不過目前看來還是實驗階段。 
具體如何回放的看src/core/SkPicturePlayBack.cpp中SkPicturePlayBack::draw函數即可,不詳述。

延遲渲染——SkDeferredCanvas

Google的註釋很清楚地解釋了這個類的作用。這個類的用法和原始的SkCanvas基本一致。只是設置了延遲屬性後,需要在使用繪圖結果前flush一下。

<code class="hljs applescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">/** \<span class="hljs-type" style="box-sizing: border-box;">class</span> SkDeferredCanvas
    Subclass <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> SkCanvas <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> encapsulates an SkPicture <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">or</span> SkGPipe <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> deferred
    drawing. The main difference <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">between</span> this <span class="hljs-type" style="box-sizing: border-box;">class</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">and</span> SkPictureRecord (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span>
    canvas provided <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">by</span> SkPicture) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> this <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> a full drop-<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> replacement
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> SkCanvas, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">while</span> SkPictureRecord only supports draw operations.
    SkDeferredCanvas will transparently trigger <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> flushing <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> deferred
    draw operations when an attempt <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> made <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> access <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> pixel data.
*/</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

用法如下:

<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">SkSurface* surface =  SkSurface::NewRasterPMColor(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">720</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1280</span>);
SkDeferredCanvas* canvas = SkDeferredCanvas::Create(surface);
canvas->clear(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x0</span>);
canavs->setDeferredDrawing(true);
/*......*/
canvas->drawRect(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
canvas->drawText(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>);
/*......*/
canvas->flush();//需要這一步來保持繪製完成
canvas->unref();
surface->unref();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>

另外可以爲SkDeferredCanvas設置一個監聽器NotificationClient: 
class NotificationClient { 
public: 
virtual ~NotificationClient() {} 
virtual void prepareForDraw() {}//準備開始渲染時調用 
virtual void storageAllocatedForRecordingChanged( 
size_t /newAllocatedStorage/) {} 
virtual void flushedDrawCommands() {}//當前繪製任務清空時調用,可以是拿去編碼/顯示/後處理等 
virtual void skippedPendingDrawCommands() {}//當前繪製任務被取消掉時調用,可以用來清除額外的資源 
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章