硬件加速,直观上说就是依赖GPU实现图形绘制加速,同软硬件加速的区别主要是图形的绘制到底是GPU来处理仍是CPU,若是是GPU,就认为是硬件加速绘制,反之,软件绘制。在Android中也是如此,不过相对于普通的软件绘制,硬件加速还作了其余方面优化,不只仅限定在绘制方面,绘制以前,在如何构建绘制区域上,硬件加速也作出了很大优化,所以硬件加速特性能够从下面两部分来分析:java
不管是软件绘制仍是硬件加速,绘制内存的分配都是相似的,都是须要请求SurfaceFlinger服务分配一块内存,只不过硬件加速有可能从FrameBuffer硬件缓冲区直接分配内存(SurfaceFlinger一直这么干的),二者的绘制都是在APP端,绘制完成以后一样须要通知SurfaceFlinger进行合成,在这个流程上没有任何区别,真正的区别在于在APP端如何完成UI数据绘制,本文就直观的了解下二者的区别,会涉及部分源码,但不求甚解。node
大概从Android 4.+开始,默认状况下都是支持跟开启了硬件加速的,也存在手机支持硬件加速,可是部分API不支持硬件加速的状况,若是使用了这些API,就须要主关闭硬件加速,或者在View层,或者在Activity层,好比Canvas的clipPath等。可是,View的绘制是软件加速实现的仍是硬件加速实现的,通常在开发的时候并不可见,那图形绘制的时候,软硬件的分歧点究竟在哪呢?举个例子,有个View须要重绘,通常会调用View的invalidate,触发重绘,跟着这条线走,去查一下分歧点。android
从上面的调用流程能够看出,视图重绘最后会进入ViewRootImpl的draw,这里有个判断点是软硬件加速的分歧点,简化后以下算法
ViewRootImpl.javacanvas
private void draw(boolean fullRedrawNeeded) {
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
<!--关键点1 是否开启硬件加速-->
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
dirty.setEmpty();
mBlockResizeBuffer = false;
<!--关键点2 硬件加速绘制-->
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
...
<!--关键点3 软件绘制-->
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
...复制代码
关键点1是启用硬件加速的条件,必须支持硬件而且开启了硬件加速才能够,知足,就利用HardwareRenderer.draw,不然drawSoftware(软件绘制)。简答看一下这个条件,默认状况下,该条件是成立的,由于4.+以后的手机通常都支持硬件加速,并且在添加窗口的时候,ViewRootImpl会enableHardwareAcceleration开启硬件加速,new HardwareRenderer,并初始化硬件加速环境。缓存
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
<!--根据配置,获取硬件加速的开关-->
// Try to enable hardware acceleration if requested
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
if (hardwareAccelerated) {
...
<!--新建硬件加速图形渲染器-->
mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated =
mAttachInfo.mHardwareAccelerationRequested = true;
}
...复制代码
其实到这里软件绘制跟硬件加速的分歧点已经找到了,就是ViewRootImpl在draw的时候,若是须要硬件加速就利用 HardwareRenderer进行draw,不然走软件绘制流程,drawSoftware其实很简单,利用Surface.lockCanvas,向SurfaceFlinger申请一块匿名共享内存内存分配,同时获取一个普通的SkiaCanvas,用于调用Skia库,进行图形绘制,bash
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
<!--关键点1 -->
canvas = mSurface.lockCanvas(dirty);
..
<!--关键点2 绘制-->
mView.draw(canvas);
..
关键点3 通知SurfaceFlinger进行图层合成
surface.unlockCanvasAndPost(canvas);
} ...
return true; }复制代码
drawSoftware工做彻底由CPU来完成,不会牵扯到GPU的操做,下面重点看下HardwareRenderer所进行的硬件加速绘制。多线程
开头说过,硬件加速绘制包括两个阶段:构建阶段+绘制阶段,所谓构建就是递归遍历全部视图,将须要的操做缓存下来,以后再交给单独的Render线程利用OpenGL渲染。在Android硬件加速框架中,View视图被抽象成RenderNode节点,View中的绘制都会被抽象成一个个DrawOp(DisplayListOp),好比View中drawLine,构建中就会被抽象成一个DrawLintOp,drawBitmap操做会被抽象成DrawBitmapOp,每一个子View的绘制被抽象成DrawRenderNodeOp,每一个DrawOp有对应的OpenGL绘制命令,同时内部也握着绘图所须要的数据。以下所示:并发
如此以来,每一个View不只仅握有本身DrawOp List,同时还拿着子View的绘制入口,如此递归,便可以统计到全部的绘制Op,不少分析都称为Display List,源码中也是这么来命名类的,不过这里其实更像是一个树,而不只仅是List,示意以下:框架
构建完成后,就能够将这个绘图Op树交给Render线程进行绘制,这里是同软件绘制很不一样的地方,软件绘制时,View通常都在主线程中完成绘制,而硬件加速,除非特殊要求,通常都是在单独线程中完成绘制,如此以来就分担了主线程不少压力,提升了UI线程的响应速度。
知道整个模型后,就代码来简单了解下实现流程,先看下递归构建RenderNode树及DrawOp集。
HardwareRenderer是整个硬件加速绘制的入口,实现是一个ThreadedRenderer对象,从名字能看出,ThreadedRenderer应该跟一个Render线程息息相关,不过ThreadedRenderer是在UI线程中建立的,那么与UI线程也一定相关,其主要做用:
可见ThreadedRenderer的做用是很重要的,简单看一下实现:
ThreadedRenderer(Context context, boolean translucent) {
...
<!--新建native node-->
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
<!--新建NativeProxy-->
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
ProcessInitializer.sInstance.init(context, mNativeProxy);
loadSystemProperties();
}复制代码
从上面代码看出,ThreadedRenderer中有一个RootNode用来标识整个DrawOp树的根节点,有个这个根节点就能够访问全部的绘制Op,同时还有个RenderProxy对象,这个对象就是用来跟渲染线程进行通讯的句柄,看一下其构造函数:
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance())
, mContext(nullptr) {
SETUP_TASK(createContext);
args->translucent = translucent;
args->rootRenderNode = rootRenderNode;
args->thread = &mRenderThread;
args->contextFactory = contextFactory;
mContext = (CanvasContext*) postAndWait(task);
mDrawFrameTask.setContext(&mRenderThread, mContext);
}复制代码
从RenderThread::getInstance()能够看出,RenderThread是一个单例线程,也就是说,每一个进程最多只有一个硬件渲染线程,这样就不会存在多线程并发访问冲突问题,到这里其实环境硬件渲染环境已经搭建好好了。下面就接着看ThreadedRenderer的draw函数,如何构建渲染Op树:
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();
<!--关键点1:构建View的DrawOp树-->
updateRootDisplayList(view, callbacks);
<!--关键点2:通知RenderThread线程绘制-->
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
...
}复制代码
只关心关键点1 updateRootDisplayList,构建RootDisplayList,其实就是构建View的DrawOp树,updateRootDisplayList会进而调用根View的updateDisplayListIfDirty,让其递归子View的updateDisplayListIfDirty,从而完成DrawOp树的建立,简述一下流程:
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
<!--更新-->
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
<!--获取DisplayListCanvas-->
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
<!--利用canvas缓存Op-->
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onHardwarePreDraw(canvas);
canvas.insertReorderBarrier();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.insertInorderBarrier();
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
<!--将全部Op填充到RootRenderNode-->
mRootNode.end(canvas);
}
}
}复制代码
简单看一下View递归构建DrawOp,并将本身填充到
@NonNull
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
...
// start 获取一个 DisplayListCanvas 用于绘制 硬件加速
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
// 是不是textureView
final HardwareLayer layer = getHardwareLayer();
if (layer != null && layer.isValid()) {
canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
} else if (layerType == LAYER_TYPE_SOFTWARE) {
// 是否强制软件绘制
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
// 若是仅仅是ViewGroup,而且自身不用绘制,直接递归子View
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else {
<!--调用本身draw,若是是ViewGroup会递归子View-->
draw(canvas);
}
}
} finally {
<!--缓存构建Op-->
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
}
return renderNode;
}复制代码
TextureView跟强制软件绘制的View比较特殊,有额外的处理,这里不关心,直接看普通的draw,假如在View onDraw中,有个drawLine,这里就会调用DisplayListCanvas的drawLine函数,DisplayListCanvas及RenderNode类图大概以下
DisplayListCanvas的drawLine函数最终会进入DisplayListCanvas.cpp的drawLine,
void DisplayListCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
points = refBuffer<float>(points, count);
addDrawOp(new (alloc()) DrawLinesOp(points, count, refPaint(&paint)));
}复制代码
能够看到,这里构建了一个DrawLinesOp,并添加到DisplayListCanvas的缓存列表中去,如此递归即可以完成DrawOp树的构建,在构建后利用RenderNode的end函数,将DisplayListCanvas中的数据缓存到RenderNode中去:
public void end(DisplayListCanvas canvas) {
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
<!--将DrawOp缓存到RenderNode中去-->
nSetDisplayListData(mNativeRenderNode, renderNodeData);
// canvas 回收掉]
canvas.recycle();
mValid = true;
}复制代码
如此,便完成了DrawOp树的构建,以后,利用RenderProxy向RenderThread发送消息,请求OpenGL线程进行渲染。
DrawOp树构建完毕后,UI线程利用RenderProxy向RenderThread线程发送一个DrawFrameTask任务请求,RenderThread被唤醒,开始渲染,大体流程以下:
不过再这以前先复习一下绘制内存的由来,毕竟以前DrawOp树的构建只是在普通的用户内存中,而部分数据对于SurfaceFlinger都是不可见的,以后又绘制到共享内存中的数据才会被SurfaceFlinger合成,以前分析过软件绘制的UI是来自匿名共享内存,那么硬件加速的共享内存来自何处呢?到这里可能要倒回去看看ViewRootImlp
private void performTraversals() {
...
if (mAttachInfo.mHardwareRenderer != null) {
try {
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
mSurface.allocateBuffers();
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
....
/**
* Allocate buffers ahead of time to avoid allocation delays during rendering
* @hide
*/
public void allocateBuffers() {
synchronized (mLock) {
checkNotReleasedLocked();
nativeAllocateBuffers(mNativeObject);
}
}复制代码
能够看出,对于硬件加速的场景,内存分配的时机会稍微提早,而不是像软件绘制事,由Surface的lockCanvas发起,主要目的是:避免在渲染的时候再申请,一是避免分配失败,浪费了CPU以前的准备工做,二是也能够将渲染线程个工做简化,在分析Android窗口管理分析(4):Android View绘制内存的分配、传递、使用的时候分析过,在分配成功后,若是有必要,会进行一次UI数据拷贝,这是局部绘制的根基,也是保证DrawOp能够部分执行的基础,到这里内存也分配完毕。不过,仍是会存在另外一个问题,一个APP进程,同一时刻会有过个Surface绘图界面,可是渲染线程只有一个,那么究竟渲染那个呢?这个时候就须要将Surface与渲染线程(上下文)绑定。
static jboolean android_view_ThreadedRenderer_initialize(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject jsurface) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
sp<ANativeWindow> window = android_view_Surface_getNativeWindow(env, jsurface);
return proxy->initialize(window);
}复制代码
首先经过android_view_Surface_getNativeWindowSurface获取Surface,在Native层,Surface对应一个ANativeWindow,接着,经过RenderProxy类的成员函数initialize将前面得到的ANativeWindow绑定到RenderThread
bool RenderProxy::initialize(const sp<ANativeWindow>& window) {
SETUP_TASK(initialize);
args->context = mContext;
args->window = window.get();
return (bool) postAndWait(task);
}复制代码
仍旧是向渲染线程发送消息,让其绑定当前Window,其实就是调用CanvasContext的initialize函数,让绘图上下文绑定绘图内存:
bool CanvasContext::initialize(ANativeWindow* window) {
setSurface(window);
if (mCanvas) return false;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
return true;
}复制代码
CanvasContext经过setSurface将当前要渲染的Surface绑定到到RenderThread中,大概流程是经过eglApi得到一个EGLSurface,EGLSurface封装了一个绘图表面,进而,经过eglApi将EGLSurface设定为当前渲染窗口,并将绘图内存等信息进行同步,以后经过RenderThread绘制的时候才能知道是在哪一个窗口上进行绘制。这里主要是跟OpenGL库对接,全部的操做最终都会归结到eglApi抽象接口中去。假如,这里不是Android,是普通的Java平台,一样须要类似的操做,进行封装处理,并绑定当前EGLSurface才能进行渲染,由于OpenGL是一套规范,想要使用,就必须按照这套规范走。以后,再建立一个OpenGLRenderer对象,后面执行OpenGL相关操做的时候,其实就是经过OpenGLRenderer来进行的。
上面的流程走完,有序DrawOp树已经构建好、内存也已分配好、环境及场景也绑定成功,剩下的就是绘制了,不过以前说过,真正调用OpenGL绘制以前还有一些合并操做,这是Android硬件加速作的优化,回过头继续走draw流程,其实就是走OpenGLRenderer的drawRenderNode进行递归处理:
void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t replayFlags) {
...
<!--构建deferredList-->
DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
<!--合并及分组-->
renderNode->defer(deferStruct, 0);
<!--绘制layer-->
flushLayers();
startFrame();
<!--绘制 DrawOp树-->
deferredList.flush(*this, dirty);
...
}复制代码
先看下renderNode->defer(deferStruct, 0),合并操做,DrawOp树并非直接被绘制的,而是首先经过DeferredDisplayList进行一个合并优化,这个是Android硬件加速中采用的一种优化手段,不只能够减小没必要要的绘制,还能够将类似的绘制集中处理,提升绘制速度。
void RenderNode::defer(DeferStateStruct& deferStruct, const int level) {
DeferOperationHandler handler(deferStruct, level);
issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler);
}复制代码
RenderNode::defer其实内含递归操做,好比,若是当前RenderNode表明DecorView,它就会递归全部的子View进行合并优化处理,简述一下合并及优化的流程及算法,其实主要就是根据DrawOp树构建DeferedDisplayList,defer原本就有延迟的意思,对于DrawOp的合并有两个必要条件,
1:两个DrawOp的类型必须相同,这个类型在合并的时候被抽象为Batch ID,取值主要有如下几种
enum OpBatchId {
kOpBatch_None = 0, // Don't batch kOpBatch_Bitmap, kOpBatch_Patch, kOpBatch_AlphaVertices, kOpBatch_Vertices, kOpBatch_AlphaMaskTexture, kOpBatch_Text, kOpBatch_ColorText, kOpBatch_Count, // Add other batch ids before this }; 复制代码
在合并过程当中,DrawOp被分为两种:须要合的与不须要合并的,并分别缓存在不一样的列表中,没法合并的按照类型分别存放在Batch mBatchLookup[kOpBatch_Count]中,能够合并的按照类型及MergeID存储到TinyHashMap<mergeid_t, DrawBatch> mMergingBatches[kOpBatch_Count]中,示意图以下:
合并以后,DeferredDisplayList Vector mBatches包含所有整合后的绘制命令,以后渲染便可,须要注意的是这里的合并并非多个变一个,只是作了一个集合,主要是方便使用各资源纹理等,好比绘制文字的时候,须要根据文字的纹理进行渲染,而这个时候就须要查询文字的纹理坐标系,合并到一块儿方便统一处理,一次渲染,减小资源加载的浪费,固然对于理解硬件加速的总体流程,这个合并操做能够彻底无视,甚至能够直观认为,构建完以后,就能够直接渲染,它的主要特色是在另外一个Render线程使用OpenGL进行绘制,这个是它最重要的特色。而mBatches中全部的DrawOp都会经过OpenGL被绘制到GraphicBuffer中,最后经过swapBuffers通知SurfaceFlinger合成。
软件绘制同硬件合成的区别主要是在绘制上,内存分配、合成等总体流程是同样的,只不过硬件加速相比软件绘制算法更加合理,同时减轻了主线程的负担。
做者:看书的小蜗牛
理解Android硬件加速的小白文
仅供参考,欢迎指正