engine3d-dev / engine3d

Open-source game engine to be used for developing games
Apache License 2.0
1 stars 3 forks source link

New API for begin and end functions - Vulkan Abstraction Layer #17

Open SpinnerX opened 2 months ago

SpinnerX commented 2 months ago

I am thinking that I do not want functions such as Begin() and End() to be within their respective class instance.

This is because it is common to see begin() and end() function calls. The only thing that this should help is reduce the amount of unecessary functions within their respective class instances.

Vulkan API Example (before abstraction)

Using command buffer, and render pass as an example to give context on why this is important.

Basically what this does is vkBeginCommandBuffer indicates when to start recording command buffers. As vkEndCommandBuffer implies when to end recording command buffers.

In context of render passes, vkCmdBeginRenderPass and vkCmdEndRenderPass indicates how to setup and use the render pass just before the render pass actually performs the task.

The idea behind with abstracting this is to change from doing this and discussing a much better abstracted API that would allow users that either work or improve Engine3D's vulkan abstraction layer would not handle these directly.

VkResult res = vkBeginCommandBuffer(commandBuffer, &beginInfo);

vkCmdBeginRenderPass(commandBuffer, &beginInfo, contents_flag);

//....

vkCmdBeginRenderPass(commandBuffer);

VkResult res = vkEndCommandBuffer(commandBuffer);

New API (with abstraction)

The whole point of this API is users should be able to abstract all of the manual work of setting up and utilizing vulkan features. Minimizing boilerplate, but allowing room for expand-ability upon use of Engine3D's vulkan abstraction layer without limitations.

This is just an example of thinking of implementing submission flow but also allowing the integration of being able to record our command buffers and submitting our render passes as a single workload.

Concern - My concern is figuring out a generic API enough that it could work with synchronization. Such as VkFence, VkSemaphore, and handling various other edge cases (these edge cases require more R&D).

Task/Submission

On the context of the submission/task flow. I do think when you want to submit a task. We also need to think about how we want this to look, work, and think about what are the intentions of further abstracting to this. While still supplying the necessary descriptions of the task so vulkan knows how to handle each submission.

/*
Idea behind this code
We pass in the command buffer we want to record
Then we pass in the recorded command buffer and initiate the render pass process just before we submit to render to the display.

That is at least the intention of this following new API example.
*/
vk::Submit([buffer = m_CommandBuffer](){
      vk::Begin(m_RenderPass);
      // doing operation
      vk::End(m_RenderPass);
});

Synchronization

If we were to think how synchronization may be used in the API, I have an idea at the time of editing this issue.

Would probably have the fence/semaphore be part of the abstraction with the command buffers. Users can either specify in the command buffer itself or set it to nullptr meaning that they will not be using either a fence or semaphore. (Though not sure about this point that I mentioned)

Regarding option 2, might need to think of a better way of handling synchronization with command buffers. By figuring out if these even need to be supplied. (TBD)



// Option 1
vk::VulkanCommandBuffer m_CommandBuffer = vk::VulkanCommandBuffer(1, &fence, semaphore);

// Option 2 - showing first option. One with no fence but with semaphore
vk::VulkanCommandBuffer m_CommandBuffer = vk::VulkanCommandBuffer(1, nullptr, [](){
     VkSemaphoreCreateInfo createInfo = {...};
      VkSemaphore semaphore = {....};
       return semaphore;
});

// Option 2 - showing second option. One with no semaphore but with fence
vk::VulkanCommandBuffer m_CommandBuffer = vk::VulkanCommandBuffer(1, [](){
     VkFenceCreateInfo createInfo = {...};
      VkFence fence = {....};
       return fence
}, nullptr);

vk::Submit([buffer = m_CommandBuffer](){
      vk::Begin(m_RenderPass);
      // doing operation

      // buffer.WaitSemaphoreFinished();
      // buffer.WaitFenceIdle();
      vk::End(m_RenderPass);
});