版權聲明:本文爲博主原創文章,轉載請註明出處:http://blog.csdn.net/jxt1234and2010
概念
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:順序記錄方式,不做處理
任務記錄的一些注意點
- 圖片存儲時需要判斷該圖片是否是可變的(immutable),若不是可變的,需要複製圖片到緩存。
- SkPaint的Shader及各種特效只存儲指針/引用。需要應用自行保證不做更改。
- SkPath也是複製了整個SkPath及SkPathRef中所有點到緩存,注意到需要記錄和恢復的比較複雜的類,需要實現 writeToMemory 和 readFromMemory 兩個方法
- 使用SkWriter32寫入,在結束記錄後將其內容全部複製到 SkPicture
回放過程
鑑於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() {}//當前繪製任務被取消掉時調用,可以用來清除額外的資源
};