站点链接:技术博客页 CSDN版文章链接: https://bbs.csdn.net/ 知乎专栏版文章链接: https://www.zhihu.com/people/suguang
一句话点题:会渲染,不会验证,等于不会渲染
写渲染的人,逃不开三个问题:这一帧画错在哪、这一帧画慢在哪、整个系统卡在哪。前两个问题对着 RenderDoc 的 Event Browser 翻一翻 draw call 就能答;第三个问题 RenderDoc 答不了,它天生只看得见 "这一帧",看不见"这一帧在整条流水线里从哪来、到哪去"。要把"慢"和"卡"从一句现象变成一条结论, 需要另一把尺子,量到进程、线程、帧时间线那一层的 Perfetto。
先把这三个问题的边界分清,因为它们的回答方式完全不同:
- 画错(correctness):答案藏在"这一帧内部的命令与资源"里,跟系统调度无关。你只要有能力把一帧 拆开、逐条重放、逐段看输入,就能定位到具体某一条 draw。这类问题的尺子是 RenderDoc。
- 画慢(performance):答案藏在"这一帧内部的成本分布"里:哪条 draw 吃 GPU、哪条切了管线状态、 哪张贴图带宽爆炸。它仍在帧内,RenderDoc 的性能标注和 Event Browser 能给你热点。
- 卡(responsiveness / jank):答案已经跑到帧外面去了:线程没被调度、等了 vsync、GPU 排队、 合成环节超时。这时候即使把每一帧都画得"又快又对",整体还是卡。必须换 Perfetto 去量系统级。
很多人吃亏就吃在把"卡"当"慢"来查:拿着 RenderDoc 把帧内 draw 翻了个底朝天,一头汗,结果瓶颈根本 不在 GPU 而在那个没醒过来的渲染线程。这就是本系列一直在强调的一句话:定位问题,先归因范围,再 谈手段。范围不对,工具再强都是空转。
这就是本系列的出发点:在图形这条链上,"验证"不是事后的安慰,是整个方法论的底座。这篇文章把 这套"如何验证"的坐标系立起来,后面 12 篇全在这张图上走。我们会先看清楚两把尺子各量什么: RenderDoc 量 API 层的一帧,Perfetto 量系统层的整条路径,再把它们画成一张"帧级 × 系统级"的双轴图, 最后落地"自跑 → 真图 → 归因 → 结论"这条证据链,作为整个系列每一篇都遵守的取证规程。
打个不恰当的比方:一个渲染器的功力,一半在画,一半在验。 很多作者写作时急于展示引擎的第二招、第 三招,却总嫌验证摆不上台面。可你一旦把"画"和"验"摆在一起看就会发现:画得再快、再花哨,如果没有 一套能放手心、能复现、能判对错的验证方法托底,你永远不知道自己画的对不对、快在哪、卡在哪。本系列 偏要把这件"最不起眼却最底座"的事放到第一根坐标轴上立住,理由就一个:不先把验证立稳,后面谈任何调优 都是往沙子上盖楼。先教会你"怎么看准",再谈"怎么改好",这也是为什么 13 篇里打头的是方法论而非某个 炫技技巧。
先给结论再展开:帧级要问"这一帧画得对不对、快不快",系统级要问"这一帧为什么晚到、被谁抢了"。 前者用 RenderDoc,后者用 Perfetto。它们不是同一个东西的两种实现,而是同一张图上两个互相垂直、各管 一摊的轴。读过第三章会看到,把这两个轴交叉起来,会得到一张能把"慢 / 卡 / 错 / 画坏"四类症状各归其 位的决策地图,那是本系列最重要的一张图,也是你以后 debug 任何图形问题都该先在心里过一遍的检阅 表。工程上那句"工欲善其事,必先利其器",在这条链上被翻译成更狠的一句:选错尺子,等于没选。
这句话再往下推一步,就得点破本系列最容易被诟病、也最值得坚持的一点:方法论里没有"随便看看"的位置。 很多图形开发者不是不会调试,而是把调试当成了"装一个工具、点几个按钮、看个大概"。这套坐标系要换的 恰恰是这个习惯:它要求你每一次调试都先回答"我这次的症状在哪个象限",再决定"我该把哪个工具、怎么 用"。按钮是术,坐标系是道。本文先把道立住,接下来的 12 篇,每一篇都只会教你"在一个象限里,把那把尺 子用到底",而不会再回头问"我现在到底该用哪个工具"。把这道工序前置做完,后面每一篇的技术密度你才接 得住。
这里还要预先回答一个你可能会问的质疑:"我为什么不直接看驱动厂商给的面板?" 因为面板给的是"这 一帧在自家硬件上的表现",基于它做的任何归因,都被限制在那块硬件、那类驱动里。本系列要立的东西,是 跨 API、跨硬件、可复现的方法,它可以被 RenderDoc 抓、被 Perfetto 采、被别人照做。面板是"听说这里 有问题",坐标系是"你自己能走到问题跟前再把证据拿出来"。两者的差别,正是第五章会展开的"开源 vs 闭源" 那条不可妥协的红线。
一、第一支柱:在哪一帧上做文章——API 层的 RenderDoc
RenderDoc(Baldur Karlsson 主导,MIT 协议)是一个跑在图形 API 与驱动之间的抓帧与分析器官方文档。它切入
的方式很特别:不碰你的业务代码,而是在 vkQueueSubmit、glDrawElements、ID3D12CommandQueue::
ExecuteCommandLists 这些API 调用点上做拦截。等你把一整批 GPU 命令递过去时,它在中间把它们
原样照下来,存成一份 .rdc 的抓帧文件,然后给你一个能逐条回放、逐条检查的后端。
理解 RenderDoc,第一件要弄清的是"它到底站在哪"。图形调用往下走有一条固定的链:你(App)→ 图形
API → 驱动(driver)→ 显卡。RenderDoc 的注入点就在 App 与驱动之间:它代替驱动先接到你的 API
调用,做一次"旁路":一方面把调用原样转给真驱动去执行(这样画面照常渲染),另一方面把调用、参数、
资源句柄全部记录进抓帧缓冲区。时机上它通常要卡一帧的边界(EndFrame 之类)作为"一帧"的起止,
把这一帧里发生的所有调用封成一个完整单元。这个"截在 API 层"的设计,是本系列能够"自证式验证"的
根基:因为它截的是命令本身,而不是结果的像素RenderDoc 工作机制。
为什么"截在 API 层"这件事值得单开一章?因为截在哪一层,决定它答得了什么、答不了什么。RenderDoc 截下来的,是"提交给 GPU 的命令 + 它依赖的全部资源和管线状态",这意味着它能把这一帧拆开重放:
- 可以只重放第 200 条 draw call,看那一条独立画出来是什么样;
- 可以把某张纹理的内容换成你导出的一份,看是不是这张贴图喂错了导致花屏;
- 可以把某个管线状态的参数改掉,看结果跟不跟(例如把混合关掉,还原"原始颜色");
- 可以把顶点流替换成本地数据,看 mesh 是怎么被装配出来的,甚至导出成 obj 到建模软件里核对。
这是"这一帧画错在哪"的答案来源:它不是靠猜,是靠把证据拉回来看、再对着证据改一改看反应。 图形 dev 里最值钱的能力,就是把一个现象做成"可改、可还原、可对比"的实验,而 RenderDoc 恰恰把这一 整套实验台给你搭好了。
但它也正因为此有明确的边界:RenderDoc 的镜头一旦拉近到"一个帧的内部",就把帧外面的整条节奏 推出了画面。它看不到这一帧是从哪个线程提交的、看不到这条 draw 在 CPU 上等了多久 vsync、看不到 surface 在合成器里排了几层队。这就是上面那张分工图里 RenderDoc 落在"帧级"这一侧的根本原因:它 是放大镜,不是望远镜。
这里藏着一个经常被忽略、但恰恰是本系列立身的点:抓帧不等于回放。你提交时用的是某个真驱动,
而 RenderDoc 的重放(ReplayContext)是让它自己的 replay 后端再去执行同一批命令,两边的驱动
可能不完全一致,扩展支持、状态机都可能不同。这背后有一条图形调试圈著名的经验法则,叫
Payne's Law:
你在 RenderDoc 里看到的行为,第一次以「中性于具体驱动」的方式被看清,因为 RenderDoc 先把 你的命令抽象成它自有的中间表示,再把它重新分发到可选的后端去重放。
这句话的实战含义很深:当你的程序在真机上表现怪异、到 RenderDoc 里却"本分"了,往往不是 RenderDoc 骗你,而是你的代码踩中了真机驱动的某个特定行为,比如某个你没显式初始化的状态,真驱动默认 给了一坨垃圾,而 RenderDoc 的中性重放给了干净默认值。能把"真机 vs 中性重放"的差异看出来,正是 自证式验证最值钱的部分:你多了一个可对照、可控的参考系,一切"它就是这么怪的"都有了可检验的 下家。
把"RenderDoc 站在哪个口上"落成一张官方界面示意就更直观,下图就是 RenderDoc 把抓帧器注入到一个
已经在运行的进程里。正因为接管的是"提交命令"这个必经之口,同一份 .rdc 才能在之后切换不同的
replay 后端去放,而这也正是前面那句"真机 vs 中性重放"能立起来的原因:后端可以换,对照组才存在。
RenderDoc 是靠什么截住这几条主流 API 的,值得单独交代一句,因为它决定了"能截住谁、会被谁绕过"。
在 Vulkan 上,它走的是官方 layer 机制:Installable Client Layer 由驱动按链式顺序加载,RenderDoc 把
自己排在与真驱动之间的某一环上,于是所有 vkCmdDraw、vkCmdDispatch、状态绑定都先经它之手Vulkan Layer;在
OpenGL 上,它走的是运行时函数拦截:用自己的符号表把 glDrawElements、glBufferData 这些入口重
定向到记录器;在 Direct3D 上,它则热挂钩 ID3D11DeviceContext::Draw、ID3D12CommandQueue::
ExecuteCommandLists 这类提交入口。三套入口长得完全不一样,思想却一致:不动你的业务代码,只接管
"提交命令"这个必经之口RenderDoc 特性清单。
也正是因为"接管的是必经之口",RenderDoc 就天然带着一个边界:它只能截到它认识的那个 API。跨 API 转译(比如用翻译层把 DX 命令跑在 Vulkan 上)、自己把命令直接灌进更底层接口、或干脆用自定义调度 绕开即时模式,都可能绕过常规入口。这一层你暂时不必深究每个钩子的实现,但要记住结论:接入点决定 覆盖范围。工程里遇到"RenderDoc 抓不到"时,优先怀疑的不是配置写错,而是调用走了它不认识的路。
讲到这,值得把"一副抓帧里到底存了什么"再剖细一层,因为它决定了"能查什么问题"。一份 .rdc 不是
一张截图,而是一份结构化的场景记录,主要装这几类东西:
- draw / dispatch 调用清单:每一帧里所有
Draw、Dispatch、EndRenderPass、状态切换的顺序化 列表,这是 Event Browser 的数据来源,也是"重放任意一条"的索引; - 资源:每张纹理、每个缓冲、每条 VB/IB 的内容(按需保留若干 mip / 若干关键帧,减小体积),加 上它们的生命周期与绑定关系;
- 管线状态:每个 PSO(Pipeline State Object)的完整描述:着色器、输入装配、混合、深度模板、 光栅化配置,全部可检可改;
- 着色器与反汇编:去重的 shader 二进制,以及对应当前驱动出来的反汇编文本,看到 GPU 真正执行 的指令;
- 帧边界与 buffer 标记:哪里算"一帧"的起止,方便按帧回放与多帧比对。
明白这份清单,你就明白 RenderDoc 相对"截图"强在哪:它不给你一个结果,给你一份可以反复切片、 还原、替换的实验材料。这正好对上第四节说的"中间状态出口":抓帧文件就是其中最重要的一口井。
再补一个能立刻用上的最小回放流程,把"证据链"跑通给你看:抓一帧 → 在 Event Browser 里找到你怀疑 的那条 draw → 单步到它,Exporter 把这条 draw 的 input(顶点流、纹理、状态)导出来 → 改一个参数(比如 换纹理)再重放 → 对比前后两帧的输出差异。这个循环把"我怀疑是 X"变成"我改掉 X 画面对了",就是 自证式验证的具体模样。工具的价值不在于按钮多,在于能不能让你把怀疑做成可验证的实验。
到这里已经能回答"画错/画慢"两类问题的一半了,但还有一件事把"自证式验证"推向闭环,值得单独说: 把"CPU 慢"和"GPU 慢"切开,这是 RenderDoc 性能分析里最有价值的一招。思路是抓同一切片两遍:一遍真提交,CPU 发命令、驱动提交、GPU 真画;一遍用 Null Context / off-screen,只让 CPU 把整批命令 按原样发出去,驱动照常装载状态,但 GPU 不真的执行绘制。两条时间线一减,就能把"CPU 这头的命令构建与 提交成本"和"GPU 那头的实际绘制成本"单独拿出来看。很多"慢"的真相在没做这一步前是糊成一团的:比如 一个 draw 数不高却明显卡顿的场景,减完才发现 CPU 在每条 draw 前做了一次昂贵的资源解析,GPU 其实一直 闲着;反过来,一个 GPU 占用常年爆表的,可能 CPU 很清闲,真正的瓶颈在带宽或像素覆盖。这一步把"慢" 从"一坨"切成"两瓣",是自证式验证能给出确定结论的关键分水岭。
再把这五件武器里的第一梯队单独拎出来讲透,因为它们是你进到"帧内"后最先会碰到的两道门:Event Browser 回答"哪条命令在拖",Resource Inspector 回答"这条命令喂了什么"。
Event Browser 把一帧的所有调用排成一张可折叠、可单步停留的清单,也允许按资源反向追溯"谁用了这张
纹理"Event Browser 文档。验证一个"帧内慢"的疑点时,标准动作是把它按耗时倒序、或按某类代价指标排序,第一时间锁住排头
的那几条。这里有个新手常踩的坑:GPU 视角的耗时和 CPU 视角的耗时通常对不上。同一条 draw,CPU 侧
可能只花了几十微秒(因为只记了提交),GPU 侧却拖了几毫秒(因为真在那上头耗费了算力与带宽);要同时
看两个视角,得配合管线栈、结合驱动给出的那一簇性能计数器一起判,而不是只看某一个 tab。
Resource Inspector 则把任意一张缓冲、贴图、Descriptor、RenderPass 的字节翻开给你看。它在"像素错了"这
类问题上价值极高:怀疑是贴图喂错,就把 vkCmdBindDescriptorSets 上绑的那张纹理内容导出来看是不是你
上传的那份;怀疑混合错乱,就逐项核对 blend 因子、深度模板配置与 render pass 的 attachment 属性,看是
不是哪个环节把 state 写串了。每一处"像素为什么是这样的"的追问,最终都落回到"某种资源当时是那份
数据"这个可核实的点上。 另外两件,Mesh Viewer 验证顶点流与索引流如何装配成三角形,Shader 反汇编 /
Debugger 看到 GPU 真正执行的微码而不是你没调对的高级语言,本系列后文会各拆一集单独深挖,这里先把
"五件武器各自能验证一个动作"的全貌钉在坐标系上。
RenderDoc 的重放能力,沉淀成了后续五篇要逐个深挖的武器:
- Event Browser:把这一帧的所有 draw / dispatch / 状态绑定排成一张可筛选、可跳转的清单,配合管线 状态查看,是"帧内慢"的热点定位入口;
- Resource Inspector:翻开任意一张缓冲、贴图、Descriptor、RenderPass 的字节,看它到底长什么样、 喂给谁了;
- Mesh Viewer:数顶点流与索引流如何装配成三角形,能在 mesh 层面验证拓扑与属性;
- Shader 反汇编 / Debugger:看到 GPU 实际执行的微码,而不是你没调对的高级语言,这是"为什么像素 是这样"最底层的一环。
上面已经给 Event Browser 与 Resource Inspector 各埋了一道门,剩下的 Mesh Viewer 与 Shader 反汇编, 以及完整的 Capture/Replay 机制、请求后的多帧对比与性能标注,后文会逐集单独拆开。这一篇只把'哪些 动作落在哪个象限'的全貌钉住,接下来是深挖,不是一笔带过。
在把这一章收掉之前,值得补最后一块拼图:多帧对比与性能标注,是 RenderDoc 从"查一帧"走向"查一段"的 关口。单帧回放能回答"这一帧里哪条命令错",但要回答"为什么稳定在 58 帧而不是 60 帧",就得不只是一帧。PART A 的最后一篇会演示怎么连续抓几帧、把同一 draw 在不同帧的耗时并排摊开,那一根的耗时若随帧 数稳步爬升,多半是资源越积越多、或某个缓存被蹭爆;若某帧突然跳高又在下一帧回落,多半是偶发的状态 切换或一次大上传。配合性能标注(给 region 打上起止标记),还能把"开销是均匀摊开"还是"集中在某几 个 region"从一坨分成几格。从"抓一帧"到"抓一段、比一串",是 RenderDoc 从"对错"进入"快慢"那一侧的门 槛,也是帧内这一轴真正能回答"画慢"的全部射程。
二、第二支柱:系统为什么卡——Perfetto 的 trace 视角
RenderDoc 看不进"帧从哪来",Perfetto(Google 主导,Apache-2.0)正好补竖起来的这根轴Perfetto 官方文档。Perfetto 是一
套系统级 trace 采集与分析平台:它把 CPU 调度、线程唤醒、渲染线程切片、GPU 队列、帧时间线
(FrameTimeline)、乃至内核事件,按统一的 protobuf 格式汇进一份 .perfetto-trace 文件,再用
trace_processor(内嵌 SQLite)Trace Processor与 Web UI 两个出口让你查询、过滤、统计。
要看懂 Perfetto,先看它的数据从哪来。它设计成"一堆 data source 往里灌,一个统一格式往外流":
- CPU/线程层:
ftrace/ system_profiler 收集调度事件(sched_wakeup / sched_switch)与线程切片,回答 "这个线程什么时候醒、什么时候睡、被谁抢了时间片"; - 渲染线程层:App 可以打自定义切片(TraceEvent),把"性能接口开始 / 结束"包成一段可量化的区间;
- GPU 层:通过驱动/ runtime 把
vkQueueSubmit到 GPU 实际执行之间的队列排布暴露出来,回答"GPU 什么时候真的在跑"; - 帧时间线层:FrameTimeline 记录平台从"输入事件 → App 渲染 → GPU 完成 → 合成上屏"的端到端时间戳,逐帧可定位哪一段拖了 deadline(详见我下一节展开)。官方文档见 Android Jank detection with FrameTimeline;
- 内核层:中断、内存、锁等事件,把系统级的下钻做到最底。完整数据源清单见 Perfetto 数据源总览。
至于数据是怎么从"分散的进程"汇成"一份文件"的,Perfetto 用的是一个生产者/消费者模型:每个被追踪
的进程里都嵌着一个 producer,它把本地采样到的事件(CPU 调度、线程切片、GPU 队列)打包成 protobuf,
通过共享内存投递给一个中心化的 tracing service(守护进程);追踪结束或到达截止时长时,这个 session
汇聚所有原始块,再交给 consumer 落盘成一份 .perfetto-trace。这个设计带来两个对图形开发者极重要的
特性:一是采集与渲染进程解耦:被追踪的游戏进程可以照常跑,采样由旁路的 tracing service 承担,对
帧率的干扰被压到最低;二是时间戳全局统一:所有 producer 都往同一个服务投递,由它把各自时钟对齐
到同一根时间轴上。没有这一步,"CPU 时间戳"和"GPU 时间戳"就只是两堆对不齐的数字,跨层对照无从谈起。
这些来源在运行时被拍平成一份单一时间线,这是 Perfetto 的精髓:它不给你"CPU 一段、GPU 一段" 两张割裂的表,而是把所有事件按全局时钟对齐成一条可以横向对时间的轴。于是你能问"这条 draw 提交的 瞬间,GPU 在干嘛?渲染线程是不是正被别的进程挤掉?"这种跨层对照的问题,这正是"卡"这种 系统性疾病唯一能查的位置。
它回答的问题,与 RenderDoc 是完全不同的另一类。RenderDoc 问"这一帧里画对没有";Perfetto 问的是 "这一帧为什么晚了 8 毫秒"。答案可能藏在几个截然不同的地方:
- 渲染线程根本没被调度到:CPU 被别的进程占着,线程醒了却轮不到;
- 线程起来了,但卡在一次
sfence或 vsync 等待上,CPU 时间全耗在等,而不是在干活; - draw 提交给了 GPU,但 GPU 队列前面还排着三层活,实际执行晚了几毫秒,GPU util 显示不高,问题在 队列,不在利用率;
- 画面合成器 / SurfaceFlinger 那里排队,Present 出去的调度超过 deadline,这一帧其实画完了,是"送" 慢了。
这几类原因的解法南辕北辙:一个要调线程优先级,一个要砍 draw 数,一个要减带宽,一个要改合成 参数。不先归到具体那一类,调什么都像抓瞎。Perfetto 的价值就是给你一条能往下钻的轴:切片(slice) 看每段代码占了多少时间,调度事件(scheduling)看线程到底醒没醒、被谁挤掉,FrameTimeline 看端到端 掉帧的判定边界(调度语义见 Perfetto CPU Scheduling)。
拿"线程醒了却没轮上"这一条为例,看 Perfetto 的调度切片怎么读,你就有第一手的手感。一串典型的
RenderThread 出现在视图上时,往往是三段连续的颜色:一段 CPU 上的 slice(线程真在跑,颜色深)、
一段空白(线程被切走,别人在占用这个核)、再一段 slice。如果每帧里的空白都在固定位置出现,且和
另一个高优先级线程的 slice 严丝合缝地咬合,那"被抢时间片"的嫌疑就锁死了。要确认它,就去查那段的
sched_switch 事件,谁从 RenderThread 切换进来、持有多久、为什么优先级更高。"被抢"这件事,只有
在调度事件这一层才看得到;你不钻到这一层,就永远只能猜"好像被别的东西拖住"。
官方界面示意里的调度读法是这样的:横向是时间轴,两行并排的是 RenderThread(或任意线程)与抢占它的
那个线程,中间的空白与咬合处一眼可认出"谁进谁出"。图上白色空档的宽度、与高优先级线程 slice 的贴合
程度,就是"被抢时长"的直接读数。
Perfetto 的价值就是给你这条能一条条砍下去的轴:先看线程级别的 slice 分布,看出"空转";再下探到 sched 事件,锁定"被谁抢";必要时再落到内核事件,追到锁、内存、中断那一层。每一层都对应一个"为什么" 的更精确答案,这就是它和"只看一个总览条"的本质区别。
为了把"跨层对照"这四个字落到一处能摸得到的场景,把前面列过的两种成因,"画完了但送慢了"和"线程没轮
上",拼在一起看:假定一帧从 App 提交到 GPU 完成只花 4ms,完全在预算内,但用户体感却掉帧。这时你把
三个视图并排看,FrameTimeline 主视图里,能看到那帧的呈现时间戳晚到了;App 渲染那条 bar 却没超;真正超的
是它后面那一段 Compositor / 合成器的 slice。于是结论自然地推出来:问题既不在渲染(App 段正常)也
不在 draw(GPU 段正常),而在合成排队:可能是有太多 layer、可能是合成线程也在被抢、可能是 buffer
往返太慢。一次体感掉帧,用 RenderDoc 完全看不到(帧内一切正常),Perfetto 用四个并排的时间戳就把它
归到了"合成"这一格。 这就是"系统级这一轴单独存在的理由":有些问题,天生就躺在帧外面。
FrameTimeline 的官方界面示意正好示范这种跨层读法:把主视图与 App 渲染、合成各段的 bar 在 trace 里对齐 着看,一眼就能看出超时的不是渲染而是合成段。它示范的正是跨层对照的读法:不只看"这一帧超没超", 而是看"超的是哪一段"。
Perfetto 的下钻不是"看面板",而是查数据。trace_processor 把 trace 装进 SQLite,
你可以写 PerfettoSQL(一种扩展的 SQL)去问"过去 500 帧里,每帧渲染线程累计 slice 时间的中位数是
多少""jank 帧里 GPU 队列深度分布如何"(查询语法见 PerfettoSQL)。这在第六章讲工具选型时会具体展开,但这里先埋一句:能写
查询的分析器,和只能看的分析器,是两种物种。后者只会告诉你有问题,前者能告诉你问题长什么样。
这句话不是一句标语,用一条最小查询就能钉住。假设你想找"过去 20 秒里,渲染线程每帧的累计 self
slice 时间",可以在 trace_processor_shell 里写类似这样的 PerfettoSQL:
SELECT frame_id,
SUM(dur) AS render_slice_ns,
CAST(SUM(dur) AS REAL) / 1e6 AS render_slice_ms
FROM slice
WHERE name GLOB 'RenderThread:*'
GROUP BY frame_id
ORDER BY render_slice_ms DESC;
这么一问,"到底哪一帧、哪一段 slice 占了大头"就从"翻面板肉眼找"变成了一条可复核的查询。昨天、 今天、别人的机器,跑同一句 SQL 出同一份结果。等第九篇专门讲 FrameTimeline 时,会有一整套统计 jank 归因分布的查询展开;这里先埋下"分析器有两种物种"的证据。
Perfetto 官方文档同样给过这类查询结果的表格式输出示意,trace_processor 吐出的那几张表,就是"查数据"
而非"看面板"的最直观现场:数字列、frame_id、耗时排序,全是可以复算的证据。
唯一要提醒的是时钟语义:dur 是某线程在该 slice 上实际被执行的时长,不含它被调度走、等待的那
段时间。所以要看"线程为什么慢",光看 self slice 不够,必须配合 sched 层的 sched_switch 事件,看线程
在两次 slice 之间是不是被别的进程抢走了时间片。"这个线程自己干活的时长"和"它在墙钟里耗掉的时长",
正是上面那张分工图里 CPU/线程层与内核层各自负责回答的两件事。
顺着这点再补一句和本系列立身相关的:.perfetto-trace 本身是一份开放、可读的 protobuf 数据,不是某
个私有数据库。这意味着两点。其一,你能用 trace_processor_shell 任意查询、筛行、把它当数据库用,而不
是被锁在一两个预设视图里;其二,这份文件可以存档、可以跨机器移植、可以日后重查,一份 trace 就是一份
可复用的证据。别人拿着一份 .pftrace 照你的查询步骤重跑,能得出和你一模一样的结果,这正是"自证式
验证"在系统级这一轴上的落点:它不是"我看了一下",是"我留下了一份别人也能读的数据"。
对图形开发者来说,最值得下功夫的是 FrameTimeline。它把"掉帧"从一句抱怨变成三个时间戳里能归因 的定量结论。可以简记为它记了什么:一个 snapshot 里,输入事件 → App 开始渲染 → GPU 完成 → 合成 Present 各有一个精确的时间戳(vsync id 把它们串起来)。jank 判定就落在几个可测的边界上:
- 渲染是否超过了当前 vsync 的 deadline(渲染慢);
- 渲染虽没超时,但合成环节排队,Present 晚了(合成慢);
- 画面本身 ok,但相位错位,错过了一次 vsync 采样(调度/相位问题)。
这三种判定对应三种完全不同的修法,而它们在 FrameTimeline 上是一眼可分的。这块是本系列"系统级" 这一轴最能落地的地方,第九篇会专门把它拆成一整章,包括如何用 SQL 批量统计 jank 帧的归因分布。
这里先把 FrameTimeline 里最反直觉的一点讲透,免得你第一眼就看错:它记的不是"这帧渲染了多久", 而是"这帧该在什么时候上屏 vs 实际什么时候上屏"的错位Android Jank detection。每一个 snapshot 都带一个预期呈现时间 (expected present / vsync deadline)和一组实际完成时间戳。掉帧的本质,是"实际完成时间戳"落到了 "预期呈现时间"之后。至于晚了多久、晚在哪一段,才是 jank 归因真正要切的。
也因此,念 FrameTimeline 时脑子里要有一条三段的因果链:输入事件决定这一帧该处理谁(输入延迟); App 渲染 + GPU 完成决定内容什么时候 ready(渲染/提交延迟);合成 Present 决定它最终在哪个 vsync 落上去(合成/呈现延迟)。三段里任何一段晚,整帧就晚,但修法完全不同:输入段是采样与线程唤醒的 问题,渲染段是 draw 与提交的问题,合成段是合成器与 buffer 往返的问题。把"这一帧晚"剖成"是哪一段 晚",正是坐标系"帧外·呈现"这一轴里最细的刻度,也是后续几篇反复用来演示的标尺。
把"掉帧判定边界"用官方界面示意钉成画面:FrameTimeline 那一行 snapshot 里,绿色是命中 vsync 的帧、红 色是把"预期呈现时间"拖延到 deadline 之后的帧,中间那条竖直虚线就是判别边界,"掉帧"不是感受,是这条 线上一眼可数的读数。
三、一张坐标系:帧级 × 系统级,四个象限怎么用
把前两章的两根支柱交叉成一张图,就得到本系列最重要的一张地图。横轴往外是系统级,纵轴往里 是帧级,两轴交叉切出四个象限,每一象限对应一套相对固定的排查动作。
- 横轴·系统级:从单帧往外拉,量"这一个帧在整条流水线里怎么被调度、排队、呈现"。问题形如"这里 空转了一段""FrameTimeline 报 jank""等 vsync 太久"。首选 Perfetto。
- 纵轴·帧级:从单帧往里钻,量"这一个帧内的命令、资源、着色器对不对、快不快"。问题形如"这条 draw 的着色器绑错""这张贴图没上传""顶点流顺序乱了"。首选 RenderDoc。
两轴为什么是"垂直"而不是"同一条线的两端"?因为它们是独立的自由度:你可以有一个帧内完全没问题的 程序,却因为线程没起来而卡成 ppt;也可以有一个调度完美、却单帧画错像素的程序。对错 / 快慢与 调度 / 呈现,是两件不搭界的事,把谁当成谁的子集都会误诊。
四个象限,虽然是一张判断表,但真正的功夫在归位。一个问题被拖进四个象限里来回试,是最烧时间 的。下面这张表就是给"归位"用的:每一行给你"现象 → 该进哪象限 → 该拿哪把尺子"。
| 象限 | 典型判定 | 首选工具与动作 |
|---|---|---|
| 帧内 · 对错 | 画面有错、黑屏、崩溃、物体丢失、花屏 | RenderDoc 逐条回放 + Mesh/Shader 检查 |
| 帧内 · 快慢 | 单帧 draw 变慢、GPU 占用饱和、draw 数爆炸 | RenderDoc 性能标注 + Event Browser 定位热点 |
| 帧外 · 调度 | 整体空转、线程没起来、CPU 被抢、等锁 | Perfetto 线程切片 + 调度视图 |
| 帧外 · 呈现 | 掉帧、jank、Present 超 deadline、合成排队 | Perfetto FrameTimeline + 合成/呈现链 |
举两个真实的归位例子,帮这张表立起来:
例 A:画面一张张地跳、像幻灯片。 很多人第一反应是"是不是 draw 太多 GPU 爆了",然后冲进 RenderDoc 数 draw。但跳帧的节奏是"整屏一起跳、每隔一段固定时间",这更像呈现阶段的问题。去 Perfetto 的 FrameTimeline 看一眼会发现,渲染线程其实没超时,是合成环节每次都晚一拍、或者 vsync 相位错位。归位 到"帧外·呈现",修法立刻变清晰。
例 B:某个场景一走进就掉十几帧 FPS。 画面不花、不崩,就是慢。这是帧内·快慢的典型。进 RenderDoc 抓那一帧,Event Browser 按耗时倒序排,热点 draw / 状态切换立刻现形,再做性能标注看是顶点 带宽、像素覆盖还是切换成本。
这条"按耗时倒序"的读法,用 Event Browser 的官方界面示意最直观:事件树里逐条浏览,热点 draw 一目了 然。它和上一张调度示意正好一左一右:左图在讲帧内的慢,右图在讲帧外的卡,两张并排才是这套坐标系 的完整写照。
再补一个最容易误诊的第三种情况,它顺带演示了"两把尺子要交叉用":
例 C:帧率稳定地在 60 与 58 之间轻微抖动,肉眼不易察觉,但某个高频交互场景手感发闷。 因为是"微 快慢",很多人直接冲进 RenderDoc 找帧内热点,可 draw 的 GPU 时间并没有明显上涨;再拿 Perfetto 的 FrameTimeline 一看,也没超 deadline。真正的元凶往往落在"帧外·调度"。渲染线程每隔几帧就被一次 内存分配或一次线程切换拦腰切断,CPU 时间片被抢走 3–4 毫秒。同样的现象,单看任何一把尺子都会误归 位,只有把 RenderDoc 的帧内 GPU 时间、和 Perfetto 的帧外调度切片交叉比对,才能把它准确推到第四象限。 这就是"先单轴、再交叉"的归位纪律:先用一把尺子排除它那一侧,再把证据交到另一把尺子那边去接力。
这张四象限不是玄学,它其实是给本系列 13 篇里每一篇开头都用的判据口诀:凡是"画错 / 画慢",左脚 跨进左边两象限;凡是"卡 / 迟延 / 掉帧",右脚跨进右边两象限。两边需要的方法、证据形态、乃至工具, 完全不同。先把这一步做对,再谈任何深度。归位是方法论的入口,也是分诊的台阶。
别再把这四象限当成一张需要死背的表格,它更该是一张贴在工位上的心电图判读卡:遇到任何图形问题, 先在心里过一遍:这是"帧内"还是"帧外"?是"对错/快慢"还是"调度/呈现"?一次到位通常发生在症状足够 典型时;多数情况反而是快速排除:先断定"不是帧内",就把 RenderDoc 收起来,省下最贵的排查时间; 先断定"不是帧外",就把 Perfetto 放一边。归位动作的收益不在"猜对的那次",在于"排除掉的那一大堆"。 这也是为什么本系列把坐标系放在开篇:它先驯化你的第一反应,而第一反应决定了你的时间花在哪。
必须诚实交代一句四象限的极限,免得它被当成万能药:不是每个问题都能干净地落进某一个象限。带宽 和内存这一类"资源瓶颈"就常横跨两轴。一条 draw 的像素覆盖过高,既可能让 GPU 在帧内变慢,也可能让 显存带宽在帧外成为合成瓶颈;着色器编译停顿既可能表现为某帧的采样卡顿(系统级),也可能让渲染线程 在那帧少画了一块(帧内)。对这种横跨性问题,坐标系的作用不是强行把它塞进一格,而是逼你先界定 "在哪个时序上观察到它"。先按当前观察点归位,再顺藤摸到另一根轴上。坐标系的价值恰恰在于:它让你 知道自己在哪个轴上观察,从而知道下一个该往哪根轴的方向找。地图标注得越诚实,越不可能迷路。
四、证据链:自跑 → 真图 → 归因 → 结论
坐标系和判据都有了,还缺一环:怎么把真相拿出来。本系列对"证据"有一条硬规矩,四个字—— 自跑出图。不允许截图搬厂商面板,不允许只见说法、不见现场。任何论断,都要能走完下面这条链:
- 自跑:在自己机器上把那个行为先跑出来。RenderDoc 抓一帧、Perfetto 采一段 trace,甚至一段你 写的、能复现现象的脚本。这一步的判据是"能不能复现":凡是复现不了的现象,先别急着画结论, 先怀疑自己看错了。
- 真图:把原始产物截下来。要的是"我们自己跑的、换台机器照着步骤能复现"的东西,不是网上找 一张看着像的。分母是原始证据,不是示意图。
- 归因:对着图把"为什么"讲清楚,把"现象"和"原因"切开。截图本身不算归因,归因是解释,你要 指得出"这一块堆叠是因为那条 draw 的管线状态切换",而不是"就是慢"。
- 结论:给一个可复核的定量值或定位,而不是"感觉快了一点"之类的形容词。要么给数字(中位数、 P99、帧耗时分布),要么给确定的位置(第 N 条 draw / 某帧的某个事件),让后来人可以核。
为什么非要"真图"不可?因为图形的坑,绝大多数藏在你看不见的中间状态里。一个 draw 慢,可能是 上一条 draw 的管线状态切换把它拖住了;一个像素错,可能是混合因子在某个层层叠的场景里被写错了; 一个越界,往往要到反汇编、看偏移量才定得下罪。这些中间状态的出口就那么几个,抓帧文件、trace 切片、反汇编文本,你不去碰它们,"我猜大概是 X"就永远成立。真图,就是我们把自己按到这几个出口 上、拍下来的原始证据,是"我看到了,不是我以为"的凭证。
这里必须把"自跑真图"和"外部示意图"分清,因为它们在本系列里地位完全不同:
- 自跑真图是证据:是我们这台机器上跑出来的、可复现的、可作为归因依据的原始产物;
- 外部示意图是资料:多是官方文档的原理示意(RenderDoc 机制图、Perfetto 架构图),用来辅助 理解概念,不能当"我的程序就是这样"的证据。
一篇好的验证文两者都要:用示意图把概念讲清楚,用真图把结论钉死。但这两种位不能互相替代,尤其 不能用示意图去"证明"一个本该由真图证明的事实,那等于把参考书当目击证人。本篇作为方法论综述, 正文中的工具界面与机制图暂时以来源清楚的官方示意先行交代形态;从后续工具实机篇起,每篇保证足够的 自跑真图,把"证据 > 转述"这条铁律第一次落到实处。
把这条链套到上一章例 B 那个"进场景掉帧"的场景,走一遍全流程,你会对每一步的"分量"更有体感:
- 自跑:本地用 RenderDoc 抓一进该场景的那一帧,同时用 Perfetto 采一段包含"按下进入键"动作的 trace。两次抓,现象都成立,"能复现"这一关过了。
- 真图:把两处原始产物截下来放进正文:Event Browser 按耗时倒序的截图、和 trace 里那一条 bar 的 时间切片截图。要的是"我们这台机器跑的、别人照着能复现",不是网上找一张看着像的。
- 归因:对着 Event Browser,发现热点集中在那条绑了两张大纹理的 draw 上;再对着 trace 那个切片,
看到渲染线程在为这条纹理做一次关键的
glTexSubImage2D上传,整整吞掉一段连续的 CPU slice。于是 "现象慢"被归成"纹理上传穿透 CPU 预算"这一个具体成因,而不是停在"就是慢"。 - 结论:给可复核的数字:"该 draw 平均 6.2ms,其中纹理上传占 4.1ms;把一次性上传改成预分配的 DMA 缓冲后降到 1.8ms。"这个结论别人能照抓照核,它是一句可被判错的话,这才是证据的价值。
这个例子同时暴露了证据链最容易偷懒的一环:第 3 步的"归因"里,很多人在 Event Browser 看到那条 draw 就停了,忘了去 trace 那边补"CPU 在干嘛"。归因要归到"成因"而不是"现象",而"成因"常常就藏在第二 把尺子那头。所以证据链不是四条并列的空口号,它是一条循环回路。走到结论发现对不上,就退回真图 重新归因,甚至回来自跑换一组参数再来一遍。
下面这张是本篇第一个"证据长什么样"的实物参照,RenderDoc 官方界面里多帧抓帧并排比对的形态。它的作用 不是给你看图(图本身不深奥),而是示范一份抓帧证据该是什么形态:能被逐条检看、能换台机器按步骤 复现的原始产物。本篇为方法论综述,正文工具界面图以官方示意先行讲清形态,自跑真图样本统一自下一篇 实机开跑时按取证规矩补齐;后续每一篇,这套"证据形态"的标准都会被沿用。
五、工具选型:为什么"开源免费"这一条不可妥协
坐标系、证据链都立起来之后,还差最后一个前提:用什么工具。本系列全站只认开源免费工具,这不 是个口号,是三个能落到实处的硬理由。逐条讲:
(1)可复现 —— 自证式验证的地基。 RenderDoc 是 MIT,Perfetto 是 Apache-2.0,源码都在(各自仓库见 RenderDoc GitHub 与 Perfetto GitHub)。任何一步 结论,你都能把代码拉回来自己重跑一遍、把概念复现出来,而不是依赖某个闭源面板"说是什么就是什么"。 自证式验证的前提是"任何人能复现",而只有源码开放才谈得上真正可复现。闭源工具你只能信它的输出, 没办法核它怎么算的。那就退回了"听人转述"。
(2)可注水排查 —— 能往下挖,才停得住现象层。 RenderDoc 的重放能让你改状态看差异:换纹理、换
参数、换后端,看结果跟不跟;Perfetto 的 trace_processor_shell 能让你写 SQL 任意过滤 trace 里的行。
闭源面板通常只给你它想给你的那一两个视图,你没法往下挖,就永远停在现象层。知道"慢了",但
不知道"慢在哪一步、为什么"。图形调试大量时候靠"改一改 / 看一看 / 再改"的循环收敛,这个循环只有
开放接口才能跑。
(3)可进 CI —— 把验证自动化。 这两样工具都能纯命令行跑:renderdoccmd 可以导出与分析抓帧,
trace_processor_shell 可以对 trace 跑 SQL(命令入口见 RenderDoc 命令行 与 Perfetto 命令行工具)。于是整套"自跑 → 验证 → 比对阈值"能挂进自动化回归,每次
构建后自动抓一帧、自动采一段 trace、自动比对开销是否超时。这是"自证式验证"最后的闭环。靠手工人肉
盯,只能验证"昨天那次",自动化才能验证"每次都这样"。
补两个能立刻写进脚本的入口,把"可进 CI"落到命令级:抓帧可以用 renderdoccmd capture -o out.rdc 配合
--cap-vulkan / --cap-gl 指定要拦的 API;分析抓帧可以用 renderdoccmd 的导出 / 重放子命令,或把它喂给
RenderDoc 的 Python 后端 /replay/ 接口去跑自定义脚本。trace 这边,perfetto 命令行按一份配置指定数据
源与时长去采一段(perfetto --config /tmp/config.pbtxt --out /tmp/t.pftrace),采完交给
trace_processor_shell --query "..." 去跑那句你要的 SQL。两条命令都能无人值守地跑在构建机上。构建
后自动抓一帧、自动采一段 trace、自动比对开销是否超阈值。这正是"自证式验证"能够规模化、从"昨天验证
过"升级成"每天都在验证"的落点。
再把对比叠一层:厂商面板强在自家硬件 / 自家驱动上的深度:它给你 CPU 曲线、GPU util、瓶颈提示, 都很专业,但那是盒子外面看盒子,而且只对它的硬件有效。而本系列的主题恰好是"看清楚盒子里": 看驱动怎么切管线、看 CPU 与 GPU 怎么在队列上排队、看中间表示怎么编译,这套要的是跨 API、跨硬件、 可复现的开放性。这不是贬低厂商工具,是定位差异:我们的深度在方法论,厂商的深度在自家生态。
所以"选哪把尺子"没有标准答案,只有一个贴合你身份的判断扳机:当你想确认"是这个厂商、这块硬件、这个 驱动下到底发生了啥"时,厂商面板值得用,它在你熟悉的生态里最快、最深;当你想确认"一个独立于厂商 和硬件的机制原理"、想复现、想写给别人看、想挂 CI 时,开源工具是唯一出路。本系列站在后者的立场。 因为我们要交付的是"原理 + 证据",而不是"某个厂商的主观结论"。只要你哪天要在"这台机器的私有问题"与 "可被任何人核验的方法"之间二选一,就自然地知道这把秤该往哪头倾。把这一条想通,第五章的综合对比 才算真正完成了它的使命:不是替你选,而是告诉你每个选项各自站在哪、凭什么被选中。
六、边界声明:本篇不涉及什么
把话说满容易,把边界划清才难。这套方法论语境的坐标系,有几件事本篇刻意不进。进它们会让 "坐标系"塌成"教程堆",也会把"工具哲学"混进"操作手册":
- 不涉及 RenderDoc / Perfetto 的逐按钮操作教程。那是说明书,不是方法论。具体到"第几个 tab 点什么" 的操作细节,留给与工具对应的各篇去带。本篇只讲"为什么用它、它站在哪一层、边界在哪"。
- 不涉及平台私有 profile 的细节深挖。各家驱动 / 平台的私有 profile(Vulkan validation、vendor counters 等)是驱动厂商的地盘,本系列只会在相关设备篇里以"怎么把它接进坐标系"的视角带过,不做 厂商内部细节的搬运。
- 不涉及 Godot / UE 引擎内部实现的血肉。渲染伺服器结构、引擎级优化分属 T02(Godot 源码深挖)与 T11(UE 渲染源码)各自的专题。本篇把引擎当作"被测对象",不是"讲授内容"。
- 不涉及厂商监控面板的截图搬运。本站铁律:不搬配图、不搬厂商画面当证据。凡是"Samsung 面板截图" "某引擎 Dashboard"这类,既不能作为证据(不可复现),也会让视觉掉进"弱隐喻"。一律不收。
把这些挡在界外,恰恰是为了让"坐标系 + 证据链 + 工具铁律"这三件真正属于本篇的东西能立得住。 越这个界配的图,一律不收。这条规则从本篇起,对整个 T01 系列生效。
为什么要把这些一条条写死,而不是临到某篇再商量?因为边界是最容易"手滑松动"的地方。写"抓帧怎么 用"写到兴起,顺手就塞进一段按钮操作;讲 Perfetto 讲到一半,下意识就贴一张厂商面板图当配图。这些单看 都不算什么,但它们会一点点稀释掉本篇最想立的那件事:一套独立于具体工具、具体硬件、能讲清原理的 方法。边界划得越清楚,后面每一篇该写的越不会跑偏,它不是束缚,是把"方法论"和"教程堆"分开的那道 篱笆。凡是想懒省事、想抄近路、想"配张图好看就行"的地方,都被这道篱笆提前挡住了。
七、下一棒:从方法论到具体抓帧
坐标系立住、证据链固定、工具选型定案,13 篇的分工就完全清楚了。整个系列一条主线、三个 PART:
- PART A · RenderDoc(第 1–6 篇):从方法论落到抓帧本身。逐篇深钻 Capture 数据流、Replay 机制、 Event Browser 与 draw call 清单、Resource/Descriptor 检查、Mesh 与 Shader 反汇编,最后一篇做多帧对比 与性能标注,把"帧内·对错 / 快慢"这两个象限吃干榨净。
- PART B · Perfetto(第 7–10 篇):trace 的 protobuf 格式、trace_processor 的 SQL 查询、FrameTimeline 的 jank 判定与 vsync 事件、自定义追踪与 Flow 事件,把"帧外·调度 / 呈现"这两个象限补齐。
- PART C · 自跑闭环(第 11–13 篇):RenderDoc 抓 Godot 的渲染帧、Perfetto 剖 Android 的图形帧、 双工具联动定位一个真实的复杂性能问题,把 A 与 B 的两把尺子在同一个坑里用起来,验证这套坐标系 真的能闭环。
把三个 PART 和四象限一一对上,主线的逻辑就闭环了:PART A 一头扎进左边两象限(帧内·对错 / 快慢), 把所有"在帧内做文章"的武器用到底;PART B 转向右边两象限(帧外·调度 / 呈现),把系统级的尺子补齐; PART C 回到一个真实场景,把两把尺子交叉起来,这正是第三章例 C 演示过的"先单轴、再交叉"的完整放大 版。13 篇不是 13 个孤立的教程,而是把这张坐标系从"分诊"走到"缝合"的一次全景。 每一篇的证据形态 也跟着分工走:PART A 里真图多为抓帧与 Event Browser 截图,PART B 里真图多为 trace 切片与 SQL 查询结果, PART C 则是双工具在同一问题上的对照。
把这条主线折成一句行内地图,就是:PART A · RenderDoc 抓帧(第 1–6 篇)→ PART B · Perfetto 系统 trace(第 7–10 篇)→ PART C · 自跑闭环(第 11–13 篇)。
到这里,这张路线图对"你该按什么顺序读"已经很清楚了:从头顺读,能完整吃透这套坐标系;但你不必每篇 都把细节背下来,只要记住自己"此刻在哪个 PART、要解决哪一类问题",再跳到对应那几篇也完全可行。本系 列每一篇都尽量自洽,你从路上任一处加入都能接住上下文。坐标系替你把"前后左右"标好了,剩下的只是挑你 当下需要的那一格走进去。
下一篇正式走进 PART A 的第一站:【性能剖析工具链】(二)将深挖 RenderDoc 的 Capture 机制,
前端到底怎么把一帧"拍"下来,注入的原理、.rdc 文件的内部数据结构、以及从"提交命令"到"写盘"的
完整数据通路,会一条条拆开。
八、收束与交棒
这一篇文章没有引入任何陌生的复杂代码,它做的是更费力的一件事:把"验证"从一顿操作,提炼成一套 坐标系。你现在手里有四件东西:一根纵向的尺子(RenderDoc 量帧内)、一根横向的尺子(Perfetto 量 帧外)、一张四象限地图(哪类问题用哪把尺子)、一条证据链(自跑 → 真图 → 归因 → 结论),和一份不肯 妥协的工具底牌(开源、可复现、可进 CI)。
后面的 12 篇,我们都在这张图上走。坐标原点已经踩在脚下,下一步是把第一根尺子的第一格刻度, RenderDoc 的 Capture 数据流,掰开来看。所谓"如何验证",说到底是八个字:先归范围,再取证据。
在交棒之前,把"读了这篇能带走什么"再替你归一遍。如果你今天就要开工,请你至少记住三句能立刻用的东 西:第一,遇到图形问题先别急着开工具,先问它是"画错/画慢"还是"卡/迟延",这一问决定了你用哪把尺 子;第二,任何结论想立得住,都要走完"自跑 → 真图 → 归因 → 结论"这条链,缺一环都是"听说";第三,工具 必须是开源的、可重放的、能进 CI 的,否则你永远停在现象层。 这三句不需要任何背景就能挂在脑子里, 剩下的方法论细节,后续 12 篇会一层层替你补满。方法论给人的从来不是"一次性的答案",而是"每次都能 自己走到答案"的路径。这就是本系列要说清楚、也打算用 13 篇来兑现的事。而它真正的回报,是陪你穿过接 下来的每一篇、每一台机器、每一个深夜的排查。坐标原点已经踩下,剩下的,就是我们一集一集地把它走完。
相关链接 - 本系列:《图形性能剖析工具链》开篇(方法论坐标,本站首发,全系列 13 篇)—— 技术博客页 - 下一篇(预告):【性能剖析工具链】(二)抓帧:RenderDoc 如何拍下一帧 - 工具底牌:RenderDoc(MIT,开源可重放)—— RenderDoc - 工具底牌:Perfetto(Apache-2.0,系统层 trace)—— Perfetto
图片版权与出处 - 图「Perfetto 多数据源汇入 trace」取自 Perfetto 官方文档《Buffers and dataflow》(https://perfetto.dev/docs/concepts/buffers),作者:Google,Apache-2.0 许可,可自由商用,须保留出处。 - 图「RenderDoc 多帧抓帧并排比对」取自 RenderDoc 官方文档截图目录(https://renderdoc.org/docs,GitHub baldurk/renderdoc@v1.x 的 docs/imgs/Screenshots/MultipleCaptures.png),作者:Baldur Karlsson,MIT 许可。 - 图「RenderDoc 注入到已运行进程」取自 RenderDoc 官方文档截图目录(GitHub baldurk/renderdoc@v1.x 的 docs/imgs/Screenshots/Injecting.png),作者:Baldur Karlsson,MIT 许可。 - 图「Perfetto CPU 调度细节」取自 Perfetto 官方文档 CPU Scheduling(https://perfetto.dev/docs/data-sources/cpu-scheduling,GitHub google/perfetto 的 docs/images/cpu-sched-details.png),作者:Google,Apache-2.0 许可。 - 图「trace 查询表格式输出」取自 Perfetto 官方 Android trace 分析手册(GitHub google/perfetto 的 docs/images/android-trace-analysis-cpu-num-events-process.png),作者:Google,Apache-2.0 许可。 - 图「RenderDoc Event Browser 事件树」取自 RenderDoc 官方文档截图目录(GitHub baldurk/renderdoc@v1.x 的 docs/imgs/Screenshots/EventBrowserRegions.png),作者:Baldur Karlsson,MIT 许可。 - 图「Perfetto FrameTimeline vsync 对齐」取自 Perfetto 官方文档 FrameTimeline(GitHub google/perfetto 的 docs/images/frametimeline/app-vsyncid.png),作者:Google,Apache-2.0 许可。 - 图「Perfetto FrameTimeline 预期 vs 实际」取自 Perfetto 官方文档 FrameTimeline(GitHub google/perfetto 的 docs/images/frametimeline/app-timelines.png),作者:Google,Apache-2.0 许可。
With Best Wishes.