Sign in to follow this  
dm_3x

Multi-threading synchronisation (maybe) issue on graphic problem

Recommended Posts

dm_3x    100
I opened a thread originally on OpenGL section and here due to no one can give me an answer of what is wrong. However, after a couple more days of debugging, I believe the problem should be somewhere related to the multi-threading problem rather than OpenGL. I'm on VS 2005 (C program). Again, I am used the main thread, start with WinMain function, to create 2 child threads. The first one creates the actual MAIN window, and the 2nd one runs the OpenGL render function. Also, these 2 thread header file don't see one of another, only the main thread source code points to both header file. As a result, the main thread has to call the child, main window, thread to get the HWND and HDC. I've found that due to timing issue sometimes the HWND and HDC aren't yet initialized! I fixed that problem, and the situation still remain -- the background is still null. So, I tried to move the SwapBuffers inside the main window thread instead of the render thread. And I do see some messy thing are shown on the window this time, which made me think that probably it's the synchronisation issue instead. Anyway, I know OpenGL calls is not thread safe, but that's probably an old thing and I'm not calling OpenGL function from all the threads, but just 1. So, it shouldn't be an OpenGL issue at all. And again, if the window thread is not a child thread, but a main thread instead; the graphic works completely fine. However, just not working under that 2 child threads situation. So, I think it's something to do with either synchronisation or some with the window programming issue. The source code is pretty long to post here, so I just explain instead. Thanks for help.

Share this post


Link to post
Share on other sites
MENTAL    383
As was said in the other thread, you can only use opengl on the thread the render context was created on. Try creating the entire window + setting up OpenGL in the render thread. That should allow OpenGL to run in your child thread while you do your normal stuff in the main thread.

You can even have the event loop and WndProc running from the main thread (indeed, the WndProc function is called from whichever thread your message pump is running in), just create the window + render context in the GL thread.


edit: Also, if you're setting up your Viewport and perspective by responding to window resize events, chances are you're calling those GL functions in the main thread and not the render thread. You'll have to find a way of notifying the render thread that the window dimensions have changed, and have the render thread do the appropriate glViewport/gluPerspective calls at the start of each frame (providing there has been a change from the previous frame).

Share this post


Link to post
Share on other sites
dm_3x    100
Quote:
Original post by MENTAL
Try creating the entire window + setting up OpenGL in the render thread. That should allow OpenGL to run in your child thread while you do your normal stuff in the main thread.


Okay, tried that one didn't work either. It does show something, but very messy. Also, if I tell the WM_PAINT to return 0, instead of let the default window procedure take care of it, there's nothing in the background.

The funny thing is the 2 thread method which the window thread, the main thread, creates a child thread that does the render job. All those SetPixelFormat, wglCreateContext...etc functions are actually coded inside the render thread. And that program works totally fine.

The only thing is if the users move the window, or try to resize the window, the fps actually drop. I've did searched on google a bit and looks like that probably windows issue as well.


Quote:
Original post by MENTAL
edit: Also, if you're setting up your Viewport and perspective by responding to window resize events, chances are you're calling those GL functions in the main thread and not the render thread. You'll have to find a way of notifying the render thread that the window dimensions have changed, and have the render thread do the appropriate glViewport/gluPerspective calls at the start of each frame (providing there has been a change from the previous frame).


Thanks for reminder, I'm just remaking one of the NeHe's example, not a problem here.


Anyway, feel free to comment if you've any suggestion. This problem got to be either somewhere of my coding or window's architecture issue.

[sourcecode]
int main( int argc, char **argv )
{
printf( "This is just testing how to show the source codes\n" );
return 0;
}
[/sourcecode]

Share this post


Link to post
Share on other sites
MENTAL    383
It's your coding. I've put together a basic spinning window demo by hacking apart the nehe base code. All rendering is done in a child thread but window creation and the message pump is in the main thread.

I'm just tidying it up as the code is an absolute mess, and then I'll upload it. Will be another 3-4 hours as I'm going out for a Pizza :P

Share this post


Link to post
Share on other sites
MENTAL    383
All done: http://depositfiles.com/files/sjs7m5yog (no project files as you might not be using 2010 - just create blank Win32 project, add these files, and compile).

Steps you need to follow:

1) CreateWindow has to be called on the thread that your message loop will be operating on. This is because window messages are bound to the thread that created the window.

2) wglMakeCurrent has to be called on the thread that you with to do the drawing on. This is because the rendering context will be bound to that particular thread, and OpenGL will refuse calls from any other thread.

3) Initialization can not be properly parallel. You need to create the window first on your main thread, then create the renderer thread, and then do the wglMakeCurrent commands.

4) The code provided is a bit hacky but is designed to show the basics. For production code (my app is MT but I have the renderer running on the main thread so didn't come into these problems) a proper message-passing system is needed. For this, I abuse critical sections and member variables to pass data back and forth instead.

5) You can call GetDC and all the pixel format functions, right up to and including wglCreateContext on any thread you like - whether you do it on the main or render thread (or even make another thread just for the lulz) is up to you. Just make sure you adher to #1 and #2.

6) This was tested using 2010 Express 32-bit on Windows 7 (both Debug and Release mode). The casting to Get/SetWindowLongPtr is probably wrong for 64-bit mode, as is possibly the cast for ThreadProc.


Hope it helps. This is the easy part - safely passing information back and forth between your main thread when you create/move/modify objects is where it really starts to get interesting :P

Suspending the renderer thread and having it only draw frames when instructed to by the main thread is an exercise for you :)

Share this post


Link to post
Share on other sites
dm_3x    100
Really appreciate your work. After I look at the coding, the overall structure is the 2 threading system which the window act as the main thread and create a child thread that does the rendering work. I have NO problem getting this way to work.

The problem is the 3 threading system which the window is a child thread. And BE MORE SPECIFIC from register window class to the end, the whole program (structure), is done inside a child thread. This 3 threading system is what I'm trying to exploring whether is a valid method under the MS Windows architecture.

If it's in your coding, the follow changes should be made as:


// you have main.cpp
int APIENTRY WinMain ( .... )
{
Application myApp ;

myApp.Init_App_Main_thread (); // this should start a thread first
// error checking and wait for the HWND to get valid

m_renderer.Init_Render_thread () (m_device_context, WINDOW_WIDTH, WINDOW_HEIGHT);
// error checking to make sure the render sets up correctly

// now both threads are initialized correctly (assuming)
// wait for the window to be done or
// if any codes need to be done in the main program, do it


// if the window is done, user command or whatever.
// clean up
m_renderer.Stop ();
myApp.Shutdown ();

}




This 3 threading situation is the problem I encounter now. Anyway, if you have time spare please try to play with it and see whether this is valid method in the windows programming. I've no knowledge what the MS limitation is, just trying to figure out.

Why I don't want the 2 threading system? Again exploring, trying to see if that fits multi-platform coding. I would like to minimize the code as much as possible since OGL is multi-platform. Again not sure if this is valid in windows, I'm just exploring.

Thanks

[Edited by - dm_3x on May 2, 2010 5:21:03 PM]

Share this post


Link to post
Share on other sites
MENTAL    383
You can do it with 3 threads if you really want. However, you must have your message loop (Peek/Translate/Dispatch) running in the thread you created the HWND in.

You would have to organise it as so:


Main Thread: Starts up the Window and Renderer threads in order, then goes into a loop doing game update stuff.

Window Thread: Creates the HWND (and HDC if you want). If successful, operates the standard message pump until WM_QUIT is received. In order to ensure the correct order of deinitialization, it would have to either stop the Render Thread itself, or notify the Main Thread that it's time to close the application. As far as I am aware, it is perfectly safe to call DestroyWindow from any thread. Once WM_QUIT is encountered, you could have the Window Thread exit (and notify the Main Thread of this). Main Thread can then stop the Render Thread, and then call a Shutdown method on the window (this would execute on Main Thread).

Render Thread: Calls wglMakeCurrent (and can do anything starting from GetDC if you want), and then goes into a loop drawing stuff until told otherwise. This thread must be created after Window Thread is fully initialized, and stopped before the HWND is destroyed. wglMakeCurrent (NULL, NULL) must be called from the Render Thread. wglDeleteContext can be called from any thread.

To be honest, the reason this kind of system isn't used much is because it introduces a lot more headaches due to having the message pump on it's own thread (and dependant on Renderer as it has to shutdown in a specific order). Either having the pump running on the Main Thread is the common way, or in the Render Thread (along with all window creation stuff).

Although I can't see why you want a 3-thread system (unless this is just an experiment), it certainly is possible.


edit: a slightly simpler approch would be to start the Render Thread from the Window Thread. As the Window Thread would have full knowledge of the renderer (this is a one-way relationship - all the renderer needs to know is the device context), it would be far easier to structure the code. You would still need a way of signalling the main thread to stop accessing the window or renderer however, otherwise you could (and to be frank - will) get access violations.

Share this post


Link to post
Share on other sites
dm_3x    100
Quote:
Original post by MENTAL
Although I can't see why you want a 3-thread system (unless this is just an experiment), it certainly is possible.


I'm trying to see how this would work just for experiment, and somehow that just doesn't work! Again, if you got time to spare, try it see if it works in your way. I'm still trying to figure out for mine


Got some more to edit here

Quote:
Original post by MENTAL
To be honest, the reason this kind of system isn't used much is because it introduces a lot more headaches due to having the message pump on it's own thread (and dependant on Renderer as it has to shutdown in a specific order). Either having the pump running on the Main Thread is the common way, or in the Render Thread (along with all window creation stuff).


I employed the finite system machine system, so it seem to work ok to me, but still the same stupid problem just not working.

Quote:
Original post by MENTAL
Window Thread: Creates the HWND (and HDC if you want). If successful, operates the standard message pump until WM_QUIT is received. In order to ensure the correct order of deinitialization, it would have to either stop the Render Thread itself, or notify the Main Thread that it's time to close the application. As far as I am aware, it is perfectly safe to call DestroyWindow from any thread. Once WM_QUIT is encountered, you could have the Window Thread exit (and notify the Main Thread of this). Main Thread can then stop the Render Thread, and then call a Shutdown method on the window (this would execute on Main Thread).

Render Thread: Calls wglMakeCurrent (and can do anything starting from GetDC if you want), and then goes into a loop drawing stuff until told otherwise. This thread must be created after Window Thread is fully initialized, and stopped before the HWND is destroyed. wglMakeCurrent (NULL, NULL) must be called from the Render Thread. wglDeleteContext can be called from any thread.


This is exactly what I'm doing. The render starts from GetDC to wglMakeCurrent, as well as all the clean up needed to be done. Not sure if that's me or just the windows architecture not allowing to be done in this way.

However, there is one interesting I found, in 3 threads structure. If all those register window class, create window function are NOT done by the window thread but called by the main thread. And then you create a thread that only takes the windows message and try to pump in to the WndProc. You'll see the OpenGL is drawing but the window isn't responding at all. That's the what I call the "semi 3 threads". You can try it if you've time.

[Edited by - dm_3x on May 3, 2010 12:33:25 AM]

Share this post


Link to post
Share on other sites
MonoFocus    100
MENTAL explains it very well in his guide, but if you understand it better by looking at source code:

RenderThread.c:
extern volatile HGLRC MAINWIN_GLRC;
extern volatile BOOL TRD_DORENDER;

typedef struct
{
int width;
int height;
}
DIMENSIONS,*pDIMENSIONS;

void Render_Proc ( void* args )
{
auto pDIMENSIONS dims = (pDIMENSIONS) args;

wglMakeCurrent(MAINWIN_GLRC);
glViewPort(0,0,dims->width,dims->height);

while(TRD_DORENDER)
/* render until TRD_DORENDER becomes FALSE */

_endthread();
}
void Init_Render_thread ( int WINDOW_WIDTH, int WINDOW_HEIGHT )
{
/* wait until MAINWIN_GLRC != NULL */
while(!MAINWIN_GLRC) /* read note *1 */
Sleep(10);

auto DIMENSIONS dims;

dims.width = WINDOW_WIDTH;
dims.height = WINDOW_HEIGHT;
TRD_DORENDER = TRUE;

_beginthread(Render_Proc,0x2000,&dims);
}








MainProgram.c:
volatile HGLRC MAINWIN_GLRC = 0;
volatile BOOL TRD_DORENDER = 0;

Init_App_Main_thread ( void )
{
auto HWND hwnd = /* get the main window HWND */
auto int cx = /* main window width */
auto int cy = /* main window height */

Init_Render_thread(cx,cy);

auto HDC hdc = GetDC(hwnd);
/* setup pixel format here ... */
MAINWIN_GLRC = wglCreateContext(hdc); /* IMPORTANT: create the GLRC in the same thread that creates the output window -- not in the render thread!! */

/* somewhere later in the code ... */

TRD_DORENDER = FALSE;

/* perhaps wait for the render thread to finish (not strictly necessary) */
}








*1: I don't know if you are familiar with the concept of volatile variables, but basically it means that their values are read from memory every time they are needed. For example, if you do not make MAINWIN_GLRC volatile the program will most likely only read the value one time and thus never exit this loop.

This code is obviously just a rough model, but I hope you get the idea.

[Edited by - MonoFocus on May 3, 2010 8:33:40 AM]

Share this post


Link to post
Share on other sites
MENTAL    383
Quote:
However, there is one interesting I found, in 3 threads structure. If all those register window class, create window function are NOT done by the window thread but called by the main thread. And then you create a thread that only takes the windows message and try to pump in to the WndProc. You'll see the OpenGL is drawing but the window isn't responding at all. That's the what I call the "semi 3 threads". You can try it if you've time.


Yep I encountered this one too. This is because Windows sends messages to the thread that created the window. Hence, if you're trying to do the message pump in another thread, it won't do anything as that thread won't actually have the messages sent to it (the messages will be sitting unprocessed in whichever thread made the window).

I'll give the triple-thread approch a try to see if I can get it working.

Share this post


Link to post
Share on other sites
MENTAL    383
Yep, triple-thread system working: http://depositfiles.com/files/g9ldeumax (again no project files, and the code is even more horrible and may include resource leaks for windows thread handles).

Main Thread

1. Starts Window Thread. Control doesn't return to Main Thread until the window has either been initialized successfully (go to 2) or failed (exit).

2. Starts Renderer Thread. Control doesn't return to Main Thread until the renderer has either been initialized successfully (go to 2) or failed.

3. Sits in a loop checking to see if Window Thread is still running. If so, do game logic. If not, go to 4

4. Stop the renderer. This will cause the Renderer Thread to safely exit, and the renderer will be shutdown fully.

5. Stop the window. If the Window Thread has already exited, this still needs to be called to actually destroy the window itself.

Window Thread

1. Create window

2. Message loop until WM_QUIT is received (either from closing the window, or from having Main Thread call Stop when it is still running).

3. Exit the thread. Window Thread will not clean itself up if the window is manually closed due to the renderer needing to be stopped first. A call to "Stop" must be made at all times to fully free resources.

Renderer Thread

1. Init OpenGL

2. Sit in a loop drawing until it's Stop method is called.



It's horrible horrible horrible but it works. A proper message/event system is far more efficiant for passing things back and forth between threads, but this is just a proof of concept :).

Share this post


Link to post
Share on other sites
dm_3x    100
Thank you for work, I can see mostly it's the synchronization issue from my coding here. And I'm still trying to get my C program to work as it should be. But at least I can see things are drawing for now. Just don't know why the program won't quit when I hit the close button. :[

Anyway, keeping the window as the main thread probably is much better idea. At least those message passing won't give people that much a headache. :D

Thank you.

Share this post


Link to post
Share on other sites
MENTAL    383
Quote:
Original post by dm_3xJust don't know why the program won't quit when I hit the close button. :[


Because the quit message is sent to the thread that created the window which in this case is different from the main application. You need to have a message pump (PeekMessage, TranslateMessage, DispatchMessage) running on the window thread. When it picks up WM_QUIT, it needs to somehow signal the main thread that the application needs to close.

I did this by having the window thread exit, and the main thread repeatedly checking to see if the window thread has stopped running (if so, we know it encountered a WM_QUIT message). This is an absolutely terrible way of doing things for anything more sophisticated than a simple demo for a forum :)

Good luck, and welcome to Parallel Hell :)

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this