Search code examples
c++game-enginegame-physicsvulkan

Buffer memory allocation for Dynamic uniform buffer in Vulkan


I want to split this question into 3 parts:-

  1. what is the concept of limits.minUniformBufferOffsetAlignment why do we need to use this to get our required alignment and derive the offset of the buffer from it? can't Vulkan distinguish different data types and their respective alignment automatically?

  2. in Sascha Willems example he calculates the Dynamic alignment for one 4X4 matrix as follows

    size_t uboAlignment = vulkanDevice->properties.limits.minUniformBufferOffsetAlignment
    dynamicAlignment = (sizeof(glm::mat4) / uboAlignment) * uboAlignment + ((sizeof(glm::mat4) % uboAlignment) > 0 ? uboAlignment : 0);
    

    One 4X4 matrix is 64 bytes and the minimum alignment size allowed is 256 bytes but as far as i know his dynamicAlignment calculation results in (64+256 = 320 //which i believe is not allowed in vulkan) i dont quite get how this dynamicAlignment calculation works

  3. how this dynamicAlignment variable reflects to

    VkBufferCreateInfo bufferInfo = {};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    

    and memory mapping something like

    void * data;
    vkMapMemory(device, uniformStagingBufferMemory, 0, sizeof(matrix), 0, &data);
    memcpy(data, &matrix, sizeof(matrix));
    vkUnmapMemory(device, uniformStagingBufferMemory);
    copyBuffer(uniformStagingBuffer, uniformBuffer, sizeof(matrix));
    

Solution

    1. The minimum UBO alignment is a restriction on you. That is, it tells you that the start of every UBO must be some multiple of that alignment. It tells you where you must put your data in memory, in order for the system to use that data.

      That's not something Vulkan can automatically make you do. Vulkan is an explicit API: you live within its restrictions, not vice-versa. Note that OpenGL has the same restriction on UBO data.

    2. This code is quite broken, but it works by accident. The result of (sizeof(glm::mat4) / uboAlignment) is zero, because division of integers will always result in integers. And since minUniformBufferOffsetAlignment is required by the standard to be no less than 256, dividing 64 by this value will always produce zero, rounded down. And 0 * uboAlignment is still zero.

      So the only part of the code left is ((sizeof(glm::mat4) % uboAlignment) > 0 ? uboAlignment : 0) 64 % 256 is 192, which is > 0, so the result is the uboAlignment.

      So it works, but only by accident. He may as well have just used minUniformBufferOffsetAlignment directly.

    3. First, mapping of memory is a very heavyweight operation. You should never map memory just to set a matrix, then immediately unmap it. If you intend to modify memory through mapping, you should keep it mapped until you're ready to delete it. This will not inhibit performance, prevent you from using that memory in accord with the allowed usage methods, or anything else.

      Second, as clearly outlined by the specification, if you create a VkBuffer that can be used as a uniform buffer, the offset you specify must adhere to minUniformBufferOffsetAlignment. Similarly, when using such a buffer as a UBO resource, the offset you provide must be aligned to minUniformBufferOffsetAlignment as well, whether the offset is dynamic or static.