More Threads.

posted in Not dead...
Published February 14, 2010
Advertisement
While I do have a plan to follow up on my last entry with some replies and corrections (I suggest reading the comments if you haven't already) my last attempt to do so made it to 6 pages and 2500+ words so I need to rethink it a bit I think [grin]

However, this week I have some very much 'work in progress' code which is related to my last entry on threading. This is very much an experiment (and a very very basic one right now) so keep that firmly in mind when reading the below [grin]

As my last entry mentioned I toying with the idea of using a single always active rendering thread which was fed a command list and just dumbly executed it in order. While this is doable with current D3D9 and D3D10 tech (where your command list is effectively a bunch of functions to be called/states to be set) D3D11 makes this much easier with its deferred contexts.

In addition to this with the release of the VS2010 RC the Concurrency Runtime also moves towards final and provides us with some tools to try this out, namely Agents.

Agents, in the CR are defined as;
Quote:
The Agents Library is a C++ template library that promotes an actor-based programming model and in-process message passing for fine-grained dataflow and pipelining tasks.


This allows you to set them off and then have them wait for data to appear before processing it and then passing on more data to another buffer and so on.

In this instance we are using an agent as a consumer of data for rendering, with a little feedback to the submitting thread to keep things sane.

Agents are really easy to use, which is an added bonus, simply inherit from the Concurrency::Agents class, impliment the 'run' method and you are good to go. At which point its just a matter of calling 'start' on an instance and away it goes.

In this instance I have an Agent which sits in a tight loop, reading data from a buffer and then, post-present, writing a value back which tells the sending thread it is ready for more data. The latter command is to try and prevent the data sender from throwing too much work at the thread in question so that it gets too far behind on frames (most likely in a v-sync setup where your update loop is taking < 16ms to process. For example if your loop took 4ms then you could write 4 frames before the renderer had processed one).

The main loop for the agent is, currently, very simple;
void RenderingAgent::run(){	Concurrency::asend(completionNotice, 1);	bool shouldQuit = false;	while(!shouldQuit)	{		RendererCommand command = Concurrency::receive(commandList);				switch(command.cmdID)		{		case EDrawingCommand_Quit:			shouldQuit = true;			break;		case EDrawingCommand_Present:			g_pSwapChain->Present(1, 0);			Concurrency::asend(completionNotice, 1);			break;		case EDrawingCommand_Render:			g_pImmediateContext->ExecuteCommandList(command.cmd, FALSE);			SAFE_RELEASE( command.cmd );			break;		}	}	done();}


The first 'asend' is used to let the data submitter know the agent is alive and ready for data, at which point it enters the loop and blocks on the 'recieve' function.

As soon as data is ready at the recieve point the agent is woken up and can process it.

Right now we can only understand 3 messages;
- Quit: which terminates the renderer, calling 'done' to kill the agent
- Present: which performs a buffer swap and, once that is done, tells the data sender we are ready for more data
- Render: which uses a D3D11 command list to do 'something'

'Render' will be the key to this as a D3D11 Command list can deal with a whole chunk of rendering without us having to do anything besides call it and let the context process it.

The main loop itself is currently just as simple;
Concurrency::unbounded_buffer commandList;	Concurrency::overwrite_buffer<int> completionNotice;	RenderingAgent renderer(commandList,completionNotice);	renderer.start();	RendererCommand present(EDrawingCommand_Present, 0);	g_pd3dDevice->CreateDeferredContext(0, &g_pDeferredContext);	// Single threaded update type message loop	DWORD baseTime = timeGetTime();	while(WM_QUIT != msg.message)	{		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))		{			TranslateMessage(&msg);			DispatchMessage(&msg);		}		else		{			DWORD newTime = timeGetTime();			if(newTime - baseTime > 16)			{				Concurrency::receive(completionNotice);				float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f };				g_pDeferredContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor);				ID3D11CommandList * command = NULL;				g_pDeferredContext->FinishCommandList(FALSE, &command);				Concurrency::asend(commandList, RendererCommand(EDrawingCommand_Render, command));				Concurrency::asend(commandList, present);				baseTime = newTime;			}			SwitchToThread();		}	}	RendererCommand quitCommand (EDrawingCommand_Quit, 0);	Concurrency::send(commandList, quitCommand);	Concurrency::agent::wait(&renderer);


The segment starts by creating two buffers for command sending and sync setup.

The 'unbound_buffer' allows you to place data into a quque for agents to pull out later. The 'overwrite_buffer' can store one value only, with any new messages overwriting the old ones.

After that we create out agent and start it up and create a 'present' command to save us from doing it in the loop. Next a deferred context is created and we go into the main loop.

In this case its hard coded to only update at ~60fps, although changing the value to '30' from '16' does drop the framerate down to ~30fps (both values were checked with PIX).

After that we check to see if the renderer is done and if so clear the screen, save that command list off, construct a new RendererCommand containing the pointer and send it of to the renderer. After that it passes the present command to the renderer and goes back around.

The final section is the shut down which is simply a matter of sending the 'quit' command to the renderer and waiting for the agent to enter its 'done' state.

At which point we should be free to shut down D3D and exit the app.

The system works, at least for simple clear screen setups anyway, I need to expand it a bit to allow for proper drawing [grin] althought thats more a case of loading the shaders and doing it than anything else.

The RendererCommand itself is the key link;
- This needs to be 'light' as it is copied around currently. If this proves a problem then pulling them from a pool and passing a pointer might be a better way to go in the future. Fortuntely such a change is pretty much a template change and a couple of changes from '.' to '->' in the renderer.

- The RendererCommand is expandable as well; right now it is only one enum value and a pointer, however it could be expanded to include function pointers or other things which could be dealt with in the renderer itself. This would allow you to send functions to the renderer which execute D3D commands instead of just saved display lists.

One of the key things with this system will be the use of a bucket sorted command list where each bucket starts with a state setup for that bucket to be processed (saved as a command list) and then each item in the bucket just setups its local state and then does its rendering.

I'm not 100% sure on how I'm going to handle this as yet however.

I'm also currently toying with making the main game loop an agent itself, effectively pushing the message loop into its own startup/window thread and using that to supply the game agent with input data.

There is however another experiment I need to do first with groups of tasks, requeueing and joining them to control program flow as my biggest issues are;
- how to control the transistion between the various stages of data processing, specifically when dealing with entity update and scene rendering.
- how to control when scene data submission occurs. This is more than likely going to end up as a task which runs during either the 'sync' or 'update' phase as at that point the data for the rendering segment should be queued up and sorted ready to go.

So, still a few experiments and problems to solve, but as this works I finish the weekend feeling good about some progress [grin]
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement

Latest Entries

Advertisement