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

时间:2016-03-21 17:48:39

标签: 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)采用纹理坐标

         

地址(x,y,z,图层)=图层 arrayPitch + z depthPitch + y rowPitch + x texelSize + offset

  
     

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

     
    

//(x,y,z,layer)采用块坐标

         

地址(x,y,z,图层)=图层 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),因此没有 arrayPitch depthPitch 。由于硬件直接支持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
    }
}

这是初始化图像的代码:

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);

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

Black/Pink checkerboard pattern

这就是结果:

Green/Black checkerboard pattern, much smaller than source image

我确定问题出在我发布的第一个代码剪辑中,但是,如果没有,我已经编写了一个很小的改编版本来自Vulkan SDK的三角形演示,它会产生相同的结果。它可以下载here。包含了源代码,我从三角形演示中更改的是 tri.c 中的“demo_prepare_texture_image”函数(第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压缩。我也在不同的硬件上测试过,结果相同。

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

1 个答案:

答案 0 :(得分:3)

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

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

fixed image