安装SDK
LunarG的SDK、Runtime,以及NVidia的驱动。
实例
LunarG出品的
https://github.com/LunarG/VulkanSamples
mkdir build cd build python ../scripts/update_deps.py --arch Win64 cmake -C helper.cmake -G "Visual Studio 14 2015 Win64" .. 或 cmake -C helper.cmake -G "Visual Studio 15 2017 Win64" .. cmake --build .
Note:需要python3环境。如果已經有Python2,可以自定义模式安裝python3并取消勾选所有选项(最精简就够了),安装后把path指向python3目录。再执行update_deps.py。最后再改回去python2
编译问题:
__std_reverse_trivially_swappable_8:貌似是同时安装VS2015和VS2017才会出现的问题。用cmake -C helper.cmake -G "Visual Studio 15 2017 Win64" .. 试试。
民间出品的
https://github.com/SaschaWillems/vulkan
这个不需要安装LunarG的SDK就可以编译运行,很傻瓜化(但应该需要安装新的NVidia驱动)。实质是直接把LunarG的vulkan include目录嵌进去了。
LunarG draw cube学习笔记
https://vulkan.lunarg.com/doc/sdk/1.1.92.1/windows/tutorial/html/index.html
1. 创建必要的VkInstance对象(vkCreateInstance)
VkResult VKAPI_CALL vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
void VKAPI_CALL vkDestroyInstance(
VkInstance instance,
const VkAllocationCallbacks* pAllocator);
给定pCreateInfo对象,可以创建VkInstance对象(放在pInstance返回),要通过VkResult检查是否正确创建。
2. 枚举设备enumerate_devices(vkEnumeratePhysicalDevices)
这一步是确定本机有什么物理设备(GPU),写入到VkPhysicalDevice数组里。
VkInstance inst;
std::vector<VkPhysicalDevice> gpus;
···
uint32_t gpu_count = 1;
VkResult U_ASSERT_ONLY res = vkEnumeratePhysicalDevices(inst, &gpu_count, NULL);
assert(gpu_count);
gpus.resize(gpu_count);
res = vkEnumeratePhysicalDevices(inst, &gpu_count, gpus.data());
assert(!res && gpu_count >= 1);
vkEnumeratePhysicalDevices同一个接口用两次,第一次的第三个参数设NULL,可以获取gpu数量;知道gpu数量后,第二次传入一个raw的VkPhysicalDevice数组指针,直接让api初始化这个数组。
因为是raw的数组指针,所以要先resize这个vector,确保有足够的空间放数据。
3. 创建一个(逻辑)设备对象 vkCreateDevice
uint32_t queue_family_count;
std::vector<VkQueueFamilyProperties> queue_props;
vkGetPhysicalDeviceQueueFamilyProperties(gpus[0], &queue_family_count, NULL);
assert(queue_family_count >= 1);
queue_props.resize(queue_family_count);
vkGetPhysicalDeviceQueueFamilyProperties(gpus[0], &queue_family_count, queue_props.data());
assert(queue_family_count >= 1);
vkGetPhysicalDeviceQueueFamilyProperties的用法和vkEnumeratePhysicalDevices非常相似。
vkGetPhysicalDeviceQueueFamilyProperties的用途是获取这个gpu的queue_family_count和queue_props。queue_props则是一个VkQueueFamilyProperties数组。
typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags;
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
注意这个是family,每个family里可以有多个queue,queue的数量看queueCount。每个family对应多个flags集合:
typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001,
VK_QUEUE_COMPUTE_BIT = 0x00000002,
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
VK_QUEUE_PROTECTED_BIT = 0x00000010,
VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkQueueFlagBits;
例如一般第一个family的flags是15,即VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT | VK_QUEUE_SPARSE_BINDING_BIT。
queue_family_count和queue_props有了后,就可以创建device了。创建device的接口是vkCreateDevice。
在调用这个接口前需要准备好VkDeviceQueueCreateInfo对象、VkDeviceCreateInfo对象。
VkDeviceQueueCreateInfo对象:
VkDeviceQueueCreateInfo queue_info = {};
for (unsigned int i = 0; i < queue_family_count; i++) {
if (queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
queue_info.queueFamilyIndex = i;
break;
}
}
float queue_priorities[1] = {0.0};
queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info.pNext = NULL;
queue_info.queueCount = 1;
queue_info.pQueuePriorities = queue_priorities;
VkDeviceCreateInfo对象:
VkDeviceCreateInfo device_info = {};
device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
device_info.pNext = NULL;
device_info.queueCreateInfoCount = 1;
device_info.pQueueCreateInfos = &queue_info;
device_info.enabledExtensionCount = 0;
device_info.ppEnabledExtensionNames = NULL;
device_info.enabledLayerCount = 0;
device_info.ppEnabledLayerNames = NULL;
device_info.pEnabledFeatures = NULL;
然后就:
VkDevice device;
VkResult U_ASSERT_ONLY res = vkCreateDevice(gpus[0], &device_info, NULL, &device);
assert(res == VK_SUCCESS);
vkDestroyDevice(device, NULL);
4. 创建VkCommandPool和VkCommandBuffer
vkCreateCommandPool创建池,vkAllocateCommandBuffers则从池里分配buffer:
VkDevice device;
VkCommandPool cmd_pool;
VkCommandBuffer cmd;
VkCommandPoolCreateInfo cmd_pool_create_info = {};
cmd_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
cmd_pool_create_info.pNext = NULL;
cmd_pool_create_info.queueFamilyIndex = graphics_queue_family_index; // 上一步的
cmd_pool_create_info.flags = 0;
res = vkCreateCommandPool(device, &cmd_pool_create_info, NULL, &cmd_pool);
assert(res == VK_SUCCESS);
/* Create the command buffer from the command pool */
VkCommandBufferAllocateInfo cmd_buffer_alloc_info = {};
cmd_buffer_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cmd_buffer_alloc_info.pNext = NULL;
cmd_buffer_alloc_info.commandPool = cmd_pool;
cmd_buffer_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmd_buffer_alloc_info.commandBufferCount = 1;
res = vkAllocateCommandBuffers(device, &cmd_buffer_alloc_info, &cmd);
assert(res == VK_SUCCESS);
5. swapchain
extension
首先要在创建instance之前插个步骤,设置instance_extension_names:
void init_instance_extension_names(struct sample_info &info) {
info.instance_extension_names.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
#ifdef __ANDROID__
info.instance_extension_names.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#elif defined(_WIN32)
info.instance_extension_names.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_IOS_MVK)
info.instance_extension_names.push_back(VK_MVK_IOS_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_MACOS_MVK)
info.instance_extension_names.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
#elif defined(VK_USE_PLATFORM_WAYLAND_KHR)
info.instance_extension_names.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#else
info.instance_extension_names.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME);
#endif
}
还有device的extension:
void init_device_extension_names(struct sample_info &info) {
info.device_extension_names.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
swapchain的解释:就是一系列image buffer,gpu会往里面绘制东西,这些buffer可显示到硬件。
正因为是由硬件来绘制,所以才需要一个device级别的扩展,才能使得swapchain工作。
pSupportsPresent
先是查出有多少queue支持presenting,总共要遍历queue_family_count次:
// Iterate over each queue to learn whether it supports presenting:
VkBool32 *pSupportsPresent = (VkBool32 *)malloc(info.queue_family_count * sizeof(VkBool32));
for (uint32_t i = 0; i < info.queue_family_count; i++) {
vkGetPhysicalDeviceSurfaceSupportKHR(info.gpus[0], i, info.surface, &pSupportsPresent[i]);
}
有了pSupportsPresent布尔数组后,就遍历下看看哪个queue可以作为graphics queue、present queue :
// Search for a graphics and a present queue in the array of queue
// families, try to find one that supports both
info.graphics_queue_family_index = UINT32_MAX;
info.present_queue_family_index = UINT32_MAX;
for (uint32_t i = 0; i < info.queue_family_count; ++i) {
// 前提得支持Graphics,再看pSupportsPresent
if ((info.queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
if (info.graphics_queue_family_index == UINT32_MAX)
info.graphics_queue_family_index = i;
if (pSupportsPresent[i] == VK_TRUE) {
info.graphics_queue_family_index = i;
info.present_queue_family_index = i;
break;
}
}
}
有可能找不到合适的present_queue_family_index(也意味着没有同时支持graphics和present的queue),则随便找一个pSupportsPresent[i]为VK_TRUE的queue,即让graphic queue和present queue分离:
if (info.present_queue_family_index == UINT32_MAX) {
// If didn't find a queue that supports both graphics and present, then
// find a separate present queue.
for (size_t i = 0; i < info.queue_family_count; ++i)
if (pSupportsPresent[i] == VK_TRUE) {
info.present_queue_family_index = i;
break;
}
}
// 此时可以释放pSupportsPresent了
free(pSupportsPresent);
创建surface
这里用到了2个参数info.connection、info.window,暂时跳过;vkCreateWin32SurfaceKHR创建一个surface并放进info.surface:
VkWin32SurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = NULL;
createInfo.hinstance = info.connection;
createInfo.hwnd = info.window;
res = vkCreateWin32SurfaceKHR(info.inst, &createInfo, NULL, &info.surface);
初始化 VkSwapchainCreateInfoKHR
VkSwapchainCreateInfoKHR swapchain_ci = {};
swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_ci.pNext = NULL;
swapchain_ci.surface = info.surface; // 上一节刚创建的
swapchain_ci.minImageCount = desiredNumberOfSwapChainImages; // 调用vkGetPhysicalDeviceSurfaceCapabilitiesKHR接口得到,VkSurfaceCapabilitiesKHR结构里存放这些信息
swapchain_ci.imageFormat = info.format; // 调用vkGetPhysicalDeviceSurfaceFormatsKHR接口得到
swapchain_ci.imageExtent.width = swapchainExtent.width;
swapchain_ci.imageExtent.height = swapchainExtent.height;
swapchain_ci.preTransform = preTransform; // VkSurfaceTransformFlagBitsKHR, 调用vkGetPhysicalDeviceSurfaceCapabilitiesKHR接口得到
swapchain_ci.compositeAlpha = compositeAlpha;// 一般是VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR
swapchain_ci.imageArrayLayers = 1;
swapchain_ci.presentMode = swapchainPresentMode; // 写死的VK_PRESENT_MODE_FIFO_KHR
swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
swapchain_ci.clipped = true;
swapchain_ci.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_ci.queueFamilyIndexCount = 0;
swapchain_ci.pQueueFamilyIndices = NULL;
uint32_t queueFamilyIndices[2] = {(uint32_t)info.graphics_queue_family_index, (uint32_t)info.present_queue_family_index};
if (info.graphics_queue_family_index != info.present_queue_family_index) {
// If the graphics and present queues are from different queue families,
// we either have to explicitly transfer ownership of images between
// the queues, or we have to create the swapchain with imageSharingMode
// as VK_SHARING_MODE_CONCURRENT
swapchain_ci.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_ci.queueFamilyIndexCount = 2;
swapchain_ci.pQueueFamilyIndices = queueFamilyIndices;
}
准备了一大堆的信息,然后就可以调用vkCreateSwapchainKHR:
res = vkCreateSwapchainKHR(info.device, &swapchain_ci, NULL, &info.swap_chain);
assert(res == VK_SUCCESS);
这个操作创建了一个images集合,组成了swapchain。
有了swapchain对象后,就可以取出swapchainImages对象,很经典的先获得count再获得指针的操作:
res = vkGetSwapchainImagesKHR(info.device, info.swap_chain, &info.swapchainImageCount, NULL);
assert(res == VK_SUCCESS);
VkImage *swapchainImages = (VkImage *)malloc(info.swapchainImageCount * sizeof(VkImage));
assert(swapchainImages);
res = vkGetSwapchainImagesKHR(info.device, info.swap_chain, &info.swapchainImageCount, swapchainImages);
assert(res == VK_SUCCESS);
image view
image view大概意思是用来管理image的内存的,有很多关于image的元信息。
创建image view就比较直白了:
typedef struct _swap_chain_buffers {
VkImage image;
VkImageView view;
} swap_chain_buffer;
std::vector<swap_chain_buffer> buffers;
···
for (uint32_t i = 0; i < info.swapchainImageCount; i++) {
VkImageViewCreateInfo color_image_view = {};
color_image_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
color_image_view.pNext = NULL;
color_image_view.flags = 0;
color_image_view.image = buffers[i].image;
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = info.format;
color_image_view.components.r = VK_COMPONENT_SWIZZLE_R;
color_image_view.components.g = VK_COMPONENT_SWIZZLE_G;
color_image_view.components.b = VK_COMPONENT_SWIZZLE_B;
color_image_view.components.a = VK_COMPONENT_SWIZZLE_A;
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
res = vkCreateImageView(info.device, &color_image_view, NULL, &buffers[i].view);
assert(res == VK_SUCCESS);
}
···
/* Clean Up */
for (uint32_t i = 0; i < info.swapchainImageCount; i++) {
vkDestroyImageView(info.device, info.buffers[i].view, NULL);
}
6. 深度缓冲区depth buffer(VkImage)
首先depth buffer是可选的。但是做实时3D渲染都会用到depth buffer,所以必须学习。
depth buffer只需要一个,即使swapchain有多个image。
depth buffer总的创建步骤是:
- 创建depth buffer 图像对象(image object)
- 分配depth buffer 设备内存(device memory)
- 把这块内存绑定到image object
- 创建depth buffer image view
首先查询VK_FORMAT_D16_UNORM是否支持:
const VkFormat depth_format = VK_FORMAT_D16_UNORM;
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(info.gpus[0], depth_format, &props);
if (props.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_LINEAR;
} else if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
} else {
/* Try other depth formats? */
std::cout << "VK_FORMAT_D16_UNORM Unsupported.\n";
exit(-1);
}
创建depth buffer:
{
VkImageCreateInfo image_info = {};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = NULL;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = depth_format;
image_info.extent.width = info.width;
image_info.extent.height = info.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.samples = NUM_SAMPLES; // 1
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.queueFamilyIndexCount = 0;
image_info.pQueueFamilyIndices = NULL;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.flags = 0;
info.depth.format = depth_format;
/* Create image */
res = vkCreateImage(info.device, &image_info, NULL, &info.depth.image);
assert(res == VK_SUCCESS);
}
//其中info.depth为:
struct {
VkFormat format;
VkImage image;
VkDeviceMemory mem;
VkImageView view;
} depth;
这样就完成了第一步创建depth buffer对象。
// 等下给vkAllocateMemory用的参数
VkMemoryAllocateInfo mem_alloc = {};
mem_alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
mem_alloc.pNext = NULL;
mem_alloc.allocationSize = 0;
mem_alloc.memoryTypeIndex = 0;
VkMemoryRequirements mem_reqs;
vkGetImageMemoryRequirements(info.device, info.depth.image, &mem_reqs);
mem_alloc.allocationSize = mem_reqs.size;
// 根据mem_reqs的信息,决定内存类型
pass =
memory_type_from_properties(info, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &mem_alloc.memoryTypeIndex);
assert(pass);
// 2. 分配内存 放进VkDeviceMemory对象info.depth.mem
res = vkAllocateMemory(info.device, &mem_alloc, NULL, &info.depth.mem);
assert(res == VK_SUCCESS);
// 3. 把内存绑定给info.depth.image对象
res = vkBindImageMemory(info.device, info.depth.image, info.depth.mem, 0);
assert(res == VK_SUCCESS);
最后一步创建image view :
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = NULL;
view_info.image = VK_NULL_HANDLE;
view_info.format = depth_format;// VK_FORMAT_D16_UNORM
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.flags = 0;
// 4. 创建image view
view_info.image = info.depth.image;
res = vkCreateImageView(info.device, &view_info, NULL, &info.depth.view);
assert(res == VK_SUCCESS);
7. Uniform Buffer (VkBuffer)
这个uniform buffer的uniform,就是指glsl的uniform,同个东西。glsl只需要调用api直接set uniform即可,而vulkan则需要自己分配内存并填充数据来实现。
三个步骤:
- Creating the Uniform Buffer Object
- Allocating the Uniform Buffer Memory
- Mapping and Setting the Uniform Buffer Memory
struct {
VkBuffer buf;
VkDeviceMemory mem;
VkDescriptorBufferInfo buffer_info;
} uniform_data;
···
/* VULKAN_KEY_START */
VkBufferCreateInfo buf_info = {};
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buf_info.pNext = NULL;
buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
buf_info.size = sizeof(info.MVP);
buf_info.queueFamilyIndexCount = 0;
buf_info.pQueueFamilyIndices = NULL;
buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
buf_info.flags = 0;
res = vkCreateBuffer(info.device, &buf_info, NULL, &info.uniform_data.buf);
assert(res == VK_SUCCESS);
然后分配内存,和depth buffer的时候几乎一样:
VkMemoryRequirements mem_reqs;
vkGetBufferMemoryRequirements(info.device, info.uniform_data.buf, &mem_reqs);
VkMemoryAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = NULL;
alloc_info.memoryTypeIndex = 0;
alloc_info.allocationSize = mem_reqs.size;
pass = memory_type_from_properties(info, mem_reqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&alloc_info.memoryTypeIndex);
assert(pass && "No mappable, coherent memory");
res = vkAllocateMemory(info.device, &alloc_info, NULL, &(info.uniform_data.mem));
assert(res == VK_SUCCESS);
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT是让CPU(host)可以访问这块内存;The VK_MEMORY_PROPERTY_HOST_COHERENT_BIT 则让CPU写内存对设备可见且不需要刷内存缓存。所以只是一个便捷设置,避免调用vkFlushMappedMemoryRanges、vkInvalidateMappedMemoryRanges,确保数据对GPU可见。
最后vkMapMemory:
uint8_t *pData;
// info.uniform_data.mem已经由vkAllocateMemory分配了
// pData转2维数组指针骚操作
res = vkMapMemory(info.device, info.uniform_data.mem, 0, mem_reqs.size, 0, (void **)&pData);
assert(res == VK_SUCCESS);
// 把MVP矩阵复制到映射内存
memcpy(pData, &info.MVP, sizeof(info.MVP));
// 解除映射关系
vkUnmapMemory(info.device, info.uniform_data.mem);
// 然后绑定内存给uniform buffer
res = vkBindBufferMemory(info.device, info.uniform_data.buf, info.uniform_data.mem, 0);
assert(res == VK_SUCCESS);
// 设置VkDescriptorBufferInfo
info.uniform_data.buffer_info.buffer = info.uniform_data.buf;
info.uniform_data.buffer_info.offset = 0;
info.uniform_data.buffer_info.range = sizeof(info.MVP); // 在这里确定了内存范围
buffer_info结构:
typedef struct VkDescriptorBufferInfo {
VkBuffer buffer;
VkDeviceSize offset;
VkDeviceSize range;
} VkDescriptorBufferInfo;
8. descriptor、Descriptor Sets、Descriptor Set Layouts、 Pipeline Layouts
descriptor
上一节的uniform buffer只是做到了内存的处理,还没涉及到怎么使用uniform buffer。里面存了MVP矩阵信息,是要给vertex shader用的。接下来介绍怎么做,要用到一个新的东西叫descriptor。
一个descriptor是一个特别的shader变量,shader可以用它来访问buffer和image资源,因此它像是一个指向资源的指针。Vulkan API允许在draw操作之间改变这些变量,使得shader可以在不同的draw里访问不同的资源。
一个descriptor可以绑定一个放着mvp矩阵的uniform buffer,也可以创建多个uniform buffer,然后切换绑定不同的uniform buffer实现不同的视角转换。
虽然目前为止还没用到textures,不过descriptor的一个用法是,用多个descriptor来引用不同的纹理,在单次draw里这些纹理同时可用。
descriptor set
descriptor set的创建在下节介绍,这里只是介绍用法。
descriptor set layout
为了描述一个descriptor set,得用一个descriptor set layout。
A descriptor set layout可以描述多个descriptor sets的内容。同时,每一个descriptor set也需要自己的layout。
先是准备好VkDescriptorSetLayoutBinding对象:
/* Number of descriptor sets needs to be the same at alloc, */
/* pipeline layout creation, and descriptor set layout creation */
#define NUM_DESCRIPTOR_SETS 1
VkDescriptorSetLayoutBinding layout_binding = {};
layout_binding.binding = 0; // 索引0?
layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; // uniform buffer
layout_binding.descriptorCount = 1; // descriptor set里只有一个descriptor
layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;// vertex shader阶段使用
layout_binding.pImmutableSamplers = NULL;
然后就可以初始化std::vector
/* Next take layout bindings and use them to create a descriptor set layout
*/
VkDescriptorSetLayoutCreateInfo descriptor_layout = {};
descriptor_layout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_layout.pNext = NULL;
descriptor_layout.bindingCount = 1;
descriptor_layout.pBindings = &layout_binding;
info.desc_layout.resize(NUM_DESCRIPTOR_SETS);
res = vkCreateDescriptorSetLayout(info.device, &descriptor_layout, NULL, info.desc_layout.data());
assert(res == VK_SUCCESS);
Pipeline Layouts
一个pipeline layout包含1到多个descriptor set layout。
/* Now use the descriptor layout to create a pipeline layout */
VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {};
pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pPipelineLayoutCreateInfo.pNext = NULL;
pPipelineLayoutCreateInfo.pushConstantRangeCount = 0;
pPipelineLayoutCreateInfo.pPushConstantRanges = NULL;
pPipelineLayoutCreateInfo.setLayoutCount = NUM_DESCRIPTOR_SETS;// 给定descriptor set layout数量
pPipelineLayoutCreateInfo.pSetLayouts = info.desc_layout.data(); // descriptor set layout内存地址
res = vkCreatePipelineLayout(info.device, &pPipelineLayoutCreateInfo, NULL, &info.pipeline_layout);
assert(res == VK_SUCCESS);
pipeline layout将会被用来创建图形管线(graphics pipeline)
博主将十分感谢对本文章的任意金额的打赏^_^