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 工作相对于屏障的同步范围。

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

布局转换

纹理子资源可以为各种访问方法使用不同的布局。 例如,当用作呈现目标或深度模具时,纹理通常会被压缩;对于着色器读取或复制命令,纹理通常不会被压缩。 纹理屏障使用 LayoutBeforeLayoutAfter D3D12DDI_BARRIER_LAYOUT 值来描述布局转换。

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

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

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

访问转换

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

访问转换表示如下:

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

访问转换不执行同步。 预计依赖访问之间的同步将使用屏障中适当的 SyncBeforeSyncAfter 值来处理。

AccessBefore 对于指定的 AccessAfter 可见,并不能保证资源内存对于不同的访问类型也是可见的。 例如:

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 布局,并且没有读/写危险,因为读取不能依赖于并发写入。 (旧资源屏障规则禁止将写入状态位与任何其他状态位组合在一起。因此,使用旧资源屏障不能在同一队列中同时读取和写入资源。)

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

子资源范围

开发人员通常希望转换一系列子资源。 一个例子是为给定的纹理数组转换完整的 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 范围内访问。 同样,在以下条件下,纹理子资源最初也可以在没有屏障的情况下访问:

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

布局 D3D12DDI_BARRIER_LAYOUT_COMMON 中的纹理子资源(或特定于队列的常见布局,如 D3D12DDI_BARRIER_LAYOUT_DIRECT_QUEUE_COMMON)没有可能未完成的读取或写入操作,可以在 ExecuteCommandLists 命令流中访问,而不需要使用以下任何访问类型的屏障:

  • 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_TEXTURE_BARRIER::Flags 成员中存在 D3D12DDI_TEXTURE_BARRIER_FLAG_DISCARD 时,屏障布局从 D3D12DDI_BARRIER_LAYOUT_UNDEFINED 转换到任何潜在的压缩布局(例如,D3D12DDI_BARRIER_LAYOUT_RENDER_TARGET、D3D12DDI_BARRIER_LAYOUT_DEPTH_STENCIL、D3D12DDI_BARRIER_LAYOUT_UNORDERED_ACCESS)必须初始化压缩元数据。

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

屏障排序

屏障按正向顺序排列(API 调用顺序、屏障组索引、屏障阵列索引)。 同一子资源上的多个屏障必须按排队顺序完成。

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