You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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, &region);

内容的提问来源于stack exchange,提问作者gmmo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:27:26