1
votes

I've followed the tutorial at www.vulkan-tutorial.com and I'm trying to split the Uniform buffer into 2 seperate buffers, one for View and Projection and one for Model. I've found however once I add another buffer to the layout, even if my shaders don't use it's content, no geometry is rendered. I don't get anything from the validation layers.

I've found that if the two UBOs are the same buffer, I have no problem. But if I assign them to different buffers, nothing appears on the screen. Have added descriptor set generation code.

Here's my layout generation code. All values are submitted correctly, bindings are 0, 1 and 2 respectively and this is reflected in shader code. I'm currently not even using the data in the buffer in the shader - so it's got nothing to do with the data I'm actually putting in the buffer.

Edit: Have opened up in RenderDoc. Without the extra buffer, I can see the normal VP buffer and it's values. They look fine. If I add in the extra buffer, it does not show up, but also the data from the first buffer is all zeroes.

Descriptor Set Layout generation:

    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();

Descriptor Set Generation:

//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);
    }

Edit: Here is descriptor set binding code

    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]);

Buffer updating code:

    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]);
2
Where is the code that actually binds the descriptor sets using vkCmdBindDescriptorSets? Are there any validation layer messages? And did you run your application through a tool like RenderDoc to check if data is properly uploaded to the uniform buffers?Sascha Willems
Not used render doc before, I can give it a try. No validation layer messages. I'll add the binding code nowFigwig
RenderDoc is a really incredible tool for spying on your pipeline state, and it's easy to hook up in Vulkan (depending on your SDK/version/etc). Specifically, you'll want to capture a frame and examine the pipeline state for the draw calls for which you are expecting to see pixels on the screen. UBO state is visible under the VS stage. There's a great quickstart guide here (VR not necessary): developer.oculus.com/blog/…Aaron Hull
Have opened up in RenderDoc. Without the extra buffer, I can see the normal VP buffer and it's values. They look fine. If I add in the extra buffer, it does not show up, but also the data from the first buffer is all zeroes. Any ideas? Will copy into question for new readersFigwig
Maybe you're writing to the wrong buffers? Can you add the code that actually updates your buffers?Sascha Willems

2 Answers

2
votes

I found the problem - not a Vulkan issue but a C++ syntax one sadly. I'll explain it anyway but likely to not be your issue if you're visiting this page in the future.

I generate my descriptor writes in a loop. They're stored in a vector and then updated at the end of the loop

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);

One parameter of the descriptor write is pImageInfo or pBufferInfo. These point to a struct that contains specific data for that buffer or image. I filled these in within the loop

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

Because these are passed by reference, not value, the descriptorWrite when being updated refers to the data in the original struct. But because the original struct was made in a loop, and the vkUpdateDescriptors line is outside of the loop, by the time that struct is read it's out of scope and deleted.

While this should result in undefined behaviour, I can only imagine because there's no new variables between the end of the loop and the update call, the memory still read the contents of the last descriptorWrite in the loop. So all descriptors read that memory, and had the resources from the last descriptorWrite pushed to them. Fixed it all just by putting the VkDescriptorBufferInfos in a vector of their own at the start of the loop.

0
votes

It looks to me like the offset you're setting here is causing the VkWriteDescriptorSet to read overflow memory:

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

If you were only updating part of a buffer every frame, you'd do something like this:

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

If you place the model in another buffer, you probably want to create a different binding for your descriptor set and your bufferInfo for your model data would look like this:

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