如何在 DirectX 11 中频繁更新顶点缓冲区数据?

How to update vertex buffer data frequently in DirectX 11?

我正在尝试使用 dx 中的映射函数更新我的顶点缓冲区数据。虽然它确实更新了一次数据,但如果我对其进行迭代,模型就会消失。我实际上是在尝试通过用户输入实时操作顶点,为此我必须在选择顶点时每帧更新顶点缓冲区。

发生这种情况的原因可能是在调用 Unmap 函数之前,Map 函数禁止 GPU 访问顶点。因此,如果访问在每一帧都被阻止,则无法渲染网格是有道理的。然而,当我每帧更新顶点然后在一段时间后停止时,戏剧性地网格应该再次出现,但它没有。

我知道每帧更新数据的正确方法是使用常量缓冲区,但使用常量缓冲区操作顶点可能不是一个好主意。而且我认为没有其他方法可以更新顶点数据。我希望动态顶点缓冲区能够处理每一帧的更新。

    D3D11_MAPPED_SUBRESOURCE mappedResource;
    ZeroMemory(&mappedResource, sizeof(D3D11_MAPPED_SUBRESOURCE));

    //  Disable GPU access to the vertex buffer data.
    pRenderer->GetDeviceContext()->Map(pVBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

    //  Update the vertex buffer here.
    memcpy((Vertex*)mappedResource.pData + index, pData, sizeof(Vertex));

    //  Reenable GPU access to the vertex buffer data.
    pRenderer->GetDeviceContext()->Unmap(pVBuffer, 0);

带有D3D11_MAP_WRITE_DISCARD标志的映射缓冲区将导致整个缓冲区内容变为无效。您不能使用它来更新单个顶点。而是将缓冲区保留在 CPU 端,然后每帧更新一次 GPU 端的整个缓冲区。

因为这已经回答了您正在使用 Discard 的关键问题(这意味着您将无法从 GPU 检索内容),我想我会在选项方面添加一些内容。

我的问题是,您是需要性能还是需要将数据集中在一个位置的便利性?

您可以尝试一些配置。

  1. 将您的缓冲区设置为具有 CPU 读取和写入访问权限。这虽然意味着您将在总线上上下推动缓冲区。最后,它还会导致 GPU 出现性能问题,例如阻塞等(等待数据移回 GPU)。我个人不在我的编辑器中使用它。
  2. 如果内存不是问题,请在 CPU 端设置缓冲区的副本,每个帧映射与丢弃和块复制数据。这是高性能的,但也是内存密集型的。您显然必须管理数据分区和索引到此 space。这个我没用过,自己玩了一下,太费劲了!
  3. 硬着头皮,按照2映射到缓冲区,将每个顶点对象写入映射缓冲区。我这样做了,除非缓冲区非​​常大,否则我在自己的编辑器中没有遇到过问题。
  4. 使用计算机着色器更新缓冲区,创建资源视图和访问视图并通过常量缓冲区传递更新。有点大锤来敲开核桃。并且仍然不能阻止您可能需要按照第 1 项将数据从 GPU ala 拉回的事实。

管理缓冲区有一些变化,例如您也可以使用交错(2 个副本,一个在 GPU 上,而另一个正在写入),您也可以尝试。有一些相当华丽的机制,例如在另一个线程中构建缓冲区的内容,然后标记更新。

归根结底,DX 11 不提供直接编辑 GPU 内存中数据的能力(有人可能更清楚),CPU 和 GPU 之间有很多转换。

祝您在选择任何技术时都好运。

如果您为 UWP 开发 - 使用 map/unmap 可能会导致同步问题。 ID3D11DeviceContext 方法不是线程安全的:https://docs.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-render-multi-thread-intro

如果您从一个线程更新缓冲区并从另一个线程渲染 - 您可能会遇到不同的错误。在这种情况下,您必须使用一些同步机制,例如临界区。示例在这里 https://developernote.com/2015/11/synchronization-mechanism-in-directx-11-and-xaml-winrt-application/