Я пытался загрузить сжатые изображения со сжатием S3TC (BC/DXT) в Vulkan, но пока мне не очень повезло.
Вот что говорит спецификация 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 — количество байтов между текселами с одинаковыми координатами x и y в соседних срезах 3D-изображения (координаты z отличаются на единицу). Выраженный в виде формулы адресации, начальный байт тексела в подресурсе имеет адрес:
// (x,y,z,layer) находятся в тексельных координатах
address(x,y,z,layer) = layerarrayPitch + zdepthPitch + yrowPitch + xtexelSize + смещение
Для сжатых форматов rowPitch — это количество байтов между сжатыми блоками в соседних строках. arrayPitch — это количество байтов между блоками в соседних слоях массива. depthPitch — это количество байтов между блоками в смежных фрагментах 3D-изображения.
// (x,y,z,layer) находятся в блочных координатах
address(x,y,z,layer) = layerШаг массива + zШаг глубины + yШаг строки + xРазмер блока + смещение;
arrayPitch не определен для изображений, которые не были созданы как массивы. depthPitch определяется только для 3D-изображений.
Для цветовых форматов элемент aspectMask VkImageSubresource должен иметь значение VK_IMAGE_ASPECT_COLOR_BIT. Для форматов глубины/трафарета аспект должен быть либо VK_IMAGE_ASPECT_DEPTH_BIT, либо VK_IMAGE_ASPECT_STENCIL_BIT. В реализациях, которые хранят аспекты глубины и трафарета отдельно, запрос каждого из этих макетов подресурсов будет возвращать другое смещение и размер, представляющие область памяти, используемую для этого аспекта. В реализациях, которые хранят аспекты глубины и трафарета с чередованием, возвращаются одни и те же смещение и размер, представляющие чередующееся выделение памяти.
Мое изображение представляет собой обычное 2D-изображение (0 слоев, 1 MIP-карта), поэтому в нем нет 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 и сопоставления памяти изображений.
Я использую gli для загрузки dds-данных (которые должны для «отличной работы с Vulkan»), который также включен в загрузку выше. Чтобы собрать проект, необходимо добавить каталог Vulkan SDK include в проект «tri» и изменить путь к dds (tri.c, строка 809).
Исходный образ ("x64/Debug/test.dds" в проекте) использует сжатие DXT1. Я также тестировал на другом оборудовании, с тем же результатом.
Любой пример кода для инициализации/сопоставления сжатых изображений также очень поможет.