Vulkan Tutorial 6 统一缓冲区

news2024/11/25 6:34:11

目录

20 layout and buffer

顶点着色器

描述符集布局

21 统一缓冲区

更新统一数据

22 Descriptor pool and sets

描述符池

描述符集

使用描述符集

对齐要求


20 layout and buffer

我们现在可以将任意属性传递给每个顶点的顶点着色器,模型-视图-投影矩阵将其作为顶点数据包含在内,但这是一种内存浪费。资源描述符描述符是着色器自由访问缓冲区和图像等资源的一种方式。我们将设置一个包含转换矩阵的缓冲区,并让顶点着色器通过描述符访问它们。描述符的使用包括三个部分:

  • 管道创建期间指定描述符布局
  • 从描述符池中分配一个描述符集
  • 渲染时绑定描述符集

描述符布局指定管道将访问的资源类型,就像渲染通道指定将访问的附件类型一样。描述符集指定绑定到描述符的实际缓冲区或图像资源,就像帧缓冲区指定要绑定到渲染通道附件的实际图像视图一样。描述符集然后像顶点缓冲区和帧缓冲区一样绑定到绘图命令。

描述符有多种类型,但在本章中我们将使用统一缓冲区对象 (UBO)。

顶点着色器

修改顶点着色器以包含上面指定的统一缓冲区对象。

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};//顶点着色器在 C 结构中拥有的数据

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;
//uniform请注意, ,in和声明的顺序out无关紧要

void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
}

描述符集布局

下一步是在 C++ 端定义 UBO,并在顶点着色器中将此描述符告知 Vulkan

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};

我们需要提供有关着色器中用于管道创建的每个描述符绑定的详细信息,设置一个新函数来定义所有这些信息,称为createDescriptorSetLayout. 它应该在管道创建之前调用

成员变量

//所有描述符绑定都组合到一个 VkDescriptorSetLayout对象中。
    VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout;
    

void createDescriptorSetLayout() {
    VkDescriptorSetLayoutBinding uboLayoutBinding{};
//前两个字段指定binding在着色器中使用的和描述符的类型,它是一个统一的缓冲区对象
    uboLayoutBinding.binding = 0;
    uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    uboLayoutBinding.descriptorCount = 1;//指定数组中值的数量
//我们的 MVP 转换是在一个单一的统一缓冲对象中
//指定在哪个着色器阶段将引用描述符。该字段可以是值或值stageFlags的组合。
//在我们的例子中,我们只引用来自顶点着色器的描述符
    uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
    uboLayoutBinding.pImmutableSamplers = nullptr; // Optional
//该pImmutableSamplers字段仅与图像采样相关的描述符相关


    VkDescriptorSetLayoutCreateInfo layoutInfo{};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;

if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) {
    throw std::runtime_error("failed to create descriptor set layout!");
}
}

我们需要在管道创建期间指定描述符集布局,以告知 Vulkan 着色器将使用哪些描述符。描述符集布局在管道布局对象中指定。修改VkPipelineLayoutCreateInfo 以引用布局对象:

pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;

描述符布局应该在我们创建新的图形管道时保持不变,即直到程序结束:

void cleanup() {
    cleanupSwapChain();

    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);

    ...
}

21 统一缓冲区

为着色器指定包含 UBO 数据的缓冲区,但我们需要先创建此缓冲区。我们将每帧都将新数据复制到统一缓冲区,因此拥有暂存缓冲区并没有任何意义。在这种情况下,它只会增加额外的开销,并且可能会降低性能而不是提高性能。

uniformBuffers, 和添加新的类成员uniformBuffersMemory



std::vector<VkBuffer> uniformBuffers;
std::vector<VkDeviceMemory> uniformBuffersMemory;
std::vector<void*> uniformBuffersMapped;

//创建一个createUniformBuffers在之后调用 createIndexBuffer并分配缓冲区的新函数
void createUniformBuffers() {
    VkDeviceSize bufferSize = sizeof(UniformBufferObject);

    uniformBuffers.resize(MAX_FRAMES_IN_FLIGHT);
    uniformBuffersMemory.resize(MAX_FRAMES_IN_FLIGHT);
    uniformBuffersMapped.resize(MAX_FRAMES_IN_FLIGHT);

//我们在创建后立即映射缓冲区,vkMapMemory以获取一个指针,稍后我们可以将数据写入该指针
//缓冲区在应用程序的整个生命周期内保持映射到此指针。这种技术称为“持久映射”
    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
        createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, uniformBuffers[i], uniformBuffersMemory[i]);

        vkMapMemory(device, uniformBuffersMemory[i], 0, bufferSize, 0, &uniformBuffersMapped[i]);
    }
}

//统一数据将用于所有绘制调用,因此包含它的缓冲区应该只在我们停止渲染时销毁。
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
        vkDestroyBuffer(device, uniformBuffers[i], nullptr);
        vkFreeMemory(device, uniformBuffersMemory[i], nullptr);
    }

更新统一数据

drawFrame里提交下一帧之前调用updateUniformBuffer,此函数将在每一帧生成一个新的变换,使几何体旋转。我们需要包含两个新的标头来实现此功能:

#define GLM_FORCE_RADIANS
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#include <chrono>
//标glm/gtc/matrix_transform.hpp头公开了可用于生成模型变换
//标准chrono库标头公开函数以进行精确计时。
//我们将使用它来确保无论帧速率如何,几何体每秒旋转 90 度。

void updateUniformBuffer(uint32_t currentImage) 
{
    static auto startTime = std::chrono::high_resolution_clock::now();

    auto currentTime = std::chrono::high_resolution_clock::now();
    float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();
}//以秒为单位计算自渲染开始以来以浮点精度计算的时间。

我们现在将在统一缓冲区对象中定义模型、视图和投影转换。模型旋转将是使用变量围绕 Z 轴的简单旋转time

UniformBufferObject ubo{};
ubo.model = glm::rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f));

glm::rotate函数将现有的变换、旋转角度和旋转轴作为参数。构造函数glm::mat4(1.0f)返回一个单位矩阵。使用旋转角度time * glm::radians(90.0f) 达到每秒旋转 90 度的目的。

ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));

对于视图转换,我决定以 45 度角从上方查看几何体。该glm::lookAt函数将眼睛位置、中心位置和上轴作为参数。

ubo.proj = glm::perspective(glm::radians(45.0f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f);

ubo.proj[1][1] *= -1;GLM 最初是为 OpenGL 设计的,其中剪辑坐标的 Y 坐标是倒置的。最简单的补偿方法是翻转投影矩阵中 Y 轴比例因子的符号。

22 Descriptor pool and sets

描述符池

不能直接创建描述符集,它们必须像命令缓冲区一样从池中分配.我们将编写一个新函数createDescriptorPool 来设置它。

VkDescriptorPoolSize poolSize{};
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
//描述符集将包含哪些描述符类型以及它们的数量
poolSize.descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

//我们将为每一帧分配这些描述符之一。此池大小结构由 main 引用VkDescriptorPoolCreateInfo:

VkDescriptorPoolCreateInfo poolInfo{};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
//指定可以分配的描述符集的最大数量

poolInfo.maxSets = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);

//添加一个新的类成员来存储描述符池的句柄并调用 vkCreateDescriptorPool创建它
if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &descriptorPool) != VK_SUCCESS) {
    throw std::runtime_error("failed to create descriptor pool!");
}

描述符集

我们现在可以自己分配描述符集。为此添加一个函数:createDescriptorSets 

std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo{};
//指定要分配的描述符池、要分配的描述符集的数量以及它们基于的描述符布局
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
allocInfo.pSetLayouts = layouts.data();

//添加一个类成员来保存描述符集句柄并分配它们 vkAllocateDescriptorSets
descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
if (vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data()) != VK_SUCCESS) {
    throw std::runtime_error("failed to allocate descriptor sets!");
}

您不需要显式清理描述符集,因为它们会在描述符池被销毁时自动释放。调用 vkAllocateDescriptorSets将分配描述符集,每个描述符集都有一个统一的缓冲区描述符。

void cleanup() {
    ...
    vkDestroyDescriptorPool(device, descriptorPool, nullptr);

    vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
    ...
}

 现在已经分配了描述符集,但仍然需要配置其中的描述符

//添加一个循环来填充每个描述符:

for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
    VkDescriptorBufferInfo bufferInfo{};
    bufferInfo.buffer = uniformBuffers[i];
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(UniformBufferObject);
}

//前两个字段指定要更新的描述符集和绑定。
VkWriteDescriptorSet descriptorWrite{};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = descriptorSets[i];
descriptorWrite.dstBinding = 0;//统一的缓冲区绑定索引0
descriptorWrite.dstArrayElement = 0;

//指定描述符的类型。可以一次更新数组中的多个描述符
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;

descriptorWrite.pBufferInfo = &bufferInfo;//该pBufferInfo字段用于引用缓冲区数据的描述符
descriptorWrite.pImageInfo = nullptr; // Optional引用图像数据的描述符
descriptorWrite.pTexelBufferView = nullptr; // Optional引用缓冲区视图的描述符

vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, nullptr);

使用描述符集

我们现在需要更新recordCommandBuffer函数,以实际将每个帧的正确描述符集绑定到着色器中的描述符vkCmdBindDescriptorSets。这需要在调用之前完成vkCmdDrawIndexed:

vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[currentFrame], 0, nullptr);

与顶点和索引缓冲区不同,描述符集不是图形管道所独有的。因此,我们需要指定是否要将描述符集绑定到图形或计算管道。下一个参数是描述符所基于的布局。接下来的三个参数指定第一个描述符集的索引、要绑定的集的数量以及要绑定的集的数组。最后两个参数指定用于动态描述符的偏移量数组。 

 再次运行您的程序,您现在应该看到以下内容:

如果projection没有乘-1

 

 

对齐要求

到目前为止我们忽略的一件事是 C++ 结构中的数据应该如何与着色器中的统一定义匹配。

struct UniformBufferObject {
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};
//4*4*4 = 64 对齐
//model偏移量为0,view偏移量为 64,proj偏移量为 128。所有这些都是 16 的倍数

layout(binding = 0) uniform UniformBufferObject {
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

//尝试将结构和着色器修改为如下所示:

struct UniformBufferObject {
    glm::vec2 foo;
    glm::mat4 model;
    glm::mat4 view;
    glm::mat4 proj;
};
//开头vec2,其大小仅为 8 个字节
//可以使用alignasC++11 中引入的说明符

layout(binding = 0) uniform UniformBufferObject {
    vec2 foo;
    mat4 model;
    mat4 view;
    mat4 proj;
} ubo;

重新编译你的着色器和你的程序并运行它,你会发现你到目前为止工作的彩色方块已经消失了!那是因为我们没有考虑对齐要求

Vulkan 期望结构中的数据以特定方式在内存中对齐,例如:

  • 标量必须按 N 对齐(= 4 个字节,给定 32 位浮点数)。
  • vec2必须对齐 2N(= 8 字节)
  •  vec3 or vec4必须对齐 4N(= 16 字节)
  • 嵌套结构必须按其成员的基本对齐方式对齐(四舍五入为 16 的倍数)。
  • 矩阵mat4必须与 vec4具有相同的对齐方式。

 可以使用alignasC++11 中引入的说明符

一种方法可以在大多数时候不必考虑这些对齐要求。我们可以GLM_FORCE_DEFAULT_ALIGNED_GENTYPES在包含 GLM 之前定义:

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEFAULT_ALIGNED_GENTYPES
#include <glm/glm.hpp>

不幸的是,如果您开始使用嵌套结构,此方法可能会失效。考虑 C++ 代码中的以下定义:

struct Foo {
    glm::vec2 v;
};

struct UniformBufferObject {
    Foo f1;
    Foo f2;
};


struct Foo {
    vec2 v;
};

layout(binding = 0) uniform UniformBufferObject {
    Foo f1;
    Foo f2;
} ubo;

//在这种情况下f2,将有一个偏移量8,而它应该有一个16偏移
//因为它是一个嵌套结构。在这种情况下,您必须自己指定对齐方式:

struct UniformBufferObject {
    Foo f1;
    alignas(16) Foo f2;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【干货分享】一篇文章帮你搞定前端高频面试题

前言 如今前端技术日新月异。对于前端开发人员来说&#xff0c;不仅需要掌握最新的前沿技术&#xff0c;还需要保持对基础知识的熟练掌握。而面试则是进入优秀企业的必经之路。在面试中&#xff0c;高频面试题的掌握是获得成功的关键。本文将为大家总结前端高频面试题及其答案&…

网络爬虫是什么

网络爬虫又称网络蜘蛛、网络机器人&#xff0c;它是一种按照一定的规则自动浏览、检索网页信息的程序或者脚本。网络爬虫能够自动请求网页&#xff0c;并将所需要的数据抓取下来。通过对抓取的数据进行处理&#xff0c;从而提取出有价值的信息。 认识爬虫 我们所熟悉的一系列…

23种设计模式之备忘录模式(Memento Pattern)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的备忘录模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大佬…

高精度电压源如何设计出来的

高精度电压源是一种用于提供高精度电压的电子设备&#xff0c;通常用于测量和控制系统。高精度电压源的设计是一个复杂的过程&#xff0c;需要考虑多个因素&#xff0c;包括电路设计、元件选型、测量误差、稳定性等。下面将从电路设计和元件选型两个方面&#xff0c;详细介绍高…

如果通过Map转换成指定的class类

文章目录 前言如果通过Map转换成指定的class类1. 依赖2. 前期准备3. 测试 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话…

自然语言处理实战项目8- BERT模型的搭建,训练BERT实现实体抽取识别的任务

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下自然语言处理实战项目8- BERT模型的搭建&#xff0c;训练BERT实现实体抽取识别的任务。BERT模型是一种用于自然语言处理的深度学习模型&#xff0c;它可以通过训练来理解单词之间的上下文关系&#xff0c;从而为下游…

spring boot--web响应

2. 响应 前面我们学习过HTTL协议的交互方式&#xff1a;请求响应模式&#xff08;有请求就有响应&#xff09; 那么Controller程序呢&#xff0c;除了接收请求外&#xff0c;还可以进行响应。 2.1 ResponseBody 在我们前面所编写的controller方法中&#xff0c;都已经设置了…

spring集成mybatis

目录 (1)新建javaEE web项目 ​(2)加入相关依赖的坐标 (3) 创建相应的包和类 (4) 配置spring和mybatis的配置文件 在resources中建mybatis-config.xml 在 resources中建spring.xml 在 resources中建db.xml 在 resources中建config.propertis 集成mybatis配置 ,导入myb…

MyBatis参数传递(提供ParamNameResolver类来进行参数封装)源码分析

MyBatis接口方法中可以接收各种各样的参数&#xff0c;MyBatis底层对于这些参数进行不同的封装处理方式。 单个参数&#xff1a;实体类、Map集合、Collection、List、Array以及其他类型。 多个参数&#xff1a;Param注解定义的名称要与sql语句中参数占位符中的名称相同。 这里…

RDD缓存有哪些特点?

RDD之间进行相互迭代计算(Transformation的转换)&#xff0c;当执行开启后&#xff0c;新RDD的生成&#xff0c;代表老RDD的消失。RDD的数据是过程数据&#xff0c;只在处理的过程中存在&#xff0c;一旦处理完成&#xff0c;就不见了。这个特性可以最大化的利用资源&#xff0…

【CSAPP】Binarybomb 实验(phase_1-6+secret_phase)

Binarybomb 实验&#xff08;phase_1-6secret_phase&#xff09; 实验内容 一个“binary bombs”&#xff08;二进制炸弹&#xff0c;下文将简称为炸弹&#xff09;是一个Linux可执行C程序&#xff0c;包含了7个阶段&#xff08;phase1~phase6和一个隐藏阶段&#xff09;。炸…

【CANoe示例分析】0002_SOMEIPDemo

该工程由Vector官方提供,作为仿真SOME/IP节点的示例。Demo中介绍了两种仿真SOME/IP节点的方法,一种是基于arxml数据库的仿真,另一种是没有数据库(arxml、fibex)的仿真。 无论是哪种形式的仿真,如果想要通过CAPL程序接收或者发送SOME/IP信息,都需要添加交互 层的信息,这…

不懂就问:年薪百万的程序员是怎么做到的?

很多人对程序员的第一反应就是“工资高”。 从行业平均薪酬来看&#xff0c;“程序员”相关专业的收入确实更高一点。 但是&#xff0c;“程序员”内部薪资却存在着很大的差异&#xff0c;多数人月薪在1-2万&#xff0c;一线城市可以达到3-5万&#xff0c;而顶级程序员&#…

探索Java面向对象编程的奇妙世界(六)

⭐ 多态(polymorphism)⭐ 对象的转型(casting)⭐ 抽象类⭐ 接口 interface ⭐ 多态(polymorphism) 多态指的是同一个方法调用&#xff0c;由于对象不同可能会有不同的行为。现实生活中&#xff0c;同一个方法&#xff0c;具体实现会完全不同。 比如&#xff1a;同样是调用人“吃…

回归方程的显著性检验——F检验

回归方程的显著性检验——F检验 9.2 回归方程的显著性检验 (edu-edu.com.cn) 概念 记号&#xff1a; y i y_i yi​&#xff1a;真实值&#xff0c;观测值 y ˉ \bar{y} yˉ​&#xff1a;真实值的平均值 y ^ \hat{y} y^​&#xff1a;估计值&#xff0c;预测值 几个差&#x…

Activiti、Flowable与CCFlow的选型对比

前言 工作流是什么&#xff0c;这个问题我们就不在此进行解释了&#xff0c;这里我们主要讲解一下Activiti、Flowable和CCFlow三款工作流的对比&#xff0c;为大家选型时做一些参考。 Activiti和Flowable大家可能多少都听说过&#xff0c;都是国外的工作流引擎&#xff0c;都…

Axure教程—单色面积图(中继器)

本文将教大家如何用AXURE制作单色面积图 一、效果介绍 如图&#xff1a; 预览地址&#xff1a;https://icg26y.axshare.com/ 下载地址&#xff1a;https://download.csdn.net/download/weixin_43516258/87837919?spm1001.2014.3001.5503 二、功能介绍 简单填写中继器内容即…

软件设计师总结-含括学习方法和学习过程,可参考

目录 考前备战宏观    心路历程-感受    学习阶段-计划的安排 微观一、课本和视频的学习    本阶段的目的:    侧重点    涉及的学习方法&#xff08;最后有如何使用这些方法&#xff09;    学习结果 二、32小时通关辅助前面的知识点    本阶段的…

提升企业管理效率的利器——ADManager Plus

在当今信息时代&#xff0c;企业的规模和复杂性不断增长&#xff0c;管理各个方面变得愈发具有挑战性。而在企业管理中&#xff0c;活跃目录&#xff08;Active Directory&#xff09;起着至关重要的作用。它是一种用于组织内部的用户、计算机、组和其他对象进行集中管理的目录…

javascript中的this与函数讲解

前言 javascript中没有块级作用域&#xff08;es6以前&#xff09;&#xff0c;javascript中作用域分为函数作用域和全局作用域。并且&#xff0c;大家可以认为全局作用域其实就是Window函数的函数作用域&#xff0c;我们编写的js代码&#xff0c;都存放在Window函数内&#x…