【问题标题】:Unable to create image from compressed texture data (S3TC)无法从压缩纹理数据创建图像 (S3TC)
【发布时间】:2016-07-08 09:15:15
【问题描述】:

我一直在尝试在 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) = layerarrayPitch + zdepthPitch + yrowPitch + xtexelSize + offset

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

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

address(x,y,z,layer) = layerarrayPitch + zdepthPitch + yrowPitch + xblockSize + 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 编写了一个三角形演示的小改编,它产生了相同的结果.可以下载here。包含源代码,我从三角形演示中更改的只是 tri.c 中的“demo_prepare_texture_image”函数(第 803 到 903 行)以及“dds.cpp”和“dds .h”文件。 “dds.cpp”包含加载dds和映射图像内存的代码。

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

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

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

【问题讨论】:

    标签: c++ image image-compression vulkan


    【解决方案1】:

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

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

    【讨论】:

    • 如果我更改格式,生成的图像会更加混乱 (sciolyte.com/sharex/2016-03-25_15-35-40.gif)。 RenderDoc 在尝试通过它运行任何程序后立即崩溃。演示是否真的对您进行了更改?你有什么改变吗?
    • 如果它对您有用,您能否提供您的代码以及所做的更改?我还没有让它在我身边工作。
    • 除了 DDS 的加载路径,以及使用我安装的 Vulkan 1.0.5 SDK 和 VS 120 工具链的一些项目设置,我没有更改 tri_dds.zip 下载中的任何其他内容。可能是驱动问题?也许附上你的 vulkaninfo?
    • 事实证明,这似乎确实是驱动程序问题。我已经在两个不同的 AMD 系统(图像在两个系统上都损坏)和两个 Nvidia 系统(图像在两个系统上都显示得非常好)上进行了测试。我已向 AMD 报告,仍在等待回复。
    • 显然 AMD 驱动程序对正确完成资源转换更为敏感(而 Nvidia 非常宽容),这可能可以解释这一点。我自己没有测试过。
    猜你喜欢
    • 2014-05-18
    • 1970-01-01
    • 2021-06-19
    • 2018-03-31
    • 1970-01-01
    • 2010-10-06
    • 2019-02-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多