无法从压缩纹理数据创建图像(S3TC)

Sil*_*lan 8 c++ image image-compression vulkan

我一直在尝试在Vulkan中使用S3TC(BC/DXT)压缩加载压缩图像,但到目前为止我没有太多运气.

以下是Vulkan规范关于压缩图像的内容:

https://www.khronos.org/registry/dataformat/specs/1.1/dataformat.1.1.html#S3TC:

使用S3TC压缩图像格式存储的压缩纹理图像被表示为4×4纹素块的集合,其中每个块包含64或128位纹素数据.该图像被编码为普通的2D光栅图像,其中每个4×4块被视为单个像素.

https://www.khronos.org/registry/vulkan/specs/1.0/xhtml/vkspec.html#resources-images:

对于使用线性平铺创建的图像,rowPitch,arrayPitch和depthPitch描述了线性内存中子资源的布局.对于未压缩格式,rowPitch是相邻行中具有相同x坐标的纹素之间的字节数(y坐标相差一).arrayPitch是纹素之间的字节数,在图像的相邻数组层中具有相同的x和y坐标(数组层值相差一).depthPitch是在3D图像的相邻切片中具有相同x和y坐标的纹素之间的字节数(z坐标相差1).表示为寻址公式,子资源中texel的起始字节具有地址:

//(x,y,z,layer)是texel坐标

address(x,y,z,layer)= layer arrayPitch + z depthPitch + y rowPitch + x texelSize + offset

对于压缩格式,rowPitch是相邻行中压缩块之间的字节数.arrayPitch是相邻阵列层中块之间的字节数.depthPitch是3D图像的相邻切片中的块之间的字节数.

//(x,y,z,layer)是块坐标

address(x,y,z,layer)= layer arrayPitch + z depthPitch + y rowPitch + x blockSize + offset;

对于未创建为数组的图像,未定义arrayPitch.depthPitch仅针对3D图像定义.

对于颜色格式,VkImageSubresource的aspectMask成员必须为VK_IMAGE_ASPECT_COLOR_BIT.对于深度/模板格式,方面必须是VK_IMAGE_ASPECT_DEPTH_BIT或VK_IMAGE_ASPECT_STENCIL_BIT.在分别存储深度和模板方面的实现上,查询每个子资源布局将返回不同的偏移量和大小,表示用于该方面的内存区域.在存储深度和模板方面交错的实现上,返回相同的偏移和大小并表示交叉存储器分配.

我的图像是普通的2D图像(0层,1个mipmap),因此没有arrayPitchdepthPitch.由于硬件直接支持S3TC压缩,因此应该可以在不对其进行解压缩的情况下使用图像数据.在OpenGL中,这可以使用glCompressedTexImage2D来完成,这在过去对我有用.

在OpenGL中,我使用了GL_COMPRESSED_RGBA_S3TC_DXT1_EXT作为图像格式,对于Vulkan,我使用的是VK_FORMAT_BC1_RGBA_UNORM_BLOCK,它应该是等效的.这是我映射图像数据的代码:

auto dds = load_dds("img.dds");
auto *srcData = static_cast<uint8_t*>(dds.data());
auto *destData = static_cast<uint8_t*>(vkImageMapPtr); // Pointer to mapped memory of VkImage
destData += layout.offset(); // layout = VkImageLayout of the image
assert((w %4) == 0);
assert((h %4) == 0);
assert(blockSize == 8); // S3TC BC1
auto wBlocks = w /4;
auto hBlocks = h /4;
for(auto y=decltype(hBlocks){0};y<hBlocks;++y)
{
    auto *rowDest = destData +y *layout.rowPitch(); // rowPitch is 0
    auto *rowSrc = srcData +y *(wBlocks *blockSize);
    for(auto x=decltype(wBlocks){0};x<wBlocks;++x)
    {
        auto *pxDest = rowDest +x *blockSize;
        auto *pxSrc = rowSrc +x *blockSize; // 4x4 image block
        memcpy(pxDest,pxSrc,blockSize); // 64Bit per block
    }
}
Run Code Online (Sandbox Code Playgroud)

这是初始化图像的代码:

vk::Device device = ...; // Initialization
vk::AllocationCallbacks allocatorCallbacks = ...; // Initialization
[...] // Load the dds data
uint32_t width = dds.width();
uint32_t height = dds.height();
auto format = dds.format(); // = vk::Format::eBc1RgbaUnormBlock;

vk::Extent3D extent(width,height,1);

vk::ImageCreateInfo imageInfo(
    vk::ImageCreateFlagBits(0),
    vk::ImageType::e2D,format,
    extent,1,1,
    vk::SampleCountFlagBits::e1,
    vk::ImageTiling::eLinear,
    vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment,
    vk::SharingMode::eExclusive,
    0,nullptr,
    vk::ImageLayout::eUndefined
);

vk::Image img = nullptr;
device.createImage(&imageInfo,&allocatorCallbacks,&img);

vk::MemoryRequirements memRequirements;
device.getImageMemoryRequirements(img,&memRequirements);
uint32_t typeIndex = 0;
get_memory_type(memRequirements.memoryTypeBits(),vk::MemoryPropertyFlagBits::eHostVisible,typeIndex); // -> typeIndex is set to 1
auto szMem = memRequirements.size();
vk::MemoryAllocateInfo memAlloc(szMem,typeIndex);
vk::DeviceMemory mem;
device.allocateMemory(&memAlloc,&allocatorCallbacks,&mem); // Note: Using the default allocation (nullptr) doesn't change anything
device.bindImageMemory(img,mem,0);

uint32_t mipLevel = 0;
vk::ImageSubresource resource(
    vk::ImageAspectFlagBits::eColor,
    mipLevel,
    0
);
vk::SubresourceLayout layout;
device.getImageSubresourceLayout(img,&resource,&layout);

auto *srcData = device.mapMemory(mem,0,szMem,vk::MemoryMapFlagBits(0));
[...] // Map the dds-data (See code from first post)
device.unmapMemory(mem);
Run Code Online (Sandbox Code Playgroud)

代码运行没有问题,但生成的图像不正确.这是源图像:

黑色/粉红色棋盘图案

这就是结果:

绿色/黑色棋盘图案,比源图像小得多

我确定问题出在我发布的第一个代码剪辑中,但是,如果它没有,我已经编写了一个小型的三角形演示版本来自Vulkan SDK,它会产生相同的结果.它可以在这里下载.该源代码包含所有我从三角形演示改变都是在"demo_prepare_texture_image" -函数tri.c(行803至903)和"dds.cpp"和"dds.h"文件."dds.cpp"包含用于加载dds和映射图像内存的代码.

我正在使用gli来加载dds-data(它应该与Vulkan完美配合),这也包含在上面的下载中.要构建项目,必须将Vulkan SDK include目录添加到"tri"项目中,并且必须更改dds的路径(tri.c,第809行).

源映像(项目中的"x64/Debug/test.dds")使用DXT1压缩.我也在不同的硬件上测试过,结果相同.

用于初始化/映射压缩图像的任何示例代码也将有很大帮助.

Mue*_*ito 3

您的问题实际上非常简单 - 在demo_prepare_textures函数中,第一行有一个变量tex_format,它被设置为VK_FORMAT_B8G8R8A8_UNORM(这就是原始示例中的值)。这最终用于创建 VkImageView。如果您只是将其更改为VK_FORMAT_BC1_RGBA_UNORM_BLOCK,它将在三角形上正确显示纹理。

顺便说一句 - 您可以RenderDoc使用 Vulkan SDK 安装附带的 来验证您的纹理是否正确加载。捕获它并查看选项TextureViewer卡,Inputs选项卡显示您的纹理看起来与磁盘上的纹理相同,即使格式不正确。

固定图像