Войти
ПрограммированиеСтатьиГрафика

Vulkan API «Hello Triangle» (4 стр)

Автор:

Команды

Очередь команд, как ни странно, содержит в себе команды, отправленные на обработку GPU. Сами команды не помещаются в очередь непосредственно, а записываются в буферы команд, которые, в свою очередь, могут сами быть записаны в такие же. Буферы команд выделяются в пулах команд и могут быть отправлены в очередь на обработку.

План:
•  Создать очередь
•  Создать пул команд и обнулить его
•  Создать буферы команд, для последующей работы с каждым из изображений цепочки обмена
•  Записать командные буферы

Очередь команд

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

void vkGetDeviceQueue(
    VkDevice  device,
    uint32_t  queueFamilyIndex,
    uint32_t  queueIndex,
    VkQueue*  pQueue);

Стоит обратить внимание на то, что данная функция не возвращает успеха выполнения. Согласно спецификации, параметры queueFamilyIndex и queueIndex должны находится в приемлемых диапазонах семейств и очереди, для конкретного семейства.

Пожалуй, создадим одну:

VkQueue vk_queue;
{
  vkGetDeviceQueue(vk_device, 0, 0, &vk_queue);
}

Еще одним полезным свойством очереди является то, что можно ожидать на ее выполнение:

VkResult vkQueueWaitIdle(
    VkQueue  queue);

Пока это не сильно изменит поведение нашей программы, ведь в очереди ничего нет. Тем не менее, добавим ожидание в главном цикле, просто чтобы проверить работоспособность уже написанного кода:

if(vkQueueWaitIdle(vk_queue) != VkResult::VK_SUCCESS)
{
  throw std::exception("failed to wait for queue");
}

Пул команд

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

VkResult vkCreateCommandPool(
    VkDevice        device,
    const VkCommandPoolCreateInfo*  pCreateInfo,
    const VkAllocationCallbacks*    pAllocator,
    VkCommandPool*      pCommandPool);

typedef struct VkCommandPoolCreateInfo {
    VkStructureType    sType;
    const void*      pNext;
    VkCommandPoolCreateFlags  flags;
    uint32_t      queueFamilyIndex;
} VkCommandPoolCreateInfo;

В отличие от предыдущих структур, здесь поле flags составляется из битовых операций над значениями VkCommandPoolCreateFlagBits и устанавливает некоторые его свойства. Так же, после создания, нам потребуется обнулить пул.

VkCommandPool vk_commandPool;
{
  VkCommandPoolCreateInfo vk_commandPoolCreateInfo;
  {
    vk_commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    vk_commandPoolCreateInfo.pNext = nullptr;
    vk_commandPoolCreateInfo.flags = VkCommandPoolCreateFlagBits::VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    vk_commandPoolCreateInfo.queueFamilyIndex = 0;
  }

  if(vkCreateCommandPool(vk_device, &vk_commandPoolCreateInfo, nullptr, &vk_commandPool) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to create command pool");
  }

  if(vkResetCommandPool(vk_device, vk_commandPool, VkCommandPoolResetFlagBits::VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to reset command pool");
  }
}

Буфер команд

Буферы команд хранят готовые последовательности команд и экономят время, необходимое на синхронизацию CPU-GPU. Вместо того, чтобы отправлять на обработку сотню команд, мы синхронизируемся лишь единожды, передавая за раз все необходимое.

Буферы не создаются, а выделяются из существующего пула:

VkResult vkAllocateCommandBuffers(
    VkDevice          device,
    const VkCommandBufferAllocateInfo*  pAllocateInfo,
    VkCommandBuffer*        pCommandBuffers);

с помощью соответствующей структуры с информацией:

typedef struct VkCommandBufferAllocateInfo {
    VkStructureType    sType;
    const void*      pNext;
    VkCommandPool    commandPool;
    VkCommandBufferLevel  level;
    uint32_t      commandBufferCount;
} VkCommandBufferAllocateInfo;

Так же, буфер можно обнулить:

VkResult vkResetCommandBuffer(
    VkCommandBuffer      commandBuffer,
    VkCommandBufferResetFlags  flags);

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

VkResult vkBeginCommandBuffer(
    VkCommandBuffer      commandBuffer,
    const VkCommandBufferBeginInfo*  pBeginInfo);

VkResult vkEndCommandBuffer(
    VkCommandBuffer      commandBuffer);

Мы сознательно упускаем описание тривиальной структуры VkCommandBufferBeginInfo.

Запись буферов

Т.к. у нас пока не хватает деталей для вывода треугольника, для начала было бы неплохо хотя бы залить экран цветом. Тем не менее, есть небольшая загвоздка: для вывода в изображение и для вывода самого изображения, оно должно пребывать в разных форматах. Мы решим эту проблему, следующим образом:
1.  Первое преобразование (в формат, необходимый для рисования в изображение) уже происходит в нашем пассе вывода
2.  Второе преобразование (в формат, необходимый для вывода изображения на экран) будет производится в буфера команд по завершению пасса, с помощью специального объекта – барьера
Составим план:
•  Выделяем необходимое количество буферов команд (по одному на каждый буфер кадра)
•  Для каждого буфера команд (и, соответственно, кадра и вида):
  o  Обнуляем буфер
  o  Начинаем запись
  o  Начинаем вывод (для очистки экрана)
  o  Завершаем вывод
  o  Переводим изображение в необходимый для вывода поверхность формат
  o  Завершаем запись

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

void vkCmdPipelineBarrier(
    VkCommandBuffer      commandBuffer,
    VkPipelineStageFlags      srcStageMask,
    VkPipelineStageFlags      dstStageMask,
    VkDependencyFlags      dependencyFlags,
    uint32_t        memoryBarrierCount,
    const VkMemoryBarrier*    pMemoryBarriers,
    uint32_t        bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*  pBufferMemoryBarriers,
    uint32_t        imageMemoryBarrierCount,
    const VkImageMemoryBarrier*  pImageMemoryBarriers);

Из всех приведенных параметров нас интересуют только последние – imageMemoryBarrierCount/pImageMemoryBarriers:

typedef struct VkImageMemoryBarrier {
    VkStructureType    sType;
    const void*      pNext;
    VkAccessFlags    srcAccessMask;
    VkAccessFlags    dstAccessMask;
    VkImageLayout    oldLayout;
    VkImageLayout    newLayout;
    uint32_t      srcQueueFamilyIndex;
    uint32_t      dstQueueFamilyIndex;
    VkImage      image;
    VkImageSubresourceRange  subresourceRange;
} VkImageMemoryBarrier;

С их помощью можно изменять некоторые параметры изображения прямо во время выполнения. Так мы изменим планировку на приемлемую для вывода (VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) и разрешим доступ к памяти на чтение (VkAccessFlagBits::VK_ACCESS_MEMORY_READ_BIT).

std::vector<VkCommandBuffer> vk_commandBuffers(vk_swapchainImages.size());
{
  VkCommandBufferAllocateInfo vk_CommandBufferAllocateInfo;
  {
    vk_CommandBufferAllocateInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    vk_CommandBufferAllocateInfo.pNext = nullptr;
    vk_CommandBufferAllocateInfo.commandPool = vk_commandPool;
    vk_CommandBufferAllocateInfo.level = VkCommandBufferLevel::VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    vk_CommandBufferAllocateInfo.commandBufferCount = vk_swapchainImages.size();
  }

  if(vkAllocateCommandBuffers(vk_device, &vk_CommandBufferAllocateInfo, vk_commandBuffers.data()) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to allocate command buffers");
  }

  for(uint32_t i = 0; i < vk_swapchainImages.size(); ++i)
  {
    auto &vk_commandBuffer = vk_commandBuffers[i];
    auto &vk_image = vk_swapchainImages[i];
    auto &vk_framebuffer = vk_framebuffers[i];

    if(vkResetCommandBuffer(vk_commandBuffer, VkCommandBufferResetFlagBits::VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT) != VkResult::VK_SUCCESS)
    {
      throw std::exception("failed to reset command buffer");
    }

    VkCommandBufferInheritanceInfo vk_commandBufferInheritanceInfo;
    {
      vk_commandBufferInheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
      vk_commandBufferInheritanceInfo.pNext = nullptr;
      vk_commandBufferInheritanceInfo.renderPass = vk_renderPass;
      vk_commandBufferInheritanceInfo.subpass = 0;
      vk_commandBufferInheritanceInfo.framebuffer = vk_framebuffer;
      vk_commandBufferInheritanceInfo.occlusionQueryEnable = VK_FALSE;
      vk_commandBufferInheritanceInfo.queryFlags = 0;
      vk_commandBufferInheritanceInfo.pipelineStatistics = 0;
    }
    VkCommandBufferBeginInfo vk_commandBufferBeginInfo;
    {
      vk_commandBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
      vk_commandBufferBeginInfo.pNext = nullptr;
      vk_commandBufferBeginInfo.flags = 0;
      vk_commandBufferBeginInfo.pInheritanceInfo = &vk_commandBufferInheritanceInfo;
    }
    vkBeginCommandBuffer(vk_commandBuffer, &vk_commandBufferBeginInfo);
    {
      VkClearValue vk_clearValue;
      {
        vk_clearValue.color.float32[0] = 1.0f;
        vk_clearValue.color.float32[1] = 0.0f;
        vk_clearValue.color.float32[2] = 0.0f;
        vk_clearValue.color.float32[3] = 1.0f;
      }
      VkRenderPassBeginInfo vk_renderPassBeginInfo;
      {
        vk_renderPassBeginInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        vk_renderPassBeginInfo.pNext = nullptr;
        vk_renderPassBeginInfo.renderPass = vk_renderPass;
        vk_renderPassBeginInfo.framebuffer = vk_framebuffers[i];
        vk_renderPassBeginInfo.renderArea = VkRect2D{
          VkOffset2D{0, 0},
          VkExtent2D{
            vk_surfaceCapabilitiesKHR.currentExtent.width,
            vk_surfaceCapabilitiesKHR.currentExtent.height
          }
        };
        vk_renderPassBeginInfo.clearValueCount = 1;
        vk_renderPassBeginInfo.pClearValues = &vk_clearValue;
      };

      vkCmdBeginRenderPass(
        vk_commandBuffer,
        &vk_renderPassBeginInfo,
        VkSubpassContents::VK_SUBPASS_CONTENTS_INLINE
      );
      vkCmdEndRenderPass(vk_commandBuffer);

      VkImageMemoryBarrier vk_imageMemoryBarrier;
      {
        vk_imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
        vk_imageMemoryBarrier.pNext = nullptr;
        vk_imageMemoryBarrier.srcAccessMask = 0;
        vk_imageMemoryBarrier.dstAccessMask =
          VkAccessFlagBits::VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
          VkAccessFlagBits::VK_ACCESS_MEMORY_READ_BIT;
        vk_imageMemoryBarrier.oldLayout = VkImageLayout::VK_IMAGE_LAYOUT_UNDEFINED;
        vk_imageMemoryBarrier.newLayout = VkImageLayout::VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
        vk_imageMemoryBarrier.srcQueueFamilyIndex = 0;
        vk_imageMemoryBarrier.dstQueueFamilyIndex = 0;
        vk_imageMemoryBarrier.image = vk_image;
        vk_imageMemoryBarrier.subresourceRange = {VkImageAspectFlagBits::VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
      }
      vkCmdPipelineBarrier(
        vk_commandBuffer,
        VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
        VkPipelineStageFlagBits::VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &vk_imageMemoryBarrier
      );
    }
    vkEndCommandBuffer(vk_commandBuffer);
  }
}

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

Страницы: 1 2 3 4 5 6 7 Следующая »

#Vulkan, #основы

15 сентября 2016

Комментарии [69]