Android Graphics Pipeline :从Button到Framebuffer

原文

Android Graphics Pipeline: From Button to Framebuffer (Part 1)

Android Graphics Pipeline: From Button to Framebuffer (Part 2)

这一系列的博文中, 我们要弄清楚 Android Graphics Pipeline 的内部机制。 针对这一主题, Google 已经给出了一些相关分享及文档, 例如在 Google I/O 2012 中, Chet Haase 和 Romain Guy 做了题为 For Butter or Worse 的报告(如果还没看, 就去看看吧!),又如文档 Graphics architecture。 虽然这些资料能够从宏观上帮助我们了解一个简单的 View 是如何显示在屏幕上的, 但是它对于我们了解背后的源码帮助并不大。 本系列博文将会带你进入 Android Graphics Pipeline 的美妙世界。

前方高能预警, 博文中包含大量的源代码及时序图。 不过接下来的文章值得一看, 即使你对 Android Graphics Pipeline 丝毫不感兴趣, 你也可以从中学到很多有用的知识(或者你也至少去看看下面这些精美的图片)。 所以给自己泡杯咖啡,接着看吧。

引言

为了充分理解 View 是如何一步步显示在屏幕上, 我们将用一个简单的 Demo 来描述 Android Graphics Pipeline 的每个重要阶段: 由 Android Java API(SDK)开始, 然后是 native 层的 C++ 代码, 最后分析 OpenGL 的绘图操作。

这里写图片描述

Demo: 该 Demo 足以覆盖 Android 内部绘图系统的主要代码, 是一个相当不错的例子。

该 activity 由一个简单的 RelativeLayout, 一个含图标及标题的 ActionBar 以及一个读作 “Hello world!” 的 Button 组成!

这里写图片描述

该示例程序的 view hierarchy 实际上相当复杂。

在 Android 的 view hierarchy 中, RelativeLayout 由一个简单的颜色渐变的背景构成。 更复杂的是, ActionBar 是由背景(由渐变效果结合一个 bitmap 构成)、 One Button 的文本元素以及应用图标(也是一个 bitmap)组成的。 9-Patch 用作 Button 的背景, 文本 Hello Wolrd! 绘制在它的最上层。 屏幕顶部的 navigation bar 以及底部的 status bar 并非该 activity 的组成部分, 它们是由 SystemUI 进行渲染。

大图:流程概述

如果看过了 Google I/O 的 For Butter or Worse 的报告, 你肯定认识出下面这张幻灯片, 它完整的展示 Android Graphics Pipeline。

这里写图片描述

发布于 Google I/O 2012 的 Android Graphics Pipeline

Surface Flinger 负责创建 graphics buffer 并将其合成到主显示器上。虽然该过程对 Android 系统非常重要,但在此我们不作赘述。

相反的, 我们将重点关注以下这些组件, 因为它们在 view 显示到屏幕的过程中将起到重要作用:

这里写图片描述

在 Android Graphics Pipeline 中需要重点关注的部分(至少对本系列博文而言)

Display Lists

也许你已经知道, Android 采用 DisplayLists 的来渲染所有的 view。 对于不了解的人而言, Display Lists 就是一个绘图命令的序列集合,其中的绘图命令是用来渲染特定的 view 的。 DisplayLists 是 Android Graphics Pipeline 实现高性能的重要组成部分。

这里写图片描述

view hierarchy 中的每个 view 都有相应的 DisplayLists, 每个开发者都知道 onDraw() 方法, 而 DisplayLists 就是由 onDraw() 方法生成的。 只有经过评估并执行之后的 DisplayLists 才能将其 view hierarchy 显示在屏幕上。 如果某个单一的 view 失效(如用户输入,动画或转换), 受影响的 DisplayLists 将会在重建之后最终重绘。 这就可以避免 Android 在每一个层级上都去调用相当耗资源的 onDraw() 方法。

DisplayLists 也可以进行嵌套, 这意味着 DisplayLists 可以通过指令在其内部构建一个子 DisplayLists。 这样的设计使得我们可以通过 DisplayLists 来复制 view hierarchy。 毕竟, 即使在我们这个如此简单的 Demo 中也嵌套了多个 view。

这些混杂的命令可以直接映射为 OpenGL 命令, 如转换、 设置剪辑矩阵, 或者更复杂的命令, 如 DrawText 和 DrawPatch。 当然,这些复杂的命令需要有相应的 OpenGl 命令集所支持。

一个 button 的display list示例

Save 3
DrawPatch
Save 3
ClipRect 20.00, 4.00, 99.00, 44.00, 1
Translate 20.00, 12.00
DrawText 9, 18, 9, 0.00, 19.00, 0x17e898
Restore
RestoreToCount 0

在上面的示例中, 你可以清楚的看出 display list 为绘制一个简单的 button 进行了什么操作。 第一个操作是将当前的转换矩阵保存进栈, 以便今后恢复。 然后又它绘制了 9-Patch 的 button, 紧接着又是一个存储指令。 这个存储的操作十分必要, 因为在文本绘制时, 其剪辑矩形的建立过程只影响绘制文本所在的区域。 而移动端的 GPU 可以将矩形标记出来以便在后期优化相关的绘制调用。 接下来,将绘制点转换到文本区域, 进行文本的绘制。 最后, 会从栈中恢复起先的转换矩阵和状态, 同时也将重置剪辑矩形。

在本文的底部可以看到我们 Demo 的 DisplayLists 的完整日志。

深入代码

带着这些新知识,我们来深入分析相关的代码。

Root View

每一个 Android 的 activity 在 view hierarchy 的顶部都有一个隐式的 root view, 它仅含一个子 view。 而这才是由应用开发者定义的第一个真正的应用的 view。 root view 负责调度和执行多个操作,如绘制,使 views 失效等等。

类似的, 每一个 view 都有其父节点。 view hierarchy 中的第一个 view 以 root view 为其父节点。 然而, 虽然 View 可以作为所有可视元素和组件的基类,但它不能设定任何子节点。 因此,在标准布局中(如 RelativeLayout 等),可以由其派生类 ViewGroup 作为容器基类来支持多个子节点。

如果一个 view (部分)失效, view 将调用 root view 的 invalidateChildInParent() 方法。 root view 将跟踪所有的失效区域并在 choreographer 中调度一个新的遍历, 并于下一个 VSync 事件中执行。

这里写图片描述

ViewRoot:InvalidateChildInParent(…)

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    // Add the new dirty rect to the current one
    mDirty.union(dirty.left, dirty.top, 
                 dirty.right, dirty.bottom);
    // Already scheduled?
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
    return null;
}

创建 Display Lists

如前所述, 每个 view 负责生成自己的 Display Lists。 当 VSync 事件触发且 choreographer 在 root view 上调用 performTraversals 时, root view 将要求 HardwareRenderer 将绘制 view, 而 HardwareRenderer 又会反过来要求 view 生成其 Display Lists。

这里写图片描述

作为 Android 中一个较大的类,目前 View 大概由 20000 行代码来实现。 但这并不奇怪, 毕竟它是每个组件和应用的基础构件。 它需要处理键盘、 轨迹球以及触屏事件, 还有滚动、 滚动条、 测量以及布局等等。

这里写图片描述

被 HardwareRenderer 调用后, View.getDisplayList(…) 方法将创建一个内部 DisplayLists, 它将被用于 view 的生命周期剩余部分。 内部的 DisplayLists 被要求提供一个足以容纳该 view 的 canvas。 view 及其子 view 将会用 GLES20RecordingCanvas 进行绘制, 然后提交至 draw(…) 方法。该 canvas 比较特殊, 它不会执行绘制命令, 而是将这些命令保存在 DisplayLists 中。 这意味着组件和 view 可以使用普通的绘制 API, 而无需关注命令在 DisplayLists 中的具体绘制过程。

private DisplayList getDisplayList(DisplayList displayList, 
                                   boolean isLayer) {
    HardwareCanvas canvas = displayList.start(width, height);
    if (!isLayer && layerType != LAYER_TYPE_NONE) {
        // Layers don't get drawn via a display list
    } else {
        draw(canvas);
    }
    return displayList;
}

在 draw(…) 方法内部, view 会执行 onDraw() 代码, 将自身呈现于 canvas 上。 如果该 view 有子节点, 它还将调用它们的 draw(…) 方法。 这些子节点可以是任何东西, 可以是普通的 button、 另一个 layout 或者 view group。

这里写图片描述

绘制 Display Lists

在 Android 4.3 之前, UI 绘制的流程与 UI 元素被添加到 view hierarchy 的顺序相同,并将它们添加至 Display Lists。 这样设计对 GPU 而言非常糟糕, 因为在绘制不同的元素时 GPU 需要不停的切换状态。 例如, 现在我们要绘制两个 button, 那么 GPU 就需要先为第一个 button 绘制 9-Patch 和文本, 之后又对第二个 button 做同样的操作, 这将至少切换3次状态。

重新安排绘制流程

为了使得状态切换的次数最小化, Android 按照绘图操作的类型和状态属性重新安排绘制流程。 下面简单的用下面这个新的 Demo 来做说明:

这里写图片描述

由重叠元素组成的示例 activity, 用于模拟重新排序绘图操作时可能出现的情况。

如上图所示, 简单的按照 UI 元素的类型来重排并合并操作并不能满足我们需求。 因为无论是先绘制文本再绘制位图, 还是先绘制位图再绘制文本, 都无法得到原先的结果, 这显然是不能够接受的。

为了正确绘制示例中的 activity, 文本 A 和 B 必须首先绘制, 然后是位图 C, 最后才是文本 D。 前两个文本元素可以合并成一个操作, 但是文本 D 不行, 因为合并之后将导致它覆盖位图 C。

进一步减少绘制 Display Lists 所需的时间, 大部分操作可以在重排序后合并在一起。 合并操作在 DeferredDisplayList 中执行, 这个函数叫这个名字的原因是绘图操作的执行不会立即开始, 而是推迟到所有的操作都已经完成分析、 重新排序并且合并之后。

因为每一个 Display Lists 操作都负责自身的绘制, 因此,支持合并同类型不同操作的绘制操作必须能够在一个绘制调用中执行多个不同的操作。 并非所有的操作都能够支持合并,某些操作只能支持重排序。

这里写图片描述

OpenGLRenderer 是 Skia 2D drawing API 的一种实现, 但它并非通过 CPU 而是通过 GPU 来完成所有绘制的硬件加速。 在 Android Graphics Pipeline 中, 这是第一个由 C++ 实现的类。 该渲染器被设计为协同 GLES20Canvas 一起工作, 于 Android 3.0 引入。 而它也只能协同 Display Lists 才能正常工作。

合并多个操作成一个绘制调用, 每个操作都会通过调用 addDrawOp(DrawOp) 被添加进 DeferredDisplayList。 绘图操作要求通过调用 DrawOp.onDefer(…) 来提供 batchId(它可以表明该操作是否可以合并)和 mergeId(指定合并后的操作)。

batchId 可以是 9-Patch 的 OpBatch_Patch, 也可以是文本元素的 OpBatch_Text。 因此,它被定义成一个简单的枚举类型。 mergeId 由 DrawOp 自己决定, 它用于判断两个操作相同的 DrawOp 类型是否可以合并。 对于 9-Patch 而已, mergeId 是指向图片资源; 对于文本元素, 则是对应的文字颜色。 来自同一个资源集的资源文件可能会被合并到同一个 batch 中, 这将大大减少渲染时间。

一个操作的所有信息都被收入到一个简单的结构中:

struct DeferInfo {
    // Type of operation (TextView, Button, etc.)
    int batchId;

    // State of operation (Text size, font, color, etc.)
    mergeid_t mergeId;

    // Indicates if operation is mergable
    bool mergeable;
};

当一个操作的 batchId 和 mergeId 都确定之后, 如果它不能被合并就将被加至队列的尾部。 如果没有可用的 batch, 我们就会创建一个新的 batch。 过一般情况下,这些绘制操作都是可以合并的。 为了知道每一个最近合并的 batch 的去向,我们会通过一个简化的算法调用 MergeBatches 的实例 hashmap,用 batchId 构建键值对保存相应的 batch。对每一个 batch 使用 hashmap 能避免使用 mergeId 导致的冲突。

vector<DrawBatch> batches;
Hashmap<MergeId, DrawBatch*> mergingBatches[BatchTypeCount];

void DeferredDisplayList::addDrawOp(DrawOp op):
    DeferInfo info;
    /* DrawOp fills DeferInfo with its mergeId and batchId */
    op.onDefer(info);

    if(/* op is not mergeable */):
        /* Add Op to last added Batch with same batchId, if first
           op then create a new Batch */
        return; 

    DrawBatch batch = NULL;
    if(batches.isEmpty() == false):
        batch = mergingBatches[info.batchId].get(info.mergeId);
        if(batch != NULL && /* Op can merge with batch */):
            batch.add(op);
            mergingBatches[info.batchId].put(info.mergeId, batch);
            return;

        /* Op can not merge with batch due to different states,
           flags or bounds */
        int newBatchIndex = batches.size();
        for(overBatch in batches.reverse()):
            if (overBatch == batch):
                /* No intersection as we found our own batch */
                break;

            if(overBatch.batchId  == info.batchId):
                /* Save position of similar batches to insert 
                   after (reordering) */
                newBatchIndex == iterationIndex;

            if(overBatch.intersects(localBounds)):
                /* We can not merge due to intersection */
                batch = NULL
                break;

    if(batch == NULL):
        /* Create new Batch and add to mergingBatches */
        batch = new DrawBatch(...);
        mergingBatches[deferInfo.batchId].put(info.mergeId, batch);
        batches.insertAt(newBatchIndex, batch);
    batch.add(op);

如果当前操作能够与其他具有相同 mergeId 和 batchId 的操作合并,那么这个操作和下一个可以合并的操作都会被添加到现有的 batch 中。但如果它因为状态不一致、绘制标记或边界限制无法被合并,算法就需要将它插入到一个新的 batch 中。为了实现这样的需求,我们则需要获得 batch 队列中所有 batch 对应的 postion。理想情况下,它能在当前绘制操作中找到一个和它状态相同的 batch。不过需要注意的是,在这个绘制操作中为它找到合适的位置的过程中,也必须保证它和其他 batch 没有交集。因此,batch 列表都以逆序寻找一个合适的位置,并确认对应的位置与其他元素没有交集。如果出现了交集,那么对应操作则不能被合并,并需要在这个位置创建一个新的 DrawBatch,并将其插入 Mergebatchedhaspmap。新的 batch 会被添加到 batch 队列的相应位置中。无论发生什么,改操作都会被加入到当前的 batch 中,区别在于:是在新的 batch 还是已存在的 batch 中。

具体的实现会比我们的简化讲解更复杂。但其中优化的方法值得我们学习:算法通过移除堵塞的绘制操作尽可能地避免重绘,同时通过对为合并的操作进行重排序,从而避免 GPU 状态改变带来的开销。

绘制(Deferred) Display Lists

在重排序和合并后,新的 deferred display list 终于可以被绘制到屏幕上了。

这里写图片描述

在 OpenGLRenderers::drawDisplayList(…) 方法里,deferred display list 其实就是一个填满了操作的新建普通显示列表,填充完成后延迟显示页面将绘制它自身。

OpenGLRenderer: drawDisplayList(…)

status_t OpenGLRenderer::drawDisplayList(
               DisplayList* displayList, Rect& dirty,
               int32_t replayFlags) {
    // All the usual checks and setup operations 
    // (quickReject, setupDraw, etc.)
    // will be performed by the display list itself
    if (displayList && displayList->isRenderable()) {
        DeferredDisplayList deferredList(*(mSnapshot->clipRect));
        DeferStateStruct deferStruct(
            deferredList, *this, replayFlags);
        displayList->defer(deferStruct, 0);
        return deferredList.flush(*this, dirty);
    }
    return DrawGlInfo::kStatusDone;
}

multiDraw(…) 方法会在列表中的第一个操作中被调用,而其他的操作都被视作参数。被调用的操作负责立刻绘制所有被提供的操作,并调用 OpenGLRenderer 执行绘制其自身的操作。

Display List 操作

每一个绘制操作都会在拥有对应显示操作列表的 Canvas 里被执行,所有显示操作列表必须实现重载了绘制操作的 replay() 方法。这些绘制操作调用 OpenGLRenderer 去绘制他们,当我们创建一个操作时需要提供一个 renderer 的引用。除此以外,我们还需要实现 onDefer() 方法,并返回操作的 drawId 和 mergeId。为合并的 batch 会设置相应的绘制 id 为 kOpBatch_None。可合并的操作必须实现用于立刻绘制所有已合并的操作的 multiDraw() 方法。

例如, 绘制 9-Patch 的操作(即 DrawPatchOp)就包含了以下的 multiDraw(…) 实现:

DrawPatchOp::multiDraw(…)

virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
        const Vector<OpStatePair>& ops, const Rect& bounds) {

    // Merge all 9-Patche vertices and texture coordinates 
    // into one big vector
    Vector<TextureVertex> vertices;
    for (unsigned int i = 0; i < ops.size(); i++) {
        DrawPatchOp* patchOp = (DrawPatchOp*) ops[i].op;
        const Patch* opMesh = patchOp->getMesh(renderer);
        TextureVertex* opVertices = opMesh->vertices;
        for (uint32_t j = 0; j < opMesh->verticesCount; 
             j++, opVertices++) {
            vertices.add(TextureVertex(opVertices->position[0], 
                                       opVertices->position[1],
                                       opVertices->texture[0], 
                                       opVertices->texture[1]));
        }
    }

    // Tell the renderer to draw multipe textured polygons
    return renderer.drawPatches(mBitmap, getAtlasEntry(),
                        &vertices[0], getPaint(renderer));
}

9-Patch 图片的 batchId 总是常量 kOpBatch_Patch,而 mergeId 则是指向图片的指针,因此,所有使用了相同图片的 9-Patch 对象都能够被合并为一个。此外,这种特性对我们使用资源文件里的图片非常有帮助,因为现在 Android 框架层所有经常被使用的 9-Patch 图片都可以根据其相同的纹理合并到同一个地方存储、使用。

纹理集

Android 的起始进程 Zygote 总会预加载一些与所有进程共享的资源文件,这些资源文件夹包含了频繁被使用的那些 9-Batch 图片和 Android 控件使用的图片。但在 Android 4.4 之前,每一个进程在 GPU 内存中都拥有这些资源文件的独立拷贝。从 Android 4.4 开始,这些频繁被使用的资源文件则被打包到一个纹理贴图集,随后传输到 GPU 内存中,并共享于所有进程之中。在这些操作完成之后才有可能对标准 Android 框架中的 9-Patch 和 Drawable资源文件进行合并。

这里写图片描述

系统产生的纹理贴图集是为了减少切换纹理带来的 GPU 负荷。

刚刚那张图展示了 Nexus 7 在 Android 4.4 系统下生成的纹理贴图集,图集中包含了所有频繁被使用的 Android 框架层图像资源,如果你看得仔细,你会发现 9-Patch 文件没有突出布局和间距区域的边界,而原始的资源文件在系统启动之初则进行了解析,不过它们之后也不会被用于绘制,所以我们也不用在意。

在 Android 进行系统更新后,系统第一次进行引导时(或者每一次进行引导时), AssetAtlasService 都会重新生成纹理贴图集,并在之后的每一次重新启动过程中再次使用它,直到 Android 更新的内容被应用于系统中。

为了生成纹理贴图集,service 组件会强行搜查各种图集配置,想尽千方百计找到那个能穿上水晶鞋的贴图集配置,那么什么样的贴图集配置是最好的呢?答案是:纹理资源最丰富,且纹理尺寸最小。原因在于:我们所获得的配置信息,会被写入到 /data/system/framework_atlas.config 中,此外,无论元素是否允许旋转,是否添加了间距,配置信息中都包含了我们选择的算法和尺寸大小。完成上述操作后,配置信息就会在之后的每一次重新启动过程中被应用,生成纹理贴图集。之后,系统会分配一个 RGBA8888 的图形缓存区,通过使用 Skiabitmap 将 资源纹理贴图集和所有资源文件绘制到这个缓存区中。经过上述繁复的操作后,资源纹理贴图集将在 AssetAtlasService 组件整个生命周期中有效,只有在系统自身被关闭时才会被释放。

为了真正把所有资源文件打包到一个图集中,AssetAtlasService 会从打包空白纹理开始。在将第一个资源文件放入之后,剩下的空间将会被切分成两个矩形区域。然后利用我们在配置信息中选择的算法,可以将这两个区域分别作为水平方向和垂直方向的视图处理区域。而空白纹理之后的纹理资源文件将会被添加到足以存放它的第一个区域中。而这个区域会再次被切分成两个区域,把下一个资源文件添加到相应区域后再次切分,循环往复。可能有人会问了,这样难道不会因为迭代操作使得时间开销很大吗?不用担心,AssetAtlasService 同时使用多个线程并行操作,使得时间开销被大大减少。

当一个新的 App 被创建,它的硬件渲染器需要 AssetAtlasService 为它提供相应的纹理贴图,并且当渲染器每一次需要绘制 bitmap 或者 9-Patch 图片,它都会先检查它的贴图集。

字体缓存与渲染

为了合并包含文字的 View 的绘制操作,一个简单的方法就是缓存字体。但与纹理贴图集的操作方法相反,字体集是每一个 App 或字体独有的。但由于字体的颜色会被应用到 shader 中,因此 Android 不会将文字颜色添加到字体集中。

这里写图片描述

左侧: 字体渲染器生成的字体集。 右侧: CPU 生成的集合几何形状, 用于呈现字符。

就算你只是瞥一眼字体集,你都会注意到只有一部分字符被绘制,如果你看得认真一些,你会注意到只有被使用的字符才会被绘制出来(没有重复的字符)!如果你看到这里在想 Android 支持多少种语言,抑或是支持多少种字符,那只缓存被使用的字符确实是最优解。又因为 Actionbar 和 Button 使用着相同的字体,那么它们俩使用的所有字符都能被合并到一种纹理中。

为了将相应的文字绘制到视图中,渲染器需要在一块拥有边界的纹理区域中生成一个几何体。几何体由 CPU 生成后通过 OpenGL 的 glDrawElements() 命令绘制,如果设备支持 OpenGL ES 3.0,字体渲染器会异步更新字体的纹理缓存,并将其上传到框架的入口,即 GPU 仍接近处于闲置状态时,进行这样的操作能够为每一个框架的加载节省下宝贵的时间。为了实现异步上传,纹理缓存会被实现为 OpenGL 的像素缓存对象。

OpenGL

在博文的前言阶段我曾许下承诺:我会结合部分原生的 OpenGL 绘制命令讲解博文中的知识。那么现在,就是我兑现诺言的时刻了。从此刻开始,我会用 OpenGL 的绘制 log 讲解之前的简单示例 Activity(只有一个 Button 的那个例子!)

glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glGenBuffers(n = 1, buffers = [3])
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)
glBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])
glDisable(cap = GL_BLEND)
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 33.0, 0.0, 1.0])
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)
eglSwapBuffers()

完整的 OpenGL 绘制调用 Log 可在博文的最后看到。

总结

我们已经看到了 Android 是怎样将它的 view hierarchy 转换成 Display List 中的一系列的渲染命令, 重新排序并且合并这些命令,并且最终如何执行这些命令。

回到我们的那个只有一个 button 的示例 activity, 整个 view 的渲染过程分为 5 步:

这里写图片描述

  1. 布局绘制背景图, 也就是线性布局;
  2. ActionBar 及 Button 的背景 9-Patch 被绘制。 这两个操作合并为一个 batch, 因为它们的 9-Patch 均位于相同的纹理区域;
  3. 为 ActionBar 绘制线型布局;
  4. 同时绘制 Button 以及 ActionBar 的文本。 因为这两个 view 使用相同的字体, 其字体渲染可以使用相同的字体纹理,因此合并这两个操作;
  5. 绘制应用程序的图标。

这就是你从中所掌握的,我们从 view hierarchy 追踪至最后的 OpenGL 命令的整个路径, 整个系列到此结束。

下载

本文中所参照的学士论文可供下载

附录(完整的Log)

Display List

Start display list (0x5ea4f008, PhoneWindow.DecorView, render=1)
  Save 3
  ClipRect 0.00, 0.00, 720.00, 1184.00
  SetupShader, shader 0x5ea5af08
  Draw Rect    0.00    0.00  720.00 1184.00
  ResetShader
  Draw Display List 0x5ea64d30, flags 0x244053
  Start display list (0x5ea64d30, ActionBarOverlayLayout, render=1)
    Save 3
    ClipRect 0.00, 0.00, 720.00, 1184.00
    Draw Display List 0x5ea5ad78, flags 0x24053
    Start display list (0x5ea5ad78, FrameLayout, render=1)
      Save 3
      Translate (left, top) 0, 146
      ClipRect 0.00, 0.00, 720.00, 1038.00
      Draw Display List 0x5ea59bf8, flags 0x224053
      Start display list (0x5ea59bf8, RelativeLayout, render=1)
        Save 3
        ClipRect 0.00, 0.00, 720.00, 1038.00
        Save flags 3
        ClipRect   32.00   32.00  688.00 1006.00
        Draw Display List 0x5cfee368, flags 0x224073
        Start display list (0x5cfee368, Button, render=1)
          Save 3
          Translate (left, top) 32, 32
          ClipRect 0.00, 0.00, 243.00, 96.00
          Draw patch    0.00    0.00  243.00   96.00
          Save flags 3
          ClipRect   24.00    0.00  219.00   80.00
          Translate by 24.000000 23.000000
          Draw Text of count 12, bytes 24
          Restore to count 1
        Done (0x5cfee368, Button)
        Restore to count 1
      Done (0x5ea59bf8, RelativeLayout)
    Done (0x5ea5ad78, FrameLayout)
    Draw Display List 0x5ea64ac8, flags 0x24053
    Start display list (0x5ea64ac8, ActionBarContainer, render=1)
      Save 3
      Translate (left, top) 0, 50
      ClipRect 0.00, 0.00, 720.00, 96.00
      Draw patch    0.00    0.00  720.00   96.00
      Draw Display List 0x5ea64910, flags 0x224053
      Start display list (0x5ea64910, ActionBarView, render=1)
        Save 3
        ClipRect 0.00, 0.00, 720.00, 96.00
        Draw Display List 0x5ea63790, flags 0x224053
        Start display list (0x5ea63790, LinearLayout, render=1)
          Save 3
          Translate (left, top) 17, 0
          ClipRect 0.00, 0.00, 265.00, 96.00
          Draw Display List 0x5ea5fe80, flags 0x224053
          Start display list (0x5ea5fe80, 
                              ActionBarView.HomeView, render=1)
            Save 3
            ClipRect 0.00, 0.00, 80.00, 96.00
            Draw Display List 0x5ea5ed00, flags 0x224053
            Start display list (0x5ea5ed00, ImageView, render=1)
              Save 3
              Translate (left, top) 8, 16
              ClipRect 0.00, 0.00, 64.00, 64.00
              Save flags 3
              ConcatMatrix 
                [0.67 0.00 0.00] [0.00 0.67 0.00] [0.00 0.00 1.00]
              Draw bitmap 0x5d33ae70 at 0.000000 0.000000
              Restore to count 1
            Done (0x5ea5ed00, ImageView)
          Done (0x5ea5fe80, ActionBarView.HomeView)
          Draw Display List 0x5ea63618, flags 0x224053
          Start display list (0x5ea63618, LinearLayout, render=1)
            Save 3
            Translate (left, top) 80, 23
            ClipRect 0.00, 0.00, 185.00, 49.00
            Save flags 3
            ClipRect    0.00    0.00  169.00   49.00
            Draw Display List 0x5ea634a0, flags 0x224073
            Start display list (0x5ea634a0, TextView, render=1)
              Save 3
              ClipRect 0.00, 0.00, 169.00, 49.00
              Save flags 3
              ClipRect    0.00    0.00  169.00   49.00
              Draw Text of count 9, bytes 18
              Restore to count 1
            Done (0x5ea634a0, TextView)
            Restore to count 1
          Done (0x5ea63618, LinearLayout)
        Done (0x5ea63790, LinearLayout)
      Done (0x5ea64910, ActionBarView)
    Done (0x5ea64ac8, ActionBarContainer)
    Draw patch    0.00  146.00  720.00  178.00
  Done (0x5ea64d30, ActionBarOverlayLayout)
Done (0x5ea4f008, PhoneWindow.DecorView)

OpenGL

eglCreateContext(version = 1, context = 0)
eglMakeCurrent(context = 0)
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGetString(name = GL_VERSION) = OpenGL ES 2.0 14.01003
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGenBuffers(n = 1, buffers = [1])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)
glBufferData(target = GL_ARRAY_BUFFER, size = 64, data = [64 bytes], 
             usage = GL_STATIC_DRAW)
glDisable(cap = GL_SCISSOR_TEST)
glActiveTexture(texture = GL_TEXTURE0)
glGenBuffers(n = 1, buffers = [2])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferData(target = GL_ARRAY_BUFFER, size = 131072, data = 0x0, 
             usage = GL_DYNAMIC_DRAW)
glGetIntegerv(pname = GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
              params = [16])
glGetIntegerv(pname = GL_MAX_TEXTURE_SIZE, params = [2048])
glGenTextures(n = 1, textures = [1])
glBindTexture(target = GL_TEXTURE_2D, texture = 1)
glEGLImageTargetTexture2DOES(target = GL_TEXTURE_2D, 
                             image = 2138532008)
glGetError(void) = (GLenum) GL_NO_ERROR
glDisable(cap = GL_DITHER)
glClearColor(red = 0,000000, green = 0,000000, blue = 0,000000, 
             alpha = 0,000000)
glEnableVertexAttribArray(index = 0)
glDisable(cap = GL_BLEND)
glGenTextures(n = 1, textures = [2])
glBindTexture(target = GL_TEXTURE_2D, texture = 2)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)
glTexImage2D(target = GL_TEXTURE_2D, level = 0, 
             internalformat = GL_ALPHA, width = 1024, height = 512, 
             border = 0, format = GL_ALPHA, type = GL_UNSIGNED_BYTE, 
             pixels = [])
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, 
                param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glViewport(x = 0, y = 0, width = 800, height = 1205)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 1)
glTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 0, width = 1024, height = 80, format = GL_ALPHA, type = GL_UNSIGNED_BYTE, pixels = 0x697b7008)
glInsertEventMarkerEXT(length = 0, marker = Flush)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glBindTexture(target = GL_TEXTURE_2D, texture = 1)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9729)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9729)
glCreateShader(type = GL_VERTEX_SHADER) = (GLuint) 1
glShaderSource(shader = 1, count = 1, string = attribute vec4 position;
attribute vec2 texCoords;
uniform mat4 projection;
uniform mat4 transform;
varying vec2 outTexCoords;

void main(void) {
    outTexCoords = texCoords;
    gl_Position = projection * transform * position;
}

, length = [0])
glCompileShader(shader = 1)
glGetShaderiv(shader = 1, pname = GL_COMPILE_STATUS, params = [1])
glCreateShader(type = GL_FRAGMENT_SHADER) = (GLuint) 2
glShaderSource(shader = 2, count = 1, string = precision mediump float;

varying vec2 outTexCoords;
uniform sampler2D baseSampler;

void main(void) {
    gl_FragColor = texture2D(baseSampler, outTexCoords);
}

, length = [0])
glCompileShader(shader = 2)
glGetShaderiv(shader = 2, pname = GL_COMPILE_STATUS, params = [1])
glCreateProgram(void) = (GLuint) 3
glAttachShader(program = 3, shader = 1)
glAttachShader(program = 3, shader = 2)
glBindAttribLocation(program = 3, index = 0, name = position)
glBindAttribLocation(program = 3, index = 1, name = texCoords)
glGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTES, params = [2])
glGetProgramiv(program = 3, pname = GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, params = [10])
glGetActiveAttrib(program = 3, index = 0, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC4], name = position)
glGetActiveAttrib(program = 3, index = 1, bufsize = 10, length = [0], size = [1], type = [GL_FLOAT_VEC2], name = texCoords)
glGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORMS, params = [3])
glGetProgramiv(program = 3, pname = GL_ACTIVE_UNIFORM_MAX_LENGTH, params = [12])
glGetActiveUniform(program = 3, index = 0, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = projection)
glGetActiveUniform(program = 3, index = 1, bufsize = 12, length = [0], size = [1], type = [GL_FLOAT_MAT4], name = transform)
glGetActiveUniform(program = 3, index = 2, bufsize = 12, length = [0], size = [1], type = [GL_SAMPLER_2D], name = baseSampler)
glLinkProgram(program = 3)
glGetProgramiv(program = 3, pname = GL_LINK_STATUS, params = [1])
glGetUniformLocation(program = 3, name = transform) = (GLint) 2
glGetUniformLocation(program = 3, name = projection) = (GLint) 1
glUseProgram(program = 3)
glGetUniformLocation(program = 3, name = baseSampler) = (GLint) 0
glUniform1i(location = 0, x = 0)
glUniformMatrix4fv(location = 1, count = 1, transpose = false, value = [0.0025, 0.0, 0.0, 0.0, 0.0, -0.001659751, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -1.0, 1.0, -0.0, 1.0])
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [800.0, 0.0, 0.0, 0.0, 0.0, 1205.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
glEnableVertexAttribArray(index = 1)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7af4)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x681e7afc)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 4)
glDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 0, size = 576, data = [ 576 bytes ])
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 576, size = 192, data = [ 192 bytes ])
glEnable(cap = GL_BLEND)
glBlendFunc(sfactor = GL_SYNC_FLUSH_COMMANDS_BIT, dfactor = GL_ONE_MINUS_SRC_ALPHA)
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glGenBuffers(n = 1, buffers = [3])
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 3)
glBufferData(target = GL_ELEMENT_ARRAY_BUFFER, size = 24576, data = [ 24576 bytes ], usage = GL_STATIC_DRAW)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf18)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0xbefdcf20)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 48)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 72, type = GL_UNSIGNED_SHORT, indices = 0x0)
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 2)
glBufferSubData(target = GL_ARRAY_BUFFER, offset = 768, size = 576, data = [ 576 bytes ])
glDisable(cap = GL_BLEND)
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 33.0, 0.0, 1.0])
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x300)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x308)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 54, type = GL_UNSIGNED_SHORT, indices = 0x0)
glEnable(cap = GL_BLEND)
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
glBindTexture(target = GL_TEXTURE_2D, texture = 2)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd008)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x696bd010)
glVertexAttribPointerData(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)
glVertexAttribPointerData(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x??, minIndex = 0, maxIndex = 80)
glDrawElements(mode = GL_MAP_INVALIDATE_RANGE_BIT, count = 120, type = GL_UNSIGNED_SHORT, indices = 0x0)
glGenTextures(n = 1, textures = [3])
glBindTexture(target = GL_TEXTURE_2D, texture = 3)
glPixelStorei(pname = GL_UNPACK_ALIGNMENT, param = 4)
glTexImage2D(target = GL_TEXTURE_2D, level = 0, internalformat = GL_RGBA, width = 64, height = 64, border = 0, format = GL_RGBA, type = GL_UNSIGNED_BYTE, pixels = 0x420cd930)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MIN_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_MAG_FILTER, param = 9728)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_S, param = 33071)
glTexParameteri(target = GL_TEXTURE_2D, pname = GL_TEXTURE_WRAP_T, param = 33071)
glUniformMatrix4fv(location = 2, count = 1, transpose = false, value = [64.0, 0.0, 0.0, 0.0, 0.0, 64.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 16.0, 38.0, 0.0, 1.0])
glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 1)
glVertexAttribPointer(indx = 0, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x0)
glVertexAttribPointer(indx = 1, size = 2, type = GL_FLOAT, normalized = false, stride = 16, ptr = 0x8)
glBindBuffer(target = GL_ELEMENT_ARRAY_BUFFER, buffer = 0)
glDrawArrays(mode = GL_TRIANGLE_STRIP, first = 0, count = 4)
glGetError(void) = (GLenum) GL_NO_ERROR
eglSwapBuffers()
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值