【HDR学习】TextureView绘制流程分析(四)

  • 概述

  • TextureView优缺点

越来越多的应用需要使用自己的绘制引擎进行复杂内容的绘制,比如需要使用 GL 绘制 3D 的内容,或者绘制复杂的文档,图表时不希望阻塞 UI 线程,或者部分内容是通过类似 Flutter 这样的第三方 UI Toolkit 进行绘制。通常这部分内容会通过 SurfaceView 或者 TextureView 呈现在 UI 界面上。

一般来说 SurfaceView 能够提供更好的性能,但是因为 SurfaceView 本身的输出不是通过 Android 的 UI Renderer(HWUI),而是直接走系统的窗口合成器 SurfaceFlinger,所以无法实现对普通 View 的完全兼容。包括不支持 transform 动画,不支持半透明混合,移动,大小改变,隐藏/显示等时机会出现各种瑕疵等等,总的来说 SurfaceView 只适用于有限的场景。

TextureView 正是为了解决 SurfaceView 这些的问题而诞生,在使用上基本可以无缝替换 SurfaceView,并且因为 TextureView 跟普通 View 一样是通过 UI Renderer 绘制到当前 Activity 的窗口上,所以它跟普通 View 基本上是完全兼容的,不存在 SurfaceView 的种种问题。但同时正是因为 TextureView 需要通过 UI Renderer 输出,也导致了新的问题的出现。除了性能比较 SurfaceView 会有明显下降外(低端机,高 GPU 负荷场景可能存在 15% 左右的帧率下降),另外因为需要在三个线程之间进行写读同步(包括 CPU 和 GPU 的同步),当同步失调的时候,比较容易出现掉帧或者吞帧导致的卡顿和抖动现象

  • 绘制流程

  • TextureView 绘制和输出

TextureView 的绘制和输出通常会涉及三个线程:

  1. 我们自己创建的用于绘制 TextureView 的线程(当然实际上是绘制 TextureView 创建的 SurfaceTexture,通过 Surface 接口),在这里我们称之为 TextureView Render 线程(实际的名字取决于应用的绘制引擎);
  2. 系统创建的用于操作 View 的 UI 线程,也是应用的主线程;
  3. 系统创建的用于绘制所有 View 的内容到当前 Activity 窗口的 Android Render 线程;

一般情况下,TextureView Render 和 UI 线程都是由 VSync 信号驱动的(Choreographer 的回调),而 Android Render 线程是由 UI 线程驱动的。

  • SurfaceTexture( TextureView Render 线程)

TextureView 本质上是 SurfaceTexture 的 View 封装,而 SurfaceTexture 本质上是一个 Buffer Queue。当我们使用 GL 绘制 SurfaceTexture 时(SurfaceTexture 包装成 Surface 作为当前上下文的 Window Surface):

  1. 新的一帧的第一个 GL Draw Call 会触发一个 Dequeue Buffer 的操作;
  2. 后续的 GL Draw Calls 都绘制到这个 Buffer 上;
  3. 调用 eglSwapBuffers 会触发一个 Queue Buffer 的操作;
  4. Queue Buffer 会导致 SurfaceTexture 发送一个 OnFrameAvailable 的消息回 UI 线程;

  • View 的 UI 线程

先看下 TextureView 输出的流程:

//文件:TextureView.java
private final SurfaceTexture.OnFrameAvailableListener mUpdateListener =
            new SurfaceTexture.OnFrameAvailableListener() {
        @Override
        public void onFrameAvailable(SurfaceTexture surfaceTexture) {
            updateLayer();
            invalidate();
        }
    };

    private void updateLayer() {
        synchronized (mLock) {
            mUpdateLayer = true;
        }
    }

    @Override
    public final void draw(Canvas canvas) {
        ...

        if (canvas.isHardwareAccelerated()) {
            DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;

            TextureLayer layer = getTextureLayer();
            if (layer != null) {
                applyUpdate();
                applyTransformMatrix();

                mLayer.setLayerPaint(mLayerPaint); // ensure layer paint is up to date
                displayListCanvas.drawTextureLayer(layer);
            }
        }
    }
  1. 如上面的 TextureView 代码所示,TextureView 通过回调在 UI 线程收到 SurfaceTexture 的 OnFrameAvailable 消息后,它会先 UpdateLayer 然后再 Invalidate 自己;
  2. UpdateLayer 只是设置一个 mUpdateLayer 的标记,Invalidate 会触发 UI 绘制下一帧
  3. UI 线程在绘制新的一帧的过程,图中的Draw(CPU),实际上就是调用被 Invalidated 的 View 的 draw 方法;
  4. TextureView 的 draw 主要做两件事情,第一是 applyUpdate,applyUpdate 实际上是生成一个 UpdateSurfaceTexture 的任务等待 Android Render 线程执行,第二是生成一个 DrawTextureLayer 的指令到自己的 DisplayList,这里的 TextureLayer 实际上又是对 SurfaceTexture 的封装;
  5. 当 UI 线程的 Draw(CPU) 完成后,意味着更新完所有需要更新的 View 的 DisplayList,此时它会请求 Android Render 线程开始绘制这些 DisplayList;

  • Android Render 线程

  1. 在 Draw (GPU) 的过程中,会先执行 UpdateSurfaceTexture 任务,它实际上是调用 SurfaceTexture 的 UpdateTexImage;
  2. UpdateTexImage 会从 SurfaceTexture 的 Buffer Queue 里面取出之前在 TextureView Render 线程 Queue 的最新绘制的 Buffer,然后绑定到跟 SurfaceTexture 关联的 Texture ID 上,最后释放旧的 Buffer 回 Queue;
  3. 接下来就是生成每个 DisplayList 里面的绘制指令对应的 GL 指令进行绘制;
  4. DrawTextureLayer 指令其实就是 TextureLayer 关联的 SurfaceTexture 的绘制,使用跟 SurfaceTexture 关联的 Texture ID 作为纹理绘制一个多边形,通过这种方式将 2 中的 Buffer 输出到当前 Activity 的 Window 上;

上面就是完整的 TextureView 一帧的绘制和输出的全流程,需要经过三个不同的线程。从这个流程我们可以看到 TextureView 除了增加了额外的 GPU 开销导致性能低于 SurfaceView 以外,因为绘制完成的 Buffer 需要经过 Android 的 UI Renderer 输出,所以也增加了一个 VSync 周期的输出延迟,对游戏来说,输出延迟的增加通常是不可接受的,当然正常情况下,游戏不需要也不会使用 TextureView。但是对一般应用来说,输出延迟的增加也不见得完全是坏事,一来一般应用对输出延迟要求没那么高,二来虽然增加了输出延迟,但是这也意味着渲染流水线更长了,而更长的流水线意味着更高的吞吐量和宽容度,让我们有机会可以让帧率更平稳

  • 代码流程梳理

  • draw()的代码流程(UI线程)

  • isHardwareAccelerated

TextureView想要可以正常绘制,当前Activity必须要打开硬件渲染。因为这里面必须使用硬件渲染独有的Canvas进行绘制。否则标志位没进来,TextureView跳过draw的方法就是背景色。

  • getTextureLayer

初始化好TextureView的绘制环境。让TextureView持用SurfaceTexture对象,SurfaceTexture持有了图元消费者GLConsumer

1)createTextureLayer:生成一个TextureLayer。

会通过CanvasContext.createTextureLayer成功创建DeferredLayerUpdater后返回

//  /frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
    return mRenderPipeline->createTextureLayer();
}

这里使用了一个pipeline进行TextureLayer的创建。这个pipeline一般是在硬件模式开启下使用的。Android在硬件渲染上为了更好的兼容Skia,OpenGL,vulkan,在初始化ThreadRenderer的时候,会根据你在系统中设置的标志,从而打开对应的硬件渲染,如下:

CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
                                     RenderNode* rootRenderNode, IContextFactory* contextFactory) {
    auto renderType = Properties::getRenderPipelineType();

    switch (renderType) {
        case RenderPipelineType::OpenGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<OpenGLPipeline>(thread));
        case RenderPipelineType::SkiaGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
        case RenderPipelineType::SkiaVulkan:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                     std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
        default:
            LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
            break;
    }
    return nullptr;
}

能看到在硬件渲染模式中,每一种诞生的CanvasContext都会伴随对应类型的管道Pipeline。这种设计十分常见,就是一个工厂模式。SkiaGL和vulkan是比较新鲜的语言。Android P默认是使用SkiaOpenGLPipeline,而Android O是OpenGLPipeline。

//文件:/frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
static Layer* createLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight,
                          sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend) {
    GlLayer* layer =
            new GlLayer(renderState, layerWidth, layerHeight, colorFilter, alpha, mode, blend);
    layer->generateTexture();
    return layer;
}

DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() {
    mEglManager.initialize();
    return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::OpenGL);
}

在这里,我们就能看到诞生的TextureLayer为什么名字是DeferredLayerUpdater(延时Layer的更新者)。因为真正工作的对象其实是上面的GlLayer。

2)new SurfaceTexture():创建SurfaceTexture。

【createBufferQueue】

首先通过createBufferQueue初始化了两个极其重要的角色:

  • IGraphicBufferProducer 图元生产者
  • IGraphicBufferConsumer 图元消费者

接着,使用初始化好的图元消费者,在SurfaceTexture在native层中初始化了一个极其重要的角色GLConsumer。当然在SurfaceTexture这里面图元生产者和图元消费者要区别于SF进程的图元生产者和图元消费者。

【setFrameAvailableListener】???

从SF系列可以得知,当Surface调用了queueBuffer之后,将会调用setFrameAvailableListener注册的监听

接下来这个方法则是把native层对应JNISurfaceTextureContext 监听设置到Java层中的对象。每当有图元通过queueBuffer把图元传递进来则会调用如下方法:

void JNISurfaceTextureContext::onFrameAvailable(const BufferItem& /* item */)
{
    bool needsDetach = false;
    JNIEnv* env = getJNIEnv(&needsDetach);
    if (env != NULL) {
        env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
    } else {
        ALOGW("onFrameAvailable event will not posted");
    }
    if (needsDetach) {
        detachJNI();
    }
}

这个方法本质上是反射调用SurfaceTexture.java中的postEventFromNative方法:

    //SurfaceTexture.java
   private static void postEventFromNative(WeakReference<SurfaceTexture> weakSelf) {
        SurfaceTexture st = weakSelf.get();
        if (st != null) {
            Handler handler = st.mOnFrameAvailableHandler;
            if (handler != null) {
                handler.sendEmptyMessage(0);
            }
        }
    }

通过注册在SurfaceTexture中的handler发送消息,进行FrameAvailable的回调。

  • 问题:为什么说TextureView比起SurfaceView从性能上总是慢上几帧呢?

一般的View的draw流程走的顺序是:先从ViewRootImpl诞生一个Canvas,把这个Canvas不断的向下传递,每一个View在自己的draw方法绘制对应的内容在Canvas上,当遍历好整个View tree,ViewRootImpl将会调用unlockCanvasAndPost发送到SF进程。

TextureView的draw流程走的顺序是:TextureView本身内置了lockCanvas以及unlockCanvasAndPost的方法直接通信到SF中。只要我们在SurfaceTexture.OnFrameAvailableListener回调中进行调用。回调过程中,第一个Looper,是来自Handler的消息Looper(上面postEventFromNative提及到的),第二个Looper是来自RenderThread的入队处理,下文会提到。因此延时2次,比起外部的ViewRootImpl和SurfaceView延时1-3帧的结果也是靠谱的。

3)nCreateNativeWindow(mSurface):把SurfaceTexture的GraphicBufferProducer设置到Surface中。并把Surface的地址保存在TextureView中。

//文件:/frameworks/base/core/java/android/view/TextureView.java
private long mNativeWindow;

4)setDefaultBufferSize:设置SurfaceTexture的宽高

5)setOnFrameAvailableListener: 监听SurfaceTexture的回调。

  • applyUpdate

为TextureLayer更新做准备。

prepare():prepare其实就是给DeferredLayerUpdater设置是否开启透明以及DeferredLayerUpdater的绘制范围。换句话说,就是TextureView绘制的宽高保存到DeferredLayerUpdater中。

updateSurfaceTexture():  会调用DeferredLayerUpdater的updateTexImage。打开了刷新的标志位。

onSurfaceTextureUpdated:  调用onSurfaceTextureUpdated触发第二个生命周期SurfaceTexture更新

  • applyTransformMatrix

 TextureLayer保存变换矩阵

setTransform():把Matrix保存在DeferredLayerUpdater。

pushLayerUpdate():在这里就把DeferredLayerUpdater保存到DrawFrameTask中,等待ViewRootImpl后续流程统一把DrawFrameTask中保存的内容进行绘制。

  • setLayerPaint

设置了Paint的画笔。

  • drawTextureLayer

drawTextureLayer的代码流程:

1)RecordingCanvas  drawTextureLayer

是硬件渲染的用的Canvas。通过这个Canvas调用native层进行渲染。硬件渲染的流程可以参考下面【硬件加速时序】,和SkiaCanvas一样的原理,只是画像素的时候从CPU合成转移到GPU等硬件合成。

在native层,也是根据pipe的类型生成对应不同的硬件渲染Canvas,在这里我们挑选默认的SkiaRecordingCanvas来聊聊。

2)SkiaRecordingCanvas  drawLayer

此时会使用一个智能指针包裹LayerDrawable。LayerDrawable则会持有DeferredLayerUpdater。drawDrawable绘制LayerDrawable中的内容。SkiaRecordingCanvas继承于SkiaCanvas。

3)LayerDrawable  onDraw

会从DeferredLayerUpdater 获取Layer对象,而这个Layer对象就是通过DeferredLayerUpdater保存的函数指针生成的GLLayer。

TextureView代码流程讲解

  • lockCanvas (UI线程) 

文件:frameworks/base/core/java/android/view/TextureView.java

    public Canvas lockCanvas() {
        return lockCanvas(null);
    }

    public Canvas lockCanvas(Rect dirty) {
        if (!isAvailable()) return null;

        if (mCanvas == null) {
            mCanvas = new Canvas();
        }

        synchronized (mNativeWindowLock) {
            if (!nLockCanvas(mNativeWindow, mCanvas, dirty)) {
                return null;
            }
        }
        mSaveCount = mCanvas.save();

        return mCanvas;
    }

这里的mNativeWindow在上文有提到过。就是android_view_TextureView_createNativeWindow方法初始化Window的时候生成的一个临时的Surface,不过只是保存在native层而已。

这里也要注意一下,lockCanvas的时候,发现mCanvas为空就会生成一个新的Canvas对象。换句话说,TextureView在调用lockCanvas的时候,实际上是使用自己的Canvas,自己的Surface。从设计上和SurfaceView几乎一致。

接下来的native层逻辑就和SurfaceView中的一致。

  • unlockCanvasAndPost (UI线程)

    public void unlockCanvasAndPost(Canvas canvas) {
        if (mCanvas != null && canvas == mCanvas) {
            canvas.restoreToCount(mSaveCount);
            mSaveCount = 0;

            synchronized (mNativeWindowLock) {
                nUnlockCanvasAndPost(mNativeWindow, mCanvas);
            }
        }
    }

同理,这里面就要把textureView自己的Surface中NativeWindow承载的内容发送到SF进程,并且刷新mCanvas中的SkBitmap内容。

  • 硬件加速时序(渲染线程)

硬件加速的时序从java层到native层,整个时序大概如下:

1. 网上参考

【流程解说】

1)ViewRootImpl 调用到ThreadedRenderer渲染器,ThreadedRenderer在主线程调用updateRootDisplayList(view,callbacks)完成显示列表 DisplayList的更新。DisplayList的更新是从ThreadedRenderer中mRootNode调用beginRecording(..)开始的,mRootNode就是ViewRootImpl 调用setView() 设置的根布局的RenderNode。RecordingCanvas用于记录暂存的显示列表DisplayList。

2)显示列表更新后需要同步和绘制显示列表,通过jni调用到native层的RenderProxy,

3)  接着调用DrawFrameTask的postAndWait()提交一个绘制帧任务到渲染线程RenderThread.

4)  在DrawFrameTask的run方法中会调用到CanvasContext.cpp进行显示列表的同步和绘制.

【相关类介绍】

1)ThreadedRenderer.java

渲染器, 作用是通过 native层的 android_view_ThreadedRenderer.cpp 创建一个RenderProxy 代理渲染线程, 管理渲染线程RenderThread 中的native层上下文(CanvasContext.cpp)。

API 29 开始ThreadedRenderer中部分代码移动到HardwareRenderer,但整体变化不大。

2)RenderNode

View 树和RenderNode树的对应关系如图, :

视图层的一个View对应硬件加速渲染的一个RenderNode;

java层的renderNode持有native层的renderNode的指针;

native层的renderNode 包含一个displayList和其他相关属性

displayList记录了每个View在硬件加速下的绘制步骤;在native层一个绘制步骤用一个RecordedOp表示。

View对应的硬件加速绘制步骤的记录从java层的RenderNode.java开始, start()和end()分表表示一个view的硬件加速绘制过程记录的开始和结束,API 29后start()和end()被beginRecording() 和endRecording()替代。

3) RecordingCanvas

RecordingCanvas是用来记录硬件加速绘制动作的画布。

API 28后,java层的 RecordingCanvas最终继承于BaseCanvas0,BaseRecordingCanvas中对BaseCanvas进行了扩展。硬件加速过程中,某个View对应native层的RecordingCanvas/SkiaRecordingCanvas的作用就是用DisplayList记录下硬件加速的绘制过程,然后把记录完成的DisplayList返回给该view的RenderNode,相当于充当一个临时的画布容器记录绘制过程。

4)DisplayList

显示列表DisplayList存在于RecordingCanvas 和 RenderNode中。DisplayList是RenderNode中用于记录View硬件加速绘制步骤的一个容器,记录了自己和 child View硬件加速绘制记录,每一记录用一个RecordedOp(别名是BaseOpType)表示。

5)RecordedOp

RecordedOp表示DisplayList中一个硬件加速的绘制过程。RenderNode和DisplayList和 RecordedOp的关系如下:

6)RenderThread

硬件加速渲染线程,同UI线程一样,全局只有一个实例。显示列表DisplayList的更新在主线程,同步和绘制在渲染线程

7)  CanvasContext

画布上下文,连接全局的EGL上下文和渲染surface,一个Surface对应一个CanvasContext。

Android硬件加速流程和源码分析

2. HMOS3.0

  • TextureView的用法

代码示例:

public class MyActivity extends Activity implements TextureView.SurfaceTextureListener {
      private TextureView mTextureView;
      SurfaceTexture.OnFrameAvailableListener mFrameAvailableListener;

      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);

          mTextureView = new TextureView(this);
          mTextureView.setSurfaceTextureListener(this);

          setContentView(mTextureView);
      }

      public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
          ……
      }

      public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
          // Ignored, Camera does all the work for us
      }

      public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
          ……
          return true;
      }

      public void onSurfaceTextureUpdated(SurfaceTexture surface) {
          // Invoked every time there's a new Camera preview frame
      }
  }

首先通过setSurfaceTextureListener监听TextureView的生命周期:

  • 1.onSurfaceTextureAvailable(TextureView可用).
  • 2.onSurfaceTextureSizeChanged   TextureView大小发生了变化
  • 3.onSurfaceTextureUpdated   TextureView有视图数据进行了更新。
  • 4.onSurfaceTextureDestroyed    TextureView销毁了。
  • 5.setOnFrameAvailableListener   监听TextureView绘制每一帧结束后的回调。

  • 网站汇总

TextureView代码流程讲解

TextureView 源码浅析

Android硬件加速流程和源码分析

TextureView绘制流程讲解

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HDR是高动态范围的缩写,是一种摄影技术,可以在一张照片中捕捉到更广泛的亮度范围。通过合成多张曝光不同的照片,HDR可以在高光和阴影部分都有更多的细节,并产生更加真实和生动的影像效果。在Android手机上,有许多应用程序可以实现HDR拍摄,例如引用中提到的该项目。使用Android手机进行HDR拍摄非常简单,在相机设置中启用HDR模式即可。对于不同的摄像机,HDR处理可能会有所不同,最好是进行尝试和实验,以找到最适合您的摄影效果。同时,需要注意的是,虽然HDR可以是一个强大的工具,但过度使用它也可能导致照片效果不自然。因此,在使用HDR时,需要根据具体情况来决定是否使用以及如何使用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [什么是HDR和如何在Android上使用它 | MOS86](https://blog.csdn.net/weixin_33993891/article/details/117302249)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [android_hdr:适用于安卓的 HDR 应用程序](https://download.csdn.net/download/weixin_42097508/19885525)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值