向 vulkan 管道添加额外的 UBO 会停止所有几何渲染

Adding an extra UBO to a vulkan pipeline stops all geometry rendering

我已经按照 www.vulkan-tutorial.com 上的教程进行操作,我正在尝试将 Uniform 缓冲区拆分为 2 个单独的缓冲区,一个用于视图和投影,一个用于模型。然而,我发现一旦我向布局添加另一个缓冲区,即使我的着色器不使用它的内容,也不会渲染任何几何体。我没有从验证层得到任何东西。

我发现如果两个 UBO 是同一个缓冲区,我没有问题。但是,如果我将它们分配给不同的缓冲区,则屏幕上不会出现任何内容。添加了描述符集生成代码。

这是我的布局生成代码。所有值均已正确提交,绑定分别为 0、1 和 2,这反映在着色器代码中。我目前甚至没有在着色器中使用缓冲区中的数据 - 所以它与我实际放入缓冲区中的数据无关。

编辑:已在 RenderDoc 中打开。没有额外的缓冲区,我可以看到正常的 VP 缓冲区及其值。他们看起来很好。如果我添加额外的缓冲区,它不会显示,而且第一个缓冲区的数据也全为零。

描述符集布局生成:

    std::vector<VkDescriptorSetLayoutBinding> layoutBindings;

        /*
        newShader->features includes 3 "features", with bindings 0,1,2.
        They are - uniform buffer, uniform buffer, sampler
        vertex bit, vertex bit, fragment bit

        */

    for (auto a : newShader->features)
    {
        VkDescriptorSetLayoutBinding newBinding = {};
        newBinding.descriptorType = (VkDescriptorType)layoutBindingDescriptorType(a.featureType);
        newBinding.binding = a.binding;
        newBinding.stageFlags = (VkShaderStageFlags)layoutBindingStageFlag(a.stage);
        newBinding.descriptorCount = 1;
        newBinding.pImmutableSamplers = nullptr;

        layoutBindings.push_back(newBinding);
    }

    VkDescriptorSetLayoutCreateInfo layoutCreateInfo = {};

    layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;

    layoutCreateInfo.bindingCount = static_cast<uint32_t>(layoutBindings.size());

    layoutCreateInfo.pBindings = layoutBindings.data();

描述符集生成:

//Create a list of layouts
    std::vector<VkDescriptorSetLayout> layouts(swapChainImages.size(), voa->shaderPipeline->shaderSetLayout);

    //Allocate room for the descriptors
    VkDescriptorSetAllocateInfo allocInfo = {};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = voa->shaderPipeline->descriptorPool;
    allocInfo.descriptorSetCount = static_cast<uint32_t>(swapChainImages.size());
    allocInfo.pSetLayouts = layouts.data();

    voa->descriptorSets.resize(swapChainImages.size());

    if (vkAllocateDescriptorSets(vdi->device, &allocInfo, voa->descriptorSets.data()) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate descriptor sets!");
    }

    //For each set of commandBuffers (frames in flight +1)
    for (size_t i = 0; i < swapChainImages.size(); i++) {

        std::vector<VkWriteDescriptorSet> descriptorWrites;


        //Buffer Info construction
        for (auto a : voa->renderComponent->getMaterial()->shader->features)
        {
            //Create a new descriptor write
            uint32_t index = descriptorWrites.size();

            descriptorWrites.push_back({});

            descriptorWrites[index].dstBinding = a.binding;

            if (a.featureType == HE2_SHADER_FEATURE_TYPE_UNIFORM_BLOCK)
            {
                VkDescriptorBufferInfo bufferInfo = {};

                if (a.bufferSource == HE2_SHADER_BUFFER_SOURCE_VIEW_PROJECTION_BUFFER) 
                {
                    bufferInfo.buffer = viewProjectionBuffers[i];
                    bufferInfo.offset = 0;
                    bufferInfo.range = sizeof(ViewProjectionBuffer);
                }
                else if (a.bufferSource == HE2_SHADER_BUFFER_SOURCE_MODEL_BUFFER)
                {
                    bufferInfo.buffer = modelBuffers[i];
                    bufferInfo.offset = voa->ID * sizeof(ModelBuffer);
                    bufferInfo.range = sizeof(ModelBuffer);
                }

                //The following is the same for all Uniform buffers
                descriptorWrites[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
                descriptorWrites[index].dstSet = voa->descriptorSets[i];

                descriptorWrites[index].dstArrayElement = 0;
                descriptorWrites[index].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
                descriptorWrites[index].descriptorCount = 1;
                descriptorWrites[index].pBufferInfo = &bufferInfo;
            }
            else if (a.featureType == HE2_SHADER_FEATURE_TYPE_SAMPLER2D)
            {
                VulkanImageReference ref = VulkanTextures::images[a.imageHandle];

                VkDescriptorImageInfo imageInfo = {};
                imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
                imageInfo.imageView = ref.imageView;
                imageInfo.sampler = defaultSampler;

                descriptorWrites[index].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
                descriptorWrites[index].dstSet = voa->descriptorSets[i];

                descriptorWrites[index].dstArrayElement = 0;
                descriptorWrites[index].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
                descriptorWrites[index].descriptorCount = 1;
                descriptorWrites[index].pImageInfo = &imageInfo;
            }
            else
            {
                throw std::runtime_error("Unsupported feature type present in shader");
            }
        }

        vkUpdateDescriptorSets(vdi->device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
    }

编辑:这是描述符集绑定代码

    vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

    //Very temporary Render loop. Binds every frame, very clumsy
    for (int j = 0; j < max; j++)
    {

        VulkanObjectAttachment* voa = objectAttachments[j];
        VulkanModelAttachment* vma = voa->renderComponent->getModel()->getComponent<VulkanModelAttachment>();

        if (vma->indices == 0) continue;

        vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, voa->shaderPipeline->pipeline);

        VkBuffer vertexBuffers[] = { vma->vertexBuffer };
        VkDeviceSize offsets[] = { 0 };

        vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

        vkCmdBindIndexBuffer(commandBuffers[i], vma->indexBuffer, 0, VK_INDEX_TYPE_UINT32);

        vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, voa->shaderPipeline->pipelineLayout, 0, 1, &voa->descriptorSets[i], 0, nullptr);


        vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(vma->indices), 1, 0, 0, 0);
    }

    vkCmdEndRenderPass(commandBuffers[i]);

缓冲区更新代码:

    ViewProjectionBuffer ubo = {};

    ubo.view = HE2_Camera::main->getCameraMatrix();

    ubo.proj = HE2_Camera::main->getProjectionMatrix();

    ubo.proj[1][1] *= -1;

    ubo.model = a->object->getModelMatrix();

    void* data;

    vmaMapMemory(allocator, a->mvpAllocations[i], &data);
    memcpy(data, &ubo, sizeof(ubo));
    vmaUnmapMemory(allocator, a->mvpAllocations[i]);
}

std::vector<ModelBuffer> modelBuffersData;

for (VulkanObjectAttachment* voa : objectAttachments)
{
    ModelBuffer mb = {};
    mb.model = voa->object->getModelMatrix();

    modelBuffersData.push_back(mb);


void* data; 
vmaMapMemory(allocator, modelBuffersAllocation[i], &data);
memcpy(data, &modelBuffersData, sizeof(ModelBuffer) * modelBuffersData.size());
vmaUnmapMemory(allocator, modelBuffersAllocation[i]);

在我看来,您在此处设置的偏移量导致 VkWriteDescriptorSet 读取溢出内存:

else if (a.bufferSource == HE2_SHADER_BUFFER_SOURCE_MODEL_BUFFER)
{
    bufferInfo.buffer = modelBuffers[i];
    bufferInfo.offset = voa->ID * sizeof(ModelBuffer);
    bufferInfo.range = sizeof(ModelBuffer);
}

如果你只是每帧更新缓冲区的一部分,你会做这样的事情:

bufferInfo.buffer = mvpBuffer[i];
bufferInfo.offset = sizeof(mat4[]{viewMat, projMat});
bufferInfo.range = sizeof(modelMat);

如果您将模型放在另一个缓冲区中,您可能希望为您的描述符集创建不同的绑定,并且您的 bufferInfo 模型数据将如下所示:

bufferInfo.buffer = modelBuffer[i];
bufferInfo.offset = 0;
bufferInfo.range = sizeof(modelMat);

我发现了问题 - 不是 Vulkan 问题,而是 C++ 语法问题。不管怎样,我都会解释,但如果您以后访问此页面,则可能不是您的问题。

我在循环中生成我的描述符写入。它们存储在一个向量中,然后在循环结束时更新

std::vector<VkDescriptorWrite> descriptorWrites;
for(int i = 0; i < shader.features.size); i++)

{
//Various stuff to the descriptor write
}

vkUpdateDescriptorSets(vdi->device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);

描述符write的一个参数是pImageInfopBufferInfo。这些指向包含该缓冲区或图像的特定数据的结构。我在循环中填写了这些

{//Within the loop above
//...
    VkDescriptorBufferInfo bufferInfo = {};
    bufferInfo.buffer = myBuffer;
    descriptorWrites[i].pBufferInfo = &bufferInfo;
//...
}

因为这些是通过引用传递的,而不是值传递的,更新时的descriptorWrite引用了原始结构中的数据。但是因为原始结构是在循环中创建的,并且 vkUpdateDescriptors 行在循环之外,所以在读取结构时它已经超出范围并被删除。

虽然这会导致未定义的行为,但我只能想象因为在循环结束和更新调用之间没有新变量,内存仍然读取循环中最后一个 descriptorWrite 的内容。所以所有描述符都读取该内存,并将最后一个 descriptorWrite 中的资源推送给它们。只需在循环开始时将 VkDescriptorBufferInfo 放入它们自己的向量中即可解决所有问题。