An explanation of threading and virtual reality

OpenGL is not thread-safe. This sounds like a design flaw. In reality, it's more like a design requirement. You want your graphics API to draw frames as quickly and frequently as possible. As you may know, or will soon learn, waiting is something that threads end up doing a lot of the time. If you introduce multithreaded access to your graphics hardware, you introduce periods where the hardware might be waiting on the CPU simply to figure out its thread scheduling and who needs access at the time. It's much simpler and faster to say "only one thread may access the GPU." Technically speaking, as graphics APIs become more advanced (DirectX 12 and Vulkan), this is not strictly true, but we will not be getting into multithreaded rendering in this book.

Let's first take a step back and ask the question, "Why do we need to use threads?" To some of you who are more experienced application developers, the answer should be obvious. But not all programmers need to use threads, and, even worse, many programmers use threads inappropriately, or when they aren't needed in the first place. For those of you still in the dark, a thread is a fancy term for "a way to run two procedures at the same time." On a practical level, the operating system takes control of scheduling threads to run one after another, or on different CPU cores, but as programmers, we assume that all threads are running "simultaneously."

Incidentally, while we are only allowed one CPU thread to control the GPU, the whole point of a GPU is that it is massively multithreaded. Mobile GPUs are still getting there, but high-end Tegra chips have hundreds of cores (currently, the X1 is at 256 cores), lagging behind their desktop equivalents with thousands of cores (Titan Black @ 2880 cores). A GPU is set up to process each pixel (or other similar small datum) on a separate thread, and there is some hardware magic going on that schedules all of them automatically with zero overhead. Think of your render thread as a slow taskmaster instructing a tiny army of CPUs to do your bidding and report back with the results, or in most cases, just draw them right to the screen. This means that the CPU is already doing a fair amount of waiting on behalf of the GPU, freeing your other worker threads to do their tasks and then wait when there is more CPU render work to be done.

Threads are generally useful when you want to run a process which will take a while, and you want to avoid blocking the program's execution, or main, thread. The most common place where this comes up is starting a background process and allowing the UI to continue to update. If you're creating a media encoder program, you don't want it to be unresponsive for 30 minutes while it decodes a video. Instead, you'd like the program to run as normal, allowing the user to click on buttons and see progress updates from the background work. In this scenario, you have to let the UI and background threads take a break now and then to send and check messages passed between the two. Adjusting the length of the break, or sleep time, and thread priority values allows you to avoid one thread hogging too much CPU time.

Back to OpenGL and graphics programming. It is common in a game engine to split the work into a few, distinct threads (render, physics, audio, input, and so on). However, the render thread is always a kind of orchestrator because rendering still tends to be the most time-sensitive job and must happen at least 30 times per second. In VR, this constraint is even more important. We're not worried about physics and audio, perhaps, but we still need to make sure that our renderer can draw things as quickly as possible, or the feeling of presence is lost. Furthermore, we can never stop rendering, as long as the person is looking at the screen. We need threads to avoid "hiccups" or unacceptably long periods between render frames.

Head tracking is essential to a VR experience. A person who is moving their head, looking only at a fixed image, will start to experience nausea, or simsickness. Even some text on a black background, if it is not compensated by some sort of fixed horizon, will eventually cause discomfort. Sometimes, we do have to block the render thread for significant periods of time, and the best option is to first fade the image to a solid color, or void. This can be comfortable for a short period of time. The worst thing that can happen in VR is periodic hiccups or frame rate drops due to extensive work being done on the render thread. If you don't maintain a constant, smooth, frame rate, your VR experience is worthless.

In our case, we need to decode a series of rather large bitmaps and load them into GPU textures. Unfortunately, the decode step takes a few hundred milliseconds and causes those hiccups we were just talking about. However, since this isn't GPU work, it doesn't have to happen on the render thread! If we want to avoid any heavy lifting in our setup(), preDraw(), and postDraw() functions, we should create a thread any time that we want to decode a bitmap. In the case of updating our grid of previews, we should probably just create a single thread, which can run the whole update process, waiting in between each bitmap. In the CPU land, the OS needs to use some resources to schedule threads and allocate their resources. It's much more efficient to just create a single thread to run through the entire job, rather than spinning up and taking down a thread for each bitmap.

Of course, we're going to need to make use of our old friend queueEvent in order to do any graphics work, in this case generating and loading the texture. As it turns out, updating the display of the image is not graphics work, since it just involves changing a value on our material. We do, however, need to wait on the graphics work in order to get this new value. As a result of these optimizations and constraints, we need a locking system in order to allow one thread to wait on the others to finish its work, and to prevent the user from interrupting or restarting this procedure before it has completed. This is what we just implemented in the previous topic.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset