避免在 Vulkan 中每帧更改纹理布局的有效方法

问题描述 投票:0回答:2

我有一些很少更新的纹理数据,但我无法控制它何时更新(数据是从呈现 GUI 的外部库提供的)。

目前,我在每一帧中更改其布局两次:

  1. VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
    ,具有管道阶段
    VK_PIPELINE_STAGE_TRANSFER_BIT
    VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
  2. 然后从
    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
    ,阶段为
    src=VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
    dst=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT

我认为,每次发生的这种转换可能是浪费的,因为我几乎总是没有任何纹理更新,所以我正在考虑实现这个过程:

  1. 检查更新。
  2. 如果有任何更新,请记录并提交一个单独的命令缓冲区,将我的纹理从只读最优转换为传输 dst 最优。
  3. 如果有任何更新,记录传输命令并在主命令缓冲区中将其转换回只读最优状态。
  4. 否则,只需按原样使用纹理,无需任何转换。

这是一个好方法吗?还有其他方法可以减少此类布局转换吗? 我也考虑只使用

VK_IMAGE_LAYOUT_GENERAL

textures vulkan
2个回答
1
投票

您确定这是一个实际问题吗?驱动程序知道 dst_optimal 之后的下一个布局传输很可能是 read_only。因此,这些转变不太可能真正起到任何作用。

如果在发生更改时完全覆盖纹理,那么工作流程应该是:

if(image_changed){
    pipeline barrier from VK_IMAGE_LAYOUT_UNDEFINED to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL 
    vkCmdCopyBufferToImage(cmd, staging_buffer, texture, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
    //copy from staging buffer to texture
    pipeline barrier from VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
}

VK_IMAGE_LAYOUT_UNDEFINED
因为
src
让驱动程序完全丢弃旧数据,跳过其中一个转换。

这假设

image_changed
是一个足够好的支票。


0
投票

我刚刚明白我忘记了我自己可以跟踪纹理的状态。

例如,在我的问题中,纹理有 3 种可能的状态和相应的布局:

  1. 刚刚创建:
    VK_IMAGE_LAYOUT_UNDEFINED
  2. 它用于图形管道,最近没有任何传输到其中:
    VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
  3. 有一个转移到其中,所以它有布局
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL

因此我们可以将状态存储在枚举中:

enum TextureState{
   just_created,
   used_for_render,
   had_transfer,
}

并在记录每帧命令缓冲区期间访问它(伪代码):

// Copying data to texture.
if (need_to_copy_to_texture) {
   if (tex_state == TextureState::had_transfer) {
      // Do nothing, we already in compatible layout
   }
   else if (tex_state == TextureState::just_created) {
      // Need VkImageMemoryBarrier here
      pipeline barrier VK_IMAGE_LAYOUT_UNDEFINED =>
           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
   }
   else if (tex_state == TextureState::used_for_render) {
      // Need VkImageMemoryBarrier here
      pipeline barrier VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL =>
           VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
   }
   record copy of data to texture;
   tex_state = TextureState::had_transfer;
}

// Now we want to render
if (tex_state == TextureState::used_for_render) {
  // Do nothing, we already in compatible layout for rendering
}
else if (tex_state == TextureState::just_created) {
  // This probably should not happen.
  assert(false);
}
else if (tex_state == TextureState::had_transfer) {
  // Need VkImageMemoryBarrier here
  pipeline barrier VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL =>
       VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
}
tex_state = TextureState::used_for_render;
begin rendering;
bind pipeline with our texture;
bind descriptor set with out texture;
issue draw call;
end rendering;

submit command buffer to a queue;

这样我们可以确保:

  1. 每次操作我们总是有正确的纹理布局。
  2. 我们仅在需要时才在纹理布局之间进行传输,因此不会浪费 GPU 周期来做不需要的工作。
© www.soinside.com 2019 - 2024. All rights reserved.