线性伸缩空间段类似于线性内存空间段。 但是,伸缩空间段只是地址空间,不能容纳位。
若要保存位,必须分配系统内存页,并且必须重定向地址空间范围以引用这些页面。 内核模式显示微型端口驱动程序(KMD)必须实现 DxgkDdiBuildPagingBuffer 函数,以便DXGK_OPERATION_MAP_APERTURE_SEGMENT和DXGK_OPERATION_UNMAP_APERTURE_SEGMENT操作类型来处理重定向,并且必须按 Display Miniport Driver Driver Driver 的 DriverEntry 中所述公开此函数。 Dxgkrnl 使用要重定向的地址空间范围和引用已分配的物理系统内存页的 MDL 调用 DxgkDdiBuildPagingBuffer 。
KMD 通常通过编程页表(对于视频内存管理器(VidMm)未知)来实现地址空间范围的重定向。
驱动程序必须在DXGK_SEGMENTDESCRIPTOR结构的 Flags 成员中设置 Aperture 位字段标志,以指定线性伸缩空间段。 驱动程序还可以设置以下位字段标志来指示其他段支持:
- CpuVisible 指示该段是 CPU 可访问的。
- CacheCoherent 指示该段与段重定向到的页面的 CPU 保持缓存一致性。
下图显示了线性光圈空间段的可视表示形式。
1. 核心特性
特性 | 说明 |
---|---|
虚拟地址空间 | 仅为地址范围,不包含实际内存(需动态映射物理页)。 |
动态重定向 | 通过 DxgkDdiBuildPagingBuffer 将虚拟地址绑定到系统内存页(MDL 描述)。 |
CPU 默认可见 | 通常用于 CPU 频繁访问的资源(如动态缓冲区)。 |
缓存一致性可选 | 通过 CacheCoherent 标志保持 CPU-GPU 缓存同步 |
2. 段描述符配置(DXGK_SEGMENTDESCRIPTOR)
(1) 基础配置
DXGK_SEGMENTDESCRIPTOR Segment = {.Flags = DXGK_SEGMENT_FLAGS_APERTURE, // 声明为光圈段.BaseAddress = 0x10000000, // 虚拟地址起始.Size = 0x20000000, // 512MB.SegmentId = 2,
};
(2) 可选标志
标志 | 作用 | 适用场景 |
---|---|---|
DXGK_SEGMENT_FLAGS_CPU_VISIBLE | 允许 CPU 访问(通常默认启用) | CPU-GPU 共享数据 |
DXGK_SEGMENT_FLAGS_CACHE_COHERENT | 强制缓存一致性 | 避免手动刷新缓存(如 ARM GPU) |
示例(缓存一致的线性光圈段):
{.Flags = DXGK_SEGMENT_FLAGS_APERTURE | DXGK_SEGMENT_FLAGS_CACHE_COHERENT,.BaseAddress = 0x30000000,.Size = 0x10000000, // 256MB.SegmentId = 3,
}
3. 内存映射流程
(1) 分配虚拟地址范围
- 应用程序请求内存(如 ID3D12Resource 创建)。
- VidMm 选择伸缩段,分配虚拟地址(GPU VA)。
(2) 物理页绑定(重定向)
VidMm 调用 DxgkDdiBuildPagingBuffer,传入:
- 操作类型:DXGK_OPERATION_MAP_APERTURE_SEGMENT(映射)或 UNMAP(解除映射)。
- MDL(Memory Descriptor List):描述系统内存物理页。
- 虚拟地址范围:需绑定的 GPU VA。
KMD 实现重定向:
- 编程 GPU 页表,将 GPU VA 映射到 MDL 中的物理页。
- 若启用 CACHE_COHERENT,需配置 GPU 缓存策略。
代码示例(映射操作):
NTSTATUS DxgkDdiBuildPagingBuffer(DXGKARG_BUILDPAGINGBUFFER* pArgs) {if (pArgs->Operation == DXGK_OPERATION_MAP_APERTURE_SEGMENT) {// 获取 MDL 中的物理页PHYSICAL_ADDRESS physAddr = MmGetMdlPfnArray(pArgs->pMdl)[0];// 编程 GPU 页表ProgramGpuPageTable(pArgs->VirtualAddress, physAddr);}return STATUS_SUCCESS;
}
4. 典型应用场景
(1) 动态顶点/索引缓冲区
需求:CPU 每帧更新数据,GPU 读取。
配置:
{.Flags = DXGK_SEGMENT_FLAGS_APERTURE | DXGK_SEGMENT_FLAGS_CACHE_COHERENT,.BaseAddress = 0x20000000,.Size = 0x08000000, // 128MB
}
(2) 分页资源(Pageable Resources)
需求:按需加载大型纹理。
流程:
- 初始分配虚拟地址(不绑定物理页)。
- 访问时触发缺页中断,VidMm 调用 DxgkDdiBuildPagingBuffer 动态映射。
(3) 多 GPU 共享资源
- 需求:数据在多个 GPU 间迁移。
- 优势:虚拟地址固定,仅需更新物理页映射。
5. 驱动开发注意事项
(1) 页表管理
- GPU 页表独立于 CPU:需驱动自行维护 GPU MMU(内存管理单元)的页表。
- TLB 一致性:映射/解除映射后,需无效化 GPU TLB(如通过 DXGK_INVALIDATE_TLB)。
(2) 性能优化
- 批处理映射操作:合并多次映射请求,减少 GPU 上下文切换。
- 避免过度分片:尽量分配连续物理页(减少页表项数量)。
(3) 错误处理
- 物理页不足:返回 STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER。
- 非法虚拟地址:验证 VirtualAddress 是否在段范围内。
6. 与线性内存空间段的对比
特性 | 线性光圈空间段 | 线性内存空间段 |
---|---|---|
物理内存 | 动态绑定系统内存页 | 直接分配显存 |
地址转换 | 需 GPU 页表映射 | 直接访问(无转换) |
CPU 访问 | 默认支持 | 需显式启用 (CPU_VISIBLE ) |
性能 | 较低(转换开销) | 高(零开销) |
适用场景 | CPU 频繁写、分页资源 | 高性能渲染目标 |
7. 可视化表示
GPU 虚拟地址空间:
0x10000000 ┌───────────────────────┐ ← 光圈段起始(BaseAddress)│ Virtual Range │ │ (No Physical Memory)│ ├───────────────────────┤ ← 映射操作后│ Mapped to │ │ System Memory (MDL) │ ← 物理页动态绑定
0x30000000 └───────────────────────┘ ← 段结束
8. 总结
线性伸缩空间段 = 虚拟地址 + 动态物理页绑定,适用于需灵活内存管理的场景。
关键驱动实现:
- 处理 DXGK_OPERATION_MAP_APERTURE_SEGMENT/UNMAP 操作。
- 维护 GPU 页表,确保地址转换正确。
优势:
- 支持 CPU 高效读写。
- 实现按需分页和资源共享。
通过合理使用伸缩段,驱动程序可以在保证功能性的同时,优化复杂应用场景下的内存利用率。