Vulkan API — урок 47. Изменение формата

Функция, что мы собираемся написать сейчас, включает запись и повторное выполнение буфера коман, так что теперь самое время переместить логику во вспомогательную функцию, или две:

VkCommandBuffer beginSingleTimeCommands() {
    VkCommandBufferAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    allocInfo.commandPool = commandPool;
    allocInfo.commandBufferCount = 1;

    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);

    VkCommandBufferBeginInfo beginInfo = {};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;

    vkBeginCommandBuffer(commandBuffer, &beginInfo);

    return commandBuffer;
}

void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
    vkEndCommandBuffer(commandBuffer);

    VkSubmitInfo submitInfo = {};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &commandBuffer;

    vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
    vkQueueWaitIdle(graphicsQueue);

    vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}

Код для этой функции основан на существующем коде в copyBuffer. Теперь можно упростить эту функцию до вида:

void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    VkBufferCopy copyRegion = {};
    copyRegion.size = size;
    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

    endSingleTimeCommands(commandBuffer);
}

Если мы продолжим использовать буферы, тогда нужно написать функцию для записи и выполнения vkCmdCopyImage, но эта команда требует, что бы изображение для начала было в верном формате (layout). Создадим новую функцию для обработки изменений формата:

void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    endSingleTimeCommands(commandBuffer);
}

Одним из наиболее распространенных вариантов изменения формата, как уже упоминалось ранее, является image memory barrier. Барьер конвейера, как и обычный, в основном используется для доступа к ресурсам, например для защиты от чтения во время записи в буфер, так же он может быть использован для изменения формата изображения и передачи прав  меж семействами очередей, когда используется VK_SHARING_MODE_EXCLUSIVE. Это аналог buffer memory barrier.

VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;

В коде выше два поля определяют форматы. Для старого возможно использовать значение VK_IMAGE_LAYOUT_UNDEFINED, если имеющееся содержимое изображения безразлично.

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

barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;

image и subresourceRange определяют обрабатываемое изображение и отдельную его область. Наше изображение не является массивом и  не не содержит уровней мипмаппинга, потому определени только level и layer.

barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;

Барьеры в основном используются для синхронизации, потому нужно определить какие типы операций они включают, какие действия должны произойти до и какие операции будут ожидать «за» барьером. Нам нужно сделать это несмотря на то, что уже используется синхронизация vkQueueWaitIdle. Правильные значения зависят от старого и нового формата (layout), так что к данному фрагменту еще придется вернуться.

barrier.srcAccessMask = 0; // TODO
barrier.dstAccessMask = 0; // TODO

Ну и опишем сам барьер. Все типы барьеров конвейера используются для схожих операций. Сначала определяется параметр определяет, на каком шаге конвейера должны произойти операции до барьера. Далее параметр определяет на каком этапе конвейера операции будут ожидать окончания барьера. В нашем случае это должно произойти сразу же, потому используется параметр top.

Затем указывается либо 0, либо VK_DEPENDENCY_BY_REGION_BIT. Последнее превращает барьер в условие определенной обрасти. Это означает, что реализация позволяет начать чтение из определенной части ресурса, которая, допустим, уже была записана.

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

vkCmdPipelineBarrier(
    commandBuffer,
    VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
    0,
    0, nullptr,
    0, nullptr,
    1, &barrier
);

Main Admin

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

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