入门vulkan

Tags:

安装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总的创建步骤是:

  1. 创建depth buffer 图像对象(image object)
  2. 分配depth buffer 设备内存(device memory)
  3. 把这块内存绑定到image object
  4. 创建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则需要自己分配内存并填充数据来实现。

三个步骤:

  1. Creating the Uniform Buffer Object
  2. Allocating the Uniform Buffer Memory
  3. 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 desc_layout了:

/* 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)

(未经授权禁止转载)
Written on October 20, 2018

博主将十分感谢对本文章的任意金额的打赏^_^