资源管理最佳做法

托管纹理(也称为自动纹理管理)自版本 6 起在 DirectX 中一直可用,并在后续版本中进行了多次修订和增强。 自 Direct3D 9 API 发布起,自动资源管理包括对纹理、顶点缓冲区和索引缓冲区的支持,所有这些都具有一致的共享接口。 通过使用 Direct3D 资源管理器,应用程序可以大大简化丢失设备情况的处理,并可以依赖系统来处理合理数量的过度承诺的视频内存资源。

开发人员有时在使用托管资源时遇到困难,部分原因是系统的抽象性质。 虽然资源的许多常见方案非常适合托管资源,但在某些情况下,使用非托管资源时性能更好。 本文介绍一般情况下处理资源的最佳做法、托管和非托管资源的行为方式,并提供运行时和驱动程序通常如何处理资源的一些详细信息。

本文介绍以下概念:

视频内存

要使视频系统能够使用资源,它必须位于 GPU 可访问的内存中。 本地视频内存为 GPU 提供最佳性能, (某些资源(如呈现目标和深度/模具缓冲区) 必须位于本地视频内存中。 随着 AGP 的出现,GPU 还可以直接访问部分系统内存。 此内存区域称为 AGP 光圈,称为非本地视频内存,不能用于其他目的。 非本地视频内存可由 CPU 读取和写入,CPU 通常无法高性能访问本地视频内存,因此非常适合用作共享内存资源。 关于 AGP 内存,需要记住的一个重要事项是,它与本地视频内存一样,在丢失设备的情况下失效,必须还原位于该处的永久性资产。

图 1. GPU、CPU、视频 RAM 和系统 RAM 的关系

gpu、cpu、视频 ram 和系统 ram 的关系

一些集成视频解决方案使用统一的内存体系结构 (UMA) ,其中main内存可由系统的所有组件寻址。 Direct3D 使用与本地视频内存配置相同的提示,无需对应用程序进行任何更改即可支持 UMA。 对于此类系统,资源始终位于系统内存中,驱动程序负责确保资源的工作方式与在更传统的体系结构中的工作方式非常类似,同时利用 UMA 的属性和硬件实现的任何特定行为。

图 2. GPU 和 CPU 在统一内存体系结构中具有对系统 RAM 的同等访问权限

gpu 和 CPU 在统一内存体系结构中具有对系统 ram 的同等访问权限

托管资源

大多数资源应创建为 POOL_MANAGED 中的托管资源。 所有资源都将在系统内存中创建,然后根据需要复制到视频内存中。 设备丢失情况将从系统内存副本自动处理。 由于并非所有托管资源都需要同时放入视频内存中,因此,可以在任何给定帧中呈现所需的较小视频内存工作资源集时过度提交内存。 请注意,随着时间推移,此后备存储系统内存的大部分可能会分页到磁盘,这就是为什么重置操作可能会很慢的原因,因为需要重新分页此数据以还原丢失的视频内存。

运行时在上次使用资源时会保留时间戳,当视频内存分配因加载所需托管资源失败时,它将以 LRU 方式基于此时间戳释放资源。 SetPriority 的使用优先于时间戳,因此更常用的资源应设置为更高的优先级值。 Direct3D 9.0 关于驱动程序管理的视频内存的信息有限,因此运行时可能需要逐出多个资源才能创建足够大的区域,以便分配成功。 适当的优先级可以帮助消除某些内容被逐出的情况,然后在不久之后再次需要。 应用程序还可以调用 EvictManagedResources 来强制删除所有托管资源。 同样,这是重新加载下一帧所需的所有资源的耗时操作,但对于工作集发生重大更改的级别转换和消除视频内存碎片非常有用。

还会保留帧计数,使运行时能够检测它刚刚选择逐出的资源是否在当前帧早期使用过,这意味着在单个帧中使用的资源多于视频内存中占用的资源。 这会触发替换策略切换到 MRU 模式,而不是对帧的剩余部分使用 LRU,因为在这种情况下,这往往性能稍好一些。 这种抖动行为将显著影响呈现性能。 请注意,当前帧的概念与 EndScene 相关,因此任何使用托管资源的应用程序都需要定期调用此方法。

希望查找有关托管资源在其应用程序中的行为方式的详细信息的开发人员可以通过 IDirect3DQuery9 接口使用 RESOURCEMANAGER 事件查询。 这仅在使用调试运行时时有效,因此应用程序不能依赖此信息,但它提供有关运行时管理的资源的深入详细信息。

虽然了解资源管理器的工作原理有助于优化和调试应用程序,但不要将应用程序与当前运行时或驱动程序的实现详细信息过于紧密地联系在一起。 驱动程序的修订或硬件的更改可能会显著改变行为,未来版本的 Direct3D 将显著改进和复杂的资源管理。

驱动程序管理的资源

Direct3D 驱动程序可以自由实现由 D3DCAPS2_CANMANAGERESOURCE 指示的驱动程序托管纹理功能,这允许驱动程序处理资源管理而不是运行时。 对于实现此功能的 (罕见的) 驱动程序,驱动程序的资源管理器的确切行为可能会有很大差异,你应联系驱动程序的供应商,以了解其实现方式的详细信息。 或者,可以通过在创建设备时指定D3DCREATE_DISABLE_DRIVER_MANAGEMENT来确保始终使用运行时管理器。

默认资源

虽然托管资源简单、高效且易于使用,但有时直接使用视频内存是首选,甚至需要。 此类资源在POOL_DEFAULT类别中创建。 使用此类资源确实会给应用程序带来额外的复杂情况。 需要代码来处理所有POOL_DEFAULT资源的设备丢失情况,在将数据复制到资源时必须考虑性能注意事项。 如果未指定USAGE_WRITEONLY或使呈现目标可锁定,也会造成严重的性能损失。

对POOL_DEFAULT资源调用 Lock 比使用POOL_MANAGED资源更可能导致 GPU 停止,除非使用某些提示标志。 根据资源的位置,返回的指针可以是指向临时系统内存缓冲区,也可以是直接进入 AGP 内存的指针。 如果是临时系统内存缓冲区,则需要在 解锁 调用后将数据传输到视频内存。 如果视频资源不是只写资源,则必须在 锁定期间将数据传输到临时缓冲区。 如果是 AGP 内存区域,则避免临时复制,但所需的缓存行为可能会导致性能降低。

应小心地将完整的缓存行数据写入到指向 AGP 孔径内存的任何指针中,以避免写入合并的惩罚,这会导致读写周期,并且首选对内存区域的顺序访问。 如果应用程序需要在创建过程中随机访问数据,并且你不希望为缓冲区使用托管资源,则应改用系统内存副本。 创建数据后,可以将结果流式传输到锁定的资源内存中,以避免对缓存写入合并操作产生高额损失。

LOCK_NOOVERWRITE标志可用于有效地为某些资源追加数据,但理想情况下,可以避免对同一资源进行多次 LockUnlock 调用。 正确使用各种锁标志对于最佳性能非常重要,在填充锁定的内存时使用缓存友好的数据访问模式也很重要。

使用托管资源和默认资源

混合分配托管资源和POOL_DEFAULT资源可能会导致视频内存碎片,并混淆运行时对托管资源可用的视频内存的视图。 理想情况下,应在使用POOL_MANAGED资源之前创建所有POOL_DEFAULT资源,或者在分配非托管资源之前使用 EvictManagedResources 调用。 请记住,从驻留在视频内存中的POOL_DEFAULT进行的所有分配都会占用资源不可用于资源管理器或任何其他用途的生存期内存。

请注意,与以前版本的 Direct3D 不同,版本 9 运行时会在放弃因视频内存不足而失败的非托管资源分配之前自动逐出一些托管资源,但这可能会造成额外的碎片,甚至强制将资源强制进入欠佳位置 (例如,在非本地视频内存) 具有静态纹理。 同样,最好先分配所有必需的非托管资源,然后再使用任何托管资源。

动态默认资源

以高频率生成和更新的数据不需要后备存储,因为还原设备时将重新创建所有信息。 通常最好在 POOL_DEFAULT 中创建此类数据,指定USAGE_DYNAMIC提示,以便驱动程序可以在放置资源时做出优化决策,知道资源会经常更新。 这通常意味着将资源放入非本地视频内存中,因此 GPU 访问速度通常比本地视频内存慢得多。 对于 UMA 体系结构,驱动程序可能会为动态资源选择特定的位置,以针对 CPU 写入访问进行优化。

此用法通常适用于填充顶点/索引缓冲区的软件外观解决方案和基于 CPU 的粒子系统,并且LOCK_DISCARD标志可确保在上一帧中仍在使用资源的情况下不会创建停止。 在这种情况下,使用托管资源会更新系统内存缓冲区,然后将该缓冲区复制到视频内存,然后在替换之前仅用于一两帧。 对于具有非本地视频内存的系统,通过正确使用此动态模式可以消除额外的副本。

标准纹理无法锁定,只能通过 UpdateSurfaceUpdateTexture 进行更新。 某些系统支持动态纹理,这些纹理可以锁定并使用LOCK_DISCARD模式,但在使用此类资源之前,必须检查 (D3DCAPS2_DYNAMICTEXTURES) 的功能。 对于高度动态的纹理 (视频或过程) ,应用程序可以使用 UpdateTexture 创建匹配POOL_DEFAULT和POOL_SYSTEMMEM资源并处理视频内存更新。 对于高度频繁和部分更新, UpdateTexture 范例可能是更好的选择。

与动态资源一样有用,在设计严重依赖动态提交的系统时要小心。 静态资源应置于POOL_MANAGED中,以确保本地视频内存的合理利用,并更有效地利用有限的总线和main内存带宽。 对于半静态资源,你可能会发现偶尔上传到本地视频内存的成本远远低于通过使其动态生成的恒定总线流量的成本。

系统内存资源

还可以在 POOL_SYSTEMMEM 中创建资源。 虽然图形管道无法使用它们,但它们可用作使用 UpdateSurfaceUpdateTexture 更新POOL_DEFAULT资源的源。 它们的锁定行为很简单,但如果它们正由上述方法之一使用,则可能会发生停止。

尽管它们驻留在系统内存中,但POOL_SYSTEMMEM资源仅限于相同的格式和功能 (,例如设备驱动程序支持的最大大小) 。 POOL_SCRATCH资源类型是另一种形式的系统内存资源,可以利用运行时支持的所有格式和功能,但设备无法访问。 暂存资源主要用于内容工具。

图 3. 视频 RAM、AGP 光圈和系统 RAM 中的内存资源

视频 ram、agp 光圈和系统 ram 中的内存资源

一般性建议

正确获取资源管理的技术实现详细信息将大有助于实现应用程序的性能目标。 规划资源如何呈现到 Direct3D 以及围绕及时加载数据的体系结构设计是一项更复杂的任务。 在为应用程序做出以下决策时,建议采用一些最佳做法:

  • 预处理所有资源。 在开发过程中,依赖昂贵的负载时转换和优化资源很方便,但这样做会给用户的计算机带来巨大的性能负担。 预处理的资源加载速度更快,使用速度更快,并提供执行复杂脱机工作的选项。
  • 避免每帧创建多个资源。 所需的驱动程序交互可以序列化 CPU 和 GPU,所涉及的操作很重,因为它们通常需要内核转换。 将创建分散到多个帧上,或者重复使用资源,而无需创建/释放资源。 理想情况下,应在锁定或释放最近用于呈现的资源之前等待多个帧。
  • 在帧结束时,请确保取消绑定所有资源通道 (,即流源、纹理阶段和当前索引) 。 这样做可确保删除对资源的悬而未动的引用,以免资源管理器保留实际不再使用的资源。
  • 对于纹理,请使用压缩格式 (例如,DXTn) mip 贴图,并考虑使用纹理图集。 这大大降低了带宽要求,并且可以减小资源的总体大小,从而提高效率。
  • 对于几何图形,请使用索引几何图形,因为这有助于压缩顶点缓冲区资源,新式视频硬件在重用顶点方面进行了大量优化。 通过使用可编程顶点着色器,可以压缩顶点信息并在顶点处理期间展开它。 同样,这有助于减少带宽要求,并使顶点缓冲区资源更高效。
  • 避免过度优化资源管理。 如果应用程序对特定组合进行过度优化,则将来对驱动程序、硬件和操作系统的修订可能会导致兼容性问题。 由于大多数应用程序都是 CPU 受限的,因此基于 CPU 的管理通常会导致比它解决更多的性能问题。

管理资源

丢失的设备

性能优化

压缩纹理资源

查询

D3DUSAGE

D3DPOOL

D3DCREATE