vulkano-rs / vulkano

Safe and rich Rust wrapper around the Vulkan API
Apache License 2.0
4.4k stars 435 forks source link

GpuFuture - Redesign RFC #2114

Open LeonMatthes opened 1 year ago

LeonMatthes commented 1 year ago

Hi,

I'm currently using vulkano in a personal project as my main access to Vulkan. In many ways it's so much nicer than normal ash or other direct vulkan access, whilst retaining a tight level of control. Especially coming from OpenGL, whilst Vulkan means more boilerplate, I also finally understand how everything ties together.

One aspect of Vulkano that I've avoided in my project until now is the use of GpuFuture. Whilst the API is simple to use, I also feel it oversimplifies vulkan synchronization and results in some strange behavior like #1705 .

I've spent some time trying to narrow down this disconnect between Vulkan <-> GpuFuture recently, as I'd like to be comfortable using the simpler GpuFuture API (especially as the raw Submit API has been changed after vulkano 0.31).

From my research the main issue seems to be that GpuFuture actually takes care of multiple concerns, that may be better adressed individually:

  1. Buffering multiple queue operations (anything before calling flush).
  2. Handling synchronization between queue operations (then_execute, then_signal_semaphore, then_signal_fence).
  3. Representing an event that will happen on the GPU in the future and cleaning up resources when the event has happened. (cleanup_finished, signal_finished, etc.).

Because GpuFuture takes care of all three of these, it ends up handling none of them perfectly. GpuFuture also ends up creating a tree, instead of a DAG, which is what synchronization with multiple queues actually is.

Of course I'm willing to contribute myself but don't want to waste time implementing something that other maintainers don't agree on. This is why I'm asking for comments and suggestions in this issue before I start implementing.

My current thoughts look something like this:

We can create futures for the vulkan synchronization primitives to allow for manual synchronization. This would also allow us to use type restrictions for inter-future dependencies. e.g.:

Proposed API - triangle example

let (fence, present) = previous_frame_end.take();
fence.wait();

// Result always needs to include either a SemaphoreFuture, FenceFuture or both so that there is some synchronization primitive to wait on.
let acquisition = swapchain.acquire_next_image(SwapchainAcquireInfo {
    signal_semaphore: true,
    ..Default::default()
});

let mut submission_builder = render_queue.submit();
let draw_semaphore = submission_builder
    .after(acquisition.semaphore.take())
    .commands(command_buffer)
    .signal_semaphore();
let draw_fence = submission_builder.submit_with_fence(); // returns a `FenceFuture`

let present_future = present_queue.present(SwapchainPresentInfo {
    wait_for: draw_semaphore,
    ..SwapchainPresentInfo::swapchain_image_index(swapchain, acquisition.image_index)
});
let previous_frame_end = (draw_fence, present_future);

Compare this to the old API:

let acquire_future = acquire_next_image(swapchain.clone(), None); // simplified

let future = previous_frame_end
    .take()
    .unwrap()
    .join(acquire_future)
    .then_execute(queue.clone(), command_buffer)
    .unwrap()
    .then_swapchain_present(
        queue.clone(),
        SwapchainPresentInfo::swapchain_image_index(swapchain.clone(), image_index),
    )
    .then_signal_fence_and_flush();

Whilst the old API is shorter, it also tries to hide some important details:

What this tries to solve

Open Questions

New GpuFuture trait:

Should include the following:

unsafe fn signal_finished(&self);
fn cleanup_finished(&self);
fn finished(&self) -> bool;
// blocking wait until this future has finished (basically drop impl)
// Ideally should only be used with fences.
fn wait(&mut self);
Rua commented 1 year ago

GpuFuture is likely to be deprecated and removed sometime in the future. In Vulkano 0.32, new methods were added to Queue that mirror the plain Vulkan API functions more directly. There are currently only _unchecked variants of these functions (which only show up in the documentation if you enable the document_unchecked feature), there is no safe implementation yet. A safe implementation requires a much more thorough representation of Vulkan synchronization. This is in the works, but it has to start at the bottom, in command buffers. There is currently a new CommandBufferBuilder in development for that purpose, but it will take a while before it's done.

LeonMatthes commented 1 year ago

@Rua thanks for the answer, this makes a lot of sense. I've already found these functions on Queue and the fact that queue takes care of a lot of resource handling already. I'll start using the unchecked functions then for now.

Is there some sort of milestone or issue that tracks these upcoming changes? I'd like to contribute but don't really know what to implement and what the next issues are. 😊 Let me know what some good first issues would be.

Rua commented 1 year ago

I don't think there is anything you can do for the moment. The synchronization code in the new command buffer is quite difficult, and I'm studying how the official validation layer does it, so that I can get an idea what to do for Vulkano. I can list the things I want to do at some point though:

LeonMatthes commented 1 year ago

Taking inspiration from the validation layers makes a lot of sense :)

Alright, if there are any concrete issues apart from CommandBuffers, which you're already working on, let me know. I'll keep an eye out for new issues marked as "easy" or "good first issue"