Tagged: WebKit Toggle Comment Threads | 键盘快捷键

  • Roger 7:08 pm on April 16, 2014 固定链接 | 回复
    Tags: , , , , webgl, WebKit   

    How Rendering Work (in WebKit and Blink) 

    How Rendering Work (in WebKit and Blink)

    自从开始从事浏览器内核开发工作以来,已经写过不少跟渲染相关的文章。但是一直想写一篇像 How Browsers Work 类似,能够系统,完整地阐述浏览器的渲染引擎是如何工作的,它是如何对网页渲染性能进行优化的文章,却一直因为畏惧所需要花费的时间和精力,迟迟无法动笔。不管如何,现在终于鼓起勇气来写了,希望自己能够完成吧…

    文章包括的主要内容如下 —

    • 渲染基础 - DOM & RenderObject & RenderLayer
    • WebView,绘制与混合,多线程渲染
    • 硬件加速
    • 分块渲染
    • 图层混合加速
    • 网页游戏渲染 - Canvas & WebGL

    首先明确文中关于渲染的定义,浏览器内核引擎通常又被称为网页渲染引擎,但是这里的渲染实际上是一个泛指,广义的渲染,它包括了浏览器内核所有的主要工作 - 加载,解析,排版,绘制等等。而在本文里面的渲染,指的是跟绘制相关的部分,也就是浏览器是如何将排版后的结果最终显示在屏幕上的这一过程。如果读者希望先对浏览器内核引擎,特别是 WebKit 有一个大概的了解,How Browsers WorkHow WebKit WorkWebKit for Developers 可以提供不错的入门指引。

    其次,本文主要描述 WebKit 引擎的实现,不过因为 Blink 实际上从 WebKit 分支出来的时间并不长,两者在渲染整体架构上还是基本一致的,所以文中不会明确区分这两者。

    最后,希望这篇文章能够给从事浏览器内核开发,特别是渲染引擎开发的开发者一个能够快速入门的指引,并给前端开发者优化网页渲染性能提供足够的知识和帮助。

    因为文章后续还有可能会持续修订和补充,如果要查看最新的内容,请访问个人博客的原文:http://blog.csdn.net/rogeryi/article/details/23686609,如果有什么疏漏和错误,也欢迎读者来信指正(roger2yi@gmail.com)。

    渲染基础 - DOM & RenderObject & RenderLayer

    DOM,RenderObject,RenderLayer

    图片来自 GPU Accelerated Compositing in Chrome

    当浏览器通过网络或者本地文件系统加载一个 HTML 文件,并对它进行解析完毕后,内核就会生成它最重要的数据结构 - DOM 树。DOM 树上每一个节点都对应着网页里面的每一个元素,并且网页也可以通过 JavaScript 操作这棵 DOM 树,动态改变它的结构。但是 DOM 树本身并不能直接用于排版和渲染,内核还会生成另外一棵树 - Render 树,Render 树上的每一个节点 - RenderObject,跟 DOM 树上的节点几乎是一一对应的,当一个可见的 DOM 节点被添加到 DOM 树上时,内核就会为它生成对应的 RenderOject 添加到 Render 树上。

    Render Tree

    图片来自 How WebKit Work

    Render 树是浏览器排版引擎的主要作业对象,排版引擎根据 DOM 树和 CSS 样式表的样式定义,按照预定的排版规则确定了 Render 树最后的结构,包括其中每一个 RenderObject 的大小和位置,而一棵经过排版的 Render 树,则是浏览器渲染引擎的主要输入,读者可以认为,Render 树是衔接浏览器排版引擎和渲染引擎之间的桥梁,它是排版引擎的输出,渲染引擎的输入。

    Layer Tree

    图片来自 How WebKit Work

    不过浏览器渲染引擎并不是直接使用 Render 树进行绘制,为了方便处理 Positioning(定位),Clipping(裁剪),Overflow-scroll(页內滚动),CSS Transform/Opacity/Animation/Filter,Mask or Reflection,Z-indexing(Z排序)等,浏览器需要生成另外一棵树 – Layer 树。渲染引擎会为一些特定的 RenderObject 生成对应的 RenderLayer,而这些特定的 RenderObject 跟对应的 RenderLayer 就是直属的关系,相应的,它们的子节点如果没有对应的 RenderLayer,就从属于父节点的 RenderLayer。最终,每一个 RenderObject 都会直接或者间接地从属于一个 RenderLayer。

    RenderObject 生成 RenderLayer 的条件,来自 GPU Accelerated Compositing in Chrome

    • It’s the root object for the page
    • It has explicit CSS position properties (relative, absolute or a transform)
    • It is transparent
    • Has overflow, an alpha mask or reflection
    • Has a CSS filter
    • Corresponds to canvas element that has a 3D (WebGL) context or an accelerated 2D context
    • Corresponds to a video element

    浏览器渲染引擎遍历 Layer 树,访问每一个 RenderLayer,再遍历从属于这个 RenderLayer 的 RenderObject,将每一个 RenderObject 绘制出来。读者可以认为,Layer 树决定了网页绘制的层次顺序,而从属于 RenderLayer 的 RenderObject 决定了这个 Layer 的内容,所有的 RenderLayer 和 RenderObject 一起就决定了网页在屏幕上最终呈现出来的内容。

    软件渲染模式下,浏览器绘制 RenderLayer 和 RenderObject 的顺序,来自 GPU Accelerated Compositing in Chrome

     

    In the software path, the page is rendered by sequentially painting all the RenderLayers, from back to front. The RenderLayer hierarchy is traversed recursively starting from the root and the bulk of the work is done in RenderLayer::paintLayer() which performs the following basic steps (the list of steps is simplified here for clarity):

    1. Determines whether the layer intersects the damage rect for an early out.
    2. Recursively paints the layers below this one by calling paintLayer() for the layers in the negZOrderList.
    3. Asks RenderObjects associated with this RenderLayer to paint themselves.
    4. This is done by recursing down the RenderObject tree starting with the RenderObject which created the layer. Traversal stops whenever a RenderObject associated with a different RenderLayer is found.
    5. Recursively paints the layers above this one by calling paintLayer() for the layers in the posZOrderList.

    In this mode RenderObjects paint themselves into the destination bitmap by issuing draw calls into a single shared GraphicsContext (implemented in Chrome via Skia).

    WebView,绘制与混合,多线程渲染

    WebView

    图片来自 [UC 浏览器 9.7 Android版],中间是一个 WebView,上方是标题栏和工具栏

    浏览器本身并不能直接改变屏幕的像素输出,它需要通过系统本身的 GUI Toolkit。所以,一般来说浏览器会将一个要显示的网页包装成一个 UI 组件,通常叫做 WebView,然后通过将 WebView 放置于应用的 UI 界面上,从而将网页显示在屏幕上。

    一些 GUI Toolkit,比如 Android,默认的情况下 UI 组件没有自己独立的位图缓存,构成 UI 界面的所有 UI 组件都直接绘制在当前的窗口缓存上,所以 WebView 每次绘制,就相当于将它在可见区域内的 RenderLayer/RenderObject 逐个绘制到窗口缓存上。上述的渲染方式有一个很严重的问题,用户拖动网页或者触发一个惯性滚动时,网页滑动的渲染性能会十分糟糕。这是因为即使网页只移动一个像素,整个 WebView 都需要重新绘制,而要绘制一个 WebView 大小的区域的 RenderLayer/RenderObject,耗时通常都比较长,对于一些复杂的桌面版网页,在移动设备上绘制一次的耗时有可能需要上百毫秒,而要达到60帧/每秒的流畅度,每一帧绘制的时间就不能超过16.7毫秒,所以在这种渲染模式下,要获得流畅的网页滑屏效果,显然是不可能的,而网页滑屏的流畅程度,又是用户对浏览器渲染性能的最直观和最重要的感受。

    要提升网页滑屏的性能,一个简单的做法就是让 WebView 本身持有一块独立的缓存,而 WebView 的绘制就分成了两步 1) 根据需要更新内部缓存,将网页内容绘制到内部缓存里面 2) 将内部缓存拷贝到窗口缓存上。第一步我们通常称为绘制(Paint)或者光栅化(Rasterization),它将一些绘图指令转换成真正的像素颜色值,而第二步我们一般称为混合(Composite),它负责缓存的拷贝,同时还可能包括位移(Translation),缩放(Scale),旋转(Rotation),Alpha 混合等操作。咋一看,渲染变得比原来更复杂,还多了一步操作,但实际上,混合的耗时通常远远小于网页内容绘制的耗时,后者即使在移动设备上一般也就在几个毫秒以内,而大部分时候,在第一步里面,我们只需要绘制一块很小的区域而不需要绘制一个完整 WebView 大小的区域,这样就有效地减少了绘制这一步的开销。以网页滚动为例子,每次滚动实际上只需要绘制新进入 WebView 可见区域的部分,如果向上滚动了10个像素,我们需要绘制的区域大小就是10 x Width of WebView,比起原来需要绘制整个 WebView 大小区域的网页内容当然要快的多了。

    进一步来说,浏览器还可以使用多线程的渲染架构,将网页内容绘制到缓存的操作放到另外一个独立的线程(绘制线程),而原来线程对 WebView 的绘制就只剩下缓存的拷贝(混合线程),绘制线程跟混合线程之间可以使用同步,部分同步,完全异步等作业模式,让浏览器可以在性能与效果之间根据需要进行选择,比如说异步模式下,当浏览器需要将 WebView 缓存拷贝到窗口缓存,但是需要更新的部分还没有来得及绘制时,浏览器可以在还未及时更新的部分绘制一个背景色或者空白,这样虽然渲染效果有所下降,但是保证了每一帧窗口更新的间隔都在理想的范围内。并且浏览器还可以为 WebView 创建一个更大的缓存,超过 WebView本身的大小,让我们可以缓存更多的网页内容,可以预先绘制不可见的区域,这样就可以有效减少异步模式下出现空白的状况,在性能和效果之间取得更好的平衡。

    硬件加速

    上述的渲染模式,无论是绘制还是混合,都是由 CPU 完成的,而没有使用到 GPU。绘制任务比较复杂,较难使用 GPU 来完成,并且对于各种复杂的图形/文本的绘制来说,使用 GPU 效率有时反而更低(并且系统资源的开销也较大),但是混合就不一样了,GPU 最擅长的就是并行处理多个像素的计算,所以 GPU 相对于 CPU,执行混合的速度要快的多,特别是存在缩放,旋转,Alpha 混合的时候,而且混合相对来说也比较简单,改成使用 GPU 来完成并不困难。

    并且在多线程渲染模式下,因为绘制和混合分别处于不同的线程,绘制使用 CPU,混合使用 GPU,这样可以通过 CPU/GPU 之间的并发运行有效地提升浏览器整体的渲染性能。更何况,窗口的更新是由混合线程来负责的,混合的效率越高,窗口更新的间隔就越短,用户感受到 UI 界面变化的流畅度就越高,只要窗口更新的间隔能够始终保持在16.7毫秒以内,UI 界面就能够一直保持60帧/每秒的极致流畅度(因为一般来说,显示屏幕的刷新频率是60hz,所以60帧/秒已经是极限帧率,超过这个数值意义不大,而且 OS 的图形子系统本身就会强制限制 UI 界面的更新跟屏幕的刷新保持同步)。

    所以对于现代浏览器来说,所谓硬件加速,就是使用 GPU 来进行混合,绘制仍然使用 CPU 来完成。

    分块渲染

    Tile Rendering

    图片来自 [UC 浏览器 9.7 Android版],使用256×256大小的分块

    网页的缓存通常都不是一大块,而是划分成一格一格的小块,通常为256×256或者512×512大小,这种渲染方式称为分块渲染(Tile Rendering)。使用分块渲染的主要原因是因为 –

    1. 所谓 GPU 混合,通常是使用 Open GL/ES 贴图来实现的,而这时的缓存其实就是纹理(GL Texture),而很多 GPU 对纹理的大小有限制,比如长/宽必须是2的幂次方,最大不能超过2048或者4096等,所以无法支持任意大小的缓存;
    2. 使用小块缓存,方便浏览器使用一个统一的缓存池来管理分配的缓存,这个缓存池一般会分配成百上千个缓存块供所有的 WebView 共用。所有打开的网页,需要缓存时都可以以缓存块为单位向缓存池申请,而当网页关闭或者不可见时,这些不需要的缓存块就可以被回收供其它网页使用;

    总之固定大小的小块缓存,通过一个统一缓存池来管理的方式,比起每个 WebView 自己持有一大块缓存有很多优势。特别是更适合多线程 CPU/GPU 并发的渲染模型,所以基本上支持硬件加速的浏览器都会使用分块渲染的方式。

    图层混合加速

    Layer Accelerated Compositing

    图片来自 [UC 浏览器 9.7 Android版],可见区域内有4个 Layer 有自己的缓存 – 最底层的 Base Layer,上方的 Fixed 标题栏,中间的热点新闻栏,右下方的 Fixed 跳转按钮

    图层混合加速(Accelerated Compositing)的渲染架构是 Apple 引入 WebKit 的,并在 Safari 上率先实现,而 Chrome/Android/Qt/GTK+ 等都陆续完成了自己的实现。如果熟悉 iOS 或者 Mac OS GUI 编程的读者对其应该不会感到陌生,它跟 iOS CoreAnimation 的 Layer Rendering 渲染架构基本类似,主要都是为了解决当 Layer 的内容频繁发生变化,或者当 Layer 触发一个2D/3D变换(2D/3D Transform )或者渐隐渐入动画,它的位移,缩放,旋转,透明度等属性不断发生变化时,在原有的渲染架构下,渲染性能低下的问题。

    非混合加速的渲染架构,所有的 RenderLayer 都没有自己独立的缓存,它们都被绘制到同一个缓存里面(按照它们的先后顺序),所以只要这个 Layer 的内容发生变化,或者它的一些 CSS 样式属性比如 Transform/Opacity 发生变化,变化区域的缓存就需要重新生成,此时不但需要绘制变化的 Layer,跟变化区域(Damage Region)相交的其它 Layer 都需要被绘制,而前面已经说过,网页的绘制是十分耗时的。如果 Layer 偶尔发生变化,那还不要紧,但如果是一个 JavaScript 或者 CSS 动画在不断地驱使 Layer 发生变化,这个动画要达到60帧/每秒的流畅效果就基本不可能了。

    而在混合加速的渲染架构下,一些 RenderLayer 会拥有自己独立的缓存,它们被称为混合图层(Compositing Layer),WebKit 会为这些 RenderLayer 创建对应的 GraphicsLayer,不同的浏览器需要提供自己的 GrphicsLayer 实现用于管理缓存的分配,释放,更新等等。拥有 GrphicsLayer 的 RenderLayer 会被绘制到自己的缓存里面,而没有 GrphicsLayer 的 RenderLayer 它们会向上追溯有 GrphicsLayer 的父/祖先 RenderLayer,直到 Root RenderLayer 为止,然后绘制在有 GrphicsLayer 的父/祖先 RenderLayer 的缓存上,而 Root RenderLayer 总是会创建一个 GrphicsLayer 并拥有自己独立的缓存。最终,GraphicsLayer 又构成了一棵与 RenderLayer 并行的树,而 RenderLayer 与 GraphicsLayer 的关系有些类似于 RenderObject 与 RenderLayer 之间的关系。

    混合加速渲染架构下的网页混合,也变得比以前复杂,不再是简单的将一个缓存拷贝到窗口缓存上,而是需要完成源自不同 Layer 的多个缓存的拷贝,再加上可能的2D/3D变换,再加上缓存之间的Alpha混合等操作,当然,对于支持硬件加速,使用 GPU 来完成混合的浏览器来说,速度还是很快的。

    RenderLayer 生成 GraphicsLayer 的条件,来自 GPU Accelerated Compositing in Chrome

    1. Layer has 3D or perspective transform CSS properties
    2. Layer is used by < video> element using accelerated video decoding
    3. Layer is used by a < canvas> element with a 3D context or accelerated 2D context
    4. Layer is used for a composited plugin
    5. Layer uses a CSS animation for its opacity or uses an animated webkit transform
    6. Layer uses accelerated CSS filters
    7. Layer with a composited descendant has information that needs to be in the composited layer tree, such as a clip or reflection
    8. Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer is rendered on top of a composited layer)

    混合加速的渲染架构下,Layer 的内容变化,只需要更新所属的 GraphicsLayer 的缓存即可,而缓存的更新,也只需要绘制直接或者间接属于这个 GraphicsLayer 的 RenderLayer 而不是所有的 RenderLayer。特别是一些特定的 CSS 样式属性的变化,实际上并不引起内容的变化,只需要改变一些 GraphicsLayer 的混合参数,然后重新混合即可,而混合相对绘制而言是很快的,这些特定的 CSS 样式属性我们一般称之为是被加速的,不同的浏览器支持的状况不太一样,但基本上CSS Transform & Opacity 在所有支持混合加速的浏览器上都是被加速的。被加速的CSS 样式属性的动画,就比较容易达到60帧/每秒的流畅效果了。

    Falling Leaves

    图片来自 Understanding Hardware Acceleration on Mobile Browsers,展现了经典的 CSS 动画 Demo – Falling Leaves 的图层混合的示意图,它所使用的 Transform 和 Opacity 动画在所有支持混合加速的浏览器上都是被加速的

    不过并不是拥有独立缓存的 RenderLayer 越多越好,太多拥有独立缓存的 Layer 会带来一些严重的副作用 – 首先它大大增加了内存的开销,这点在移动设备上的影响更大,甚至导致浏览器在一些内存较少的移动设备上无法很好地支持图层混合加速;其次,它加大了混合的时间开销,导致混合性能的下降,而混合性能跟网页滚动/缩放操作的流畅度又息息相关,最终导致网页滚动/缩放的流畅度下降,让用户觉得操作不够流畅。

    在 Chrome://flags 里面开启“合成渲染层边框”就可以看到哪些 Layer 是一个 Compositing Layer,也就是拥有自己的独立缓存。前端开发者可以用此帮助自己控制 Compositing Layer 的创建。总的的说, Compositing Layer 可以提升绘制性能,但是会降低混合性能,网页只有合理地使用 Compositing Layer,才能在绘制和混合之间取得一个良好的平衡,实现整体渲染性能的提升。

     

    Chrome Flags

    图片来自 Chrome,展现了经典的 CSS 动画 Demo – Falling Leaves 的合成渲染层边框

    网页游戏渲染 - Canvas & WebGL

    2D Canvas

    图片来自 [UC 浏览器 9.7 Android版],基于2D Canvas 的游戏不江湖,在主流配置手机上可以达到60帧/每秒的流畅度

    以前网页游戏一般都是使用 Flash 来实现,但是随着 Flash 从移动设备被淘汰,越来越多的网页游戏会改用 Canvas 和 WebGL 来开发,浏览器关于 Canvas 的基本绘制流程可以参考我以前的文章Introduce My Work。虽然一般网页元素都是使用 CPU 来绘制,但是对于加速的2D Canvas 和 WebGL 来说,它们的绘制是直接使用 GPU 的,所以它们一般会拥有一个GL FBO(FrameBufferObject)作为自己的缓存,Canvas/WebGL 的内容被绘制到这个 FBO 上面,而这个 FBO 所关联的纹理再在混合操作里面被拷贝到窗口缓存上。简单的来说,对于加速的2D Canvas 和 WebGL,它们的绘制和混合都是使用 GPU。

    WebGL

    图片来自 [UC 浏览器 9.7 Android版],一个演示 WebGL 的网页 Demo

    关于如何优化 Canvas 游戏的性能,请参考我以前的文章 – High Performance Canvas Game for Android(高性能Android Canvas游戏开发)

    参考索引

    How Browsers Work: Behind the scenes of modern web browsers
    How WebKit Work
    WebKit for Developers
    GPU Accelerated Compositing in Chrome
    Understanding Hardware Acceleration on Mobile Browsers
    Web Page Rendering and Accelerated Compositing
    我的2013 – 年终总结 + 浏览器渲染发展的一些思考
    Introduce My Work
    High Performance Canvas Game for Android(高性能Android Canvas游戏开发)
    OpenGL Frame Buffer Object (FBO)

     
  • Roger 8:27 pm on December 29, 2013 固定链接 | 回复
    Tags: , , , , WebKit   

    我的2013 – 年终总结 + 浏览器渲染发展的一些思考 

    又到一年的年末,秉承传统,继续为这一年写一篇总结的文章。今年依旧延续了进入公司后每年都更换不同小组的传统,下半年调到了内核渲染组,不过之前在技术研究组的工作也一直是专注于浏览器渲染相关的技术研究,所以这次调动也算是顺理成章了,工作内容基本上没有任何变化。

     
    今年一年以来的工作都跟硬件加速渲染有关,除了在不断完善和优化原有的硬件加速/图层混合加速的渲染架构,保证UC加速版的顺利发布外,另外最主要的工作就是设计和实现2D Canvas的加速渲染架构,从年初的初始实现版本到UC9.4加速版的版本,其间一共更替了三套不同的架构,而且每一次基本上都是完全推倒重来,但也解决了前一版本无法克服的一些内在缺陷,当前最新的架构基本上已经没有什么性能上明显的瓶颈,也足够灵活地根据不同的设备动态调整自己的行为,在兼容性,资源占用,性能这三者之间取得一个较好的平衡。
     
    回顾自己这一年的收获,通过研究Android图形子系统的底层实现,研究GPU和其它各种硬件的工作原理,自己对渲染架构和性能的理解也越来越深刻。“物尽其用,要事优先”是自己对高性能渲染架构的总结,一个高性能的渲染架构,设计者需要对硬件有相当的认识,知道如何将一个完整的渲染任务合理地进行分解,然后调度不同的硬件尽可能并发地完成它;要了解哪里是性能的瓶颈,哪部分是关键的任务,要保证关键任务优先获得资源的分配,尽可能降低瓶颈的影响。
     
    下半年开始,在和主管的工作总结中,还有职业等级评定的答辩会议上,都不断地被问到下面这两个问题,同时自己也在不断地进行思考。
    • 浏览器的渲染架构还有没有可能有更大的性能突破?
    • 浏览器能不能够成为一个承载游戏运行的良好平台,使得H5游戏能够跟原生游戏在同一层面竞争?
     
    浏览器的渲染架构还有没有可能有更大的性能突破?
     
    目前基于WebKit或者Blink内核浏览器的渲染架构,虽然各自的实现不太一样,但是整体架构设计都是由Apple奠定的,Apple当时把自己在MacOS上的那套Layer Rendering的渲染架构引入WebKit,率先在Safari上实现了Accelerated Compositing(图层混合加速),Chrome接着完成了自己的图层混合加速实现,而Android直到3.x的系统浏览器才开始支持,到了4.1才算是比较完善。虽然我不太了解Firefox的渲染架构实现,不过之前看过的一些文章介绍,思路总体上应该跟WebKit差不多。
     
    图层混合加速将渲染任务分解为Paint(绘制)和Composite(混合),绘制由WebKit内核自身使用CPU在一个独立的线程完成,绘制或者光栅化后的结果Layer Buffer(图层缓存)被发送到混合线程,由GPU来进行混合,而Blit(缓存的混合)则是GPU最擅长的工作,甚至可能不需要动用GPU的3D加速单元,使用2D Blitter就够了。
     
    这种架构主要的缺陷在于:
    1. 为了保证渲染性能和效果,浏览器需要大量的图层缓存,尤其对于是一些图层非常复杂,数量非常多的网页,这个问题在移动设备上更加明显;
    2. 由于绘制仍然使用CPU,对于普通的网页,绘制任务主要由图片,文本,各种不同图形的填充(使用纯色,渐变或者纹理)组成,CPU在大部份情况下的确做的比GPU更好,而且资源消耗更少,另外也有助于让GPU更专注于混合任务;但是对一些复杂的动态特效,像是CSS Filter的使用,单纯靠CPU会很困难,并且这种先绘制再混合的方式对于这些动态特效来说反而会造成性能上的损失,并且需要更多缓存;
    第一个问题,如果GPU能够支持一种无损压缩的纹理作为图层缓存,并且压缩可以高效实时的完成(甚至可以使用GPU,在纹理上传的同时完成压缩),就能够得到极大的缓解(对于网页这种大部份以文本和图形为主的位图,压缩率应该非常高,使用ZIP压缩1/10都是很正常的),不过在了解了一些纹理压缩相关的知识后,发现这一点的确不太可能,GPU主要还是为了游戏设计的,而游戏使用的都是事先生成的压缩纹理,并且GPU为了读取压缩纹理时的速度考虑,不可能支持可变压缩率,所以也无法支持无损压缩。
     
    第二个问题,可能的答案是将一部分绘制任务丢到混合线程直接由GPU来完成。不过困难在于两个方面:首先是否能够对绘制任务进行合理的分解,将其中一部分适合GPU完成的任务由GPU在混合线程直接进行绘制,而剩下的仍然保持原有的方式,简单说就是实现一种Hybrid Rendering架构;另一方面是使用GPU做缓存的混合是比较简单的,但是要直接完成复杂的绘制则比较困难,如果使用OpenGL,意味着要实现各种不同绘制的Shader,如果使用OpenVG这样的矢量图绘制API,貌似硬件兼容性和系统支持程度又有很多问题(Android系统本身是不支持OpenVG的,即使大部份GPU硬件上都支持)。除此之外使用RenderScript或者OpenCL这样的异构计算API来进行GPU加速绘制也是一种可能的方式。
     
    浏览器能不能够成为一个承载游戏运行的良好平台,使得H5游戏能够跟原生游戏在同一层面竞争?
     
    回答这个问题之前,先看一下现状,浏览器目前是通过2D Canvas和3D Canvas(WebGL)来支持2D/3D的即时绘图,这也是目前H5游戏的基础。在Android平台上,Chrome和UC都比较完整实现了2D Canvas的GPU加速,并且Chrome已经支持WebGL,UC接下来也会提供WebGL的支持,而WebGL本来就是对应到GLES。使用2D/3D Canvas开发H5游戏,存在的一些性能问题是:
    1. Canvas元素只是网页中所有元素的其中一个,所以它的绘制必须先绘制到一个单独的缓存中,仍然再跟其它元素的图层缓存进行混合输出,这个会带来一些额外的开销,导致性能损耗。不过这个问题还好,对PC来说这个额外开销不算高,而移动设备GPU的性能也越来越好(不过分辨率飙升过快也是一个问题…)
    2. 2D Canvas只提供了最基础的绘图API,难以通过这些API高效实现各种复杂的特效,比如说要实现一个粒子效果,不得不调用几十次甚至上百次drawRect或者drawBitmap,这样的效率非常低。而WebGL还好,可以允许游戏自己直接控制Buffer,自己编写Shader实现各种特效,直接交由GPU运行,从这点看,长远来说WebGL对游戏还是更有前途一些,不过相对来说,它的开发难度就要高不少(即使是使用2D Canvas开发的游戏,很多游戏也会在一些静态不需要太高性能的场景改成使用CSS+DIV来降低开发难度);
    3. 无论是2D Canvas也好,WebGL也好,它们都只提供了绘图API,离一个完整的游戏引擎相去甚远,而要依靠JS实现一个高性能的大型游戏引擎(包括物理运动引擎等)用来支撑一个大型游戏,受限于JS本身的性能,并且缺少底层硬件的直接控制能力,这点不得不说是最大的困难;
    对于第三点,Firefox和Chrome的解决思路完全不同:
    • Chrome通过Native Client允许原生代码直接在浏览器提供的一个受限环境里面运行;
    • Firefox通过ASM.js + WebGL的方式,遵循ASM.js语法的代码仍然是标准的JS代码,但是特殊的语法使得JS虚拟机可以直接将它们翻译成机器码,高效地运行;
    这两种方式先不谈技术本身的成熟度,共同存在的问题就是它们都太过复杂,游戏开发者不直接使用Cocos2D或者Unity3D这样成熟的游戏引擎开发原生游戏而使用Native Client或者ASM.js +WebGL,那简直就是脱裤子放屁,多此一举。
     
    有没有第三种解决问题的思路,我个人认为,浏览器自带游戏引擎(自行开发或者整合第三方的游戏引擎),然后通过一套标准的游戏引擎API供JS调用是一种可行的方式,这样JS就基本上变成像Lua这样的场景控制脚本,这种方式能够同时解决性能的问题和游戏开发难度的问题,当然它的问题是这样的游戏引擎标准API的制定和浏览器的实现还是相当虚无缥缈的事情……
     
    除了性能问题,H5游戏还有打包,发行等问题,不过这些可以通过打包应用(Packaged App)和Web App Store来解决。
     
    最后是我这一年写的比较满意的一些文章:
    1. Why Your Android Apps Suck https://plus.google.com/104793567654398886385/posts/TEr9MYV7tVy
    2. Android GraphicBuffer Introduction and Its Usage in Rendering https://plus.google.com/104793567654398886385/posts/j1LcfHAr5BS
    3. High Performance Canvas Game for Android(高性能Android Canvas游戏开发) http://tech.uc.cn/?p=2414
     
    • Luin 8:49 下午 on 12月 29, 2013 固定链接 | 回复

      收获很多,多谢

    • Luke 12:42 上午 on 12月 30, 2013 固定链接 | 回复

      好习惯啊,而且都是干货。
      游戏是一种对浏览器App较高强度的考验,而且前景不错,以游戏的性能来作为评价指标也不失为一个好办法。
      顺着你的思路,我觉得优化空间应该还是有的:
      1. 纹理方面: a. 元素很多的页面是否做到了纹理拼接和压缩?(但对应的缓存应该还是需要的吧)
      b. 纹理压缩算法本身可以优化。一方面可以考虑并行运算的优化,另外一方面各种算法在内存消耗和运算消耗方面可以做trade-off。
      2. 更多元素使用GPU绘制,包括css3的效果等。理论上好像基于加速的skia就好?
      3. js引擎的继续优化。
      针对游戏开发,我觉得使用游戏开发套件或者引擎来开发肯定会是主流了,基于WebGL作为图形引擎的应该会成为主流,不用过分担心2D游戏的效率问题。
      Native Client技术很好,但暂时来说我觉得和Android NDK之于SDK一样,作为辅助或者完成一部分关键功能比较合适,主流的开发环境还是JS+HTML5。不过谁知道以后会不会有NaCL的游戏引擎?再说了Js后端也可能用PNacl/LLVM啊。
      从实用的角度来说,UC还可以发布一个基于浏览器的游戏引擎。

    • 艾楠 3:11 下午 on 3月 31, 2014 固定链接 | 回复

      好习惯!~

    • 韩国女装品牌网站 12:32 下午 on 3月 25, 2016 固定链接 | 回复

      世间有许多事功亏一篑的主要原因就是意志不坚定,不能坚持到底。现实生活中,如果没有坚定意志,我们就不能享受成功的幸福。

    • 韩国食品网上商城 12:36 下午 on 3月 25, 2016 固定链接 | 回复

      成功的人,是在无数次投资失败后站起来的。

    • 韩国时尚购物起义 1:08 下午 on 3月 25, 2016 固定链接 | 回复

      对投资而言,欲速则不达

    • 韩国创意设计用品网站 8:36 上午 on 6月 29, 2016 固定链接 | 回复

      像采金一样追求知识。

  • Roger 1:20 pm on April 20, 2013 固定链接 | 回复
    Tags: , , , , WebKit   

    国内Android手机浏览器内核未来竞争势态分析 

    前几天跟主管谈到这个话题,讨论得出一些结论,记录下来以备将来验证。国内手机浏览器在所谓“自有内核”的竞争上将会分为两个不同阶段,目前处于第一阶段。

    第一阶段是基于WebKit开发的“自有内核”,大概有两种方式,一种是WebKit内核代码(WebCore部分)直接跟主干保持同步,定期更新,平台适配层架构基本跟Android系统浏览器的适配层架构保持一致,将大部分系统浏览器适配层代码移植过来;另外一种就是直接全部采用Android系统浏览器的代码,包括适配层代码和WebKit内核代码,然后WebKit内核代码再逐步向主干靠拢(Android系统浏览器包含的WebKit内核代码版本比较旧);当然这两种方式发展到一定程度,其实是殊途同归的,相对来说,第一种方式对团队的整体水平要求更高一些。

    这一阶段的主要玩家就是UC和腾讯,百度也有参与,但是它的移动战略实在让人看不懂,感觉十分摇摆不定,按理说百度人和钱都不缺,但是始终没有下定决心大幅度投入手机浏览器领域,不知道在第二阶段是否会改变策略。

    其它国内桌面浏览器的主要玩家如360,搜狗,金山基本不参与这一阶段的竞争,360只在Android系统提供的WebView组件之上做了一层壳,搜狗和金山压根就没有什么大动作,360去年上半年还通过猎头在挖做手机浏览器内核的人,据说金山本来也准备投入,但是后来都改变了策略,更多把人力投入到桌面浏览器的竞争上面,在手机浏览器上,估计会直接参与第二阶段的竞争(听猎头说,传言金山本来已经成立手机浏览器团队,但是后来又合并回去全力做桌面浏览器)。

    第二阶段的“自有内核”会是基于Chrome&Blink,Chrome从WebKit主干分离出Blink,开始自己独自主导浏览器内核的发展,并且围绕Blink构建了一套Content API,用于将Chrome核心模块如多进程沙盒模型,渲染架构,网络堆栈,V8&Dart虚拟机和Blink内核封装在一起,用于支持Chrome本身和第三方基于Chrome&Blink内核的浏览器的开发(如新版的Opera)。

    从目前的状况来看,Chrome&Blink内核在Android平台大范围普及的时机还不成熟,主要在于Chrome&Blink内核本身还处于一个不太稳定的状态,Content API还在剧烈变化中,代码的性能优化和系统稳定性还有很多不足,并且Chrome本身的多进程架构对手机的性能有比较高的要求,包括CPU/GPU和内存大小,一些性能较差的手机连基本操作的流畅度都无法保证,另外默认的渲染架构强制要求4.0以上的系统版本。这些原因会导致第二阶段的竞争最早也会在明年才开始,开始贴身肉搏式的剧烈争斗预估也要到2015年。

    估计360,搜狗,金山会直接投入第二阶段的竞争,可能现在已经开始筹建团队和进行初期的技术准备。对他们来说,第一阶段已经基本尘埃落定,参与的意义不大,有限的人力还不如早点开始准备第二阶段,并且他们在桌面浏览器上本来就是基于Chrome在开发,所以第二阶段的竞争对他们来说反而在技术储备上会更有利。

    最终,从明年到2015年上半年这段时间,国内Android手机浏览器在“自有内核”上竞争的主要玩家会逐步增加到5~6家,因为各方都是基于Chrome&Blink进行二次开发,要获得相对于其它竞争者的技术优势会变得殊为不易。技术上的领先更多会体现在谁能更快跟进HTML5标准的发展,跟进由Google主导的Chrome&Blink内核本身的演进,一些局部的性能优化和更好地解决设备兼容性问题和系统稳定性问题。
     
  • Roger 5:03 pm on April 4, 2013 固定链接 | 回复
    Tags: , , WebKit   

    Why Blink and Why not Blink 

    清明放假的第一天,Mozilla 和 Google同时宣布了他们新的浏览器引擎的开发计划 —— Servo 和 Blink。Servo 早前其实就一直有消息了,而 Blink 的发布则是相当突然,因为工作的原因,我自然是对 Blink 更感兴趣(放个假都不得安生,苦逼的程序员),更希望了解 Google 为什么要从 WebKit fork 出一个新的浏览器引擎(Why Blink),这样的做法会给 Chrome 后续的发展带来什么样正面变化(Why not Blink)。

    在Blink的官方主页里,Google 认为 Blink 的使命是通过技术创新和良好的社区合作来推动开放网络的发展(Blink’s Mission : To improve the open web through technical innovation and good citizenship)。根据官方 Developer FAQ 的说法,Google 之所以选择不再继续跟随 WebKit 而是独自开发新的引擎的主要原因是:

    There are two main reasons why we’re making this change.

    The main reason is that Chromium uses a different multi-process architecture than other WebKit-based browsers. So, over the years, supporting multiple architectures has led to increasing complexity for both the WebKit and Chromium communities, slowing down the collective pace of innovation.
    In addition, this gives us an opportunity to do open-ended investigations into other performance improvement strategies. We want web applications to be as fast as possible. So for example, we want to make as many of the browser’s duties run in parallel, so we can keep the main thread free for your application code. We’ve already made significant progress here–for example by reducing the impact JavaScript and layout has on page scrolling, and making it so an increasing number of CSS animations can run at 60fps even while JavaScript is doing some heavy-lifting–but this is just the start.

    We want to do for networking, rendering and layout what V8 did for JavaScript. Remember JS engines before V8? We want the same sort of healthy innovation that benefits all users of the web, on all browsers.

    在多进程架构上,Google一开始就独自开发了一套沙盒多进程架构,它和后来由Apple主导的WebKit2多进程架构差异很大,为了支持WebKit2架构而加入WebCore的大量代码,对Google不但一点用也没有,还不得不花时间去处理兼容性的问题,而Google需要修改WebCore来支持自己架构的代码又很难进入WebKit主干,必须很小心处理避免影响其它的Port,大量的代码不得不通过迂回的方式放在外部处理,一些没方法在外部处理而需要对WebCore进行大改的特性不得不暂时放弃。

    并且,因为历史原因,WebCore本身一开始就没有多线程或者多进程的概念,现有的架构对并行处理的支持非常困难,Google也认为必须对WebCore进行整体架构上的大改才能更好的支持并行处理,更充分利用多核CPU的能力,避免主线程过度拥挤(虽然现在大部分的WebKit Port都把主要的渲染工作分离到其它线程,但是主线程仍然需要负担HTML解析,CSS样式计算和匹配,排版,JS执行等繁重的任务,为了避免单项任务长时间阻塞主线程,WebCore目前是用延时Timer的方式将一个复杂任务分解成多段来顺序执行,这种方式即不优雅,更无法充分利用多核的能力)。

    另外,WebCore现在的模块化比较混乱;一些历史遗留的代码和仅仅用于支持某些特定平台的代码导致WebCore代码臃肿不堪;平台相关的处理也没有一个统一的标准和方式,没有一个很好的抽象层去隔离平台相关和平台无关的部分;WebCore为了可以同时支持不同的JS虚拟机(如JSC和V8)导致了额外的性能开销和妨碍了对JS性能更多的改进;除此以外,更安全的隔离机制;对现有的网络层进行更大的结构优化等等这些原因也是Google需要自己发展Blink的主要原因。

    总之,Chrome有太多激进的改进需要对WebCore进行大改,而原来那种在外围做文章,曲线救国的方式再也行不通,为了能够自行主导架构的演进方向,避免跟其它Port相互干扰,相互扯皮给双方带来的困扰和痛苦,加快开发的速度,从WebKit主干分离,自己发展新的浏览器引擎就成了必然的选择。

    从Chrome自身的开发者来看,对此无一不表示欢欣鼓舞之情 ^_^,纷纷表示 ——

    “这实在太令人兴奋啦!我们早就该这么做啦!”
    “以后老子想怎么改就怎么改,想蘸糖吃就蘸糖吃,想蘸醋吃就蘸醋吃,再也不用跟那帮斯扯来扯去纠缠不清”
    “自从用了Blink以后,内个不痛,月月轻松… so easy!妈妈再也不用担心我的学习啦…”

    http://infrequently.org/2013/04/probably-wrong/
    https://plus.google.com/u/0/116560594978217291380/posts/AeCnq76cAXb
    https://plus.google.com/u/0/105497998876878526147/posts/etnTiaXZEGM
    https://plus.google.com/u/0/116237864387312784020/posts/d62jMDKXcLb

     
  • Roger 11:47 am on March 25, 2013 固定链接 | 回复
    Tags: , , WebKit   

    Introduce My Work 2 

    早前写了一篇文章“Introduce My Work”,用一个例子 —— HTML5 Canvas的一个简单实现,介绍自己工作的内容,而这篇文章试图去描绘自己工作内容所需的一个完整的知识体系的层次结构(见下图),并对其进行简单介绍。

    android_graphics

    Browser App

    基于系统的GUI框架开发完整的浏览器应用。

    Android GUI Framework

    1. 了解Android的View System机制;
    2. 从3.0开始Android支持使用Layer作为View的Backing Store,用于对动画进行加速;
    3. 了解Android Window Surface相关的Java封装和实现细节;
    4. Canvas是Android Java层用于2D绘图的主要对象;

    WebKit

    WebKit通常可以分为WebCore和WebKit Port,在WebCore中,跟渲染相关的对象主要有RenderLayer, RenderObject, GraphicsContext, Image, ImageBuffer等,而WebKit Port则负责整个渲染架构的设计和实现,它需要考虑:

    1. 使用什么作为Backing Store,是Vector(矢量图),还是Bitmap(位图),还是Texture(贴图),还是Surface(Window Surface);
    2. 支不支持Layer Rendering(分层渲染,需要WebCore开启Accelerated Compositing的支持),支不支持Tile Rendering(分块渲染);
    3. 采用多线程(单进程)的渲染架构还是像WebKit2或者Chrome那样的多进程渲染架构;

    Android Graphics Stack (Client)

    Android在客户端的绘图堆栈通常包括:

    1. Skia,一个使用CPU进行2D绘图的绘图库;
    2. OpenGL ES,使用GPU进行3D和2D的绘图的API(比如Android 3.0后出现的hwui模块就使用GLES来取代Skia用于2D绘图,实现GPU加速的2D绘图);
    3. EGL,衔接GLES和系统的Native Window系统的适配层;
    4. Surface(Native Window),可以通过Surface接口的一个Stub实现操控远程的Native Window,另外Android 4.0后,应用还可以创建自己的Native Window并且自己负责它的混合;

    Android Graphics Stack(Server)

    SurfaceFlinger是Android用于管理Display和负责Window Composite(窗口混合),把应用的显示窗口输出到Display的系统服务。

    Android Drivers(HAL)

    Android的驱动层,通过Android本身的HAL(硬件抽象层)机制,运行于User Space,跟渲染相关的包括:

    1. hwcomposer,如果硬件支持,SurfaceFlinger可以请求hwcomposer去做窗口混合而不需要自己来做,这样的效率也会更高,减少对GPU资源的占用;
    2. gralloc,用来管理Graphics Buffer的分配和管理系统的framebuffer;
    3. copybit,早期Android在GPU不支持完整的GL ES1的情况下,内部的软件版本的GL ES1实现可以使用copybit来加速贴图操作(其实也就是加速窗口混合),hwcomposer出现后就已经移除了;
    4. gles/egl驱动;

    Linux Kernel and Drivers

    除了标准的Linux内核和驱动(如fb是framebuffer驱动),硬件厂商自己的驱动外,Android自己的一些Patches:

    1. ashmem,异步共享内存,用于在进程间共享一块内存区域,并允许系统在资源紧张时回收不加锁的内存块;
    2. pmem,用于分配物理地址连续的内存块,一般从已经预留的内存区域中分配,主要用来作为Graphics Buffer,不过4.0以后,逐步被更完整的内存管理器ION替代;
    3. binder,高效的进程间通信机制;
    4. sync,用来将呼叫者阻塞,并强制跟显示器的刷新保持同步;

    Hardware

    CPU,GPU,VPU(Video Process Unit),和内存等等。

     

    虽然目前的工作主要集中在WebKit这一层,但是由上至下打通每一个层次,对更好的拓展自己的工作范围,具备挑战更高难度的任务的能力,产出更高品质的成果,的的确确是非常重要的。应该说,自己目前仅仅只是把上面的一个知识体系的架子搭起来,描绘出一个较为完整的轮廓而已,很多部分也仅仅是了解一些简单的概念,特别是底层的层次,无论是其中的原理还是实现的细节了解的非常少。而未来的两到三年的时间,要做的最重要的事情就是不断的拓展,补充和完善上述的知识体系,以期能够早日达成“恢恢乎其于游刃必有余地矣”的境界。

     
  • Roger 11:11 am on February 2, 2013 固定链接 | 回复
    Tags: , , WebKit   

    我的2012 

    去年放假时为我的2011一整年写了一篇回顾,今年也打算继续,希望以后每年坚持下来形成习惯。

    2012年的最大变化就是工作内容从外壳的GUI框架层转向内核的网页渲染:
    • 年初第一季度的研究重点是在Android 4.0系统浏览器的硬件加速AC渲染架构和整个适配层的架构;
    • 接下第二季度的工作是参考系统浏览器的渲染架构,在我们的浏览器上也实现多线程渲染架构,当时考虑4.0的渲染架构太过复杂,而且很多版本兼容性问题无法很好解决,所以还是以2.3的系统浏览器为主要参考对象;
    • 完成初始的预研工作,实现演示Demo后,项目这边决定第三季度在8.6上多线程渲染架构改造,不过后续的实际开发工作中碰到了很多困难,主要是因为程序从原来的单线程架构一下子改成多线程,需要重写的模块和修改的地方非常多,一些原来没有很好处理多线程的地方引发了大量的崩溃问题,并且多线程在低端机上的性能表现一开始不是很好,也花费了大量的时间去做性能分析跟优化;
    • 接下来的8.7版本我的主要工作是在8.6的基础上进一步优化一些基础渲染性能,如滑屏,缩放等,最终的表现也还算不错,渲染性能比起以前的版本改进很大,虽然还无法跟支持硬件加速的4.x系统浏览器相比,但是在还未支持硬件加速的情况下也基本上做到极致了;
    • 做完8.7后,8月下旬开始准备我们浏览器整个适配层的改造工作,在8.6,8.7多线程渲染架构改造的基础上,我们决定完全抛弃掉以前自己写的C++适配层和一套从塞班时代延续过来的自己开发的C++ GUI Toolkit,完全转向跟系统浏览器一样的Java和C++结合的适配层架构,跟Android SDK一样,内核为外壳提供一个标准的Android UI组件WebView,而外壳完全使用Android原生的UI组件进行开发。不过跟8.6,8.7时不太一样的地方在于,这次我主要负责前期技术研究和框架搭建的工作,在完成Demo,技术文档和培训与宣讲后,后续的主要工作就是协助其它同学对各自负责的模块进行设计与实现,只负责一些设计和代码Review的工作而不再直接参与8.8的项目工作;
    • 10月开始了硬件加速方面的研究,在之前工作的基础上,开始移植4.1系统浏览器的渲染架构代码(这时4.1已经发布了,并且4.1比起4.0又复杂了很多),初始的移植工作完成后,已经可以运行一个硬件加速AC的Demo,接下来的主要工作就是解决版本兼容性问题,我们不但需要解决2.x和4.x之间的版本兼容性问题,而且还要解决4.x不同版本之间的兼容性问题,版本兼容性问题的解决使得我们可以一个程序同时运行在2.x和4.x上面,支持非硬件加速和硬件加速两种不同的渲染路径,可以在运行时根据系统决定选择哪种渲染方式,最后因为4.2的发布,我们又把4.2的代码也同步了过来;
    • 前面的工作完成后就差不多到年底了,马不停蹄地又开始了硬件加速2D Canvas的研究,为此也学习了Android的OpenGL ES编程,加深对Android Native Window System,EGL,GLES的理解,另外还研究了Chrome for Android的2D Canvas实现,到目前为止,硬件加速2D Canvas已经基本实现,Demo初始的测试结果还不错,跑GUIMark 3的帧数比Chrome正式版(Chrome 18)要高,不过比Chrome Beta版(Chrome 25)要低,但是考虑到目前我们所使用的Skia的代码比较旧(来自Android 4.2 AOSP),而Chrome这几个版本还在不断优化Skia的性能,后续升级Skia后,相信性能还会再有所提高;
    2013年,最重要的工作就是在之前的硬件加速相关的研究工作的基础上,协助硬件加速AC,硬件加速2D Canvas顺利进入产品,从过往的经验也可以知道,从Demo到产品,后续要做的工作还很多。另外下半年也有可能会去研究WebGL(3D Canvas)。

    而对自己的职业发展的规划,主要的技术研究方向还是会集中在以下几个领域:
    1. 网页渲染相关的研究(CSS动画,Filter/Shader,2D/3D Canvas,Chrome的多进程渲染架构等等);
    2. Android图形子系统
    3. 2D/3D绘图(Skia,OpenGL ES等等);
     
  • Roger 10:32 am on January 18, 2013 固定链接 | 回复
    Tags: Accelerated 2D Canvas, Accelerated Compositing, , , WebKit   

    Introduce My Work 

    图像

    我在LinkedIn上面的个人简介是:

    Roger at UC Mobile Ltd. (www.uc.cn), focus on graphics stack (rendering architecture) research of WebKit based Browser in Android platform, include the graphics stack of WebKit itself along with the display subsystem of Android platform, and design how to combine the best of both them to achieve butter graphics performance of page rendering.

    简单的说就是专注于Android平台基于WebKit的浏览器绘图堆栈的研究,可是,到底什么是所谓的”Android平台基于WebKit的浏览器绘图堆栈“,下面我用一个例子来说明。

    我们来考究一个最简单的HTML5 Canvas在Android平台基于WebKit的浏览器上的实现:

    1. 如果网页包含Canvas标签,或者由JavaScript动态生成,都会导致WebKit内核生成一个HTMLCanvasElement Node对象;
    2. 当JS从Canvas元素获得一个2D Context对象时(var ctx = canvas.getContext(“2d”)),实际上这个Context JS对象会跟一个C++的CanvasRenderingContext2D对象绑定,JS在这个Context上的全部调用都会被虚拟机转发给对应的CanvasRenderingContext2D对象;
    3. WebKit内部的2D绘图是基于GraphicsContext的,所以CanvasRenderingContext2D对象需要一个GraphicsContext来执行真正的2D绘图操作,它会向HTMLCanvasElement对象发出请求,通过它的drawingContext()方法返回一个GraphicsContext;
    4. 而HTMLCanvasElement则把这个请求转发给内部的ImageBuffer对象,所以真正用于绘图的GraphicsContext是由ImageBuffer提供的;
    5. ImageBuffer顾名思义,是用来管理一块位图缓存,默认情况下,它会分配一块内存,把这块内存封装成一个位图(在Android平台是SkBitmap),然后通过这个位图创建一个平台相关的绘图上下文PlatformGraphicsContext(在Android平台是SkCanvas和PlatformGraphicsContextSkia),最后把这个PlatformGraphicsContext封装成一个平台无关的GraphicsContext返回;
    6. 从上面可知,当JS通过Canvas元素获得的Context对象绘图时,最终是绘制到HTMLCanvasElement对象内部的ImageBuffer对象管理的一块内存里面;
    上面仅仅说明了JS调用本身的绘制路径,要最终在屏幕上看到结果,我们还需要把ImageBuffer里面的内容绘制到窗口上:

    1. 当JS执行Context对象的每一个绘图操作时,对应的HTMLCanvasElement对象都会对外广播一个内容发生变化的通知(canvasChanged);
    2. 这个通知会被转换成网页内容的某个区域的重绘请求;
    3. 这个网页内容的重绘请求会被WebKit内核的适配层拦截,在Android平台上,它最终会被转换成用于显示这个网页的View对象的某个区域的重绘请求(View.invalidate),重绘区域需要经过从网页的内容坐标系到View的显示坐标系的坐标转换;
    4. 当Android当前Activity的View Hierachy的某个View需要重绘时,Android会在稍后的某个一个时间点发起一个View Hierachy的遍历操作(ViewRootImpl.doTraversal);
    5. 在这个View Hierachy的遍历操作中,Android会生成一个Canvas对象,然后逐个调用View的onDraw方法,让View把自身的内容通过这个Canvas绘制到当前的窗口上,这个Canvas对象实际上从当前窗口对应的Surface对象中获得,而Surface对象需要从当前窗口的Graphics Buffer队列中取出一块空闲的Buffer(这个Buffer由系统的窗口混合器SurfaceFlinger分配),然后把这块Buffer封装成一个SkBitmap,再根据SkBitmap生成SkCanvas,再根据SkCanvas生成Java Canvas…
    6. 当用于显示网页的View对象的onDraw方法被Android系统调用到时,它需要重新把C++的SkCanvas对象从Java的Canvas对象中取出来,然后把这个SkCanvas包装成WebKit所需的GraphicsContext对象,传给WebKit让它去绘制网页,当WebKit绘制到对应这个Canvas元素的Render Object(RenderHTMLCanvas)时,RenderHTMLCanvas实际上会把这个绘制请求转发给它对应的HTMLCanvasElement对象(就是前面说的那一个),而HTMLCanvasElement则把自己的ImageBuffer对象通过传入的GraphicsContext最终绘制到当前窗口上;
    7. 当Android遍历完整个View Hierachy,所有View都通过传入的Canvas绘制到了当前的窗口上,但这时我们还未能在屏幕上看到更新的内容,因为实际上我们是绘制到了当前窗口的back buffer,Android还需要把绘制完的back buffer解锁,放回窗口的Graphics Buffer队列,通知SurfaceFlinger窗口的内容发生了变化(Surface.unlockAndPost);
    8. 当SurfaceFlinger收到窗口更新的通知时,它会切换窗口的前后台缓存(Page Flip),然后逐个把当前可见的窗口的front buffer拷贝到系统的framebuffer里面去,这样我们最终才在屏幕上看到Canvas绘制的结果(Android 4.0开始,如果硬件支持,可以省略掉将窗口front buffer拷贝到系统framebuffer这一步,由display hardware自己负责);
    上面的例子足够复杂吗,实际上已经省略了很多不太重要的部分,也简化了一些比较复杂的概念比如Android的Native Window和Window Compositing机制,并且这仅仅是一个最最最简单的实现!!!

    如果我们需要考虑支持:
    1. 支持像系统浏览器一样的多线程渲染架构或者像Chrome一样的多进程渲染架构;
    2. 支持Android的GPU加速绘图(从Android3.0 开始支持);
    3. 支持WebKit的图层混合加速(Accelerated Compositing);
    4. 支持硬件加速的2D Canvas实现(Accelerated 2D Canvas);
    上面的每一个选项都会让本来的流程再加倍的复杂,而这些就是我的主要工作内容 ^_^
     
    • unbug 10:38 上午 on 1月 18, 2013 固定链接 | 回复

      强文,营养丰富,收藏备读!

    • 一丝 10:55 上午 on 1月 18, 2013 固定链接 | 回复

      虽不明,但觉厉(PS,楼上的你好讨厌捏,老是抢在我前面。)

  • Roger 10:30 am on October 13, 2012 固定链接 | 回复
    Tags: android 4.1, rom, WebKit   

    Android 4.1 – 将系统浏览器编译成独立应用 

    为了方便在手机上(Galaxy Note with CM10),调试Android4.1 系统浏览器的代码,进行代码研究,我把系统浏览器编译成了一个独立的应用,不会跟ROM原来的系统浏览器产生冲突,可以很方便地在Eclipse自己建立的工程里面对Java部分的代码进行跟踪调试,理论上C++的部分也可以通过GDB进行调试。
    自己编译的库,显示Layer边界和信息
    首先系统浏览器可以认为分为3部分:

    1,Browser.Apk 一个全功能浏览器应用
    2,android.webkit 平台适配层的Java部分代码,对外提供了封装好的WebView
    3,libwebcore.so 包括WebKit的代码和平台适配层C++部分的代码,libchromium_net.so Chrome的网络堆栈
    我们实际只需要后面两部分(2和3),然后加上自己的一个简单的测试用外壳就可以了。

    首先参考官方的文档,建立ROM编译环境,编译ROM(http://source.android.com/source/index.html)。

    开始建立独立的应用(home/roger/a41是我的ROM的目录,需要替换成自己ROM的目录):
    1. 在Eclipse创建一个Android工程,把android.webkit目录下的Java代码拷贝过来;
    2. 将/home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/webkit下面的EventLogTags.java也拷贝到自己的工程;
    3. 因为android.webkit下的类会使用SDK中非公开的API,我们需要解决编译错误:
      1. 创建一个User Library,并且勾选System Library的选项;
      2. 加入以下Jar包:
        1. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
        2. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar
        3. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/bouncycastle_intermediates/classes.jar
      3. 在Java Build Path/Order and Export把创建的库放在最前面;
    4. 因为在我们应用中的android.webkit包跟SDK中的重名,所以我们需要更改包名,可以改成android.webkit2;
    5. 我们需要重新编译libchromium_net.so和libwebcore.so,并且使用另外的名字,并且把其代码中使用的android/webkit/ JNI路径改成android/webkit2/保证JNI的正确性:
      1. 在/home/roger/a41/external/chromium下面,把所有源文件的android/webkit/路径改成android/webkit2/;
      2. 打开/home/roger/a41/external/chromium/Android.mk,修改库名为libchromium_net2,并且加多一行“LOCAL_MODULE_TAGS := optional“,具体内容见后;
      3. 重新编译chromium_net,得到libchromium_net2.so;
      4. 在/home/roger/a41/external/webkit/Source/WebKit/android下面,把所有源文件的android/webkit/路径改成android/webkit2/;
      5. 打开/home/roger/a41/external/webkit/Android.mk,将库名改成libwebcore2.so,并且加多一行“LOCAL_MODULE_TAGS := optional“(需要修改两个地方,静态库编译和动态库编译),另外还需要把导入库libchromium_net改成libchromium_net2,具体内容见后;
      6. 重新编译webcore,得到libwebcore2.so;
    6. 接下来我们可以把修改后的libwebcore2.so和libchromium_net2.so push到手机的rom里面,假设路径是/data/local(如果没有写权限,用Root Explorer修改);
    7. 然后我们需要修改Java的代码,让它去加载我们自己的库,修改的地方位于JniUtil.java和WebViewCore.java,具体内容见后(加载顺序需要改变,先加载libchromium_net2.so再加载libwebcore2.so);
    8. 最后加上我们自己的Test Shell的代码,运行就OK了,如果只修改了C++的代码,重编译后再Push到手机,然后重新运行Test Shell就可以马上生效,Java的代码可以在Eclipse里面很方便的调试,C++的代码理论上也可以通过GDB进行调试;

    LOCAL_MODULE := libchromium_net2
    LOCAL_MODULE_CLASS := SHARED_LIBRARIES
    LOCAL_MODULE_TAGS := optional
    INTERMEDIATES := $(call local-intermediates-dir)


    # Define our module and find the intermediates directory
    LOCAL_MODULE := libwebcore2
    LOCAL_MODULE_CLASS := STATIC_LIBRARIES
    LOCAL_MODULE_TAGS := optional
    base_intermediates := $(call local-intermediates-dir)


    # Do not attempt prelink this library. Needed to keep master-gpl happy, no

    1. effect in master.
    2. TODO: remove this when master-gpl is updated.

    LOCAL_PRELINK_MODULE := false
    LOCAL_MODULE := libwebcore2
    LOCAL_MODULE_TAGS := optional
    LOCAL_LDLIBS := $(WEBKIT_LDLIBS)


    1. Build the list of shared libraries
    2. We have to use the android version of libdl

    LOCAL_SHARED_LIBRARIES := \
    libEGL \
    libGLESv2 \
    libandroid \
    libandroidfw \
    libandroid_runtime \
    libchromium_net2 \
    libcrypto \
    libcutils \
    libdl \
    libgui \
    libicuuc \
    libicui18n \
    libmedia \
    libmedia_native \
    libnativehelper \
    libskia \
    libsqlite \
    libssl \
    libstlport \
    libutils \
    libui \
    libz


    static {
    System.load(“/data/local/libchromium_net2.so”);

             System.load(“/data/local/libwebcore2.so”);
    }
     
    • ddy123 6:47 上午 on 10月 29, 2012 固定链接 | 回复

      不知道楼主对sony给出的webkit,使其支持webgl的代有研究没,想交流下。

      • Roger 9:59 上午 on 10月 29, 2012 固定链接 | 回复

        同事有研究过,貌似是使用离屏的Pixmap进行绘制,效率不怎么样。

    • 匿名 7:26 下午 on 3月 4, 2013 固定链接 | 回复

      博主,这里从第5步开始就不怎么明白了,我在源码external/chromium下面,没找到android/webkit目录啊。

  • Roger 8:12 pm on March 2, 2012 固定链接 | 回复
    Tags: , WebKit   

    阅读源代码的一些体会 

    最近一个月的时间,差不多都在看Android 4.0系统和它的WebKit实现的代码。面对庞大的代码库,复杂的系统,从一开始毫无头绪到后来慢慢厘清主要的流程和摸清系统的结构。体会到有一些方法对阅读源代码是非常有帮助的。

    Screenshot_2012-03-02-20-02-38

    需要可以编译,运行和调试

    如果只是光看代码,没办法编译,运行和调试,那对系统的理解总是只能停留在概念性的表面而无法深入。最好是能够下断点和单步跟踪,如果很难实现单步跟踪,起码也要可以输出日志,dump出一些运行时的主要对象结构和对象状态,或者直接在渲染结果上面绘制额外的示意标记。

    只有通过观察系统运行时的行为,才能深入的理解系统的运作。包括:

    • 理解系统的线程模型,线程运行的顺序,和它们之间的通讯;
    • 理解主要对象之间的关系,它们各自承担的职责;
    • 理解系统的主要流程,一些重要方法的调用链;
    • 理解系统的时间消耗分布,哪些部分对性能影响最大;
    • 等等…

    所以在开始看代码之前,先建立好一个方便调试运行的环境是十分重要的。所谓工欲善其事,必先利其器。

     

    找到那些(单元)测试或者例程

    一个好的开源项目,通常都会附带一些单元测试程序或者演示例程。它们对我们对于整个系统的研究来说,简直是无价之宝。

    • 一来它们短小精悍,比较容易理解;
    • 二来它们聚焦特定的模块,通过观察它们的运行和输出,更容易帮助我们了解一个模块的职责;
    • 并且我们很容易修改它的代码,进行各种各样的试验,进一步帮助我们了解模块运行时的行为,印证我们各种猜想;

     

    绘制类结构图

    面对庞大的类型系统和它们之间复杂的关系,一幅类结构图就像是一份地图,随时可以告诉你当前所处的位置,周围的环境,来去的方向等等。使你不会觉得放眼望去,白茫茫一片,不知何去何从。

    一开始总是比较困难的,但是更要坚持一边看代码一边绘制类图,随着你的类图越来越完善,你对系统全景也会越来越有感觉。

    使用像EA这样的工具绘制正式的UML图固然很好(还可以通过逆向工程帮助建立初始的UML模式),使用像XMind这样的工具绘制脑图也是一个不错的选择。

     

    记笔记

    不要太相信自己的记忆力,对于那些你仅仅是在阅读,而没有自己亲自实现的东西,你的大脑是很容易就把它遗忘。一瞬间的明悟,最好马上把它记下来;一些无法确认的猜想,也没有关系,记下来然后打个问号即可。即使一开始十分的散乱,后续也可以花些时间整理的更有条理,而且通过整理笔记,可以进一步加深自己的记忆和理解。使用Evernote或者类似软件是一个很好的选择,你随时都可以阅读和进一步完善它们。

     

    最后,如果你将来需要把研究的结果写成文档或者制作演讲稿,之前记录的笔记和绘制的类图就是最好的素材。

     

     

     
    • 红心地瓜 1:36 下午 on 3月 22, 2012 固定链接 | 回复

      +1
      聚焦很重要,将面聚焦成线的这个过程最辛苦。

    • 匿名 4:05 下午 on 5月 4, 2012 固定链接 | 回复

      LZ分享的很好,最近也在做Chrome在IOS平台的移植工作,另一方面也在看Android4.0中webkit的源码,有机会多多交流

  • Roger 10:26 pm on September 16, 2011 固定链接 | 回复
    Tags: , WebKit   

    新工作一年回顾 

    不知不觉到新公司已经满一年了,回想一年前,自己的确做出了一个重大的决定,而从目前来看,这个决定还是相当正确的,当前的工作比较符合自己的期望,未来职业的发展也有更多空间。

     

    回顾这一年,自己在技术方面提高较多的方面包括:

    1. 对GUI框架设计和实现的理解 —— 在UC的这一年里面,大部分工作都跟GUI框架的设计和实现有关。刚进公司的第一件比较重要的工作就是为J2ME平台设计一个类似Android这样的现代GUI框架,虽然最后没有用于实际的产品,但是自己还是从中积累了很多经验,不但在设计和实现的过程中参考了Qt,Android,LWUIT等框架,同时也加入了很多特有的设计。在转到UCMobile后,大部分工作也是跟GUI框架里面的重要模块有关,包括动画框架,样式引擎,布局引擎等等。
    2. 对系统设计的理解 —— 因为有很多机会进行底层和中间层的系统和模块设计,感觉自己对系统设计的理解又提高到了一个新的层次。尤其是设计时需要考虑很多额外的约束,包括需要考虑J2ME平台本身的性能和内存耗费的限制,或者需要考虑兼容UCMobile原来的GUI框架等等,在这么多林林总总的约束下进行设计,迫使自己不得不绞尽脑汁如何在诸多因素中取得一个较好的平衡,从而极大锻炼了逻辑推理的能力,能够对一些设计中的细微之处把握的更精准,对模块之间依赖关系的掌控也更加得心应手。
    3. 对浏览器相关技术的掌握 —— 虽然自己一直在做浏览器产品的开发,但实际的工作跟浏览器核心的部分交集较少,不过也对相关的技术,包括HTTP协议,HTML规范,CSS规范,客户端JavaScript编程等多多少少有所了解,业余的时间也研究过一下WebKit的源码,特别是CSS的部分,因为实际的工作中实现过一个CSS解析器,所以这一部分了解比较深入。

    未来需要提高的部分:

    1. GUI框架仍然是一个飞速发展的领域,特别是在以下两方面 —— 声明式UI(使用脚本描述界面,使用JavaScript处理事件),和GPU硬件加速。自己在这个领域仍然有很多需要学习的东西。
    2. 浏览器核心,毋庸置疑,这部分技术价值含量高,而且发展十分迅猛,即使在实际工作中缺少机会,自己仍然需要更多利用业余时间进行学习和实践。
     
c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
回复
e
编辑
o
Show/Hide comments
t
返回顶部
l
Go to login
h
Show/Hide help
shift + esc
取消