Невозможно создать изображение из сжатых данных текстуры (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 бит данных текселей. Изображение кодируется как обычное растровое 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. Я также тестировал на другом оборудовании, с тем же результатом.

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


person Silverlan    schedule 21.03.2016    source источник


Ответы (1)


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

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

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

person MuertoExcobito    schedule 25.03.2016
comment
Если я изменю формат, результирующее изображение будет еще более запутанным (sciolyte .com/sharex/2016-03-25_15-35-40.gif). RenderDoc вылетает сразу после попытки запустить через него любую программу. Действительно ли демо сработало для вас с этим изменением? Вы изменили что-нибудь еще? - person Silverlan; 25.03.2016
comment
Если это работает для вас, не могли бы вы предоставить свой код с изменениями? Я до сих пор не заставил его работать на моей стороне. - person Silverlan; 28.03.2016
comment
Кроме пути загрузки DDS и некоторых настроек проекта для использования моего установленного Vulkan 1.0.5 SDK и цепочки инструментов VS 120, я ничего не менял из загрузки tri_dds.zip. Возможно проблема с драйвером? Может быть, прикрепить свою вулканинфо? - person MuertoExcobito; 29.03.2016
comment
Как оказалось, проблема действительно в драйвере. Я тестировал две разные системы AMD (изображение было повреждено на обеих) и две системы Nvidia (изображение отображалось отлично на обеих). Я сообщил об этом AMD, все еще жду ответа. - person Silverlan; 29.03.2016
comment
По-видимому, драйверы AMD гораздо более чувствительны к правильному перемещению ресурсов (тогда как Nvidia очень снисходительны), что может объяснить это. Я не тестировал их на себе. - person MuertoExcobito; 30.03.2016