Vulkan学习(二):官方教程--(Physical Devices & Queue Families & Logical Device & Window Surface & Swap Chain)

Physical Devices

  Vulkan实例初始化后,需要选择渲染时使用的显卡。

//获取显卡数量
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
//获取显卡信息
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
//选择适合该程序的GPU
for (const auto& device : devices) {
	if (isDeviceSuitable(device)) {
		physicalDevice = device;
		break;
	}
}
//查询显卡属性,包括:名称,支持Vulkan的版本号
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);
//支持纹理的最大值
deviceProperties.limits.maxImageDimension2D
//查询显卡特性,包括:纹理压缩,64位浮点述。多视口渲染(VR)
VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
//是否支持几何着色器
deviceFeatures.geometryShader

Queue Families

  Vulkan中的每一个操作,从渲染到上传纹理,都需要将命令提交到队列中。不同的Queue Families只允许有一类命令子集。例如,一个队列只允许处理计算命令,或者一个队列只允许存在内存传输相关的命令

//选择的物理显卡Queue Family的数量
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
//Queue Family信息
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
//需要找到至少一个支持VK_QUEUE_GRAPHICS_BIT 的QueueFamily。
int i = 0;
for (const auto& queueFamily : queueFamilies) {
	//[10]寻找一个队列族,它能够链接window
	VkBool32 presentSupport = false;
	vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);
	if (presentSupport) {
		indices.presentFamily = i;
	}
	if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
		indices.graphicsFamily = i;
		if (indices.isComplete())
			break;
	}
		i++;
}
		

Logical Device and Queues

  在选择要使用的物理设备(Physical Device)之后,我们需要设置一个 Logical Device接入物理设备。创建过程类似于实例创建过程。支持同一物理设备创建多个逻辑设备。(Vulkan的设计模式感觉与Filament很像)

//创建Queue信息
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = indices.graphicsFamily.value();
queueCreateInfo.queueCount = 1;
//Vulkan使用0.0到1.0之间的浮点数为队列分配优先级, 来进行缓冲区执行的调度。即使只有一个队列也需要设置
float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;

//device features
VkPhysicalDeviceFeatures deviceFeatures = {};
//创建Device信息
VkDeviceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;
createInfo.pEnabledFeatures = &deviceFeatures;

//create a queue from both families
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = { indices.graphicsFamily.value(), indices.presentFamily.value() };
queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
	VkDeviceQueueCreateInfo queueCreateInfo = {};
	queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
	queueCreateInfo.queueFamilyIndex = queueFamily;
	queueCreateInfo.queueCount = 1;
	queueCreateInfo.pQueuePriorities = &queuePriority;
	queueCreateInfos.push_back(queueCreateInfo);

}
//将队列信息加入驱动info
createInfo.queueCreateInfoCount =
	static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
//开启扩展支持
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();
//创建驱动
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
	throw std::runtime_error("failed to create logical device!");
}
//获取驱动队列,显示
//因为我们只创建一个队列,所以我们只使用索引0
vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
	

Window surface

  由于Vulkan是一个与平台无关的API,它本身不能直接调用窗口接口(window system)。为了在Vulkan和窗口系统之间建立连接,将渲染结果显示在屏幕上,我们需要使用WSI(window system integration)扩展。

//Windows的创建方法
VkWin32SurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window);
createInfo.hinstance = GetModuleHandle(nullptr);
if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
	throw std::runtime_error("failed to create window surface!");
}
//Linux的创建方法与上面类似 vkCreateXcbSurfaceKHR
//VK_KHR_surface--VkSurfaceKHR对象,该对象为显示渲染图像的抽象类。
//使用GLFWWindow surface
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
	throw std::runtime_error("failed to create window surface!");

}

Swap Chain

  Vulkan没有“默认帧缓冲区”的概念,因此需要Swap Chain。其必须在Vulkan中显式创建。一般作用是使图像的显示与屏幕的刷新率同步。

Querying details of swap chain support

我们需要检查的基本属性有三种:

•基本surface 功能(图像的最小/最大数量,图像的最小/最大宽度和高度)

•surface 格式(像素格式、颜色空间)

•可用的渲染模式(presentation mode)

//查询扩展信息
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

for(const auto& extension : availableExtensions) {
	requiredExtensions.erase(extension.extensionName);
}

//查询 swap chain支持
SwapChainSupportDetails details;
//basic surface capabilities 基本性能
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
//the supported surface formats
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount,	nullptr);
if (formatCount != 0) {
	details.formats.resize(formatCount);
	vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());

}
//the supported presentation modes
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);
if (presentModeCount != 0) {
	details.presentModes.resize(presentModeCount);
	vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
}


//在创建Logical Device时开启扩展支持
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size());
createInfo.ppEnabledExtensionNames = deviceExtensions.data();

Choosing the right settings for the swap chain

可以通过三种类型的设置来确定:

•Surface格式(颜色深度)

•呈现模式(将图像“交换”到屏幕的条件)

•交换范围(交换链中图像的分辨率)

  每个VkSurfaceFormatKHR均包含格式和colorSpace成员。 指定颜色通道和类型。 例如,VK_FORMAT_B8G8R8A8_SRGB表示以8位无符号整数的顺序存储B,G,R和alpha通道,每个像素总共32位。 colorSpace成员使用VK_COLOR_SPACE_SRGB_NONLINEAR_KHR是否支持SRGB颜色空间。 请注意,在旧版本中,此标志为VK_COLORSPACE_SRGB_NONLINEAR_KHR。

for (const auto& availableFormat : availableFormats) {
	if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB 
		&& availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
		return availableFormat;
	}
}
//如果查询失败返回第一个
return availableFormats[0];

Presentation mode

Vulkan有四种显式可能的模式

  • VK_PRESENT_MODE_IMMEDIATE_KHR:应用程序提交的图像会立即传输到屏幕,这可能会导致撕裂。
  • VK_PRESENT_MODE_FIFO_KHR:
    swap chain是一个队列,当刷新显示时,显示从队列前面获取图像,程序在队列后面插入渲染图像。如果队列已满,则程序必须等待。这与现代游戏中的垂直同步最为相似。刷新显示的时刻称为“垂直空白”。
  • VK_PRESENT_MODE_FIFO_RELAXED_KHR:此模式仅在应用程序延迟且队列在最后一个空格处为空时与前一个模式不同。而不是等待下一个垂直空白,图像是立即转移时,它最终到达。这可能会导致撕裂
  • VK_PRESENT_MODE_MAILBOX_KHR:是第二种模式的另一种变体。队列已满时,不再阻塞应用程序,而是将已排队的映像替换为较新的映像。此模式可用于实现三重缓冲,这使您可以避免撕裂,而延迟问题明显小于使用双缓冲的标准垂直同步。
//三级缓存更好,如果有就开启
for (const auto& availablePresentMode : availablePresentModes) {
	if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
		 return availablePresentMode;
	}
}
return VK_PRESENT_MODE_FIFO_KHR;

Swap extent

Swap extent是swap chain的图像分辨率

//在minImageExtent和maxImageExtent内选择与窗口最匹配的分辨率
if (capabilities.currentExtent.width != UINT32_MAX) {
	return capabilities.currentExtent;
}
else {
	VkExtent2D actualExtent = { WIDTH, HEIGHT };
	actualExtent.width = std::max(capabilities.minImageExtent.width,
			std::min(capabilities.maxImageExtent.width, actualExtent.width));
	actualExtent.height = std::max(capabilities.minImageExtent.height,
			std::min(capabilities.maxImageExtent.height, actualExtent.height));
		return actualExtent;
}

Creating the swap chain


SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);
//swapChainSupport.capabilities.minImageCount + 1
//minImageCount意味着我们有时可能需要等待驱动程序完成内部操作,
//然后才能获取另一个要渲染的图像。因此,建议请求至少比minImageCount多一个图像:
//uint32_t imageCount = swapChainSupport.capabilities.minImageCount;
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
	imageCount = swapChainSupport.capabilities.maxImageCount;
}

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

createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
//imageArrayLayers指定每个图像包含的层的数量。除非开发3D应用程序,否则该值始终为1。
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

//两种方法可以处理从多个队列访问图像:
//VK_SHARING_MODE_CONCURRENT
//VK_SHARING_MODE_EXCLUSIVE
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value(),
indices.presentFamily.value() };
if (indices.graphicsFamily != indices.presentFamily) {
	createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
	createInfo.queueFamilyIndexCount = 2;
	createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else {
	createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
	createInfo.queueFamilyIndexCount = 0; 
	createInfo.pQueueFamilyIndices = nullptr; 
}
//指定对SwapChain中的图像应用某种变换
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
//alpha channel should be used for blending
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
//窗口重置--取缓存区图像方式
createInfo.oldSwapchain = VK_NULL_HANDLE;

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
	throw std::runtime_error("failed to create swap chain!");
}

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;

Retrieving the swap chain images

//检索swap chain中的VkImages
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

Image views

   在渲染管线中使用VkImage就必须创建VkImageView对象。 实际上就是视图。 它描述了如何访问图像以及访问图像的哪一部分。

swapChainImageViews.resize(swapChainImages.size());
for (size_t i = 0; i < swapChainImages.size(); i++) {

	VkImageViewCreateInfo createInfo = {};
	createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
	createInfo.image = swapChainImages[i];
	//选择视图类型 1D 2D 3D
	createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
	createInfo.format = swapChainImageFormat;
	//components字段允许旋转颜色通道。 
	createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; //default
	createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
	createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
	createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
	//描述图像的用途以及应访问图像的哪个部分。
	createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
	createInfo.subresourceRange.baseMipLevel = 0;
	createInfo.subresourceRange.levelCount = 1;
	createInfo.subresourceRange.baseArrayLayer = 0;
	createInfo.subresourceRange.layerCount = 1;
	if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
		throw std::runtime_error("failed to create image views!");
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值