Android平台Vulkan中VK_FORMAT_R8G8B8_UNORM纹理显示异常排查
问题:Android Vulkan无Alpha通道纹理渲染异常
在Android平台使用Vulkan渲染无Alpha通道的R8G8B8纹理四边形时无法正常显示,但改用带Alpha的R8G8B8A8格式后,相同逻辑能正常渲染。
正常工作的代码要点
1. 图像创建
VkImageCreateInfo imageCreateInfo = {}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.extent.width = width; imageCreateInfo.extent.height = height; imageCreateInfo.extent.depth = 1; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
2. 硬编码纹理数据
static const unsigned char tiny44[] = { 0xff,0x00,0x00,0xff, 0xff,0xff,0xff,0xff, 0xff,0x00,0x00,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0x00,0xff,0x00,0xff, 0xff,0xff,0xff,0xff, 0x00,0xff,0x00,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0xff,0xff, 0x00,0x00,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0xff,0xff,0xff, 0xff,0x00,0xff,0xff, 0xff,0xff,0xff,0xff, 0x00,0xff,0xff,0xff, }; static int32_t currWidth = 4; static int32_t currHeight = 4; static uint32_t imageSize = sizeof(tiny44);
3. 映射图像并复制到GPU内存
vkMapMemory(mainDevice.logicalDevice, imageStagingBufferMemory, 0, imageSize, 0, &data); memcpy(data, imageData, static_cast<size_t>(imageSize)); vkUnmapMemory(mainDevice.logicalDevice, imageStagingBufferMemory);
4. ImageView创建
VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.image = image; // Image to create view for viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; // Type of image (1D, 2D, 3D, Cube, etc) viewCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM; // Format of image data
修改后异常的代码要点
1. 硬编码纹理数据
static const unsigned char tiny44_no_alpha[] = { 0xff,0x00,0x00, 0xff,0xff,0xff, 0xff,0x00,0x00, 0xff,0xff,0xff, 0xff,0xff,0xff, 0x00,0xff,0x00, 0xff,0xff,0xff, 0x00,0xff,0x00, 0x00,0x00,0xff, 0xff,0xff,0xff, 0x00,0x00,0xff, 0xff,0xff,0xff, 0xff,0xff,0xff, 0xff,0x00,0xff, 0xff,0xff,0xff, 0x00,0xff,0xff, }; static int32_t currWidth = 4; static int32_t currHeight = 4; static uint32_t imageSize = sizeof(tiny44_no_alpha);
2. 图像创建函数
VkImageCreateInfo imageCreateInfo = {}; imageCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.extent.width = width; imageCreateInfo.extent.height = height; imageCreateInfo.extent.depth = 1; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.format = VK_FORMAT_R8G8B8_UNORM; // the only difference imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
疑问
除了修改VkImageCreateInfo、VkImageViewCreateInfo以及更新纹理原始数据和大小外,还需要做其他操作吗?
问题排查与解决
核心问题大概率是GPU的纹理内存对齐要求以及格式支持的细节,以下是具体解决步骤:
1. 确认物理设备对VK_FORMAT_R8G8B8_UNORM的支持
不是所有Android GPU都完美支持非4字节对齐的像素格式,先通过API验证格式兼容性:
VkFormatProperties formatProps; vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_R8G8B8_UNORM, &formatProps); // 检查是否支持OPTIMAL tiling下的采样和传输目标用途 bool isSupported = (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) && (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_DST_BIT); if (!isSupported) { // 该格式不被当前GPU支持,换用其他格式(比如VK_FORMAT_B8G8R8_UNORM,或者回到RGBA) }
2. 强制处理纹理行对齐
很多GPU要求纹理的行字节数必须是4的倍数(或更大的对齐值),即使你的4×3=12刚好是4的倍数,也需要明确计算对齐后的行跨度:
// 获取GPU要求的行对齐值 VkPhysicalDeviceImageFormatProperties imageFormatProps; vkGetPhysicalDeviceImageFormatProperties(physicalDevice, VK_FORMAT_R8G8B8_UNORM, VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 0, &imageFormatProps); uint32_t bytesPerPixel = 3; uint32_t rowPitch = (currWidth * bytesPerPixel + imageFormatProps.rowPitchAlignment - 1) / imageFormatProps.rowPitchAlignment * imageFormatProps.rowPitchAlignment; uint32_t stagingBufferSize = rowPitch * currHeight; // 创建staging buffer时用这个stagingBufferSize,而不是直接用sizeof(tiny44_no_alpha)
然后填充staging buffer时,每行数据要填充到rowPitch的长度:
vkMapMemory(mainDevice.logicalDevice, imageStagingBufferMemory, 0, stagingBufferSize, 0, &data); unsigned char* dst = static_cast<unsigned char*>(data); const unsigned char* src = tiny44_no_alpha; for (int y = 0; y < currHeight; y++) { memcpy(dst, src, currWidth * bytesPerPixel); // 填充对齐的空白字节 memset(dst + currWidth * bytesPerPixel, 0, rowPitch - currWidth * bytesPerPixel); dst += rowPitch; src += currWidth * bytesPerPixel; } vkUnmapMemory(mainDevice.logicalDevice, imageStagingBufferMemory);
3. 修正ImageView的组件映射
明确设置ImageView的颜色通道映射,避免GPU默认处理异常:
VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.image = image; viewCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewCreateInfo.format = VK_FORMAT_R8G8B8_UNORM; // 明确设置通道映射,无Alpha通道时A分量用1填充 viewCreateInfo.components.r = VK_COMPONENT_SWIZZLE_R; viewCreateInfo.components.g = VK_COMPONENT_SWIZZLE_G; viewCreateInfo.components.b = VK_COMPONENT_SWIZZLE_B; viewCreateInfo.components.a = VK_COMPONENT_SWIZZLE_ONE; // 确保子资源范围正确 viewCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewCreateInfo.subresourceRange.mipLevel = 0; viewCreateInfo.subresourceRange.levelCount = 1; viewCreateInfo.subresourceRange.baseArrayLayer = 0; viewCreateInfo.subresourceRange.layerCount = 1;
4. 检查复制命令参数
确保vkCmdCopyBufferToImage的参数正确,特别是行长度设置:
VkBufferImageCopy region = {}; region.bufferOffset = 0; region.bufferRowLength = currWidth; // 明确设置每行的像素数,让Vulkan正确解析内存 region.bufferImageHeight = currHeight; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = 0; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset = {0, 0, 0}; region.imageExtent = {currWidth, currHeight, 1}; vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
内容的提问来源于stack exchange,提问作者gmmo
相关产品推荐
相关产品推荐

