【问题标题】:Adding an extra UBO to a vulkan pipeline stops all geometry rendering向 vulkan 管道添加额外的 UBO 会停止所有几何渲染
【发布时间】:2020-04-21 01:27:54
【问题描述】:

我已经按照www.vulkan-tutorial.com 的教程进行操作,我正在尝试将统一缓冲区拆分为 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]);

【问题讨论】:

  • 实际使用vkCmdBindDescriptorSets绑定描述符集的代码在哪里?是否有任何验证层消息?您是否通过 RenderDoc 之类的工具运行应用程序来检查数据是否正确上传到统一缓冲区?
  • 之前没用过render doc,可以试试。没有验证层消息。我现在添加绑定代码
  • RenderDoc 是一个非常棒的工具,用于监视您的管道状态,并且很容易连接到 Vulkan(取决于您的 SDK/版本/等)。具体来说,您需要捕获一个帧并检查您希望在屏幕上看到像素的绘制调用的管道状态。 UBO 状态在 VS 阶段下可见。这里有一个很棒的快速入门指南(不需要 VR):developer.oculus.com/blog/…
  • 已在 RenderDoc 中打开。如果没有额外的缓冲区,我可以看到正常的 VP 缓冲区及其值。他们看起来很好。如果我添加额外的缓冲区,它不会显示出来,而且来自第一个缓冲区的数据也全为零。有任何想法吗?将复制到新读者的问题中
  • 也许你写错了缓冲区?您可以添加实际更新缓冲区的代码吗?

标签: c++ graphics vulkan


【解决方案1】:

我发现了问题——可悲的是,不是 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);

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

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

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

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

【讨论】:

  • 这比你想象的更常见
  • 我很欣赏它足够普遍,因此发布了这个帖子,但很失望这不是 Vulkan 问题,因为这个帖子或多或少只会被有 Vulkan 错误的人发现
  • 不,我的意思是,在 vulkan 上工作的人得到这个错误是非常普遍的。
【解决方案2】:

在我看来,您在此处设置的偏移量导致 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);

【讨论】:

  • MODEL_BUFFER 中的模型指的是模型矩阵,而不是模型数据,我不清楚。我希望有一个充满模型矩阵的缓冲区,并且每个描述符集中的偏移量将引用该对象的正确模型矩阵。我开始怀疑这是否可能。因为我目前只使用一个对象进行测试,所以我已经将偏移量硬编码为 0,不幸的是我仍然得到问题中描述的结果
猜你喜欢
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
相关资源
最近更新 更多