内容创建/渲染

使用 DirectX 12 在分布式系统上的应用程序之间同步当前调用

交换组和交换屏障是分别在同一系统和分布式系统上的不同窗口之间同步缓冲区交换的众所周知的方法。最初是为 OpenGL 引入的,后来通过公共 NvAPI 接口进行扩展,并在 DirectX 9 到 12 中得到支持。

NVIDIA 现在引入了当前障碍的概念。它们结合了交换组和交换障碍,并提供了一种在系统内和系统之间设置同步当前调用的简单方法。

当应用程序请求加入当前障碍时,驱动程序会根据当前系统配置尝试设置交换组或交换组与交换障碍的组合。这些函数再次通过公共 NvAPI 接口提供。

目前的障碍只有在应用程序处于全屏状态,没有窗口边框,也没有桌面缩放或任务栏组合时才有效。如果这些要求中至少有一项未得到满足,则当前的安全栅将断开并恢复到挂起状态,直到所有要求都得到满足。当当前屏障处于挂起状态时,显示器之间不会发生同步。

类似地,只有当显示器连接到相同的 GPU 并设置为相同的定时时,当前屏障才能正常工作。显示器也可以与 Quadro Sync 卡或 NVLink 连接器同步。

显示同步通过以下两种方式之一进行:

  • 显示器已配置为使用 Quadro sync 插件板形成同步组或同步到外部同步源,或两者兼而有之。
  • 通过创建跨越显示器的马赛克显示表面,实现了显示器的同步。

当通过其中一种方法同步显示定时时,则可以使用 DX12 显示屏障。

NvAPI 接口

要通过 NvAPI 中的 present barrier 扩展设置同步 present 调用,应用程序必须确保完全支持 present barrier 。如果是这种情况,它必须创建一个当前 barrier 客户端,注册所需的 DirectX 资源,并加入当前 barrier 。

查询当前屏障支持

在尝试同步当前调用之前,应用程序应首先检查当前操作系统、驱动程序和硬件配置是否支持当前屏障同步。这是通过使用所需的 D3D12 设备作为参数调用相应的函数来实现的。

ID3D12Device* device;
... // initialize the device
bool supported;
assert(NvAPI_D3D12_QueryPresentBarrierSupport(device, &supported) == NVAPI_OK);
if(supported) { LOG("D3D12 present barrier is supported on this system."); ...
}

创建当前屏障客户端句柄

如果系统提供当前屏障支持,应用程序可以通过提供 D3D12 设备和 DXGI 交换链来创建当前屏障客户端。句柄用于注册所需的资源、加入或离开当前障碍以及查询帧统计信息。

IDXGISwapChain swapChain;
... // initialize the swap chain
NvPresentBarrierClientHandle pbClientHandle = nullptr;
assert(NvAPI_D3D12_CreatePresentBarrierClient(device, swapChain, &pbClientHandle) == NVAPI_OK);

注册现有障碍资源

创建客户端后,当前障碍需要访问交换链的缓冲区资源和围栏对象,以实现适当的帧同步。围栏值由每帧的当前屏障增加,应用程序不得更改。然而,应用程序可以使用它来同步主机和设备之间的命令分配器使用。每当交换链的缓冲区发生变化时,必须再次调用该函数。

ID3D12Fence pbFence; // the app may wait on the fence but must not signal it
assert(SUCCEEDED(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pbFence))));
ID3D12Resource** backBuffers;
unsigned int backBufferCount;
... // query buffers from swap chain
assert(NvAPI_D3D12_RegisterPresentBarrierResources(pbClientHandle, pbFence, backBuffers, backBufferCount) == NVAPI_OK);

加入当前的障碍

创建当前屏障客户端句柄并注册扫描资源后,应用程序可以加入当前屏障同步。然后,未来 – 现在的呼叫与其他客户端同步。

NV_JOIN_PRESENT_BARRIER_PARAMS params = {};
params.dwVersion = NV_JOIN_PRESENT_BARRIER_PARAMS_VER1;
assert(NvAPI_JoinPresentBarrier(pbClientHandle, &params) == NVAPI_OK);

离开目前的障碍

存在一个类似的函数来保持当前的势垒同步。客户端保持不变,这样应用程序就可以轻松地再次加入。

assert(NvAPI_LeavePresentBarrier(pbClientHandle));

应用程序的主循环

当一切都设置好后,应用程序可以执行其主循环,而无需任何更改,包括当前调用。目前的障碍自行处理同步。虽然应用程序可以选择使用提供给当前屏障的围栏进行主机和设备同步,但也可以使用自己的专用围栏。

查询统计信息

当客户端注册到当前屏障时,应用程序可以随时查询帧和同步统计信息,以确保一切按预期工作。

NV_PRESENT_BARRIER_FRAME_STATISTICS stats = {};
stats.dwVersion = NV_PRESENT_BARRIER_FRAME_STATICS_VER1;
assert(NvAPI_QueryPresentBarrierFrameStatistics(pbClientHandle, &stats) == NVAPI_OK);

由函数调用填充的当前障碍统计对象提供了几个有用的值。

  • SyncMode:上次当前调用中客户端的当前障碍模式。可能值:
    • PRESENT_BARRIER_NOT_JOINED:客户尚未加入当前的障碍。
    • PRESENT_BARRIER_SYNC_CLIENT:客户端加入了当前障碍,但未与任何其他客户端同步。
    • PRESENT_BARRIER_SYNC_SYSTEM:客户端加入了当前屏障,并与系统内的其他客户端同步。
    • PRESENT_BARRIER_SYNC_CLUSTER:客户端加入了当前的障碍,并与系统内和跨系统的其他客户端同步。
  • PresentCount:帧成功加入当前障碍后从客户端显示的总次数。
  • PresentInSyncCount:自返回SyncMode以来,从客户端显示帧的总次数是PRESENT_BARRIER_SYNC_SYSTEMPRESENT_BARRIER_SYNC_CLUSTER。如果SyncMode偏离这些值,它将重置为 0 。
  • FlipInSyncCount:自返回SyncMode以来,客户端的翻转总数为PRESENT_BARRIER_SYNC_SYSTEMPRESENT_BARRIER_SYNC_CLUSTER。如果SyncMode偏离这些值,它将重置为 0 。
  • RefreshCount:自客户端返回SyncMode以来的 v 空格总数为PRESENT_BARRIER_SYNC_SYSTEMPRESENT_BARRIER_SYNC_CLUSTER。如果SyncMode偏离这些值,它将重置为 0 。

示例应用程序

NVIDIA DesignWorks Samples GitHub repo 中提供了一个专用的示例应用程序。它具有可调整和移动的彩色条和列模式,以直观地检查同步质量(图 1 )。该应用程序还支持多 GPU 设置上的交替帧渲染和立体渲染。在运行期间,它可以加入或离开当前的障碍同步。

有关源代码和用法的详细信息,请参阅位于的项目 nvpro-samples/dx12_present_barrier .

Screenshot of a black screen with a vertical red bar, horizontal green bar, and statistics in the upper left corner.
图 1.具有移动条线和实时统计信息的示例应用程序。

结论

Present barrier 同步是一种简单、高级的方法,可以在单个系统和多个分布式系统场景中在多个显示器上实现同步 Present 调用。该界面完全包含在 NvAPI 库中,仅包含六个设置函数,而复杂的管理概念对面向用户的代码隐藏。


Tags