← ALL ARTICLES

帧捕获与性能分析工具

全库目录221
本文目录21

性能分析的目标是形成证据链:哪一帧慢、哪一侧慢、哪个事件占时间、它为什么慢、修改后是否改善。

稳定复现

先保存设备、分辨率、画质、构建和相机路线,再记录角色状态、随机种子与缓存条件。VSync、帧率上限和动态分辨率决定工作如何进入显示节奏,温度、电量和后台负载会改变设备可提供的吞吐;它们与原始计时一起保存,才能重现同一负载。

稳定复现的慢帧可以直接进入捕获。偶发卡顿先使用时间线追踪保留异常前后的事件,再根据帧标记定位图形工作。普通帧的状态截图无法解释一个没有被捕获的异常,下面先把两类证据对应起来。

时间线与捕获对齐

CPU记录时给每帧分配递增frameId,并把同一ID写进渲染提交标记与GPU事件组。长时间追踪先找到异常ID及邻近窗口,再触发或保存包含该ID的图形捕获;捕获文件记录构建、场景状态与对应提交ID。CPU帧槽位、GPU执行帧和交换链图像索引各有含义,不能只凭它们都叫“第120帧”就认为对应。

若异常是frameId=120的加载提交,CPU在这一帧请求资源,GPU可能在稍后的队列时间才消费。沿提交标记找到资源上传与第一位消费者,再查看消费者之前的等待。CPU墙钟、GPU timestamp与工具自身计时各在自己的时钟域内,先用工具提供的校准或关联标记对齐;没有校准时比较同域内持续时间和事件依赖,不直接相加两个时间戳。

自行埋GPU时间查询时,每段记录同时保存frameId、队列、查询槽位代次和时间戳频率。开始与结束查询、查询结果复制依次进入同一段受追踪的队列工作,CPU在复制完成后读取,按该队列的频率将差值换算为时间。跨队列区间分别计时,用依赖关系或校准后的时间线分析重叠,不能把两个队列的查询差值当作同一段持续时间。

读回记录按Pending、Ready、Consuming和Consumed或Discarded管理。完成回调、CPU领取、取消与回收使用同一状态锁;回调先核对设备和槽位代次,确认GPU复制完成后才能把Pending改为Ready。CPU在锁内把未取消且有效的Ready领取为Consuming,并取得查询记录与读回区间的强引用,再到锁外拷贝和换算。一个记录只允许一个领取者,其他读者使用已拷出的应用结果,不再读取原区间。GPU完成只说明结果已可读,Consuming则明确说明CPU仍在读取,两者分别限制复用。

取消只设置丢弃意图。没有CPU领取者时,已提交的查询仍等覆盖查询和复制的GPU完成值,完成后才进入Discarded;未提交批次先确认永远不会再提交。已经Consuming时,即使GPU早已完成,取消也不能释放区间。领取者在成功、拷贝失败或异常退出的统一清理中回到状态锁,核对身份;取消、失效、拷贝失败或异常则记Discarded,只有完整拷贝及换算成功才发布应用侧结果并记Consumed,最后解除持有。回收要求GPU访问已结清、CPU领取者已退出,并且记录已为Consumed或Discarded。

例如frameId=120的结果Ready后,CPU开始拷贝,这时取消请求到达:清理者只留丢弃意图,frame123仍不能复用区间。CPU退出后把120记Discarded,区间才返回池中;120在曲线上留下缺口。若CPU先完成并提交Consumed,随后取消不撤销已经发布的有效样本。取消与发布在同一锁内排序,曲线便不会把123覆盖后的数值误当作120。

例如独立包追踪显示标记E所在GPU区间为4毫秒,RenderDoc重放同一标记显示6毫秒,先确认两边都包含相同Draw、分辨率、资源格式与异步依赖。若捕获拆分了重叠或插入了同步,捕获用于解释状态,性能结论回到未捕获独立包的稳定窗口。若事件集合不同,按新增/缺失的生产者追到画质、Streaming或相机配置;若集合相同且独立包多次也出现6毫秒,再按计数器和资源访问调查真实变化。Editor与独立包分别记录,不能用编辑器开销推算发布耗时。

工具不支持指定帧捕获时,在可复现场景固定输入与随机种子,记录两次运行的事件标记序列和资源版本,确认它们重现同一行为;未能重现偶发异常时保留追踪结论,不把另一张普通帧当作异常帧证据。

读回身份还包含设备代次。设备丢失后,在同一状态入口把该代次尚未消费的结果标为无效样本,禁止新的CPU领取。已经Consuming的记录保留引用,领取者即使拷贝成功也在退出时丢弃样本;故障清理等待这些CPU读者退出,并按设备故障协议确认旧GPU访问已经隔离后才释放查询和映射资源,不等待一个永远无法到达的正常完成值。新设备重新查询频率、分配槽位并开始新代次,旧异步回调只能结清旧记录。曲线保留采样缺口及故障标记,不能把无效结果填成0毫秒,也不能用新设备同编号槽位的数据补齐旧帧。

CPU与GPU时间线

引擎与平台视图

Unity Profiler先看主线程、渲染线程、Job、GC和Loading的时间线,再按平台支持读取GPU Module。主线程等待Job时,沿任务开始和完成事件寻找前驱;等待呈现时,则结合GPU结束与限帧条件判断。Gfx.WaitForPresent这个名字不能独自说明是哪一侧工作过慢。Editor与Deep Profile会增加额外工作,定位后回到独立构建验证。

平台工具继续解释图形接口和硬件状态:Direct3D可使用PIX,NVIDIA使用Nsight Graphics,AMD使用Radeon GPU Profiler,Apple使用Xcode GPU工具,Android可按设备支持选择Android GPU Inspector或芯片厂商工具。能读取哪些计数器、支持哪些API,按当前设备与工具版本确认;工具选择跟随待验证的假设。

绘制事件与图形捕获

Frame Debugger沿引擎事件展示Draw顺序、批次拆分原因、目标切换和着色器Pass,适合确认画面由哪些阶段组成。引擎事件不一定一一对应底层API调用,真实屏障、驱动工作和精确GPU时间继续到平台工具查看。

RenderDoc的事件列表连接管线状态、纹理与缓冲、网格输入输出和着色器资源。发现某个Draw写错目标时,先检查目标绑定与viewport;输出位置错误时,查看顶点输入和变换结果;颜色错误时,查看材质资源与片元输入,再使用接口支持的像素历史或着色器调试。着色器调试依赖调试信息与平台能力,缺少功能时保留中间资源输出作对照。单帧捕获主要解释这份图形工作的状态,长期卡顿、CPU调度和热降频仍用时间线追踪。

API 调试消息

图形 API 的验证层和调试回调适合在开发构建中尽早发现无效状态、资源绑定、同步和对象生命周期问题。OpenGL 调试输出的最小接入如下:

cpp
void APIENTRY OnGlMessage(GLenum source, GLenum type, GLuint id,
                          GLenum severity, GLsizei length,
                          const GLchar* message, const void* user)
{
    if (severity == GL_DEBUG_SEVERITY_NOTIFICATION)
        return;
    LogGraphicsMessage(source, type, id, severity, message);
}

glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(OnGlMessage, nullptr);

同步回调便于在调试器中定位触发调用,也会增加 CPU 开销。正式运行可以关闭同步或按来源、类型和 ID 过滤消息。

调试层只能报告 API 能识别的问题。合法但错误的矩阵、坐标空间、光照公式和性能设计仍需要帧捕获、可视化和对照实验定位。Vulkan Validation 层、D3D12 调试层和 GPU-based Validation 也遵循相同边界。

单帧分析顺序

假设捕获中一个应当有颜色的像素变黑,同时这一帧GPU耗时上升。先把画面错误和耗时作为两个待解释的现象,沿同一份事件列表检查它们是否来自同一通道。下面的顺序先建立资源和事件身份,再进入像素回溯;只有身份对齐,后面的计数器才对应正在调查的工作。

通道与资源身份

列出阴影、深度、GBuffer或Forward、光照、透明、后处理和UI,定位最后写入该像素的事件E及其输出目标。若多出一套完整通道,先检查重复相机、反射或场景捕获;这些工作既可能覆盖颜色,也会增加耗时。若通道集合符合预期,就继续追踪E实际绑定的资源。

读取目标与输入的宽高、样本数、格式、Mip和层。E把半分辨率纹理当作全分辨率读取时,错误采样位置可能直接解释黑色;格式或样本数不同则继续核对读取与Resolve规则。一个全分辨率RGBA16F临时缓冲即使输出正确,也可能增加带宽和存储成本。此时把状态错误与成本假设分别记录,避免用“改小纹理后变快”证明原黑色的原因。

绘制与覆盖

资源身份确认后,查看E所在通道的Draw数、实例数、PSO与材质切换、顶点及索引数量和批处理状态。先确认预期对象是否确实提交;对象没有对应事件时,调查剔除和批次组织。事件存在时,再进入下面的像素历史,判断片元未生成、测试拒绝还是随后被覆盖。

覆盖同时决定像素工作量。检查全屏通道、透明层数、粒子包围范围和重复绘制:画面角落的效果若提交了全屏复杂着色器,耗时上升就有了可验证的工作来源。缩紧覆盖前后比较相同事件的像素负载与最终画面,保留必要的模糊或采样边缘,不能只凭图形面积变小就判断优化正确。

着色器与依赖

若E生成了片元、通过测试却输出黑色,读取它实际使用的着色器、纹理尺寸、Mip与压缩格式,再检查输入和运算。纹理采样、循环、分支、寄存器和指令说明它执行了哪些工作;是否形成瓶颈仍要结合硬件计数器和整帧时间。把错误资源绑定与合法但昂贵的算法分开,修复颜色和减少耗时才各有明确依据。

最后沿E的输入寻找生产者,检查Queue Wait、屏障、Readback、Resolve和Copy。E等待上传可能同时解释资源迟到与GPU空泡;一次同步读回也可能只造成停顿而不改变颜色。跨队列按已校准时间线或依赖关系判断重叠,回到未捕获运行验证耗时。下面沿同一黑像素细化数值来源,必要时跨到另一资源的生产事件。

像素历史

像素历史可以显示某像素被哪些 Draw 修改、因深度/Stencil 失败或 Blend 后得到什么结果。

适合定位:

  • 为什么物体没显示;
  • 谁覆盖了颜色;
  • 透明混合顺序;
  • Stencil/深度状态;
  • 重复绘制来源。

沿一个像素回溯

假设目标像素最终是黑色。先在最终颜色的写入历史中找到最后一次改变它的事件E,再比较E前后的值:E前已黑就继续追踪上一写入者;E前有颜色、E后变黑,就检查E的着色器输出与混合状态。若该对象的绘制因深度失败没有写入,再检查该次片元深度与当时深度附件,向前找到遮挡值的写入者。这样能区分未生成片元、测试拒绝、输出错误与随后覆盖,不能仅凭最终黑色认定着色器返回零。

若E是后处理,它的黑色输出可能来自另一张输入纹理。此时读取E绑定的输入资源、mip、层和采样坐标,再切到这份资源的生产事件;不能一直沿最终颜色目标的旧写入者追踪。点采样可以定位一个输入纹素,双线性采样则检查四个参与纹素及权重,模糊和重投影继续沿各自采样足迹追踪。比如最终像素(100,50)读取半分辨率输入,必须按E实际的UV映射换算,不能直接检查输入的同名像素。资源身份、坐标映射与写入事件一起保存,回溯才跨得过通道边界。

像素历史能力取决于接口与捕获条件。缺少该功能时,以事件前后资源快照及逐通道禁用替代;这些操作改变执行路径,数值和状态调查完成后,性能仍回到原构建的计时窗口验证。

GPU 计数器

不同硬件提供不同计数器,常见维度:

  • 着色器 Core 利用率;
  • 纹理/内存吞吐;
  • 缓存命中与未命中;
  • Wave 占用率;
  • Raster/ROP;
  • 图元 Culling;
  • Tile Load/Store;
  • 射线 Tracing Unit。

计数器高不是自动的问题。例如带宽利用高但帧时间达标,可能只是硬件被充分使用。需要结合瓶颈和修改实验。

引擎自带的追踪工具

通用图形调试器擅长单帧的 GPU 细节,但定位“哪个系统在什么时候消耗了多少”需要引擎侧的追踪工具。

追踪型 Profiler

Unreal Insights 一类的工具记录一段时间内的多通道数据——CPU 各线程的调用栈、GPU 时间、内存分配、加载事件、网络收发——并按同一时间轴对齐。

它与单帧捕获的分工很清楚:单帧捕获回答“这一帧的这次绘制为什么慢”,时间轴追踪回答“卡顿出现在哪一帧、由哪个系统引起”。长时间追踪适合定位偶发卡顿;具备触发捕获或环形记录能力的工具也可保留异常区间,具体能力按平台确认。

使用要点是先划定关注区间。一段几分钟的追踪数据量巨大,应当用自定义标记圈出关卡加载、战斗开始等关键区段,再逐层下钻。

内存分类报告

内存问题需要按类别归因而非只看总量。引擎通常提供两级手段:

  • 全量报告:某一时刻按资源类型、类、包分类的内存清单,适合回答“内存被谁占了”;
  • 统计项埋点:为自定义子系统声明统计项,持续采样其内存占用,适合回答“这个系统的内存是否随时间增长”。

前者是快照,后者是曲线。判断泄漏需要多次快照或持续曲线,单次快照无法区分占用大与持续增长。

构建配置影响测量结果

性能数据必须标注构建配置和实际编译开关。下面列出常见配置约定,项目可以覆盖这些设置,报告以构建日志为准:

配置特点适用
调试无优化,可完整调试定位逻辑错误,不用于性能测量
开发优化开启,保留统计与控制台日常性能分析
发布全优化,剥离调试设施最终性能验收

开发与发布的差距可能达到可观比例——统计埋点、日志、断言本身都有成本。开发与发布的执行路径也可能不同,不能仅按固定比例换算;反过来,发布缺少埋点,难以定位问题来源。

实践做法是用开发定位与迭代,用发布做最终验收,并在报告中始终标明配置。编辑器内运行则是第三种情况,其开销与独立包不同,适合分析编辑器自身;运行时性能验收应以目标平台独立构建为依据。

下方 1440p、6 层和 4.2 毫秒是说明报告格式的假设数据,未由本仓库运行测得。正式记录同时附原始计时文件和捕获定位,避免把写法示例当作性能结论。

证据与假设

将观察、原因假设和可改变的因素写在同一条记录里:

透明粒子在 1440p 覆盖屏幕 6 层,Transparent Pass 占 4.2 ms,GPU 计数器显示颜色/纹理 Bandwidth 接近上限。把远景粒子降到半分辨率并收紧网格后,预计减少带宽和重复绘制。

这条记录给出了像素负载、通道时间和预期改变的资源访问。修改后仍需检查图像及整帧关键路径,才能接受或否定带宽假设。

捕获结果的调查路径

事件列表说明 GPU 接到了什么命令,资源视图说明命令读写了什么。先找到第一份异常输出,再沿写入者和输入向前追踪,才能把最终黑屏缩到一个具体通道。时间线随后回答这个通道是否位于关键路径;状态正确与性能改善各用自己的证据确认。

相关主题

参考资料