基于Perfetto 解读一帧的生产消费流程 Android >= S Qualcomm

news2024/10/7 4:27:11

广告

首先帮我朋友打个广告 我们一起在运营一个视频号 感兴趣的可以帮忙点击右边这个小铃铛 铃铛

1.这个流程里面的东西如果展开其实是有很多的 内容其实还是比较浅显的 sf处就不贴源码了 关一个Vsync就有的解释 当然笔者在流程上先形成一个思维闭环
2.如有小伙伴需要 笔者可提供所有原材料供二次编辑

先吐槽
其实我觉得大部分Android开发者都是聚集在上层 java层 或者说的具体点就是业务层 app层 始终没有脱离业务场景
我对应用开发范围的定义是 不限于hal层 c++代码实现层 只要涉及到业务场景的 都是应用开发
随着工作中遇到的一些00后 水平是真的不错 在这里也提醒那些80后90后 快了奥 小心被挤下来 逆水行舟 不进则退 出来混是要还的

本文阐述的预期
1.view的绘制流程 以及 送显到屏幕一整个过程
2.trace的分析方法
3.因为很多看似一点思路都没有的问题 其实是基础不够牢靠 希望笔者接下来的阐述 前期可以让大家节省多的熟悉成本

一.从一个view的setText开始

1.1view开始setText

Button btnTraceClick = findViewById(R.id.btn_trace_click);
    btnTraceClick.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Trace.beginSection("super.yu click#btn test");
            btnTraceClick.setText("帅是内在 但骚不是");
            Trace.endSection();
        }
});

可以看到2处 是加上去的trace setText就从这里开始 是会走下去请求vsync-app 即app主线程有更新ui的请求 但此时没有往下走 因为1处已经有一个requestNextVsync vsync-app的请求 等待sf进程回调上来 Choreographer#onVsync 告诉app可以doFrame 此时才会绘制 4处是线程运行状态

如果长时间的runnable或runnable preempted或running状态 60帧 超过16.6ms 那就可以看做是一个卡顿或掉帧 优化的思路可以是此处cpu有哪个进程运行时间较长 app线程得不到调度 负载较高 找对应模块的人分析 或修改优先级 等 如果是system_server例如binder 锁竞争 耗时 则要通过阅读源码去定位 或 app自身是否存在主线程耗时 出现诸如 下述log 考虑是否mainthread有耗时操作 ui结构过于复杂等等 思路不仅限于此 在Perfetto可以很直观的看出来

I/Choreographer: Skipped 196 frames! The application may be doing too much work on its main thread. 

在这里插入图片描述
分别对应2和3处

此时由于已经在1处requestNextVsync vsync-app请求 在2更新ui就不会往下走 所以只会有句scheduleTraversals 所以3处的onVsync回调其实是上一次ui更新请求的 所以ui的请求一直到屏幕显示至少得在第二个vsync信号到来

在这里插入图片描述
在这里插入图片描述
我们从这里的vsync请求往下赘述也是对应1处
在这里插入图片描述

/frameworks/base/core/java/android/view/ViewRootImpl.java#scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
    mTraversalScheduled = true;
    // 发送一个屏障信号 下次loop来 doFrame
    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
    // 编舞者 post发送请求
    mChoreographer.postCallback(
    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    ...
/frameworks/base/core/java/android/view/Choreographer.java#postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
              Object action, Object token, long delayMillis) {
    ...
    synchronized (mLock) {
    final long now = SystemClock.uptimeMillis();
    final long dueTime = now + delayMillis;
    mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
    // dueTime 肯定是大于或等于now 所以除了首次一个loop会直接走这里 其他情况会走下述的msg
    if (dueTime <= now) {
        scheduleFrameLocked(now);
    } else {
        Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
        msg.arg1 = callbackType;
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, dueTime);
    }
   ...
|
void doScheduleCallback(int callbackType) {
    synchronized (mLock) {
        if (!mFrameScheduled) {
            final long now = SystemClock.uptimeMillis();
            // 此处 肯定是在Choreographer注册了一个回调 我们先不关注他 这个回调就是后面3Choreographer#onVsync 也就是 vsync-app
            if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                scheduleFrameLocked(now);
   ...
|
private void scheduleVsyncLocked() {
    try {
        // 这里的trace会和图上一一对上
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
        mDisplayEventReceiver.scheduleVsync();
    } 
   ...
中间省略一步hal也就是DisplayEventDispatcher.java
/frameworks/native/libs/gui/DisplayEventDispatcher.cpp#scheduleVsync
status_t DisplayEventDispatcher::scheduleVsync() {
    if (!mWaitingForVsync) {
        ALOGV("dispatcher %p ~ Scheduling vsync.", this);
   ...
          if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {
              ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", this,
                    ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
          }
          // app层开始请求vsync 此处开启binder请求
          status_t status = mReceiver.requestNextVsync();



   ...

一步一步从java层 到c++ vsync-app 整个流程还是比较长的 大家可以看出 其实Java就是个壳子 他可以是kotlin可以是js也可以是Flutter 因为硬件屏幕刷新是固定 不需要每次都去校对硬件vsync当前是会否可以绘制 所以模拟一个软件的信号源 在这里把代码贴出来 可以看一下 其实就是代码流程 当然怎么去注册的 怎么回调的我们在此就不去深究了

此时3处回调vsync-app 也就是Choreographer#onVsync app就开始绘制了 其实绘制本质就是在组织结构体 组织绘制的命令 在这里我们留一个后续探究的点 也就是一个canvas gui的本质是什么 我们都知道java层的bitmap的draw api的调用方式 但java就是个壳子 真正绘制不是在这里 相当于aidl绑定两个connection就可以通信 但实质是native指向server/client端一侧的地址 从而实现进程通信的场景 所以 到底是gpu合成还是hwc合成 是有一个判断依据的 对于高刷的场景 例如游戏 都是hwc合成 简单的场景 走的是gpu合成 否则 gpu负载太高 功耗就会有增加 也是终端项目中需要check的地方

frameworks/native/libs/gui/DisplayEventReceiver.cpp
status_t DisplayEventReceiver::requestNextVsync() {
    if (mEventConnection != nullptr) {
        mEventConnection->requestNextVsync();
        return NO_ERROR;
    }
    return mInitError.has_value() ? mInitError.value() : NO_INIT;
}

// EventThread这是软件模拟硬件vsync 后续会讲到
frameworks/native/services/surfaceflinger/Scheduler/EventThread.cpp
binder::Status EventThreadConnection::requestNextVsync() {
    ATRACE_CALL();
    mEventThread->requestNextVsync(this);
    return binder::Status::ok();
}

接下来再来个图
在这里插入图片描述
大家要注意的是 我们目前为止setText的渲染其实是1处右边那代码块 此处还是之前的ui更新操作

1处ui线程ui就开始绘制了 值得注意的是 1处如果draw时长超过16.6ms那么大概率就是应用本身阻塞主线程 我们把trace堆栈放大
在这里插入图片描述
这一步我理解是遍历 比如animation input啊 有哪些 measure layout丈量等 是把ui结构进行数据化 比如这个view的坐标 color等

这里是引用一篇博客里面的解释 但我的理解就是 为了后续的遍历而去组织数据结构 分门别类
Choreographer.doFrame 计算掉帧逻辑
Choreographer.doFrame 处理 Choreographer 的第一个 callback : input
Choreographer.doFrame 处理 Choreographer 的第二个 callback : animation
Choreographer.doFrame 处理 Choreographer 的第三个 callback : insets animation
Choreographer.doFrame 处理 Choreographer 的第四个 callback : traversal

setRefreshRateIfNeed这个应该是手机厂家提供出的接口 不管 traversal 他就是遍历 draw 就是 draw 着重介绍一下postAndWait 这里就会到RenderThread 应用的渲染线程 postAndWait唤醒线程的run 此时我们进入到应用的RenderThread 拓展一下 如果是游戏进程的话 一般是unitymain gfx线程 flutter为什么会比rn要快 因为他直接和sf打交道 不需要再转换一层 想了解的可以看看官方的架构图 流程继续 这里要注意的是 这里的执行顺序是从左往右 单独模块从上到下执行 然后再回到起始点 往右执行

// 代码太多 不一一解释 这里就是把一个frame组织成一个结构体 cpu/gpu可读懂的结构体
frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
void DrawFrameTask::postAndWait() {
    ATRACE_CALL();
    AutoMutex _lock(mLock);
    mRenderThread->queue().post([this]() { run(); });
    mSignal.wait(mLock);
}
// 从这里我们就可以看到我们熟悉的canvas 当然真正的渲染不是在java进行的
// dequeueBufferDuration 这里有个queue buffer的轮转 我们后续分析
void DrawFrameTask::run() {
    const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
    ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);
   ...
    // Grab a copy of everything we need
    CanvasContext* context = mContext;
    nsecs_t dequeueBufferDuration = 0;
    if (CC_LIKELY(canDrawThisFrame)) {
        dequeueBufferDuration = context->draw();
    } else {
       ...
...
}

这里postAndWait后会到2处 也就是自身的渲染线程了 但是2处就是渲染个寂寞 真正渲染的地方是在3处 我们把2处放大一下
在这里插入图片描述
我们现在到应用出帧的地方 也就是renderthread 可以看出 DrawFrames 66363537 和上述1处 Choreographer#doFrame 66363537 id是一样的 但是没有进行渲染 是cpu在执行其他线程 没有得到调度 是因为该进程中的一个线程在初始化 有一定的负载
在这里插入图片描述
dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 此处MainActivity应该是Producter才对 不应该是Consumer 在这里我们需要引入两个知识点BufferQueue和GPU Fence

// ********** @引用_start 努比亚技术团队**********

BufferQueue要解决的是生产者和消费者的同步问题 应用程序生产画面 SurfaceFlinger消费画面 SurfaceFlinger生产画面而HWC Service消费画面 用来存储这些画面的存储区我们称其为帧缓冲区buffer

在BufferQueue的设计中 一个buffer的状态有以下几种:

FREE:表示该buffer可以给到应用程序 由应用程序来绘画

DEQUEUED:表示该buffer的控制权已经给到应用程序侧,这个状态下应用程序可以在上面绘画

QUEUED: 表示该buffer已经由应用程序绘画完成 buffer的控制权已经回到SurfaceFlinger手上

ACQUIRED:表示该buffer已经交由HWC Service去合成了 这时控制权已给到HWC Service

FREE->DEQUEUED->QUEUED->ACQUIRED->FREE

CPU和GPU的工作完全是异步的 Fence提供了一种方式来处理不同硬件对共享资源的访问控制

// ********** @引用_end 努比亚技术团队**********
在这里插入图片描述
其实真正渲染的地方是在3处 我们放大一下 渲染线程中我们只需要重点了解dequeuebuffer和queuebuffer
在这里插入图片描述
此时 应用的renderThread从自身的bufferqueue申请一块buffer用来绘制 需要注意的是从R之后为了分担sf的压力 bufferqueue都在各自应用进程里进行 所以dequeuebuffer此处没有binder调用dequeueBuffer 拿一块buffer的地址下标 也就是往结构体填充指令的数组 下图放大

在这里插入图片描述

/frameworks/native/libs/gui/BufferQueueProducer.cpp
status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* outFence,
                                              uint32_t width, uint32_t height, PixelFormat format,
                                              uint64_t usage, uint64_t* outBufferAge,
                                              FrameEventHistoryDelta* outTimestamps) {
      ATRACE_CALL();
      { // Autolock scope
          std::lock_guard<std::mutex> lock(mCore->mMutex);
          // trace中 dequeueBuffer - VRI[MainActivity]#0(BLAST Consumer)0 也是此处的拼接
          mConsumerName = mCore->mConsumerName;
...

VRI[MainActivity]#0(BLAST Consumer)0: 0 这里的solt是0

在dequeuebuffer 右边还有一句 HWC release fence 19 has signaled 这里dequeuebuffer后这个solt地址并不是立即就往上填充数据 是要等待 GPU释放对应的Fence 只是告诉你我要释放了 相当于bt模组和modem 你请求查询数据 然后模组告诉你有数据了 然后你还得调用个get请求去获取这些数据

接下来就是queuebuffer部分 图片部分放大

在这里插入图片描述

// 表示hwc release fence 19 buffer 还给了bufferqueue 但gpu还没有绘制完
Trace GPU completion fence 19

// 将绘制好的buffer返回Surfacefinger
eglSwapBuffersWithDamageKHR

status_t BufferQueueProducer::queueBuffer(int slot,
          const QueueBufferInput &input, QueueBufferOutput *output) {
      ATRACE_CALL();
      ATRACE_BUFFER_INDEX(slot);
  
      int64_t requestedPresentTimestamp;
      bool isAutoTimestamp;
      android_dataspace dataSpace;
      Rect crop(Rect::EMPTY_RECT);
      int scalingMode;
      ...
      if (frameAvailableListener != nullptr) {
              // 按照需要回调至app层
              frameAvailableListener->onFrameAvailable(item);
              ...
      ...

此时就会到SurfaceFlinger进程 值得注意的是 BufferQueue 可以看BLASTBufferQueue

在这里插入图片描述
在这里插入图片描述
waiting for presentFence 699 可以看出kernel消费情况
在这里插入图片描述
所以 setText 开始到显示 最终是这样的
在这里插入图片描述
其实整体内容还是比较浅显的 sf这块还是比较复杂的 设计的场景有点多 后续也只能找一个闭环去用贴源码解释

感谢观看

参考文献


  1. https://blog.csdn.net/rzleilei/article/details/94639329
  2. 作者:努比亚技术团队
    链接:https://www.jianshu.com/p/3c61375cc15b
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1686923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

备战电赛 | 集创赛提前练 • 第三周

集创赛提前练 | 第三周 &#xff08;点击下面的题目简介即可进入答题界面哦。&#xff09; 题目一 题目难度&#xff1a;中等 题目二&#xff1a;请设计一个可以实现任意小数分频的时钟分频器&#xff0c;例如&#xff0c;8.7分频的时钟信号 题目难度&#xff1a;简单 题目三…

position: absolute对el-dialog的影响

当用到position: absolute,会使元素脱离文档流,从而对原始层级发生变化,导致蒙层无法消失.

酒店线上提前预定小程序源码系统 优惠卷+现实秒杀功能 附带完整的安装代码包以及搭建教程

系统概述 酒店线上提前预定小程序源码系统是一款专门为酒店行业打造的智能化预订平台。它通过整合酒店的房源信息、价格策略、预订流程等&#xff0c;为用户提供了一站式的预订服务。该系统采用先进的技术架构&#xff0c;具有高度的稳定性和安全性&#xff0c;能够满足酒店和…

CentOS 7安装prometheus

说明&#xff1a;本文介绍如何在CentOS操作系统上安装prometheus Step1&#xff1a;下载安装包 访问Github仓库&#xff0c;下载对应版本的prometheus安装包 https://github.com/prometheus/prometheus/releases 操作系统的版本信息&#xff0c;可通过下面这两个命令查看&am…

六种常用设计模式

单例设计模式 单例模式指在整个系统生命周期里&#xff0c;保证一个类只能产生一个实例&#xff0c;确保该类的唯一性。 单例模式分类 单例模式可以分为懒汉式和饿汉式&#xff0c;两者之间的区别在于创建实例的时间不同&#xff1a; 懒汉式&#xff1a;指系统运行中&#…

大语言模型是通用人工智能的实现路径吗?【文末有福利】

相关说明 这篇文章的大部分内容参考自我的新书《解构大语言模型&#xff1a;从线性回归到通用人工智能》&#xff0c;欢迎有兴趣的读者多多支持。 关于大语言模型的内容&#xff0c;推荐参考这个专栏。 内容大纲 相关说明一、哲学与人工智能二、内容简介三、书籍简介与福利粉…

飞睿智能超宽带UWB标签模组,简化设备开发流程,实时高速率数传交互应用

在科技飞速发展的今天&#xff0c;UWB超宽带技术因其高精度、低功耗和高安全性的特点&#xff0c;正逐渐成为智能设备定位和数据传输的新宠。 UWB技术是一种无线通信技术&#xff0c;它通过使用非常宽的频带进行数据传输&#xff0c;从而实现高数据传输速率和高精度定位。 飞…

「51媒体」线下活动媒体同步直播,云分发,分流直播

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 线下活动除了邀请嘉宾&#xff0c;邀请媒体&#xff0c;邀请行业大咖KOL&#xff0c;来为活动站台&#xff0c;背书外&#xff0c;我们也可以将线下的活动同步在线上进行直播&#xff0c…

构建 Elastic Cloud Serverless

作者&#xff1a;来自 Elastic Jason Tedor Elastic Cloud Serverless 架构 2022 年 10 月&#xff0c;我们引入了 Elasticsearch 的无状态架构。 我们该计划的主要目标是发展 Elasticsearch&#xff0c;以利用云原生服务提供的操作、性能和成本效率。 该计划成为我们最近宣…

解决Ubuntu无法使用root登录的问题

1、登录普通用户 2、使用vi编辑器打开/etc/pam.d/gdm-autologin并注释掉auth required pam_succeed_if.so user ! root quiet_success 3、使用vi编辑器打开/etc/pam.d/gdm-password并注释掉auth required pam_succeed_if.so user ! root quiet_success 4、注销用户重新用roo…

【Linux安全】Firewalld防火墙

目录 一.Firewalld概述 二.Firewalld和iptables的关系 1.firewalld和iptables的联系 2.firewalld和iptables的区别 三.Firewalld区域 1.概念 2.九个区域 3.区域介绍 4.Firewalld数据处理流程 四.Firewalld-cmd命令行操作 1.查看 2.增加 3.删除 4.修改 五.Firewa…

最新FinalShell专业版激活

支持的版本 可以激活任意版本的FinalShell为专业版&#xff0c;包括最新版4.3.10 激活方式 打开FinalShell&#xff0c;点击左下角 激活/升级。 账号密码任意输入几个字符&#xff0c;点离线激活。 复制机器码&#xff0c;将机器码发送给微信公众号【小白学算法】,即可获…

数据结构学习/复习15--排序部分复习总结

一、学过的排序 1.插入排序 2.希尔排序 3.直接选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 8.计数排序 二、各项排序的思想及改进(无特殊说明均以升序为例) 1.插入排序及其改进希尔排序 &#xff08;1&#xff09;插入排序的思想及具体操作细节 将一个数字按顺…

pytorch使用gpu训练模型

前言 仅记录学习过程&#xff0c;有问题欢迎讨论 因为网上博客参差不齐&#xff0c;我也踩了很多坑&#xff0c;留下我自己成功的经验哈哈。 1.安装CUDA和CUDNN 参考这个博客&#xff1a; https://blog.csdn.net/shdabai/article/details/131248257 2.安装gpu版本 pytorch …

【openlayers系统学习】3.6-3.7添加可视化选择器,手动选择可视化的图像源

六、添加可视化选择器&#xff08;选择可视化的图像类型&#xff09; 在前面的示例中&#xff0c;我们已经看到了同一Sentinel-2图像的真彩色合成、假彩色合成和NDVI渲染。如果能让用户从这些可视化中选择一个或更多&#xff0c;而不必每次都更改我们的代码&#xff0c;那就太…

【热门话题】一文带你读懂公司是如何知道张三在脉脉上发了“一句话”的

按理说呢&#xff0c;A公司和脉脉属于不同的平台&#xff0c;而且脉脉上大家可以匿名发言&#xff0c;所以&#xff0c;即便我坐在你边上&#xff0c;我发了一句话上去&#xff0c;你也不知道是谁发的。但通过一些技术&#xff0c;我们却可以分析出&#xff0c;公司是如何知道张…

Git远程控制

文章目录 1. 创建仓库1.1 Readme1.2 Issue1.3 Pull request 2. 远程仓库克隆3. 推送远程仓库4. 拉取远程仓库5. 配置Git.gitignore配置别名 使用GitHub可以&#xff0c;采用Gitee也行 1. 创建仓库 1.1 Readme Readme文件相当于这个仓库的说明书&#xff0c;gitee会初始化2两份…

docker容器安装nexus3以及nexus3备份迁移仓库数据

一、安装步骤 1.搜索nexus3镜像 docker search nexus3 2.拉取镜像 docker pull sonatype/nexus3或者指定版本 docker pull sonatype/nexus3:3.68.0 3.查看拉取的镜像 docker images | grep "nexus3" 4.启动nexus服务 直接启动 docker run -d --name nexus3 -…

基于Django框架深度学习口罩检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 随着全球疫情的蔓延&#xff0c;口罩成为了重要的防护工具。然而&#xff0c;在实际场景中&am…

MySQL —— 复合查询

一、基本的查询回顾练习 前面两章节整理了许多关于查询用到的语句和关键字&#xff0c;以及MySQL的内置函数&#xff0c;我们先用一些简单的查询练习去回顾之前的知识 1. 前提准备 同样是前面用到的用于测试的表格和数据&#xff0c;一张学生表和三张关于雇员信息表 雇员信息…