本节摘要:显存管理在 D3D 12 里从黑箱变成明账:堆放置、预算申报、驻留分级三件事构成完整的粮仓管理。本节给出放置决策的实操标准、预算 API 的用法,以及流式加载的呼吸节律设计。
D3D 11 时代显存是黑箱:驱动替你搬,爆了就偷偷分页,程序卡一下但活着。D3D 12 把账本交给你:资源放哪个堆、何时在场、何时退场都要显式经营。这带来一个角色转变——图形程序员要像仓库管理员一样做预算。好消息是账本虽显式,规则却不复杂:三个决策点想清楚,显存管理就从玄学变成工程。
3.4 节建过堆类型的概念,这里给实操标准。默认堆(DEFAULT):GPU 专属显存,读性能最好——长期存在的静态资源(网格、纹理、已烘焙数据)的归宿,代价是 CPU 不能直写,填充要走"上传堆中转 + 一次性拷贝"。上传堆(UPLOAD):CPU 可写——常量缓冲这类每帧更新数据的家,也是静态资源入场的临时月台。回读堆(READBACK):CPU 可读——截图、性能统计回传的收发室。
实操上有一个经典优化:静态资源的两段式入场。数据先写进上传堆临时区,录一条拷贝命令搬到默认堆,然后释放上传堆那份数据。3.2 节演示时顶点缓冲直接常驻上传堆是简化教学——正式项目里,高频读取的静态资源常驻上传堆会白白损失读取性能。
显存不是无限粮仓,爆了的表现不是崩溃而是性能骤降——分页搬运开始侵蚀帧时间。应对是预算申报制度:
// 查询适配器的显存账本 DXGI_QUERY_VIDEO_MEMORY_INFO info{}; adapter->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info); // info.Budget = 系统批给进程的额度 // info.CurrentUsage = 当前已用 // 主动申报:本进程计划长期占用多少,供系统调度参考 DXGI_MEMORY_RESERVATION memRes{}; adapter->SetVideoMemoryReservation(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &memRes);
预算经营的纪律:给系统留余量——把预算打到额度八九成为止,操作系统、浏览器、驱动自己都要用显存;按场景分级——大厅、战斗、过场的资源集分别编目,场景切换时按册换装;监控入码——把 CurrentUsage 记进性能日志,显存爬坡在测试期就能看见,而不是上线后才爆。
显存放不下整个世界的资源时,需要驻留管理:声明哪些资源必须常驻、哪些可以按需请回。API 层面是成对的操作——把资源标记为"将要用,请留在场内"或"可以退场";配合优先级,系统在压力下按优先级决定谁先退。这是"全有或全无"到"按需呼吸"的转变。转变的另一面是责任:声明了"将要用"却长期不用,等于占着名额不办事——驻留声明的纪律要与下一节的预算表连着看,别让"护住常驻"的初衷变成"全家常驻"的挥霍。

值得把"爆显存"的病理讲透,因为它的账面后果常被误解。超过预算后程序不会崩——系统把住不下的资源驱逐到系统内存,GPU 需要时再经总线搬回来。这条路的代价是数量级的:显存带宽以每秒数百 GB 计,总线搬运以每秒几十 GB 计,一次驱逐回迁就是几十毫秒的坑。所以"爆显存"的真实症状是帧时间曲线上密密麻麻的尖刺,平均帧率看着还行,玩起来一卡一卡。诊断手段就在决策点二的账本里:CurrentUsage 贴着 Budget 跑的时段,就是尖刺高发期。有一类隐形消耗要特别点名:对端拷贝与临时纹理——调试工具、截图功能、分辨率切换的中间缓冲,平时看不见,凑热闹时恰好压垮预算。显存日志里给每笔大额分配打上用途标签,是能救命的纪律。
要,而且要做成机制而不是补丁。做法是把显存档位做成画质分级的输入:启动时查询预算,按档位选资源集——高档用全分辨率纹理集与完整粒子池,低档用减半的 mip 上限与精简变体。这条纪律的反面是"按理想配置写死":开发机 12 GB 显存跑得欢,玩家 6 GB 卡上开场三分钟准时爆账。测试矩阵里必须有低显存档,Q&A 也要有"预算七成"的验收线。顺带一提,共享显存的核显是另一档算术:它没有独立显存段,预算来自系统内存,带宽低一个量级——上一章功能级别协商时判定的适配器类型,此处要再次入账。
预算经营落到纸上,就是一张按场景分册的账目表。给个可以直接抄的骨架:
场景:主城 · 预算上限 4.5 GB(额度 5 GB 的九成) 常驻层(不可退场)........ 1.2 GB 交换链与 UI 图集 ......... 200 MB 角色基础网格与动画 ....... 600 MB 全局材质库 ................ 400 MB 场景层(按区块进出)...... 2.4 GB 建筑与地形贴图集 ......... 1.6 GB 植被与道具 ............... 800 MB 弹性层(可降级可驱逐).... 900 MB 流式缓冲与临时目标 ....... 600 MB 特效池冗余 ................ 300 MB 实测用量 .................. 4.1 GB ← 与预算的差值就是安全垫
这张表的价值不在精确数字,而在分层结构:常驻、按需、弹性三层各有进出规则,显存压力来临时先退弹性层、再收场景层、常驻层雷打不动——驱逐的优先级在装机时就定好,而不是运行时临场纠结。每层配一行实测用量日志,爬坡趋势在测试期就盯住。表本身入代码库随版本演进,新场景上架先填表再审——显存预算从口头约定变成可评审的资产,6.3 节的全部机制就都有了落脚点。
背景:开放场景飞行时周期性卡顿,PIX 显示卡顿帧集中在大批拷贝命令同时执行。操作:把纹理上传从直接队列挪到拷贝队列并限量——单帧传输配额封顶;配合距离驱动的吸呼策略,把"玩家前方扇区优先上传"做成优先级队列。结果:传输被摊平到无感,卡顿帧消失,边缘区域的资源到达略晚但提前距离保证玩家看不见缺口。解读:流式加载的三个关键参数——提前距离(决定"吸"的时机)、传输配额(决定帧时间抖动)、退场滞后(防止边界抖动导致资源反复进出)。变式:室内小场景不需要呼吸系统,全量常驻反而简单可靠——机制复杂度要匹配场景规模,别为小仓库建调度中心。中型场景另有折中:"半呼吸"方案——全量常驻但分块加载,只做优先级不做驱逐,够用即停。
粮仓管好了,出院前做最后一次体检:常见陷阱清单——把本章与前面各章的翻车现场串成检查表。