Push Constants
我们现在可以渲染任意网格了,但是我们还不能对它们做太多事情。我们仍然不知道如何从 CPU 向着色器发送顶点缓冲区之外的数据。虽然下一章将详细解释各种方法,但有一种简单的方法我们可以立即开始使用:push constants。
Push constants 允许我们以非常简单且高效的方式向着色器发送少量数据(它的大小有限制)。Push constants 可以将数据发送到任何着色器阶段(顶点着色器和片段着色器),并存储在命令缓冲区本身中。
要使用 push constants,您首先需要在创建 VkPipelineLayout 时设置它们的大小(在每个阶段)。然后,使用命令 vkCmdPushConstants
,数据将被嵌入到命令缓冲区中,并且可以从着色器访问。我们将使用它们来进行变换矩阵,以便能够移动对象并进行正确的 3D 渲染。首先,我们将使用它来移动我们正在渲染的三角形。
New PipelineLayout
我们将需要一个新的 PipelineLayout 来保存 push constant 的大小。为此,我们将在 vulkanEngine 中添加一个新成员。
class VulkanEngine {
public:
///other code...
VkPipelineLayout _meshPipelineLayout;
//other code ...
}
我们还将创建一个结构体来将其保存在 vk_engine.h
中
//add the include for glm to get matrices
#include <glm/glm.hpp>
struct MeshPushConstants {
glm::vec4 data;
glm::mat4 render_matrix;
};
目前,我们只在其中存储一个 glm::vec4
(4 个浮点数)和一个 glm::mat4
(16 个浮点数)。然后我们将使用矩阵来变换三角形。当您创建 push constant 结构体时,适用对齐规则。您可以通过阅读有关 GLSL std430
布局来找到它们的精确规则。这些规则可能非常复杂,因此为了简单起见,我们将自己限制为 glm::vec4
和 glm::mat4
,它们具有简单的对齐规则。Push constants 的最小大小为 128 字节,这对于诸如 4x4 矩阵和一些额外参数之类的常见事物来说已经足够了。
为了在管线中为 push constants 腾出空间,我们在 init_pipelines()
函数内部创建它
//we start from just the default empty pipeline layout info
VkPipelineLayoutCreateInfo mesh_pipeline_layout_info = vkinit::pipeline_layout_create_info();
//setup push constants
VkPushConstantRange push_constant;
//this push constant range starts at the beginning
push_constant.offset = 0;
//this push constant range takes up the size of a MeshPushConstants struct
push_constant.size = sizeof(MeshPushConstants);
//this push constant range is accessible only in the vertex shader
push_constant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
mesh_pipeline_layout_info.pPushConstantRanges = &push_constant;
mesh_pipeline_layout_info.pushConstantRangeCount = 1;
VK_CHECK(vkCreatePipelineLayout(_device, &mesh_pipeline_layout_info, nullptr, &_meshPipelineLayout));
//later ....
//remember to destroy the pipeline layout
_mainDeletionQueue.push_function([=]() {
//other deletions
vkDestroyPipelineLayout(_device, _meshPipelineLayout, nullptr);
});
Push constants 是按范围写入的。一个重要的原因是,您可以在不同的阶段拥有不同的 push constants,位于不同的范围。例如,您可以在顶点着色器上保留 64 字节(1 个 glm::mat4
)大小,然后从偏移量 64 开始像素着色器 push constant。这样,您将在不同的阶段拥有不同的 push constants。鉴于正确设置范围很棘手,在本教程中,我们将采用更整体的方法,在顶点和片段着色器上使用相同的 push constants。
现在我们有了布局,我们还需要修改着色器。让我们将 push constant 添加到 tri_mesh.vert
着色器。
#version 450
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 vNormal;
layout (location = 2) in vec3 vColor;
layout (location = 0) out vec3 outColor;
//push constants block
layout( push_constant ) uniform constants
{
vec4 data;
mat4 render_matrix;
} PushConstants;
void main()
{
gl_Position = PushConstants.render_matrix * vec4(vPosition, 1.0f);
outColor = vColor;
}
push_constant 块必须与我们在 C++ 端拥有的结构体 1 对 1 匹配,否则 GPU 将错误地读取我们的数据。
现在我们可以修改我们创建三角形管线的方式来挂钩新的管线布局。
在 init_pipelines
中,在创建网格管线之前,我们将挂钩新的布局
pipelineBuilder._pipelineLayout = _meshPipelineLayout;
_meshPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
现在我们的网格管线有了 push constants 的空间,所以我们现在可以执行命令来使用它们。
在 draw()
函数中,在 vkCmdDraw
调用之前,我们将计算并写入 push constant。
//make a model view matrix for rendering the object
//camera position
glm::vec3 camPos = { 0.f,0.f,-2.f };
glm::mat4 view = glm::translate(glm::mat4(1.f), camPos);
//camera projection
glm::mat4 projection = glm::perspective(glm::radians(70.f), 1700.f / 900.f, 0.1f, 200.0f);
projection[1][1] *= -1;
//model rotation
glm::mat4 model = glm::rotate(glm::mat4{ 1.0f }, glm::radians(_frameNumber * 0.4f), glm::vec3(0, 1, 0));
//calculate final mesh matrix
glm::mat4 mesh_matrix = projection * view * model;
MeshPushConstants constants;
constants.render_matrix = mesh_matrix;
//upload the matrix to the GPU via push constants
vkCmdPushConstants(cmd, _meshPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(MeshPushConstants), &constants);
//we can now draw
vkCmdDraw(cmd, _triangleMesh._vertices.size(), 1, 0, 0);
您还需要添加
#include <glm/gtx/transform.hpp>
到 vk_engine.cpp
文件,以便您可以使用 glm 变换矩阵函数。
在 push constant 调用中,我们需要设置指向数据的指针及其大小(类似于 memcpy
),以及 VK_PIPELINE_STAGE_VERTEX_BIT
,它匹配我们的数据应该可访问的阶段。如果我们在顶点和片段着色器上都有相同的 push constant,我们将同时拥有这两个标志。
如果您现在运行程序,您将看到三角形在窗口中旋转,上下颠倒。通过修改 camPos
和一般的视图矩阵,您现在可以创建一个 3D 相机。
下一步:加载 OBJ 网格