DirectX12 上传同步 D3D12_HEAP_TYPE_UPLOAD

如何解决DirectX12 上传同步 D3D12_HEAP_TYPE_UPLOAD

我想确保我的 D3D12_HEAP_TYPE_UPLOAD 资源在使用前已上传

显然要做到这一点,您先调用 ID3D12Resource::UnmapID3D12CommandList::CloseID3D12CommandQueue::ExecuteCommandList,然后调用 ID3D12CommandQueue::Signal

然而,这让我感到困惑。调用 ID3D12Resource::Unmap 完全没有连接到命令列表和队列,除了创建资源的设备。但是我每个设备有多个命令队列。那么它是如何选择将资源上传到哪个命令队列的呢?

这在任何地方都有记录吗?我能找到的唯一帮助是示例中的注释。

解决方法

根据 Microsoft DocsMapUnmap 所做的只是处理 CPU 上的虚拟内存地址映射。与 Direct3D 11 不同,您可以安全地将资源映射(即保持映射到虚拟内存)很长时间,而 Direct3D 11 则必须Unmap

几乎所有示例都在 D3DX12.H 实用程序标头中使用 UpdateSubresources 助手。这有一些重载,但它们都做相同的基本事情:

  • 创建/映射“中间”资源(即上传堆上的内容)。
  • 从 CPU 获取数据并将其复制到“中间”资源中(完成后取消映射,因为无需保留虚拟内存地址分配)。
  • 然后在命令列表(可以是图形队列命令列表、复制队列命令列表或计算队列命令列表)上调用 CopyBufferRegionCopyTextureRegion

您可以根据需要将任意数量的这些发布到命令列表中,但“中间”资源在完成之前必须保持有效。

与 Direct3D 12 中的大多数内容一样,您可以使用围栏来执行此操作。当该围栏完成时,您知道可以释放“中间”资源。此外,在您关闭并提交命令列表以供执行之前,没有 副本实际上会启动。

需要将最终资源从复制状态转换为可用于渲染的状态。通常,您将这些发布到同一个命令列表中,但如果您使用复制队列或计算队列命令列表,则存在一些限制。

有关此的完整实现,请参阅 DirectX Tool Kit for DX12

请注意,可以直接从上传堆渲染纹理或使用顶点/索引缓冲区。它不如将其复制到默认堆中那样有效,但类似于 Direct3D 11 USAGE_DYNAMIC。在这种情况下,保持上传堆“映射”并重新使用相同的地址一旦你知道它不再被使用是有意义的。否则,可能会发生腐败或其他坏事。

,

一旦您将数据复制到映射指针,它就可以立即被命令使用,在上传资源的情况下,在这种情况下无需取消映射资源(您可以在发布或应用程序关闭时取消映射) .

但是,重要的是要注意(特别是阅读您的评论),该命令稍后将在 gpu 上执行,因此如果您打算重用该内存,则需要有一些同步机制。

让我们做一个简单的伪代码示例: 您有一个名为 buffer1 的缓冲区(您已经创建并映射了该缓冲区),现在您可以通过 mappingPtr1 访问其内存。

copy data1 to mappedPtr1
call compute shader in commandList
execute CommandList

现在一切都会正确执行(假设您有同步,一帧)

现在,如果您执行以下操作:

copy data1 to mappedPtr1
call compute shader in commandList (1)
copy data2 to mappedPtr1
call compute shader in commandList (1)
execute CommandList

在这种情况下,由于您在与 data1 相同的位置复制了 data2, 第一次计算着色器调用将使用 data2(当您调用 execute CommandList 时,它是最新的可用数据)

现在让我们看一个稍微不同的例子:

copy data1 to mappedPtr1
call compute shader in commandList1
execute CommandList1
copy data2 to mappedPtr1
call compute shader in commandList2
execute CommandList2

现在会发生什么是不确定的,因为您不知道 CommandList1 和 CommandList2 何时会被有效处理。

如果 CommandList1 在之前被处理(足够快):

copy data2 to mappedPtr1

那么 data1 将成为当前内存并被使用

但是,如果您的 commandList 有点重并且 CommandList1 在您完成调用时尚未处理

copy data2 to mappedPtr1

这很可能发生,那么当 gpu 使用时,两个计算都将再次使用 data2。

这是因为 executeCommandList 是一个非阻塞函数,当它返回时仅表示您的命令已准备好执行,而不是命令已被处理。

为了保证您在正确的时间使用正确的数据,在这种情况下您有多种选择:

1/使用围栏等待完成

copy data1 to mappedPtr1
call compute shader in commandList1
execute CommandList1 on commandQueue
attachSignal (1) to commandQueue 
add a waitevent for value (1)  
copy data2 to mappedPtr1
call compute shader in commandList2
execute CommandList2 on commandQueue
attachSignal (2) to commandQueue 
add a waitevent for value (2)

这很简单,但效率极低,因为现在您要等待 gpu 完成 commandList 的所有执行,然后才能继续任何 cpu 工作。

2/使用不同的资源:

既然您现在复制到 2 个不同的位置,您当然可以保证您的数据在两次调用中都不同。

3/使用具有偏移量的单个资源。

您还可以创建一个更大的资源来保存所有调用的数据,然后复制一次。

这里假设您的数据是 64 字节(因此您将创建一个 128 字节的缓冲区)

copy data1 to mappedPtr1 (offset 0)
bind address from mappedPtr1 (offset 0) to compute
call compute shader in commandList1
execute CommandList1 on commandQueue 
copy data2 to mappedPtr1 (offset 64)
bind address from mappedPtr1 (offset 64) to compute
call compute shader in commandList2
execute CommandList2 on commandQueue

请注意,您仍然应该有围栏来指示帧何时完成处理,这是确保您最终可以重用上传部分的唯一方法。

如果要将数据复制到默认堆(特别是在单独的复制队列上进行),则还需要复制队列上的 Fence 和主队列中的等待以确保复制队列具有完成处理并且数据可用(根据另一个答案,在这种情况下,您还需要在默认堆资源中设置资源障碍)

希望它是有道理的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?