Vulkan API — урок 36. Что такое staging buffer

На данный момент буфер вершин работает исправно, но вот тип памяти, позволяющий получить к себе доступ из CPU, не самый лучший выбор для чтения из самой видеокарты. Наиболее оптимальным будет VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT и, как правило, он не доступен CPU. В этом и следующем мы создадим два буфера. Один, промежуточный (staging buffer), с памятью доступной из CPU, для загрузки данных из массива вершин, и конечный буфер вершин в локальной памяти устройства. Затем мы будем использовать команду копирования для перемещения данных.

Подобное решение может поднять FPS более чем на 50% в отдельных случаях (и таки да, я сейчас говорю о сферических енотах в вакууме).

И еще, далее буду использовать слово «маппинг», в качестве продолжения предыдущего урока, где часто вставлял английское слово, и для более корректного описания, одним словом полностью значение передать тяжело, ну и в речи других рускоязычных чаще встречается именно такой вариант.

Очередь передачи/переноса (Transfer queue)

Команда копирования буфера выполняется только семействами очередей, которые поддерживает операции передачи, для проверки этого используем VK_QUEUE_TRANSFER_BIT. Хорошая новость заключается в том, что любое семейство с битом VK_QUEUE_GRAPHICS_BIT или VK_QUEUE_COMPUTE_BIT уже имеет поддержку  VK_QUEUE_TRANSFER_BIT. В реализации нам не потребуется явно указывать его в queueFlags.

Если захочется в этой программе использовать новое семейство для операций переноса данных, то понадобится сделать следующее:

  • Изменить QueueFamilyIndices и findQueueFamilies для явного поиска семейства с битом VK_QUEUE_TRANSFER_BIT  (не VK_QUEUE_GRAPHICS_BIT).
  • Доработать createLogicalDevice, что бы она запрашивала дескриптор очереди передачи.
  • Создать второй пул команд для буферов команд, что подаются на новое семейство очередей.
  • Изменить sharingMode ресурсов на VK_SHARING_MODE_CONCURRENT и установить и графическую и очередь передачи.
  • Все операции передачи, на подобии vkCmdCopyBuffer должны направляться к новой очереди

Абстрактное создание буфера

Поскольку будет создано несколько буферов, то следует перенести процесс создания буфера в отдельную функцию. Создадим новую функцию createBuffer и переместим в неё код из createVertexBuffer (за исключением маппинга). Убедитесь, что были добавлены такие параметры, как размер буфера, настройки и использование памяти, так что функцию можно использовать для создания различных типов буферов:

void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VDeleter<VkBuffer>& buffer, VDeleter<VkDeviceMemory>& bufferMemory) {
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    if (vkCreateBuffer(device, &bufferInfo, nullptr, buffer.replace()) != VK_SUCCESS) {
        throw std::runtime_error("failed to create buffer!");
    }

    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(device, buffer, &memRequirements);

    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);

    if (vkAllocateMemory(device, &allocInfo, nullptr, bufferMemory.replace()) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate buffer memory!");
    }

    vkBindBufferMemory(device, buffer, bufferMemory, 0);
}

Последние два параметра метода определяют куда будут записываться результирующие дескрипторы.

Теперь из метода createVertexBuffer следует удалить создание буфера и аллокатора, и просто вызвать createBuffer:

void createVertexBuffer() {
    VkDeviceSize bufferSize = sizeof(vertices[0]) * vertices.size();
    createBuffer(bufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory);

    void* data;
    vkMapMemory(device, vertexBufferMemory, 0, bufferSize, 0, &data);
        memcpy(data, vertices.data(), (size_t) bufferSize);
    vkUnmapMemory(device, vertexBufferMemory);
}

Main Admin

3 Comments

    • Да, все будет). Устроил себе выходные, в течении получаса собираюсь выложить следующий урок.
      А извинения точно неуместны, осознание что это кому-то нужно — уже приятное и заставляющее работать чувство.

      • Еще как нужно. Тем более что ваш ресурс — единственный в своем роде (полноценные уроки по вулкану на русском) и поэтому еще более ценен для начинающих программистов.

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

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