纹理流送

纹理流送系统为多线程,且基于优先级。在使用纹理池时(即在控制台上),对于有纹理占用的显存,只有在另一具备更高优先级的纹理需要使用该显存的情况下,系统才会流出当前占用中的纹理。这样可以使纹理质量更加稳定,并避免不必要的磁盘访问。

纹理的优先级主要基于距离而确定,不过也会考虑其它因素,如时间、所需分辨率,以及是否强制(标记为需要完整流入)。

该系统定期为各个流送纹理计算两项值:所需的 Mip 等级的数量,以及优先级。接下来,它会根据优先级为所有纹理分类,并确保所有纹理在显存中都至少存在所需的 Mip 等级数,该过程从具有最高优先级的纹理开始。如果某纹理需要流入更多的 Mip 等级,而此时没有足够的显存可用,系统会开始流出低优先级纹理的 Mip 等级,并稍后重试。

流送类型

流送系统在确定某纹理需要在显存中存在多少 Mip 等级时使用了多种启发法。通常,一个纹理只使用一种启发法,但在有些情况下也会采用多种,例如将同一纹理同时用于静态网格体与骨骼网格体时。在这种情况下,系统会选择结果最高(即所需 Mip 等级数最高)的启发法。

所需 Mip 等级的数量会声明到某个有效范围内。该范围通过多种因素确定,如纹理组 LOD 设置、过场 Mip 等级、Xbox 上打包的 Mip tail、全局最小和最大值设置(GMinTextureResidentMipCount 和 GMaxTextureMipCount),以及该纹理是否强制完整载入。

某些类型的纹理是不会通过一般的启发法来处理的,因为流送系统不会主动追踪它们。这在特效纹理中尤为常见。这类纹理会通过回退启发法来处理,其结果要么是全部,要么是无,视其在过去 90 秒内是否渲染过而定。由于流送系统不会追踪这些纹理,如果其它几何体也在使用同一纹理,则可能会出现问题。流送系统只会知道其它几何体对该纹理的使用情况,并仅根据相应的启发法来流送该纹理。即使使用该纹理的特效距离镜头很近,如果上述几何体的距离较远,所需 Mip 等级数仍会变得很低。请避免在这类情况下共享纹理。

StreamType Static

这类流送类型用于放置在 Unreal Editor 中的场景中的所有静态物体。基本而言,它以距离为依据,即估算纹理在屏幕上占据的像素数,从而推算出为匹配该分辨率而所需的 Mip 等级。

StreamType Dynamic

动态物体(如骨骼网格体或蔓延的物体)上的纹理使用动态启发法。它与静态流送类型很相似,但流送系统会追踪连接/断开情况,以及位置和边界框的变化,并相应地更新算法。

StreamType Forced

对于标记为 bForceMipStreaming 的纹理,或者被 Blueprint 操作或游戏代码强制流送的纹理,其 WantedMips 会被设置为最大值。它们的优先级会高于一般纹理,并会按其 Mip 等级被立即流送。强制纹理通常具有计时器,当计时结束时,会自动恢复到正常流送行为。该计时器会为使用带来便利,并避免忘记关闭强制标志的情况。可以将计时器设置为较为宽松的、能够确保覆盖完所需时段的数值。不过,最好还是能够在已知不再需要强制流送的时候立即手动关闭强制标记。在已知不再需要强制流送时迅速关闭 Forced 标记十分重要,因为众多的强制纹理会占用大量纹理显存,并会导致其它纹理被流出。您可以使用 STAT STREAMINGDETAILS 或 LISTSTREAMINGTEXTURES 来监控 Forced 纹理的使用情况。

StreamType LastRenderTime

如果某纹理未受流送系统追踪,且未经任何一般启发法处理,则会回退到 LastRenderTime 启发法。这种启发法非常简单,它会在渲染某纹理时按全 Mip 等级流送该纹理,并在其不再受渲染时(如纹理转到镜头后方、被其它纹理阻挡,或不再用于当前场景时)将其于显存中保留 90 秒。

这种流送类型通常只用于粒子效果纹理,使用时应格外小心。可使用 STAT STREAMINGDETAILS 和 LISTSTREAMINGTEXTURES 来监视显存使用情况。

StreamType Orphaned

如果流送系统对某纹理的追踪刚刚发生中断(如场景转换时或某动态物体刚刚消失时),但该纹理仍然存在,则纹理在一段时间内会变为 Orphaned,而不会立即回退到 LastRenderTime 流送类型。在这段时间中,它会保留其当前拥有的 Mip 数(或比最大值低 1,取较低者)。这可以防止纹理在场景转换的情况下被临时性地全 Mip 等级流送(因为若纹理最近被渲染过,LastRenderTime 会按全 Mip 等级流送该纹理)。可以将此过程视为将纹理锁定到当前状态,直至其再次受到追踪或从显存中被删除。

Decals

放置在场景中的静态贴图会使用 Static 流送类型。在运行时内产生的动态贴图会使用 LastRenderTime。如果某贴图纹理被同时用于静态和动态物体上,则需要特别注意,因为 LastRenderTime 在此情况下会占先,以避免粒子效果在纹理共享上存在的问题。

预流送纹理

可通过几种方式在纹理需要渲染之前就开始流送它们。如果在加载场景时配用了全屏载入动画,且动画已停止时,流送系统会自动阻断 CPU,并根据此时的当前设置(镜头/玩家位置、载入的和可见的几何体等)流送所有其认为需要的纹理。它会在日志中添加一行,记录该过程所需的时间,以及在此时间内流送了多少纹理。可以通过一个 .ini 设置来设定此阻断操作的时限 (TextureStreaming -> LoadMapTimeLimit)。可以视需要随时手动调用此过程 (IStreamingManager::Get().StreamAllResources)。

通过在游戏代码中强制将特定的纹理或网格体载入到显存中(调用 PrestreamTextures() 函数),可以提前流送这些纹理或网格体。在指定时间内,即使没有使用这些纹理,也会将其全部 Mip 等级载入。当开始使用它们时,流送系统可以转而以正常方式处理这些纹理。

如果游戏代码已知游戏很快将传送到一个新的地点,也可以向流送系统添加额外的观察地点,即调用 IStreamingManager::Get().AddViewSlaveLocation()。流送系统会以相同的方式对待这些地点与镜头观察地点,并根据所有的观察地点流送纹理。

在游戏过程中,如果场景通过 Blueprints 流入/流出,则应在 Kismet 中通过 Stream-In-Textures Kismet 操作节点谨慎进行预流送。该节点的使用方式与 PrestreamTextures() 和 AddViewSlaveLocation() 函数完全一致。请注意,流送系统只会考虑已加载且可见的场景。

Stream-In-Textures Blueprint 节点也具有“All Loaded”输出,此输出将在所有所需的纹理都已载入,或经过特定的时长后(以先发生者为准)触发。如有必要,可将其用于触发载入画面。请注意,如果超出纹理预算,所有所需的纹理均无法置入显存并无法加载,因此请确保您设置的时限是合理的。(将来可能会变更此行为,即在所有能够加载的纹理均载入后触发,从而避免发生这一特殊情况。)

升位

可以对特定的观察地点或 Actor 进行升位。升位系数为 1.0 时表示正常操作,若高于此值则表示会更加积极地提升相关纹理的所需 Mip 等级数(就好像它们靠近镜头一样),而低于此值表示会更消极地将所需 Mip 等级数保持在较低值。

升位系数会自动重置为 1.0,因此需要不断地对每一帧设置升位系数,直至不再需要该系数。

这可以用于对特定的重要角色进行升位,或针对很快可能放大(或已经放大)的镜头更加积极地预先流送纹理。

TexelFactor

TexelFactor 直接影响着所需 Mip 等级数。此值越大,流入的 Mip 等级就越多。在烘焙阶段,系统会检查网格体中的所有三角形,并对比 UV 映射与三角形大小的比值,从而预先计算出该值。不过也可以通过设置组件上的 StreamingDistanceMultiplier 属性来手动调整该值。同样,设置的值越大,流入的 Mip 等级就越多。将 StreamingDistanceMultiplier 设为 2.0 时,TexelFactor 会翻倍。将 StreamingDistanceMultiplier 设为 0.5 时,TexelFactor 会减半。在运行时中,通过 InvestigateTexture 或 ListStreamingTextures 控制台命令,可在 TexelFactor 中查看效果。

过场 Mip 等级

纹理可以具备仅用于过场动画而非一般游戏画面的超高分辨率 Mip 等级。过场 Mip 等级数可在纹理属性中设置。要启用这些过场 Mip 等级,必须使用 Stream-In-Textures Blueprint 操作来为纹理组启用该等级,同时还必须强制流送该纹理。在 C++ 中,也可以通过调用 UTexture2D::SetForceMipLevelsToBeResident() 来启用。

特殊纹理组

有几种纹理组需要特别处理。TEXTUREGROUP_Skybox 纹理始终会完全流入,它们会自动标记为 Forced。TEXTUREGROUP_UI 纹理的 Mip 等级会在烘焙阶段全部去除,因此一定不会流送这些纹理。TEXTUREGROUP_Lightmap 与 TEXTUREGROUP_Shadowmap 会使用 .ini 中额外设置的升位系数(“LightmapStreamingFactor”和“"ShadowmapStreamingFactor”)。使用三个 TEXTUREGROUP_Character 组中任意一组的纹理会具有稍高的优先级,在加载地图时会首先流入,而在紧急流出时会最后流出。

紧急流出

纹理在初次同步创建时(通常只有最小的 Mip 的等级),它必须大小合适,能够置入显存,否则游戏会发生显存不足的问题。如果显存不足,流送系统会试图流出纹理数据来腾出空间。此时系统可谓六亲不认,它会尽可能地强制流出任何纹理来为纹理池腾出空间。这一操作会花一些时间来完成,因为系统需要扫描众多的纹理,并为纹理池显存去除碎片以创建连续的空余空间。只要出现这种情况,日志中都会添加一行进行记录。

纹理流送的优化与诊断

首先应查看的是 STAT STREAMING 中的“Over Budget”值。如果为 0,则表示一切正常。如果不为 0,则表示使用的纹理超过了纹理池的容量,应降低显存的使用并/或增加纹理池空间。在某些情况下,使用非零的“Over Budget”的值也是可以接受的,因为流送系统以优先级为基准,因此画面质量也不会受影响。

要查找可移除以降低显存消耗的纹理,可使用“ListStreamingTextures”和“ListTextures”控制台命令。请注意,纹理池显存是流送纹理(使用情况随时间变化)和非流送纹理(使用情况更加恒定)共用的。ListStreamingTextures 命令会列出流送纹理以及来自流送系统的信息。ListTextures 会列出所有纹理,但信息会更加简略。

可通过 ListStreamingTextures 检查是否有纹理被不必要地标记为 Forced 或 LastRenderTime,或是否存在大小超过必要范围的纹理。检查是否可以共享更多的纹理,从而去除实际并不需要的单独纹理。大小超出必要范围的纹理可以通过修改 LODBias 或 StreamingDistanceMultiplier 属性来调整。检查是否存在过度使用极度消耗显存的过场 Mip 等级(这类纹理会被标记为 Forced)的情况。

通过 ListTexturesfor 检查是否存在未流送而使用了大量显存的纹理,并视情况进行优化。

要查看特定的纹理,可使用“TrackTexture ”和“InvestigateTexture ”控制台命令。查看异常的流出,或检查是否存在未能检测到变化的情况。检查 WantedMips 是否为预期的值。检查其是否正在使用预期的流送类型。检查系统是否考虑了所有的纹理实例(即同一纹理的所有使用情况)。确认不存在未追踪的纹理(如粒子效果纹理)被常规追踪实例(如静态网格体)共用的情况。

如果在加载新地图时纹理变得模糊,请检查在关闭载入画面(确保使用全屏载入动画)时调用 FStreamingManagerCollection::StreamAllResources() 时,观察地点是否在正确的位置。确保此时所有数据均已完全载入。

一些 .ini 设置

这些 .ini 设置均位于 TextureStreaming 部分中。

属性 描述
PoolSize 纹理池的大小,单位为 MB。
MemoryMargin 要维持的空余显存量(用作流入新数据的临时显存),单位为 MB。
LoadMapTimeLimit 在载入画面最后流入全部纹理时要阻断的最长秒数。
LightmapStreamingFactor 用于 TEXTUREGROUP_Lightmap 纹理的额外升位系数。
ShadowmapStreamingFactor 用于 TEXTUREGROUP_Shadowmap 纹理的额外升位系数。
BoostPlayerTextures 自动应用到玩家角色上的所有纹理的升位系数。

Stat Streaming

属性 描述
Game Thread Update Time 在游戏线程中,每帧所用的时间。
Pool Memory Used 当前从纹理池分配的显存总量(而非仅流送纹理)。
Required Streaming Textures 所有流送纹理所需的显存总量。
Streaming Textures 当前从纹理池分配的显存总量,仅计算流送纹理。
Over Budget 估算的超预算纹理显存的量(仅流送纹理)。
Num Wanting Textures 当前需要按 Mip 等级流入的纹理数量。
Streaming Textures 显存中的流送纹理总量。

Stat StreamingDetails

属性 描述
Under Budget 估算的预算内纹理显存的量(仅流送纹理)。
Rendering Thread Update Time 在渲染器线程中,更新纹理时每帧所用的时间。
Rendering Thread Finalize Time 在渲染器线程中,完成纹理时每帧所用的时间。
Static Textures In Memory 静态纹理当前使用的显存总量。
Dynamic Textures In Memory 动态纹理当前使用的显存总量。
LastRenderTime Textures In Memory LastRenderTime 纹理当前使用的显存总量。
Forced Textures In Memory Forced 纹理当前使用的显存总量。
Lightmaps In Memory 光照贴图和阴影贴图当前使用的显存总量。
Lightmaps On Disk 磁盘上对于当前活动的纹理可用的光照贴图和阴影贴图数据总量。
Intermediate Textures Size 当前用于纹理流入/流出的临时显存的量。
Textures Streamed In (Frame) 该帧中流送的纹理的数量。
Textures Streamed In (Total) 自启动起流入的纹理的数量。
Lightmaps Streamed In (Total) 自启动起流入的光照贴图和阴影贴图的数量。
Intermediate Textures 当前用于流入/流出的临时纹理的数量。
Requests In Cancelation Phase 处于取消阶段的请求的数量。
Requests In Update Phase 处于 Mip 更新阶段的请求的数量。
Requests In Finalize Phase 处于 Mip 完成阶段的请求的数量。
Streaming Latency, Average (sec) 环形缓冲区中所有延迟采样的平均值,单位为秒。
Streaming Bandwidth, Average (MB/s) 平均带宽使用量,单位为 MB/秒。
Growing Reallocations 自启动起的增长原状再分配的总数。
Shrinking Reallocations 自启动起的缩减原状再分配的总数。
Full Reallocations 自启动起的完整再分配(包括纹理复制)的总数。
Failed Reallocations 自启动起的失败重分配(流送操作被静默忽略)的总数。
Panic Defragmentations 自启动起的紧急碎片去除的总数。
Num Textures Instances 当前的纹理实例数量。
Num Lightmap Instances 当前的光照贴图和阴影贴图实例数量。
Dynamic Streaming Total Time (sec) 自启动起花费于动态原语的累计总时间,单位为秒。

控制台命令

属性 描述
STAT Streaming 显示关于纹理流送系统的信息。
STAT StreamingDetails 显示关于纹理流送系统的更多详细信息。
ListStreamingTextures 显示符合 的全部流送纹理的列表。列表中包含的信息有当前大小、自其渲染起已经过了多少秒、使用的是哪种流送启发法等等。格式为 CSV,因此可以复制/粘贴到 Excel 中供详细分析。
InvestigateTexture 显示流送系统对于所有包含指定的 字符串的纹理所拥有的信息。
TrackTexture 流送系统将开始追踪包含指定的 字符串的纹理,并记录任何状态变更。
UntrackTexture 从追踪纹理列表中移除指定的纹理。
ListTrackedTextures 显示当前追踪的纹理的列表。
TextureGroups 显示所有纹理组的显存信息。
DumpTextureStreamingStats 显示纹理流送系统的显存信息。