Unable to create image from compressed texture data (S3TC)

I am trying to load compressed images using S3TC (BC / DXT) compression in Vulkan, but so far I have not been very lucky.

Here's what the Vulkan spec says about compressed images:

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

Compressed texture images stored using S3TC compressed image formats are presented as a set of 4x4 texel blocks, where each block contains 64 or 128 bits of texel data. The image is encoded as a normal 2D bitmap in which each 4x4 block is treated as one pixel.

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

For images created with linear tiles, rowPitch, arrayPitch, and depthPitch describe the layout of the subresource in linear memory. For uncompressed formats, rowPitch is the number of bytes between texels with the same x coordinate in adjacent lines (y coordinates differ by one). arrayPitch is the number of bytes between texels with the same x and y coordinates in neighboring arrays of the image array (array level values โ€‹โ€‹differ by one). depthPitch is the number of bytes between texels with the same x and y coordinates in adjacent slices of a three-dimensional image (z coordinates differ by one). Expressed as an addressing formula, the texel start byte in the subresource has the address:

// (x, y, z, layer) are in texel coordinates

address (x, y, z, layer) = layerarrayPitch + zdepthPitch + yrowPitch + xtexelSize + offset

For compressed formats, rowPitch represents the number of bytes between compressed blocks in adjacent lines. arrayPitch is the number of bytes between blocks in adjacent arrays. depthPitch is the number of bytes between blocks in adjacent slices of a three-dimensional image.

// (x, y, z, layer) are in square coordinates

address (x, y, z, layer) = layerarrayPitch + zdepthPitch + yrowPitch + xblockSize + offset;

arrayPitch undefined for images that were not created as arrays. depthPitch is defined only for 3D images.

For color formats, the aspectMask member of VkImageSubresource must be VK_IMAGE_ASPECT_COLOR_BIT. For depth / stencil formats, the aspect should be either VK_IMAGE_ASPECT_DEPTH_BIT or VK_IMAGE_ASPECT_STENCIL_BIT. In implementations that store depth and stencil aspects separately, querying each of these subresource layouts returns a different offset and size representing the area of โ€‹โ€‹memory used for that aspect. In implementations that store aspects of depth and stencils, they alternate, return the same offsets and sizes, and represent the distribution of alternating memory.

My image is a normal 2D image (0 layers, 1 mipmap), so there is no arrayPitch or depthPitch. Since S3TC compression is directly supported by the hardware, it should be possible to use image data without unpacking it first. In OpenGL, this can be done using glCompressedTexImage2D, and this has worked for me in the past.

In OpenGL, I used GL_COMPRESSED_RGBA_S3TC_DXT1_EXT as the image format, for Vulkan I use VK_FORMAT_BC1_RGBA_UNORM_BLOCK, which should be equivalent. Here is my code to display image data:

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 } } 

And here is the code to initialize the image:

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

The code works without problems, but the resulting image is incorrect. This is the original image:

Black and pink checkerboard pattern.

And this is the result:

Green / black checkerboard pattern, much smaller than the original image

I am sure that the problem is in the first updated code that I published, however, if it is not, I wrote a small adaptation of the demonstration of the triangle from the Vulkan SDK, which gives the same result, It can be downloaded here . The source code is included, all that I changed from the demo triangle is the demo_prepare_texture_image function in tri.c (lines 803 - 903) and the files "dds.cpp" and "dds.h". "dds.cpp" contains code for loading dds and displaying image memory.

I use gli to load dds data (which should "work fine with Vulkan"), which is also included in the download above. To create a project, the Vulkan SDK directory must be added to the tri project, and the path to the dds must be changed (tri.c, Line 809).

The original image ("x64 / Debug / test.dds" in the project) uses DXT1 compression. I tested other equipment with the same result.

Any sample code to initialize / map compressed images will also help a lot.

+8
c ++ image vulkan image-compression
source share
1 answer

Your problem is actually quite simple - in the function demo_prepare_textures in the first line there is a variable tex_format , which is set to VK_FORMAT_B8G8R8A8_UNORM (which is in the original example). This is ultimately used to create a VkImageView. If you just change this to VK_FORMAT_BC1_RGBA_UNORM_BLOCK , it will correctly display the texture in the triangle.

Aside - you can check if your texture is loaded correctly with the RenderDoc that comes with the Vulkan SDK installation. By capturing it and viewing the TextureViewer tab, the Inputs tab shows that your texture looks identical to the text on disk, even with the wrong format.

fixed image

+2
source share

All Articles