不使用阶段缓冲区复制数据到VK_FORMAT_R8G8B8_UNORM线性平铺图像异常
Vulkan直接复制R8G8B8格式图像采样异常的问题解决
问题描述
在Vulkan中向图像复制数据有两种方式:使用阶段缓冲区或直接复制。使用阶段缓冲区时,VK_FORMAT_R8G8B8A8_UNORM和VK_FORMAT_R8G8B8_UNORM格式均能正常工作;但不使用阶段缓冲区时,VK_FORMAT_R8G8B8A8_UNORM格式工作正常,切换为VK_FORMAT_R8G8B8_UNORM格式后,采样结果异常。已确认不同格式下源数据均正确。
问题原因
直接复制路径采用**线性平铺(VK_IMAGE_TILING_LINEAR)**图像,这种图像的内存布局会遵循设备的行对齐要求。VK_FORMAT_R8G8B8_UNORM每个像素占3字节,当图像宽度×3无法满足设备对齐标准(通常为4或8字节)时,每行末尾会被填充空白字节。而源数据是紧密排列的无填充格式,直接用memcpy整段拷贝会导致后续行的数据错位,最终采样时出现异常。
VK_FORMAT_R8G8B8A8_UNORM每个像素占4字节,行长度天然符合4字节对齐要求,不会产生填充,因此直接拷贝可以正常工作。
解决方案
需要获取线性图像的实际行步长(stride),逐行拷贝有效像素数据,跳过设备自动添加的填充部分:
- 调用
vkGetImageSubresourceLayout获取线性图像的内存布局,其中rowPitch字段为每行的实际字节数(包含填充) - 逐行将源数据中的有效像素数据拷贝到映射的图像内存,每行仅拷贝
宽度×像素字节数的内容,跳过填充区域
修改后的代码片段
在直接复制的分支中,替换原有的memcpy代码块:
// Map image memory void *data; VK_CHECK_RESULT(vkMapMemory(device, mappableMemory, 0, memReqs.size, 0, &data)); // 获取图像子资源布局,提取行步长 VkImageSubresource subresource = {}; subresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresource.mipLevel = 0; subresource.arrayLayer = 0; VkSubresourceLayout subresourceLayout; vkGetImageSubresourceLayout(device, mappableImage, &subresource, &subresourceLayout); // 逐行拷贝数据,处理行对齐填充 uint32_t pixelSize = 3; // R8G8B8格式每个像素占3字节 uint32_t srcRowSize = texture.width * pixelSize; uint8_t* srcPtr = ktxTextureData; uint8_t* dstPtr = static_cast<uint8_t*>(data); for (uint32_t y = 0; y < texture.height; y++) { memcpy(dstPtr, srcPtr, srcRowSize); srcPtr += srcRowSize; dstPtr += subresourceLayout.rowPitch; } vkUnmapMemory(device, mappableMemory);
额外说明
- 阶段缓冲区路径正常是因为
vkCmdCopyBufferToImage会自动处理格式的对齐和布局转换,无需手动考虑行步长 - 对于其他非4字节对齐的格式(如R5G6B5),直接复制时也需要用相同方式处理行步长
内容的提问来源于stack exchange,提问作者Camellia Ca
相关产品推荐
相关产品推荐

