Vulkan学习笔记【二】 设备选择

        当使用Vulkan创建图形应用程序时,需要创建一个VkDevice对象,该对象是与物理图形设备之间的接口。在创建VkDevice之前,需要创建一些队列(VkQueue),这些队列是用来将图形命令发送到GPU的。每个VkQueueFamily代表一组具有相似性质的队列,例如渲染队列、计算队列等。在创建VkDevice时,需要选择一个或多个VkQueueFamily作为设备队列。通过选择适当的VkQueueFamily,可以将不同类型的图形命令提交到GPU中。例如,可以使用渲染队列来提交渲染指令,如绘制三角形、纹理映射等;可以使用计算队列来提交计算指令,如矩阵运算、算法实现等。创建队列的过程包括指定队列的属性、优先级等,这些属性将用于在创建VkDevice时选择适当的队列。一旦创建了VkDevice,就可以使用选择的队列来提交图形命令,以实现高效的图形处理。        

物理设备与队列族的选择

        在通过VkInstance初始化Vulkan库之后,我们需要在系统中寻找并选择支持我们所需功能的显卡。通过vkEnumeratePhysicalDevices可以查看本地环境所支持的所有物理设备以及其特点。

// Provided by VK_VERSION_1_0
VkResult vkEnumeratePhysicalDevices(
    VkInstance                                  instance,
    uint32_t*                                   pPhysicalDeviceCount,
    VkPhysicalDevice*                           pPhysicalDevices);
  • instance是先前用vkCreateInstance创建的Vulkan实例的句柄。
  • pPhysicalDeviceCount是一个指向整数的指针,与可用或查询的物理设备数量相关,如下所述。
  • pPhysicalDevices是NULL或指向VkPhysicalDevice句柄数组的指针。

在获得物理设备的指针后,我们可以通过vkGetPhysicalDeviceProperties查询物理设备的一般属性:

// Provided by VK_VERSION_1_0
void vkGetPhysicalDeviceProperties(
    VkPhysicalDevice                            physicalDevice,
    VkPhysicalDeviceProperties*                 pProperties);

VkPhysicalDeviceProperties是一个描述指定物理设备属性的结构体。 

// Provided by VK_VERSION_1_0
typedef struct VkPhysicalDeviceProperties {
    uint32_t                            apiVersion;
    uint32_t                            driverVersion;
    uint32_t                            vendorID;
    uint32_t                            deviceID;
    VkPhysicalDeviceType                deviceType;
    char                                deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
    uint8_t                             pipelineCacheUUID[VK_UUID_SIZE];
    VkPhysicalDeviceLimits              limits;
    VkPhysicalDeviceSparseProperties    sparseProperties;
} VkPhysicalDeviceProperties;
  • apiVersion是设备支持的Vulkan版本,编码方式见Version Numbers

  • driverVersion 是供应商指定的驱动程序版本。

  • vendorID 是物理设备供应商(见下文)的唯一标识符。

  • deviceID 是在供应商提供的设备中,物理设备的唯一标识符。

  • deviceType is a VkPhysicalDeviceType specifying the type of device.

  • deviceName 是一个VK_MAX_PHYSICAL_DEVICE_NAME_SIZE大小的char数组,其中包含一个以空结尾的UTF-8字符串,即设备名称。

  • pipelineCacheUUID 是VK_UUID_SIZE uint8_t值的数组,表示设备的通用唯一标识符。

  • limits limits是VkPhysicalDeviceLimits 结构,指定物理设备的特定设备限制。有关详细信息,请参见 Limits 。

  • sparseProperties​指定物理设备的各种稀疏相关属性。

 此外,还可以通过vkGetPhysicalDeviceFeatures获得指定物理设备的特性,其返回一个VkPhysicalDeviceFeatures类型的结构体,描述了物理设备支持的一些特性,例如几何着色器或者细分着色器。

        在此之前,我们已经简单地提到过,在Vulkan中几乎每一个操作,从绘图到上传纹理,都需要将命令提交到队列中。有不同类型的队列来自不同的队列族,每个队列族只允许一个命令子集。例如,可能有一个队列族只允许处理计算命令,或者一个队列族只允许处理与内存传输相关的命令。我们需要检查设备支持哪些队列族,以及其中哪个队列族支持我们想要使用的命令。我们通过vkGetPhysicalDeviceQueueFamilyProperties获得指定物理设备的队列属性VkQueueFamilyProperties。

// Provided by VK_VERSION_1_0
void vkGetPhysicalDeviceQueueFamilyProperties(
    VkPhysicalDevice                            physicalDevice,
    uint32_t*                                   pQueueFamilyPropertyCount,
    VkQueueFamilyProperties*                    pQueueFamilyProperties);
// Provided by VK_VERSION_1_0
typedef struct VkQueueFamilyProperties {
    VkQueueFlags    queueFlags;
    uint32_t        queueCount;
    uint32_t        timestampValidBits;
    VkExtent3D      minImageTransferGranularity;
} VkQueueFamilyProperties;
  • queueFlags是VkQueueFlagBits的位掩码,表示此队列族中队列的功能。
  • queueCount是此队列族中队列的无符号整数计数。每个队列族必须至少支持一个队列。
  • timestampValidBits是通过vkCmdWriteTimestamp2或vkCmdWriteTimestamp写入的时间戳中有意义位的无符号整数计数。计数的有效范围是36到64位,或者值为0,表示不支持时间戳。有效范围之外的位保证为零。
  • minImageTransferGranularity是最小粒度支持

逻辑设备的创建

        在选择要使用的物理设备之后,我们需要设置一个逻辑设备作为它的接口。逻辑设备创建过程类似于实例创建过程,并描述了我们想要使用的特性。在查询了哪些队列族可用之后,我们还需要指定要创建哪些队列。如果您有不同的需求,甚至可以从同一个物理设备创建多个逻辑设备。 

        逻辑设备的创建涉及到在结构体中再次指定一堆细节,其中第一个将是VkDeviceQueueCreateInfo。该结构体描述了所创建逻辑设备所使用的队列的参数。

// Provided by VK_VERSION_1_0
typedef struct VkDeviceQueueCreateInfo {
    VkStructureType             sType;
    const void*                 pNext;
    VkDeviceQueueCreateFlags    flags;
    uint32_t                    queueFamilyIndex;
    uint32_t                    queueCount;
    const float*                pQueuePriorities;
} VkDeviceQueueCreateInfo;
  • sType是标识该结构的VkStructureType值。
  • pNext为NULL或指向扩展该结构的结构的指针。
  • flags是指示队列行为的位掩码。
  • queueFamilyIndex是一个无符号整数,表示要在该设备上创建队列的队列族的索引。该索引对应于vkGetPhysicalDeviceQueueFamilyProperties返回的pQueueFamilyProperties数组元素的索引。
  • queueCount是一个无符号整数,指定要在queueFamilyIndex指示的队列族中创建的队列数量,并使用由flags指定的行为。
  • pQueuepriority是一个指向queueCount规范化浮点值数组的指针,指定将提交给每个创建队列的工作的优先级。

在指定完逻辑设备所使用的队列信息后,我们可以开始创建我们的逻辑设备,逻辑设备的创建与实例的创建类似。

// Provided by VK_VERSION_1_0
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;
  • sType是一个VkStructureType值,标识该结构。
  • pNext是NULL或指向扩展该结构的结构的指针。
  • flags将保留以供将来使用。
  • queueCreateInfoCount是pqueuecreateinfoos数组的无符号整数大小。有关更多细节,请参阅 Queue Creation 部分。
  • pQueueCreateInfos是一个指针,指向VkDeviceQueueCreateInfo结构的数组,描述了需要与逻辑设备一起创建的队列。
  • enabledLayerCount已弃用。
  • ppEnabledLayerNames已弃用。
  • enabledExtensionCount是要启用的设备扩展数。
  • ppEnabledExtensionNames是一个指向enabledExtensionCount数组的指针,该数组包含了为创建的设备启用的扩展名。
  • pEnabledFeatures是NULL或指向VkPhysicalDeviceFeatures结构的指针,该结构包含所有要启用的特性的布尔指针。有关更多细节,请参阅 Features部分。

队列是与逻辑设备一起自动创建的,我们可以使用vkGetDeviceQueue函数来检索每个队列族的队列句柄。参数包括逻辑设备、队列族、队列索引和指向存储队列句柄的变量的指针。

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

 运行代码

#define GLFW_INCLUDE_VULKAN
#include<GLFW/glfw3.h>
#include <exception>
#include <iostream>
#include <vector>
#include <optional>

#define WIDTH 800
#define HEIGHT 600

#if NDEBUG
const bool enableValidationLayers = false;
#else
const bool enableValidationLayers = true;
#endif // enableValidationLayers

const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pCallback) {
	auto func = (PFN_vkCreateDebugUtilsMessengerEXT)
		vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
	if (func != nullptr) {
		return func(instance, pCreateInfo, pAllocator, pCallback);
	}
	else {
		return VK_ERROR_EXTENSION_NOT_PRESENT;
	}
}

void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
	auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
	if (func != nullptr) {
		func(instance, debugMessenger, pAllocator);
	}
}

class HelloVulkanApplication {
public:
	void run() {
		initWindow();
		initVulkan();
		mainLoop();
		cleanup();
	}

private:
	GLFWwindow* window;

	VkInstance instance;
	VkDebugUtilsMessengerEXT debugMessenger;

	VkPhysicalDevice physicalDevice;
	VkDevice device;

	VkQueue graphicsQueue;

	void initWindow() {
		glfwInit();

		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

		window = glfwCreateWindow(WIDTH, HEIGHT, "Learn Vulkan", nullptr, nullptr);
	}

	void initVulkan() {
		createInstance();
		setupDebugMessenger();        //设置vulkan使用过程中的调试信息
		pickPhysicalDevice();
		createLogicalDevice();
	}

	void mainLoop() {
		while (!glfwWindowShouldClose(window)) {
			glfwPollEvents();
		}
	}

	void cleanup() {
		if (enableValidationLayers) {
			DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
		}

		vkDestroyDevice(device, nullptr);

		vkDestroyInstance(instance, nullptr);

		glfwDestroyWindow(window);

		glfwTerminate();
	}

	void createInstance() {
		if (enableValidationLayers && !checkValidationLayerSupport()) {
			throw std::runtime_error("validation layers requested, but not available");
		}

		VkApplicationInfo appInfo = {};
		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
		appInfo.pApplicationName = "Hello Vulkan";
		appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.pEngineName = "No Engine";
		appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
		appInfo.apiVersion = VK_API_VERSION_1_0;


		VkInstanceCreateInfo createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
		createInfo.pApplicationInfo = &appInfo;

		std::vector<const char*> extensions = getRequireExtensions();
		createInfo.enabledExtensionCount = extensions.size();
		createInfo.ppEnabledExtensionNames = extensions.data();

		if (enableValidationLayers) {
			createInfo.enabledLayerCount = validationLayers.size();
			createInfo.ppEnabledLayerNames = validationLayers.data();

			// 调试vkCreateInstance和vkDestroyInstance
			VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
			populateDebugMessengerCreateInfo(debugCreateInfo);
			createInfo.pNext = &debugCreateInfo;
		}
		else {
			createInfo.enabledLayerCount = 0;
			createInfo.pNext = nullptr;
		}

		VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
		if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
			throw std::runtime_error("Failed to create Vulkan Instance!");
		}
	}

	std::vector<const char*> getRequireExtensions() {
		uint32_t glfwExtensionCount = 0;
		const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

		std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
		if (enableValidationLayers) {
			extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
		}
		return extensions;
	}

	void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
		createInfo = {};
		createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
		createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
		createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
		createInfo.pfnUserCallback = debugCallback;
	}

	void setupDebugMessenger() {
		if (!enableValidationLayers) return;

		VkDebugUtilsMessengerCreateInfoEXT createInfo;
		populateDebugMessengerCreateInfo(createInfo);

		if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
			throw std::runtime_error("failed to set up debug messenger!");
		}
	}

	bool checkValidationLayerSupport() {
		uint32_t layertCount;
		vkEnumerateInstanceLayerProperties(&layertCount, nullptr);

		std::vector<VkLayerProperties> availabelLayers(layertCount);
		vkEnumerateInstanceLayerProperties(&layertCount, availabelLayers.data());

		for (auto& validationlayer : validationLayers) {
			bool layerFound = false;
			for (auto& availabelayer : availabelLayers) {
				if (strcmp(validationlayer, availabelayer.layerName) == 0) {
					layerFound = true;
					break;
				}
			}

			if (!layerFound) {
				return false;
			}
		}

		return true;
	}

	static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
		std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
		return VK_FALSE;
	}

	void pickPhysicalDevice() {
		uint32_t deivceCount = 0;
		vkEnumeratePhysicalDevices(instance, &deivceCount, 0);

		std::vector<VkPhysicalDevice> Devices(deivceCount);
		vkEnumeratePhysicalDevices(instance, &deivceCount, Devices.data());
		for (auto& device : Devices) {
			if (isDeviceSuitable(device)) {
				physicalDevice = device;
				break;
			}
		}

		if (physicalDevice == VK_NULL_HANDLE) {
			throw std::runtime_error("failed to find a suitable GPU!");
		}
	}

	bool isDeviceSuitable(VkPhysicalDevice device) {
		std::optional<uint32_t> indices = findQueueFamilies(device);

		return indices.has_value();
	}

	std::optional<uint32_t> findQueueFamilies(VkPhysicalDevice device) {
		std::optional<uint32_t> indice;

		uint32_t queueFamilyCount = 0;
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

		std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
		vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
		int i = 0;
		for (const auto& queueFamily : queueFamilies) {
			if (queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
				indice = i;
			}
			if (indice.has_value()) {
				break;
			}
			i++;
		}

		return indice;
	}

	void createLogicalDevice() {
		std::optional<uint32_t> indice = findQueueFamilies(physicalDevice);

		VkDeviceQueueCreateInfo queueCreateInfo = {};
		queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
		queueCreateInfo.queueFamilyIndex = indice.value();
		queueCreateInfo.queueCount = 1;
		float queuePriority = 1.0f;
		queueCreateInfo.pQueuePriorities = &queuePriority;

		VkPhysicalDeviceFeatures deviceFeatures{};

		VkDeviceCreateInfo createInfo{};
		createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
		createInfo.pQueueCreateInfos = &queueCreateInfo;
		createInfo.queueCreateInfoCount = 1;
		createInfo.pEnabledFeatures = &deviceFeatures;

		createInfo.enabledExtensionCount = 0;

		if (enableValidationLayers) {
			createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
			createInfo.ppEnabledLayerNames = validationLayers.data();
		}
		else {
			createInfo.enabledLayerCount = 0;
		}

		if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
			throw std::runtime_error("failed to create logica device!");
		}

		vkGetDeviceQueue(device, indice.value(), 0, &graphicsQueue);
	}
};

int main() {
	HelloVulkanApplication helloVulkan;

	try {
		helloVulkan.run();
	}
	catch (const std::exception& e) {
		std::cerr << e.what() << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w还是晒太阳吧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值