如何更新 vulkan 中每一帧的纹理?

How to update texture for every frame in vulkan?

正如我的问题标题所说,我想为每一帧更新纹理。

我有一个想法: 使用以下配置创建 VkImage 作为纹理缓冲区:
initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED
usage= VK_IMAGE_USAGE_SAMPLED_BIT

它的内存类型是VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT

在绘图循环中:

第一帧:

  1. 将纹理数据映射到 VkImage(使用 vkMapMemory)。
  2. VkImage 布局从 VK_IMAGE_LAYOUT_PREINITIALIZED 更改为 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
  3. 将此 VkImage 用作纹理缓冲区。

第二帧:

布局将在第一帧之后 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,我可以将下一个纹理数据直接映射到这个 VkImage 而不改变它的布局吗?如果我做不到,我可以将此 VkImage 更改为哪种布局?

在 vkspec 11.4 中它说:

The new layout used in a transition must not be VK_IMAGE_LAYOUT_UNDEFINED or VK_IMAGE_LAYOUT_PREINITIALIZED

所以,我无法将布局更改回 _PREINITIALIZED
任何帮助将不胜感激。

你这里有很多问题。

第一个:

create a VkImage as a texture buffer

没有这样的事情。 OpenGL 缓冲区纹理的等效项是 Vulkan 缓冲区视图。这不使用任何类型的 VkImageVkBufferViews 没有图像布局。

其次,假设您正在使用某种 VkImage,您已经认识到布局问题。除非纹理位于 GENERAL 布局中(除其他外),否则您无法修改纹理背后的内存。所以你必须强制转换到那个,等到转换命令真正完成执行,然后再做你的修改。

第三,Vulkan 的执行是异步的,与 OpenGL 不同,它不会向您隐藏这一点。当您想要更改它时,着色器可能仍会访问有问题的图像。所以通常,你需要对这些东西进行双重缓冲。

在第 1 帧上,您设置图像 1 的数据,然后使用它进行渲染。在第 2 帧上,您设置图像 2 的数据,然后使用它进行渲染。在第 3 帧上,您覆盖图像 1 的数据(使用事件确保 GPU 实际上已完成第 1 帧)。

或者,您可以使用暂存缓冲区来使用双缓冲而无需等待 CPU。也就是说,您不是直接写入图像,而是写入主机可见内存。然后使用 vkCmdCopyBufferToImage 命令将该数据复制到图像中。这样,CPU 就不必等待事件或围栏来确保图像在发送数据之前位于 GENERAL 布局中。

顺便说一句,Vulkan 不是 OpenGL。内存映射总是持久的;如果您要在每一帧都映射一块内存,则没有理由取消映射它。

对于您的情况,您不需要 LAYOUT_PREINITIALIZED。那只会使您的代码复杂化(迫使您为第一帧提供单独的代码)。

LAYOUT_PREINITIALIZED 确实是一个非常特殊的布局,仅用于图像生命周期的开始。它对静态纹理更有用。

LAYOUT_UNDEFINED开始,当你需要从CPU端写图像时使用LAYOUT_GENERAL

我提出这个方案:

渲染循环之前

  1. UNDEFINED
  2. 创建您的 VkImage

第 1 到第 N 帧(又名渲染循环)

  1. 将图像转换为 GENERAL
  2. 同步(可能与 VkFence
  3. 映射图像,写入,取消映射(好吧,映射和取消映射可能在渲染循环之外)
  4. 同步(可能隐式完成)
  5. 将图像过渡到您接下来需要的任何布局
  6. 进行渲染等等
  7. 从 1 开始。

这是一个简单的实现,但应该足以满足普通爱好者的使用。

可以实现双缓冲访问——例如VkBuffer 用于 CPU 访问,VkImage 用于 GPU 访问。并且VkCmdCopy*必须完成数据交接。

它并不比上述方法复杂多少,并且可以带来一些性能优势(如果您在项目阶段需要这些优势)。您通常希望您的资源位于设备本地内存中,而这通常也不是主机可见的。

它会是这样的:

渲染循环之前

  1. 创建你的 VkBuffer b UNDEFINEDHOST_VISIBLE 内存支持并映射它
  2. 创建你的 VkImage i UNDEFINEDDEVICE_LOCAL 记忆
  3. 准备 ib 之间的同步原语:例如如果传输在同一个队列中,则可以使用两个信号量或事件或障碍

第 1 到第 N 帧(又名渲染循环)

bi 上的操作可以完全分离(甚至可以在不同的队列上)所以:

对于b

  1. 过渡 bGENERAL
  2. 同步到 CPU(可能等待 VkFencevkQueueIdle
  3. 无效(如果不相干),写入,刷新(如果不相干)
  4. 同步到 GPU(如果 3. 在队列提交之前隐式完成)
  5. 过渡 bTRANSFER
  6. 同步以确保 i 未在使用中(可能正在等待 VkSemaphore
  7. 过渡 iTRANSFER
  8. vkCmdCopy*bi
  9. 同步以告知我已完成 i(可能发出 VkSemaphore 信号)
  10. 从 1 开始。

(第 2 处的栅栏和第 6 处的信号灯必须预先发出信号或跳过第一帧才能工作)

对于i

  1. 同步以确保 i 可以免费使用(可能正在等待 VkSemaphore
  2. 过渡i到任何需要的
  3. 进行渲染
  4. 同步以告知我已完成 i(可能发出 VkSemaphore 信号)
  5. 从 1 开始。