• Advertisement
Sign in to follow this  

Platform-independent multi-threading solution

This topic is 3227 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Our project is trying to set up a basic multi-threaded game loop architecture test application. The test application will work as follows: - thread1: input (poll for events) - thread2: graphics (clear background with a color, render a triangle, then swap buffers) Before going multi-threaded, we used SDL to platform independently create the window. However, it doesn't seem entirely trivial to get SDL to work with multiple threads. In particular, there's no info on what needs to be reinitiated when starting a new thread, or how to properly handle having the graphics in another thread than the input. http://www.libsdl.org/docs/html/thread.html The code listed below compiles and runs properly. However, there are plenty of issues left to solve: 1. when using the currently commented out SetVideoMode line that gives fullscreen instead of windowed mode, the program refuses to terminate when exiting by pressing ESCAPE (instead, we get a blue screen). Removing boost::join(graphicsThread); makes it possible to terminate without blue screen, but a Ctrl+Alt+Del is required instead as it otherwise refuses to terminate. Clearly, it's not acceptable that the game architecture can't be run in full screen... 2. why can't the mutex be locked around the SDL_PollEvent method call, without preventing a window from being created? 3. have any mutexing been forgotten so that there are remaining race conditions? (Conversely, have we been overly cautious below, by locking things that don't need locking?) 4. is SDL at all a good choice for event handling and window setup when going multi-threaded? What's the best alternative if we still don't want to sacrifice platform independency (and preferably want to use a library so as to avoid writing all wrappers from scratch)?
#include <iostream>
#include <boost/thread.hpp>

#include <windows.h>
#include <GL/glew.h>
#include <SDL/SDL.h>
#include <SDL/SDL_main.h>

const std::string GAME_NAME = "Test";

bool running=true;

boost::recursive_mutex sdlAccess;

struct ivec2 {
	ivec2(int x, int y) : x(x), y(y) {}
	int x,y;
};

SDL_Surface *initWindow(const std::string& windowTitle, const ivec2& resolution, int colorDepth, int depthDepth) {
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
	SDL_EnableUNICODE(1);

	SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, depthDepth);
	SDL_Surface *backbuffer;
	backbuffer = SDL_SetVideoMode(resolution.x, resolution.y, colorDepth, SDL_OPENGL | SDL_RESIZABLE);
	//backbuffer = SDL_SetVideoMode(1024, 768, 32, SDL_OPENGL | SDL_RESIZABLE | SDL_FULLSCREEN);

	if(!backbuffer) {
		std::cout << "Could not set GL video mode: " << SDL_GetError() << std::endl;
		SDL_Quit();
		exit(2);
	}
	SDL_WM_SetCaption(windowTitle.c_str(), windowTitle.c_str());
	return backbuffer;
}

struct GraphicsThread {
public:
	void operator()() {
		sdlAccess.lock();
		SDL_Surface *backbuffer = initWindow(GAME_NAME, ivec2(1024, 768), 32, 24);
		sdlAccess.unlock();
		while(running) {
			//"RENDER"
			glClearColor(100,0,100,0);
			glClearDepth(100.0f);
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			glBegin(GL_TRIANGLES);
				glVertex3f( 0.0f, 1.0f, 0.0f);
				glVertex3f(-1.0f,-1.0f, 0.0f);
				glVertex3f( 1.0f,-1.0f, 0.0f);
			glEnd();

			sdlAccess.lock();
			SDL_GL_SwapBuffers();
			sdlAccess.unlock();
		}
		sdlAccess.lock();
		SDL_FreeSurface(backbuffer);
		SDL_Quit();
		sdlAccess.unlock();
	}
};

bool pollEventSafe(SDL_Event *event) {
//	sdlAccess.lock();
	bool result = SDL_PollEvent(event);
//	sdlAccess.unlock();
	return result;
}

int main(int argc, char *argv[]) {
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
	SDL_EnableUNICODE(1);
	GraphicsThread x;
	boost::thread graphicsThread(x);
	while(running) {
		//RECEIVE INPUT
		SDL_Event event;
		//while(SDL_PollEvent(&event)) {
		while(pollEventSafe(&event)) {
			switch(event.type){
				case SDL_KEYDOWN:
					if(event.key.keysym.unicode == SDLK_ESCAPE) {
						std::cout << "Quit!\n";
						running=false;
					}
				break;
				case SDL_QUIT:
					//TODO!
				break;
			}
		}
	}

	graphicsThread.join();

	sdlAccess.lock();
	SDL_Quit();
	sdlAccess.unlock();

	return 0;
}


[Edited by - sb01234 on April 21, 2009 1:27:18 PM]

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by sb01234
4. is SDL at all a good choice for event handling and window setup when going multi-threaded? What's the best alternative if we still don't want to sacrifice platform independency (and preferably want to use a library so as to avoid writing all wrappers from scratch)?


SDL is IMHO a disaster since it is completely tied to global context. Some of its libraries are effectively impossible to multi-thread.

If you have even the slightest option, switch to SFML. It doesn't get around API-specific limitations, but at least the API itself is much more concurrency friendly.

Regardless of which library or API you use, OGL and Window API will need to be called from thread that set them up. There is no trivial way to get around that. But this is limitation of respective APIs, not wrapper libraries.

Share this post


Link to post
Share on other sites
First off, certain SDL calls must happen in the main() thread. SDL_SetVideoMode and SDL_PollEvent() must occur in the same thread.

You may be able to get OpenGL to work in a separate thread once you have the window set up, but you probably would need to use platform specific code to get this to work.

In any case, you aren't going to benefit much from this, as your event loop will spin busy soaking up time you could spend rendering on a single processor machine, and on a multi-processor machine it still won't be doing much. Events are few and far between, they don't really need a thread to themselves. Consider that over 99% of its time will be spent returning from SDL_PollEvent() saying: "I have no new events".

Also, SDL_PollEvent() could be called before SDL_Init(). This is bad.

But anyway, I think that you may be doing it wrong. There are a couple of different ways to approach making a multi-threaded game, with various tradeoffs.

One of the simplest is to create a thread per "sub-system". A sub system could be sound, graphics, physics, game logic, whatever. This isn't too hard to implement, but doesn't give a benefit once you have more cores than sub-systems.

Another basic way is to separate the total computation into "tasks", and have the tasks run on whatever cores are available. This can utilise more cores, but it becomes harder to write. Remember: the more locking you have, you have a potential for your code to behave serially and thus give no benefit.

Another is to use copying, because you don't always need to wait for the most up to date information to become available. For example, you can do physics for frame N on one core, and render frame N - 1 on another core by copying the relevant state. But again, you need to be sure that you have enough physics computation to overcome the cost of setting all this up. Or your AI can do its computation based on where the player was a frame or two ago and still come up with a reasonable result.

A general approach to multi-threading is hard. Passing information between threads without causing corruption is hard. Actually make everything work without risking a crash or deadlock, and actually gaining something in the process is hard.

Here are two links I found in my bookmarks about multi-threading and games. I bookmarked them intending to come back to them some other time, so I can't vouch for them any more than to say they looked interesting at the time.

Gamedev thread.
Multi-core in the Source Engine.

And on an unrelated note, don't call SDL_FreeSurface() on a surface created with SDL_SetVideoMode().

Share this post


Link to post
Share on other sites
Hm, I suspected as much...

[Edited by - sb01234 on April 19, 2009 11:11:38 AM]

Share this post


Link to post
Share on other sites
Quote:
Original post by rip-off
...

The input thread will later contain gameplay and physics simulation as well, and be run with a fixed time step (using a thread-friendly sleep rather than a busy-waiting loop). The graphics thread will, on the other hand, run as often as possible. On top of this, we consider delegating "tasks" to worker threads. The test application is just a way of testing that the window creation API allows input and graphics to run in separate threads (hence the in itself pointless input thread).

Share this post


Link to post
Share on other sites
Has anyone tested if using QT for setting up the basic rendering window and handling input is a viable option?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement