窗口、实例与设备
这篇文章介绍 Vulkan 中的实例和设备,我们使用 GLFW + Vulkan 搭建最基础的渲染环境。以下是涉及到的函数和结构体整理。虽然涉及到很多函数和结构体,但整体流程实际上非常简单:
1. 初始化并创建一个可显示的窗口(GLFWInit > WindowHint > CreateWindow)。
2. 初始化 Vulkan 全局环境并创建实例(VkInstanceCreateInfo > vkCreateInstance)。
3. 选择显卡、检查队列族、创建设备与获取队列(vkEnumeratePhysicalDevices > VkDeviceCreateInfo > vkCreateDevice > vkGetDeviceQueue)。
代码就不细讲了,记录在 Commit f68d8a2。
一、 窗口
glfwInit() 是 GLFW 中的初始化函数。在使用 GLFW 进行任何图形窗口或输入操作之前,必须先调用它。它的函数原型为:
- GLFWAPI int glfwInit(void);
glfwInit() 用来初始化 GLFW 库的内部状态。它主要完成以下几件事:
1. 设置全局状态:初始化内部全局变量。准备好窗口管理、输入事件系统、时间函数等模块。
2. 平台相关初始化:在 Windows 上,它会注册窗口类并准备消息循环;在 Linux 上,它会连接 X11 或 Wayland 等图形系统。
3. 准备错误回调机制:初始化之后你可以通过 glfwSetErrorCallback() 注册错误处理函数。如果初始化失败,GLFW 会在内部记录错误原因。
glfwWindowHint() 函数用于在窗口创建之前设置窗口的特性,例如 API 类型、是否可调整大小、上下文版本号和窗口样式等。它的函数原型为:
- GLFWAPI void glfwWindowHint(int hint, int value);
参数 hint 指定窗口的某个属性。参数 value 是对应 hint 的值。
比如,GLFW_CLIENT_API 属性指定创建窗口时使用哪种图形客户端 API。常用值有:GLFW_OPENGL_API,使用 OpenGL 创建上下文(默认);GLFW_OPENGL_ES_API,使用 OpenGL ES;GLFW_NO_API,不创建任何图形上下文,通常用于 Vulkan 或自定义渲染 API。
GLFW_RESIZABLE 属性指定窗口是否可以被用户调整大小。可选值有:GLFW_TRUE,窗口可以被拖动改变大小(默认);GLFW_FALSE,窗口大小固定,不能被拖动。
glfwCreateWindow() 函数用于创建一个可渲染的窗口。它的函数原型为:
- GLFWAPI GLFWwindow* glfwCreateWindow(
- int width,
- int height,
- const char* title,
- GLFWmonitor* monitor,
- GLFWwindow* share);
参数 width 指定窗口宽度(像素);参数 height 指定窗口高度(像素);参数 title 指定窗口标题(字符串)。
参数 monitor 指定全屏显示的显示器。如果为 NULL,则创建窗口模式;参数 share 指定要共享上下文的窗口,用于资源共享。如果不共享,传 NULL。
glfwWindowShouldClose() 函数用于查询窗口是否应该关闭。它通常在渲染循环中使用,用来判断窗口是否收到了关闭请求。它的函数原型为:
- GLFWAPI int glfwWindowShouldClose(GLFWwindow* window);
glfwPollEvents() 函数处理所有已经在事件队列中的窗口系统事件,并触发相应的回调函数。简单来说,它让 GLFW 更新窗口状态、检测输入(鼠标、键盘、窗口关闭等)并分发事件回调。它的函数原型为:
- GLFWAPI void glfwPollEvents(void);
glfwDestroyWindow() 函数用于销毁一个之前通过 glfwCreateWindow() 创建的窗口,并清理与该窗口相关的所有内部资源(如上下文、输入状态、回调函数、系统句柄等)。它的函数原型为:
- GLFWAPI void glfwDestroyWindow(GLFWwindow* window);
glfwTerminate() 是 GLFW 库中用于清理所有全局资源、关闭库功能的函数。它是使用 GLFW 的最后一步,用来安全退出并释放内存。它的函数原型为:
- GLFWAPI void glfwTerminate(void);
二、 实例
VkInstanceCreateInfo 结构体是用于创建实例的核心结构。它的定义如下:
- typedef struct VkInstanceCreateInfo {
- VkStructureType sType;
- const void* pNext;
- VkInstanceCreateFlags flags;
- const VkApplicationInfo* pApplicationInfo;
- uint32_t enabledLayerCount;
- const char* const* ppEnabledLayerNames;
- uint32_t enabledExtensionCount;
- const char* const* ppEnabledExtensionNames;
- } VkInstanceCreateInfo;
sType 参数指定结构体类型,必须设置为 VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO。
pNext 参数用于结构体链。设置为 NULL,表示没有扩展结构。
flags 参数在 Windows 平台下设置为 0。
pApplicationInfo 参数填写你的应用和引擎信息。有帮助驱动进行优化、指定 Vulkan API 版本等作用。
enabledLayerCount 和 ppEnabledLayerNames 参数用于设置验证层。
enabledExtensionCount 和 ppEnabledExtensionNames 参数指定启用哪些实例级扩展。
VkApplicationInfo 结构体,作为 VkInstanceCreateInfo 结构体的一部分传入。它用于描述应用程序和引擎基础信息。它不是必填的,但建议提供,因为驱动会根据这里的信息进行优化兼容。它的定义如下:
- typedef struct VkApplicationInfo {
- VkStructureType sType;
- const void* pNext;
- const char* pApplicationName;
- uint32_t applicationVersion;
- const char* pEngineName;
- uint32_t engineVersion;
- uint32_t apiVersion;
- } VkApplicationInfo;
sType 参数指定结构体类型,必须为 VK_STRUCTURE_TYPE_APPLICATION_INFO。
pNext 参数用于结构体链扩展。此处极少扩展,一般填写 NULL。
pApplicationName 参数填写你的程序名称。
applicationVersion 参数填写应用程序版本号,通常用 VK_MAKE_VERSION 宏指定,格式为 major/minor/patch。
pEngineName 参数指定引擎名称。比如 "UE5"、"Unity" 等。没有使用某个公共引擎,可以直接写 "NoEngine"。
engineVersion 参数指定引擎版本号,与 applicationVersion 相同格式。
apiVersion 参数指定你想使用的 Vulkan API 版本。比如选择 VK_API_VERSION_1_1、VK_API_VERSION_1_2 或 VK_API_VERSION_1_3。
glfwGetRequiredInstanceExtensions() 函数是使用 GLFW + Vulkan 时必不可少的函数之一。它返回创建 Vulkan 实例时必须启用的扩展列表。可以用来设置 VkInstanceCreateInfo.enabledExtensionCount 和 VkInstanceCreateInfo.ppEnabledExtensionNames。它的函数原型为:
- GLFWAPI const char** glfwGetRequiredInstanceExtensions(uint32_t* count);
GLFW 会通过 count 参数返回所需的扩展数量。返回值是扩展名的字符串数组,长度为 count。
注意,函数返回的扩展数组是由 GLFW 内部管理。我们可以复制这个数组,方便后续使用或扩展。
VkExtensionProperties 是 Vulkan 中用于描述扩展信息的结构体。它的定义如下:
- typedef struct VkExtensionProperties {
- char extensionName[VK_MAX_EXTENSION_NAME_SIZE];
- uint32_t specVersion;
- } VkExtensionProperties;
extensionName 参数返回扩展名称的 C 字符串。specVersion 参数是扩展的规范版本号。
vkEnumerateInstanceExtensionProperties() 是 Vulkan 初始化阶段最常见的查询函数之一,用来获取当前系统上可用的实例扩展。它的函数原型为:
- VKAPI_ATTR VkResult VKAPI_CALL vkEnumerateInstanceExtensionProperties(
- const char* pLayerName,
- uint32_t* pPropertyCount,
- VkExtensionProperties* pProperties);
pLayerName 参数指定某个层支持的扩展。传 NULL 表示查询全局扩展。
pPropertyCount 参数返回扩展的数量。
pProperties 参数返回扩展信息的数组。
使用的话,先 pProperties 传 NULL,只获取扩展的数量。然后按数量分配数组大小,再设置获取扩展数据。
结合 vkEnumerateInstanceExtensionProperties(),可以对 glfwGetRequiredInstanceExtensions() 获取的扩展进行检查,增加程序健壮性。
vkCreateInstance() 函数是 Vulkan 程序的第一步、最核心的初始化函数之一。
它使用提供的配置(layers、extensions、AppInfo 等),创建一个 Vulkan 实例 VkInstance。这是任何 Vulkan 程序都会调用的第一个 Vulkan API,用来初始化 Vulkan 框架、激活扩展、启用验证层等。它的函数原型为:
- VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(
- const VkInstanceCreateInfo* pCreateInfo,
- const VkAllocationCallbacks* pAllocator,
- VkInstance* pInstance);
pCreateInfo 参数是创建实例的配置结构体,我们在 VkInstanceCreateInfo 里已经介绍过。
pAllocator 参数用于自定义内存分配器。一般情况下传 NULL,直接让 Vulkan 用默认内存分配策略。
pInstance 参数返回创建好的 VkInstance。
返回值为 VK_SUCCESS 表示创建成功。
vkDestroyInstance 函数用于销毁 Vulkan 实例并释放其相关的所有资源。它的函数原型为:
- VKAPI_ATTR void VKAPI_CALL vkDestroyInstance(
- VkInstance instance,
- const VkAllocationCallbacks* pAllocator);
instance 参数传入要销毁的 VkInstance 对象。
pAllocator 参数指定自定义内存分配器回调函数,没有就传 NULL。
三、 物理设备
vkEnumeratePhysicalDevices() 函数用于获取系统中可用的物理设备列表。它的函数原型为:
- VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(
- VkInstance instance,
- uint32_t* pPhysicalDeviceCount,
- VkPhysicalDevice* pPhysicalDevices);
instance 参数传入已创建的 VkInstance。
pPhysicalDeviceCount 参数输出物理设备的数量。pPhysicalDevices 参数输出 VkPhysicalDevice 的数组。
在创建逻辑设备之前,必须确定队列族支持哪些操作。VkQueueFamilyProperties 是 Vulkan 中用于描述队列族能力的核心结构。它的定义如下:
- typedef struct VkQueueFamilyProperties {
- VkQueueFlags queueFlags;
- uint32_t queueCount;
- uint32_t timestampValidBits;
- VkExtent3D minImageTransferGranularity;
- } VkQueueFamilyProperties;
queueFlags 参数表示该队列族支持哪些功能,常见位掩码:VK_QUEUE_GRAPHICS_BIT 支持图形渲染命令;VK_QUEUE_COMPUTE_BIT 支持计算着色器;VK_QUEUE_TRANSFER_BIT 支持纯传输操作;VK_QUEUE_SPARSE_BINDING_BIT 支持稀疏资源绑定;VK_QUEUE_PROTECTED_BIT 支持受保护的操作。
queueCount 参数表示该队列族中可创建多少条独立的队列。
timestampValidBits 参数表示该队列族支持的时间戳精度。
minImageTransferGranularity 参数表示图像传输时的最小传输粒度。
vkGetPhysicalDeviceQueueFamilyProperties() 函数用于查询一个物理设备支持的所有队列族的属性。它的函数原型为:
- VKAPI_ATTR void VKAPI_CALL vkGetPhysicalDeviceQueueFamilyProperties(
- VkPhysicalDevice physicalDevice,
- uint32_t* pQueueFamilyPropertyCount,
- VkQueueFamilyProperties* pQueueFamilyProperties);
physicalDevice 是由 vkEnumeratePhysicalDevices() 返回的一个物理设备句柄。
pQueueFamilyPropertyCount 参数输出队列族的数量。
pQueueFamilyProperties 参数输出数组,存放每个队列族的属性。
四、 逻辑设备
VkDeviceQueueCreateInfo 这个结构体用于 Vulkan 创建逻辑设备时,指定要从哪个队列族中创建多少条队列,以及这些队列的优先级。它的定义如下:
- typedef struct VkDeviceQueueCreateInfo {
- VkStructureType sType;
- const void* pNext;
- VkDeviceQueueCreateFlags flags;
- uint32_t queueFamilyIndex;
- uint32_t queueCount;
- const float* pQueuePriorities;
- } VkDeviceQueueCreateInfo;
sType 参数指定结构类型,必须是 VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO。
pNext 参数扩展用,通常填 NULL。
flags 参数大部分情况填 0。
queueFamilyIndex 参数指定要从哪个队列族创建队列。通常通过 vkGetPhysicalDeviceQueueFamilyProperties() 查到队列族数量,然后选择一个索引。
queueCount 参数指定从该队列族中创建多少条队列。
pQueuePriorities 指定每条队列的优先级,范围为 0.0 到 1.0。
VkDeviceCreateInfo 是创建 Vulkan 逻辑设备时最核心的配置结构体。它告诉 Vulkan,用这个物理设备创建一个逻辑设备,并启用哪些队列、扩展和特性。它的定义如下:
- typedef struct VkDeviceCreateInfo {
- VkStructureType sType;
- const void* pNext;
- VkDeviceCreateFlags flags;
- uint32_t queueCreateInfoCount;
- const VkDeviceQueueCreateInfo* pQueueCreateInfos;
- // enabledLayerCount is deprecated and should not be used
- uint32_t enabledLayerCount;
- // ppEnabledLayerNames is deprecated and should not be used
- const char* const* ppEnabledLayerNames;
- uint32_t enabledExtensionCount;
- const char* const* ppEnabledExtensionNames;
- const VkPhysicalDeviceFeatures* pEnabledFeatures;
- } VkDeviceCreateInfo;
sType 参数必须设置为 VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO。
pNext 参数为扩展链,通常为 NULL。
flags 参数,一般填 0。
queueCreateInfoCount 参数指定数组 pQueueCreateInfos 的数量。
pQueueCreateInfos 参数为 VkDeviceQueueCreateInfo 结构的数组,上面已经介绍过该结构体。
enabledLayerCount 和 ppEnabledLayerNames 在 Vulkan 1.0 后已被弃用。现在所有需要的 layer 都为 instance level。
enabledExtensionCount 参数指定设备扩展的数量;ppEnabledExtensionNames 参数指定要启用的扩展名称数组。
pEnabledFeatures 参数指定启用哪些物理设备特性。
vkCreateDevice() 函数用于创建逻辑设备 VkDevice。它从选定的物理设备的基础上创建一个逻辑设备,并初始化队列、扩展和特性等。它的函数原型为:
- VKAPI_ATTR VkResult VKAPI_CALL vkCreateDevice(
- VkPhysicalDevice physicalDevice,
- const VkDeviceCreateInfo* pCreateInfo,
- const VkAllocationCallbacks* pAllocator,
- VkDevice* pDevice);
physicalDevice 参数指定要基于哪个物理设备创建逻辑设备。
pCreateInfo 参数为 VkDeviceCreateInfo 类型,在上面已经介绍过。
pAllocator 参数一般填 NULL,自定义内存分配器才会用到。
pDevice 参数返回创建好的逻辑设备句柄。
vkGetDeviceQueue() 函数用来从已创建的逻辑设备 VkDevice 中取得队列 VkQueue 句柄。
Vulkan 的所有 GPU 操作都通过队列进行。这个函数不会创建队列,只是取出你在 vkCreateDevice() 时声明过的队列。它的函数原型为:
- VKAPI_ATTR void VKAPI_CALL vkGetDeviceQueue(
- VkDevice device,
- uint32_t queueFamilyIndex,
- uint32_t queueIndex,
- VkQueue* pQueue);
device 参数为已经创建好的逻辑设备。
queueFamilyIndex 参数指定队列族的索引。
queueIndex 参数指定该队列族中取第几个队列。
pQueue 参数输入队列句柄。