5
\$\begingroup\$

I am making a voxel game (a bit like Minecraft) in C++(11), and I've come across an issue with creating a world efficiently.

In my program, I have a World class, which holds a 3D array of Region class pointers. When I initialize the world, I give it a width, height, and depth so it knows how large of a world to create. Each Region is split up into a 32x32x32 area of blocks, so as you may guess, it takes a while to initialize the world once the world gets to be above 8x4x8 Regions. In order to alleviate this issue, I thought that using threads to generate different levels of the world concurrently would make it go faster.

Having not used threads much before this, and being still relatively new to C++, I'm not entirely sure how to go about implementing one thread per level (level being a xz plane with a height of 1), when there is a variable number of levels.

I tried this:

for(int i = 0; i < height; i++)
{
    std::thread th(std::bind(&World::load, this, width, height, depth));
    th.join();
}

Where load() just loads all Regions at height "height". But that executes the threads one at a time (which makes sense, looking back), and that of course takes as long as generating all Regions in one loop.

I then tried:

std::thread t1(std::bind(&World::load, this, w, h1, h2 - 1, d));
std::thread t2(std::bind(&World::load, this, w, h2, h3 - 1, d));
std::thread t3(std::bind(&World::load, this, w, h3, h4 - 1, d));
std::thread t4(std::bind(&World::load, this, w, h4, h - 1, d));

t1.join();
t2.join();
t3.join();
t4.join();

This works in that the world loads about 3-3.5 times faster, but this forces the height to be a multiple of 4, and it also gives the same exact VAO object to every single Region, which need individual VAOs in order to render properly. The VAO of each Region is set in the constructor, so I'm assuming that somehow the VAO number is not thread safe or something (again, unfamiliar with threads).

So basically, my question is two one-part:

  1. How to I implement a variable number of threads that all execute at the same time, and force the main thread to wait for them using join() without stopping the other threads?
  2. How do I make the VAO objects thread safe, so when a bunch of Regions are being created at the same time across multiple threads, they don't all get the exact same VAO? Turns out it has to do with GL contexts not working across multiple threads. I moved the VAO/VBO creation back to the main thread. Fixed!

Here is the code for block.h/.cpp, region.h/.cpp, and CVBObject.h/.cpp which controls VBOs and VAOs, in case you need it.

If you need to see anything else just ask.

EDIT: Also, I'd prefer not to have answers that are like "you should have used boost". I'm trying to do this without boost to get used to threads before moving onto other libraries.

\$\endgroup\$
  • \$\begingroup\$ How about using opencl bindings for c++ ? Uses N threads each M of them is packed in threadblocks. You can use your gpu too, if it supports. I mean if you have N workitems, you can connect those items through M threads using local memory or global memory. \$\endgroup\$ – huseyin tugrul buyukisik Oct 31 '13 at 15:23
  • \$\begingroup\$ @huseyintugrulbuyukisik seriously your advise doesn't make any sense. OpenCL and threads are Two different programming paradigms converting to openCL not only will change the whole implementation, but he will also face new (probably harder) problems to solve something with a tool that he doesn't need in the first place! \$\endgroup\$ – concept3d Oct 31 '13 at 16:24
1
\$\begingroup\$

Re your first question - you could create your variable number of threads and store each one in a container (eg a vector).

When you need all the data, you can do a join on each one - a for loop perhaps. See the answer to this question. It uses boost but the idea is similar.

\$\endgroup\$
  • \$\begingroup\$ The question you linked is a nice simple solution that works with std::thread as well. As I did not need a complicated solution, this worked rather well. Thanks. \$\endgroup\$ – sm81095 Oct 31 '13 at 18:25
4
\$\begingroup\$

How to I implement a variable number of threads that all execute at the same time?

Build/use a container class (I would call it a ThreadPool) for holding threads or thread references (note that std::thread is only movable and not copyable), each time one of the threads is started it is not joined by default. Wehn ThreadPool is going to be destructed you need to join each thread until it's finished.

How to force the main thread to wait for them using join() without stopping the other threads?

It seems you need a way to communicate between threads, joining one will let you wait for it. A better idea would be not to join any thread and let the main thread block (or do other things) and wait for a signal/event from the ThreadPool when all worker threads finished their work.

\$\endgroup\$
2
\$\begingroup\$
  1. I do not understand the problem. If you do not call the join(), they won't be waited for. You should call the join() only right before you absolutely need the data they produce. The other threads wont be stopped if you call join() on one of them. See the join() reference: http://www.cplusplus.com/reference/thread/thread/join/
  2. VAOs are not shared in between contexts. You need to pass the VBOs to the drawing thread where you will create the VAOs and bind the VBOs in them.
\$\endgroup\$
  • \$\begingroup\$ The VAOs and VBOs are created in the region constructor, since the Region holds an instance of it's mesh, which is only rebuilt if the blocks in the Region change. I specifically avoided recreating the VAO every single time the Region is drawn. As for having only one GL context available, I moved the CVBObject creation to a seperate method, which I plan to call in a loop back in the main thread AFTER all of the Regions have been created and the threads have closed. Will this work? \$\endgroup\$ – sm81095 Oct 31 '13 at 2:26
  • \$\begingroup\$ You can reuse VAOs too, they do not need to be recreated every time. Just unbind old stuff and bind new stuff in. And will that work? I do not know as I am not really that familiar with your code. You should try and see and post results. \$\endgroup\$ – Lasse Oct 31 '13 at 2:30
  • \$\begingroup\$ Oh and you do not really have to do that every frame if the VBO data does not change. \$\endgroup\$ – Lasse Oct 31 '13 at 2:34
  • \$\begingroup\$ I placed the code for the creation of the VAOs and VBOs into a separate method called CreateMeshHandler(), which is called in the World constructor immediately after the 4 .join() lines. However, it throws a runtime error "Access violation reading location 0xFDFDFDFD" as soon as it reaches the first iteration of the loop that calls the method for all Regions in the world. \$\endgroup\$ – sm81095 Oct 31 '13 at 2:36
  • \$\begingroup\$ As I said I am not familiar with your code, so I can not help you by guessing what your code does. Also I am C# guy and I've never used OpenGL with C++, so I do not know what could produce that error. My GUESS is that there is something wrong with your VAO or VBO creation / initialization. \$\endgroup\$ – Lasse Oct 31 '13 at 2:43
1
\$\begingroup\$

No one seems to have told you what the mistake in your first approach was:

Calling join() right after each threads creation means your main thread waits for each thread before he creates the next one. Obviously this results in a sequential execution, not a parallel one. You need to maintain a list or something of the threads, and call join() on each of them after the creation loop.

Expressed in pseudocode, this is what i mean:

for(...){ createThreads }
for(...){ joinThreads }
\$\endgroup\$
  • 1
    \$\begingroup\$ it seems you didn't read the other answers \$\endgroup\$ – concept3d Oct 31 '13 at 9:00
  • \$\begingroup\$ True, @kutschkem, the other answers didn't spell it out like that, but they did fix the problem. \$\endgroup\$ – ThorinII Oct 31 '13 at 9:40
  • \$\begingroup\$ This is the same solution that @Thorinll linked, so thanks for posting this as well. \$\endgroup\$ – sm81095 Oct 31 '13 at 18:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.