Link

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::vec4glm::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 相机。

triangle

下一步:加载 OBJ 网格