You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取SharpDX/DirectX 11中帧绘制后GPU的等待时长

解决方案:DirectX 11/SharpDX 中追踪帧渲染到显示的延迟及输入到输出的延迟

一、获取帧绘制完成到GPU送显的时长

核心思路

在无VSYNC、高帧率(1000FPS)场景下,帧渲染完成后会进入GPU显示队列,等待显示器下一次刷新周期。我们通过GPU时间戳查询获取GPU完成渲染的时间点,结合显示器刷新率计算帧实际显示的时间点,两者差值即为目标时长。

具体实现步骤

  1. 初始化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);
    }
    
  2. 记录GPU渲染完成时间
    在每帧所有渲染调用(Draw/Compute等)执行完毕后,插入结束时间戳查询:

    public void RenderFrame()
    {
        // 清除渲染目标、执行Draw/Compute调用等前置逻辑
        // ...你的渲染代码...
        
        // 记录GPU完成当前帧渲染的时间点
        DeviceContext.End(_timestampEndQuery);
        
        // 提交帧到交换链
        SwapChain.Present(0, PresentFlags.None);
    }
    
  3. 计算渲染到显示的延迟
    在后续帧中检查查询结果,将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时间点,将其与处理该输入的帧关联,最终计算该帧显示时间与输入时间的差值。

具体实现步骤

  1. 记录输入事件时间戳
    在捕获输入事件(如键盘/鼠标消息)时,用QueryPerformanceCounter记录精确时间:

    private long _lastInputTimestamp;
    private bool _isInputPending;
    
    // 示例:鼠标点击事件处理
    private void OnMouseDown(object sender, MouseEventArgs e)
    {
        QueryPerformanceCounter(out _lastInputTimestamp);
        _isInputPending = true;
    }
    
  2. 关联输入与对应帧
    在处理输入的帧中,将输入时间戳绑定到当前帧:

    private long _inputTimestampForCurrentFrame;
    
    public void RenderFrame()
    {
        if (_isInputPending)
        {
            _inputTimestampForCurrentFrame = _lastInputTimestamp;
            _isInputPending = false;
            // 执行输入对应的业务逻辑(如角色移动、触发交互)
            // ...你的输入处理代码...
        }
        
        // 执行渲染逻辑、插入时间戳查询、提交帧
        // ...同之前的渲染代码...
    }
    
  3. 计算输入到输出的延迟
    在获取到该帧的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 01:17:02