• Android渲染--重温硬件加速上


    Android中绘图的API很多,比如2D的基于Skia的接口,3D的绘图OpenGLES,Vulkan等。Android早期系统多数都是采用2D的绘图模式,比如绘制一张Bitmap图片。随着用户对视觉效果的追求以及硬件的能力突破,原有的渲染已经无法满足要求。所以Android在4.4后开始默认打开硬件加速来帮助加速渲染。

    硬件加速,直白的说就是依赖GPU实现图形绘制加速,软硬件加速的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制。为什么要用GPU替代CPU?

    在渲染过程中,尤其是动画过程中,经常涉及位置、大小、插值、缩放、旋转、透明度变化、动画过渡、毛玻璃模糊,甚至包括3D变换、物理运动的计算,逻辑的处理相对较少,还可能会涉及到浮点计算,CPU和GPU本身的能力决定了他们可发挥的能力,GPU更适合做计算处理,也是移动端异构运算的一种实现。

    然而在Android中,硬件加速还做了其他方面优化,不仅仅限定在绘制方面,绘制之前,在如何构建绘制区域上,硬件加速也做出了很大优化,因此硬件加速特性可以从下面两部分来分析:

    1. 前期策略:如何构建需要绘制的区域
    2. 后期绘制:单独渲染线程,依赖GPU进行绘制

    上面的策略展开来讲第一步是将2D的绘图操纵转换为对应的3D的绘图操纵,这个转换过程我们把它叫做录制。第二步在RenderThread线程用OpenGLES/Vulkan通过GPU去渲染。在上层view的绘制过程中就有硬件加速控制的相关代码如下:

    1. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    2. //判断是否开启硬件加速
    3. final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
    4. ......
    5. if (hardwareAcceleratedCanvas) {
    6. // Clear INVALIDATED flag to allow invalidation to occur during rendering, but
    7. // retain the flag's value temporarily in the mRecreateDisplayList flag
    8. mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
    9. mPrivateFlags &= ~PFLAG_INVALIDATED;
    10. }
    11. ......
    12. if (drawingWithRenderNode) { // 硬件加速
    13. // Delay getting the display list until animation-driven alpha values are
    14. // set up and possibly passed on to the view
    15. renderNode = updateDisplayListIfDirty();
    16. if (!renderNode.hasDisplayList()) {
    17. // Uncommon, but possible. If a view is removed from the hierarchy during the call
    18. // to getDisplayList(), the display list will be marked invalid and we should not
    19. // try to use it again.
    20. renderNode = null;
    21. drawingWithRenderNode = false;
    22. }
    23. }
    24. .....
    25. if (!drawingWithDrawingCache) {
    26. if (drawingWithRenderNode) {
    27. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    28. ((RecordingCanvas) canvas).drawRenderNode(renderNode); // 硬件加速
    29. } else {
    30. // Fast path for layouts with no backgrounds
    31. if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
    32. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    33. dispatchDraw(canvas);
    34. } else {
    35. draw(canvas);
    36. }
    37. }
    38. } else if (cache != null) {
    39. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    40. if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
    41. // no layer paint, use temporary paint to draw bitmap
    42. Paint cachePaint = parent.mCachePaint;
    43. if (cachePaint == null) {
    44. cachePaint = new Paint();
    45. cachePaint.setDither(false);
    46. parent.mCachePaint = cachePaint;
    47. }
    48. cachePaint.setAlpha((int) (alpha * 255));
    49. canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
    50. } else {
    51. // use layer paint to draw the bitmap, merging the two alphas, but also restore
    52. int layerPaintAlpha = mLayerPaint.getAlpha();
    53. if (alpha < 1) {
    54. mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
    55. }
    56. canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
    57. if (alpha < 1) {
    58. mLayerPaint.setAlpha(layerPaintAlpha);
    59. }
    60. }
    61. }
    62. ......
    63. return more;
    64. }

    所谓构建就是递归遍历所有视图,将需要的操作缓存下来,之后再交给单独的Render线程利用OpenGL渲染。在Android硬件加速框架中,View视图被抽象成RenderNode节点,View中的绘制都会被抽象成一个个DrawOp(DisplayListOp),比如View中drawLine,构建中就会被抽象成一个DrawLintOp,drawBitmap操作会被抽象成DrawBitmapOp,每个子View的绘制被抽象成DrawRenderNodeOp,每个DrawOp有对应的OpenGL绘制命令,同时内部也握着绘图所需要的数据。 

    构建完成后,就可以将这个绘图Op树交给Render线程进行绘制,这里是同软件绘制很不同的地方,软件绘制时,View一般都在主线程中完成绘制,而硬件加速,除非特殊要求,一般都是在单独线程中完成绘制,如此一来就分担了主线程很多压力,提高了UI线程的响应速度。

    HardwareRenderer构建DrawOp

    HardwareRenderer是整个硬件加速的入口:

    1. public HardwareRenderer() {
    2. // 创建rendernode
    3. mRootNode = RenderNode.adopt(nCreateRootRenderNode());
    4. mRootNode.setClipToBounds(false);
    5. // 创建RenderProxy
    6. mNativeProxy = nCreateProxy(!mOpaque, mIsWideGamut, mRootNode.mNativeRenderNode);
    7. if (mNativeProxy == 0) {
    8. throw new OutOfMemoryError("Unable to create hardware renderer");
    9. }
    10. Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
    11. ProcessInitializer.sInstance.init(mNativeProxy);
    12. }

    hwui部分native的代码在libs/hwui/renderthread/下面,大家有兴趣可行查阅,下面贴一些核心的代码片段:

    1. RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
    2. 39 IContextFactory* contextFactory)
    3. 40 : mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
    4. 41 mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
    5. 42 return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
    6. 43 });
    7. 44 mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
    8. 45 pthread_gettid_np(pthread_self()), getRenderThreadTid());
    9. 46 }

    从RenderThread::getInstance()可以看出,RenderThread是一个单例,每个进程最多只有一个硬件渲染线程。

    1. void RenderThread::initThreadLocals() {
    2. 232 setupFrameInterval();
    3. 233 initializeChoreographer();
    4. 234 mEglManager = new EglManager();
    5. 235 mRenderState = new RenderState(*this);
    6. 236 mVkManager = VulkanManager::getInstance();
    7. 237 mCacheManager = new CacheManager();
    8. 238 }

    会初始化Opengl和Vulkan的管理器,按需求选用是OpenGl还是Vulkan,Vulkan相关的也都在该包下。

    DisplayListCanvas的JNI实现如下:

    1. * frameworks/base/core/jni/android_view_DisplayListCanvas.cpp
    2. const char* const kClassPathName = "android/view/DisplayListCanvas";
    3. static JNINativeMethod gMethods[] = {
    4. // ------------ @FastNative ------------------
    5. { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
    6. (void*) android_view_DisplayListCanvas_callDrawGLFunction },
    7. // ------------ @CriticalNative --------------
    8. { "nCreateDisplayListCanvas", "(JII)J", (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
    9. { "nResetDisplayListCanvas", "(JJII)V", (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
    10. { "nGetMaximumTextureWidth", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
    11. { "nGetMaximumTextureHeight", "()I", (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
    12. { "nInsertReorderBarrier", "(JZ)V", (void*) android_view_DisplayListCanvas_insertReorderBarrier },
    13. { "nFinishRecording", "(J)J", (void*) android_view_DisplayListCanvas_finishRecording },
    14. { "nDrawRenderNode", "(JJ)V", (void*) android_view_DisplayListCanvas_drawRenderNode },
    15. { "nDrawLayer", "(JJ)V", (void*) android_view_DisplayListCanvas_drawLayer },
    16. { "nDrawCircle", "(JJJJJ)V", (void*) android_view_DisplayListCanvas_drawCircleProps },
    17. { "nDrawRoundRect", "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
    18. };

    RecordingCanvas实现 

    1. RecordingCanvas::RecordingCanvas(size_t width, size_t height)
    2. : mState(*this), mResourceCache(ResourceCache::getInstance()) {
    3. resetRecording(width, height);
    4. }
    5. void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) {
    6. LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!");
    7. mDisplayList = new DisplayList();
    8. mState.initializeRecordingSaveStack(width, height);
    9. mDeferredBarrierType = DeferredBarrierType::InOrder;
    10. }

    RecordingCanvas里面定义了各种OP,

    1. struct DrawArc final : Op {
    2. 231 static const auto kType = Type::DrawArc;
    3. 232 DrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
    4. 233 const SkPaint& paint)
    5. 234 : oval(oval)
    6. 235 , startAngle(startAngle)
    7. 236 , sweepAngle(sweepAngle)
    8. 237 , useCenter(useCenter)
    9. 238 , paint(paint) {}
    10. 239 SkRect oval;
    11. 240 SkScalar startAngle;
    12. 241 SkScalar sweepAngle;
    13. 242 bool useCenter;
    14. 243 SkPaint paint;
    15. 244 void draw(SkCanvas* c, const SkMatrix&) const {
    16. 245 c->drawArc(oval, startAngle, sweepAngle, useCenter, paint);
    17. 246 }
    18. 247 };

    上面提到说RenderNode会保存Op的数据和操作信息: 

    1. void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
    2. 218 if (mDamageGenerationId == info.damageGenerationId) {
    3. 219 // We hit the same node a second time in the same tree. We don't know the minimal
    4. 220 // damage rect anymore, so just push the biggest we can onto our parent's transform
    5. 221 // We push directly onto parent in case we are clipped to bounds but have moved position.
    6. 222 info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
    7. 223 }
    8. 224 info.damageAccumulator->pushTransform(this);
    9. 225
    10. 226 if (info.mode == TreeInfo::MODE_FULL) {
    11. 227 pushStagingPropertiesChanges(info);
    12. 228 }
    13. 229
    14. 230 if (!mProperties.getAllowForceDark()) {
    15. 231 info.disableForceDark++;
    16. 232 }
    17. 233 if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
    18. 234 info.stretchEffectCount++;
    19. 235 }
    20. 236
    21. 237 uint32_t animatorDirtyMask = 0;
    22. 238 if (CC_LIKELY(info.runAnimations)) {
    23. 239 animatorDirtyMask = mAnimatorManager.animate(info);
    24. 240 }
    25. 241
    26. 242 bool willHaveFunctor = false;
    27. 243 if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
    28. 244 willHaveFunctor = mStagingDisplayList.hasFunctor();
    29. 245 } else if (mDisplayList) {
    30. 246 willHaveFunctor = mDisplayList.hasFunctor();
    31. 247 }
    32. 248 bool childFunctorsNeedLayer =
    33. 249 mProperties.prepareForFunctorPresence(willHaveFunctor, functorsNeedLayer);
    34. 250
    35. 251 if (CC_UNLIKELY(mPositionListener.get())) {
    36. 252 mPositionListener->onPositionUpdated(*this, info);
    37. 253 }
    38. 254
    39. 255 prepareLayer(info, animatorDirtyMask);
    40. 256 if (info.mode == TreeInfo::MODE_FULL) {
    41. 257 pushStagingDisplayListChanges(observer, info);
    42. 258 }
    43. 259
    44. 260 if (mDisplayList) {
    45. 261 info.out.hasFunctors |= mDisplayList.hasFunctor();
    46. 262 mHasHolePunches = mDisplayList.hasHolePunches();
    47. 263 bool isDirty = mDisplayList.prepareListAndChildren(
    48. 264 observer, info, childFunctorsNeedLayer,
    49. 265 [this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
    50. 266 bool functorsNeedLayer) {
    51. 267 child->prepareTreeImpl(observer, info, functorsNeedLayer);
    52. 268 mHasHolePunches |= child->hasHolePunches();
    53. 269 });
    54. 270 if (isDirty) {
    55. 271 damageSelf(info);
    56. 272 }
    57. 273 } else {
    58. 274 mHasHolePunches = false;
    59. 275 }
    60. 276 pushLayerUpdate(info);
    61. 277
    62. 278 if (!mProperties.getAllowForceDark()) {
    63. 279 info.disableForceDark--;
    64. 280 }
    65. 281 if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
    66. 282 info.stretchEffectCount--;
    67. 283 }
    68. 284 info.damageAccumulator->popTransform();
    69. 285 }
    1. void RenderNode::pushLayerUpdate(TreeInfo& info) {
    2. 174 #ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers
    3. 175 LayerType layerType = properties().effectiveLayerType();
    4. 176 // If we are not a layer OR we cannot be rendered (eg, view was detached)
    5. 177 // we need to destroy any Layers we may have had previously
    6. 178 if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
    7. 179 CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
    8. 180 CC_UNLIKELY(!properties().fitsOnLayer())) {
    9. 181 if (CC_UNLIKELY(hasLayer())) {
    10. 182 this->setLayerSurface(nullptr);
    11. 183 }
    12. 184 return;
    13. 185 }
    14. 186
    15. 187 if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) {
    16. 188 damageSelf(info);
    17. 189 }
    18. 190
    19. 191 if (!hasLayer()) {
    20. 192 return;
    21. 193 }
    22. 194
    23. 195 SkRect dirty;
    24. 196 info.damageAccumulator->peekAtDirty(&dirty);
    25. 197 info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
    26. 198 if (!dirty.isEmpty()) {
    27. 199 mStretchMask.markDirty();
    28. 200 }
    29. 201
    30. 202 // There might be prefetched layers that need to be accounted for.
    31. 203 // That might be us, so tell CanvasContext that this layer is in the
    32. 204 // tree and should not be destroyed.
    33. 205 info.canvasContext.markLayerInUse(this);
    34. 206 #endif
    35. 207 }

     核心流程里绘制的Ops都放在mDisplayList中,这边会去递归的调用每个RenderNode的prepareTreeImpl。

    pushLayerUpdate,将要更新的RenderNode都加到TreeInfo的layerUpdateQueue中,还有其对应的damage大小。

    累加器的popTransform,就是将该Node的DirtyStack生效,收集好DisplayList后就是绘制了。

  • 相关阅读:
    汽车4G车载TBOX智能信息终端
    浏览器打开JupyterLab后所有快捷键与窗口按键均失效怎么办?
    xavier china server
    笔记网站测试报告
    【测试人生】浅谈游戏策划配置表检查的技术设计
    22. 【Linux教程】Linux 结束进程
    3、flex弹性盒布局(flex:1?、水平垂直居中、三栏布局)
    SAS基本统计分析语句
    学生用什么台灯最好?分享专业的学生护眼台灯
    DHCP自动分配IP原理
  • 原文地址:https://blog.csdn.net/jh1988abc/article/details/126102860