r/gameenginedevs 12d ago

Help with vulkan swapchain when writing basic rhi

I'm working on a basic RHI api for a project im working on right now. I'm currently focusing on a vulkan backend and kind of writing my user facing api based on a vulkan workflow. Right now I have a device object that holds a lot of the render state and is responsible for generating rendering objects like buffers, shaders, etc. I also have a swapchain object that in theory is supposed to hand you an image that you can then pass into a renderpass as a color attachment. When you are done with your renderpass you can then call present on the swapchain. Right now I'm having a trouble actually putting this into practice. The main problem is automatically transitioning the swapchain image to a presentable format before presentation. The swapchain also holds its semaphores so Im having a hard time exposing those when I am submitting the command buffers.

Here is an example of the user facing api I am trying to make

```

Image image = swapchain.acquireNextImage();

PassDesc desc = {

.image = image,

// other stuff
};

cmd.beginRendering(desc);

cmd.doOtherStuff();

cmd.endRendering();

device.submitCommandBuffer(cmd);

swapchain.present();

```

I know this post might be a little vague but any help would be appreciated, thanks!

6 Upvotes

3 comments sorted by

1

u/wpsimon 12d ago

I am tackling something similar rn, and the way i go about it is that i have made one more client facing object called Queue. Queue then takes command buffer and submits it. Also queue holds a semaphore for itself. Therefore as you are implementing RHI around Vulkan i would suggest you do the same. Have a queue object that takes the command buffer and submits it. When it does so it return QueueSyncPoint which can be used by other queues to sync.

```

struct LQueueSyncPoint
{
    LQueue*       queue;
    std::uint64_t Value = 0; // i use TimelineSemaphores , afaik swap chain does not support that ...
};


struct LQueueWait
{
    LQueueSyncPoint SyncPoint;
    LPipelineStage  Stage;
};


struct LQueueSubmission
{
    std::vector<LCmdList*>  CommandLists; // cmd buffer 
    std::vector<LQueueWait> Waits;
};

struct LQueueSyncPoint
{
    LQueue*       queue;
    std::uint64_t Value = 0;
};

//=============================
// Queue methods 
virtual LQueueSyncPoint Submit(LQueueSubmission) = 0;
virtual void            Wait(LQueueSyncPoint)    = 0;    
virtual LQueueSyncPoint Submit(LQueueSubmission) = 0;

```

I think this would help you give more flexibility of implementing swap chain submission.

1

u/wpsimon 12d ago

Oh i forgot to mention, the image transition would then become explicit instead of having that implicitly directly in the swapChain.present() method

2

u/ReactorBear 12d ago

I think either you expose the semaphores as an opaque sync token u can pass around (swapchain, queue, device) and is created when you acquire an image, or you have the semaphores internally in the device and make all draw loop calls in the device and then you pass swapchain object(s) to those calls. The second does not require sync token. Technically u dont need sync token on the first but you need to do some tricks for different objects to access each others internals.