Vulkan API — урок 48. Вспомогательные функции

Копирование изображений

Перед тем, как вернуться к createTextureImage, создадим еще одну вспомогательную функцию copyImage:

void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    endSingleTimeCommands(commandBuffer);
}

Подобно буферам, необходимо указать, какая часть изображения должна быть скопирована в какую часть другого изображения. Для этого имеется структура VkImageCopy:

VkImageSubresourceLayers subResource = {};
subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subResource.baseArrayLayer = 0;
subResource.mipLevel = 0;
subResource.layerCount = 1;

VkImageCopy region = {};
region.srcSubresource = subResource;
region.dstSubresource = subResource;
region.srcOffset = {0, 0, 0};
region.dstOffset = {0, 0, 0};
region.extent.width = width;
region.extent.height = height;
region.extent.depth = 1;

Поля структуры итак уже должны быть очевидны. Операции копирования ставятся в очередь используя функцию vkCmdCopyImage, в которой определяются источник изображения/формата и пункт назначения. Предположим, что на данном этапе они уже будут приведены к оптимальному формату.

vkCmdCopyImage(
    commandBuffer,
    srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
    dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
    1, &region
);

Подготовка изображения текстуры

Теперь у нас есть все необходимое для окончательной настройки изображения текстуры, вернемся к createTextureImage. Последнее что сделали там – создали изображение текстуры. Следующий шаг – копирование промежуточного изображения в текстурное, оно включает три операции:

  • Изменение промежуточного изображения в VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
  • Изменение изображения текстуры в  VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
  • Выполнить операцию копирования

Это все сделать крайне просто используя созданные ранее функции:

transitionImageLayout(stagingImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_PREINITIALIZED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
copyImage(stagingImage, textureImage, texWidth, texHeight);

Оба значения, VK_IMAGE_LAYOUT_PREINITIALIZED и VK_IMAGE_LAYOUT_UNDEFINED, подойдут для старого формата, т.к. мы не заботимся о содержимом до операции копирования.

Для того, чтобы иметь возможность начать выборку из изображения текстуры в шейдер, нам нужно еще одно изменение формата:

void copyImage(VkImage srcImage, VkImage dstImage, uint32_t width, uint32_t height) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    endSingleTimeCommands(commandBuffer);
}

Маски доступа/барьеров

Если запустить ваше приложение с активированными слоями проверок (validation layers) сейчас, то увидите сообщение о некорректных масках доступа в transitionImageLayout. Ранее для них было установлено значение 0, настоящие значения для них должны быть основаны на макетах.

Нужно «обработать» все три изменения (transitions), оригинальные названия этапов оставлю без перевода:

  • Preinitialized → transfer source: CPU пишет (wait on host writes, основано на host visible memory – памяти видимой процессору), изменение ожидает для последующего чтения
  • Preinitialized → transfer destination: CPU пишет, изменение ожидает для последующей записи
  • Transfer destination → shader reading: изменение пишет, шейдер ожидает для последующего чтения

Три правила определяются следующими масками доступа:

if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) {
    barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_PREINITIALIZED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
    barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
    barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
} else {
    throw std::invalid_argument("unsupported layout transition!");
}

В случае, если возникнет необходимость, то эту функцию в дальнейшем можно будет расширить.

Есть еще один возможный вариант формата изображения, причем универсальный – VK_IMAGE_LAYOUT_GENERAL. Но, как не сложно догадаться, он не обеспечит оптимальную производительность, хотя в отдельных случаях может оказаться полезным, например когда изображение используется и для ввода, и для вывода.

Main Admin

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *