9

我一直在尝试在 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 坐标相差 1)。arrayPitch 是图像相邻数组层中具有相同 x 和 y 坐标的纹素之间的字节数(数组层值相差 1)。depthPitch 是 3D 图像相邻切片中具有相同 x 和 y 坐标的纹素之间的字节数(z 坐标相差 1)。用寻址公式表示,子资源中纹素的起始字节具有地址:

// (x,y,z,layer) 在纹理坐标中

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。对于深度/模板格式,aspect 必须是 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
    }
}

这是初始化图像的代码:

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

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

黑色/粉色棋盘格图案

这是结果:

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

我确信问题在于我发布的第一个代码被剪断,但是,如果没有,我已经从 Vulkan SDK 编写了一个三角形演示的小改编,它产生了相同的结果。可以在这里下载。包含源代码,我从三角形演示中更改的只是 tri.c 中的“demo_prepare_texture_image”函数第 803 到 903 行)以及“dds.cpp”和“dds.h”文件。“dds.cpp”包含加载 dds 和映射图像内存的代码。

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

源图像(项目中的“x64/Debug/test.dds”)使用 DXT1 压缩。我也在不同的硬件上进行了测试,结果相同。

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

4

1 回答 1

3

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

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

固定图像

于 2016-03-25T11:10:30.193 回答