Link

VkGraphicsPipelineCreateInfo

构建图形管线比构建计算管线要复杂得多。对于计算管线,我们只需要一个着色器模块和管线布局,因此不需要抽象层。但是图形管线包含大量选项,如果没有简化方法,创建它们可能会非常复杂。

因此,我们将创建一个 PipelineBuilder 结构体,用于跟踪所有这些选项,并提供一些更简单的函数来启用/禁用我们想要的功能,并尽可能保持默认设置。许多这些选项在本教程中不会使用,因此尝试缩小范围将很有用。

管线上的某些选项可以设置为动态的,这意味着我们将在绑定管线和记录绘制命令时设置这些选项。例如,我们将视口设置为动态的,因为如果我们将其“烘焙”到管线中,如果我们想更改渲染分辨率,就需要创建新的管线。

在编写构建器之前,让我们看看我们需要填写什么。与创建计算管线需要 VkComputePipelineCreateInfo 一样,图形管线需要 VkGraphicsPipelineCreateInfo 结构体。

typedef struct VkGraphicsPipelineCreateInfo {
    VkStructureType                                  sType;
    const void*                                      pNext;
    VkPipelineCreateFlags                            flags;
    uint32_t                                         stageCount;
    const VkPipelineShaderStageCreateInfo*           pStages;
    const VkPipelineVertexInputStateCreateInfo*      pVertexInputState;
    const VkPipelineInputAssemblyStateCreateInfo*    pInputAssemblyState;
    const VkPipelineTessellationStateCreateInfo*     pTessellationState;
    const VkPipelineViewportStateCreateInfo*         pViewportState;
    const VkPipelineRasterizationStateCreateInfo*    pRasterizationState;
    const VkPipelineMultisampleStateCreateInfo*      pMultisampleState;
    const VkPipelineDepthStencilStateCreateInfo*     pDepthStencilState;
    const VkPipelineColorBlendStateCreateInfo*       pColorBlendState;
    const VkPipelineDynamicStateCreateInfo*          pDynamicState;
    VkPipelineLayout                                 layout;
    VkRenderPass                                     renderPass;
    uint32_t                                         subpass;
    VkPipeline                                       basePipelineHandle;
    int32_t                                          basePipelineIndex;
} VkGraphicsPipelineCreateInfo;

图形管线的规范页面可以在这里找到,可用于详细检查内容。

stageCountpStages 包含 ShaderStageCreateInfo,其中将包含管线上不同阶段的着色器模块。我们将在此处发送我们的片元着色器和顶点着色器。

VkPipelineVertexInputStateCreateInfo 包含顶点属性输入和顶点缓冲区的配置。如果我们正确配置它,我们的顶点着色器将以最佳方式获取顶点属性作为输入。但我们不会使用它,因为我们只是要将数据数组发送到着色器并自己索引它,这允许提高性能的技术,并允许更复杂的压缩数据的顶点格式。这通常被称为“顶点拉取”,即使你做的事情与固定硬件顶点输入等效,在现代 GPU 上它的性能也大致相同。

VkPipelineInputAssemblyStateCreateInfo 包含三角形拓扑的配置。我们使用它来设置管线以绘制三角形、点或线。

VkPipelineTessellationStateCreateInfo 是固定曲面细分的配置。我们不会使用它,并将其保留为空。

VkPipelineViewportStateCreateInfo 包含有关像素将渲染到的视口的信息。这使您可以设置管线将绘制的像素区域。我们将默认它,因为我们将为此使用动态状态。

VkPipelineRasterizationStateCreateInfo 具有关于三角形如何在顶点着色器和片元着色器之间光栅化的信息。它具有深度偏移(用于渲染阴影)的选项,在线框和实体渲染之间切换,以及绘制或跳过背面的配置。

VkPipelineMultisampleStateCreateInfo 允许我们配置多重采样抗锯齿。这是一种通过在三角形边缘多次光栅化片元来提高渲染抗锯齿效果的方法。我们将默认设置为不进行抗锯齿处理,但稍后我们将研究如何使用它。

VkPipelineDepthStencilStateCreateInfo 包含深度测试和模板配置。

VkPipelineColorBlendStateCreateInfo 具有颜色混合和附件写入信息。它用于使三角形透明或其他混合配置。

VkPipelineDynamicStateCreateInfo 配置动态状态。Vulkan 管线的一个主要缺点是它们的配置在创建时是“硬编码”的。因此,如果我们想执行诸如打开和关闭深度测试之类的操作,我们将需要 2 个管线。它甚至硬编码了视口,因此如果我们想更改渲染目标的大小,我们也需要重建所有管线。构建管线是一项非常昂贵的操作,我们希望最大限度地减少使用的管线数量,因为它对性能至关重要。因此,Vulkan 管线的某些状态可以设置为动态的,然后可以在记录命令时在运行时修改配置选项。给定 GPU 支持的动态状态取决于 GPU 供应商、驱动程序版本和其他变量。我们将对视口和裁剪配置使用动态状态,因为几乎所有 GPU 都支持它,并且它消除了在构建管线时硬编码绘制图像分辨率的需要。

VkGraphicsPipelineCreateInfo 采用 VkPipelineLayout,它与构建计算管线时使用的布局相同。

它还接受 VkRenderPass 和子通道索引。我们将不使用它,因为我们使用动态渲染,因此所有与 VkRenderPass 相关的系统都将被完全跳过。相反,我们需要使用添加到其 pNext 链中的 VkPipelineRenderingCreateInfo 扩展 VkGraphicsPipelineCreateInfo。此结构体保存管线将使用的附件格式列表。

让我们开始编写构建器。所有管线代码都将在 vk_pipelines.h/cpp 上。如果您正在查看本章代码,可以在共享文件夹中找到它。

class PipelineBuilder {
public:
    std::vector<VkPipelineShaderStageCreateInfo> _shaderStages;
   
    VkPipelineInputAssemblyStateCreateInfo _inputAssembly;
    VkPipelineRasterizationStateCreateInfo _rasterizer;
    VkPipelineColorBlendAttachmentState _colorBlendAttachment;
    VkPipelineMultisampleStateCreateInfo _multisampling;
    VkPipelineLayout _pipelineLayout;
    VkPipelineDepthStencilStateCreateInfo _depthStencil;
    VkPipelineRenderingCreateInfo _renderInfo;
    VkFormat _colorAttachmentformat;

	PipelineBuilder(){ clear(); }

    void clear();

    VkPipeline build_pipeline(VkDevice device);
}

管线构建器将保存我们需要跟踪的大部分状态,以及颜色附件格式和着色器阶段的数组。实际的 CreateInfo 结构体将从 build_pipeline() 函数完全填充。我们有一个 clear() 函数,它会将所有内容设置为空/默认属性。管线构建器的构造函数将调用它,但拥有 clear 函数很有用,以便我们可以在需要时手动调用它。

让我们首先编写 clear() 函数。

void PipelineBuilder::clear()
{
    // clear all of the structs we need back to 0 with their correct stype

    _inputAssembly = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };

    _rasterizer = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };

    _colorBlendAttachment = {};

    _multisampling = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };

    _pipelineLayout = {};

    _depthStencil = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };

    _renderInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO };

    _shaderStages.clear();
}

我们将在此处设置每个结构体的 .sType,并将其他所有内容保留为 0。这是使用 cpp20 初始化器,因此我们在括号内未编写的参数将默认为 0。Vulkan 中的大多数 Info 结构体都设计为 0 是有效的清除/默认选项,因此这里效果很好。

让我们开始编写 build_pipeline 函数。首先,我们将开始设置一些我们遗漏的 Info 结构体,因为它们不会被配置。

VkPipeline PipelineBuilder::build_pipeline(VkDevice device)
{
    // make viewport state from our stored viewport and scissor.
    // at the moment we wont support multiple viewports or scissors
    VkPipelineViewportStateCreateInfo viewportState = {};
    viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportState.pNext = nullptr;

    viewportState.viewportCount = 1;
    viewportState.scissorCount = 1;

    // setup dummy color blending. We arent using transparent objects yet
    // the blending is just "no blend", but we do write to the color attachment
    VkPipelineColorBlendStateCreateInfo colorBlending = {};
    colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
    colorBlending.pNext = nullptr;

    colorBlending.logicOpEnable = VK_FALSE;
    colorBlending.logicOp = VK_LOGIC_OP_COPY;
    colorBlending.attachmentCount = 1;
    colorBlending.pAttachments = &_colorBlendAttachment;

    // completely clear VertexInputStateCreateInfo, as we have no need for it
    VkPipelineVertexInputStateCreateInfo _vertexInputInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };

我们首先仅使用视口计数填充 VkPipelineViewportStateCreateInfo,而没有其他内容。使用动态视口状态,我们不需要在此处填充视口或模板选项。

然后,我们使用逻辑混合(我们不会使用它)的一些默认选项填充 VkPipelineColorBlendStateCreateInfo,并挂钩 VkPipelineColorBlendAttachmentState 以获取单个附件的混合选项。我们在此处仅支持渲染到一个附件,因此这很好。如果需要绘制到多个附件,则可以将其制成 VkPipelineColorBlendAttachmentState 数组。

让我们继续该函数,并开始填充 VkGraphicsPipelineCreateInfo

    // build the actual pipeline
    // we now use all of the info structs we have been writing into into this one
    // to create the pipeline
    VkGraphicsPipelineCreateInfo pipelineInfo = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
    // connect the renderInfo to the pNext extension mechanism
    pipelineInfo.pNext = &_renderInfo;

    pipelineInfo.stageCount = (uint32_t)_shaderStages.size();
    pipelineInfo.pStages = _shaderStages.data();
    pipelineInfo.pVertexInputState = &_vertexInputInfo;
    pipelineInfo.pInputAssemblyState = &_inputAssembly;
    pipelineInfo.pViewportState = &viewportState;
    pipelineInfo.pRasterizationState = &_rasterizer;
    pipelineInfo.pMultisampleState = &_multisampling;
    pipelineInfo.pColorBlendState = &colorBlending;
    pipelineInfo.pDepthStencilState = &_depthStencil;
    pipelineInfo.layout = _pipelineLayout;

我们将构建器上的所有配置结构体连接起来,并将 _renderInfo 添加到图形管线信息本身的 pNext 中。

接下来是设置动态状态

    VkDynamicState state[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };

    VkPipelineDynamicStateCreateInfo dynamicInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
    dynamicInfo.pDynamicStates = &state[0];
    dynamicInfo.dynamicStateCount = 2;

    pipelineInfo.pDynamicState = &dynamicInfo;

设置动态状态只是用 VkDynamicState 枚举数组填充 VkPipelineDynamicStateCreateInfo。我们现在将使用这两个。

这就是管线所需的一切,所以我们终于可以调用创建函数了。

    // its easy to error out on create graphics pipeline, so we handle it a bit
    // better than the common VK_CHECK case
    VkPipeline newPipeline;
    if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo,
            nullptr, &newPipeline)
        != VK_SUCCESS) {
        fmt::println("failed to create pipeline");
        return VK_NULL_HANDLE; // failed to create graphics pipeline
    } else {
        return newPipeline;
    }

这就是主要创建函数。我们现在需要正确设置选项,因为目前整个管线本质上为空,由于缺少选项,这将导致错误。

void PipelineBuilder::set_shaders(VkShaderModule vertexShader, VkShaderModule fragmentShader)
{
    _shaderStages.clear();

    _shaderStages.push_back(
        vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, vertexShader));

    _shaderStages.push_back(
        vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader));
}

我们首先添加一个函数来设置顶点和片元着色器。我们将它们添加到 _shaderStages 数组中,并带有适当的信息创建,我们已经从构建计算管线中获得了这些信息。

接下来,我们添加一个函数来设置输入拓扑

void PipelineBuilder::set_input_topology(VkPrimitiveTopology topology)
{
    _inputAssembly.topology = topology;
    // we are not going to use primitive restart on the entire tutorial so leave
    // it on false
    _inputAssembly.primitiveRestartEnable = VK_FALSE;
}

VkPrimitiveTopology 具有 VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST、VK_PRIMITIVE_TOPOLOGY_POINT_LIST 等选项。PrimitiveRestart 用于三角形带和线条带,但我们不使用它。

光栅化器状态是一个重要的状态,因此我们将其拆分为几个选项。

void PipelineBuilder::set_polygon_mode(VkPolygonMode mode)
{
    _rasterizer.polygonMode = mode;
    _rasterizer.lineWidth = 1.f;
}

我们需要将 lineWidth 设置为 1.f 作为默认值,然后我们设置多边形模式,该模式控制线框与实体渲染和点渲染。

void PipelineBuilder::set_cull_mode(VkCullModeFlags cullMode, VkFrontFace frontFace)
{
    _rasterizer.cullMode = cullMode;
    _rasterizer.frontFace = frontFace;
}

剔除模式将设置正面和背面剔除的剔除模式。

接下来是设置多重采样状态。我们将默认结构体设置为禁用多重采样。稍后我们可以添加其他函数来启用不同级别的多重采样以进行抗锯齿处理

void PipelineBuilder::set_multisampling_none()
{
    _multisampling.sampleShadingEnable = VK_FALSE;
    // multisampling defaulted to no multisampling (1 sample per pixel)
    _multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
    _multisampling.minSampleShading = 1.0f;
    _multisampling.pSampleMask = nullptr;
    // no alpha to coverage either
    _multisampling.alphaToCoverageEnable = VK_FALSE;
    _multisampling.alphaToOneEnable = VK_FALSE;
}

接下来,我们将添加一个混合模式函数

void PipelineBuilder::disable_blending()
{
    // default write mask
    _colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
    // no blending
    _colorBlendAttachment.blendEnable = VK_FALSE;
}

我们将拥有 disable_blending() 函数,该函数将 blendEnable 设置为 false,但设置正确的写入掩码。稍后我们将添加更多混合模式的函数。我们需要在此处设置适当的 colorWriteMask,以便我们的像素输出将正确写入附件。

现在我们挂钩我们的格式,让我们为深度测试和颜色附件都添加函数。

void PipelineBuilder::set_color_attachment_format(VkFormat format)
{
    _colorAttachmentformat = format;
    // connect the format to the renderInfo  structure
    _renderInfo.colorAttachmentCount = 1;
    _renderInfo.pColorAttachmentFormats = &_colorAttachmentformat;
}

void PipelineBuilder::set_depth_format(VkFormat format)
{
    _renderInfo.depthAttachmentFormat = format;
}

在颜色附件上,管线需要通过指针获取它,因为它需要颜色附件数组。这对于诸如延迟渲染之类的东西很有用,您可以在其中一次绘制到多个图像,但我们尚不需要此功能,因此我们可以将其默认为仅 1 种颜色格式。

我们需要的最后一个函数是禁用深度测试逻辑的函数。

void PipelineBuilder::disable_depthtest()
{
    _depthStencil.depthTestEnable = VK_FALSE;
    _depthStencil.depthWriteEnable = VK_FALSE;
    _depthStencil.depthCompareOp = VK_COMPARE_OP_NEVER;
    _depthStencil.depthBoundsTestEnable = VK_FALSE;
    _depthStencil.stencilTestEnable = VK_FALSE;
    _depthStencil.front = {};
    _depthStencil.back = {};
    _depthStencil.minDepthBounds = 0.f;
    _depthStencil.maxDepthBounds = 1.f;
}

在填充了管线构建器的所有基本功能后,我们现在可以绘制一个三角形了。对于我们的三角形,我们将使用顶点着色器中的硬编码顶点位置,输出将是纯色。

这些是着色器

colored_triangle.vert

#version 450

layout (location = 0) out vec3 outColor;

void main() 
{
	//const array of positions for the triangle
	const vec3 positions[3] = vec3[3](
		vec3(1.f,1.f, 0.0f),
		vec3(-1.f,1.f, 0.0f),
		vec3(0.f,-1.f, 0.0f)
	);

	//const array of colors for the triangle
	const vec3 colors[3] = vec3[3](
		vec3(1.0f, 0.0f, 0.0f), //red
		vec3(0.0f, 1.0f, 0.0f), //green
		vec3(00.f, 0.0f, 1.0f)  //blue
	);

	//output the position of each vertex
	gl_Position = vec4(positions[gl_VertexIndex], 1.0f);
	outColor = colors[gl_VertexIndex];
}

colored_triangle.frag

#version 450

//shader input
layout (location = 0) in vec3 inColor;

//output write
layout (location = 0) out vec4 outFragColor;

void main() 
{
	//return red
	outFragColor = vec4(inColor,1.0f);
}

在我们的顶点着色器中,我们有一个硬编码的位置数组,我们从 gl_VertexIndex 索引到它。这与计算着色器上的 LocalThreadID 的工作方式类似。对于顶点着色器的每次调用,这将是一个不同的索引,我们可以使用它来处理我们的顶点,这将写入固定功能 gl_Position 变量。由于数组的长度仅为 3,如果我们尝试渲染超过 3 个顶点(1 个三角形),这将导致错误。

在我们的片元着色器中,我们将在 layout = 0 处声明一个输出(这连接到渲染过程的渲染附件),并且我们有一个简单的硬编码红色输出。

现在让我们创建绘制此三角形所需的管线和布局。我们正在添加新的着色器文件,因此请确保您重建 CMake 项目并构建 Shaders 目标。

在 VulkanEngine 类中,我们将添加一个 init_triangle_pipeline() 函数,以及一些成员来保存管线及其布局

VkPipelineLayout _trianglePipelineLayout;
VkPipeline _trianglePipeline;

void init_triangle_pipeline();

我们将从 init_pipelines() 函数调用此 init_triangle_pipeline() 函数。

让我们编写该函数。我们将首先将 2 个着色器加载到 VkShaderModules 中,就像我们对计算着色器所做的那样,但这次是更多着色器。

	VkShaderModule triangleFragShader;
	if (!vkutil::load_shader_module("../../shaders/colored_triangle.frag.spv", _device, &triangleFragShader)) {
		fmt::print("Error when building the triangle fragment shader module");
	}
	else {
		fmt::print("Triangle fragment shader succesfully loaded");
	}

	VkShaderModule triangleVertexShader;
	if (!vkutil::load_shader_module("../../shaders/colored_triangle.vert.spv", _device, &triangleVertexShader)) {
		fmt::print("Error when building the triangle vertex shader module");
	}
	else {
		fmt::print("Triangle vertex shader succesfully loaded");
	}
	
	//build the pipeline layout that controls the inputs/outputs of the shader
	//we are not using descriptor sets or other systems yet, so no need to use anything other than empty default
	VkPipelineLayoutCreateInfo pipeline_layout_info = vkinit::pipeline_layout_create_info();
	VK_CHECK(vkCreatePipelineLayout(_device, &pipeline_layout_info, nullptr, &_trianglePipelineLayout));

我们还创建了管线布局。与之前的计算着色器不同,这次我们这里没有推送常量,也没有描述符绑定,所以它实际上只是一个完全空的布局。

现在我们使用之前创建的 Pipeline Builder 创建管线。

	PipelineBuilder pipelineBuilder;

	//use the triangle layout we created
	pipelineBuilder._pipelineLayout = _trianglePipelineLayout;
	//connecting the vertex and pixel shaders to the pipeline
	pipelineBuilder.set_shaders(triangleVertexShader, triangleFragShader);
	//it will draw triangles
	pipelineBuilder.set_input_topology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST);
	//filled triangles
	pipelineBuilder.set_polygon_mode(VK_POLYGON_MODE_FILL);
	//no backface culling
	pipelineBuilder.set_cull_mode(VK_CULL_MODE_NONE, VK_FRONT_FACE_CLOCKWISE);
	//no multisampling
	pipelineBuilder.set_multisampling_none();
	//no blending
	pipelineBuilder.disable_blending();
	//no depth testing
	pipelineBuilder.disable_depthtest();

	//connect the image format we will draw into, from draw image
	pipelineBuilder.set_color_attachment_format(_drawImage.imageFormat);
	pipelineBuilder.set_depth_format(VK_FORMAT_UNDEFINED);

	//finally build the pipeline
	_trianglePipeline = pipelineBuilder.build_pipeline(_device);

	//clean structures
	vkDestroyShaderModule(_device, triangleFragShader, nullptr);
	vkDestroyShaderModule(_device, triangleVertexShader, nullptr);

	_mainDeletionQueue.push_function([&]() {
		vkDestroyPipelineLayout(_device, _trianglePipelineLayout, nullptr);
		vkDestroyPipeline(_device, _trianglePipeline, nullptr);
	});

构建管线后,我们可以将三角形绘制为我们每帧创建的命令缓冲区的一部分。

我们为背景运行的计算着色器需要绘制到 GENERAL 图像布局,但在进行几何体渲染时,我们需要使用 COLOR_ATTACHMENT_OPTIMAL。可以使用图形管线绘制到 GENERAL 布局,但这性能较低,并且验证层会抱怨。我们将创建一个新函数 draw_geometry() 来保存这些图形命令。让我们首先更新绘制循环。

	VK_CHECK(vkBeginCommandBuffer(cmd, &cmdBeginInfo));

	// transition our main draw image into general layout so we can write into it
	// we will overwrite it all so we dont care about what was the older layout
	vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL);

	draw_background(cmd);

	vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

	draw_geometry(cmd);

	//transtion the draw image and the swapchain image into their correct transfer layouts
	vkutil::transition_image(cmd, _drawImage.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
	vkutil::transition_image(cmd, _swapchainImages[swapchainImageIndex], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);

现在填充 draw_geometry 函数

void VulkanEngine::draw_geometry(VkCommandBuffer cmd)
{
	//begin a render pass  connected to our draw image
	VkRenderingAttachmentInfo colorAttachment = vkinit::attachment_info(_drawImage.imageView, nullptr, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);

	VkRenderingInfo renderInfo = vkinit::rendering_info(_drawExtent, &colorAttachment, nullptr);
	vkCmdBeginRendering(cmd, &renderInfo);

	vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _trianglePipeline);

	//set dynamic viewport and scissor
	VkViewport viewport = {};
	viewport.x = 0;
	viewport.y = 0;
	viewport.width = _drawExtent.width;
	viewport.height = _drawExtent.height;
	viewport.minDepth = 0.f;
	viewport.maxDepth = 1.f;

	vkCmdSetViewport(cmd, 0, 1, &viewport);

	VkRect2D scissor = {};
	scissor.offset.x = 0;
	scissor.offset.y = 0;
	scissor.extent.width = _drawExtent.width;
	scissor.extent.height = _drawExtent.height;

	vkCmdSetScissor(cmd, 0, 1, &scissor);

	//launch a draw command to draw 3 vertices
	vkCmdDraw(cmd, 3, 1, 0, 0);

	vkCmdEndRendering(cmd);
}

要绘制我们的三角形,我们需要使用 cmdBeginRendering 开始一个渲染过程。这与我们在上一章为 imgui 所做的事情相同,但这次我们将其指向我们的 _drawImage 而不是交换链图像。

我们执行 CmdBindPipeline,但不是使用 BIND_POINT_COMPUTE,我们现在使用 VK_PIPELINE_BIND_POINT_GRAPHICS。然后,我们必须设置我们的视口和裁剪。这是必需的,因为我们在创建管线时将它们保留为未定义,因为我们使用的是动态管线状态。设置好后,我们可以执行 vkCmdDraw() 来绘制三角形。完成后,我们可以完成渲染过程以结束我们的绘制。

如果您此时运行程序,您应该会看到一个三角形渲染在基于计算着色器的背景之上

triangle

下一步: 网格缓冲区