Android中绘图的API很多,比如2D的基于Skia的接口,3D的绘图OpenGLES,Vulkan等。Android早期系统多数都是采用2D的绘图模式,比如绘制一张Bitmap图片。随着用户对视觉效果的追求以及硬件的能力突破,原有的渲染已经无法满足要求。所以Android在4.4后开始默认打开硬件加速来帮助加速渲染。
硬件加速,直白的说就是依赖GPU实现图形绘制加速,软硬件加速的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制。为什么要用GPU替代CPU?
在渲染过程中,尤其是动画过程中,经常涉及位置、大小、插值、缩放、旋转、透明度变化、动画过渡、毛玻璃模糊,甚至包括3D变换、物理运动的计算,逻辑的处理相对较少,还可能会涉及到浮点计算,CPU和GPU本身的能力决定了他们可发挥的能力,GPU更适合做计算处理,也是移动端异构运算的一种实现。
然而在Android中,硬件加速还做了其他方面优化,不仅仅限定在绘制方面,绘制之前,在如何构建绘制区域上,硬件加速也做出了很大优化,因此硬件加速特性可以从下面两部分来分析:
上面的策略展开来讲第一步是将2D的绘图操纵转换为对应的3D的绘图操纵,这个转换过程我们把它叫做录制。第二步在RenderThread线程用OpenGLES/Vulkan通过GPU去渲染。在上层view的绘制过程中就有硬件加速控制的相关代码如下:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
//判断是否开启硬件加速
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
......
if (hardwareAcceleratedCanvas) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
mPrivateFlags &= ~PFLAG_INVALIDATED;
}
......
if (drawingWithRenderNode) { // 硬件加速
// Delay getting the display list until animation-driven alpha values are
// set up and possibly passed on to the view
renderNode = updateDisplayListIfDirty();
if (!renderNode.hasDisplayList()) {
// Uncommon, but possible. If a view is removed from the hierarchy during the call
// to getDisplayList(), the display list will be marked invalid and we should not
// try to use it again.
renderNode = null;
drawingWithRenderNode = false;
}
}
.....
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode); // 硬件加速
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
if (alpha < 1) {
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
}
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
if (alpha < 1) {
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
}
......
return more;
}
所谓构建就是递归遍历所有视图,将需要的操作缓存下来,之后再交给单独的Render线程利用OpenGL渲染。在Android硬件加速框架中,View视图被抽象成RenderNode节点,View中的绘制都会被抽象成一个个DrawOp(DisplayListOp),比如View中drawLine,构建中就会被抽象成一个DrawLintOp,drawBitmap操作会被抽象成DrawBitmapOp,每个子View的绘制被抽象成DrawRenderNodeOp,每个DrawOp有对应的OpenGL绘制命令,同时内部也握着绘图所需要的数据。
构建完成后,就可以将这个绘图Op树交给Render线程进行绘制,这里是同软件绘制很不同的地方,软件绘制时,View一般都在主线程中完成绘制,而硬件加速,除非特殊要求,一般都是在单独线程中完成绘制,如此一来就分担了主线程很多压力,提高了UI线程的响应速度。
HardwareRenderer是整个硬件加速的入口:
public HardwareRenderer() {
// 创建rendernode
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
// 创建RenderProxy
mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode);
if (mNativeProxy == 0) {
throw new OutOfMemoryError("Unable to create hardware renderer");
}
Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
ProcessInitializer.sInstance.init(mNativeProxy);
}
hwui部分native的代码在libs/hwui/renderthread/下面,大家有兴趣可行查阅,下面贴一些核心的代码片段:
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
39 IContextFactory* contextFactory)
40 : mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
41 mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
42 return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
43 });
44 mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
45 pthread_gettid_np(pthread_self()), getRenderThreadTid());
46 }
从RenderThread::getInstance()可以看出,RenderThread是一个单例,每个进程最多只有一个硬件渲染线程。
void RenderThread::initThreadLocals() {
232 setupFrameInterval();
233 initializeChoreographer();
234 mEglManager = new EglManager();
235 mRenderState = new RenderState(*this);
236 mVkManager = VulkanManager::getInstance();
237 mCacheManager = new CacheManager();
238 }
会初始化Opengl和Vulkan的管理器,按需求选用是OpenGl还是Vulkan,Vulkan相关的也都在该包下。
DisplayListCanvas的JNI实现如下:
* frameworks/base/core/jni/android_view_DisplayListCanvas.cpp
const char* const kClassPathName = "android/view/DisplayListCanvas";
static JNINativeMethod gMethods[] = {
// ------------ @FastNative ------------------
{ "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
(void*) android_view_DisplayListCanvas_callDrawGLFunction },
// ------------ @CriticalNative --------------
{ "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
{ "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
{ "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
{ "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
{ "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
{ "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
{ "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
{ "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
{ "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
};
RecordingCanvas实现
RecordingCanvas::RecordingCanvas(size_t width, size_t height)
: mState(*this), mResourceCache(ResourceCache::getInstance()) {
resetRecording(width, height);
}
void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) {
LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!");
mDisplayList = new DisplayList();
mState.initializeRecordingSaveStack(width, height);
mDeferredBarrierType = DeferredBarrierType::InOrder;
}
RecordingCanvas里面定义了各种OP,
struct DrawArc final : Op {
231 static const auto kType = Type::DrawArc;
232 DrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
233 const SkPaint& paint)
234 : oval(oval)
235 , startAngle(startAngle)
236 , sweepAngle(sweepAngle)
237 , useCenter(useCenter)
238 , paint(paint) {}
239 SkRect oval;
240 SkScalar startAngle;
241 SkScalar sweepAngle;
242 bool useCenter;
243 SkPaint paint;
244 void draw(SkCanvas* c, const SkMatrix&) const {
245 c->drawArc(oval, startAngle, sweepAngle, useCenter, paint);
246 }
247 };
上面提到说RenderNode会保存Op的数据和操作信息:
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
218 if (mDamageGenerationId == info.damageGenerationId) {
219 // We hit the same node a second time in the same tree. We don't know the minimal
220 // damage rect anymore, so just push the biggest we can onto our parent's transform
221 // We push directly onto parent in case we are clipped to bounds but have moved position.
222 info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
223 }
224 info.damageAccumulator->pushTransform(this);
225
226 if (info.mode == TreeInfo::MODE_FULL) {
227 pushStagingPropertiesChanges(info);
228 }
229
230 if (!mProperties.getAllowForceDark()) {
231 info.disableForceDark++;
232 }
233 if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
234 info.stretchEffectCount++;
235 }
236
237 uint32_t animatorDirtyMask = 0;
238 if (CC_LIKELY(info.runAnimations)) {
239 animatorDirtyMask = mAnimatorManager.animate(info);
240 }
241
242 bool willHaveFunctor = false;
243 if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
244 willHaveFunctor = mStagingDisplayList.hasFunctor();
245 } else if (mDisplayList) {
246 willHaveFunctor = mDisplayList.hasFunctor();
247 }
248 bool childFunctorsNeedLayer =
249 mProperties.prepareForFunctorPresence(willHaveFunctor, functorsNeedLayer);
250
251 if (CC_UNLIKELY(mPositionListener.get())) {
252 mPositionListener->onPositionUpdated(*this, info);
253 }
254
255 prepareLayer(info, animatorDirtyMask);
256 if (info.mode == TreeInfo::MODE_FULL) {
257 pushStagingDisplayListChanges(observer, info);
258 }
259
260 if (mDisplayList) {
261 info.out.hasFunctors |= mDisplayList.hasFunctor();
262 mHasHolePunches = mDisplayList.hasHolePunches();
263 bool isDirty = mDisplayList.prepareListAndChildren(
264 observer, info, childFunctorsNeedLayer,
265 [this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
266 bool functorsNeedLayer) {
267 child->prepareTreeImpl(observer, info, functorsNeedLayer);
268 mHasHolePunches |= child->hasHolePunches();
269 });
270 if (isDirty) {
271 damageSelf(info);
272 }
273 } else {
274 mHasHolePunches = false;
275 }
276 pushLayerUpdate(info);
277
278 if (!mProperties.getAllowForceDark()) {
279 info.disableForceDark--;
280 }
281 if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
282 info.stretchEffectCount--;
283 }
284 info.damageAccumulator->popTransform();
285 }
void RenderNode::pushLayerUpdate(TreeInfo& info) {
174 #ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers
175 LayerType layerType = properties().effectiveLayerType();
176 // If we are not a layer OR we cannot be rendered (eg, view was detached)
177 // we need to destroy any Layers we may have had previously
178 if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
179 CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
180 CC_UNLIKELY(!properties().fitsOnLayer())) {
181 if (CC_UNLIKELY(hasLayer())) {
182 this->setLayerSurface(nullptr);
183 }
184 return;
185 }
186
187 if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) {
188 damageSelf(info);
189 }
190
191 if (!hasLayer()) {
192 return;
193 }
194
195 SkRect dirty;
196 info.damageAccumulator->peekAtDirty(&dirty);
197 info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
198 if (!dirty.isEmpty()) {
199 mStretchMask.markDirty();
200 }
201
202 // There might be prefetched layers that need to be accounted for.
203 // That might be us, so tell CanvasContext that this layer is in the
204 // tree and should not be destroyed.
205 info.canvasContext.markLayerInUse(this);
206 #endif
207 }
核心流程里绘制的Ops都放在mDisplayList中,这边会去递归的调用每个RenderNode的prepareTreeImpl。
pushLayerUpdate,将要更新的RenderNode都加到TreeInfo的layerUpdateQueue中,还有其对应的damage大小。
累加器的popTransform,就是将该Node的DirtyStack生效,收集好DisplayList后就是绘制了。