您的位置:首页 > 其它

Skia深入分析9——延迟渲染和显示列表

2016-06-26 21:42 435 查看
版权声明:本文为博主原创文章,转载请注明出处: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

回放过程

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() {}//当前绘制任务被取消掉时调用,可以用来清除额外的资源 

};

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: