Vulkan 学习(6)---- vkBuffer 创建

Overview

Vulkan中的缓存资源通过VkBuffer对象来表示,是一种用于存储通用数据的资源,可以用来存储顶点数据,索引数据,Uniforms 数据块等
VkBuffer 表示的是一个线性内存块,这说明它的内存布局是连续的,类似于数组,这种布局适合存储顺序访问的数据,比如 顶点数据和索引数据,也支持随机访问

VkBuffer 创建

创建 VkBuffer 时,可以通过设置 VkBufferCreateInfo 不同的 usage 标志来指定 VkBuffer 的用途
vkCreateBuffer 的函数原型是:

VKAPI_ATTR VkResult VKAPI_CALL vkCreateBuffer(
    VkDevice                                    device,
    const VkBufferCreateInfo*                   pCreateInfo,
    const VkAllocationCallbacks*                pAllocator,
    VkBuffer*                                   pBuffer);

其中的关键配置信息是pCreateInfo,其类型VkBufferCreateInfo定义如下:

typedef struct VkBufferCreateInfo {
    VkStructureType        sType; // 必须为 VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO
    const void*            pNext; // nullptr
    VkBufferCreateFlags    flags; // 0
    VkDeviceSize           size; // 缓存大小,单位为字节
    VkBufferUsageFlags     usage; // usage ,important
    VkSharingMode          sharingMode; // 缓存的共享模式
    uint32_t               queueFamilyIndexCount; // 指定 pQueueFamilyIndices 数组中元素数量
    const uint32_t*        pQueueFamilyIndices;// 指定将会访问该缓存的设备队列
} VkBufferCreateInfo;

其中需要重点关注的是 VkBufferUsageFlags,定义如下:

typedef enum VkBufferUsageFlagBits {
    VK_BUFFER_USAGE_TRANSFER_SRC_BIT = 0x00000001,
    VK_BUFFER_USAGE_TRANSFER_DST_BIT = 0x00000002,
    VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT = 0x00000004,
    VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT = 0x00000008,
    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT = 0x00000010,
    VK_BUFFER_USAGE_STORAGE_BUFFER_BIT = 0x00000020,
    VK_BUFFER_USAGE_INDEX_BUFFER_BIT = 0x00000040,
    VK_BUFFER_USAGE_VERTEX_BUFFER_BIT = 0x00000080,
    VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT = 0x00000100,
}
  • VK_BUFFER_USAGE_TRANSFER_SRC_BIT 该缓存用于数据传输的数据源
  • VK_BUFFER_USAGE_TRANSFER_DST_BIT 该缓存用于数据传输的目的数据
  • VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT 该缓存用于传输统一纹理像素
  • VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT 该缓存用于保存纹理像素数据
  • VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT 该缓存用于传输任意统一数据
  • VK_BUFFER_USAGE_STORAGE_BUFFER_BIT 该缓存用于存储任意格式数据。用于设备读取和存
  • VK_BUFFER_USAGE_INDEX_BUFFER_BIT 该缓存用于存储整型索引数据
  • VK_BUFFER_USAGE_VERTEX_BUFFER_BIT该缓存用于存储具有相同结构的顶点数据
  • VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT 该缓存用于间接数据。用于存储指令参数,设备可一次性读取这些参数

资源共享类型 vkSharingMode

typedef enum VkSharingMode {
    VK_SHARING_MODE_EXCLUSIVE = 0,
    VK_SHARING_MODE_CONCURRENT = 1,
    VK_SHARING_MODE_MAX_ENUM = 0x7FFFFFFF
} VkSharingMode;
  • VK_SHARING_MODE_EXCLUSIVE 设备队列独享资源,该资源一次只能被一种设备队列族中的队列访问

  • VK_SHARING_MODE_CONCURRENT 设备队列共享资源,该资源只能一次被多种设备队列族中的队列访问

需要注意的是,在Vulkan中创建的所有资源(VkBuffer, VkImage等)都是虚资源
也就是说,创建的资源只是占用了一个位置,创建了一个句柄,并没有对应存储资源数据的内存,后续要通过 vkBindBufferMemory 将资源绑定到相应的设备内存 VkDeviceMemory,所以数据实际存储在设备内存
一旦设备内存绑定到一个资源对象(VkBuffer, VkImage)上,这个内存绑定就不能再次改变了

VkBufferRequirement 查询

在设备内存绑定到资源上之前,需要确定使用什么类型的内存,以及资源需要多少内存
这个时候,可以使用vkGetBufferMemoryRequirements获取缓冲区的内存需求,包括内存大小,对齐要求以及适合的内存类型,注意此API 是查询已经创建的 VkBuffer 的信息

vkGetBufferMemoryRequirements 的函数原型:

VKAPI_ATTR void VKAPI_CALL vkGetBufferMemoryRequirements(
    VkDevice                                    device,
    VkBuffer                                    buffer,
    VkMemoryRequirements*                       pMemoryRequirements);

其中的 VkMemoryRequirements 包含了缓冲区的内存需求信息:

typedef struct VkMemoryRequirements {
    VkDeviceSize    size; // 缓冲区所需的内存大小(以自己为单位)
    VkDeviceSize    alignment; // 缓冲区的内存对齐要求
    uint32_t        memoryTypeBits; // 缓冲区的可用内存类型掩码和 枚举内存信息的flag 匹配
} VkMemoryRequirements;

参考代码实现如下:

uint32_t vulkanBasicDemo::findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
    VkPhysicalDeviceMemoryProperties memProperties;
    vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);

    for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
        if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
            return i;
        }
    }

    throw std::runtime_error("failed to find suitable memory type!");
}

void vulkanBasicDemo::vulkanCreateVulkanBuffer() {
    // 顶点数据的vector
    std::vector<float> vertices = {
        1.0f,  1.0f, 0.0f,
        1.0f,  1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
    };

    // 创建 VkBuffer 用于存储顶点数据
    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.pNext = nullptr;
    bufferInfo.flags = 0;
    bufferInfo.size = sizeof(vertices); // 设置缓冲区大小
    bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // 设置缓冲区用途 USAGE_VERTEX_BUFFER
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; // 共享模式
    bufferInfo.queueFamilyIndexCount = 1;
    bufferInfo.pQueueFamilyIndices = &graphicsQueueFamilyIndex;//设备队列索引
    VkBuffer vertexBuffer;
    if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
        throw std::runtime_error("failed to create vertex buffer!");
    }
    std::cout << "Create VkBuffer Success!!" << std::endl;


    // 获取内存需求, 包含 Vulkan 内存对齐信息,以及内存对齐之后,内存的 size\memoryTypeBits
    VkMemoryRequirements memRequirements;
    vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements);

    // 分配设备内存
    VkMemoryAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    // 获取到对CPU可见且自动同步的设备内存类型
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

    VkDeviceMemory vertexBufferMemory;
    if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate vertex buffer memory!");
    }

    // 绑定内存
    vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0);
    std::cout << "VkBuffer Bind Memory Success!!" << std::endl;

    // 内存映射,填充数据
    void* data;
    vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);//获取设备内存映射的内存地址
    memcpy(data, vertices.data(), (size_t)bufferInfo.size);//将顶点数据拷贝到设备内存映射的内存地址

    // 内存解映射
    vkUnmapMemory(device, vertexBufferMemory);
}
<think>嗯,用户问的是Vulkan的校验层是什么,以及它的机制和原理。之前用户问了关于在Vulkan窗口中使用中文的问题,现在转向了校验层,说明他们可能在开发过程中遇到了问题,比如验证错误或者想了解调试工具。我需要先回忆一下Vulkan校验层的相关知识,确保准确。 首先,校验层是Vulkan中的调试工具,用于在开发阶段检查错误和潜在问题。Vulkan本身设计为高性能,所以默认情况下不进行这些检查,而校验层则可以在运行时动态添加,用来验证API的正确使用。 接下来,用户可能想知道校验层具体如何工作。机制方面,校验层通过拦截API调用,执行检查,然后传递调用给驱动程序。这样可以在不修改应用代码的情况下插入验证逻辑。需要解释清楚校验层如何被加载和启用,比如通过实例创建时的层列表。 然后,原理部分需要说明校验层的实现方式,比如钩子函数、消息回调等。可能用户对底层的实现细节感兴趣,比如校验层如何检测资源泄漏或同步问题。需要提到常见的校验层如VK_LAYER_KHRONOS_validation,以及它们检查的具体内容,比如参数有效性、对象生命周期、内存管理等。 另外,用户可能想知道如何启用和配置校验层,包括在代码中指定层名称,设置回调函数处理验证消息。也许他们遇到了启用校验层后报错的情况,需要指导如何正确配置。需要提醒他们校验层可能影响性能,仅用于开发阶段。 还要考虑用户可能的实际应用场景,比如他们在开发一个Vulkan应用,遇到了难以调试的问题,希望通过校验层快速定位错误。需要强调校验层在调试中的作用,以及如何结合工具如RenderDoc使用。 最后,确保回答结构清晰,分点说明,覆盖定义、机制、原理、常见检查内容、使用方法和注意事项。使用简洁的语言,避免过于技术化,但又要足够详细,帮助用户理解并应用校验层到他们的项目中。</think>Vulkan 的 **校验层(Validation Layers)** 是用于在开发和调试阶段检查 Vulkan API 调用正确性的一组工具。它们通过拦截和验证 Vulkan 函数的参数、状态和逻辑,帮助开发者发现潜在的错误(如内存泄漏、资源未释放、非法参数等),而不会影响最终发布的性能。以下是其机制和原理的详细说明: --- ### **一、校验层的作用** 1. **错误检测** 检查 Vulkan API 的调用是否符合规范(例如参数是否合法、资源是否已释放、同步机制是否正确等)。 2. **警告和提示** 发现潜在的性能问题(如冗余的屏障、未使用的资源)或不推荐的用法。 3. **调试辅助** 提供详细的错误信息(如错误代码、调用栈、资源句柄),帮助快速定位问题。 --- ### **二、校验层的机制** #### 1. **动态拦截与验证** - **拦截 API 调用** 校验层通过 Vulkan 的 **分层架构**,在应用程序与 Vulkan 驱动之间插入“钩子”,拦截所有 Vulkan 函数调用。 - **链式调用** 多个校验层可以串联(例如同时启用参数校验和内存校验),每个层依次处理请求后,将结果传递给下一层或驱动。 #### 2. **启用与配置** - **显式启用** 校验层需要在创建 Vulkan 实例(`VkInstance`)时通过 `VkInstanceCreateInfo` 手动指定。例如: ```cpp const char* layers[] = { "VK_LAYER_KHRONOS_validation" }; VkInstanceCreateInfo createInfo = { .enabledLayerCount = 1, .ppEnabledLayerNames = layers, // ... }; ``` - **调试扩展** 通常配合 `VK_EXT_debug_utils` 扩展使用,以接收详细的错误消息。 #### 3. **消息回调** - 通过 `VK_EXT_debug_utils` 注册回调函数,将校验层的错误、警告信息输出到控制台或日志文件: ```cpp VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = { .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, .pfnUserCallback = debugCallback, // 自定义回调函数 }; ``` --- ### **三、校验层的原理** #### 1. **分层架构(Layer Chain)** - Vulkan 的驱动架构允许在运行时动态加载多个校验层,形成一个调用链。每个层可以: - 修改传入的参数(例如填充默认值)。 - 记录调用信息。 - 验证参数合法性。 - 阻止非法调用(通过返回错误或触发断言)。 #### 2. **校验内容示例** 校验层会检查以下常见问题: - **资源泄漏**:未释放的 `VkImage`、`VkBuffer` 等对象。 - **状态冲突**:错误的管线绑定、不兼容的渲染流程。 - **内存管理**:非法内存访问、未映射的内存。 - **同步错误**:缺少内存屏障或信号量。 - **着色器错误**:SPIR-V 模块的合法性。 #### 3. **核心实现** - **钩子函数表(Dispatch Table)** 每个校验层维护一个函数指针表,拦截 Vulkan 函数调用后执行自定义逻辑(如参数检查),再调用下一层的实现。 - **对象追踪** 校验层内部会记录所有 Vulkan 对象(如 `VkDevice`、`VkCommandBuffer`)的生命周期,确保它们的创建和销毁符合规范。 - **线程安全** 多线程环境下,校验层需要确保对共享数据(如对象状态)的访问是线程安全的。 --- ### **四、常用校验层** - **VK_LAYER_KHRONOS_validation** Khronos 官方维护的“全能”校验层,整合了参数校验、线程安全、内存管理等多项功能。 - **VK_LAYER_LUNARG_api_dump** 打印所有 Vulkan API 调用的详细信息(参数、返回值)。 - **自定义校验层** 开发者可以编写自己的层,针对特定需求进行验证。 --- ### **五、性能与使用建议** - **仅限调试阶段** 校验层会显著降低性能(甚至可能使帧率下降 50% 以上),因此仅在开发时启用。 - **按需启用** 根据问题类型选择性启用特定层(例如仅启用内存校验)。 - **结合工具使用** 与 Vulkan 调试工具(如 RenderDoc、Nsight Graphics)配合,提高调试效率。 --- ### **总结** 校验层是 Vulkan 开发中不可或缺的调试工具,通过动态拦截和验证 API 调用,帮助开发者发现隐藏的错误和性能问题。其核心原理基于 Vulkan 的分层架构和钩子函数机制,结合详细的错误报告,极大降低了 Vulkan学习和调试门槛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值