UE 5.8.1 · D3D12RHI · Windows

D3D12 显存分配全景

引擎里的一次 CreateBuffer / CreateTexture,最后落在哪个 ID3D12Heap 的哪个偏移?本文按资源类型逐条追踪路由,拆开每个分配器的工作机制,说明它为什么这样设计,并把对齐与尺寸如何在半路改变路由讲清楚。

依据:引擎源码(Windows 平台 USE_BUFFER_POOL_ALLOCATOR = 1、USE_TEXTURE_POOL_ALLOCATOR = 1,旧版 Buddy 纹理/Buffer 池在本平台已编译剔除)与 Microsoft D3D12 官方文档。所有数值为代码默认值,可调项注明 CVar。设计理由分三种来源标注:源码注释官方文档推断

D3D12 创建资源只有三个 API:CreateCommittedResource、CreatePlacedResource(放进 CreateHeap 建的显式 Heap)、CreateReservedResource。下面每种配色先写实际资源类型,再写 UE 的使用方式。其中「共享 Buffer 池」「UploadHeapAllocator · Buddy」「Bump Page」三种,底层都是 CreateCommittedResource 建出的大 Buffer,区别只在 UE 如何按 offset + size 把它切给多个逻辑资源。

§0一页结论

每一行是一类资源(或一种 CPU 访问操作)的最终落点,按 DEFAULT Buffer、UPLOAD、初始数据、Lock、READBACK、光追、Texture、RDG / Reserved 分组。创建路由按 §3、§4 的顺序判定,先命中者生效;池内分配若超过池的单次上限,一律回退到 Committed。落点名称与代码保持一致,配色含义见页首。

资源 / 操作判定条件落点关键参数设计理由
DEFAULT Heap · Buffer
静态只读 BufferVB · IB · 只读 Structured / ByteAddress非 Dynamic;Alignment ≤ 256;无 UAV共享 Buffer 池32MB Committed Buffer · 256B 粒度 · 单次 ≤16MB · 可整理数量多、创建后状态不变,可以共用一个 ID3D12Resource;粒度细到 256B
可写 / 高对齐 BufferUAV · LCM(Stride, 16) > 256 · MultiState需要独立资源状态,或偏移满足不了对齐Placed 池32MB Heap · 64KB 粒度 · 单次 ≤16MB · 可整理每个资源各有一个 ID3D12Resource 和状态;Placed Resource 的视图偏移恒为 0,任何 stride 都自然对齐
大 Buffer · Shared Buffer> 16MB,或带 SharedCommitted按 64KB 取整大分配频率低,池化收益小 源码注释;Shared 需要 HEAP_FLAG_SHARED
UPLOAD Heap · FD3D12UploadHeapAllocator 与常量
Dynamic / Volatile BufferUsage 含 AnyDynamicAllocUploadResource
SmallBlockAllocator
BigBlockAllocator
Committed
≤64KB → MultiBuddy(4MB Buffer)
≤64MB → PoolAllocator(32MB Buffer · 512B)
更大 → Committed UPLOAD Buffer
不经过 FastConstantPageAllocator
GPU 直接读 UPLOAD Heap,CPU 常驻 Map 零拷贝写;再次整块写入时换一块新地址(Rename)
Uniform Buffer(MultiFrame)UniformBuffer_MultiFrameAllocUploadResource
SmallBlockAllocator
与 Dynamic Buffer 同一入口 · 256B 对齐
UB ≤64KB(4096 × 16B)→ 必然命中 SmallBlockAllocator
路由策略与 Dynamic Buffer 相同,只是尺寸上限让它到不了 BigBlockAllocator;跨帧、生命周期不定,必须能逐个释放
Uniform Buffer(SingleFrame · SingleDraw)以及命令上下文的松散常量SingleFrame · SingleDrawFD3D12FastConstantAllocator64KB Page 取自 FastConstantPageAllocator · Page 内 256B 对齐
UB:线程局部 FTransientUniformBufferAllocator
松散常量:FD3D12CommandContext::ConstantsAllocator
一帧内用完:只推 offset,旧 Page 随 fence 回收,没有逐个释放
初始数据 · 拷进目标后即丢弃
Buffer 初始数据RHICreateBufferInitializer按调用线程分流;Dynamic Buffer 直接写自身映射地址FD3D12FastAllocator
AllocUploadResource
渲染 / RHI 线程 → FastAllocator 4MB Page(>4MB 转 AllocUploadResource)
其他线程 → AllocUploadResource
工作线程上的 Staging 可能跨帧存活,不适合按帧回收的 Bump Page 推断
Texture 初始数据RHICreateTextureInitializer · RHIAsyncCreateTexture2DInitializer 不看线程;异步创建单独一条路FD3D12FastAllocator
AllocUploadResource
Initializer → FastAllocator(>4MB 转 AllocUploadResource)
RHIAsyncCreateTexture2D → AllocUploadResource · Copy Queue
与 Buffer 不同,Initializer 路径没有线程判断(§4.6)
Lock / Unlock · 写
Dynamic Buffer · Lock 写首次 Lock 或 RLM_WriteOnly_NoOverwrite;否则(再次 RLM_WriteOnly)自身映射地址
AllocUploadResource + Rename
新块 = 整个 Buffer · BufferAlignment
旧块挂 fence 延迟释放
等价 D3D11 的 MAP_WRITE_DISCARD,不改写 GPU 可能正在读的旧数据
静态 Buffer · Lock 写非 Dynamic · RLM_WriteOnly(不看线程)AllocUploadResource512B 对齐 · Unlock 时排队 CopyBufferRegionStaging 只活到拷贝完成
Texture · Lock 写含 UpdateTexture2D · BeginUpdateTexture3DRLM_WriteOnly(不看线程)FD3D12FastAllocator512B 对齐 · >4MB 转 AllocUploadResource
Unlock 时排队 CopyTextureRegion
Dynamic 纹理的 Staging 若来自 BigBlockAllocator 或 StandAlone,Unlock 后回收给该纹理复用 源码注释
READBACK Heap · 回读(创建途径分散,见 §5.5)
FRHIStagingBufferRHICopyToStagingBuffer · FRHIGPUBufferReadback容量不足时才重新分配Readback Pool
Committed
≤64KB → 4MB Committed READBACK Buffer 子分配 · 不整理
>64KB → Committed
READBACK 资源恒为 COPY_DEST,可以共享一个 Buffer
CPUReadback 纹理TexCreate_CPUReadback · FRHIGPUTextureReadback创建时带 CPUReadbackCommittedREADBACK Buffer · 大小 = GetCopyableFootprints
RHIMapStagingSurface 等 Fence 后 Map
常驻的拷贝目标,可反复使用
Lock 读LockBuffer · FD3D12Texture::Lock 的 RLM_ReadOnly非 Dynamic Buffer;任意纹理Committed每次新建 READBACK Buffer · SubmitAndBlockUntilGPUIdle同步等待 GPU,代价很高
ReadSurfaceData 系列RHIReadSurfaceData · ReadSurfaceFloatData · Read3DSurfaceFloatData每次调用Committed临时建 READBACK Buffer · 同步读回唯一例外:非 MSAA 的 RHIReadSurfaceData 发现源纹理已在 READBACK Heap 时直接复用
Query 结果Timestamp · Occlusion · PipelineStatistics创建 FD3D12QueryHeapCommittedCreateCommittedResource(READBACK · COPY_DEST)· 常驻 Map每个 Query Heap 一块结果 Buffer,长期复用
DEFAULT Heap · 光追 Buffer
光追加速结构(AS)AccelerationStructure · SingleState · 256B 对齐共享 Buffer 池(AS 专用)32MB Committed Buffer · 单次 ≤16MB · 不整理 · 驻留优先级 HIGHAS 状态永远不变,可共享 Buffer;AS 创建后未必立刻构建,拷贝未构建的 AS 会让 GPU 崩溃,所以禁用整理 源码注释
光追 ScratchUAV · 状态模式 DefaultPlaced 池64KB 粒度 · 驻留优先级 HIGHUAV 推导为 MultiState
DEFAULT Heap · Texture
只读小纹理非 RT/DS、单采样、非 Reserved,最高 mip ≤16 个 4KB tileReadOnly4K 池4MB Heap · 4KB 粒度 · 不整理避免小纹理被 64KB 对齐放大;块都是 4KB 的倍数,几乎不产生碎片 源码注释
只读纹理其余只读(仅 SRV)纹理ReadOnly 池64MB Heap · 64KB 粒度 · 单次 ≤64MB · 可整理流送纹理数量大、创建销毁交错,需要整理才能回收空 Heap
RT · DS · UAV 纹理以及 MSAA、Shared 纹理—CommittedRT/UAV 池默认大小为 0源码没写不池化的理由(分析见 §4.4 推断);MSAA 需要 4MB 对齐,超过池对齐
RDG · Reserved
RDG Transient Resourcer.RDG.TransientAllocator = 1Transient Heap≥128MB Heap · 64KB 对齐 · 按生命周期复用同一帧内,pass 生命周期不重叠的资源共享同一段物理内存
Reserved Buffer / 纹理ReservedResource 标记Reserved64KB tile · 16MB Backing Heap虚拟地址一次预留,物理内存按需增减,不需要连续大块

§1为什么需要这么多分配器

D3D12 只给了三个创建资源的 API(Committed、Placed、Reserved),引擎又在 Committed Buffer 之上自造了第四种用法:共享 Buffer 子分配。每种都有代价,没有一种能同时做到创建快、不浪费、状态独立、释放安全。UE 的做法是按资源特征,把每个请求路由到代价最小的那一种。

1.1四种落点

Committed CreateCommittedResource R1 R2 R3 3 个资源 = 3 个隐式 Heap 资源 ↔ Heap:1 对 1 状态:每个资源独立 Buffer 按 64KB 取整 每次创建一次显存分配 Placed CreatePlacedResource R1 R2 R3 1 个 ID3D12Heap · 一格 64KB N 个资源共享 1 个显式 Heap 状态:每个资源独立 偏移按 64KB(小纹理 4KB) CreateHeap 贵,放置便宜 共享 Buffer 子分配 CreateCommittedResource 的 UE 用法 1 个 Committed Buffer(32MB) N 段 [offset, size],最小 256B N 段共享 1 个 Committed 资源 状态:所有段共用一个 粒度:256B / 512B 分配只是 CPU 簿记 Reserved CreateReservedResource 虚拟地址范围(无物理内存) Heap 16MB Heap 16MB 64KB tile 映射到多个 Backing Heap 只占虚拟地址 物理页:64KB tile UpdateTileMappings 提交 可增长、可收缩
图 1四种落点的区别只在三件事:谁拥有 ID3D12Resource、谁拥有物理内存、资源状态以什么为单位。后文所有分配器,都是这四种落点之一,再加上一种 CPU 侧的切分算法。严格说 D3D12 API 只有第一、二、四列三种;第三列是在 CreateCommittedResource 建出的大 Buffer 上由 UE 做 CPU 侧切分,UploadHeapAllocator 的 Buddy、FastAllocator 的 Bump Page 也属于这一类。

1.2四个硬约束

约束 ① 创建与驻留有成本。每个 Committed 资源、每个 ID3D12Heap 都对应一次 WDDM 显存分配(要进内核),在驻留管理里也各是一个独立对象。若每帧成百上千个小资源都这样创建,CPU 开销和驻留簿记都扛不住,所以要「建一次大 Heap 或大 Buffer,反复切」。

约束 ② 放置有对齐粒度。Buffer 的 Alignment 只能是 64KB 或 0(等价 64KB);不使用 Tight Alignment 时,Buffer 实际占用是不小于 Width 的最小 64KB 倍数 官方文档。纹理默认 64KB、MSAA 默认 4MB,满足「小资源」条件的纹理可降到 4KB、MSAA 纹理可降到 64KB 官方文档。于是一个 1KB 的常量缓冲,不管 Committed 还是 Placed,都要占掉 64KB:

Committed / Placed Buffer:1KB 对齐浪费 63KB(按 64KB 取整) 0 16KB 32KB 48KB 64KB 共享 Buffer 池:1KB × 60 0 16KB 32KB 48KB 64KB 同样 64KB:独立放置只装得下 1 个 1KB Buffer;共享池按 256B 粒度能装 60 个(每个占 1024B)
图 2按比例绘制,条带代表 64KB,每格 1KB。Buffer 放置的最小单位是 64KB,这就是 UE 必须在大 Buffer 里自己切小块的根本原因。

约束 ③ 资源状态以 ID3D12Resource 为单位。在 legacy barrier 模型里,一个 Buffer 只有一个状态,UE 的状态跟踪也挂在 FD3D12Resource 上。多个逻辑 Buffer 共用一个 ID3D12Resource,就只能共用一个状态,所以共享 Buffer 子分配只适用于创建后状态不变的资源:只读 Buffer(GENERIC_READ)、UPLOAD Heap(GENERIC_READ)、READBACK Heap(COPY_DEST)、加速结构(RAYTRACING_ACCELERATION_STRUCTURE)。需要在 SRV、UAV、CopyDest 之间切换的资源必须拥有独立的 ID3D12Resource,只能 Placed 或 Committed 源码注释。本版本已支持 Enhanced Barriers,但分配器仍按单状态 / 多状态划分。

约束 ④ GPU 异步执行。CPU 侧释放资源时,GPU 可能还在执行引用它的命令,所以任何释放都要等对应帧的 fence 完成(§8.1)。反过来,只活一帧的数据(常量、Upload Staging)根本不需要逐个释放:在 Page 内顺序分配,等 GPU 跑完这一帧,整个 Page 直接复用。

1.3由约束推出的四个路由维度

维度取值决定了什么来自约束
状态可变性SingleState / MultiStateDefault 模式下,带 UAV 即推导为 MultiState共享 Buffer,还是独立 ID3D12Resource(Placed / Committed)③
对齐16…256B · 4KB · 64KB · 4MB能否进 256B 共享池、能否进 4K 纹理池;MSAA 只能 Committed②
尺寸≤ 池的单次上限 / 超出进池,还是 Committed①
生命周期单帧 · 图内临时 · 持久 · 可伸缩Bump Page、Transient Heap、池、Reserved① ④

一句话小而多的进池;可写的要独立状态;只活一帧的用 Bump Page 按页回收;太大或对齐特殊的直接 Committed;需要伸缩的走 Reserved。

§2分配器家族与尺寸阈值

先认识所有分配器和它们的尺寸分界,后面各节再逐个拆开机制。

2.1谁拥有什么

FD3D12DefaultBufferAllocator

归属
每个 Device 一个
后备
按配置惰性创建多个 FD3D12PoolAllocator:32MB Committed Buffer(共享)或 32MB ID3D12Heap(Placed)
算法
Best-fit 空闲链表 · 帧间整理
服务
DEFAULT Heap Buffer、光追 AS / Scratch、FRHIStagingBuffer(READBACK)

FD3D12TextureAllocatorPool

归属
每个 Device 一个
后备
4 个 FD3D12PoolAllocator:ReadOnly4K(4MB Heap)、ReadOnly(64MB Heap)、RT、UAV(后两者默认关闭)
算法
Best-fit · 仅 ReadOnly 池整理
服务
非 Transient、非 Reserved 的 DEFAULT Heap 纹理

FD3D12UploadHeapAllocator

归属
Adapter,每个 GPU 一个
后备
Committed UPLOAD Buffer,常驻 Map
算法
SmallBlockAllocator(MultiBuddy · ≤64KB)· BigBlockAllocator(PoolAllocator · ≤64MB)· FastConstantPageAllocator(MultiBuddy · 64KB Page)
服务
AllocUploadResource:Dynamic Buffer、MultiFrame UB、静态 Buffer Lock 写、工作线程上的 Buffer 初始数据、FastAllocator 超过 4MB 的请求;AllocFastConstantAllocationPage:只给 FD3D12FastConstantAllocator 供页

FD3D12FastAllocator(DefaultFastAllocator)

归属
每个 Device 一个
后备
4MB Committed UPLOAD Buffer 作为 Page,常驻 Map
算法
Bump 指针 · 页随 fence 复用
服务
渲染 / RHI 线程的 Buffer 初始数据;Texture 初始数据、Texture Lock 写、UpdateTexture2D / 3D(不看线程);光追 SBT 数据

FD3D12FastConstantAllocator

归属
线程局部 FTransientUniformBufferAllocator + 每个 FD3D12CommandContext 的 ConstantsAllocator;不属于 UploadHeapAllocator
后备
向 UploadHeapAllocator 的 FastConstantPageAllocator 要 64KB Page(Buddy 块)
算法
Bump · 256B 对齐
服务
SingleFrame / SingleDraw UB、松散着色器常量

FD3D12TransientResourceHeapAllocator

归属
RDG 每次图执行创建;Heap 缓存挂在 Adapter
后备
≥128MB 的 DEFAULT ID3D12Heap
算法
首次适配 + 生命周期别名 · Placed Resource 缓存
服务
RDG 图内 Transient 纹理与 Buffer

Reserved 资源(CommitReservedResource)

归属
资源自身(FD3D12Resource)
后备
16MB Backing Heap,按 64KB tile 映射
算法
从尾部增长 / 从尾部收缩,先用完 slack
服务
GPUScene、VSM 物理页池、SVT、Hair 曲线、Nanite 流送(实验)

Committed 回退路径

归属
各分配器内部的兜底分支
后备
每个资源一个隐式 Heap
算法
无,直接 CreateCommittedResource
服务
超出池上限、Shared、RT / DS / UAV / MSAA 纹理;FRHIStagingBuffer 小块以外的 READBACK 资源

2.2统一句柄:FD3D12ResourceLocation

不管落点是什么,FD3D12Buffer、FD3D12Texture、FD3D12UniformBuffer 都只持有一个 FD3D12ResourceLocation。它记录底层的 FD3D12Resource、相对资源起点的 OffsetFromBaseOfResource、GPUVirtualAddress(= 资源基址 + 偏移)、CPU 映射地址和请求大小。绑定视图、做拷贝都只读这几个字段,所以 Buffer 被整理搬到别的池、或者 Dynamic Buffer 换了一块新地址(Rename),上层代码都不受影响。它的类型决定了释放时的行为:

类型何时产生释放时做什么
eStandAloneCommitted、StandAlone Placed(开启分配追踪时)、Reserved、READBACK Buffer(Lock 读、CPUReadback 纹理等)资源进入 RHI 延迟删除队列(DeferDelete)
eSubAllocation从池或 Buddy 分配器切出的块块交还分配器并挂上 fence,GPU 执行完该帧后才真正并入空闲
eFastAllocationBump Page 里的切片(FD3D12FastAllocator、FD3D12FastConstantAllocator)什么都不做,切片随整个 Page 复用而失效
eHeapAliasedTransient Heap 上的 Placed ResourcePlaced Resource 对象延迟删除;Heap 内区间由 Transient 分配器回收
eAliased · eNodeReferenceXR 纹理别名 · 多 GPU 节点引用引用计数递减,最后一个引用释放时才删除

2.3尺寸阈值总图

UploadHeapAllocator SmallBlock · Buddy · 4MB BigBlock · 32MB 池 · 512B 对齐 · 首次适配 Committed 只读 Buffer 共享 Buffer 池 · 32MB · 256B 粒度 Committed UAV / 高对齐 Buffer Placed 池 · 32MB Heap · 64KB 粒度 Committed 光追 AS AS 专用共享池 · 不整理 Committed FRHIStagingBuffer Readback Pool · 4MB Committed READBACK 只读小纹理(≤16 tile) ReadOnly4K · 4MB Heap · 4KB 判定看最高 mip 的 tile 数,不看总大小 只读纹理 ReadOnly 池 · 64MB Heap · 64KB 粒度 Committed RT · DS · UAV 纹理 池默认大小为 0 → 全部 Committed FD3D12FastAllocator 4MB Page · Bump 转 BigBlock Committed FastConstantAllocator 64KB Page · 256B 对齐 上限 64KB,恰为 D3D12 常量缓冲上限 4096 × 16B RDG Transient Transient Heap · 单个 Heap ≥128MB · 64KB 对齐 Reserved 64KB tile · 16MB Backing Heap · 按需提交 1KB 4KB 64KB 1MB 4MB 16MB 64MB 256MB
图 3横轴是单次分配大小(对数刻度),颜色是落点,色块边界就是代码里的阈值(对应的宏与 CVar 见 §10)。同一类资源,跨过阈值就换了分配器。

§3Buffer

Buffer 的路由最复杂:同一个 CreateBuffer 可能落到 UploadHeapAllocator、共享 Buffer 池、Placed 池、Reserved 或 Committed。决定因素是 Usage、由 Stride 推出的对齐、状态模式和尺寸。

3.1描述与对齐怎么算

路由之前,FD3D12Buffer::GetResourceDescAndAlignment 先把请求翻译成 D3D12_RESOURCE_DESC 和一个 Alignment:

  • Width 向上对齐到 16(RHI_RAW_VIEW_ALIGNMENT)。ByteAddress 视图按 4 字节一个元素计数,对齐到 16 能保证末尾数据不会在整除时丢掉 源码注释。
  • Flags:UAV → ALLOW_UNORDERED_ACCESS;既不要 SRV 也不是 AS → DENY_SHADER_RESOURCE(进池前会被抹掉,好让更多 Buffer 共用一个池);Shared → ALLOW_SIMULTANEOUS_ACCESS;AS → RAYTRACING_ACCELERATION_STRUCTURE。
  • Alignment:Reserved 取 64KB(tile 大小);否则当 Stride > 0,且是 StructuredBuffer 或者不是 ByteAddress / DrawIndirect 时,取 LCM(Stride, 16);其余取 16。

结构化 Buffer 要按 Stride 对齐,是因为它的 SRV/UAV 用元素序号(FirstElement)而不是字节数描述起点,子分配偏移必须是 Stride 的整数倍,视图才指得准 源码注释。这个 Alignment 会直接改变路由和实际占用:

Buffer请求大小Alignment落点实际占用
ByteAddress · IndexBuffer · Stride 为 01024B16共享 Buffer 池1024B
Structured,Stride 3232 × 32 = 1024B32共享 Buffer 池1024B
Structured,Stride 2424 × 42 = 1008B48共享 Buffer 池1280B256 不是 48 的倍数:先加 48,再按 256 取整
Structured,Stride 3636 × 28 = 1008B144共享 Buffer 池1280B1008 + 144 = 1152,再按 256 取整
Structured,Stride 100100 × 10 = 1000B400Placed 池Alignment > 25665536B
任意 Buffer + UAV1024B—Placed 池MultiState65536B

实践结构化 Buffer 的 Stride 取 16 的倍数且不超过 256,小 Buffer 就能留在 256B 粒度的共享池;LCM(Stride, 16) 一旦超过 256,哪怕只有 1KB 也会按 64KB 放置。

3.2路由阶梯

第一级在 FD3D12Adapter::AllocateBuffer(前两条)与 FD3D12DefaultBufferAllocator::AllocDefaultResource(后三条)里决定去哪个分配器,自上而下判定,先命中者生效:

  1. Usage 含 Dynamic / Volatile?AllocateBuffer
    是
    UploadHeapAllocator AllocUploadResource≤64KB SmallBlockAllocator · ≤64MB BigBlockAllocator · 更大 Committed(§5.1)
  2. 调用方传入了 ResourceAllocator?RDG Transient 分配器的适配器
    是
    Transient Heap 上的 Placed Resource§6
  3. DrawIndirect,且 d3d12.AllowPoolAllocateIndirectArgBuffers = 0?该 CVar 代码默认值是 1,帮助文本里的 0 已过时,默认不会命中
    是
    Committed为规避 UE-115982 驱动崩溃保留的开关
  4. Usage 含 ReservedResource?
    是
    CreateReservedResource之后按需 CommitReservedResource(§7)
  5. 以上都不是抹掉 DENY_SHADER_RESOURCE,按池键查找或创建池
    →
    默认 Buffer 池,进入第二级

第二级决定池的策略(GetResourceAllocationStrategy):

  1. Alignment > 256?kD3D12ManualSubAllocationAlignment
    是
    Placed 池Placed Resource 偏移恒为 0,任何对齐都满足
  2. 状态模式为 MultiState,或 Default 模式下带 UAV?
    是
    Placed 池每个资源需要独立状态
  3. 以上都不是SingleState
    →
    共享 Buffer 池32MB Committed Buffer · 256B 粒度 · 偏移子分配

进池以后还有最后一道闸(FD3D12PoolAllocator::AllocateResource):请求大小超过池的单次上限(DEFAULT Heap 16MB、READBACK 64KB),或者带 ALLOW_SIMULTANEOUS_ACCESS,就不进池,直接 CreateCommittedResource,并把 Buffer 的 Alignment 改回 64KB。

3.3池如何划分

FD3D12DefaultBufferAllocator 并没有预建好的池,只有一个按需增长的 FD3D12PoolAllocator 数组。每个池由 FD3D12ResourceInitConfig(Heap 类型、Heap Flags、资源标志、初始状态)加上分配策略确定,SupportsAllocation 负责匹配,而两种策略的匹配规则不一样:

  • 共享 Buffer 池要求配置完全一致。池里所有逻辑 Buffer 共用一个 Committed ID3D12Resource(FD3D12MemoryPool::Init 用 CreateBuffer 创建),资源标志和初始状态必须相同。进池前抹掉 DENY_SHADER_RESOURCE,就是为了让不需要 SRV 的 VB/IB 和需要 SRV 的结构化 Buffer 落进同一个池。
  • Placed 池只比较 Heap 类型和 Heap Flags。每个 Placed Resource 有自己的 desc 和状态,Heap(FD3D12MemoryPool::Init 用 CreateHeap 创建)只关心能不能放 Buffer,所以资源标志各不相同的可写 Buffer 共用同一组 Heap。
池Heap · 策略池大小池对齐单次上限整理新池创建
只读 BufferDEFAULT · 共享32MB256B16MB开Async,常备 1 个预分配池
可写 / 高对齐 BufferDEFAULT · Placed32MB64KB16MB开Async,常备 1 个预分配池
Readback PoolFRHIStagingBufferREADBACK · 共享4MB256B64KB关(非 DEFAULT Heap)Async,常备 1 个预分配池
光追 ASDEFAULT · 共享32MB256B16MB关Immediate,无预分配

DEFAULT Heap Buffer 池的单次上限(16MB)是池大小(32MB)的一半,任何进池的请求都保证能被一个新池装下;更大的请求本来就少,直接 Committed,CPU 开销可以忽略 源码注释。上限大于池大小的分配器(例如 UploadHeapAllocator 的 BigBlockAllocator:32MB 池、64MB 上限)遇到超过池大小的请求时,会按请求向上取 2 的幂(不超过上限)新建一个池。

3.4共享 Buffer 池内部:链表 + Best-fit

每个 FRHIMemoryPool 用一条按偏移排序的双向链表记录所有块(已分配或空闲),再用一个按大小升序排列的 FreeBlocks 数组做 Best-fit 查找。Best-fit 优先用最贴合的空闲块,把大块尽量留整。

Allocate(1.8MB) LowerBound(FreeBlocks, 1.8MB) → 2MB 块 所有块按偏移串成双向链表,头尾接 HeadBlock VB 3MB 2MB IB 4MB SB 6.5MB 空闲 14MB 锁定 1.5MB 0 32MB 已释放、等待 fence:暂不与左右空闲块合并 FreeBlocks(按大小升序,Best-fit) 1.5MB 2MB 14MB 切分:从 2MB 块的头部切出 1.8MB(按 256B 取整),剩余 0.2MB 重新有序插入 FreeBlocks;剩余为 0 则删掉该空闲块。 释放:块先保持锁定并挂上 fence;fence 完成后 MarkFree,再与未锁定的相邻空闲块合并。
图 4一个 32MB 共享池的快照(按比例)。锁定有两个用途:刚分配的块在初始数据写完(UnlockPoolData)之前保持锁定,整理器不会搬一个还没写内容的块;释放后的块在 GPU 执行完之前保持锁定,既不参与合并,也不会被重新分配。

子分配的对齐在池内这样处理(FRHIMemoryPool::GetAlignedSize / GetAlignedOffset):空闲块的偏移永远是池对齐(256B)的倍数;若请求的 Alignment 整除 256,块大小直接按 256 取整;若不整除,先多留一个 Alignment 的余量,再把起点推到 Alignment 的倍数:

新块 [256, 1536) = 1280B 0 256 1536 2048 前一块 数据 1008B(24 × 42) 余量 240B 空闲 数据起点 288 = 6 × 48,满足 Stride 对齐 每格 256B 块大小 = AlignArbitrary(1008 + 48, 256) = 1280B:因为 256 % 48 ≠ 0,先多留一个 Alignment,保证起点对齐后仍放得下数据 释放时 MarkFree 用同一公式还原块大小,把偏移向下对齐回 256 的倍数,再与相邻空闲块合并
图 5Stride 24(Alignment 48)的 1008B 结构化 Buffer 落在偏移 256 的空闲块上(按比例)。32B 的填充让 SRV 的 FirstElement = 288 / 24 = 12 成为整数;尾部 240B 是按 256B 取整的余量。

3.5Placed 池

Placed 池和共享池用的是同一套 FRHIMemoryPool 簿记,区别只有两点:池对齐是 64KB;分配成功后要在 AlignDown(offset, 64KB) 处调用 CreatePlacedResource,得到一个独立的 ID3D12Resource。于是每个 UAV Buffer 都有自己的状态,可以被跟踪、单独转换,代价是每块至少 64KB。

对 Buffer 而言,Placed 和 Committed 的取整浪费其实一样(都是 64KB 的倍数)。所以池化 Placed Resource 省下的不是粒度,而是创建成本和驻留对象数量:32MB 的 Heap 只建一次,之后 Placed Resource 只是在已存在的物理内存上创建资源对象 推断。

3.6初始数据、Lock 与 Dynamic Buffer

操作Staging 从哪里来如何进入目标要点
创建时带初始数据在渲染线程或 RHI 线程FD3D12FastAllocator4MB Bump Page;>4MB 转 AllocUploadResource排队 CopyBufferRegion。目标在共享池时,要把整个大 Buffer 临时转到 COPY_DEST 再转回源码注释自认「不够优化,应当批处理」
创建时带初始数据在其他线程(如异步加载)AllocUploadResource同上Staging 可能要跨帧存活,不适合按帧回收的 Bump Page 推断
可跟踪状态的 Placed Buffer 带初始数据同上直接以 COPY_DEST 创建,省掉一次转换共享池里的 Buffer 做不到
Dynamic:首次 Lock,或 WriteOnly_NoOverwrite无,本身就在 UPLOAD Heap直接写映射地址—
Dynamic:再次 WriteOnly LockAllocUploadResource 新分配一块大小 = 整个 Buffer在 RHI 时间线上 Rename 到新地址,旧块走 fence 延迟释放效果等同 D3D11 的 MAP_WRITE_DISCARD
静态 Buffer:Lock 写AllocUploadResource512B 对齐Unlock 时排队 CopyBufferRegion不看线程,一律走 UploadHeapAllocator
静态 Buffer:Lock 读Committed READBACK排队拷贝后 SubmitAndBlockUntilGPUIdle同步等待 GPU,代价很高

新分配的池块默认处于锁定状态:带初始数据的 Buffer 在拷贝完成后才 UnlockPoolData,没有初始数据的 Buffer 创建后立即解锁。只有解锁的块才会被整理器搬动。

对比Buffer 只有初始数据按线程选 Staging(渲染 / RHI 线程用 FastAllocator,其他线程用 AllocUploadResource),Lock 写一律走 AllocUploadResource。Texture 的 Initializer 与 Lock 写都不看线程,一律先走 FastAllocator(RHIAsyncCreateTexture2D 例外),见 §4.6。

3.7光追 Buffer

  • 加速结构(AS):CreateRayTracingBuffer 以 RAYTRACING_ACCELERATION_STRUCTURE 标志、256B 对齐、SingleState、BVHRead | BVHWrite 创建,于是走共享 Buffer 策略,进入 AS 专用池(32MB、单次 ≤16MB、Immediate、不整理),整个池的驻留优先级设为 HIGH;超过 16MB 的 Committed AS 单独设 HIGH。
  • AS 能共享大 Buffer 的原因:AS 始终处于 RAYTRACING_ACCELERATION_STRUCTURE 状态,构建与引用都通过「GPU 虚拟地址 + 偏移」,完全符合约束 ③。
  • AS 池不整理的原因:AS Buffer 可能创建后还没构建,对它执行 CopyRaytracingAccelerationStructure 会让 GPU 崩溃;不能整理意味着浪费更多,所以单独配了一组池参数 源码注释。
  • Scratch:UAV + Default 状态模式 → MultiState → Placed 池,并单独提升驻留优先级。源码注释的解释是:剩下的 scratch 分配已经不多,为它们专开单状态 Heap 反而浪费。
  • 着色器绑定表(SBT):MultiState、64B 对齐(D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT)→ Placed 池;数据先写进 FastAllocator,再走拷贝队列上传。

§4Texture

纹理的实际大小由驱动决定,必须用 GetResourceAllocationInfo 查询。路由的关键只有两个问题:能不能 4KB 对齐?是不是只读?

4.1对齐怎么定

FD3D12TextureAllocatorPool::AllocateTexture 先设置 Desc.Alignment,再向驱动查询真实的 SizeInBytes 与 Alignment(纹理大小因架构而异,可能比像素数据本身大)官方文档:

  • 能 4KB 对齐 → 4KB(D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT);
  • 否则 MSAA → 4MB,其余 → 64KB。

FD3D12Texture::CanBe4KAligned 的条件:非 Reserved;不是 NV12 / P010 视频格式;没有 RT / DS 标志;单采样;按「一块 4KB」的 tile 形状估算,最高 mip 需要的 tile 数 ≤ 16。这与官方规则一致:最详细 mip 的估算大小不超过 64KB(16 × 4KB)时才允许 4KB 对齐 官方文档。

RGBA8 · 128×128 tile = 32×32 texel = 4KB 16 tile ≤ 16 → 4KB 对齐 进 ReadOnly4K 池(4MB Heap) RGBA8 · 256×256 tile = 32×32 texel = 4KB 前 16 块 64 tile > 16 → 64KB 对齐 进 ReadOnly 池(64MB Heap) BC7 · 256×256 tile = 64×64 texel(16×16 块)= 4KB 16 tile ≤ 16 → 4KB 对齐 块压缩让同尺寸纹理更容易进 4K 池
图 6Get4KTileShape 按格式的每像素位数算出 4KB 能装多少 texel(32bpp 为 32×32,BC7 为 16×16 个块,BC1 / BC4 宽度再翻倍),再数最高 mip 需要几块。UE 按 Width × Height × DepthOrArraySize 计数,纹理数组的长度也会计入。

4.2路由阶梯

第一级在 CreateTextureInternal / SafeCreateTexture2D:

  1. 调用方传入了 ResourceAllocator?RDG Transient
    是
    Transient Heap 上的 Placed Resource§6
  2. TexCreate_CPUReadback?
    是
    Committed READBACK Buffer大小由 GetCopyableFootprints 计算
  3. TexCreate_ReservedResource?仅 2D / 3D
    是
    CreateReservedResource64KB_UNDEFINED_SWIZZLE 布局;带 ImmediateCommit 时立即提交全部(§7)
  4. 以上都不是
    →
    TextureAllocatorPool,进入第二级

第二级选池:

  1. 带 RT 或 DS 标志?
    是
    RenderTarget 池 → 默认关闭 → Committed池大小 d3d12.PoolAllocator.RTUAVTextureVRAMPoolSize = 0
  2. 带 UAV 标志,或需要 BCn UAV 别名?
    是
    UAV 池 → 默认关闭 → Committed
  3. 能 4KB 对齐?
    是
    ReadOnly4K 池4MB Heap · 4KB 粒度 · 单次 ≤4MB
  4. 以上都不是
    →
    ReadOnly 池64MB Heap · 64KB 粒度 · 单次 ≤64MB

最后一道闸与 Buffer 相同:请求超过池的单次上限、带 Shared,或者 Alignment 大于池对齐(MSAA 的 4MB),就改走 Committed。开启分配追踪时,这条回退路径改为「单独 CreateHeap + CreatePlacedResource」,见 4.5。

4.3四个纹理池

池Heap FlagsHeap 大小池对齐单次上限状态跟踪整理
ReadOnly4KALLOW_ONLY_NON_RT_DS_TEXTURES4MB4KB4MB不跟踪(只读)关块都是 4KB 的倍数且很小,本来就不易碎片化 源码注释
ReadOnlyALLOW_ONLY_NON_RT_DS_TEXTURES64MB64KB64MB不跟踪(只读)开
RenderTarget默认关闭ALLOW_ONLY_RT_DS_TEXTURES064KB0MultiState关重建 Placed Resource 时取不到 ClearValue 源码注释
UAV默认关闭ALLOW_ONLY_NON_RT_DS_TEXTURES064KB0MultiState关处理不了 BCn / UINT 的 UAV 别名 源码注释

纹理池全部是 Placed 策略,因为纹理不存在「在一个大资源里按偏移切」的用法:每张纹理必须是独立的 ID3D12Resource。

4.4为什么 RT / DS / UAV 纹理默认不池化

源码只写了这两个池不能整理的原因,没有写默认关闭池化的原因。以下是结合机制的分析 推断:

  • 尺寸大,粒度收益小。屏幕尺寸的颜色、深度、UAV 纹理动辄数 MB 到数十 MB,64KB 取整的浪费占比可以忽略。
  • 即使池化也不能整理。两个池都关闭了整理,释放后留下的空洞只能等整个 Heap 清空,长时间运行碎片会累积。
  • 高频的 Transient RT 已有更合适的去处。RDG 图内的 Transient RT 走 Transient Heap(§6);长寿命的池化 RT 创建频率低,Committed 的创建成本可以接受。
  • 驻留粒度更细。Committed 资源各自是驻留单位,不会因为用到一张小 RT 就把整个 Heap 拉进显存(§9)。

确有需要时,可在启动配置里把 d3d12.PoolAllocator.RTUAVTextureVRAMPoolSize 与 RTUAVTextureMaxAllocationSize 设为非零来启用(两者都是只读 CVar)。

4.5其他细节

  • 尺寸查询有缓存与不缓存两种。纹理池分配时调用 GetResourceAllocationInfoUncached;RHICalcTexturePlatformSize 与 Transient 路径用按 desc 哈希缓存的版本。
  • Shared 纹理(ALLOW_SIMULTANEOUS_ACCESS)一律 Committed,并加上 D3D12_HEAP_FLAG_SHARED。
  • MSAA 纹理的 Alignment 为 4MB,大于任何纹理池的池对齐,必然 Committed 源码注释。
  • 分配追踪会改变分配形态。当 D3D12.TrackAllAllocations、GPU 崩溃调试或内存 Trace 开启(且 Resource Heap Tier 2)时,StandAlone 纹理不再 Committed,而是「单独 CreateHeap + CreatePlacedResource」,以便拿到 GPU 虚拟地址来定位崩溃;池 Heap 的 Flags 也改为 ALLOW_ALL_BUFFERS_AND_TEXTURES。用 Insights 分析显存时要意识到这一点。
  • XR 纹理别名(eAliased)让多个 ResourceLocation 引用同一资源,靠引用计数管理,源码注释明确建议尽量避免。

4.6初始数据、Lock 与 Update

Texture 选 Staging 的方式与 Buffer 不同:Initializer、Lock 写、UpdateTexture2D / 3D 都不看调用线程,一律先向 FD3D12FastAllocator 要(超过 4MB 由它转交 AllocUploadResource);只有 RHIAsyncCreateTexture2D 直接走 UploadHeapAllocator。

操作Staging 从哪里来如何进入目标要点
RHICreateTextureInitializerBulkData · InitializerFD3D12FastAllocator整个 mip 链一块 · 512B 对齐纹理以 COPY_DEST 创建;UploadInitialData 逐 subresource 排队 CopyTextureRegion,再转到目标状态并 UnlockPoolData没有线程判断,与 Buffer 的 AllocateUploadMemory 不同
RHIAsyncCreateTexture2DAllocUploadResource总大小 >4MB 且多 mip 时,mip0 与其余 mip 各分一块在 Copy Queue 上 UpdateSubresources,完成后由任务 UnlockPoolData注释称 4MB 以上新建 Page 的耗时会陡增,拆出 mip0 以免落入 StandAlone 回退而卡顿 源码注释
Lock 写RLM_WriteOnlyFD3D12FastAllocatorSubresource 大小 · 512B 对齐Unlock 时排队 CopyTextureRegionDynamic 纹理:Staging 若是 StandAlone 或来自 BigBlockAllocator(AT_Pool),Unlock 后经延迟删除回收给该纹理,下次 Lock 直接复用 源码注释
UpdateTexture2D整个 mip 且在 Immediate 命令列表上 → 走 Lock / Unlock;否则 FD3D12FastAllocator排队 CopyTextureRegion—
BeginUpdateTexture3DFD3D12FastAllocator开启对应 CVar 且非块压缩格式时改走 Compute ShaderEndUpdateTexture3D 排队拷贝—
Lock 读RLM_ReadOnlyCommittedREADBACK Buffer · Subresource 大小 · 每次新建排队 CopyTextureRegion 后 SubmitAndBlockUntilGPUIdle,再 Map同步等待 GPU;多 GPU 未支持(源码 ensure)

§5UPLOAD 与 READBACK Heap:UploadHeapAllocator、FastAllocator、FastConstantAllocator 与回读资源

CPU 可写内存(UPLOAD Heap)的分配特征和显存正好相反:数量极多、尺寸小、寿命短,而且状态恒为 GENERIC_READ,天然适合共享 Buffer 子分配,难点在于速度和按帧回收。CPU 可读内存(READBACK Heap)状态恒为 COPY_DEST,同样可以共享,但回读资源的创建入口分散在多处,见 5.5。

5.1FD3D12UploadHeapAllocator 的三个子分配器

FD3D12UploadHeapAllocator 每个 GPU 一个,内部按用途拆成三个子分配器,底层都是 CreateCommittedResource 建出、常驻 Map 的 UPLOAD Buffer。对外只有两个入口:AllocUploadResource 按大小在前两个之间路由,超过 64MB 时由 BigBlockAllocator 回退到 Committed;AllocFastConstantAllocationPage 只从第三个取 64KB Page。FD3D12FastConstantAllocator 本身不在其中,它只是这些 Page 的使用者(5.4)。

分配器服务实现池粒度单次上限为什么这样选
SmallBlockAllocatorAllocUploadResource ≤64KBMultiBuddy4MB512B 起,取整到 2 的幂64KB快,但会取整到 2 的幂 源码注释
BigBlockAllocatorAllocUploadResource >64KBPoolAllocator按偏移的首次适配 · 不整理32MB512B64MB,超出 Committed慢一些,但取整浪费少 源码注释
FastConstantPageAllocatorAllocFastConstantAllocationPage:FD3D12FastConstantAllocator 的 64KB PageMultiBuddy4MB64KB 起4MB大小固定、通常同帧释放,单独成池以免把其他池切碎 源码注释
  • 512B 的由来。它是 D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT,官方要求线性子资源拷贝按它对齐 官方文档;以它作最小粒度,Upload Staging 可以直接服务纹理数据拷贝 推断。
  • BigBlockAllocator 用首次适配、不整理。空闲数组按偏移排序,取第一个放得下的块。Staging 寿命短、不值得搬动;首次适配让分配集中在低地址端,高地址端保持连续 推断。
  • 新池异步预建。d3d12.UploadHeap.PoolAllocationStrategy = 1(Async):每用掉一个预建池,就在后台线程再建一个(OverFlowPoolsCount = 1)源码注释,避免在渲染线程上同步创建 32MB 资源造成卡顿 推断。DEFAULT Heap Buffer 池采用同样的策略。
  • 谁在调用 AllocUploadResource。Dynamic Buffer(创建与再次 Lock)、MultiFrame UB、静态 Buffer 的 Lock 写、工作线程上的 Buffer 初始数据、RHIAsyncCreateTexture2D、FastAllocator 超过 4MB 的请求,以及光追与 ResourceCollection 的若干上传。
  • MultiFrame UB 为什么只落在 SmallBlockAllocator。它与 Dynamic Buffer 走同一个入口,但创建时 check 常量缓冲 ≤ 4096 × 16B = 64KB,而 SmallBlockAllocator 的判定是 ≤64KB,所以永远到不了 BigBlockAllocator。

5.2Buddy 分配

4MB · order 13 2MB · 12 1MB · 11 512KB · 10 256KB · 9 128KB · 8 64KB · 7 4MB Buffer(全空时 FreeBlocks[13] = {0}) 右半块 → FreeBlocks[12] → FreeBlocks[11] → [10] [9] 右半块进 FreeBlocks[8] 左半块分配出去;右半块进 FreeBlocks[7] 40KB → ceil(40960 / 512) = 80 单位 → 取整到 128 = 2^7 Buddy 偏移 = offset XOR 2^order;释放时 Buddy 也空闲就合并升阶,递归 分配只在各阶空闲集合间拆分,不需要搜索空闲链表 请求 40KB 浪费 24KB 放大:分配出去的 64KB 块
图 7SmallBlockAllocator 中一次 40KB 请求在 4MB Buffer 上的 Buddy 切分(按比例,最小单位 512B)。Buddy 的分配与释放只是在各阶空闲集合之间拆分、合并,速度快,空闲块能自动归并;代价是内部浪费最多接近一半,所以只用在 ≤64KB 的小块上。

5.3FD3D12FastAllocator:4MB Bump Page

RequestFastAllocatorPage:RefCount == 1 且 FrameFence ≤ GPU 已完成的 fence Page Pool(空闲 Page) Committed UPLOAD 4MB · 常驻 Map CurrentAllocatorPage 对齐后顺序推进,放不下就换 Page delete Page 池内超过 5 个 Page 且闲置 ≥10 帧 Page 满:ReturnFastAllocatorPage 并 UpdateFence CleanupPages(10):每帧最多释放 1 个 Page,至少保留 5 个 NextFastAllocOffset 一个 Page 4MB 切片类型为 eFastAllocation,释放时什么都不做;Page 被复用时切片自然失效,所以只装「GPU 执行完本帧就不再需要」的数据。 超过 4MB 的请求:UPLOAD 类型转交 UploadHeapAllocator::AllocUploadResource,其他 Heap 类型单独建一个 Committed Buffer。
图 8每次分配都会把 Page 的 FrameFence 更新为「下一个待发出的 fence」。只要 GPU 还没执行完引用它的那一帧,这个 Page 就不会被再次取出,这是 Bump Page 不需要逐个释放、却依然安全的原因。

5.4FD3D12FastConstantAllocator 与 Uniform Buffer

Uniform Buffer 按生命周期分两条路(D3D12UniformBuffer.cpp):UniformBuffer_MultiFrame 走 UploadHeapAllocator::AllocUploadResource(256B 对齐),因 UB ≤64KB 必然落在 SmallBlockAllocator,可以逐个释放;SingleFrame / SingleDraw 走线程局部的 FTransientUniformBufferAllocator(FD3D12FastConstantAllocator 的子类,每个线程懒创建一个)。命令上下文里的松散着色器常量用各 FD3D12CommandContext 自己的 ConstantsAllocator,同样是 FD3D12FastConstantAllocator。更新 Uniform Buffer 同样是「分配新位置再替换」,不会原地改写 GPU 可能正在读的数据。

FastConstantPageAllocator:UploadHeapAllocator 内的 MultiBuddy,4MB 池,最小块 64KB 每格 64KB 当前 64KB Page(放大) 3.8KB 6KB 9KB Offset AlignedSize = Align(Bytes, 256);放不下时向 FastConstantPageAllocator 要一个新 64KB Page,旧 Page 由 ResourceLocation.Clear() 走 fence 延迟释放 单次上限就是 Page 大小 64KB,恰好等于 D3D12 常量缓冲上限 4096 × 16B,超过会触发 check
图 9两级复用:64KB Page 来自 UploadHeapAllocator 的 FastConstantPageAllocator(Buddy),Page 内再做 256B 对齐的 Bump 分配(256B 是 D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT)。第二行按比例绘制。

小结UPLOAD 侧的共同点是常驻 Map、状态恒定、按帧回收:SmallBlockAllocator(Buddy)管小而碎的 Staging,BigBlockAllocator 管大块,FastAllocator / FastConstantAllocator(Bump Page)管只活一帧的数据。

5.5READBACK:回读资源的创建途径

READBACK Heap 上的资源必须以 COPY_DEST 创建、且不能转换状态 官方文档,所以理论上都能共享一个 Buffer。但 D3D12RHI 里回读资源的入口很分散:只有 FRHIStagingBuffer 经过 FD3D12DefaultBufferAllocator 的 READBACK 池,其余入口都直接调用 FD3D12Adapter::CreateBuffer(内部就是 CreateCommittedResource)或 CreateCommittedResource。

入口典型调用方落点大小 · 生命周期如何读取
RHICopyToStagingBufferFD3D12StagingBufferFRHIGPUBufferReadbackReadback Pool≤64KB:4MB Committed READBACK Buffer 子分配 · 256B 粒度 · 不整理Committed>64KB容量不足时才重新分配,否则复用RHILockStagingBuffer 直接返回映射地址 + Offset
TexCreate_CPUReadbackCreateTextureInternal · SafeCreateTexture2DFRHIGPUTextureReadbackCommittedREADBACK Buffer · 大小 = GetCopyableFootprints随纹理对象,反复作为拷贝目标RHIMapStagingSurface:Fence 未完成则 Wait,再 Map
LockBufferRLM_ReadOnly · 静态 BufferCPU 同步读 BufferCommitted大小 = Offset + Size每次 Lock 新建,Unlock 后释放排队拷贝后 SubmitAndBlockUntilGPUIdle
FD3D12Texture::LockRLM_ReadOnlyCPU 同步读纹理Committed大小 = Subresource每次 Lock 新建同上
ReadSurfaceData 系列RHIReadSurfaceData · ReadSurfaceFloatData · Read3DSurfaceFloatData · MSAA 版本CPU 同步读像素Committed临时 READBACK Buffer;MSAA 版本另建一张临时 Committed RT 做逐采样 resolve每次调用新建同步读回;非 MSAA 的 RHIReadSurfaceData 若源纹理已在 READBACK Heap 则直接复用
FD3D12QueryHeapTimestamp · Occlusion · PipelineStatistics 查询CommittedCreateCommittedResource(READBACK · COPY_DEST)随 Query Heap 常驻创建后 Map 一次,常驻映射

结论回读资源真正进池的只有 ≤64KB 的 FRHIStagingBuffer。Lock 读、ReadSurfaceData 这类同步路径每次都新建 Committed 并阻塞等待 GPU,适合调试与工具场景;需要逐帧回读时,用 FRHIGPUBufferReadback / FRHIGPUTextureReadback 这类按 Fence 查询的封装更合适 推断。

§6RDG Transient Resource:Transient Heap 与内存别名

RDG 编译图时知道每个 Transient Resource 在哪个 pass 首次使用、在哪个 pass 最后使用。Transient 分配器正是利用这一点,让生命周期不重叠的资源共用同一段物理内存。

6.1Heap 从哪里来

  • Heap 缓存 FD3D12TransientHeapCache 挂在 Adapter 上;RDG 每次执行图时创建一个 FD3D12TransientResourceHeapAllocator 向它借 Heap,执行完归还。
  • 新 Heap 大小 = max(RoundUpToPowerOfTwo(首个分配), 128MB)(RHI.TransientAllocator.MinimumHeapSize)。
  • Heap 属性:DEFAULT;Resource Heap Tier 2 时 Buffer、纹理、RT 混放(AllowAll),否则按类型分 Heap;Desc.Alignment = 4MB,MSAA 纹理也能放;带 CREATE_NOT_ZEROED;驻留优先级 HIGH。
  • 归还的 Heap 进入空闲列表,闲置超过 16 个回收周期(RHI.TransientAllocator.GarbageCollectLatency)才销毁。

6.2分配:首次适配 + 生命周期别名

P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 0 32MB 64MB 96MB 128MB Heap 内偏移 SceneColor 40MB(P1–P6) GBufferA 20MB(P1–P4) GBufferB 20MB(P1–P4) SSAO 16MB(P5–P7) Bloom 30MB(P7–P10) Blur 30MB(P8–P10) Bloom 复用 SceneColor 的 [0, 30MB);Blur 横跨 SceneColor、SSAO、GBufferA 释放的三段区间,落在 [30, 60MB) 6 个资源共申请 156MB,同一时刻峰值 80MB,全部放进一个 128MB Heap
图 10示例图(资源与尺寸为说明用),纵轴按比例。每个矩形是一个 Placed Resource 占用的「偏移区间 × 生命周期」。空闲区间会记住上一个主人的生命周期(fence),新资源只有与之不重叠时才能占用;RDG 按 pass 顺序分配,所以首次适配会自然把新资源塞进刚刚结束使用的区间。
  • 对齐与取整:纹理对齐取 max(64KB, GetResourceAllocationInfo 返回的对齐);Buffer 统一按 64KB 对齐并取整。
  • Placed Resource 缓存:每个 Heap 按 hash(CreateInfo, Heap 内偏移) 缓存 Placed Resource(各 64 个纹理、64 个 Buffer)。下一帧同样的资源落在同样的偏移,就直接复用已有的 ID3D12Resource,不再调用 CreatePlacedResource。
  • 句柄:ResourceLocation 类型为 eHeapAliased 并标记为 Transient,不计入常规 Buffer 统计,内存 Trace 也不单独记录这些 Placed Resource(Heap 本身已记录)。
  • 开关:r.RDG.TransientAllocator 为 0 关闭,1 开启(默认),2 仅对带 FastVRAM 标志的资源开启。

§7Reserved Resource(Tiled)

Reserved Resource 先只占 GPU 虚拟地址,物理内存以 64KB tile 为单位按需映射。它解决两个问题:资源要能增长或收缩;不想占用一整块连续的大显存。

Reserved Resource 的虚拟地址范围:40MB,每 64KB 一个 tile 已提交 20MB 未提交:只占虚拟地址 UpdateTileMappings Backing Heap #0 · 16MB(已满) 4MB slack 12MB Backing Heap #1 · 16MB Commit(N):先用完最后一个 Backing Heap 的 slack,不够再按剩余需求新建 Heap(每个 ≤16MB,d3d12.ReservedResourceHeapSizeMB) Decommit(N):从尾部把 tile 映射成 NULL;Backing Heap 整个不再使用就 DeferDelete,否则计入 slack Backing Heap Flags 按资源类型取 ALLOW_ONLY_BUFFERS / ALLOW_ONLY_(NON_)RT_DS_TEXTURES;RT、DS、UAV 的 Backing Heap 驻留优先级为 HIGH
图 11按比例绘制。提交与收缩都只发生在尾部,所以「已提交 N 字节」恒等于「前 N/64KB 个 tile 已映射」,无需维护空洞。packed mip 必须在一次映射中完成,所以不能跨 Backing Heap,源码会 check。

7.1机制要点

  • 创建:CreateReservedResource;纹理必须使用 D3D12_TEXTURE_LAYOUT_64KB_UNDEFINED_SWIZZLE 布局,Alignment 为 0 或 64KB;Buffer 的对齐取 tile 大小,Stride 不得超过 64KB。
  • 提交:CommitReservedResource(RequiredCommitSizeInBytes) 把目标大小按 64KB 向上取整,用 GetResourceTiling 求出各 mip 的 tile 布局,再分段 UpdateTileMappings。带 TexCreate_ImmediateCommit 的纹理在创建时就提交全部。
  • 驻留:资源对象本身不注册驻留,驻留跟踪落在每个 Backing Heap 上;命令列表引用该资源时,会把所有 Backing Heap 的驻留句柄加入驻留集合。
  • 限制:不能带初始数据、不能是 Dynamic、不能使用自定义分配器(源码 checkf);Reserved Buffer 不能超过创建时的大小增长(UnifiedBuffer 中的 ensure)。

7.2引擎里谁在用

系统CVar默认为什么用 Reserved Resource
GPUScene 实例数据r.GPUScene.UseReservedResourcestrue一次预留最大容量,增长时只提交更多 tile,免去重建再拷贝 推断
虚拟阴影贴图物理页池r.Shadow.Virtual.AllocatePagePoolAsReservedResource1配合 ImmediateCommit,用 N 个小块代替一整块连续显存,让 WDDM 换入换出更高效 源码注释
稀疏体积纹理 tile 数据r.SparseVolumeTexture.Streaming.UseReservedResources1由多个小块物理内存支撑以减少碎片,并突破单资源 2GB 限制 源码注释
Nanite 流送r.Nanite.Streaming.ReservedResources0(实验)更好的显存利用率、更高效的扩缩 源码注释
Hair 曲线簇数据—必需要求平台支持 Reserved Resource(源码 check)

§8延迟释放、整理与回收

分配只是开始。块什么时候真正可复用、碎片怎么收拢、空池什么时候还给系统,全都挂在帧节奏上。

8.1Frame fence 与每帧节奏

FD3D12ManualFence 是一个手动推进的帧计数器:渲染线程在 RHIEndFrame_RenderThread 调用 AdvanceTOP,RHI 线程在 RHIEndFrame 末尾调用 AdvanceBOP,GetCompletedFenceValue 返回 GPU 已经完成的值。释放请求记下 GetNextFenceToSignal(),也就是「下一个将要发出的 fence」;只有 GPU 执行完这一帧的工作,IsFenceComplete 才返回 true,块才真正回到空闲状态。

帧 N 帧 N+1 帧 N+2 渲染线程 释放请求:记下 fence = N 录制帧 N 录制帧 N+1 录制帧 N+2 AdvanceTOP RHI 线程 执行帧 N EndFrame Begin 执行帧 N+1 EndFrame Begin 执行帧 N+2 EndFrame GPU 执行帧 N 的命令 执行帧 N+1 的命令 fence N 完成 真正回收:并入空闲、Release Placed Resource EndFrame:UploadHeapAllocator CleanUp(20) → 纹理池 CleanUp(20) → Buffer 池 CleanupFreeBlocks(20) → FastAllocator CleanupPages(10) → AdvanceBOP Begin(渲染线程在帧末排队的 lambda):Buffer 池整理并提交拷贝 → 纹理池整理并提交拷贝 → 光追压缩请求
图 12示意图,时间比例不代表真实耗时。CPU 侧的「释放」只是登记;物理上的回收一定晚于 GPU 完成对应帧。括号内数字是清理时使用的帧延迟参数。
对象何时可以复用空闲后多久还给系统
池内块Buffer 池、纹理池、BigBlockAllocator记录的 fence 完成后,下一次 CleanUpAllocations 时整个池为空,且最后使用的 fence + 20 ≤ 已完成 fence 时销毁Buffer 池处的代码注释写的是 2 帧,实际参数为 20
Buddy 块SmallBlockAllocator、FastConstantPageAllocator同上(DeferredDeletionQueue)整个 Buddy 池(4MB Buffer)为空且闲置 ≥20 帧时销毁
Bump PageFD3D12FastAllocatorPage 的 FrameFence 已完成且无人引用时,可被重新取出池内超过 5 个 Page 且闲置 ≥10 帧,每帧最多释放 1 个
Committed / StandAlone进入 RHI 延迟删除队列,GPU 用完后 ReleaseRelease 即归还
Transient Heap图执行结束,归还 Heap 缓存闲置 ≥16 个回收周期
Reserved Backing HeapDecommit 时 DeferDelete走延迟删除后归还

8.2碎片整理(Defrag)

目标池 #2 已用 85% 待清空池 #1 已用 18% 放不下,留在原池 0 32MB ① 尾部的 3MB 先搬 ② 0.8MB 进 1MB 空隙 每帧 Begin 时: ① 选一个使用率 < 90% 的池 ② 从链表尾部往前,逐块 TryAllocate 到更满的池,最满者优先 ③ 旧块走 fence 延迟释放 ④ 共享池:改指向新 Buffer 与偏移 Placed 池:在新偏移重建 Placed Resource ⑤ 通知监听者重建 SRV / UAV ⑥ 排队 CopyBufferRegion / CopyResource ⑦ 新块锁定到下一 fence;拷贝满 32MB 即停
图 13示例(按比例)。斜线块是锁定状态:上方是搬入后等待拷贝完成的新块,下方是等待 fence 的旧块。Best-fit 让 0.8MB 的块落进 1MB 的空隙,而不是去切 4.8MB 的大块。
  • 哪些池会整理:只有 DEFAULT Heap 上开启整理的池,即只读 / 可写 Buffer 池与 ReadOnly 纹理池。其余池不整理的原因见 §3.3、§4.3。
  • 哪些块会被搬:已分配、未锁定、有 owner 的块。刚创建还没写入初始数据的块、正在被整理的块都是锁定的。
  • 分配顺序配合整理:开启整理的分配器每次 CleanUp 后按已用量给池排序,新分配优先进最满的池 源码注释,空的池于是越来越空,最终被 20 帧回收规则销毁。不整理的分配器则优先小池,并按池序号稳定排序。
  • 代价:搬一个 Placed 块要重建 Placed Resource、重建视图,再做一次 GPU 拷贝。每帧拷贝预算为 32MB,Buffer 与纹理各一份(d3d12.VRAMBufferPoolDefrag.MaxCopySizePerFrame、d3d12.VRAMTexturePoolDefrag.MaxCopySizePerFrame);每次最多处理 1 个池(RHIPoolAllocator.DefragMaxPoolsToClear),在池之间轮转。

8.3池的增长

新池有两种来源:Immediate 在需要时于当前线程同步创建;Async 先取用预建的备用池,同时在后台线程再补建一个。DEFAULT Heap Buffer 池和 UploadHeapAllocator 使用 Async,AS 池和纹理池使用 Immediate。备用池创建时还不知道自己的池序号,被取用时才初始化空闲链表(InitFreeBlocks)。

§9驻留与 Heap Flags

分配决定资源放在哪里,驻留(Residency)决定它此刻是否真的占着显存。池化会让驻留粒度变粗,这是池化不太显眼的代价。

9.1默认不驻留,按需拉入

  • 开关:D3D12.ResidencyManagement = 1、D3D12.ResourcesStartResident = 0。Heap 与 Committed 资源以 D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT 创建,创建时不占显存预算。该标志需要 ID3D12Device8,系统不支持时强制改为创建即驻留。
  • 驻留对象:每个 Committed 资源、每个 Heap(池 Heap、Transient Heap、Reserved Resource 的 Backing Heap)各持有一个 FD3D12ResidencyHandle,注册到 D3DX12Residency 管理器。池里的 Placed Resource 没有自己的句柄,查询时返回所在 Heap 的句柄。
  • 何时拉入:命令列表提交前,上下文把本批命令用到的资源句柄(UpdateResidency)放进驻留集合,由管理器确保它们驻留;超出预算时,按最近最少使用的顺序驱逐其他对象。
  • 多 GPU:只跟踪 GPU 独占的 Heap,CPU 可访问的 Heap 不参与驻留管理。
Committed:驻留单位 = 单个资源 RT A RT B(被引用) UAV C 只把被引用的 RT B 拉进显存;A、C 可以各自被驱逐 Placed 池:驻留单位 = 整个 Heap 被引用的纹理 引用其中一张纹理,整个 64MB Heap 都要驻留;驱逐时也只能整个 Heap 驱逐 驻留优先级 HIGH:AS 池与大 AS、Scratch、Transient Heap、RT / DS / UAV 的 Reserved Backing Heap,显存紧张时这些对象最后才被驱逐 管理器按最近最少使用的顺序驱逐,预算来自 IDXGIAdapter3::QueryVideoMemoryInfo
图 14池化把许多资源并进一个驻留单位:创建和簿记更便宜,但驻留的最小粒度变成整个 Heap。这也是「ReadOnly 纹理池 64MB 一个 Heap」这一参数背后的权衡之一。

9.2Heap Flags 速查

标志 / 设置用在哪里作用
HEAP_FLAG_CREATE_NOT_RESIDENT池 Heap、Transient Heap、Committed 资源(不以驻留状态启动时)创建时不占显存预算,首次被引用才驻留
HEAP_FLAG_CREATE_NOT_ZEROED池 Heap、Transient Heap;Committed 资源中非 RT / DS 的跳过清零,加快创建;RT / DS 的 Committed 资源不加此标志
HEAP_FLAG_ALLOW_ONLY_BUFFERSBuffer 池、Buffer 的 Reserved Backing HeapHeap 内只放 Buffer
ALLOW_ONLY_NON_RT_DS_TEXTURES
ALLOW_ONLY_RT_DS_TEXTURES
纹理池、纹理的 Reserved Backing Heap按是否 RT / DS 分 Heap
ALLOW_ALL_BUFFERS_AND_TEXTURESTransient Heap(Tier 2)、开启分配追踪时的池 Heap混放;追踪时需要在 Heap 上放一个临时 Buffer 来取得 Heap 的 GPU 虚拟地址
HEAP_FLAG_SHAREDShared 资源(一律 Committed)跨进程 / 跨设备共享
RESIDENCY_PRIORITY_HIGHAS 池与大 AS、Scratch、Transient Heap、RT / DS / UAV 的 Reserved Backing Heap降低被驱逐的概率

§10数值与 CVar 速查

表中 d3d12.* 池参数与 RHI.TransientAllocator.* 多为只读 CVar,需要在启动前通过配置文件设置;RHIPoolAllocator.* 与 d3d12.FastAllocator.MinPagesToRetain 可在运行时修改。

数值含义宏 / CVar位置
对齐
16BBuffer Width 对齐、默认 Buffer AlignmentRHI_RAW_VIEW_ALIGNMENTRHIDefinitions.h
64B光追着色器表对齐D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENTd3d12.h
256B共享 Buffer 池粒度,超过即 PlacedkD3D12ManualSubAllocationAlignmentD3D12Allocation.h
256B常量缓冲放置对齐、AS 字节对齐D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT
D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BYTE_ALIGNMENT
d3d12.h
512BSmallBlockAllocator 最小块、BigBlockAllocator 对齐、静态 Buffer Lock 写与 Texture Staging 对齐D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENTd3d12.h
4KB小纹理放置对齐、ReadOnly4K 池D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENTd3d12.h
64KBBuffer 与默认纹理放置对齐、Placed Buffer 池、Transient 最小对齐、Reserved tileD3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT
MIN_PLACED_RESOURCE_SIZE
d3d12.h · D3D12Allocation.h
4MBMSAA 纹理放置对齐、Transient Heap 的 Desc.AlignmentD3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENTd3d12.h
Buffer 池
32MB / 16MBDEFAULT Heap Buffer 池大小 / 单次上限BUFFER_POOL_DEFAULT_POOL_SIZE
BUFFER_POOL_DEFAULT_POOL_MAX_ALLOC_SIZE
D3D12Allocation.cpp
4MB / 64KBReadback Pool 大小 / 单次上限READBACK_BUFFER_POOL_DEFAULT_POOL_SIZE
READBACK_BUFFER_POOL_MAX_ALLOC_SIZE
D3D12RHIDefinitions.h
32MB / 16MBAS 池大小 / 单次上限BUFFER_POOL_RT_ACCELERATION_STRUCTURE_POOL_SIZE
BUFFER_POOL_RT_ACCELERATION_STRUCTURE_MAX_ALLOC_SIZE
D3D12Allocation.cpp
1 · 32MBBuffer 池整理开关 · 每帧拷贝预算d3d12.VRAMBufferPoolDefrag
d3d12.VRAMBufferPoolDefrag.MaxCopySizePerFrame
D3D12Allocation.cpp
1 · 1新池 Async · 预建池数d3d12.DefaultBuffer.PoolAllocationStrategy
d3d12.DefaultBuffer.OverFlowPoolsCount
D3D12Allocation.cpp
1间接参数 Buffer 允许进池d3d12.AllowPoolAllocateIndirectArgBuffersD3D12Allocation.cpp
纹理池
64MB / 64MBReadOnly 池 Heap 大小 / 单次上限d3d12.PoolAllocator.ReadOnlyTextureVRAMPoolSize
d3d12.PoolAllocator.ReadOnlyTextureMaxAllocationSize
D3D12Allocation.cpp
0 / 0RT、UAV 纹理池(默认关闭)d3d12.PoolAllocator.RTUAVTextureVRAMPoolSize
d3d12.PoolAllocator.RTUAVTextureMaxAllocationSize
D3D12Allocation.cpp
4MB / 4MBReadOnly4K 池(硬编码)—D3D12Allocation.cpp
1 · 32MB纹理池整理开关 · 每帧拷贝预算d3d12.VRAMTexturePoolDefrag
d3d12.VRAMTexturePoolDefrag.MaxCopySizePerFrame
D3D12Allocation.cpp
整理(通用)
0.9使用率不低于该值的池不整理RHIPoolAllocator.DefragSizeFractionRHIPoolAllocator.cpp
1每次整理最多处理的池数(负数表示全部、不分帧)RHIPoolAllocator.DefragMaxPoolsToClearRHIPoolAllocator.cpp
UPLOAD 与常量
64KB / 4MBSmallBlockAllocator 上限 / 池大小d3d12.UploadHeap.SmallBlock.MaxAllocationSize
d3d12.UploadHeap.SmallBlock.PoolSize
D3D12Allocation.cpp
64MB / 32MBBigBlockAllocator 上限 / 池大小d3d12.UploadHeap.BigBlock.MaxAllocationSize
d3d12.UploadHeap.BigBlock.PoolSize
D3D12Allocation.cpp
1 · 1UploadHeapAllocator 新池 Async · 预建池数d3d12.UploadHeap.PoolAllocationStrategy
d3d12.UploadHeap.OverFlowPoolsCount
D3D12Allocation.cpp
64KBFastConstantAllocator Page 大小(也是 FastConstantPageAllocator 的最小块)d3d12.FastConstantAllocatorPageSizeD3D12Allocation.cpp
4MBFastAllocator Page 大小(硬编码)FD3D12Device 构造函数D3D12Device.cpp
5FastAllocator 至少保留的 Page 数d3d12.FastAllocator.MinPagesToRetainD3D12Allocation.cpp
Transient 与 Reserved
1RDG 使用 Transient 分配器r.RDG.TransientAllocatorRenderGraphPrivate.cpp
128MBTransient Heap 最小尺寸RHI.TransientAllocator.MinimumHeapSizeRHICoreTransientResourceAllocator.cpp
64 · 64每个 Transient Heap 缓存的 Placed 纹理 / Buffer 数RHI.TransientAllocator.TextureCacheSize
RHI.TransientAllocator.BufferCacheSize
RHICoreTransientResourceAllocator.cpp
16Transient Heap 回收延迟(周期)RHI.TransientAllocator.GarbageCollectLatencyRHICoreTransientResourceAllocator.cpp
16MBReserved Resource 的 Backing Heap 大小d3d12.ReservedResourceHeapSizeMBD3D12Resources.cpp
回收与驻留
20 · 20 · 20 · 10UploadHeapAllocator · 纹理池 · Buffer 池 · FastAllocator Page 的清理帧延迟硬编码D3D12Adapter.cpp · D3D12Allocation.cpp · D3D12RHI.cpp
1 · 0驻留管理开关 · 创建即驻留D3D12.ResidencyManagement
D3D12.ResourcesStartResident
D3D12Adapter.cpp
0追踪全部分配(开启后分配形态会改变,见 §4.5)D3D12.TrackAllAllocationsD3D12Adapter.cpp

展望本版本 D3D12RHI 没有使用 D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT(源码中无引用)。按新规范,紧凑对齐的 Buffer 放置对齐可以低到 256B 以内 官方文档;一旦采用,Placed Buffer 的 64KB 取整不再成立,共享 Buffer 池存在的主要理由(约束 ②)会被削弱,但单状态约束 ③ 依然存在 推断。

§11源码索引与参考

路径相对引擎根目录,行号对应本文依据的 UE 5.8.1 源码。

  • D3D12RHI/Private/D3D12Allocation.h
    分配器声明;kD3D12ManualSubAllocationAlignment(25);MIN_PLACED_RESOURCE_SIZE(73)
  • D3D12RHI/Private/D3D12Allocation.cpp
    CVar 默认值(19–190)· Buddy 后备资源 Initialize(278–374)· Buddy(381–771)· MultiBuddy(777–1032)· UploadHeapAllocator(1041–1187:三个子分配器 1041–1087、AllocUploadResource 1103–1137、AllocFastConstantAllocationPage 1140–1148)· DEFAULT Buffer 池(1418–1747)· 纹理池(1754–1934)· FastAllocator(2094–2305)· FastConstantAllocator(2307–2349)
  • D3D12RHI/Private/D3D12PoolAllocator.h / .cpp
    策略枚举(h 7–19)· 池初始化:Placed 用 CreateHeap、共享用 CreateBuffer(cpp 45–164)· 策略判定(231–247)· AllocateResource(345–564)· 释放(595–667)· 整理(690–777)· CleanUp(780–863)· 拷贝(979–1075)
  • RHICore/Private/RHIPoolAllocator.cpp
    对齐计算(252–277)· TryAllocate 与 Best-fit(363–407、475–515)· 合并(531–579)· 池排序(932–958)· Defrag(961–1073)
  • D3D12RHI/Private/D3D12Resources.h / .cpp
    FD3D12ResourceLocation(h 613–791)· CommitReservedResource(cpp 223)· Create*Resource(784–1150,CreateBuffer 即 CreateCommittedResource 1123–1150)· ReleaseResource(1297–1400)· OnAllocationMoved(1498–1624)
  • D3D12RHI/Private/D3D12Buffer.cpp
    AllocateUploadMemory 线程分流(78–95)· AllocateBuffer(168–233)· GetResourceDescAndAlignment(377–424)· CreateBufferInternal(474–519)· CreateBufferInitializerForWriting(521–561)· Lock / Unlock(637–834)
  • D3D12RHI/Private/D3D12Texture.cpp
    CanBe4KAligned(181–211)· GetResourceDesc(224–361)· SafeCreateTexture2D(481–587)· CreateTextureInternal(621 起,CPUReadback 695–708)· CreateTextureInitializerForWriting(1029–1067)· RHIAsyncCreateTexture2D(1154 起)· Lock / UploadInitialData / Unlock(1958–2298)· UpdateTexture2D(2300–2399)· BeginUpdateTexture3D(2693–2736)
  • D3D12RHI/Private/D3D12UniformBuffer.cpp
    UB 上限 check(61)· MultiFrame / SingleFrame 分流(73–90、191–206)
  • D3D12RHI/Private/D3D12TransientResourceAllocator.cpp
    Transient Heap 创建(9–87)· 纹理与 Buffer 的 Placed Resource(137–242)
  • RHICore/Private/RHICoreTransientResourceAllocator.cpp
    首次适配与别名(132–296)· 默认参数(521–530)· Heap 缓存(542–627)
  • D3D12RHI/Private/D3D12RayTracing.cpp
    CreateRayTracingBuffer(3688–3749)· 着色器绑定表(1757–1784)
  • D3D12RHI/Private/D3D12RHI.cpp · D3D12Adapter.cpp
    帧节奏(RHI 547–612)· EndFrame(Adapter 1837–1875)· 驻留 CVar(Adapter 29–46)· NOT_ZEROED / NOT_RESIDENT 支持检测(Adapter 1101–1145)
  • D3D12RHI/Private/D3D12Adapter.h · D3D12CommandContext.h · D3D12Device.cpp
    FTransientUniformBufferAllocator(Adapter.h 133)· GetTransientUniformBufferAllocator(Adapter.cpp 1877–1892)· ConstantsAllocator(CommandContext.h 570)· DefaultFastAllocator 4MB UPLOAD(Device.cpp 118)
  • D3D12RHI/Private/D3D12Commands.cpp · D3D12DirectCommandListManager.cpp
    RHICopyToStagingBuffer(Commands 214–289)· FD3D12StagingBuffer::Lock(DirectCommandListManager 114–127)
  • D3D12RHI/Private/D3D12RenderTarget.cpp
    GetStagingTexture(367 起)· ReadSurfaceDataMSAARaw(670 起)· RHIMapStagingSurface(850–888)· RHIReadSurfaceFloatData(907 起)· RHIRead3DSurfaceFloatData(1038 起)
  • D3D12RHI/Private/D3D12Query.cpp
    Query Heap 结果 Buffer(99–124)
  • RHI/Private/RHIGPUReadback.cpp
    FRHIGPUBufferReadback 使用 Staging Buffer(45–55)· FRHIGPUTextureReadback 创建 CPUReadback 纹理(150–180)· MapStagingSurface(223)
  • D3D12RHI/Private/D3D12RHIDefinitions.h · Windows/WindowsD3D12RHIDefinitions.h
    平台开关(USE_BUFFER_POOL_ALLOCATOR 等)与池尺寸宏
  • D3D12RHI/Private/D3D12Util.h
    GetTilesNeeded · Get4KTileShape(399 起)

11.1官方文档

  1. D3D12_RESOURCE_DESC:Alignment 的取值规则、小资源的 4KB / 64KB 条件
  2. ID3D12Device::GetResourceAllocationInfo:纹理大小因适配器而异;非紧凑对齐的 Buffer 按 64KB 取整
  3. Residency:Placed Resource 的偏移对齐、Heap 与驻留
  4. D3D12_HEAP_TYPE:UPLOAD 资源须为 GENERIC_READ,READBACK 资源须为 COPY_DEST 且不能转换状态
  5. UMA Optimizations: CPU Accessible Textures and Standard Swizzle:512B、256B 等数据放置对齐常量
  6. Direct3D 12 Tight Placed Resource Alignment:紧凑对齐规范