当使用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;
}