年度总结之二:GPU Driver

本文详细解析了Direct3D驱动的工作流程,包括DMA buffer的分配、command buffer的使用及GPU执行过程。针对ATI显卡游戏卡顿问题,探讨了DMA buffer调度不当导致的性能瓶颈,并提出解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

关于Direct3D驱动流程的大致描述

                     

 

 

 

上图的流程可以知道, 任何一个有界面的程序, 如果要显示在具备独立显卡的显示器上首先第一步要做的就是在显存中分配空间, 此内核函数DxgkDdiCreateDevice(DirectX graphic kernel driver device interface create deivce的缩写)是由显卡厂商提供的, display miniport driver, 主要起到在显存中分配空间的作用, 注意这个分配的空间放的是GPU可执行代码, 分配空间的名字叫DMA buffer, DMA Direct Memory Access的缩写, 是计算机中的硬件, DMA bufferDMA可以访问的buffer, 通过DMA硬件, 可以大大提到向DMA buffer写入数据的能力. 上面进行的操作都是在内核态下,  所以上面的DMA buffer, 和函数,在用户态下都访问不了, 要访问的话, 必须通过系统提供给用户层的api. 当用户调用DirectX3D Run Time提供的CreateDevice,   CreateDeivce其实是一个虚函数, 当编译器工作时, CreateDevice形成的代码其实是一个在虚拟表中的偏移量, 虚拟表是一个放函数地址地方. 然后直接Call这个地址经过Direc3D Runtime的一序列函数的处理, 最终调用use-mode display drive(用户态显示驱动)中的CreateDeivce函数, 而这个函数调用Direct3D runtime中提供的pfnCreateContextCb(point function create context call back) 这个函数主要的作用是在系统内存或者AGP内存中分配command buffer, command buffer就是放GPU执行指令的地方, 通常这个command buffer的大小是固定的,,返回一个主要是command buffer的结构(此结构描述context, 也就是主要描述command buffer, 比如它的地址, 当然此结构还描述了其它东西), 到画图的最后阶段, command buffer最终会被GPU处理。当用户调用Direct3D Run time中分配资源的函数时, 比如类似CreateResource的函数, 然后Direct3D run time调用use-mode driver中的CreateResource, use-mode driver中的函数最终调回Direct3D run time中的分配资源的函数, 通过回调函数pfnAllocationCb, 此函数最终通过DirectX  Graphics  kernel  subystem调用display miniport driver中的DxgkDdiCreateAllocation函数, 此函数主要是在显存或者AGP内存或者 系统内存中分配资源空间。由此可见 use-mode display driver主要起到钩子的作用, 也就是截获Direct3D的执行流程, 然后执行自己的处理, 然后又回调回去, Direct3D Run time应该是指d3d9.dll, 等其它文件。Direct3D run time中画图操作函数最终调用use-mode display driver中的draw图函数, 联想一下刚才的command buffer, 由此可见此函数就是把draw图函数转化为GPU能够识别的command, 并把这些command 放到command buffer。当用户调用present, Direct3D run time调用use-mode display driver中的present的函数, 而这个函数的执行又通过回调函数pfnPresentCB,  返回Direct3D run time执行,有另一种情况, 也就是command buffer满了的时候, Direct3D run time会自动调用      use-mode display driver中的flush函数, 此函数也一样通过pfRenderCb回调给Direct3D run time执行.

现在已经分为两种不同的执行流程, 一个present, 一个是flush, 两者执行的操作是不一样的, present描述如下: Direct3D run time通过系统api, 进入到内核态中的directx  graphic kernel subsytem中执行,  然后 graphic kernel紧接着调用显卡驱动中的DxgkDdiPresent函数(也就是display miniport driver中的函数), 从而把command buffer中的GPU可执行代码, 通过DMA传输到GPU能访问的显存中,  紧接着grapphic kernel subsytem调用display miniport driver中的DxgkDdiBuildPagingBuffer, 此函数是建立一个以page(, 每页在目前系统通常为4kb)为单位的buffer, buffer里面放的主要是移动修改内存的代码 (比如把不在video中的资源(page出去的资源), 移动(page)video), 然后把这些paging buffer提交给GPU可执行单元去执行, 最后一步就是调用DxgkDdiPath修改DMA buffer中的资源的物理地址(比如移动到video中的资源, 他有新的地址), 通过调用DxgkSubmiCommand命令, DMA buffer放入一个队列GPU执行单元完成这个DMA buffer, 就在DxgkDdiInterupteRoutine中返回true, 在此函数返回之前, 同时通过回调DxgkCbNotifyInterrupt通知graphic kernel subsystem已经完成了一个DMA buffer, graphic kernel subsytem做好善后处理, 同时通过回函数DxgkCbQueueDpc, 在此DMA buffer对应的线程中一个队列中放一个delay procedure call,  当线程IRQLIRQL DISPATCH_LEVEL,IRQL_DISPATH_PASSIVE_LEVEL 或其它更低的中断权限时, DPC得到处理, 一般处理方法是调用程序中设置的回调函数(记住每个线程有每个线程的GPU context)。Flush的函数执行流程就是present的前半部, 即把command buffer转移到DMA buffer. Flush多了, 应用程序的效率就低, 因为从用户态到内核态进行的操作比较多。注意:DMA buffer耗的资源内存较多时, 可能分为几个小的DMA buffer, 当需要分配的Page buffer > 4kb, 可能分为多page buffer。

 

 

上面的文章详细的描述了vista下的D3D驱动流程,在xp下的没找到。现在讲一下我在项目中遇到的实际问题,在引擎整改到尾声时,发现一个bug,游戏在ATI显卡的机器上运行是就会出现卡顿的现象,具体说就是,某一帧的渲染时间徒增的现象。在NV显卡的机器上出现的较少,后来通过在每帧渲染开始时,添加代码:while(S_FALSE == m_pQuery->GetData( NULL, 0, D3DGETDATA_FLUSH ));就解决了问题。但是原来出问题的机器的游戏帧率下降了不少。根据上文的分析,我认为是显卡驱动的问题,即某些驱动的DMAbuffer的调度处理不当,导致了过一段时间就会出现DMABuffer充满的情况,而这时,D3D再present就会不起作用,只能等待。而添加了上面的代码就在每帧开始前,主动等待GPU空闲,即处理完所有的DMAbuffer才继续,这样虽然能解决问题,但是也造成了CPU的idle,最终导致帧率的下降。希望以后能有更深入的了解,分析出真正的原因。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值