Advertisement

VertexBuffer offset of sub-allocation

Started by January 23, 2019 04:22 AM
4 comments, last by Laval B 6 years ago

I recently made the jump from OpenGL to vulkan.

Following a tutorial i was able to display a simple colored triangle on the screen. 

Now, given that the tutorial was creating unique buffers for each allocation (vertex/Indexbuffer) i started implementing the vulkan memory allocator from AMD.

The allocator seems to work, but i'm currently stumbling upon an issue in binding the vertex- and index buffer.

Originally the buffers where bound with this code:


VkBuffer vertexBuffers[] = { vertexBuffer };
		VkDeviceSize offsets[] = { 0 };//offset is in both cases zero, as the vertex and indexbuffer have seperate buffers
		vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

		vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16);

But after using the vulkan memory allocator, the memory was sub-allocated and thus buffers could be reused between object but the offset/startingposition of the specific data has to be taken into account.

So i rewrote the code like this:


//in this case the vertex and indexbuffer share the same VkBuffer object but the offset is different
VkBuffer vertexBuffers[] = { vertexBuffer.buffer };
		VkDeviceSize offsets[] = { vertexBuffer.allocationInfo.offset };
		vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

		vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer.buffer, indexBuffer.allocationInfo.offset, VK_INDEX_TYPE_UINT16);


//=======

		//uniforms
		vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, nullptr);
		//-------
		vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);


		vkCmdEndRenderPass(commandBuffers[i]);

Starting the c++ project does render the triangle correctly, but the validation layers throw this message:


validation layer: vkCmdDrawIndexed() index size (2) * (firstIndex (0) + indexCount (6)) + binding offset (256) = an ending offset of 268 bytes,
which is greater than the index buffer size (12). The Vulkan spec states: (indexSize * (firstIndex + indexCount) + offset) must be less than or 
equal to the size of the bound index buffer, with indexSize being based on the type specified by indexType, where the index buffer, indexType, and 
offset are specified via vkCmdBindIndexBuffer (https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VUID-
vkCmdDrawIndexed-indexSize-00463)

 

I'm confused why it compares the index size * index Count + offset with the index buffer size. (which in this case is 12 bytes). The offset itself should be just an offset and not be counted towards the buffer size. Am i setting the parameters incorrectly? Are the offset parameters the offset for the starting position in the buffer for the information?

I looked up the function calls in the vulkan documentation but i can't seem to find the issue here. (Unless i'm mixing up the offsets somehow).

Hi

14 hours ago, Lewa said:

But after using the vulkan memory allocator, the memory was sub-allocated and thus buffers could be reused between object but the offset/startingposition of the specific data has to be taken into account.

2

I think you misunderstand the offset for binding a buffer to memory and the offset into a buffer. I have never worked with the memory allocator from AMD but typically a large section of memory is allocated and parts of that memory are bound to different buffers. The binding to the memory takes an offset. The buffer now occupies the memory starting at this offset.

If you use the buffer all offsets into the buffer start at the location in memory that the buffer is bound to. The offset in the memory is no longer needed as it is implicitly a part of the buffer. So if you want to bind an index buffer the offset that you have to give is the offset into the buffer and not into memory. In your case, this should be 0, as the indices start at offset 0 in the buffer.

From the source of the VmaAllocationInfo struct:

Quote

/** \brief Offset into deviceMemory object to the beginning of this allocation, in bytes. (deviceMemory, offset) pair is unique to this allocation.
It can change after call to vmaDefragment() if this allocation is passed to the function, or if allocation is lost.
*/
VkDeviceSize offset;

7

The offset in the VmaAllocationInfo is the offset into memory and not the buffer.

Advertisement

Looking at my code it seems that i somehow mixed up buffer creation and memory allocation.

(I thought that the memory allocator creates one large buffer and makes multiple small allocations, the reverse of what is happening.) Looking back at it my assumption doesn't make any sense though.

 

I also figured out what was causing those issues. I not only applied the offsets in the bind calls but also in the vkBufferCopy command during the transfer of the data from a stagingbuffer to the GPU:


	VkBufferCopy copyRegion = {};
	copyRegion.srcOffset = 0;//srcBuffer.allocationInfo.offset; << Fixed
	copyRegion.dstOffset = 0;//dstBuffer.allocationInfo.offset; << fixed
	copyRegion.size = size;
	vkCmdCopyBuffer(commandBuffer, srcBuffer.buffer, dstBuffer.buffer, 1, &copyRegion);

 

So, from what i'm understanding is that we allocate bigger chunks of memory and create individual buffers (with the requested size) which are then bound to specific locations in those memory blocks. I suppose the buffer is storing the offset which has to be applied to the allocated memory block behind the scenes? (and thus we don't have to worry about it in the bind/copy,etc... calls?)

And the offsets are (as you said) only the offset of the buffer (starting from the given location to which the buffer was bound to with "vkBindBufferMemory"), and thus should be 0 in most scenarios (unless we want for example only copy a specific region of the buffer).  Is this correct?

Basically yes.

There are a few resources from both Nvidia and AMD that go into detail about how you should allocate for maximum performance if you are interested

Nvidia:
https://developer.nvidia.com/vulkan-memory-management

AMD:
https://gpuopen.com/vulkan-device-memory/

TLDR: Allocate a big chunk of memory and suballocate buffers and textures from it. Nvidia also mentions that allocating one giant buffer and storing all data there(uniforms, vertices, and indices) is the best way. You then have to use the offset more often.

You also have to take care of alignment requirements when doing sub-allocations as well as affinity. You can take a look at DOOM3 BFG source code. They have a class called idVulkanAllocator in the Vulkan renderer that is very simple to understand.

We think in generalities, but we live in details.
- Alfred North Whitehead

This topic is closed to new replies.

Advertisement