2
votes

I want to pass a uniform buffer object containing two 4x4 matrices to the vertex shader. I have declared the struct in C++ as follows:

struct PerRenderUBO
{
    glm::mat4 viewProjection;
    glm::mat4 projection; //unused
};

And in GLSL:

layout(std140, set = 0, binding = 0) uniform UBO {
    mat4 viewProjection;
    mat4 projection; //unused
} perRenderUBO;

However, as soon as I declare more than one member in my UBO struct, some objects get drawn incorrectly due to a wrong viewProjection matrix. If i comment out the 'projection' data member in both the PerRenderUBO struct and the GLSL declaration, everything renders correctly (although I didn't even use the projection in the shader).

This leads me to believe that there must be something wrong with the data alignment. I've declared the layout with the std140 annotation. sizeof(PerRenderUBO) returns 128. I've tried to declare 'projection' as float and the problem persists.

Here are the most important parts of the descriptor set creation:

/** Create Layout **/
vk::DescriptorSetLayoutBinding perRenderUBOBinding;
perRenderUBOBinding.binding = 0;
perRenderUBOBinding.descriptorCount = 1;
perRenderUBOBinding.descriptorType = vk::DescriptorType::eUniformBuffer;
perRenderUBOBinding.stageFlags = vk::ShaderStageFlagBits::eVertex;
perRenderUBOBinding.pImmutableSamplers = nullptr;

std::vector<vk::DescriptorSetLayoutBinding> bindings{perRenderUBOBinding};

vk::DescriptorSetLayoutCreateInfo createInfo;
createInfo.bindingCount = bindings.size();
createInfo.pBindings = bindings.data();
this->perRenderUBOLayout = vkDevice->createDescriptorSetLayout(createInfo);

/** Create host-visible and host-coherent buffer **/
vk::BufferCreateInfo bufferCreateInfo;
bufferCreateInfo.size = sizeof(PerRenderUBO);
bufferCreateInfo.usage = vk::BufferUsageFlagBits::eUniformBuffer;
bufferCreateInfo.sharingMode = vk::SharingMode::eExclusive;
bufferCreateInfo.queueFamilyIndexCount = queueFamilyIndices.size();
bufferCreateInfo.pQueueFamilyIndices = queueFamilyIndices.data();
this->buffer = vkDevice->createBuffer(bufferCreateInfo);

vk::MemoryRequirements memoryRequirements = vkDevice->getBufferMemoryRequirements(this->buffer);
//allocates device memory as proposed in the specification (10.2 Device Memory)
this->bufferDeviceMemory = allocate(memoryRequirements, vk::MemoryPropertyFlags{vk::MemoryPropertyFlagBits::eHostVisible} | vk::MemoryPropertyFlags{vk::MemoryPropertyFlagBits::eHostCoherent});
vkDevice->bindBufferMemory(this->buffer, this->bufferDeviceMemory, 0);


this->descriptorPool = ...//create a descriptor pool for 1 uniform buffer
this->descriptorSet = ...//allocate descriptor set for above layout

vk::DescriptorBufferInfo bufferInfo;
bufferInfo.buffer = this->buffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(PerRenderUBO);

vk::WriteDescriptorSet writeDescriptorSet;
writeDescriptorSet.dstSet = *this->descriptorSet;
writeDescriptorSet.dstBinding = 0;
writeDescriptorSet.dstArrayElement = 0;
writeDescriptorSet.descriptorType = vk::DescriptorType::eUniformBuffer;
writeDescriptorSet.descriptorCount = 1;
writeDescriptorSet.pBufferInfo = &bufferInfo;
writeDescriptorSet.pImageInfo = nullptr;
writeDescriptorSet.pTexelBufferView = nullptr;

vkDevice->updateDescriptorSets({writeDescriptorSet}, {});

Before executing the primary draw command buffer, I update the buffer for the PerRenderUBO as follows:

std::vector<PerRenderUBO> data; //contains 1 instance of PerRenderUBO
vk::DeviceSize offset = 0;
vk::DeviceSize size = data.size() * sizeof(PerRenderUBO);

void* memory = vkDevice->mapMemory(this->bufferDeviceMemory, offset, size);
std::memcpy(memory, data.data(), size);
this->deviceMemory->unmap();

I've checked buffer sizes and offsets several times and everything looks fine. Also, due to the fact that the same descriptor set is bound for every draw command and some objects render correctly, I believe that the data in the buffer itself must be correct. What am I missing?

1
Can you also add the part that actually uploads the ubo data?Sascha Willems
I've added the code for buffer creation and for the buffer update.user3067395
You only have one PerRenderUBO instance, but you talk about rendering multiple objects. Do you just have one draw per command buffer (since you map/memcpy/unmap just before submitting the command buffer)? If so, what ensures that you don't overwrite the UBO contents for draw N+1 before draw N has completed?Jesse Hall

1 Answers

1
votes

The fact that some objects are drawn incorrectly and some correctly may suggest a problem with synchronization or with buffer not being updated on time.

First You need to inform the driver which parts of the buffer were updated by the host. I don't see such code and You don't mention anything about it. This is done by flushing a memory - You need to call the vkFlushMappedMemoryRanges() function.

You may also need to set a barrier which informs the driver that the buffer was accessed by the host. But as far as I remember such barrier is set implicitly on command buffer submission.