Vulkan:将 3 通道图像上传到设备

Vulkan: upload 3 channel image to device

假设主机端有一个3通道图像(float或uint8),需要将其传输到设备图像。 vkCmdCopyBufferToImage 用于它。对于设备图像的格式,我看到两个选项:

  1. 使用 R32G32B32A32_SFLOAT/R8G8B8A8_SNORM 并将有效 PCI 带宽减少 25% + 处理主机上的额外通道(额外 CPU and/or RAM)。
  2. 使用未广泛支持的 R32G32B32_SFLOAT/R8G8B8_SNORM。

有没有办法在设备上坚持使用 4 通道,但从 3 通道主机缓冲区填充它?

是:阅读 pixel-by-pixel 整个图像,获取每个 3 通道像素并编写它的 4 通道版本。然后将格式正确的 4 通道数据上传到 Vulkan。

Vulkan 不是 OpenGL;它提供的唯一功能(大部分)是 硬件 功能:GPU 为您做的事情。将 3 通道数据转换为正确的格式不是硬件所做的事情。所以 Vulkan 不会这样做;那是你的工作。

NicolBolas 是正确的,它需要硬件自动将非 4 通道图像传输到 4 通道图像内存,或者支持 3 通道图像纹理。但是与其做一些缓慢的事情,比如从主机逐像素复制你的图像,为什么不充分利用吞吐量增强并使用计算着色器将你的纹理作为着色器缓冲区,并以正确的格式读出数据?

我的意思是:

  • 你有一张 3 通道图像,但此时它只是数据。

  • 您暂时将其作为缓冲区上传到设备。

  • 在计算着色器中,从图像中读取通道,并将结果直接写入图像在设备上。

使用图像的示例

如前所述,您还可以直接写入图像缓冲区,这意味着您不必调用单独的缓冲区副本来复制图像。

#version 450

// should be tightly packed if I read the spec correctly
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf
layout(std430, binding = 0) buffer in_3_channel 
{
    vec3 input_channel[ ];
};

layout (binding = 1, rgba32f) uniform image2D result_image;

layout (local_size_x = 256) in;

layout (binding = 2) uniform UBO 
{
    int image_size;
    int image_cols;
} ubo;


void main() 
{
    uint index = gl_GlobalInvocationID.x;
    if (index >= ubo.image_size){
        return; 
    }

    vec3 in_color = input_channel[index];
    vec4 out_color = vec4(in_color, 1.0);
    // using an image instead, we would write using this:
    int row = gl_GlobalInvocationID.x / image_cols;
    int col = gl_GlobalInvocationID.x % image_cols;
    ivec2 image_write_location = ivec2(row, col);
    imageStore(result_image, image_write_location, out_color);
}

使用缓冲区的示例:

#version 450

// should be tightly packed if I read the spec correctly
// https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf
layout(std430, binding = 0) buffer in_3_channel 
{
    vec3 input_channel[ ];
};

layout(std430, binding = 1) buffer out_4_channel{
    vec4 output_channel[ ];
};
//alternatively you could use a image buffer directly ex:
//
//layout (binding = 1, rgba32f) uniform image2D result_image;

layout (local_size_x = 256) in;

layout (binding = 2) uniform UBO 
{
    int image_size;
} ubo;


void main() 
{
    uint index = gl_GlobalInvocationID.x;
    if (index >= ubo.image_size){
        return; 
    }

    vec3 in_color = input_channel[index];
    vec4 out_color = vec4(in_color, 1.0);
    output_channel[index] = out_color;
    // using an image instead, we would write using this:
    // ivec2 image_write_location = ivec2(gl_GlobalInvocationID.x / image_cols, gl_GlobalInvocationID.x % image_cols);
    //imageStore(result_image, image_write_location, out_color);
}

如果您想随后将缓冲区复制到纹理,您可以将数据复制到图像,如本例所示:https://vulkan-tutorial.com/Texture_mapping/Images

    //staging buffer is your new converted RGB -> RGBA buffer. 
    transitionImageLayout(textureImage, VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
        copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
    transitionImageLayout(textureImage, VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

结论

现在您已经获得了主机->设备带宽优势,利用了 GPU 的计算能力,并且只在 3 通道内存的初始传输上接触了主机。