Do I need separate Graphics and Transfer commands?

I was following this tutorial on Vulkan https://vulkan-tutorial.com/Vertex_buffers/Staging_buffer and I decided to do this bit:

The buffer copy command requires a queue family that supports transfer operations, which is indicated using VK_QUEUE_TRANSFER_BIT. The good news is that any queue family with VK_QUEUE_GRAPHICS_BIT or VK_QUEUE_COMPUTE_BIT capabilities already implicitly support VK_QUEUE_TRANSFER_BIT operations.

The implementation is not required to explicitly list it in queueFlags in those cases. If you like a challenge, then you can still try to use a different queue family specifically for transfer operations. It will require you to make the following modifications to your program:

• Modify QueueFamilyIndices and findQueueFamilies to explicitly look for a queue family with the VK_QUEUE_TRANSFER bit, but not the VK_QUEUE_GRAPHICS_BIT.
• Modify createLogicalDevice to request a handle to the transfer queue
• Create a second command pool for command buffers that are submitted on the transfer queue family
• Change the sharingMode of resources to be VK_SHARING_MODE_CONCURRENT and specify both the graphics and transfer queue families
• Submit any transfer commands like vkCmdCopyBuffer (which we'll be using in this chapter) to the transfer queue instead of the graphics queue

It's a bit of work, but it'll teach you a lot about how resources are shared between queue families.

Are there any benefits of using a second queue only for transfer operations ?

Your question seems... very confused. The "challenge" you tried to do specifically stated to "use a different queue family specifically for transfer operations". But that's not what your code is trying to do; you used the same queue family.

The point of creating a "transfer queue" is to allow you to perform transfer operations without having to submit them as part of another batch. vkQueueSubmit cannot be called on the same queue from multiple threads. So if one thread builds some transfer work, but the other thread is doing some graphics work, if you only have one queue then the two threads must have some communication mechanism to submit that work.

If you have a transfer queue, that inter-thread communication is no longer necessary. Or at least, it is no longer necessary for the basic submission action. Obviously whichever batch of work that uses the results of the transfer commands will have to receive a semaphore to wait on from the thread that submitted the transfer work. But that is usually a less contentious inter-thread communication path, which can easily be done with a lockless queue of some kind.

The challenge in question is specifically talking about a queue family that only has the VK_QUEUE_TRANSFER_BIT flag set (or more specifically, does not have graphics or compute capabilities). Such transfer-only queues are usually tied to specialized hardware that (potentially) perform transfers faster than a graphics queue, but likely with extra limitations (as defined in VkQueueFamilyProperties::minImageTransferGranularity and other properties).

So basically, you're doing the challenge wrong.

Do keep in mind that Vulkan does not require implementations to provide a transfer-only queue. So if you want to take advantage of the presence of such a queue, you also have to take into account the possibility that no such queue exists.

Now, here's the thing: the term "queue family" is used because you can potentially get multiple queues from the same family. VkQueueFamilyProperties::queueCount tells you how many queues a particular family allows you to get.

So if a GPU does not provide a dedicated transfer-only queue, but you still want the asynchronous benefits of submitting transfer operations outside of non-transfer work, you can create a second graphics queue.

Of course, Vulkan also doesn't require implementations to provide more than one graphics or compute queue. So again, you have to have a synchronous fallback plan in case the hardware only provides you with one queue.