🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

OpenGL loading in a second thread causing stutter in main thread?

Started by
20 comments, last by wintertime 3 years, 5 months ago

So I went and did it. I added a secondary OpenGL context to my project so I can load scenes in a separate thread from main. It actually wasn't too big of a deal to implement what I have so far. Basically on initial load I pull the scene into the main thread, then any scene load after that is pushed off to a secondary thread with a separate OpenGL context (made current on the executing thread). Once the secondary thread has finished, I then swap the context used by the main thread with the context that was used on the secondary thread.

This allows a scene to remain responsive to a user while they are in game, while the next scene loads in the background. However, I've noticed that while the active scene does remain responsive, there is a rather severe intermittent stutter/jump.

This is what I'm currently attempting to track down a solution for if one exists and I had a question I was hoping someone more knowledgeable with opengl than myself might be able to answer.

Since this loading is happening in a separate thread, I would think that the main thread should continue to execute at the same rate without being affected by the processing in the secondary thread, however since that's not the case, the only thing I could think of that may be causing this would be if the secondary thread was locking some memory that the main thread was attempting to access for drawing, causing the main thread to have to wait.

After reviewing my code, I could not find any cases where I'm explicitly accessing the same memory values between threads, EXCEPT MAYBE the opengl calls as I'm not well versed with what's actually going on under the hood when these calls are made. For example, lets say my main thread which is working off it's context is calling ‘glDrawElements’ every tick, and my secondary thread is generating and loading new buffers in it's own context via ‘glGenVertexArrays’, ‘glBindVertexArray’, ‘glBufferData’, etc.

Would the fact that my secondary thread is loading data into a separate context from my main thread still have an effect on my main thread, potentially causing the issue described above? If not, does anyone have any other ideas as to what may be causing this?

Thanks! ?

Advertisement

I attempt to do the same things but probably safer to avoid putting any real-time scene render related functions to the secondary thread. Instead, I will start on non-timing-critical functions such as Navier-Stoke simulations (ping-pong render) to the second thread.

My guess is the security model of your pc doesn't allow quick access to shared memory. Check out Intel TBB.

https://software.intel.com/content/www/us/en/develop/tools/oneapi/components/onetbb.html

Data parallel vs. Function parallel.

I may be misunderstanding, but if you are swapping the loading thread context with the main thread( I"m assuming the main thread is also the draw thread) without explicitly making sure that all pending work in that context is complete then you will certain see the behavior you are describing. The fact that an OpenGL call return immediately does not necessarily imply that the CPU/GPU doesn't have work related to the said task to do. You are added ‘work’ to a ‘queue' and certain task may be deferred by the driver, especially resource creation task such as texture etc. Instead of swapping the context, its would probably be better to use some for of synchronization ( combination of fences and cpu sync primitives ) to signal that the resource is completely loaded and ready to use.

The bottleneck could be the CPU/GPU communication. BindBuffer and bufferData call. If you only have one GPU, both main thread and secondary thread are sharing that single communication pipe. IMO, that's why Nvidia comes out with CUDA programming model.

https://www.infoworld.com/article/3299703/what-is-cuda-parallel-programming-for-gpus.html#:~:text=CUDA%20is%20a%20parallel%20computing,parallelizable%20part%20of%20the%20computation.

Note that even if MainThread has nothing to do, it must call BindBuffer and BufferData per tick.

cgrant said:

I may be misunderstanding, but if you are swapping the loading thread context with the main thread( I"m assuming the main thread is also the draw thread) without explicitly making sure that all pending work in that context is complete then you will certain see the behavior you are describing. The fact that an OpenGL call return immediately does not necessarily imply that the CPU/GPU doesn't have work related to the said task to do. You are added ‘work’ to a ‘queue' and certain task may be deferred by the driver, especially resource creation task such as texture etc. Instead of swapping the context, its would probably be better to use some for of synchronization ( combination of fences and cpu sync primitives ) to signal that the resource is completely loaded and ready to use.

That is correct, I am swapping the loading thread context with the main thread once the loading thread has completed. The main thread is also the draw thread. Once the contexts are switched the stutter is no longer visible. It's as if something is causing the main thread to slow way down every so many random frames. I see no rendering artifacts or anything of that sort. I'll see if I can get a short video uploaded that would probably make things much more clear.

@mr.otakhi

mr.otakhi said:

The bottleneck could be the CPU/GPU communication. BindBuffer and bufferData call. If you only have one GPU, both main thread and secondary thread are sharing that single communication pipe. IMO, that's why Nvidia comes out with CUDA programming model.

https://www.infoworld.com/article/3299703/what-is-cuda-parallel-programming-for-gpus.html#:~:text=CUDA%20is%20a%20parallel%20computing,parallelizable%20part%20of%20the%20computation.

Note that even if MainThread has nothing to do, it must call BindBuffer and BufferData per tick.

This would make sense

Calling BindBuffer every frame should not result in slowdown, BufferData would in some case if for ex orphaning is not used. But if you are calling BufferData every frame then you may have to rethink the approach used to upload data. Also regardless of what programming model is used, the GPU is still a shared resource. Granted, the OpenGL specification impose limits on the liberties the driver can take under certain circumstances.

I have uploaded a video to youtube that shows what I'm attempting to describe. Also the following is a link to my GPU class. The main thread calls draw() every frame. When a new scene is loaded in a new thread, primeGPU() is invoked, then data is loaded via loadShader(), loadMesh(), and loadTexture(). When loading is complete (tracked in a separate class), the main loop swaps scenes and opengl contexts.

That is bizarre, what else are you doing per-frame besides drawing ? Any buffer uploads going on ?

cgrant said:

That is bizarre, what else are you doing per-frame besides drawing ? Any buffer uploads going on ?

I actually just updated my post above yours with my gpu class if you'd like to take a look. I do no buffer uploads in the main render thread. Once the gpu has been primed with data, that's the data it has until another scene is swapped in.

This topic is closed to new replies.

Advertisement