kvark / blade

Sharp and simple graphics library
MIT License
515 stars 33 forks source link

Temporary buffers #12

Open kvark opened 1 year ago

kvark commented 1 year ago

There is often a need to create a buffer knowing that it's going to perish on the next submit. Exposing a nice API for this is, however, not trivial. I could see at least 3 different approaches:

  1. Just add temporary: bool flag to struct BufferDesc and allocate accordingly. This helps with performance of small allocations, but it doesn't have with ergonomics: the user still has to know when to delete the buffers.
  2. Add something like CommandEncoder::create_temporary_buffer, which would automatically manage its lifetime and destroy whenever the underlying command pool is reset. As nice as this sounds from the ergonomics point of view, it has implementation caveats:
    • the logic of tracking temporary buffers is backend-agnostic. We could try moving it into a shared implementation, it's not a lot of code.
    • when inside a transfer pass (or any other), we can't borrow CommandEncoder even immutably... We could re-expose this method on the encoders.
    • in Vulkan backend, the command encoder doesn't have access to memory. Could be exposed?
  3. Expose a buffer belt primitive in a satellite library. Good thing is that it's fairly versatile. Bad thing is that the API is still not as nice.
kvark commented 10 months ago

Somewhat addressed by 36109ea9b1bc471b9e91703935423acc1ccb6f1b with the addition of FramePacer and FrameResources

kvark commented 3 months ago

I've been prototyping this a bit, and still haven't found a concise and efficient solution.

I believe, something like create_temporary_buffer would be ideal for the user. Some of the downside I expressed in the original issue may not really be that important:

The lack of access to memory is still a problem. We could refactor it to have access to memory by adding an indirection, which is probably OK. What bothers me is that this approach wouldn't be most efficient anyway. The most efficient one is to do a buffer belt, basically. Now that's mostly backend-agnostic, and is already exposed. But it's not as nice to use.

There is a reduced scope function that we can expose though - something like CommandEncoder::add_temporary_buffer(buffer). It would attach the lifetime of a buffer to the command encoder, using all the same backend-specific ways to delete it properly. This is straightforward to implement, but it doesn't quite solve the problem. If the user is allocating a buffer themselves, they don't get the "TRANSIENT" flag in Vulkan. One would still have something like a buffer belt, unless they are lazy. And with a buffer belt you don't really want to destroy those buffers, you'd want to re-use them instead. So the new function wouldn't help.