zoukankan      html  css  js  c++  java
  • 利用D3DQUERY实现简单的GPU计时器

    最近想给引擎加一个准确定位各渲染模块消耗的功能,因此翻阅资料,自己实现了一个简短的GPU时钟。在引擎中整合如下:

    值得注意的是,使用硬件查询后,回读显卡中的值会导致CPU/GPU的等待,因而会损失一些时间。因此这个功能只能算作开发时的调试工具之用(真正产品不会有人关心GPU时间...)

    我昨晚花了少量时间做了一个简单的实现,现在在这里贴出来,基本是全部代码,摘回去可直接使用~ 如有设计上的疏忽还请见谅!

    改计时器使用十分方便,集成好之后,测量每个阶段的时间,只需补上3条代码,即可~

    原理

    D3D文档中提到了有这样一个硬件查询类型:D3DQUERYTYPE_TIMESTAMP

    改硬件查询会在查询的地方,返回一个UINT64类型的当前的GPU TICK值,因此,可以使用这个来取得每个模块的开始和结束TICK,在每帧结束的时候GETDATA,计算出每一个阶段的耗时

    此外,D3DQUERYTYPE_TIMESTAMPFREQ这个查询类型,可以返回时钟频率,这时我们便可以通过  (结束TICK - 开始TICK) / 时钟频率 来得出GPU中这一段的耗时了。

    实现

    因此,我们可以实现一个简单的GPUTIMER类:

    class gkGpuTimer
    {
    public:
    gkGpuTimer();
    virtual ~gkGpuTimer() {}

    float getTime() {return m_fElapsedTime;}

    // init D3DQUERY
    void init(IDirect3DDevice9* pDevice);
    void destroy();

    // mark the timestamp
    void start();
    void stop();

    // get timestep when finish a frame
    void update();

    private:
    IDirect3DQuery9* m_pEventStart;
    IDirect3DQuery9* m_pEventStop;
    IDirect3DQuery9* m_pEventFreq;

    float m_fElapsedTime;
    bool m_skip; // judge if should get data
    };

    很简单

    一对init(),destroy(),在设备创建和丢失的时候对D3D对象进行重建和释放

    一对start(),stop(),在需要测量的代码段两边放置,分别取得开始和结束的TIMESTAMP和FREQ

    一个update(),在每帧结束的时候,从GPU取得测量值,计算之后存储于m_fElapsedTime中

    m_skip用于判断是否应该从GPU取值,因为由于渲染流程的改变,有些计时器可能在一些情况下并不会测量。

    下面看一下简单的实现

    (注: gEnv->pCVManager->r_ProfileGpu == 1 是我引擎中的参数,为1时表示打开GPU计时

    gkGpuTimer::gkGpuTimer()
    {
    m_pEventStart = NULL;
    m_pEventStop = NULL;
    m_pEventFreq = NULL;
    m_fElapsedTime = 0.0f;
    m_skip = true;
    }

    void gkGpuTimer::init(IDirect3DDevice9* pDevice)
    {
    // initialze the querys [2/2/2012 Kaiming]
    pDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMP, &m_pEventStart);
    pDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMP, &m_pEventStop);
    pDevice->CreateQuery(D3DQUERYTYPE_TIMESTAMPFREQ, &m_pEventFreq);
    }

    void gkGpuTimer::destroy()
    {
    SAFE_RELEASE( m_pEventStart );
    SAFE_RELEASE( m_pEventStop );
    SAFE_RELEASE( m_pEventFreq );
    }

    void gkGpuTimer::start()
    {
    if( gEnv->pCVManager->r_ProfileGpu == 1 )
    {
    // get start time stamp and freq
    if (m_pEventStart)
    m_pEventStart->Issue(D3DISSUE_END);
    if (m_pEventFreq)
    m_pEventFreq->Issue(D3DISSUE_END);
    }

    m_skip = false;
    }

    void gkGpuTimer::stop()
    {
    if( gEnv->pCVManager->r_ProfileGpu == 1 )
    {
    // get end time stamp
    if (m_pEventStop)
    m_pEventStop->Issue(D3DISSUE_END);
    }

    m_skip = false;
    }

    void gkGpuTimer::update()
    {
    if( gEnv->pCVManager->r_ProfileGpu == 1 )
    {
    UINT64 startTime = 0;
    UINT64 endTime = 0;
    UINT64 freq = 1;

    if (!m_skip)
    {
    // at the end of a frame, getdata from device
    while(S_FALSE == m_pEventStart->GetData( (void *)&startTime, sizeof(UINT64), D3DGETDATA_FLUSH ))
    ;
    while(S_FALSE == m_pEventStop->GetData( (void *)&endTime, sizeof(UINT64), D3DGETDATA_FLUSH ))
    ;
    while(S_FALSE == m_pEventFreq->GetData( (void *)&freq, sizeof(UINT64), D3DGETDATA_FLUSH ))
    ;

    // calculate the elapsedtime in microseconds
    m_fElapsedTime = (float)(endTime - startTime) / (float)(freq) * 1000.0f;
    }
    else
    {
    m_fElapsedTime = 0.0f;
    }
    }
    m_skip = true;
    }

    最后,就是集成到渲染流程中了,我实现了几个简单函数,在渲染流程的恰当阶段调用即可

    (注:ms_GPUTimers为我渲染器的一个静态成员)

    typedef std::map<std::wstring, gkGpuTimer> gkGpuTimerMap; 
    // build gpu timers when init renderer
    void gkRendererD3D9::buildGpuTimers()
    {
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"GPUTime", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"ReflectGen", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"ShadowMapGen", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Zpass", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"SSAO", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"ShadowMaskGen", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Deferred Lighting", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Ambient Pass", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Lights", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Opaque", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"Transparent", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"HDR", gkGpuTimer()) );
    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"PostProcess", gkGpuTimer()) );
    }

    // init gpu timers when device reset
    void gkRendererD3D9::initGpuTimers(IDirect3DDevice9* pDevice)
    {
    gkGpuTimerMap::iterator it = ms_GPUTimers.begin();
    for (; it != ms_GPUTimers.end(); ++it)
    {
    it->second.init(pDevice);
    }
    }

    // release gpu timers when device lost
    void gkRendererD3D9::destoryGpuTimers()
    {
    gkGpuTimerMap::iterator it = ms_GPUTimers.begin();
    for (; it != ms_GPUTimers.end(); ++it)
    {
    it->second.destroy();
    }
    }

    // getdata from gpu when a frame end
    void gkRendererD3D9::updateGpuTimers()
    {
    if(gEnv->pCVManager->r_ProfileGpu == 1)
    {
    gkGpuTimerMap::iterator it = ms_GPUTimers.begin();
    for (; it != ms_GPUTimers.end(); ++it)
    {
    it->second.update();
    }
    }
    }

    使用了map结构,因此在设置计时器的时候就特别方便了,比如:

    gkRendererD3D9::ms_GPUTimers[L"SSAO"].start();
    // ssao render process...
    gkRendererD3D9::ms_GPUTimers[L"SSAO"].stop();

    添加完毕后,在想要拿时间的地方,

    gkRendererD3D9::ms_GPUTimers[L"SSAO"].getTime()

    即可。

    总结

    自此,GPUTIMER全部完成,用了不到200行代码。

    使用上,就是

    ms_GPUTimers.insert( gkGpuTimerMap::value_type( L"测量名", gkGpuTimer()) );
    gkRendererD3D9::ms_GPUTimers[L"测量名"].start();
    gkRendererD3D9::ms_GPUTimers[L"测量名"].stop();

    三条代码,当然如果加上一些宏技巧,可以更加工程化一些~

    KISS



    作者:gameKnife
    出处:http://gameknife.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    重写方法,重载方法,虚方法和抽象方法的使用
    基类和派生类
    C#修饰符讲解大全
    通过HTTP请求WEBAPI的方式
    计算机各种协议讲解
    时间戳
    SQL Server知识详解
    基本概念和术语
    22.C++- 继承与组合,protected访问级别
    22.QT-QXmlStreamReader解析,QXmlStreamWriter写入
  • 原文地址:https://www.cnblogs.com/gameknife/p/2337539.html
Copyright © 2011-2022 走看看