1. 修补阶段的核心作用
graph TD
A[UMD提交命令] --> B(生成DMA缓冲区)
B --> C[资源虚拟地址]
C --> D{DxgkDdiPatch}
D -->|插入物理地址| E[可执行DMA缓冲区]
E --> F[GPU执行]
- 地址转换桥梁:将UMD提供的虚拟资源引用转换为硬件可识别的物理地址
- 内存虚拟化关键:支持VidMm动态迁移资源内存位置(显存↔系统内存)
- 安全隔离:确保用户模式无法直接操作物理地址
2. 修补流程详解
(1) 触发时机
在以下操作后由GPU调度器触发:
- 分页操作完成(DxgkDdiBuildPagingBuffer)
- 资源驻留状态变更(显存不足时逐出到系统内存)
(2) 内核驱动处理(DxgkDdiPatch)
NTSTATUS DxgkDdiPatch(
IN_CONST_HANDLE hDevice,
INOUT_PDXGKARG_PATCH pPatchArgs)
{
// 1. 遍历修补位置列表
for (UINT i = 0; i < pPatchArgs->PatchLocationListSize; i++) {
DXGK_PATCHLOCATION* pLoc = &pPatchArgs->pPatchLocationList[i];
// 2. 从VidMm获取物理地址
PHYSICAL_ADDRESS pa = VidMmGetPhysAddr(pLoc->hAllocation);
// 3. 写入DMA缓冲区指定位置
WriteGpuAddress(
pPatchArgs->pDmaBuffer + pLoc->DmaBufferOffset,
pa + pLoc->AllocationOffset);
}
return STATUS_SUCCESS;
}
(3) 关键数据结构
// 修补位置描述符
struct DXGK_PATCHLOCATION {
UINT DmaBufferOffset; // DMA缓冲区中的偏移量(字节)
HANDLE hAllocation; // 资源分配句柄
UINT AllocationOffset; // 资源内部的偏移量
UINT PatchType; // 地址类型(如32/64位)
};
// DMA缓冲区示例(修补前)
struct DmaBuffer {
BYTE commandHeader[16];
UINT64 textureVirtualAddr; // 待修补位置
// ...其他命令数据
};
3. 硬件兼容性处理
(1) 不同地址类型处理
void WriteGpuAddress(void* pDest, PHYSICAL_ADDRESS pa) {
switch (patchType) {
case DXGK_PATCH_32BIT:
*(UINT32*)pDest = (UINT32)pa.QuadPart;
break;
case DXGK_PATCH_64BIT:
*(UINT64*)pDest = pa.QuadPart;
break;
case DXGK_PATCH_GPUVA: // GPU虚拟地址
*(GPU_VIRTUAL_ADDRESS*)pDest = ConvertToGpuVa(pa);
break;
}
}
(2) 多GPU架构适配
GPU类型 | 修补特点 | 驱动实现要点 |
---|---|---|
独立显卡(dGPU) | 物理地址为显存绝对地址 | 直接写入PCI BAR空间偏移量 |
集成显卡(iGPU) | 物理地址为系统内存地址 | 需转换为GTT(图形转换表)索引 |
UMA架构 | 统一内存空间,地址连续 | 可跳过部分修补操作 |
4. 性能优化技巧
(1) 批处理修补请求
// 合并连续修补位置(示例优化)
for (UINT i = 0; i < listSize; ) {
if (IsContiguousPatch(&list[i], &list[i+1])) {
PerformBulkPatch(&list[i], 2); // 批量处理
i += 2;
} else {
PerformSinglePatch(&list[i]);
i++;
}
}
(2) 缓存友好布局
// 优化前(碎片化修补)
DmaBuffer: [Draw][TexAddr1][Transform][TexAddr2]
// 优化后(集中修补)
DmaBuffer: [TexAddr1][TexAddr2][Draw][Transform]
5. 安全与错误处理
(1) 验证机制
# 查看待修补DMA缓冲区
!dxgkd_ext.dmabuffer 0xADDR -patch
# 检查资源物理地址
!dxgkd_ext.allocation 0xHANDLE -physaddr
(2) 错误恢复策略
错误类型 | 恢复动作 |
---|---|
无效分配句柄 | 标记设备为错误状态,触发TDR |
偏移量越界 | 丢弃当前DMA缓冲区,记录调试信息 |
硬件访问违例 | 重置GPU引擎,重建命令队列 |
6. 调试与诊断
(1) WinDbg扩展命令
# 查看待修补DMA缓冲区
!dxgkd_ext.dmabuffer 0xADDR -patch
# 检查资源物理地址
!dxgkd_ext.allocation 0xHANDLE -physaddr
(2) ETW事件日志
// 驱动中添加诊断事件
void TracePatchEvent(DXGK_PATCHLOCATION* pLoc) {
EVENT_DATA_DESCRIPTOR desc[3];
EventDataDescCreate(&desc[0], &pLoc->hAllocation, sizeof(HANDLE));
EventDataDescCreate(&desc[1], &pLoc->DmaBufferOffset, sizeof(UINT));
EventWrite(g_ProviderHandle, &EVT_PatchOperation, 3, desc);
}
7. 现代演进(Windows 10+)
(1) GPU虚拟地址支持
// DX12引入的GPUVA模式
if (Caps.SupportsGpuVirtualAddress) {
pLoc->PatchType = DXGK_PATCH_GPUVA; // 避免频繁修补
}
(2) 直接捕获支持
// 通过DXGK_PATCHFLAG_NO_PATCH跳过冗余修补
if (IsReplayMode()) {
pPatchArgs->Flags |= DXGK_PATCHFLAG_NO_PATCH;
}
最佳实践总结
- 最小化修补点:合并资源绑定操作
- 预计算偏移量:UMD应在生成命令时记录精确修补位置
- 处理多引擎:为3D/计算/拷贝引擎维护独立修补逻辑
- 监控修补开销:通过!dxgkd_ext.patchstats分析性能瓶颈