Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreaded example #5322

Open
junglie85 opened this issue Feb 29, 2024 · 5 comments
Open

Multithreaded example #5322

junglie85 opened this issue Feb 29, 2024 · 5 comments

Comments

@junglie85
Copy link
Contributor

I was wondering if it would be possible to add an example of using the API in a simple multithreaded environment?

As I’ve been trying to multithread an application I’m developing, I’ve noticed that there are a few considerations that are not necessarily obvious and a relatively simple example would help a lot. I was thinking a 2 thread pipelined approach would show how to do this without being too complex, i.e.:

Update 0
Update 1 / Render 0
Update 2 / Render 1
Update n / Render n-1

The main aspects I am struggling to work through where an example would help are:

  • window / surface management across threads - who owns what and when are surface textures acquired (since we can only get the current texture and no others).
  • likely tied in with the first is when to present the surface texture and again which thread has responsibility for doing so.
  • how to synchronise the 2 threads - I spent a long time chasing down an unwrap on a None in Queue because of calling present after a surface texture view had been dropped.
  • how to create resources - should this be multithreaded too? In which case it gets a lot more complicated because you need to make it async with 2-way communication between threads; I’d be happy with synchronous (and is what I currently do).
  • how to send draw commands to the renderer thread - channels, a big struct wrapped in an Arc+Mutex. Channels seem to work fine for me.
  • when to upload data - just send it to the render thread and write using the queue because that goes at the start of submit? Seems to work okay for me.

There are some projects such as Bevy renderer that can help provide an example, but that is not a good simple example. I also realise that there are probably multiple approaches to solving this, but I think having an example of one would help show how to use wgpu well in a multithreaded environment.

I’m happy to contribute but would need someone to be willing to help.

@Wumpf
Copy link
Member

Wumpf commented Mar 1, 2024

Typical use of multithreading would be less about using different threads for different frames: usually you need to react to user input in what you show and having several frames queued up that were created at the same time would just give you weird latency artifacts. Instead the more typical approach would be to have several independently recorded command buffers that are then queued up to form a single frame.

An example that uses several command buffers that are recorded on different threads would be a nice addition I think! But to answer all the questions you listed this would get fairly complex, also because to many of them there's no single answer.

Let me try to give brief ones anyways, but we aware that many of these are qualitative:

window / surface management across threads - who owns what and when are surface textures acquired (since we can only get the current texture and no others).

"main thread only". This is more or less mandated by the OS on some platform where you have a special ui thread that owns the surface texture.

likely tied in with the first is when to present the surface texture and again which thread has responsibility for doing so.
how to synchronise the 2 threads - I spent a long time chasing down an unwrap on a None in Queue because of calling present after a surface texture view had been dropped.

that actually sounds like a bug in wgpu! There should be no situation where you get a crash on unwrap because of a dropped texture. If the queue holds on to it, it should be still alive

how to create resources - should this be multithreaded too? In which case it gets a lot more complicated because you need to make it async with 2-way communication between threads; I’d be happy with synchronous (and is what I currently do).

It doesn't hurt a huge amount to create a handful of resources parallel different threads, but the general advice is to create resources rarely and have them stick around, so it's ideally a non-problem.

how to send draw commands to the renderer thread - channels, a big struct wrapped in an Arc+Mutex. Channels seem to work fine for me.

Channel is usually the way to go in Rust, yep. As mentioned before a good architecture might have just command buffers as a result.

when to upload data - just send it to the render thread and write using the queue because that goes at the start of submit? Seems to work okay for me.

Any thread can do uploads they need, but it does indeed come with a synchronization cost. If you're on native, then buffer mappings may be very beneficial here over the otherwise recommended queue.write_buffer_init

--
if you're still interested in contributing an example, it might be worth coming by the wgpu user channel and talk more about it there :) https://matrix.to/#/#wgpu-users:matrix.org

@junglie85
Copy link
Contributor Author

Thanks for sharing your thoughts.

re the multiple threads per frame comment, I perhaps didn’t explain myself very well. The idea would be that there is a main thread and then a render thread. The main thread would submit a whole bunch of draw commands to the render thread which would then be recorded into a command buffer and submitted. That’s extra to wgpu though, where I guess the main thread would need to do the recording and submit command buffers.

If I’ve understood you correctly, the main thread would need to own the surfaces. So there would be a phase where multiple threads record command buffers, either from a thread pool or scoped thread or similar, then join, finish and submit all encoders/command buffers and present surfaces back on the main thread?

Still interested, will try and find some time to come on over to matrix for a chat. Probably not this side of the weekend though.

@junglie85
Copy link
Contributor Author

"main thread only". This is more or less mandated by the OS on some platform where you have a special ui thread that owns the surface texture.

I couldn’t see any info about this on Surface. Is this documented anywhere?

@Wumpf
Copy link
Member

Wumpf commented Mar 2, 2024

ah apologies, I was mistaken! This is true for the event loop (see winit's event loop https://docs.rs/winit/latest/winit/event_loop/struct.EventLoop.html) but not for the surface really

@lylythechosenone
Copy link
Contributor

To add my input to this, as far as I know it is perfectly fine to use a surface from any thread on most platforms. However, the surface must only be created or destroyed from the main thread in some cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants