Невозможно создать изображение из сжатых текстурных данных (S3TC)

Я пытался загрузить сжатые изображения с помощью сжатия S3TC (BC / DXT) в Vulkan, но пока мне не повезло.

Вот что спецификация Vulkan говорит о сжатых изображениях:

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

Сжатые текстурные изображения, сохраненные с использованием форматов сжатых изображений S3TC, представляются в виде набора текстовых блоков 4 × 4, где каждый блок содержит 64 или 128 бит текстовых данных. Изображение кодируется как обычное двухмерное растровое изображение, в котором каждый блок 4 × 4 рассматривается как один пиксель.

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

Для изображений, созданных с использованием линейного тайлинга, rowPitch, arrayPitch и deepPitch описывают расположение подресурса в линейной памяти. Для несжатых форматов rowPitch — это число байтов между текселями с одинаковой координатой x в соседних строках (координаты y отличаются на единицу). arrayPitch — это число байтов между текселями с одинаковыми координатами x и y в смежных слоях массива изображения (значения слоя массива отличаются на единицу). глубинаPitch — это число байтов между текселями с одинаковыми координатами x и y в смежных срезах трехмерного изображения (координаты z отличаются на единицу). Выраженный в виде формулы адресации, начальный байт текселя во вложенном ресурсе имеет адрес:

// (x, y, z, layer) находятся в текстовых координатах

адрес (x, y, z, layer) = уровеньarrayPitch + zdeepPitch + yrowPitch + xtexelSize + смещение

Для сжатых форматов rowPitch — это число байтов между сжатыми блоками в смежных строках. arrayPitch — количество байтов между блоками в смежных слоях массива. deepPitch — это количество байтов между блоками в смежных срезах трехмерного изображения.

// (x, y, z, layer) находятся в координатах блока

адрес (x, y, z, layer) = уровеньarrayPitch + zdeepPitch + yrowPitch + xblockSize + смещение;

arrayPitch не определено для изображений, которые не были созданы как массивы. Глубина подачи определяется только для 3D-изображений.

Для цветовых форматов элемент aspectMask в VkImageSubresource должен иметь значение 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);

Код работает без проблем, однако полученное изображение не является правильным. Это исходное изображение:

Черно-розовый шахматный узор

И вот результат:

Зеленый / черный рисунок шахматной доски, намного меньше, чем исходное изображение

Я уверен, что проблема заключается в первом фрагменте кода, который я опубликовал, однако, если это не так, я написал небольшую адаптацию треугольника из Vulkan SDK, которая дает тот же результат. Можно скачать Вот. Исходный код включен, все, что я изменил из демо треугольника, это функция «demo_prepare_texture_image» в tri.c (Строки с 803 по 903) и файлы «dds.cpp» и «dds.h». «dds.cpp» содержит код для загрузки dds и отображения памяти изображений.

я использую циклооксигеназы загрузить dds-данные (которые должны «отлично работать с Vulkan»), которые также включены в загрузку выше. Чтобы построить проект, в проект «tri» необходимо добавить каталог включения Vulkan SDK и изменить путь к dds (tri.c, строка 809).

Исходное изображение («x64 / Debug / test.dds» в проекте) использует сжатие DXT1. Я также проверил на другом оборудовании, с тем же результатом.

Любой пример кода для инициализации / отображения сжатых изображений также очень поможет.

7

Решение

Ваша проблема на самом деле довольно проста — в demo_prepare_textures функция, первая строка, есть переменная tex_format, который установлен в VK_FORMAT_B8G8R8A8_UNORM (что есть в оригинальном образце). Это в конечном итоге используется для создания VkImageView. Если вы просто измените это на VK_FORMAT_BC1_RGBA_UNORM_BLOCK, он отображает текстуру правильно на треугольнике.

Как в стороне — вы можете проверить, что ваша текстура загружена правильно, с RenderDoc, который поставляется с установкой Vulkan SDK. Делая захват этого, и глядя в TextureViewer вкладка, Inputs Вкладка показывает, что ваша текстура выглядит так же, как на диске, даже с неверным форматом.

фиксированное изображение

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]