通俗地说,渲染图像就是为图像的每个像素选择合适的颜色。例如,如果图像的分辨率为1920x1080,表示图像中有1920x1080个像素,渲染的过程,就是为每个位置的像素计算出合适的颜色。具体来说,假设每种颜色具有RGBA四个通道,且每个通道用1个字节表示(可以表示255种不同的情况),那么图像应当占据1920x1080x4个字节的内存。

渲染完一幅图片后,就可以将其显示到窗口上。从而,整个工作流程可以描述为:

渲染图像 → 显示图像 → 渲染图像 → 显示图像 → …… 

然而,渲染需要将颜色写入内存,而显示需要从内存中读取数据。也就是说,当内存被用于显示图像时,渲染应当停止工作。显而易见的是,这样并没有充分利用资源。

这个问题很容易解决:只要使用两块或更多的内存。例如,有两块内存A和B,渲染的结果写到内存A中,显示从内存B读取数据;在下一次,交换A和B,即显示从内存A读取数据,渲染的结果写到内存B中……之后反复交换A和B。这也是交换链中“交换”一词的由来。

本文将要介绍的内容位于“地图”第三层的右上方:

1. 创建交换链前的准备

交换链用于管理多个图像对象,在创建交换链前需要查询物理设备对交换链的支持程度:例如交换链中可以创建多少张图像、图像的最大尺寸与格式等。为此,定义函数createSwapChain,本节的代码都写在这个函数中。

首先查询物理设备对图像大小与数量的支持:

VkSurfaceCapabilitiesKHR surfaceCapabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &surfaceCapabilities);

查询需要物理设备mPhysicalDevice和表面mSurface作为参数,查询结果保存在结构体VkSurfaceCapabilitiesKHR中。目前,我们需要此结构体的以下成员:

minImageCount, maxImageCount: 交换链中的图像数必须在minImageCount ~ maxImageCount内;

currentImageExtent: 一个VkExtent2D对象,VkExtent2D只有两个成员widthheight, 所以它表示什么就不用我多说了吧;

minImageExtent, maxImageExtent: 同样为VkExtent2D对象,如果表面支持改变大小,它们会被依次设置为表面的最小和最大尺寸。

supportedTransforms, currentTransforms: 是一个VkSurfaceTransformFlagBitsKHR对象。有的表面可以在展示图像前对图像加以变换,例如翻转图像等等,如果物理设备支持某项功能,supportedTransforms相应的位会被置为1. currentTransforms表示当前使用的变换类型。

接下来,查询物理设备对图像格式的支持:

uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, mSurface, &formatCount, nullptr);
vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, mSurface, &formatCount, surfaceFormats.data());

从前面的文章一直看过来的小伙伴们,想必不用我再过多解释上面这段代码的含义了吧,这段代码返回一个VkSurfaceFormatKHR类型的数组。VkSurfaceFormatKHR结构体仅有两个成员:

format: 像素在内存中的保存格式,例如VK_FORMAT_R8G8B8_UINT(无符号的RGB格式)、VK_FORMAT_R8G8B8_SINT(有符号的RGB格式);

colorSpace: 支持的颜色空间,目前只有VK_COLORSPACE_SRGB_NONLINEAR_KHR这一个取值。

2. 创建交换链对象

查找好物理设备支持的参数后,就可以创建一个交换链对象啦。不过在此之前,首先设置交换链图像的数目。前面提到过,交换链的图像数量在minImageCount ~ maxImageCount内,这里设置图像的数量为min(minImageCount + 1, maxImageCount), 代码如下:

int imageCount = surfaceCapabilities.minImageCount + 1 <= surfaceCapabilities.maxImageCount ?
surfaceCapabilities.minImageCount + 1 : surfaceCapabilities.maxImageCount; // 设置图像的数量

为什么minImageCount要+1?

在博主的物理设备中,支持的图像数量为2~64. 如果设置为2,当渲染完成而显示仍未结束时,渲染将不得不等待显示的完成。因此,只要物理设备支持,图像数量至少设置为3.

之后,填充结构体VkSwapchainCreateInfoKHR:

VkSwapchainCreateInfoKHR swapchainCreateInfo{
VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, // .sType
nullptr, // .pNext
0, // .flags
mSurface, // .surface
imageCount, // .minImageCount
surfaceFormats[0].format, // .imageFormat
surfaceFormats[0].colorSpace, // .imageColorSpace
surfaceCapabilities.currentExtent, // imageExtent
1, // .imageArrayLayers
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, // .imageUsage
VK_SHARING_MODE_EXCLUSIVE, // .imageSharingMode
0, // .queueFamilyIndexCount
nullptr, // .pQueueFamilyIndices
surfaceCapabilities.currentTransform, // .preTransform
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, // .compositeAlpha
VK_PRESENT_MODE_FIFO_KHR, // .presentMode
VK_TRUE, // .clipped
VK_NULL_HANDLE, // .oldSwapChain
};

其中:

surface:之前创建的表面,这里设置为mSurface;

minImageCount: 交换链中的图像数量,这里设置为刚才计算出的值imageCount;

imageFormat, imageColorSpace: 支持的格式,此处使用查询到的格式数组的第一个元素;

imageExtent: 图像大小,此处设置为查询到的值;

imageArrayLayers: 每张图像可以有多层,暂时设置为1,只使用一层;

imageUsage: 暂时不用管它,按照上面的设置即可;

imageSharingMode, queueFamilyIndexCount, pQueueFamilyIndices: 这三个成员与之前多次提到的、图形队列与显示队列有关。如果二者不是同一个队列,就会涉及到图像在不同队列见的共享问题,此时需要告诉交换链两个队列所在队列族的索引;

preTransform:图像在展示给用户前所作的变换,此处设置为查询到的currentTransform;

compositeAlpha: 如何处理透明通道,此处暂时忽略透明通道;

presentMode: 图像如何与窗口同步,例如显示到窗口的速率,此处设置为VK_PRESENT_MODE_FIFO_KHR,图像存储在一个队列中,并依次向用户展示;

clipped, oldSwapChain: 暂时不管它们。

上面的设置有一个问题:imageSharingMode, queueFamilyIndexCountpQueueFamilyIndices的取值假设图形队列和显示队列是同一个队列,如果它们不是同一个队列,那么就应该另外设置这些成员的值:

if (mGraphicsQueueFamilyIndex != mPresentQueueFamilyIndex) {
swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchainCreateInfo.queueFamilyIndexCount = 2;
uint32_t queueFamilyIndices[] = {
mGraphicsQueueFamilyIndex, mPresentQueueFamilyIndex
};
swapchainCreateInfo.pQueueFamilyIndices = queueFamilyIndices;
}

一切准备就绪后,就可以创建交换链对象了。首先在VulkanApp类中定义交换链成员:

VkSwapchainKHR mSwapChain;

之后调用函数vkCreateSwapchainKHR创建它:

if_fail(
vkCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapChain),
"failed to create swapchain!"
);
Log("create swapchain successfully");

此函数中,第一个参数为之前创建的设备(之后创建的许多对象都需要设备作为第一个参数);第二个参数为描述交换链信息的结构体;第三个参数与交换链的内存分配有关,一律设置为空指针;最后一个参数返回创建好的交换链对象。

最后,不要忘记在析构函数中销毁此对象:

vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr);

创建交换链时需要设备,销毁它时同样也需要传入设备。

3. 获取图像

很可惜,到目前创建交换链的过程还没有结束,我们还需要获取交换链创建好的图像,就像从设备对象中获取命令队列那样,幸好这个过程也很简单。

首先在VulkanApp类中定义成员变量:

vector<VkImage> mSwapChainImages;  // 交换链的图像

接下来通过函数vkGetSwapchainImagesKHR获取图像:

uint32_t swapChainImagesCount = 0;
vkGetSwapchainImagesKHR(mDevice, mSwapChain, &swapChainImagesCount, nullptr);
mSwapChainImages.resize(swapChainImagesCount);
vkGetSwapchainImagesKHR(mDevice, mSwapChain, &swapChainImagesCount, mSwapChainImages.data());

此处我们已经知道了交换链中图像的数量,所以第一次查询图像数量的操作完全可以不写,这里为了追求代码的一致性,多写了一步。

4. 创建图像视图

很可惜,到这里仍然没有结束,还需要为获取到的每一张图像创建相应的图像视图。

什么是视图?

视图这个概念很常见,例如在数据库甚至是numpy中都有类似的概念。在Vulkan中,图像(VkImage)描述了底层的数据,而图像的视图(VkImageView)描述了对图像的操纵方式,可以为一张图像创建多个视图。

首先,在VulkanApp类中添加相应的成员:

vector<VkImageView> mSwapChainImageViews;

接下来,为每一个图像创建对应的图像视图:

mSwapChainImageViews.resize(mSwapChainImages.size());
for (size_t i = 0; i < mSwapChainImages.size(); i++) {
VkImageViewCreateInfo imageViewCreateInfo{
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
mSwapChainImages[i], // .image
VK_IMAGE_VIEW_TYPE_2D, // .viewType
surfaceFormats[0].format, // .format
{
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
}, // .components
{
VK_IMAGE_ASPECT_COLOR_BIT, // .aspectMask
0, // .baseMipLevel
1, // .levelCount
0, // .baseArrayLayer
1, // .layerCount
}, // .subresourceRange
}; if_fail(
vkCreateImageView(mDevice, &imageViewCreateInfo, nullptr, &mSwapChainImageViews[i]),
"failed to create image views!"
);
}

由于目前不涉及内存相关的内容,因此暂时不去解释结构体VkImageViewCreateInfo中各成员的含义。

同样地,记得在析构函数中销毁这些视图:

for (auto swapChainImageView : mSwapChainImageViews) {
vkDestroyImageView(mDevice, swapChainImageView, nullptr);
}

图像(VkImage)不需要销毁,它们由交换链创建,亦由交换链负责销毁。与之类似的还有队列(VkQueue),它们由设备创建,无需我们手动销毁。

5. 目前为止的完整代码

这一次,我们创建了交换链对象,然而这并不是终点。由于窗口的大小可以被改变,一旦大小发生变换,这里的许多设置都需要作出相应的改变,例如调整底层图像内存的大小等。因此,当时机成熟时,我们还会再回过头来,调整这里的代码。

下一次介绍完整的渲染流程,你将看到代码量的井喷式增长。

到目前为止的完整代码
 #define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h> #include <iostream>
#include <vector>
using std::vector;
#include <cstring> #define Log(message) std::cout << "[INFO] " << message << std::endl
#define Error(message) std::cerr << "[ERROR] " << message << std::endl; exit(-1) static void if_fail(VkResult result, const char* message); class VulkanApp {
public:
VulkanApp() {
glfwInit(); // 初始化glfw库
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // 禁用OpenGL相关的API
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); // 禁止调整窗口大小 createInstance();
createSurface(); selectPhysicalDevice();
createDevice(); createSwapChain();
} ~VulkanApp() {
for (auto swapChainImageView : mSwapChainImageViews) {
vkDestroyImageView(mDevice, swapChainImageView, nullptr);
}
vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr);
vkDestroyDevice(mDevice, nullptr); vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
vkDestroyInstance(mInstance, nullptr); glfwDestroyWindow(mWindow);
glfwTerminate();
} void Run() {
while (!glfwWindowShouldClose(mWindow)) {
glfwPollEvents();
}
} private:
const vector<const char*> mRequiredLayers = {
"VK_LAYER_KHRONOS_validation"
};
const vector<const char*> mRequiredExtensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, // 等价于字符串"VK_KHR_swapchain"
};
VkInstance mInstance; // 实例
VkPhysicalDevice mPhysicalDevice; // 物理设备
int mGraphicsQueueFamilyIndex = -1; // 支持图形功能的队列族索引
int mPresentQueueFamilyIndex = -1; // 支持显示功能的队列族索引 int mWidth = 800; // 窗口宽度
int mHeight = 600; // 窗口高度
GLFWwindow* mWindow = nullptr; // glfw窗口指针
VkSurfaceKHR mSurface; // 表面
VkSwapchainKHR mSwapChain; // 交换链
vector<VkImage> mSwapChainImages; // 交换链的图像
vector<VkImageView> mSwapChainImageViews; // 交换链的图像视图 VkDevice mDevice; // (逻辑)设备
VkQueue mGraphicsQueue; // 支持图形的队列
VkQueue mPresentQueue; // 支持显示的队列 void createInstance() {
/* 填充VkApplicationInfo结构体 */
VkApplicationInfo appInfo{
VK_STRUCTURE_TYPE_APPLICATION_INFO, // .sType
nullptr, // .pNext
"I don't care", // .pApplicationName
VK_MAKE_VERSION(1, 0, 0), // .applicationVersion
"I don't care", // .pEngineName
VK_MAKE_VERSION(1, 0, 0), // .engineVersion
VK_API_VERSION_1_0, // .apiVersion
}; /* 获取glfw要求支持的扩展 */
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); /* 输出glfw所需的扩展 */
std::cout << "[INFO] glfw needs the following extensions:\n";
for (int i = 0; i < glfwExtensionCount; i++) {
std::cout << " " << glfwExtensions[i] << std::endl;
} /* 填充VkInstanceCreateInfo结构体 */
VkInstanceCreateInfo instanceCreateInfo{
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
&appInfo, // .pApplicationInfo
mRequiredLayers.size(), // .enabledLayerCount
mRequiredLayers.data(), // .ppEnabledLayerNames
glfwExtensionCount, // .enabledExtensioncount
glfwExtensions, // .ppEnabledExtensionNames
}; /* 如果创建实例失败,终止程序 */
if_fail(
vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance),
"failed to create instance"
);
} void createSurface() {
mWindow = glfwCreateWindow(mWidth, mHeight, "Vulkan App", nullptr, nullptr); // 创建glfw窗口
if (mWindow == nullptr) {
std::cerr << "failed to create window\n";
exit(-1);
} /* 创建VkSurfaceKHR对象 */
if_fail(
glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface),
"failed to create surface"
);
} void selectPhysicalDevice() {
/* 查找所有可选的物理设备 */
uint32_t physicalDeviceCount = 0;
vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr);
vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices.data()); mPhysicalDevice = VK_NULL_HANDLE; for (VkPhysicalDevice physicalDevice : physicalDevices) {
/* 1. 检查物理设备是否支持扩展 */
/* 获取物理设备支持的扩展信息 */
uint32_t extensionCount = 0;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, availableExtensions.data()); bool isAllRequiredExtensionsSupported = true; // 检查此物理设备是否支持所有的扩展
for (const char* requiredExtensionName : mRequiredExtensions) {
bool isSupported = false;
for (const auto& availableExtension : availableExtensions) {
if (strcmp(requiredExtensionName, availableExtension.extensionName) == 0) {
isSupported = true;
break;
}
}
if (isSupported == false) {
isAllRequiredExtensionsSupported = false;
break;
}
}
if (isAllRequiredExtensionsSupported) {
Log("all required extensions are supported");
}
else {
continue;
} /* 2. 检查物理设备是否支持几何着色器 */
VkPhysicalDeviceFeatures physicalDeviceFeatures;
vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures);
if (physicalDeviceFeatures.geometryShader) {
Log("geometry shader is supported");
}
else {
continue;
} /* 获取队列族的信息 */
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); for (int i = 0; i < queueFamilyCount; i++) {
/*  5.3. 检查是否支持图形功能 */
if (mGraphicsQueueFamilyIndex < 0 && (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
Log("find graphics queue family index " << i);
mGraphicsQueueFamilyIndex = i; // 保留队列族的索引
} /* 5.4. 检查是否支持显示功能  */
if (mPresentQueueFamilyIndex < 0) {
VkBool32 isPresentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, mSurface, &isPresentSupport);
if (isPresentSupport) {
mPresentQueueFamilyIndex = i;
Log("find present queue family index " << i);
}
else {
Log("present is not supported");
}
}
} if (mGraphicsQueueFamilyIndex >= 0 && mPresentQueueFamilyIndex >= 0) {
mPhysicalDevice = physicalDevice; /* 获取物理设备的属性 */
VkPhysicalDeviceProperties physicalDeviceProperties;
vkGetPhysicalDeviceProperties(mPhysicalDevice, &physicalDeviceProperties);
Log("select physical device: " << physicalDeviceProperties.deviceName);
}
} /* 如果没找到合适的物理设备 */
if (mPhysicalDevice == VK_NULL_HANDLE) {
Error("can't find suitable physical device");
}
} void createDevice() {
/* 填充VkDeviceQueueCreateInfo结构体 */
vector<VkDeviceQueueCreateInfo> deviceQueueCreateInfos;
float queuePriority = 1.0f; // 必须指定优先级,如果pQueuePriorities设置为nullptr会报错 VkDeviceQueueCreateInfo deviceGraphicsQueueCreateInfo{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
mGraphicsQueueFamilyIndex, // .queueFamilyIndex
1, // .queueCount
&queuePriority, // .pQueuePriorities
};
deviceQueueCreateInfos.push_back(deviceGraphicsQueueCreateInfo); if (mPresentQueueFamilyIndex != mGraphicsQueueFamilyIndex) {
VkDeviceQueueCreateInfo devicePresentQueueCreateInfo{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
mPresentQueueFamilyIndex, // .queueFamilyIndex
1, // .queueCount
&queuePriority, // .pQueuePriorities
};
deviceQueueCreateInfos.push_back(devicePresentQueueCreateInfo);
} /* 填充VkDeviceCreateInfo结构体 */
VkDeviceCreateInfo deviceCreateInfo{
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
deviceQueueCreateInfos.size(), // .queueCreateInfoCount
deviceQueueCreateInfos.data(), // .pQueueCreateInfos
mRequiredLayers.size(), // .enabledLayerCount
mRequiredLayers.data(), // .ppEnabledLayerNames
mRequiredExtensions.size(), // .enabledExtensionCount
mRequiredExtensions.data(), // .ppEnabledExtensionNames
nullptr, // .pEnabledFeatureks
}; if_fail(
vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice),
"failed to create device!"
);
Log("create device successfully"); vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue);
vkGetDeviceQueue(mDevice, mPresentQueueFamilyIndex, 0, &mPresentQueue);
} void createSwapChain() {
/* 获取物理设备对图像大小、数量的支持 */
VkSurfaceCapabilitiesKHR surfaceCapabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, mSurface, &surfaceCapabilities); /* 获取物理设备对图像格式的支持 */
uint32_t formatCount = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, mSurface, &formatCount, nullptr);
vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, mSurface, &formatCount, surfaceFormats.data()); /* 填充交换链结构体 */
int imageCount = surfaceCapabilities.minImageCount + 1 <= surfaceCapabilities.maxImageCount ?
surfaceCapabilities.minImageCount + 1 : surfaceCapabilities.maxImageCount; // 设置图像的数量 VkSwapchainCreateInfoKHR swapchainCreateInfo{
VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, // .sType
nullptr, // .pNext
0, // .flags
mSurface, // .surface
imageCount, // .minImageCount
surfaceFormats[0].format, // .imageFormat
surfaceFormats[0].colorSpace, // .imageColorSpace
surfaceCapabilities.currentExtent, // imageExtent
1, // .imageArrayLayers
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, // .imageUsage
VK_SHARING_MODE_EXCLUSIVE, // .imageSharingMode
0, // .queueFamilyIndexCount
nullptr, // .pQueueFamilyIndices
surfaceCapabilities.currentTransform, // .preTransform
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, // .compositeAlpha
VK_PRESENT_MODE_FIFO_KHR, // .presentMode
VK_TRUE, // .clipped
VK_NULL_HANDLE, // .oldSwapChain
}; /* 如果图形队列和展示队列不是同一个队列 */
if (mGraphicsQueueFamilyIndex != mPresentQueueFamilyIndex) {
swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchainCreateInfo.queueFamilyIndexCount = 2;
uint32_t queueFamilyIndices[] = {
mGraphicsQueueFamilyIndex, mPresentQueueFamilyIndex
};
swapchainCreateInfo.pQueueFamilyIndices = queueFamilyIndices;
} /* 创建交换链 */
if_fail(
vkCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapChain),
"failed to create swapchain!"
);
Log("create swapchain successfully"); /* 获取交换链的图像 */
uint32_t swapChainImagesCount = 0;
vkGetSwapchainImagesKHR(mDevice, mSwapChain, &swapChainImagesCount, nullptr);
mSwapChainImages.resize(swapChainImagesCount);
vkGetSwapchainImagesKHR(mDevice, mSwapChain, &swapChainImagesCount, mSwapChainImages.data()); /* 为每张图像创建对应的图像视图 */
mSwapChainImageViews.resize(mSwapChainImages.size());
for (size_t i = 0; i < mSwapChainImages.size(); i++) {
VkImageViewCreateInfo imageViewCreateInfo{
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, // .sType
nullptr, // .pNext
0, // .flags
mSwapChainImages[i], // .image
VK_IMAGE_VIEW_TYPE_2D, // .viewType
surfaceFormats[0].format, // .format
{
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
}, // .components
{
VK_IMAGE_ASPECT_COLOR_BIT, // .aspectMask
0, // .baseMipLevel
1, // .levelCount
0, // .baseArrayLayer
1, // .layerCount
}, // .subresourceRange
}; if_fail(
vkCreateImageView(mDevice, &imageViewCreateInfo, nullptr, &mSwapChainImageViews[i]),
"failed to create image views!"
);
}
}
}; int main() {
VulkanApp app;
app.Run();
} static void if_fail(VkResult result, const char* message) {
if (result != VK_SUCCESS) {
std::cerr << "[error] " << message << std::endl;
exit(-1);
}
}

Vulkan学习苦旅05:马不停蹄地渲染(创建交换链VkSwapchainKHR)的更多相关文章

  1. VULKAN学习笔记-inter教学四篇

    --交换链相关函数:实例层vkCreateWin32SurfaceKHRvkDestroySurfaceKHRvkGetPhysicalDeviceSurfaceSurportKHRvkGetPhys ...

  2. [译]Vulkan教程(10)交换链

    [译]Vulkan教程(10)交换链 Vulkan does not have the concept of a "default framebuffer", hence it r ...

  3. Vulkan Tutorial 08 交换链

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制.这种机制成为交换链,并且需要 ...

  4. Vulkan Tutorial 18 重构交换链

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 现在我们已经成功的在屏幕上绘制出三角形,但是在某些情况下, ...

  5. C++ GUI Qt4学习笔记05

    C++ GUI Qt4学习笔记05   qtc++正则表达式 QIntValidator           --  只让用户输入整数 QDoubleValidator     --  只让用户输入浮 ...

  6. Spring5.0源码学习系列之浅谈BeanFactory创建

    Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...

  7. Android学习笔记——Activity的启动和创建

    http://www.cnblogs.com/bastard/archive/2012/04/07/2436262.html Android Activity学习笔记——Activity的启动和创建 ...

  8. Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md

    写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...

  9. Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签

    写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...

  10. Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作

    写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...

随机推荐

  1. 2023陕西省大学生信息安全竞赛web writeup

    前言 早写好了,忘发了,题目质量还行,够我坐大牢 ezpop 简单的反序列化,exp如下 <?php class night { public $night; } class day { pub ...

  2. oracle表空间已满解决

    在日常的oralce使用中最长遇到的问题就是oralce的表空间满了,数据无法写入报错,这种情况下通常是磁盘没有足够的空间或者表空间的数据文件达到32G(linux最大限制单个文件不超过32G)无法继 ...

  3. Zookeeper(3)---java客户端的使用

    前面介绍了zk指令的使用,这里说一下java客户端中怎么使用这些指令 <dependency> <groupId>org.apache.zookeeper</groupI ...

  4. C#读取FX5U线圈(modbusTCP)

    第一步:导入所需的类库 第二步:包含命名空间 第三步:实例化modbus类 ModbusTcpNet busTcpClient = null; busTcpClient = new ModbusTcp ...

  5. VUE - 配置跨域

    '/api': { target: 'http://localhost:8088/', //这里后台的地址模拟的;应该填写你们真实的后台接口 changOrigin: true, //允许跨域 pat ...

  6. [IDEA] - 左侧目录结构没了,怎么处理

    把项目目录下的.idea删除,再重启IDEA打开项目就行了

  7. python环境 anaconda安装

    官网: https://www.anaconda.com/distribution/#macos 国内镜像: https://mirrors.tuna.tsinghua.edu.cn/anaconda ...

  8. Grafana监控minio的极简方法

    Grafana监控minio的极简方法 背景 想监控一下minio的部分信息. 使用过程中需要关注的内容挺多的. 只看简单的node感觉已经不够了. 所以想监控易一下. 方式和方法 minio其实集成 ...

  9. [转帖]028.PGSQL-用户创建、表空间创建、数据库创建、schema创建、表创建、生成测试数据、指定搜索路径、

    https://www.cnblogs.com/star521/p/15054341.html  登录数据库 su postgres #注意这里postgers 前后都有空格 psql -U post ...

  10. [转帖]我们为什么放弃 MongoDB 和 MySQL,选择 TiDB

    https://zhuanlan.zhihu.com/p/164706527 写在前面的话 技术选型是由技术方向和业务场景 trade-off 决定的,脱离业务场景来说技术选型是没有任何意义的,所以本 ...