-
顺序很重要 :
#define必须在#include <GLFW/glfw3.h>之前出现,否则不起作用。 -
作用 :当 GLFW 的头文件看到这个宏被定义后,它就会知道你需要 Vulkan 支持,并自动执行
#include <vulkan/vulkan.h>,你就不需要再重复包含了。
问题:只是为了添加#include <vulkan/vulkan.h>我直接写不也差不多?
答:当你定义了 GLFW_INCLUDE_VULKAN,GLFW 不只是帮你自动 #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_API,GLFW 可能会默认尝试去创建 OpenGL 上下文,导致不必要的资源浪费甚至潜在的兼容问题。
**问题:**glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE)是什么意思?
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE) 的意思是:在创建窗口时,禁止用户通过拖拽窗口边缘来调整窗口的大小。
这行代码具体做了两件事:
-
glfwWindowHint:这是你之前了解过的函数,用于在创建窗口前预设属性。 -
GLFW_RESIZABLE, GLFW_FALSE:这是一个属性键值对。GLFW_RESIZABLE表示要设置"是否可调整大小"这个属性,GLFW_FALSE则明确指定为"不允许"。
glfwCreateWindow
GLFWwindow* 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 库,为后续所有窗口和输入操作做准备。
函数原型:
c
int glfwInit(void);
返回值:
-
返回
GLFW_TRUE(非零):初始化成功。 -
返回
GLFW_FALSE(零):初始化失败,后续所有 GLFW 函数都不能使用。
它内部做了什么?
简单说,它负责向操作系统"打招呼",建立起 GLFW 与系统底层之间的联系。具体包括:
-
初始化后端:根据编译时的平台(Windows、Linux、macOS),加载对应的系统服务。
-
设置输入处理:准备好键盘、鼠标、手柄等输入设备的监听机制。
-
配置图形 API:检测系统支持的 OpenGL / Vulkan 能力,但不会创建任何窗口或上下文。
-
分配内部数据结构:为后续的窗口管理、事件队列等分配必要的内存。
它们之间有一条隐形的线连着 。这个连接点就是 GLFW_CLIENT_API 这个选项。
我们回想一下,在创建窗口前,你明确设定了:
c
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
这行代码就是关键。它告诉 GLFW:"我要创建的窗口,不需要你现在就为我建立图形上下文,因为稍后我会自己用一个图形 API(Vulkan)来接管它。"
如果把这个行为拆开来看,过程是这样的:
-
创建窗口 (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 "C"(C++ 环境下)。这要求编译器按 C 语言的规则处理函数名,而不是 C++ 那种带有参数类型信息的复杂修饰名。 -
必要性 :正是因为
VKAPI_CALL,你才能用GetProcAddress通过名字"vkCreateInstance"找到这个函数,否则函数名会变成类似?vkCreateInstance@@YA...这样的乱码。