XR SDK 显示子系统为纹理分配、帧生命周期和节奏阻止提供了一个接口。
一些设备 SDK 要求通过 SDK 本身(而不是通常的图形 API)分配纹理。如果使用 XR SDK 显示子系统,则不再需要依赖外部插件来 blit 或复制到 SDK 纹理中。
显示子系统使插件提供程序可以分配纹理。在可能的情况下,Unity 会直接渲染到纹理以避免不必要的副本。如果需要,Unity 也可以为您分配纹理。
在以下情况下,Unity 无法直接渲染到纹理,而是渲染到中间纹理,然后 blit 或复制到您的纹理:
EXT_multisampled_render_to_texture
扩展。kUnityXRRenderTextureFlagsLockedWidthHeight
标志并且 renderScale 不是 1.0。kUnityXRRenderTextureFlagsWriteOnly
标志并且 Unity 需要从纹理读回。在 PC 和移动设备上,引擎始终解析为提供程序的纹理。引擎执行隐式解析(在进行多重采样渲染到纹理扩展的移动设备上)或显式解析。
在移动设备上,提供程序应启用 kUnityXRRenderTextureFlagsAutoResolve
标志并使用 1 个样本创建其纹理。
使用 UnityXRFrameSetupHints.appSetup.sRGB
可检查 Unity 是否期望渲染到 sRGB 纹理格式。提供程序最终从 UnityXRRenderTextureDesc
的 colorFormat
字段选择输出纹理格式。如果格式是 sRGB 类型,则 Unity 会根据活动项目选择的颜色空间打开或关闭 sRGB 写入。您应该始终在合成器中使用 sRGB 到线性转换从任何 sRGB 纹理进行采样。
如果 SDK 需要深度信息,则可以通过与上述颜色缓冲区相同的方式获取深度缓冲区。UnityXRRenderTextureDesc
上的 nativeDepthTex
值指定原生资源。默认情况下,如果 nativeDepthTex
设置为 kUnityXRRenderTextureIdDontCare
,则 Unity 会尝试在具有相似描述的纹理之间共享深度缓冲区。
如果 SDK 不需要深度信息,则应该将 UnityXRRenderTextureDesc::depthFormat
设置为 kUnityXRDepthTextureFormatNone
以避免不必要的解析。
在提交期间(请参阅下面的提交在途帧部分),您可以每一帧指定不同的纹理 ID,以便处理 SDK 需要 Unity 渲染到的双缓冲或三缓冲图像的情况。提供程序插件负责管理 UnityXRRenderTextureId
的集合。
有两种方法负责帧的生命周期:PopulateNextFrameDesc
(就在渲染开始之前发生)和 SubmitCurrentFrame
(在渲染完成后立即发生)。这两种方法都在图形线程上进行调用。
在 PopulateNextFrameDesc
期间,显示提供程序预期会执行以下操作:
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 在开始提交与姿势相关的图形命令之前会等待帧生命周期完成。
提供程序可以在 PopulateNextFrameDesc
或 SubmitCurrentFrame
中等待节奏。
当 Unity 在图形线程中为某一帧提交图形命令时,下一帧的模拟循环在主线程上运行。它包含物理、脚本逻辑等。在提交所有渲染命令后,并且只有在下一帧的模拟以及在其中安排的所有图形作业完成后,才在图形线程上调用 PopulateNextFrameDesc
。PopulateNextFrameDesc
等待的图形作业之一是当前帧的 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
的用例包括以下内容:
API 支持这些附加情况(但 Unity 现在可能无法正确响应):
做出这些假设是安全的:
注意:Unity 项目和 XR SDK 必须使用相同的设置(启用/禁用)来用于单通道渲染,因为此设置会影响用户着色器。要检查是否启用了单通道渲染,请使用 UnityXRFrameSetupHints.appSetup.singlePassRendering
。
如果 cullingPassIndex
设置为相同的值,则两个渲染通道可以共享一个剔除通道。cullingPassIndex
选择要使用的 UnityXRCullingPass
。剔除通道必须在 UnityXRNextFrameDesc
中填写。