Vulkan API使用vkInstance对象来存储所有每个应用的状态。应用程序必须在执行任何其他Vulkan操作之前创建一个Vulkan实例
层通常用于验证,是可选的
第一步初始化GLFW库,因为GLFW是为创建OpenGL上下文设计。所以要告诉他不要通过后续调用创建OpenGL上下文:GLFW_NO_API
首先创建 VkInstance 实例 instance。
为该数据结构赋予自定义应用程序的信息VkApplicationInfo。
glfwInit();
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
// 创建窗口
GLFWwindow *glfwCreateWindow(int width, int height, const char *title, GLFWmonitor *monitor, GLFWwindow *share)
VkApplicationInfo appInfo = {};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;appInfo.pNext = nullptr;
appInfo.pApplicationName = "Hello Triangle";
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;
vulkan中大量信息通过结构体而不是函数参数传递。因此填充一个结构体来提供创建instance的信息
该结构体告知vulkan驱动程序要使用的扩展和验证层。
后四个为扩展与验证层相关参数。enabledExtensionCount (扩展数量)和ppEnabledExtensionNames(扩展名字)指定需要的全局扩展,Vulakn对于平台特性是零API支持的(至少暂时这样),这意味着需要一个扩展才能与不同平台的窗体系统进行交互。
VkInstanceCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
VkResult result = vkCreateInstance(&createInfo, nullptr, &instance);
//三个参数:使用有关creation info 的结构体指针,使用自定义分配器回调的指针,使用保存新对象句柄的指针
//该函数返回一个值为VK_SUCCESS或错误码VkResult类型的值。用以检测结果
验证层常见操作:
据规范检查参数数值,确认有无错误
跟踪对象的创建和销毁,查找资源泄漏
跟踪线程调用链,检查线程安全
将每个函数调用使用的参数记录到标准输出,用以初步的分析
VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* instance) {
if (pCreateInfo == nullptr || instance == nullptr) {
log("Null pointer passed to required parameter!");
return VK_ERROR_INITIALIZATION_FAILED;
}
return real_vkCreateInstance(pCreateInfo, pAllocator, instance);
}
验证层
Vulkan没有内置任何Validation layers,但是LunarG Vulkan SDK提供了一系列layers用于检测常规的错误异常。只能使用已经安装到系统上下文的Validation layers。
//指定要启用的layers 以及是否启用
const std::vector<const char*> validationLayers = {
"VK_LAYER_LUNARG_standard_validation"
};
const bool enableValidationLayers = true;
//检查所有请求的layers是否可用, 先使用vkEnumerateInstanceLayerProperties列出所有可用层
bool checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
//检查 validationLayers 中所有层是否在 availableLayers列表中, 通关strcmp 对比
for (const char* layerName : validationLayers) {
bool layerFound = false;
for (const auto& layerProperties : availableLayers) {
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
之后可以在创建实例函数中使用
void createInstance() {
if (enableValidationLayers && !checkValidationLayerSupport()) {
throw std::runtime_error("validation layers requested, but not available!");
}
...
}
验证通过后,设置VkInstanceCreateInfo结构体的参数信息(确定要启用的全局验证层)
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
单纯开启 验证层 没有任何意义, 需要设置回调,来让应用接受诊断消息。 使用VK_EXT_debug_report扩展。
扩展信息。
//根据是否开启 验证层 返回所需扩展列表
std::vector<const char*> getRequiredExtensions() {
std::vector<const char*> extensions;
//GLFW的扩展总是需要的
unsigned int glfwExtensionCount = 0;
const char** glfwExtensions;
glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
for (unsigned int i = 0; i < glfwExtensionCount; i++) {
extensions.push_back(glfwExtensions[i]);
}
//等价于 std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);
//VK_EXT_debug_report扩展,VK_EXT_DEBUG_REPORT_EXTENSION_NAME宏定义,等价字面值VK_EXT_debug_report,使用宏定义避免硬编码。
if (enableValidationLayers) {
extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
}
return extensions;
}
检查可用扩展。类似验证层
vkEnumerateInstanceExtensionProperties需要一个指向存储扩展数量的变量指针和一个VkExtensionProperties存储扩展详细信息的数组
void checkAvailableExtensions() {
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> extensions(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
std::cout << "available extensions:" << std::endl;
for (const auto& extension : extensions) {
std::cout << "\t" << extension.extensionName << std::endl;
}
}
在createInstance中调用
auto extensions = getRequiredExtensions();
//createInfo.enabledExtensionCount = glfwExtensionCount;
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
//createInfo.ppEnabledExtensionNames = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
createInfo.ppEnabledExtensionNames = extensions.data();
使用PFN_vkDebugUtilsMessengerCallbackEXT原型添加一个名为debugCallback的新静态成员函数。VKAPI_ATTR和VKAPI_CALL确保该函数具有Vulkan调用它的正确签名。 (作为VkDebugUtilsMessengerCreateInfoEXT createInfo 的指定回调函数)
//该函数为打印debug信息
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;
}
参数:
- VkDebugUtilsMessageSeverityFlagBitsEXT 消息严重程度:
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:诊断消息
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:信息性消息,如创建资源
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:有关行为的消息不一定是错误,但很可能是应用程序中的错误
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:有关无效行为的消息,可能导致崩溃
可以根据这个参数过滤所需的信息。 - VkDebugUtilsMessageTypeFlagsEXT 消息类型
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:发生了与规范或性能无关的某些事件
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:发生了违反规范或表明可能存在错误的事情
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:潜在的非最佳使用Vulkan - VkDebugUtilsMessengerCallbackDataEXT 包含消息细节
pMessage:调试消息为以空字符结尾的字符串
pObjects:与消息相关的Vulkan对象句柄数组
object Count:数组中的对象数 - pUserData 包含在回调设置期间指定的指针,并允许您将自己的数据传递给它。
调试回调也通关显示创建和销毁句柄来管理。
// 在成员变量中添加:VkDebugUtilsMessengerEXT
VkDebugUtilsMessengerEXT debugMessenger;
void setupDebugMessenger() {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT 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;
// pfnUserCallback域是一个指向回调函数的指针。pUserData是一个指向用户自定义数据的指针,它是可选的,这个指针所指的地址会被作为回调函数的参数,用来向回调函数传递用户数据。
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = nullptr; // 可选
}
应该将结构体 VkDebugUtilsMessengerEXT 传递给vkCreateDebugUtilsMessengerEXT函数以创建VkDebugUtilsMessengerEXT对象。但这是个扩展函数不会自动加载,所有要自己使用vkGetInstanceProcAddr查找其地址,创建代理函数,然后在处理它
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func != nullptr) {
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
} else {
//如果无法加载vkGetInstanceProcAddr函数,返回nullptr
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
}
之后在setDebugMessenger(前文函数)中调用次函数
// 实例化DebugUtilsMessengerEXT
if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr,
&debugMessenger) != VK_SUCCESS) {
throw std::runtime_error("failed to set up debug messenger!");
}
创建另一个代理函数, 用于清理
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != nullptr) {
func(instance, debugMessenger, pAllocator);
}
}
确保该函数是静态函数或类外函数,然后在cleanup中调用
void cleanup() {
// 释放debugMessenger(VkDebugUtilsMessengerEXT, 用于打印调试信息)
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
}
// 在 vulkan 中资源一般都是 vkCreateXXX 创建,由 vkDestroyXXX 或 vkFreeXXX 释放.
vkDestroyInstance(instance, nullptr);
// 此函数会破坏指定的窗口及其上下文。 在调用此函数时,不会为该窗口调用其他回调。
// 如果指定窗口的上下文在主线程上是最新的,则在销毁之前将其分离。
glfwDestroyWindow(window);
// 此功能会释放所有剩余的窗口和光标并释放任何其他已分配的资源。
// 调用此函数后,必须再次成功调用@ref glfwInit,然后才能使用大多数GLFW函数。
glfwTerminate();
}
通过VkInstance初始化Vulkan后,需要在系统中查找并选择一个支持我们所需功能的显卡。选择的图形显卡存储在类成员VkPhysicalDevice句柄中。当VkInstance销毁时,这个对象会被隐式销毁。
void pickPhysicalDevice() {
uint32_t deviceCount = 0;
// 首先获取本机物理显卡数量
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
// 没有GPU时抛出异常
if (deviceCount == 0) {
throw std::runtime_error("failed to find GPUs with Vulkan support!");
}
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());
// 遍历所有GPU, 选择合适的使用
for (const auto& device : devices) {
if (isDeviceSuitable(device)) {
physicalDevice = device;
break;
}
}
if (physicalDevice == VK_NULL_HANDLE) {
throw std::runtime_error("failed to find a suitable GPU!");
}
}
//用vkGetPhysicalDeviceProperties查询基本设备属性,如名称,类型和支持的Vulkan版本
bool isDeviceSuitable(VkPhysicalDevice device) {
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);
//使用vkGetPhysicalDeviceFeatures查询对纹理压缩,64位浮点数和多视图渲染(VR非常有用)等可选功能的支持
VkPhysicalDeviceFeatures deviceFeatures;
vkGetPhysicalDeviceFeatures(device, &deviceFeatures);
return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.geometryShader;
}
几乎所有Vulkan中的操作,从绘图到上传纹理,都需要将命令提交到队列中。存在来自不同队列系列的不同类型的队列,并且每个队列族仅允许命令的子集。例如,可能存在仅允许处理计算命令的队列系列或仅允许与存储器传输相关的命令的队列系列。
要检查本机设备所支持的队列簇,选出支持我们要使用的命令的队列簇。
找出一个支持图形command的队列簇
struct QueueFamilyIndices {
// 添加头文件:including <optional>
// optional可以容纳一个对象值或是为空。
// 典型的应用情景是函数调用时,如需根据条件返回一个对象(有效)或默认对象(无效)
// 若该对象构造成本很高(资源分配等),可用optional返回一个空对象,提高效率。
std::optional<uint32_t> graphicsFamily;
bool isComplete() {
return graphicsFamily.has_value();
}
};
QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
QueueFamilyIndices indices;
//检查数量
uint32_t queueFamilyCount = 0;
// 使用 vkGetPhysicalDeviceQueueFamilyProperties 检索队列系列是否正是我们需要的:
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
// VkQueueFamilyProperties结构包含有关队列系列的一些详细信息
// 包括支持的操作类型以及可基于该系列创建的队列数
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
//例找到一个支持VK_QUEUE_GRAPHICS_BIT的队列系列
int i = 0;
for (const auto& queueFamily : queueFamilies) {
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
if (indices.isComplete()) {
break;
}
i++;
}
return indices;
}
//选择支持该命令队列簇的显卡设备
bool isDeviceSuitable(VkPhysicalDevice device) {
QueueFamilyIndices indices = findQueueFamilies(device);
return indices.isComplete();
}
选择一个需要使用的物理设备后,再创建一个与之匹配的逻辑设备。
类似实例创建过程,还要指定现在要创建的队列。
从同一个物理设备可以创建多个逻辑设备。
存储逻辑设备类似物理设备,也是使用句柄。
先添加一个新的类成员来存储逻辑设备句柄。
VkDevice device;
void createLogicalDevice() {
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
//创建逻辑设备需要在结构体中明确具体的信息。 结构体VkDeviceQueueCreateInfo,描述队列簇中预要申请使用的队列数量
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;
//指定使用的设备功能feature
//这些是使用vkGetPhysicalDeviceFeatures查询支持的功能,例如几何着色器。如果不需要任何特殊的东西,可以简单地定义它并默认为VK_FALSE。
VkPhysicalDeviceFeatures deviceFeatures = {};
//在VkDeviceQueueCreateInfo和VkPhysicalDeviceFeatures创建完毕后,就可以根据这个创建逻辑设备实例
// 首先指定VkDeviceQueueCreateInfo
createInfo.pQueueCreateInfos = &queueCreateInfo;
createInfo.queueCreateInfoCount = 1;
// 指定VkPhysicalDeviceFeatures
createInfo.pEnabledFeatures = &deviceFeatures;
//其余信息与VkInstanceCreateInfo结构相似,并要求指定扩展(Extensions)和验证层(Validation Layers)。不同之处在于这次是逻辑设备的。
createInfo.enabledExtensionCount = 0;
if (enableValidationLayers) {
createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
createInfo.ppEnabledLayerNames = validationLayers.data();
} else {
createInfo.enabledLayerCount = 0;
}
}
类似创建vulkan实例,使用 vkCreateDevice 函数创建逻辑设备。需在cleanup中销毁
if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) {
throw std::runtime_error("failed to create logical device!");
}
检索队列句柄:队列是与逻辑设备一起自动创建。 VkQueue 来存储队列的句柄,如存储图形队列的句柄
VkQueue graphicsQueue;
//可以使用vkGetDeviceQueue函数来检索每个队列系列的队列句柄,参数是逻辑设备,队列系列,队列索引和指向存储队列句柄的变量的指针
vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
总结
根据createInfo创建实例instance,createInfo中指明验证层和扩展信息。需查找并启用所指定验证层,开启扩展层,glfw。开启验证层的话需要回调函数来处理验证信息,给instance设置debugMeesenger,需在cleanup销毁.
查找并选择物流设备(显卡),选择有合适队列簇的设备。再创建与之匹配的逻辑设备。同时指定要创建的队列