[toc] Vulkan 实际上是应用和硬件之间的一个接口,对于 Vulkan 而言,Instance 会携带一个 Loader, 并且对其初始化,loader 负责加载驱动和 layers, 其中 Layers 一般用于错误校验,调试输出

# 构建 VulkanInstance - createInstance

# 初始化 VkApplicationInfo

1
2
3
4
5
6
7
8
// app info
VkApplicationInfo appInfo {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "pilot_renderer";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "Pilot";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = m_vulkan_api_version;

创建 Instance 之前需要初始化应用程序 VkApplicationInfo , 主要是用于验证应用的信息

# 初始化 VkInstanceCreateInfo

1
2
3
4
5
6
7
VkInstanceCreateInfo instance_create_info {};
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instance_create_info.pApplicationInfo = &appInfo; // the appInfo is stored here

auto extensions = getRequiredExtensions();
instance_create_info.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
instance_create_info.ppEnabledExtensionNames = extensions.data();

其中 VkApplicationInfo 是 VkInstanceCreateInfo 的一个参数 getRequiredExtensions() 函数用于获取当前环境上允许的扩展 - Layers

# 初始化 VkDebugUtilsMessengerCreateInfoEXT

// 暂时不看,从名字上可以看出来是 Debug 用的

# 构建 Instance - VkInstance

1
2
3
4
if (vkCreateInstance(&instance_create_info, nullptr, &_instance) != VK_SUCCESS)
{
throw std::runtime_error("vk create instance");
}

其中 instance 就是创建好的 VkInstance, 存在 PVulkanContext

# 初始化 Debug

// 暂时忽略 initializeDebugMessenger()

# 创建 GLFW 的 Windows 接口

1
2
3
4
5
6
7
void Pilot::PVulkanContext::createWindowSurface()
{
if (glfwCreateWindowSurface(_instance, _window, nullptr, &_surface) != VK_SUCCESS)
{
throw std::runtime_error("glfwCreateWindowSurface");
}
}

简单来说,Surface 层就是 Vulkan 与窗体的交互链接,其中这里使用到的是 GLFW 的 Vulkan 实现

Surface 存到了 PVulkanContextVkSurfaceKHR

# 确定使用哪个物理设备

vkEnumeratePhysicalDevices 函数返回的仅仅是系统里每一个 physical device (比如显卡) 的句柄列表,我们的应用程序必须选择其中一个.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
void Pilot::PVulkanContext::initializePhysicalDevice()
{
uint32_t physical_device_count;
vkEnumeratePhysicalDevices(_instance, &physical_device_count, nullptr);
if (physical_device_count == 0)
{
throw std::runtime_error("enumerate physical devices");
}
else
{
// find one device that matches our requirement
// or find which is the best
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
vkEnumeratePhysicalDevices(_instance, &physical_device_count, physical_devices.data());

std::vector<std::pair<int, VkPhysicalDevice>> ranked_physical_devices;
for (const auto& device : physical_devices)
{
VkPhysicalDeviceProperties physical_device_properties;
vkGetPhysicalDeviceProperties(device, &physical_device_properties);
int score = 0;

if (physical_device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
{
score += 1000;
}
else if (physical_device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
{
score += 100;
}

ranked_physical_devices.push_back({score, device});
}

std::sort(ranked_physical_devices.begin(),
ranked_physical_devices.end(),
[](const std::pair<int, VkPhysicalDevice>& p1, const std::pair<int, VkPhysicalDevice>& p2) {
return p1 > p2;
});

for (const auto& device : ranked_physical_devices)
{
if (isDeviceSuitable(device.second))
{
_physical_device = device.second;
break;
}
}

if (_physical_device == VK_NULL_HANDLE)
{
throw std::runtime_error("failed to find suitable physical device");
}
}
}

上述代码主要是对当前设备里的硬件设备进行加分排序,选择得分最高的且可以使用的那个作为最终选择的设备

最终可以使用的设备存储在 VkPhysicalDevice

# 构建逻辑设备

createLogicalDevice() Vulkan 中所有的操作都需要将命令提交到 队列 中,不同的队列族有不同的队列类型,所以在这个阶段主要是查看硬件队列族 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Pilot::QueueFamilyIndices
Pilot::PVulkanContext::findQueueFamilies(VkPhysicalDevice physical_device) // for device and surface
{
QueueFamilyIndices indices;
uint32_t queue_family_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);
std::vector<VkQueueFamilyProperties> queue_families(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data());

int i = 0;
for (const auto& queue_family : queue_families)
{
if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) // if support graphics command queue
{
indices.graphicsFamily = i;
}

VkBool32 is_present_support = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physical_device,
i,
_surface,
&is_present_support); // if support surface presentation
if (is_present_support)
{
indices.presentFamily = i;
}

if (indices.isComplete())
{
break;
}
i++;
}
return indices;
}

先通过 & 判断是否支持命令队列,然后判断表面 (前面的 Surface) 是否支持显示 (?)

如果遍历的过程中发现设备上有支持的命令队列 (?), 就直接完成

# 构建命令池 - createCommandPool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// default graphics command pool
void Pilot::PVulkanContext::createCommandPool()
{
VkCommandPoolCreateInfo command_pool_create_info {};
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
command_pool_create_info.pNext = NULL;
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
command_pool_create_info.queueFamilyIndex = _queue_indices.graphicsFamily.value();

if (vkCreateCommandPool(_device, &command_pool_create_info, nullptr, &_command_pool) != VK_SUCCESS)
{
throw std::runtime_error("vk create command pool");
}
}

先创建命令队列的信息 VkCommandPoolCreateInfo , 然后尝试创建命令队列,放到 PVulkanContextVkCommandPool

# 构建交换链 createSwapchain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
void Pilot::PVulkanContext::createSwapchain()
{
// query all supports of this physical device
SwapChainSupportDetails swapchain_support_details = querySwapChainSupport(_physical_device);

// choose the best or fitting format
VkSurfaceFormatKHR chosen_surface_format =
chooseSwapchainSurfaceFormatFromDetails(swapchain_support_details.formats);
// choose the best or fitting present mode
VkPresentModeKHR chosen_presentMode = chooseSwapchainPresentModeFromDetails(swapchain_support_details.presentModes);
// choose the best or fitting extent
VkExtent2D chosen_extent = chooseSwapchainExtentFromDetails(swapchain_support_details.capabilities);

uint32_t image_count = swapchain_support_details.capabilities.minImageCount + 1;
if (swapchain_support_details.capabilities.maxImageCount > 0 &&
image_count > swapchain_support_details.capabilities.maxImageCount)
{
image_count = swapchain_support_details.capabilities.maxImageCount;
}

VkSwapchainCreateInfoKHR createInfo {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = _surface;

createInfo.minImageCount = image_count;
createInfo.imageFormat = chosen_surface_format.format;
createInfo.imageColorSpace = chosen_surface_format.colorSpace;
createInfo.imageExtent = chosen_extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

uint32_t queueFamilyIndices[] = {_queue_indices.graphicsFamily.value(), _queue_indices.presentFamily.value()};

if (_queue_indices.graphicsFamily != _queue_indices.presentFamily)
{
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else
{
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}

createInfo.preTransform = swapchain_support_details.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = chosen_presentMode;
createInfo.clipped = VK_TRUE;

createInfo.oldSwapchain = VK_NULL_HANDLE;

if (vkCreateSwapchainKHR(_device, &createInfo, nullptr, &_swapchain) != VK_SUCCESS)
{
throw std::runtime_error("vk create swapchain khr");
}

vkGetSwapchainImagesKHR(_device, _swapchain, &image_count, nullptr);
_swapchain_images.resize(image_count);
vkGetSwapchainImagesKHR(_device, _swapchain, &image_count, _swapchain_images.data());

_swapchain_image_format = chosen_surface_format.format;
_swapchain_extent = chosen_extent;
}
  1. 先查找各种满足条件的信息 (从硬件和环境上)
  2. 设置当前交换链最小的图片个数 + 1
  3. 然后通过 VkSwapchainCreateInfoKHR 创建交换链信息
  4. 最后通过 vkCreateSwapchainKHR 函数设置 PVulkanContext 中的 VkSwapChainKHR
  5. 配置交换链的基础数据

# 构建交换链的视图层 - createSwapchainImageViews

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Pilot::PVulkanContext::createSwapchainImageViews()
{
_swapchain_imageviews.resize(_swapchain_images.size());

// create imageview (one for each this time) for all swapchain images
for (size_t i = 0; i < _swapchain_images.size(); i++)
{
_swapchain_imageviews[i] = PVulkanUtil::createImageView(_device,
_swapchain_images[i],
_swapchain_image_format,
VK_IMAGE_ASPECT_COLOR_BIT,
VK_IMAGE_VIEW_TYPE_2D,
1,
1);
}
}

根据交换链的个数,初始化交换链视图数据列表,存在 PVulkanContext 中的 VkImageView

# 构建 FrameBuffer - createFramebufferImageAndView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void Pilot::PVulkanContext::createFramebufferImageAndView()
{
PVulkanUtil::createImage(_physical_device,
_device,
_swapchain_extent.width,
_swapchain_extent.height,
_depth_image_format,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
_depth_image,
_depth_image_memory,
0,
1,
1);
_depth_image_view = PVulkanUtil::createImageView(
_device, _depth_image, _depth_image_format, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_VIEW_TYPE_2D, 1, 1);
}

Framebuffer 代表了一个 render pass 实例中使用的内存附件的集合。这些内存附件的例子包括我们在前面示例中创建的 color image buffer 和 depth buffer。Framebuffer 提供了 render pass 在渲染时所需要的附件。

可以看出,Vulkan 的每个 Render Pass 里并没有实际的资源,实际的资源是放到 FrameBuffer 中的,每个 FrameBuffer 实际上连接到了 Attachments 中,Attachments 中每个 Buffer 连接到了实际的实例 ImageView 所以这里实际上是通过创建 ImageView 来创建 Framebuffer Pilot 中上述代码只提供了一个 _depth_image_viewVkImageView

# 构建 Vulkan 资源分配 - createAssetAllocator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Pilot::PVulkanContext::createAssetAllocator()
{
VmaVulkanFunctions vulkanFunctions = {};
vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;

VmaAllocatorCreateInfo allocatorCreateInfo = {};
allocatorCreateInfo.vulkanApiVersion = m_vulkan_api_version;
allocatorCreateInfo.physicalDevice = _physical_device;
allocatorCreateInfo.device = _device;
allocatorCreateInfo.instance = _instance;
allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;

vmaCreateAllocator(&allocatorCreateInfo, &_assets_allocator);
}

VMA (Tag), 网上搜了下,好像是资源池之类的