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

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

Автор:

Логические устройства

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

VkResult vkCreateDevice(
    VkPhysicalDevice      physicalDevice,
    const VkDeviceCreateInfo*    pCreateInfo,
    const VkAllocationCallbacks*    pAllocator,
    VkDevice*        pDevice);

Даная функция создает логической устройство, на основе физического. Опять таки, наиболее интересной здесь является структура VkDeviceCreateInfo:

typedef struct VkDeviceCreateInfo {
    VkStructureType      sType;
    const void*        pNext;
    VkDeviceCreateFlags      flags;
    uint32_t        queueCreateInfoCount;
    const VkDeviceQueueCreateInfo*  pQueueCreateInfos;
    uint32_t        enabledLayerCount;
    const char* const*      ppEnabledLayerNames;
    uint32_t        enabledExtensionCount;
    const char* const*      ppEnabledExtensionNames;
    const VkPhysicalDeviceFeatures*  pEnabledFeatures;
} VkDeviceCreateInfo;

Помимо информации о слоях устройства (enabledLayerCount/ppEnabledLayerNames), которые, кстати говоря, почти никак не связаны со слоями образца интерфейса, и информации о расширениях (enabledExtensionCount/ppEnabledExtensionNames), она содержит два более интересных поля: информацию о особенностях устройства (pEnabledFeatures) и очередях (queueCreateInfoCount/pQueueCreateInfos).

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

Далее следует подготовить информацию о очередях. В контексте Vulkan API подразумевается очередь команд, которые поступают на выполнение GPU. Логическое устройство может иметь несколько семейств очередей, каждое из которых может иметь определенное количество… очередей. О самих очередях мы поговорим позже.

typedef struct VkDeviceQueueCreateInfo {
    VkStructureType    sType;
    const void*      pNext;
    VkDeviceQueueCreateFlags  flags;
    uint32_t      queueFamilyIndex;
    uint32_t      queueCount;
    const float*      pQueuePriorities;
} VkDeviceQueueCreateInfo;

Каждое семейство имеет свой уникальный индекс, количество очередей и их приоритеты. Большинство этих значений нам принципиально не важны, а потому мы создадим всего лишь одно семейство с одной единственной очередью нулевого приоритета.

std::vector<VkDeviceQueueCreateInfo> vk_deviceQueueCreateInfos(1);
std::vector<std::vector<float>> vk_deviceQueuesPriorities(vk_deviceQueueCreateInfos.size(), std::vector<float>(1, 0.0f));
{
  for(size_t i = 0; i < vk_deviceQueueCreateInfos.size(); ++i)
  {
    auto &vk_deviceQueueCreateInfo = vk_deviceQueueCreateInfos[i];
    auto &vk_deviceQueuePriorities = vk_deviceQueuesPriorities[i];
      vk_deviceQueueCreateInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    vk_deviceQueueCreateInfo.pNext = nullptr;
    vk_deviceQueueCreateInfo.flags = 0;
    vk_deviceQueueCreateInfo.queueFamilyIndex = i;
    vk_deviceQueueCreateInfo.queueCount = vk_deviceQueuePriorities.size();
    vk_deviceQueueCreateInfo.pQueuePriorities = vk_deviceQueuePriorities.data();
  }
}

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

VkDeviceCreateInfo vk_deviceCreateInfo;
{
  vk_deviceCreateInfo.sType = VkStructureType::VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  vk_deviceCreateInfo.pNext = nullptr;
  vk_deviceCreateInfo.flags = 0;
  vk_deviceCreateInfo.queueCreateInfoCount = vk_deviceQueueCreateInfos.size();
  vk_deviceCreateInfo.pQueueCreateInfos = vk_deviceQueueCreateInfos.data();
  vk_deviceCreateInfo.enabledLayerCount = layerNames.size();
  vk_deviceCreateInfo.ppEnabledLayerNames = layerNames.data();
  vk_deviceCreateInfo.enabledExtensionCount = extensionNames.size();
  vk_deviceCreateInfo.ppEnabledExtensionNames = extensionNames.data();
  vk_deviceCreateInfo.pEnabledFeatures = &vk_physicalDeviceFeatures;
};

if(vkCreateDevice(vk_physicalDevice, &vk_deviceCreateInfo, nullptr, &vk_device) != VkResult::VK_SUCCESS)
{
  throw std::exception("failed to create device");
}

Поверхность вывода и цепочка обмена

Для вывода изображения на экран нам понадобятся два объекта: поверхность вывода (surface) и цепочка обмена (swapchain).

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

Поддержка Win32

Следует заметить, что эта часть кода является зависимой от платформы. Само собой, у нас не получится создать поверхность вывода Win32 на Linux.
В первую очередь следует задействовать поддержку расширений для платформы Win32. Для этого необходимо объявить перед включением библиотеки «vulkan.h» следующий макрос:

#define VK_USE_PLATFORM_WIN32_KHR 1

включить в список расширений образца интерфейса расширения для поверхности вывода и поверхности вывода Win32:

VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_WIN32_SURFACE_EXTENSION_NAME,

а так же расширение для цепочки обмена, в список расширений устройства:

VK_KHR_SWAPCHAIN_EXTENSION_NAME,

Поверхность вывода

Создавать поверхность мы будем с помощью функции:

VkResult vkCreateWin32SurfaceKHR(
    VkInstance          instance,
    const VkWin32SurfaceCreateInfoKHR*  pCreateInfo,
    const VkAllocationCallbacks*      pAllocator,
    VkSurfaceKHR*        pSurface);

Для нас (как и для Vulkan API) уже довольно-таки типичная конструкция, а потому сразу перейдем к описанию VkWin32SurfaceCreateInfoKHR:

typedef struct VkWin32SurfaceCreateInfoKHR {
    VkStructureType      sType;
    const void*        pNext;
    VkWin32SurfaceCreateFlagsKHR  flags;
    HINSTANCE        hinstance;
    HWND        hwnd;
} VkWin32SurfaceCreateInfoKHR;

Как видно, именно эта часть крепко связывает нас с окном Win32. hinstance указывает на нашу программу, а hwnd, соответственно, на окно.

VkSurfaceKHR vk_surfaceKHR;
{
  VkWin32SurfaceCreateInfoKHR vk_win32SurfaceCreateInfoKHR;
  {
    vk_win32SurfaceCreateInfoKHR.sType = VkStructureType::VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
    vk_win32SurfaceCreateInfoKHR.pNext = nullptr;
    vk_win32SurfaceCreateInfoKHR.flags = 0;
    vk_win32SurfaceCreateInfoKHR.hinstance = handleInstance;
    vk_win32SurfaceCreateInfoKHR.hwnd = handleWindow;
  }
  if(vkCreateWin32SurfaceKHR(vk_instance, &vk_win32SurfaceCreateInfoKHR, nullptr, &vk_surfaceKHR) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to create surface");
  }

  VkBool32 isSupported;
  if(vkGetPhysicalDeviceSurfaceSupportKHR(vk_physicalDevice, 0, vk_surfaceKHR, &isSupported) != VkResult::VK_SUCCESS || isSupported == VK_FALSE)
  {
    throw std::exception("surface not supported");
  }
}

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

Для дальнейшей работы нам также понадобится список доступных для поверхности форматов:

std::vector<VkSurfaceFormatKHR> vk_surfaceFormats;
{
  uint32_t vk_surfaceFormatsCount;
  if(vkGetPhysicalDeviceSurfaceFormatsKHR(vk_physicalDevice, vk_surfaceKHR, &vk_surfaceFormatsCount, nullptr) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to get surface formats count");
  }

  vk_surfaceFormats.resize(vk_surfaceFormatsCount);
  if(vkGetPhysicalDeviceSurfaceFormatsKHR(vk_physicalDevice, vk_surfaceKHR, &vk_surfaceFormatsCount, vk_surfaceFormats.data()) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to get surface formats");
  }
}

Описание функции vkGetPhysicalDeviceSurfaceFormatsKHR упускается намерено, из-за сходства с остальными функциями для перечислений. Единственное, что стоит подчеркнуть, так это наличие параметра vk_physicalDevice, т.е. функция на самом деле перечисляет форматы, с которыми может работать реальное железо.

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

VkSurfaceCapabilitiesKHR vk_surfaceCapabilitiesKHR;
{
  if(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk_physicalDevice, vk_surfaceKHR, &vk_surfaceCapabilitiesKHR) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to get surface capabilities");
  }
}

Например, будем использовать значение поля currentExtent, как размеры экрана.

Цепочка обмена

Данный объект будет позволять нам двойную (вообще говоря, сколь-угодно кратную) буферизацию, т.е. рисовать в одно изображение, в то время как другое выводится на поверхность.
За создание отвечает:

VkResult vkCreateSwapchainKHR(
    VkDevice        device,
    const VkSwapchainCreateInfoKHR*  pCreateInfo,
    const VkAllocationCallbacks*    pAllocator,
    VkSwapchainKHR*      pSwapchain);

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

typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType      sType;
    const void*        pNext;
    VkSwapchainCreateFlagsKHR    flags;
    VkSurfaceKHR      surface;
    uint32_t        minImageCount;
    VkFormat        imageFormat;
    VkColorSpaceKHR      imageColorSpace;
    VkExtent2D        imageExtent;
    uint32_t        imageArrayLayers;
    VkImageUsageFlags      imageUsage;
    VkSharingMode      imageSharingMode;
    uint32_t        queueFamilyIndexCount;
    const uint32_t*      pQueueFamilyIndices;
    VkSurfaceTransformFlagBitsKHR  preTransform;
    VkCompositeAlphaFlagBitsKHR  compositeAlpha;
    VkPresentModeKHR      presentMode;
    VkBool32        clipped;
    VkSwapchainKHR      oldSwapchain;
} VkSwapchainCreateInfoKHR;

Мы удержимся от рассмотрения всех параметров и обойдемся лишь наиболее важными. surface связывает цепочку с определенной поверхностью, minImageCount говорит о количестве буферов (нам необходимы хотя бы 2), imageFormat и imageColorSpace это используемые форматы и пространства цветов, а imageExtent – размер буферов (условно говоря, размер экрана).

После создания цепочки:

VkSwapchainKHR vk_swapchainKHR;
{
  VkSwapchainCreateInfoKHR vk_SwapchainCreateInfoKHR;
  {
    vk_SwapchainCreateInfoKHR.sType = VkStructureType::VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    vk_SwapchainCreateInfoKHR.pNext = nullptr;
    vk_SwapchainCreateInfoKHR.flags = 0;
    vk_SwapchainCreateInfoKHR.surface = vk_surfaceKHR;
    vk_SwapchainCreateInfoKHR.minImageCount = 2;
    vk_SwapchainCreateInfoKHR.imageFormat = vk_surfaceFormats[0].format;
    vk_SwapchainCreateInfoKHR.imageColorSpace = vk_surfaceFormats[0].colorSpace;
    vk_SwapchainCreateInfoKHR.imageExtent = VkExtent2D{800,600};
    vk_SwapchainCreateInfoKHR.imageArrayLayers = 1;
    vk_SwapchainCreateInfoKHR.imageUsage = VkImageUsageFlagBits::VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    vk_SwapchainCreateInfoKHR.imageSharingMode = VkSharingMode::VK_SHARING_MODE_EXCLUSIVE;
    vk_SwapchainCreateInfoKHR.queueFamilyIndexCount = 0;
    vk_SwapchainCreateInfoKHR.pQueueFamilyIndices = nullptr;
    vk_SwapchainCreateInfoKHR.preTransform = VkSurfaceTransformFlagBitsKHR::VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    vk_SwapchainCreateInfoKHR.compositeAlpha = VkCompositeAlphaFlagBitsKHR::VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    vk_SwapchainCreateInfoKHR.presentMode = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR;
    vk_SwapchainCreateInfoKHR.clipped = VK_FALSE;
    vk_SwapchainCreateInfoKHR.oldSwapchain = VK_NULL_HANDLE;
  }

  if(vkCreateSwapchainKHR(vk_device, &vk_SwapchainCreateInfoKHR, nullptr, &vk_swapchainKHR) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to create swapchain");
  }
}

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

std::vector<VkImage> vk_swapchainImages;
{
  uint32_t vk_swapchainImagesCount;
  if(vkGetSwapchainImagesKHR(vk_device, vk_swapchainKHR, &vk_swapchainImagesCount, nullptr) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to get swapchain images count");
  }

  vk_swapchainImages.resize(vk_swapchainImagesCount);
  if(vkGetSwapchainImagesKHR(vk_device, vk_swapchainKHR, &vk_swapchainImagesCount, vk_swapchainImages.data()) != VkResult::VK_SUCCESS)
  {
    throw std::exception("failed to get swapchain images");
  }
}

Нам предстоит проделать еще немного работы с цепочкой обмена, но для этого нам уже нужна очередь.

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

#Vulkan, #основы

15 сентября 2016

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