程序入口模块——main
xcb 窗口 框架
/* 管线布置创作,创作和描述符设置布局数 */
#define NUM_DESCRIPTOR_SETS 1
/* 等待命令缓冲区填充的时间上限 纳秒*/
#define FENCE_TIMEOUT 100000000
#include <cassert>
#include <cstdlib>
#include <xcb/xcb.h>
#include <iostream>
/*xcb窗体信息对象*/
struct WindowInfo {
#define APP_NAME_STR_LEN 80
char name[APP_NAME_STR_LEN];//窗体标题栏
xcb_window_t xcb_window;//windows窗体句柄
xcb_connection_t *xcb_connection;
int width, height;
};
xcb_window_t xcb_window;
xcb_intern_atom_reply_t *atom_wm_delete_window;
xcb_connection_t *xcb_connection;
xcb_screen_t *screen;
bool quit=false;
void init_connection(struct WindowInfo &info) {
const xcb_setup_t *setup;
xcb_screen_iterator_t iter;
int scr;
const char *display_envar = getenv("DISPLAY");
if (display_envar == nullptr || display_envar[0] == '\0') {
printf("Environment variable DISPLAY requires a valid value.\nExiting ...\n");
fflush(stdout);
exit(1);
}
xcb_connection = xcb_connect(nullptr, &scr);
if (xcb_connection_has_error(xcb_connection) > 0) {
printf(
"Cannot find a compatible Vulkan installable client driver "
"(ICD).\nExiting ...\n");
fflush(stdout);
exit(1);
}
setup = xcb_get_setup(xcb_connection);
iter = xcb_setup_roots_iterator(setup);
while (scr-- > 0) xcb_screen_next(&iter);
screen = iter.data;
}
void handle_xcb_event(const xcb_generic_event_t *event) {
uint8_t event_code = event->response_type & 0x7f;
switch (event_code) {
case XCB_EXPOSE:
// TODO: Resize window
break;
case XCB_CLIENT_MESSAGE:
break;
case XCB_KEY_RELEASE: {
const xcb_key_release_event_t *key = (const xcb_key_release_event_t *)event;
switch (key->detail) {
case 0x9: // Escape
break;
}
} break;
default:
break;
}
}
void run_xcb() {
xcb_flush(xcb_connection);
while (!quit) {
xcb_generic_event_t *event = xcb_poll_for_event(xcb_connection);
while (event) {
handle_xcb_event(event);
free(event);
event = xcb_poll_for_event(xcb_connection);
}
}
}
void create_xcb_window(struct WindowInfo &info, int32_t default_width, int32_t default_height) {
uint32_t value_mask, value_list[32];
info.width = default_width;
info.height = default_height;
assert(info.width > 0);
assert(info.height > 0);
xcb_window = xcb_generate_id(xcb_connection);
value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
value_list[0] = screen->black_pixel;
value_list[1] = XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
xcb_create_window(xcb_connection, XCB_COPY_FROM_PARENT, xcb_window, screen->root, 0, 0, info.width, info.height, 0,
XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, value_list);
/* Magic code that will send notification when window is destroyed */
xcb_intern_atom_cookie_t cookie = xcb_intern_atom(xcb_connection, 1, 12, "WM_PROTOCOLS");
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(xcb_connection, cookie, 0);
xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(xcb_connection, 0, 16, "WM_DELETE_WINDOW");
atom_wm_delete_window = xcb_intern_atom_reply(xcb_connection, cookie2, 0);
xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, xcb_window, (*reply).atom, 4, 32, 1, &(*atom_wm_delete_window).atom);
free(reply);
xcb_map_window(xcb_connection, xcb_window);
// Force the x/y coordinates to 100,100 results are identical in
// consecutive
// runs
const uint32_t coords[] = {100, 100};
xcb_configure_window(xcb_connection, xcb_window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, coords);
info.xcb_window = xcb_window;
info.xcb_connection = xcb_connection;
}
void destroy_xcb_window(){
xcb_destroy_window(xcb_connection, xcb_window);
xcb_disconnect(xcb_connection);
free(atom_wm_delete_window);
quit = true;
}
struct WindowInfo info;
int main(int argc, char **argv)
{
init_connection(info);
create_xcb_window(info, 1366, 768);
run_xcb();
return 0;
}
执行
xz@xiaqiu:~/study/vulkan/vulkan/xcb$
xz@xiaqiu:~/study/vulkan/vulkan/xcb$ g++ -o window window.cpp -lxcb
xz@xiaqiu:~/study/vulkan/vulkan/xcb$ ./window
表1-1 常用的Vulkan基本类型
名称 | Vulkan类型 | 说明 |
---|---|---|
实例 | VkInstance | 用于存储Vulkan程序相关状态的软件结构,可以在逻辑上区分不同的Vulkan应用程序或者同一应用程序内部不同的Vulkan上下文 |
物理设备 | VkPhysicalDevice | 对系统中GPU硬件的抽象,每个GPU对应于一个物理设备。另外,每个实例下可以有多个物理设备 |
设备 | VkDevice | 基于物理设备创建的逻辑设备,本质上是存储信息的软件结构,其中主要保留了与对应物理设备相关的资源。每个物理设备可以对应多个逻辑设备 |
命令池 | VkCommandPool | 服务于高效分配命令缓冲 |
命令缓冲 | VkCommandBuffer | 用于记录组成绘制或计算任务的各个命令,在命令池中分配。若执行的是不变的绘制命令,可以对记录了命令的命令缓冲进行重用 |
命令缓冲启动信息 | VkCommandBufferBeginInfo | 携带了命令缓冲启动时必要信息的对象 |
命令缓冲提交信息 | VkSubmitInfo | 携带了命令缓冲提交给队列执行时必要信息的对象,包括需要等待的信号量数量、等待的信号量列表、命令缓冲数量、命令缓冲列表、触发的信号量数量、触发的信号量列表等 |
队列家族属性 | VkQueueFamilyProperties | 携带了特定家族属性信息的软件结构,包括家族中的数量、能力标志等。每一个家族中可能含有多个能力相近的,常用的家族主要有支持图形任务和计算任务的两大类 |
队列 | VkQueue | 功能为接收提交的任务,将任务按序由所属GPU硬件依次执行 |
格式 | VkFormat | 个枚举类型,包含了Vulkan开发中用到的各种内存组织格式,如VK_FORMAT_R8G8B8A8_UNORM就表示支持RGBA四个色彩通道,每个通道8个数据比特 |
图像 | VkImage | 设备内存的一种使用模式,这种模式下对应的内存用于存储像素数据。其中存储的像素数据可能是来自于纹理图也可能是来自于绘制任务的结果等 |
图像视图 | VkImageView | 配合对象使用,其中携带了对应对象的类型、格式、色彩通道交换设置等方面的信息 |
交换链 | VkSwapchainKHR | 将画面呈现到特定目标平台(如Windows、Android、Linux等)窗体或表面的机制,通过它可以提供多个用于呈现的图像。这些与目标平台相关,可以看作目标平台呈现用KHR表面的抽象接口。持续换帧呈现时交替使用其中的多个执行,避免用户看到绘制过程中的画面引起画面撕裂。一般情况下,交换链中至少有两个用于呈现的,有些设备中数量会更多 |
帧缓冲 | VkFrameBuffer | 为绘制服务,其中可以包含颜色附件用于记录一帧画面中各个像素的颜色值)、深度附件(用于记录一帧画面中各个像素的深度值)、模板附件(用于记录一帧画面中各个像素的模板值)等 |
缓冲 | VkBuffer | 设备内存的一种使用模式,这种模式下对应的内存用于存储各种数据。比如:绘制用顶点信息数据、绘制用一致变量数据等 |
缓冲描述信息 | VkDescriptorBufferInfo | 携带了描述缓冲信息的结构体,包含对应缓冲、内存偏移量、范围等 |
渲染通道 | VkRenderPass | 其中包含了一次绘制任务需要的多方面信息,诸如颜色附件、深度附件情况,子通道列表、子通道相互依赖信息等,用于向驱动描述绘制工作的结构、过程。一般来说,每个渲染通道从开始到结束将产生一帧完成的画面 |
清除内容 | VkClearValue | 包含了每次绘制前清除帧缓冲所用数据的相关值,主要有清除用颜色值、深度值、模板值等 |
渲染通道启动信息 | VkRenderPassBeginInfo | 携带了启动渲染通道时所需的信息,包括对应的渲染通道、渲染区域的位置及尺寸、绘制前的清除数据值等 |
渲染子通道描述 | VkSubpassDescription | 一个渲染通道由多个子通道组成,至少需要一个子通道。每个子通道用一个VkSubpassDescription实例描述,其中包含了此子通道的输入附件、颜色附件、深度附件等方面的信息 |
描述集布局 | VkDescriptorSetLayout | 服务于描述集,给出布局接口。通俗讲就是给出着色器中包含了哪些一致变量、分别是什么类型、绑定编号是什么、对应于哪个管线阶段(比如顶点着色器、片元着色器)等 |
描述集 | VkDescriptorSet | 通过布局接口将所需资源和着色器连接起来,帮助着色器读入并理解资源中的数据,比如着色器中的采样器类型、一致变量缓冲等 |
写入描述集 | VkWriteDescriptorSet | 用于绘制前更新着色器所需的一致变量等 |
描述集池 | VkDescriptorPool | 用于高效地分配描述集 |
管线布局 | VkPipelineLayout | 描述管线整体布局,包括有哪些推送常量、有哪些描述集等 |
管线 | VkPipeline | 包含了执行指定绘制工作对应管线的各方面信息,诸如管线布局、顶点数据输入情况、图元组装设置、光栅化设置、混合设置、视口与剪裁设置、深度及模板测试设置、多重采样设置等 |
着色器阶段创建信息 | VkPipelineShaderStageCreateInfo | 携带了单个着色器阶段信息的对象,包括着色器的SPIR-V模块、着色器主方法名称、着色器对应阶段(比如顶点着色器、片元着色器、几何着色器、曲面细分着色器)等 |
顶点输入绑定描述 | VkVertexInputBindingDescription | 用于描述管线的顶点数据输入情况,包括绑定点编号、数据输入频率(比如每顶点一套数据)、数据间隔等 |
顶点输入属性描述 | VkVertexInputAttributeDescription | 描述顶点输入的某项数据信息(比如顶点位置、顶点颜色),包括绑定点编号、位置编号、数据格式、偏移量等 |
管线缓冲 | VkPipelineCache | 为高效地创建管线提供支持 |
格式属性 | VkFormatProperties | 用于存储指定格式类型(比如VK_FORMAT_D16_UNORM)的格式属性,包括线性瓦片特性标志、最优化瓦片特性标志、缓冲特性标志等 |
物理设备内存属性 | VkPhysicalDeviceMemoryProperties | 用于存储获取的基于指定GPU的设备内存属性,包括内存类型数量、内存类型列表、内存堆数量、内存堆列表等 |
设备内存 | VkDeviceMemory | 设备内存的逻辑抽象,前面提到的缓冲(VkBuffer)、图像(VkImage)都需要绑定设备内存才能正常工作 |
信号量 | VkSemaphore | 用于一个设备(GPU)内部相同或不同队列并发执行任务时的同步工作,一般与方法VkQueueSubmit配合使用,以确保通过VkQueueSubmit方法提交的任务在指定未触发前阻塞直至触发后才执行。要特别注意的是,若有多个提交的任务同时等待同一个触发,则此的触发仅仅会被一个等待的任务接收到,其他等待的任务还将继续等待。这里的“同步”指的是并发执行任务时解决冲突的一种策略,有兴趣的读者可以进一步查阅相关资料 |
栅栏 | VkFence | 用于主机和设备之间的同步,通俗地讲就是用于CPU和GPU并发执行任务时的同步 |
KHR表面 | VkSurfaceKHR | 此类对象服务于帧画面的呈现 |
KHR表面能力 | VkSurfaceCapabilitiesKHR | 携带了用于呈现画面的表面相关呈现能力的信息,比如画面尺寸范围、交换链中的图像数量、是否支持屏幕变换等 |
呈现信息 | VkPresentInfoKHR | 携带了执行呈现时所需的一些信息,包括需要等待的信号量数量、信号量列表、交换链的数量、交换链列表、此次呈现的图像在交换链中的索引等 |
MyVulkanManager:包含一些静态函数和方法
class MyVulkanManager
{
public:
//窗口辅助结构体
static struct WindowInfo info;
//vulkan绘制的循环标志
static bool loopDrawFlag;
static std::vector<const char *> instanceExtensionNames;//需要使用的实例扩展名称列表
static VkInstance instance;//Vulkan实例
static uint32_t gpuCount;//物理设备数量
static std::vector<VkPhysicalDevice> gpus; //物理设备列表
static uint32_t queueFamilyCount;//物理设备对应的队列家族数量
static std::vector<VkQueueFamilyProperties> queueFamilyprops;//物理设备对应的队列家族属性列表
static uint32_t queueGraphicsFamilyIndex;//支持图形工作的队列家族索引
static VkQueue queueGraphics;//支持图形工作的队列
static uint32_t queuePresentFamilyIndex;//支持显示工作的队列家族索引
static std::vector<const char *> deviceExtensionNames; //所需的设备扩展名称列表
static VkDevice device; //逻辑设备
static VkCommandPool cmdPool;//命令池
static VkCommandBuffer cmdBuffer;//命令缓冲
static VkCommandBufferBeginInfo cmd_buf_info;//命令缓冲启动信息
static VkCommandBuffer cmd_bufs[1]; //供提交执行的命令缓冲数组
static VkSubmitInfo submit_info[1]; //命令缓冲提交执行信息数组
static uint32_t screenWidth;//屏幕宽度
static uint32_t screenHeight;//屏幕高度
static VkSurfaceKHR surface;//KHR表面
static std::vector<VkFormat> formats;//KHR表面支持的格式
static VkSurfaceCapabilitiesKHR surfCapabilities;//表面的能力
static uint32_t presentModeCount;//显示模式数量
static std::vector<VkPresentModeKHR> presentModes;//显示模式列表
static VkExtent2D swapchainExtent;//交换链尺寸
static VkSwapchainKHR swapChain;//交换链
static uint32_t swapchainImageCount;//交换链中的图像数量
static std::vector<VkImage> swapchainImages;//交换链中的图像列表
static std::vector<VkImageView> swapchainImageViews;//交换链对应的的图像视图列表
static VkFormat depthFormat;//深度图像格式
static VkFormatProperties depthFormatProps; //物理设备支持的深度格式属性
static VkImage depthImage;//深度缓冲图像
static VkPhysicalDeviceMemoryProperties memoryroperties;//物理设备内存属性
static VkDeviceMemory memDepth; //深度缓冲图像对应的内存
static VkImageView depthImageView;//深度缓冲图像视图
static VkSemaphore imageAcquiredSemaphore;//渲染目标图像获取完成信号量
static uint32_t currentBuffer;//从交换链中获取的当前渲染用图像对应的缓冲编号
static VkRenderPass renderPass;//渲染通道
static VkClearValue clear_values[2];//渲染通道用清除帧缓冲深度、颜色附件的数据
static VkRenderPassBeginInfo rp_begin;//渲染通道启动信息
static VkFence taskFinishFence;//等待任务完毕的栅栏
static VkPresentInfoKHR present;//呈现信息
static VkFramebuffer* framebuffers;//帧缓冲序列首指针
static ShaderQueueSuit_Common* sqsCL;//着色器管线指针
static DrawableObjectCommonLight* triForDraw;//绘制用三色三角形物体对象指针
//三角形旋转角度
static float xAngle;
static float yAngle;
static float zAngle;
//一般来说,完整的Vulkan图形应用程序包含创建Vulkan实例、获取物理设备列表创建逻辑设备、创建命令缓冲、创建渲染通道、创建帧缓冲、创建绘制用的物体、初始化渲染管线、创建栅栏和初始化呈现信息、初始化基本变换矩阵、摄像机矩阵和投影矩阵、执行绘制、销毁相关对象等模块,具体内容如下。
static void init_vulkan_instance();//创建Vulkan实例
static void enumerate_vulkan_phy_devices();//初始化物理设备
static void create_vulkan_devices();//创建逻辑设备
static void create_vulkan_CommandBuffer();//创建命令缓冲
static void create_vulkan_swapChain();//初始化交换链
static void create_vulkan_DepthBuffer();//创建深度缓冲相关
static void create_render_pass();//创建渲染通道
static void init_queue();//获取设备中支持图形工作的队列
static void create_frame_buffer();//创建帧缓冲
static void createDrawableObject();//创建绘制用物体
static void drawObject();//执行场景中的物体绘制
static void doVulkan();//启动线程执行Vulkan任务
static void initPipeline();//初始化管线
static void createFence();//创建栅栏
static void initPresentInfo();//初始化显示信息
static void initMatrix();//初始化矩阵
static void flushUniformBuffer();//将一致变量数据送入缓冲
static void flushTexToDesSet();//将纹理等数据与描述集关联
static void destroyFence();//销毁栅栏
static void destroyPipeline();//销毁管线
static void destroyDrawableObject();//销毁绘制用物体
static void destroy_frame_buffer();//销毁帧缓冲
static void destroy_render_pass();//销毁渲染通道
static void destroy_vulkan_DepthBuffer();//销毁深度缓冲相关
static void destroy_vulkan_swapChain();//销毁交换链
static void destroy_vulkan_CommandBuffer();//销毁命令缓冲
static void destroy_vulkan_devices();//销毁逻辑设备
static void destroy_vulkan_instance();//销毁实例
};
PathData.cpp 主要是访问着色器的代码
#ifndef PathData_H
#define PathData_H
#define VertShaderPath "../shaders/vertshadertext.vert.inc";
#define FragShaderPath "../shaders/fragshadertext.frag.inc";
#endif
std::vector<unsigned int> vtx_spv =
{
#include VertShaderPath
};
std::vector<unsigned int> frag_spv =
{
#include FragShaderPath
};
,Vulkan没有指定官方的着色器编程语言,而是采用SPIR-V二进制中间格式来进行表示。但对于开发人员来说,不大可能直接使用SPIR-V进行开发,一般都需要基于某种着色器编程语言开发着色器然后再编译为SPIR-V格式。本书案例中的着色器都选用了GLSL着色器编程语言进行开发,这对于熟悉或了解一些OpenGL的开发人员来说应该是最好的选择了。
1.首先介绍的是顶点着色器,其每顶点执行一次。本节案例的顶点着色器主要包括计算顶点的最终绘制位置以及将顶点颜色传递给片元着色器,具体代码如下。
//第1行给定了所用GLSL(OpenGL Shading Language——OpenGL着色语言)的版本,不同版本GLSL支持的特性和功能不尽相同。
#version 400
//开启了GL_ARB_separate_shader_objects和GL_ARB_shading_language_420pack扩展,在Vulkan中如果想使用着色器进行开发,一般需要开启这两个扩展。
#extension GL_ARB_separate_shader_objects : enable//启动GL_ARB_separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//启动GL_ARB_shading_language_420pack
//声明了一致块myBufferVals,其中包含用于接收总变换矩阵数据的成员mvp,同时指定了此一致块的绑定点编号为0。
layout (std140,set = 0, binding = 0) uniform bufferVals
{//一致块
mat4 mvp;//总变换矩阵
} myBufferVals;
layout (location = 0) in vec3 pos;//传入的物体坐标系顶点位置
layout (location = 1) in vec3 color;//传入的顶点颜色
layout (location = 0) out vec3 vcolor;//传到片元着色器的顶点颜色
//定义了输出接口块gl_PerVertex,其中包含内建输出变量gl_Position,此内建变量负责接收最终的顶点位置并传递到渲染管线进行后继处理。
out gl_PerVertex
{//输出的接口块
vec4 gl_Position;//顶点最终位置
};
void main()
{//主函数
//为顶点着色器的主方法,首先将最终变换矩阵与物体坐标系下的顶点坐标相乘,得到顶点最终位置,然后将计算得到的顶点最终位置传递给内建变量gl_Position。最后,将传入到顶点着色器的顶点颜色数据传递给片元着色器。
gl_Position = myBufferVals.mvp * vec4(pos,1.0);//计算最终顶点位置
vcolor=color;//传递顶点颜色给片元着色器
}
片元着色器
#version 400 着色器版本号
#extension GL_ARB_separate_shader_objects : enable//启动GL_ARB_separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//启动GL_ARB_shading_language_420pack
//顶点着色器传入的顶点颜色数据以及输出到渲染管线的片元颜色值。
layout (location = 0) in vec3 vcolor;//顶点着色器传入的顶点颜色数据
layout (location = 0) out vec4 outColor;//输出到渲染管线的片元颜色值
void main()
{
//一个分量代表4个色彩通道(RGBA)中的A通道
outColor=vec4(vcolor.rgb,1.0);//将顶点着色器传递过来的颜色值输出
}