如何获取SharpDX/DirectX 11中帧绘制后GPU的等待时长
解决方案:DirectX 11/SharpDX 中追踪帧渲染到显示的延迟及输入到输出的延迟
一、获取帧绘制完成到GPU送显的时长
核心思路
在无VSYNC、高帧率(1000FPS)场景下,帧渲染完成后会进入GPU显示队列,等待显示器下一次刷新周期。我们通过GPU时间戳查询获取GPU完成渲染的时间点,结合显示器刷新率计算帧实际显示的时间点,两者差值即为目标时长。
具体实现步骤
初始化GPU时间戳查询
在DirectX 11设备初始化阶段,创建时间戳查询对象:using SharpDX.Direct3D11; using SharpDX.DXGI; using SharpDX.Mathematics.Interop; // 全局变量 private Query _timestampEndQuery; private QueryDescription _timestampQueryDesc; // 设备初始化时调用 private void InitTimestampQueries(Device device) { _timestampQueryDesc = new QueryDescription { Type = QueryType.Timestamp, Flags = QueryFlags.None }; _timestampEndQuery = new Query(device, _timestampQueryDesc); }记录GPU渲染完成时间
在每帧所有渲染调用(Draw/Compute等)执行完毕后,插入结束时间戳查询:public void RenderFrame() { // 清除渲染目标、执行Draw/Compute调用等前置逻辑 // ...你的渲染代码... // 记录GPU完成当前帧渲染的时间点 DeviceContext.End(_timestampEndQuery); // 提交帧到交换链 SwapChain.Present(0, PresentFlags.None); }计算渲染到显示的延迟
在后续帧中检查查询结果,将GPU时间戳转换为毫秒,结合显示器刷新率计算显示时间:private long _gpuFrequency; private float _displayFrameDurationMs; // 初始化时获取GPU时钟频率和显示器刷新率 private void InitTimingInfo(Device device, SwapChain swapChain) { // 获取GPU时钟频率(用于转换时间戳为实际时间) device.CheckTimestampFrequency(out _gpuFrequency); // 获取当前显示器的刷新率 var output = swapChain.GetContainingOutput(); var displayModeList = output.GetDisplayModeList(Format.R8G8B8A8_UNorm, DisplayModeEnumerationFlags.Interlaced); var currentMode = displayModeList[0]; // 匹配SwapChain实际使用的显示模式 float refreshRate = currentMode.RefreshRate.Numerator / (float)currentMode.RefreshRate.Denominator; _displayFrameDurationMs = 1000f / refreshRate; } // 每帧更新时处理查询结果 public void UpdateRenderToDisplayDelay() { if (DeviceContext.GetData(_timestampEndQuery, out long endTimestamp)) { // 转换GPU时间戳为毫秒 float gpuRenderCompleteMs = (float)(endTimestamp * 1000.0 / _gpuFrequency); // 获取当前CPU时间,计算到下一次显示器刷新的剩余时间 QueryPerformanceCounter(out long cpuPresentTime); QueryPerformanceFrequency(out long cpuFreq); float cpuPresentMs = (float)(cpuPresentTime * 1000.0 / cpuFreq); float timeSinceLastRefresh = cpuPresentMs % _displayFrameDurationMs; float timeToDisplay = _displayFrameDurationMs - timeSinceLastRefresh; // 最终的渲染完成到送显时长 float renderToDisplayDelay = timeToDisplay; // 可将该值输出或存入日志 } }
二、获取输入操作到GPU输出结果的时长
核心思路
追踪输入事件发生的CPU时间点,将其与处理该输入的帧关联,最终计算该帧显示时间与输入时间的差值。
具体实现步骤
记录输入事件时间戳
在捕获输入事件(如键盘/鼠标消息)时,用QueryPerformanceCounter记录精确时间:private long _lastInputTimestamp; private bool _isInputPending; // 示例:鼠标点击事件处理 private void OnMouseDown(object sender, MouseEventArgs e) { QueryPerformanceCounter(out _lastInputTimestamp); _isInputPending = true; }关联输入与对应帧
在处理输入的帧中,将输入时间戳绑定到当前帧:private long _inputTimestampForCurrentFrame; public void RenderFrame() { if (_isInputPending) { _inputTimestampForCurrentFrame = _lastInputTimestamp; _isInputPending = false; // 执行输入对应的业务逻辑(如角色移动、触发交互) // ...你的输入处理代码... } // 执行渲染逻辑、插入时间戳查询、提交帧 // ...同之前的渲染代码... }计算输入到输出的延迟
在获取到该帧的GPU完成时间和显示时间后,计算差值:public void UpdateInputToOutputDelay() { if (DeviceContext.GetData(_timestampEndQuery, out long endTimestamp)) { QueryPerformanceFrequency(out long cpuFreq); // 转换输入时间为毫秒 float inputTimeMs = (float)(_inputTimestampForCurrentFrame * 1000.0 / cpuFreq); // 计算当前帧的显示时间 QueryPerformanceCounter(out long cpuPresentTime); float cpuPresentMs = (float)(cpuPresentTime * 1000.0 / cpuFreq); float timeSinceLastRefresh = cpuPresentMs % _displayFrameDurationMs; float frameDisplayTimeMs = cpuPresentMs + (_displayFrameDurationMs - timeSinceLastRefresh); // 输入到输出的时长 float inputToOutputDelay = frameDisplayTimeMs - inputTimeMs; // 可将该值输出或存入日志 } }
注意事项
- 异步查询处理:GPU时间戳查询是异步的,需在后续帧中检查是否完成,不能在当前帧直接获取结果。
- 显示模式匹配:确保获取的显示模式与SwapChain的格式、分辨率一致,避免刷新率计算错误。
- 资源清理:程序退出时需释放
Query对象,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Andymanic
相关产品推荐
相关产品推荐

