zyrolasting / racket-vulkan

Racket integration with all things Vulkan :boom:
https://sagegerard.com/racket-vulkan-notes-index.html
MIT License
46 stars 4 forks source link

use `#:blocking? #t` for some FFI calls #29

Open dalev opened 2 years ago

dalev commented 2 years ago

Some Vulkan calls may block the calling (OS) thread. E.g., vkWaitForFences, vkDeviceWaitIdle, etc.

Cooperation with Racket's runtime could be improved by adding #:blocking? #t to those procedures' FFI _fun types. Without that, these procedures may prevent the runtime from making progress on other activies such as GC. (Note that this is even true if one were to "silo" Vulkan calls in a dedicated OS thread using racket/place.)

Now, I don't see anything in vk.xml that definitively identifies blocking calls, but maybe it's enough to simply scan for "Wait" in the name of the procedure?

Btw, this is somewhat related to issue #24: with #:blocking? #t, you don't want arguments to these procedures to be moved around in memory.

Fwiw, I can think of potential two work-arounds (untested!):

(1) Use call-in-os-thread to wrap blocking calls, and make-os-async-channel + sync to block in a runtime-friendly manner.

(2) Try extensions like vkGetFenceFdKHR + unsafe-file-descriptor->port. Then sync on the port.

But these both seem unnecessarily complicated (and they don't obviate the need for 'atomic-interior allocations.)

Edit: So, Ryan's talk about how the DB library handles blocking FFI calls is useful. IIUC, a blocking FFI call also blocks any racket (green) threads that were created in that OS thread. So, for a program to remain responsive, it needs blocking FFI calls marked with #:blocking? #t and also to invoke them using call-in-os-thread. (Or avoid the blocking call entirely with an approach like (2).) All that to say, I was wrong to characterize (1) as a work-around; rather, it's a point in the design space that programmers can consider once #:blocking? #t is available.

Aeva commented 2 years ago

You can probably get most or all blocking APIs from vk.xml by searching for commands that list VK_TIMEOUT as a success code, and/or have a uint64_t parameter called timeout. The spec is very precise about how things should be named, so I imagine this is no exception. vkAcquireNextImageKHR is an example of such a function.

Some of these are more important than others to handle correctly though. From a practical standpoint, most graphics applications avoid doing any kind of synchronization with the GPU with ideally the only API stalls being from presenting the final image.

You have the right idea though. Any time your application's render thread would be blocked by such a thing, it is best to run background tasks, and Racket's GC is a good candidate for that sort of thing.