Version: 2023.2
XR SDK 输入子系统
XR SDK 网格子系统

XR SDK 显示子系统

XR SDK 显示子系统为纹理分配、帧生命周期和节奏阻止提供了一个接口。

纹理分配

一些设备 SDK 要求通过 SDK 本身(而不是通常的图形 API)分配纹理。如果使用 XR SDK 显示子系统,则不再需要依赖外部插件来 blit 或复制到 SDK 纹理中。

显示子系统使插件提供程序可以分配纹理。在可能的情况下,Unity 会直接渲染到纹理以避免不必要的副本。如果需要,Unity 也可以为您分配纹理。

在以下情况下,Unity 无法直接渲染到纹理,而是渲染到中间纹理,然后 blit 或复制到您的纹理:

  • 如果在使用图像效果,则您的纹理将处于链中的最后位置。
  • 在带有 MSAA 的 PC 上,Unity 会渲染到多重采样纹理,然后在摄像机堆栈的末尾处解析为您的纹理。
  • 在移动设备上,借助多重采样自动解析扩展,当纹理进行刷新或用作任何操作(除绘制到它之外)的源或目标时,纹理会进行隐式解析。请参阅 EXT_multisampled_render_to_texture 扩展。
  • 延迟渲染、HDR 和命令缓冲区会导致 Unity 渲染到中间纹理。
  • 如果渲染到屏幕的子集,则 Unity 会渲染到中间纹理。
  • 如果“纹理布局”与 Unity 渲染到的内容不匹配(例如,Unity 将每只眼睛渲染到单独的纹理,但 RenderPass 配置为渲染到纹理数组或单一纹理)。
  • 如果设置了 kUnityXRRenderTextureFlagsLockedWidthHeight 标志并且 renderScale 不是 1.0。
  • 如果设置了 kUnityXRRenderTextureFlagsWriteOnly 标志并且 Unity 需要从纹理读回。

MSAA

在 PC 和移动设备上,引擎始终解析为提供程序的纹理。引擎执行隐式解析(在进行多重采样渲染到纹理扩展的移动设备上)或显式解析。

在移动设备上,提供程序应启用 kUnityXRRenderTextureFlagsAutoResolve 标志并使用 1 个样本创建其纹理。

sRGB

使用 UnityXRFrameSetupHints.appSetup.sRGB 可检查 Unity 是否期望渲染到 sRGB 纹理格式。提供程序最终从 UnityXRRenderTextureDesccolorFormat 字段选择输出纹理格式。如果格式是 sRGB 类型,则 Unity 会根据活动项目选择的颜色空间打开或关闭 sRGB 写入。您应该始终在合成器中使用 sRGB 到线性转换从任何 sRGB 纹理进行采样。

深度纹理

如果 SDK 需要深度信息,则可以通过与上述颜色缓冲区相同的方式获取深度缓冲区。UnityXRRenderTextureDesc 上的 nativeDepthTex 值指定原生资源。默认情况下,如果 nativeDepthTex 设置为 kUnityXRRenderTextureIdDontCare,则 Unity 会尝试在具有相似描述的纹理之间共享深度缓冲区。

如果 SDK 不需要深度信息,则应该将 UnityXRRenderTextureDesc::depthFormat 设置为 kUnityXRDepthTextureFormatNone 以避免不必要的解析。

处理双缓冲和三缓冲

在提交期间(请参阅下面的提交在途帧部分),您可以每一帧指定不同的纹理 ID,以便处理 SDK 需要 Unity 渲染到的双缓冲或三缓冲图像的情况。提供程序插件负责管理 UnityXRRenderTextureId 的集合。

帧生命周期

有两种方法负责帧的生命周期:PopulateNextFrameDesc(就在渲染开始之前发生)和 SubmitCurrentFrame(在渲染完成后立即发生)。这两种方法都在图形线程上进行调用。

PopulateNextFrameDesc 期间,显示提供程序预期会执行以下操作:

  • 等待节奏(等到 Unity 应该再次开始提交渲染命令)。或者,这可以在 SubmitCurrentFrame 中完成。
  • 获取下一个图像并通过 nextFrame 参数向 Unity 告知下一个要渲染的纹理 ID。

SubmitCurrentFrame 方法期间,显示提供程序预期会执行以下操作:

  • 提交在途的最后一帧(例如,眼睛纹理或深度纹理)以显示在屏幕上。
  • 等待节奏,作为在 PopulateNextFrameDesc 期间等待的替代方法。

阻止节奏

要在渲染到 HMD 显示器时保持尽可能低的延迟和最大吞吐量,需要在获取姿势和提交纹理时确保精确定时。每个 HMD 都具有其合成器运行时所采用的原生刷新率。由于不匹配的定时或冗余工作,渲染速度超过该刷新率会导致体验不是最佳。

Unity expects the display provider to block, or wait for frame cadence, during the frame lifecycle. Unity starts submitting rendering commands shortly after ‘waking up’ from the blocking call. You should synchronize the wake-up time to your compositor within a particular window. Some SDKs provide a floating wake-up time window based on heuristics. Meta/Oculus calls this the “queue ahead” (see Oculus developer documentation for more information). Valve calls it “running start” (see slides 18 and 19 of this presentation).

Unity 在开始提交与姿势相关的图形命令之前会等待帧生命周期完成。

在何处等待节奏

提供程序可以在 PopulateNextFrameDescSubmitCurrentFrame 中等待节奏。

当 Unity 在图形线程中为某一帧提交图形命令时,下一帧的模拟循环在主线程上运行。它包含物理、脚本逻辑等。在提交所有渲染命令后,并且只有在下一帧的模拟以及在其中安排的所有图形作业完成后,才在图形线程上调用 PopulateNextFrameDescPopulateNextFrameDesc 等待的图形作业之一是当前帧的 SubmitCurrentFrame。这便是为何在 SubmitCurrentFrame 中等待节奏是有效的。此外,在 PopulateNextFrameDesc 完成之前,Unity 不会开始渲染。

考虑到这些细节,在 SubmitCurrentFrame 而不是 PopulateNextFrameDesc 中等待节奏需进行一些取舍。例如,如果应用程序在模拟期间安排成本昂贵的图形作业,则在 SubmitCurrentFrame 中等待节奏可能会导致性能问题。因为 SubmitCurrentFrame 安排为在渲染之后运行,应用程序安排的图形作业将在 SubmitCurrentFrame 之后运行,但在 PopulateNextFrameDesc 之前运行。在这种情况下,提供程序在 SubmitCurrentFrame 中进行等待,然后它会唤醒,期望 Unity 开始渲染。但是,Unity 会在调用 PopulateNextFrameDesc 之前处理应用程序安排的图形作业,这进而使 Unity 可以开始渲染。唤醒渲染和处理更新方法中安排的图形作业之间的这种延时可能会导致延迟。开发者可以安排其图形作业在渲染后运行,以确保在 SubmitCurrentFrame 之前安排图形作业,从而优化此方面。

虽然提供程序在 SubmitCurrentFrame 中等待节奏使计算图形作业可以与主线程并行运行,但在 PopulateNextFrameDesc 中等待节奏会完全阻止 Unity 主线程。这是可以接受的,因为模拟和其他图形作业已完成。当模拟或图形线程占用太多时间并超过设备的目标帧速率时,可能会发生问题。这可能会导致帧速率减半,同时 PopulateNextFrameDesc 等待节奏中的下一个周期。

提交在途帧

Unity 调用 SubmitCurrentFrame 时,您在最后一帧设置的纹理已进行渲染,或者 Unity 已向图形驱动程序提交了渲染命令来渲染它们。Unity 现在已完成了它们,您可以将它们传递给合成器。

填写下一帧描述符

在阻止或获取要渲染的下一帧之后,您必须向 Unity 告知在下一帧中要渲染到的纹理,以及渲染通道的布局(请参阅下面的渲染通道)。

渲染通道

UnityXRRenderPass 可能涉及一个剔除通道和一次场景图遍历。这是资源密集型操作,您应该尝试通过单通道渲染等技巧来限制 Unity 执行它的次数。

每个 UnityXRRenderPass 都包含(一个输出纹理(可以是纹理数组),并输出UnityXRRenderParams(例如视图、投影矩阵和要渲染到的矩形或纹理数组切片)。

对于每一帧,显示提供程序都会设置一个 UnityXRRenderTextureId 并填写 Unity 将在下一帧渲染到 UnityXRRenderTextureId

UnityXRRenderPass 的用例包括以下内容:

  • 两通道立体渲染 (2 RenderPass x 1 RenderParams)
  • Single-pass stereo rendering (1 RenderPass x 2 RenderParams)

API 支持这些附加情况(但 Unity 现在可能无法正确响应):

  • 四通道宽 FOV 立体渲染 (4 RenderPass x 1 RenderParams)
  • Single-pass + wide FOV stereo rendering (1 RenderPass x 2 RenderParams + 2 RenderPass x 1 RenderParams)
  • 注视点渲染:
    • 每只眼睛有两个单独纹理(内部和外部)
    • 一种纹理,适用于 UV
  • 外部视图方案(HoloLens BEV,Mayo 3rd eye)(额外 RenderPass)
  • 用于在场景中合成物体或人物的远/近渲染(2 RenderPass,不同的投影,不同的目标)

做出这些假设是安全的:

  • 每个通道一个纹理(单传递是一个纹理数组)
  • 如果为单通道,则每个通道两个姿势/投影(或是注视点渲染)

注意:Unity 项目和 XR SDK 必须使用相同的设置(启用/禁用)来用于单通道渲染,因为此设置会影响用户着色器。要检查是否启用了单通道渲染,请使用 UnityXRFrameSetupHints.appSetup.singlePassRendering

剔除通道

如果 cullingPassIndex 设置为相同的值,则两个渲染通道可以共享一个剔除通道。cullingPassIndex 选择要使用的 UnityXRCullingPass。剔除通道必须在 UnityXRNextFrameDesc 中填写。

XR SDK 输入子系统
XR SDK 网格子系统