Vulkan学习笔记
顺序很重要#define必须在#include GLFW/glfw3.h之前出现否则不起作用。作用当 GLFW 的头文件看到这个宏被定义后它就会知道你需要 Vulkan 支持并自动执行#include vulkan/vulkan.h你就不需要再重复包含了。问题只是为了添加#include vulkan/vulkan.h我直接写不也差不多答当你定义了GLFW_INCLUDE_VULKANGLFW 不只是帮你自动#include了 Vulkan 头文件它实际上接管了 Vulkan 头文件的包含策略。GLFW 内部会根据你使用的平台Windows, Linux, macOS自动选择一个合适的 Vulkan 头文件包含方式。它可能会做一些细微的处理来确保 GLFW 自身的 Vulkan 函数声明和 Vulkan SDK 的头文件完美兼容避免某些平台下的头文件包含顺序问题或宏冲突。glfwWindowHint的作用glfwWindowHint函数的作用是在创建窗口之前向 GLFW 预设一系列窗口属性。你可以把它理解成填写一份“订单”告诉系统你想要一个什么样的窗口然后glfwCreateWindow会按照这份订单来“生产”窗口。在你调用glfwCreateWindow之前明确告诉 GLFW这个窗口将来是为 Vulkan、OpenGL 还是 OpenGL ES 准备的。根据你的设定GLFW 会在创建窗口实例时加载对应的上下文或做好相关准备。具体用法如下可选的参数值有三个GLFW_NO_API这是你用 Vulkan 时必须设置的。它告诉 GLFW 这个窗口不建立任何 OpenGL/ES 上下文。glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);因为 Vulkan 完全自己管理上下文不需要 GLFW 帮忙所以用这个值来明确“不用 OpenGL”。GLFW_OPENGL_API创建标准桌面 OpenGL 窗口。GLFW_OPENGL_ES_API创建 OpenGL ES 窗口常用于移动平台或轻量级嵌入式设备。在用 Vulkan 时如果你忘了设成GLFW_NO_APIGLFW 可能会默认尝试去创建 OpenGL 上下文导致不必要的资源浪费甚至潜在的兼容问题。问题glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE)是什么意思glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE)的意思是在创建窗口时禁止用户通过拖拽窗口边缘来调整窗口的大小。这行代码具体做了两件事glfwWindowHint这是你之前了解过的函数用于在创建窗口前预设属性。GLFW_RESIZABLE, GLFW_FALSE这是一个属性键值对。GLFW_RESIZABLE表示要设置是否可调整大小这个属性GLFW_FALSE则明确指定为不允许。glfwCreateWindowGLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);width和height窗口的宽和高单位是像素。title窗口标题一个字符串。比如My Vulkan App。monitor用于全屏模式。传NULL创建窗口模式。传一个GLFWmonitor*创建全屏独占模式窗口会铺满你指定的那个显示器并自动采用它的分辨率和刷新率。share用于共享资源。传另一个窗口的指针可以让新窗口和它共享 OpenGL 的纹理、缓冲等资源。在 Vulkan 中很少用一般传NULL。GLFWmonitor* primaryMonitor glfwGetPrimaryMonitor(); if (!primaryMonitor) { fprintf(stderr, Failed to find primary monitor\n); glfwTerminate(); return -1; } // 注意monitor 参数传了 primaryMonitor窗口会自动铺满整个屏幕 GLFWwindow* window glfwCreateWindow( mode-width, // 使用显示器的原生宽度 mode-height, // 使用显示器的原生高度 Vulkan Fullscreen, // 标题全屏下通常看不到 primaryMonitor, // 这就是全屏的关键参数 NULL // 不共享资源 );glfwPollEvents是你窗口事件循环的核心动力。没有它你的窗口就会卡死像个没有反应的照片。它的主要作用就一个强制 GLFW 去处理所有排队等候的事件然后立即返回。事件包含操作系统发给窗口的各种通知用户输入键盘按下、鼠标点击、鼠标移动、手柄摇杆。窗口状态窗口被拖动、大小被改变、被最小化、被关闭点那个 X 按钮。其他显示器连接变化、文件拖放等。绿色部分为不可编程模块紫色为可编程模块layout在.frag文件里的作用主要是用来硬性指定输出的去向每个颜色值分别要写到哪里和声明资源的内存布局确保数据在不同的处理器之间能被精确无误地读取。// 顶点着色器中顶点的位置数据来自第 0 号插槽颜色数据来自第 1 号插槽 layout(location 0) in vec3 inPosition; layout(location 1) in vec3 inColor; // 片元着色器中将第一个输出颜色写入第 0 号颜色附件第二个写入第 1 号 layout(location 0) out vec4 outColor; // 一般最终颜色 layout(location 1) out vec4 outNormal; // 例如延迟渲染的 G-Buffer 法线问题顶点着色器layout out的变量location为0我可以在片元着色器中用layout in locaiont 0 把顶点着色器的值带到片元着色器答完全正确你的理解非常精准。lfwInit是使用 GLFW 库时的第一个必须调用的函数作用是初始化整个 GLFW 库为后续所有窗口和输入操作做准备。函数原型cint glfwInit(void);返回值返回GLFW_TRUE非零初始化成功。返回GLFW_FALSE零初始化失败后续所有 GLFW 函数都不能使用。它内部做了什么简单说它负责向操作系统“打招呼”建立起 GLFW 与系统底层之间的联系。具体包括初始化后端根据编译时的平台Windows、Linux、macOS加载对应的系统服务。设置输入处理准备好键盘、鼠标、手柄等输入设备的监听机制。配置图形 API检测系统支持的 OpenGL / Vulkan 能力但不会创建任何窗口或上下文。分配内部数据结构为后续的窗口管理、事件队列等分配必要的内存。它们之间有一条隐形的线连着。这个连接点就是GLFW_CLIENT_API这个选项。我们回想一下在创建窗口前你明确设定了cglfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);这行代码就是关键。它告诉 GLFW“我要创建的窗口不需要你现在就为我建立图形上下文因为稍后我会自己用一个图形 APIVulkan来接管它。”如果把这个行为拆开来看过程是这样的创建窗口 (GLFW 的工作)glfwCreateWindow执行后GLFW 向操作系统申请了一个原生的空白窗口。此时这个窗口与 Vulkan 还没有发生任何关系只是一个普通的系统窗口。建立连接 (你的工作)为了让 Vulkan 能在这个窗口上画画你需要调用另一个函数glfwCreateWindowSurface。就是在这里VkInstance被用上了。VKAPI_ATTR—— 函数属性宏它控制函数的可见性和调用约定属性。在 Windows 上它通常定义为__stdcall这决定了函数调用时参数入栈和清理的方式。Vulkan 运行时是用__stdcall编译的你的程序也必须用相同约定来调用否则会堆栈错误。在 Linux / Android 等平台上它通常定义为空因为这些平台有统一的默认调用约定。对于动态库导出当编译 Vulkan 加载器这类库时它还会包含__declspec(dllexport)或__attribute__((visibility(default)))用来将函数导出。简单来说VKAPI_ATTR就是告诉编译器“这个函数需要按 Vulkan 规定的方式被找到和调用”。VKAPI_CALL—— 函数名修饰宏它主要处理 C 和 C 之间的名称问题。核心作用定义为extern CC 环境下。这要求编译器按 C 语言的规则处理函数名而不是 C 那种带有参数类型信息的复杂修饰名。必要性正是因为VKAPI_CALL你才能用GetProcAddress通过名字vkCreateInstance找到这个函数否则函数名会变成类似?vkCreateInstanceYA...这样的乱码。在 Vulkan 中物理设备代表系统中一个完整的、独立的硬件实现通常是GPU显卡但也可以是某种硬件加速器。它的核心作用是作为硬件能力的抽象层让程序能够查询硬件属性与能力程序可以通过物理设备获取详细参数比如显存大小、支持的特性几何着色器、多重采样、光线追踪等、内存类型与堆、支持的队列家族图形、计算、传输等。选择合适的硬件一个系统可能有多个物理设备例如独立显卡集成显卡。vkEnumeratePhysicalDevices可以列出所有设备你可以根据硬件特性选择最适合的比如优先选有独立显存的GPU。创建逻辑设备物理设备本身不能直接使用。你必须基于它创建逻辑设备逻辑设备才是真正用于提交命令、分配内存、创建图像/缓冲区的“接口”。vkEnumeratePhysicalDevices(instance, deviceCount, nullptr);这段代码实际做的是查询系统中所有能被当前 Vulkan 实例访问的物理设备数量而不是检查它们是否适用。具体解释这段代码的实际作用第一次调用vkEnumeratePhysicalDevices(instance, deviceCount, nullptr)仅仅获取物理设备的数量存入deviceCountDevice的Properties和Features的区别VkPhysicalDeviceProperties-硬件属性不可变的规格存储设备的静态硬件规格类似显卡的身份证信息一旦生产就固定cppVkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(device, props); // 包含的信息示例 props.deviceName; // NVIDIA GeForce RTX 3080 props.vendorID; // 0x10DE (NVIDIA) props.deviceID; // 0x2206 props.driverVersion; // 驱动版本号 props.apiVersion; // 支持的Vulkan版本 // 硬件限制关键 props.limits.maxImageDimension2D; // 最大纹理尺寸: 16384 props.limits.maxUniformBufferRange; // 最大Uniform Buffer大小 props.limits.maxPushConstantsSize; // Push Constants最大字节数 props.limits.maxBoundDescriptorSets; // 最大描述符集数量 props.limits.maxColorAttachments; // 最大颜色附件数 // 其他 props.deviceType; // 独立显卡/集成显卡/CPU等 props.pipelineCacheUUID; // 设备唯一标识cppVkPhysicalDeviceFeatures features; vkGetPhysicalDeviceFeatures(device, features); // 包含的信息示例 features.geometryShader; // 是否支持几何着色器 features.tessellationShader; // 是否支持曲面细分 features.multiDrawIndirect; // 是否支持间接多绘制 features.fillModeNonSolid; // 是否支持线框/点填充模式 features.wideLines; // 是否支持宽线条 features.samplerAnisotropy; // 是否支持各向异性过滤 features.textureCompressionBC; // 是否支持BC纹理压缩 features.dualSrcBlend; // 是否支持双源混合 features.logicOp; // 是否支持逻辑操作队列族是物理设备上一组功能相同的队列的集合。简单理解GPU 内部有不同的工位每个工位只能做特定类型的工作。标志位含义典型用途VK_QUEUE_GRAPHICS_BIT图形队列执行绘制命令vkCmdDraw*、图形管线VK_QUEUE_COMPUTE_BIT计算队列执行计算着色器、通用计算GPGPUVK_QUEUE_TRANSFER_BIT传输队列数据拷贝缓冲区/图像之间VK_QUEUE_SPARSE_BINDING_BIT稀疏绑定队列管理稀疏资源内存VK_QUEUE_PROTECTED_BIT保护队列处理加密内容第一步查询数量cppvkGetPhysicalDeviceQueueFamilyProperties(c_device, queueFamiliesCount, nullptr);传入nullptr作为第三个参数Vulkan 会只计算有多少个队列族并写入queueFamiliesCount此时你知道了需要分配多大的存储空间第二步获取数据cppstd::vectorVkQueueFamilyProperties queueFamilies(queueFamiliesCount); vkGetPhysicalDeviceQueueFamilyProperties(c_device, queueFamiliesCount, queueFamilies.data());先创建足够大的 vectorqueueFamilies(queueFamiliesCount)再次调用函数这次传入queueFamilies.data()非空指针Vulkan 会填充所有队列族的详细信息到 vector 中逻辑设备是 Vulkan 中与物理设备交互的接口。如果说物理设备代表真实的硬件GPU那么逻辑设备就是你与这块硬件建立的“会话连接”通过它来提交命令、分配资源。核心概念对比概念本质类比物理设备真实的 GPU 硬件一台电脑主机逻辑设备与硬件的会话/接口你登录电脑的用户账号就像你登录电脑后才能使用它的功能一样你必须创建逻辑设备才能使用 GPU 的功能问图形队列有什么用VkQueue graphicsQueue这个VkQueue就是图形队列图形队列是用于执行所有 GPU 渲染命令的通道。简单说你想在屏幕上画任何东西都必须通过图形队列提交命令。