D3D12 增强的屏障

Windows 11版本 22H2 WDK (WDDM 3.0) 中提供了用于增强屏障的 DDI 接口。 若要在 22H2 (或早期操作系统版本) 上使用增强障碍,必须安装 1.706.4 预览版 Agility SDK

D3D12 增强障碍使开发人员能够独立控制 GPU 工作同步、纹理布局转换和缓存刷新 (“资源内存访问”) 。 此功能提供了一组 Direct3D API 和 DDI,使开发人员能够独立控制 GPU 工作同步、纹理布局转换和缓存刷新 (资源内存访问) 。

增强的屏障将 旧资源屏障 替换为更具表现力的屏障类型。 功能包括以下内容:

  • 减少同步延迟。
  • 减少过多的缓存刷新。
  • 没有神秘的促销和衰落规则。
  • 快速、灵活的资源别名 (不同的别名拓扑) 。
  • 在屏障转换期间放弃。
  • 支持并发读/写,包括同一资源复制 (自复制) 。
  • 支持异步放弃、复制、解析和清除命令。

增强的屏障并不比旧版资源屏障简单,但它们的模糊性要小得多,因此更易于开发人员使用。

报告增强屏障支持

增强的屏障功能目前不是硬件或驱动程序要求。 驱动程序通过将 D3D12DDI_D3D12_OPTIONS_DATA_0089EnhancedBarriersSupported 成员设置为 TRUE 来指示支持。

  • D3D12DDI_FEATURE_VERSION_VIDEO_0088_0是定义 Windows 11 中引入的 D3D12 增强屏障里程碑的初步实现的版本号。

D3D12 增强屏障回调函数

指示支持增强障碍的驱动程序实现以下回调函数:

设计详细信息

驱动程序通常使用三个单独的操作来处理旧资源屏障:

  1. 同步 GPU 工作。
  2. 执行任何必要的缓存刷新操作。
  3. 执行任何必要的布局更改。

增强的屏障使开发人员能够单独控制其中每个操作。

增强障碍的类型

有三种类型的 增强 障碍:

范围障碍 取代 了旧版资源屏障。 提供范围障碍,以便可以完全实现旧资源屏障,且不会造成明显的性能损失。

  • 所有屏障类型都控制 GPU 工作同步以及屏障前后的读取或写入访问类型。

  • 纹理屏障还管理纹理子资源的布局。 除了旧资源屏障使用的熟悉的一个或全部选项外,子资源选择还可以表示为 mip、数组和平面切片的范围。

  • 缓冲区屏障和全局屏障仅控制同步和资源访问,对资源布局没有影响, (缓冲区没有布局) 。 全局屏障会影响所有缓存的内存,因此它们可能很昂贵,并且仅当范围更广的屏障不足时才应使用。

纹理屏障

  • 控制纹理子资源的缓存刷新、内存布局和同步。
  • 只能与纹理资源一起使用。
  • 允许选择单个子资源、所有子资源或一致的子资源范围 (即 mip 范围和数组范围) 。
  • 必须提供有效的非 NULL 资源指针。

缓冲区屏障

  • 控制缓冲区资源的缓存刷新和同步。
  • 只能与缓冲区资源一起使用。
  • 与纹理不同,缓冲区只有一个子资源,并且没有可转换的布局。
  • 必须提供有效的非 NULL 资源指针。

全局障碍

  • 控制单个命令队列中所有指示的资源访问类型的缓存刷新和同步。
  • 对纹理布局没有影响。
  • 需要提供类似于旧版 NULL UAV 屏障和 NULL/NULL 别名屏障的功能。

由于全局屏障不转换纹理布局,因此不能在需要更改布局的过渡中使用全局屏障。 例如,全局屏障不能用于将非同时访问纹理从D3D12DDI_BARRIER_ACCESS_RENDER_TARGET转换到D3D12DDI_BARRIER_ACCESS_SHADER_RESOURCE,因为这还需要将D3D12DDI_BARRIER_LAYOUT_RENDER_TARGET更改为D3D12DDI_BARRIER_LAYOUT_SHADER_RESOURCE。

同步

图形处理器设计为可以并行执行尽可能多的工作。 在访问依赖数据之前,必须同步依赖于以前的 GPU 工作的任何 GPU 工作。

增强屏障接口使用显式 SyncBeforeSyncAfter 值作为逻辑位字段掩码。 在执行屏障之前,屏障必须等待前面的所有命令 SyncBefore 范围完成。 同样,屏障必须阻止所有后续 SyncAfter 范围,直到屏障完成。 D3D12DDI_BARRIER_SYNC 指定 GPU 工作相对于屏障的同步范围。

有关详细信息,请参阅 增强障碍规范

布局过渡

纹理子资源可以对各种访问方法使用不同的布局。 例如,纹理在用作呈现目标或深度模具时通常会压缩,并且通常未压缩着色器读取或复制命令。 纹理屏障使用 LayoutBeforeLayoutAfterD3D12DDI_BARRIER_LAYOUT 值来描述布局过渡。

布局转换仅适用于纹理,因此它们仅在 D3D12DDI_TEXTURE_BARRIER 数据结构中表示。

LayoutBeforeLayoutAfter 都必须与执行屏障的队列类型兼容。 例如,计算队列无法将子资源转换为或移出 D3D12DDI_BARRIER_LAYOUT_RENDER_TARGET

为了提供明确定义的屏障排序,在完成一系列屏障后,子资源的布局是序列中最后的 LayoutAfter

访问转换

由于许多 GPU 写入操作都是缓存的,从写入访问到另一个写入访问或只读访问的任何障碍都可能需要缓存刷新。 增强的屏障 API 使用访问转换来指示需要使子资源的内存对特定的新访问类型可见。 与布局转换一样,如果已知关联子资源的内存已可供所需使用,则可能不需要某些访问转换。

访问转换的表示方式如下:

  • 对于纹理,作为D3D12DDI_TEXTURE_BARRIER结构的一部分。
  • 对于缓冲区,作为D3D12DDI_BUFFER_BARRIER结构的一部分。

访问转换不执行同步。 预期使用屏障中的相应 SyncBeforeSyncAfter 值来处理依赖访问之间的同步。

对指定的 AccessAfter 可见的 AccessBefore 不保证资源内存对于其他访问类型也可见。 例如:

MyTexBarrier.AccessBefore=D3D12DDI_BARRIER_ACCESS_UNORDERED_ACCESS;
MyTexBarrier.AccessAfter=D3D12DDI_BARRIER_ACCESS_SHADER_RESOURCE;

此访问转换指示后续着色器读取访问依赖于前面的无序访问-写入。 但是,如果硬件能够直接从 UAV 缓存读取着色器资源,则驱动程序实际上可能不会刷新 UAV 缓存。

D3D12DDI_BARRIER_ACCESS_COMMON

D3D12DDI_BARRIER_ACCESS_COMMON 是一种特殊的访问类型,指示任何布局兼容的访问。 转换为 D3D12DDI_BARRIER_ACCESS_COMMON 意味着子资源数据必须可用于屏障后的任何布局兼容访问。 由于缓冲区没有布局, D3D12DDI_BARRIER_ACCESS_COMMON 仅表示任何缓冲区兼容的访问。

在屏障中将 D3D12DDI_BARRIER_ACCESS_COMMON 指定为 AccessBefore 意味着所有写入访问类型的集。 不建议使用 D3D12DDI_BARRIER_ACCESS_COMMON 作为 AccessBefore ,因为这可能会导致成本高昂的意外缓存刷新。 相反,建议开发人员仅使用最狭义的写入访问位来正确限制屏障开销。 当 AccessBefore 设置为 D3D12DDI_BARRIER_ACCESS_COMMON 时,将发出调试层警告。

单队列同时访问

增强的屏障允许对同一命令队列中的同一缓冲区或同时访问纹理执行并发读/写操作。

缓冲区和同时访问的资源始终支持从一个队列进行写入访问,并从一个或多个其他队列进行并发的非依赖读取访问权限。 这种支持是因为此类资源始终使用 COMMON 布局,并且没有读/写危险,因为读取不得依赖于并发写入。 (旧版资源屏障规则不允许将写入状态位与任何其他状态位组合在一起。因此,不能使用旧版资源 barriers 在同一队列中同时读取和写入资源。)

一次一个写入器策略仍然适用,因为两个看似不重叠的写入区域可能仍具有重叠的缓存行。

子资源范围

开发人员通常需要转换一系列子资源;例如,转换给定纹理数组的完整 mip 链或所有数组切片的单个 mip 级。 增强的屏障允许开发人员使用 D3D12DDI_BARRIER_SUBRESOURCE_RANGE 结构转换逻辑上相邻的子资源范围。 (旧版资源状态转换屏障仅向开发人员提供以原子方式转换 所有 子资源状态或单个子资源状态的选项。)

计算和直接队列布局

对于直接队列和计算队列,以下 增强型屏障布局 保证相同:

  • D3D12DDI_BARRIER_LAYOUT_GENERIC_READ
  • D3D12DDI_BARRIER_LAYOUT_UNORDERED_ACCESS
  • D3D12DDI_BARRIER_LAYOUT_SHADER_RESOURCE
  • D3D12DDI_BARRIER_LAYOUT_COPY_SOURCE
  • D3D12DDI_BARRIER_LAYOUT_COPY_DEST

这些布局之一中的子资源可以在没有布局转换的直接队列或计算队列中使用。

在某些硬件上,如果之前或后续访问也位于直接队列上,则直接队列上的布局转换屏障可以显著加快。 强烈建议使用以下布局访问直接队列上的资源:

  • D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_GENERIC_READ
  • D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_UNORDERED_ACCESS
  • D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_SHADER_RESOURCE
  • D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_SOURCE
  • D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_COPY_DEST

DIRECT_QUEUE布局变体与计算队列不兼容,不能在计算命令列表屏障中使用。 但是,它们与直接队列中的计算操作兼容。

无障碍访问

由于 ExecuteCommandLists 边界之间必须没有挂起的命令或缓存刷新操作,因此最初可以在 ExecuteCommandLists 范围内访问缓冲区,且没有屏障。 同样,在以下情况下,最初访问纹理子资源时也可能没有屏障:

  • 子资源布局与访问类型兼容。
  • 任何必要的压缩元数据都已初始化。

可以在 ExecuteCommandLists 命令流中访问布局D3D12DDI_BARRIER_LAYOUT_COMMON (或特定于队列的通用布局(例如,没有潜在未完成的读取或写入操作的D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_COMMON) )中的纹理子资源,而无需使用以下任何访问类型进行访问:

  • D3D12DDI_BARRIER_ACCESS_SHADER_RESOURCE
  • D3D12DDI_BARRIER_ACCESS_COPY_SOURCE
  • D3D12DDI_BARRIER_ACCESS_COPY_DEST

此外,使用特定于队列的通用布局的缓冲区或纹理可以使用无屏障D3D12DDI_BARRIER_ACCESS_UNORDERED_ACCESS。

使用D3D12DDI_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS标志) 创建的纹理 (缓冲区和同时访问的纹理最初可以在 ExecuteCommandLists 命令流中访问,而无需使用以下任何访问类型进行屏障:

  • D3D12DDI_BARRIER_ACCESS_VERTEX_BUFFER
  • D3D12DDI_BARRIER_ACCESS_CONSTANT_BUFFER
  • D3D12DDI_BARRIER_ACCESS_INDEX_BUFFER
  • D3D12DDI_BARRIER_ACCESS_RENDER_TARGET
  • D3D12DDI_BARRIER_ACCESS_UNORDERED_ACCESS
  • D3D12DDI_BARRIER_ACCESS_SHADER_RESOURCE
  • D3D12DDI_BARRIER_ACCESS_STREAM_OUTPUT
  • D3D12DDI_BARRIER_ACCESS_INDIRECT_ARGUMENT
  • D3D12DDI_BARRIER_ACCESS_COPY_DEST
  • D3D12DDI_BARRIER_ACCESS_COPY_SOURCE
  • D3D12DDI_BARRIER_ACCESS_RESOLVE_DEST
  • D3D12DDI_BARRIER_ACCESS_RESOLVE_SOURCE
  • D3D12DDI_BARRIER_ACCESS_PREDICATION

后续访问也可以在没有屏障的情况下进行,其写入访问类型不超过一种。 但是,除D3D12DDI_BARRIER_ACCESS_RENDER_TARGET外,必须使用屏障刷新对同一资源的顺序写入。

自我资源复制

尽管与增强的屏障不完全相关,但允许从子资源的一个区域复制到另一个非相交区域的功能是一项要求很高的功能。 通过增强的屏障,具有通用布局的子资源可以用作同一 CopyBufferRegion 或 CopyTextureRegion 调用中的源和目标。 相交的源内存区域与目标内存区域之间的复制会产生未定义的结果。 调试层必须针对此类结果进行验证。 (旧版资源屏障设计不允许子资源同时处于D3D12DDI_RESOURCE_STATE_COPY_SOURCE和D3D12DDI_RESOURCE_STATE_COPY_DEST状态,因此无法复制到自身。)

放置的资源元数据初始化

旧版资源屏障设计要求在用作呈现目标或深度模具资源之前,通过清除、复制或放弃初始化新放置和激活的别名纹理资源。 此要求是因为呈现目标和深度模具资源通常使用压缩元数据,这些元数据必须进行初始化才能使数据有效。 对于具有新更新的图块映射的保留纹理也是如此。

增强的屏障支持将“放弃”选项作为屏障的一部分。 屏障布局从D3D12DDI_BARRIER_LAYOUT_UNDEFINED转换为任何可能压缩的布局 (例如,D3D12DDI_BARRIER_LAYOUT_RENDER_TARGET、D3D12DDI_BARRIER_LAYOUT_DEPTH_STENCIL D3D12DDI_BARRIER_LAYOUT_UNORDERED_ACCESS) 当 D3D12DDI_TEXTURE_BARRIER::Flags 成员中存在D3D12DDI_TEXTURE_BARRIER_FLAG_DISCARD时必须初始化压缩元数据。

除了渲染目标和深度/模具资源外,还有旧式屏障模型不支持的类似 UAV 纹理压缩优化。

屏障排序

屏障按前向顺序排队, (API 调用顺序、barrier-group-index、barrier-array-index) 。 同一子资源上的多个屏障必须像按排队顺序完成屏障一样运行。

具有匹配 的 SyncAfter 作用域(可能写入同一内存)的排队屏障必须按排队顺序完成所有写入。 此要求可避免在支持资源别名的屏障上发生数据争用。 例如,“停用”某个资源的屏障必须在“激活同一内存上的不同资源”的另一个屏障之前刷新所有缓存,从而可能清除元数据。