Skip to main content
gamedev.net
🔒 Locked

Multithreaded OpenGL

Started by fyhuang 17 replies 2.3k views
Original Post
fyhuang
fyhuang
I am creating an OpenGL engine that runs with two main threads : one to initialize the engine/run the message pump, and another to run the game. I can''t get the OpenGL init to work, even though I have tried initializing in the game thread (the one that uses it). GetLastError() returns these errors after these function calls: SetPixelFormat() : The printer name is invalid (???) wglMakeCurrent() : Invalid window handle And then all my calls in the game function give me ''Invalid handle''. Can anyone help me? [P.S.]I know I''m cross-posting, but I''m really desperate... oh, and @NeHe: can you write a tutorial about this, maybe?
- fyhuang [ site ]
fyhuang
fyhuang
Yes, I have tried moving the Init() function into the new game thread, but that doesn''t seem to do anything either.
- fyhuang [ site ]
fyhuang
fyhuang
So I have to create the window and the context in the same thread that's going to be using it?

[edit]Alright, now I'm creating the window in the same thread that the OpenGL init and game is running in, but I still get the same errors.

[edited by - fyhuang on May 1, 2004 11:39:25 AM]
- fyhuang [ site ]
Hybrid666
Hybrid666
it is very strict on the 1 thread thing, absolutely no GL related stuff in the 2nd thread - thats gotta b the problem

Hybrid
mr_leerman
mr_leerman
Alright, multi-threading OpenGL in the Windows environment is very much possible. A few months ago I was working on this problem myself (google flipcode.com for my posts there, sorry don't have links). The solution is very simple, relying on a single function call to fix all of your troubles: wglMakeCurrent().

When making opengl calls in either thread, just make sure to call wglMakeCurrent first. You'll have to look up the syntax for the call, but as I recall it wasn't too difficult to figure out.

My system works like this:
Window t_Window;
t_Window.create( blah, blah, blah, settings stuff );
-> Create calls a second hidden function that spawns a Win32
thread to create the window and fall into the message pump
loop.

Now, to render to the window you simply have to give the thread "rendering ownership" of the Context (Device or Rendering, don't remember off the top of my head) for the window by calling wglMakeCurrent() in that thread with the window's context.

Of course, to make this call you need access to the Contexts created for the window. I solved this problem by saving them as a member variables in the Window object, and providing a "makeCurrent()" call that my win32 OpenGL renderer knows to make. All this member function does is perform a "wglMakeCurrent()" call with the member contexts. Making this call in the main thread will give ownership to the main thread, but making this call from inside the message pump / creation thread gives ownership to the message / creation thread.

Speaking of dealing with both threads. Be VERY careful in planning you thread interaction. Mutex locks are your friends and when used correctly will save you from nasty lockups. I do a mutex lock everytime I give control of the context to one thread or the other (so that it can't get stolen mid-render) AND do a mutex lock anytime I'm making changes to the window's attributes (IE resizing, moving, etc). Obviously, unlock the mutex when you are done rendering or altering the window.

One last caveat: If you have multiple windows, you need to only call makeCurrent on the window you wish to render to. Therefore, you will need some way to track your "current" rendering window, so that you can call "makeCurrent()" on it before rendering. The best way to do this is dependent on your project and is left for you to design.

Also, I did a quick google of gamedev.net and pulled up two results dealing with multi-threaded OpenGL pretty quickly:

http://www.gamedev.net/community/forums/topic.asp?topic_id=87463
http://www.gamedev.net/community/forums/topic.asp?topic_id=115961

I haven't taken the time to read the threads, but they might be of some use to you.

I really hope this helps. When I was tackling this problem I received a whole lot of "its not possible!" replies on Flipcode before three or four people that were sure it was possible but weren't sure how to do it joined in. Through their suggestions and some reading (via google searches) at msdn.microsoft.com we made it work. I'm sure you can too, but I'd be glad to provide further help if you need it.

EDIT: Signature fix

---
Phear the hamsters!

[edited by - mr_leerman on May 3, 2004 1:31:58 PM]
---Phear the hamsters!
fyhuang
fyhuang
Actually, I recently just tried removing the multithreaded interface (I''m going to go with a scripted ''emulation'' of the linear interface), and it still doesn''t work.

Possibly I''m creating the window wrong or something?
- fyhuang [ site ]
merlin9x9
merlin9x9
Isn''t it suspicious to you that SetPixelFormat would give an error message that says "the printer name is invalid?"
fyhuang
fyhuang
Which is exactly the reason I can''t figure it out! If I had something like ''pixel format invalid'', I would be double-checking the pixel format descriptor and ChoosePixelFormat(). With this, I really don''t know where to look!
- fyhuang [ site ]
merlin9x9
merlin9x9
Perhaps the problem is somewhere in the initialization of your PIXELFORMATDESCRIPTOR struct. Could you post it, please?
Shannon Barber
Shannon Barber
You can render to the same context from two different threads, but OGL is poorly designed for this (it''s much easier with D3D).

Create the conext in the primary thread & initialize it. Then call wglMakeCurrent(hdc, NULL) to /deselect/ the activate context. Once you have deselected it, you can call wglMakeCurrent in the second thread and render to it.

Now, whenever you receive a message that changes the window, you need to deselect the context on the second thread, select it on the message pump thread, resize it or whatever, then re-select it on the secondary thread, and you can render again.

All of this needs to be syncronized, the monitor syncronization scheme seems like a good fit to me.
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
hplus0603
hplus0603
The Windows graphics and printer device interface is the same thing, down in the bowels of GDI. This makes some errors, functions and structures interestingly named.

You can only SetPixelFormat() on a DC _once_ and not change it afterwards. You have to create another DC (and HWND) to do it over (say, in a different thread).

It is possible to render using OpenGL from two separate threads. Easiest is to create two separate DCs and one RC per DC, keeping one active in each thread.

The same RC cannot be active (current) in more than one thread at a time. If you try to MakeCurrent() an RC that''s active in another thread, you will block until the other thread makes it not current, or you will fail.

Threading adds significant performance penalties to both your program, and the OpenGL implementation. It''s almost never a good idea to do GL from more than one thread. In fact, it''s almost never a good idea to use threads for things other than scientific number crunching, or making a synchronous system service (such as gethostbyname(), or disk I/O on Windows 98) into an asynchronous service, or special, very-low-latency device-dependent tasks such as sound playback or mouse service (but the OS usually does those for you).
enum Bool { True, False, FileNotFound };
fyhuang
fyhuang
Actually now, the problem isn''t getting the thing to render on two threads, it''s getting it to render in the first place!

Here''s the PIXELFORMATDESCRIPTOR I was using:


GLPF.nSize = sizeof( PIXELFORMATDESCRIPTOR );
GLPF.nVersion = 1;
GLPF.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
GLPF.iPixelType = PFD_TYPE_RGBA;
GLPF.cColorBits = DisplayBPP;
GLPF.cRedBits = 0;
GLPF.cRedShift = 0;
GLPF.cGreenBits = 0;
GLPF.cGreenShift = 0;
GLPF.cBlueBits = 0;
GLPF.cBlueShift = 0;
GLPF.cAlphaBits = 0;
GLPF.cAlphaShift = 0;
GLPF.cAccumBits = 0;
GLPF.cAccumRedBits = 0;
GLPF.cAccumGreenBits = 0;
GLPF.cAccumBlueBits = 0;
GLPF.cAccumAlphaBits = 0;
GLPF.cDepthBits = 16;
GLPF.cStencilBits = 0;
GLPF.cAuxBuffers = 0;
GLPF.iLayerType = PFD_MAIN_PLANE;
GLPF.bReserved = 0;
GLPF.dwLayerMask = 0;
GLPF.dwVisibleMask = 0;
GLPF.dwDamageMask = 0;


I''ve cut/pasted the one from NeHe''s tutorial:


static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be

{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor

1, // Version Number

PFD_DRAW_TO_WINDOW | // Format Must Support Window

PFD_SUPPORT_OPENGL | // Format Must Support OpenGL

PFD_DOUBLEBUFFER, // Must Support Double Buffering

PFD_TYPE_RGBA, // Request An RGBA Format

DisplayBPP, // Select Our Color Depth

0, 0, 0, 0, 0, 0, // Color Bits Ignored

0, // No Alpha Buffer

0, // Shift Bit Ignored

0, // No Accumulation Buffer

0, 0, 0, 0, // Accumulation Bits Ignored

16, // 16Bit Z-Buffer (Depth Buffer)

0, // No Stencil Buffer

0, // No Auxiliary Buffer

PFD_MAIN_PLANE, // Main Drawing Layer

0, // Reserved

0, 0, 0 // Layer Masks Ignored

};


And it still gives me the ''printer name invalid'' error...
- fyhuang [ site ]
merlin9x9
merlin9x9
Could you also post the rest of your initialization code, please?
fyhuang
fyhuang
Okay, I''m not one to freely post source code, but here:

This is the window class:


//wcex.cbSize = sizeof( WNDCLASSEX );

wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon( NULL, IDI_WINLOGO );
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
wcex.hbrBackground = NULL;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = ClassName;


This is the main loop:


// Run code

int index;
MSG msg;
RECT WindowRect;

// Register window class

if ( !RegisterClass( &wcex ) )
{
return GDL_BOOL_FAILED;
}

// Get the window rect

WindowRect.top = WindowX;
WindowRect.left = WindowY;
WindowRect.bottom = WindowHeight + WindowX;
WindowRect.right = WindowWidth + WindowY;

// Create main window

if ( windowed == true )
{
WindowStyle = WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
g_hWnd = hWnd = CreateWindowEx( WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, ClassName, WindowCaption, WindowStyle,
WindowX, WindowY, WindowWidth, WindowHeight, NULL, NULL, hInst, NULL );
AdjustWindowRectEx( &WindowRect, WindowStyle, false, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE );
}
else
{
WindowStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
g_hWnd = hWnd = CreateWindowEx( WS_EX_APPWINDOW, ClassName, WindowCaption, WindowStyle,
WindowX, WindowY, WindowWidth, WindowHeight, NULL, NULL, hInst, NULL );
AdjustWindowRectEx( &WindowRect, WindowStyle, false, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE );
}

// Check if window created

if ( g_hWnd == NULL )
{
return GDL_BOOL_FAILED;
}

// Initialize and start message pump

if ( Init() == GDL_BOOL_SUCCESS )
{
// Show/update window

ShowWindow( hWnd, SW_SHOW );
UpdateWindow( hWnd );

// Set the window focus

SetFocus( hWnd );

// Set foreground window

SetForegroundWindow( hWnd );

// Adjust window size

WindowResize( WindowWidth, WindowHeight );

// Initialize COM

CoInitialize( NULL );

// Clear out key buffer and locks

index = 0;
while ( index < 256 )
{
KeyboardBuffer[index] = false;
KeyboardLocks[index] = false;

index++;
}

//


// Clear message

ZeroMemory( &msg, sizeof( MSG ) );

// Message pump

while ( msg.message != WM_QUIT )
{
// Check for messages

if ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
{
if ( !GetMessage( &msg, NULL, 0, 0 ) )
{
goto exit;
}

TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
if ( GameFunction() == false )
{
goto exit;
}
}
}

// Destroy game thread

//SAFE_CLOSE_HANDLE( GameThread );

}

// Exit stage

exit:
// Shutdown

if ( Shutdown() != GDL_BOOL_SUCCESS )
{
ShowError( "Shutdown unsuccessful: application may not exit properly.", false );
}

// Post closing messages

PostMessage( hWnd, WM_CLOSE, 0, 0 );
PostMessage( hWnd, WM_QUIT, 0, 0 );
PostQuitMessage( 0 );

// Uninitialize COM

CoUninitialize();

// Destroy window

DestroyWindow( hWnd );

// Unregister window class

UnregisterClass( ClassName, hInst );

// Run successful

return GDL_BOOL_SUCCESS;


GameFunction() is a bool function pointer:


// Init all

DWORD result;
char message[256];
static int index = 0;

wglMakeCurrent( MainApplication.GetHDC(), MainGraphics->GetRC() );

if ( index > 1024 )
{
return false;
}

// Get the input

InputKeyboard.Read();

// Clear the screen

MainGraphics->Clear( 0.0f, 0.0f, 0.0f, 1.0f );

// Check for escape

if ( InputKeyboard.GetKey( DIK_ESCAPE ) )
{
return false;
}

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();

/*glTranslatef( -10.0f, -10.0f, 0.0f );

glColor3f( 0.0f, 0.0f, 0.0f );
glBegin( GL_QUADS );
glVertex3f( 0.0f, 0.0f, 0.0f );
glVertex3f( 100.0f, 0.0f, 0.0f );
glVertex3f( 0.0f, 100.0f, 0.0f );
glVertex3f( 100.0f, 100.0f, 0.0f );
glEnd();*/


glLoadIdentity();

glColor3f( 0.0f, 0.1f, 0.5f );
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();

MainGraphics->Flip();

MainApplication.LockFramerate( 100 );

index++;

return true;


Here''s the init function:


MainGraphics = new CGDLGraphicsGL;
MainGraphics->Init();

// Set mode

if ( MainGraphics->SetMode( false, true, 0, 1024, 768, 32 ) == false )
{
MessageBox( MainApplication.GetHWND(), "Set mode failed.", "Error", MB_OK );
return false;
}

// Init input

MainInput.Init();
InputKeyboard.Create();

return true;


And finally, here''s the set mode function:


GLuint PixelFormat = 0;
RECT ClientRect;

int errornum = 0;
DWORD result;
char message[256];
char msg2[256];

// Clear structures

ZeroMemory( &GLDM, sizeof( DEVMODE ) );
ZeroMemory( &GLPF, sizeof( PIXELFORMATDESCRIPTOR ) );

// Error checking

if ( GDLGlobal == NULL )
{
return GDL_BOOL_FAILED;
}

if ( g_hWnd == NULL && GDLGlobal->GetHWND() == NULL )
{
return GDL_BOOL_FAILED;
}
else if ( GDLGlobal->GetHWND() != NULL )
{
g_hWnd = GDLGlobal->GetHWND();
}

// Check width/height

if ( ( DisplayWidth = width ) <= 0 )
{
// Check fullscreen

if ( windowed == false )
{
// Use default width

DisplayWidth = 640;
}
else
{
// Use client width

GetClientRect( g_hWnd, &ClientRect );
DisplayWidth = ClientRect.right;
}
}

if ( ( DisplayHeight = height ) <= 0 )
{
// Check fullscreen

if ( windowed == false )
{
// Use default height

DisplayHeight = 480;
}
else
{
// Use client height

GetClientRect( g_hWnd, &ClientRect );
DisplayHeight = ClientRect.bottom;
}
}

// Check BPP

if ( ( DisplayBPP = bpp ) <= 0 || windowed == true )
{
// Set default BPP

if ( !( DisplayBPP = 32 ) )
{
return GDL_BOOL_FAILED;
}
}

// Resize client if using windowed mode

/*if ( windowed == true )
{
GDLGlobal->WindowResize( DisplayWidth, DisplayHeight );
}*/


// Check for fullscreen

if ( ( DisplayWindowed = windowed ) == false )
{
// Init display mode

GLDM.dmSize = sizeof( DEVMODE );
GLDM.dmPelsWidth = DisplayWidth;
GLDM.dmPelsHeight = DisplayHeight;
GLDM.dmBitsPerPel = DisplayBPP;
GLDM.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

// Set selected mode

/*if ( ChangeDisplaySettings( &GLDM, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL )
{
// Check if user wants to switch to windowed
#ifdef GDL_DEBUG
GDLGlobal->LogError( "CGDLGraphicsGL::SetMode() : Set fullscreen mode failed." );
#endif
if ( MessageBox( g_hWnd, "Your video card does not support the selected fullscreen mode. Switch to windowed?", "Fullscreen failed.", MB_YESNO | MB_ICONEXCLAMATION ) == IDYES )
{
windowed = true;
}
else
{
GDLGlobal->ShowMessage( "Windowed mode not requested -- OpenGL set mode failed. Closing now." );
return GDL_BOOL_FAILED;
}
}*/

}

// Setup pixel format

GLPF.nSize = sizeof( PIXELFORMATDESCRIPTOR );
GLPF.nVersion = 1;
GLPF.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
GLPF.iPixelType = PFD_TYPE_RGBA;
GLPF.cColorBits = DisplayBPP;
GLPF.cRedBits = 0;
GLPF.cRedShift = 0;
GLPF.cGreenBits = 0;
GLPF.cGreenShift = 0;
GLPF.cBlueBits = 0;
GLPF.cBlueShift = 0;
GLPF.cAlphaBits = 0;
GLPF.cAlphaShift = 0;
GLPF.cAccumBits = 0;
GLPF.cAccumRedBits = 0;
GLPF.cAccumGreenBits = 0;
GLPF.cAccumBlueBits = 0;
GLPF.cAccumAlphaBits = 0;
GLPF.cDepthBits = 16;
GLPF.cStencilBits = 0;
GLPF.cAuxBuffers = 0;
GLPF.iLayerType = PFD_MAIN_PLANE;
GLPF.bReserved = 0;
GLPF.dwLayerMask = 0;
GLPF.dwVisibleMask = 0;
GLPF.dwDamageMask = 0;

// Get a device context

/*if ( !( GDLGlobal->WindowGetDC() ) )
{
GDLGlobal->ShowError( "Couldn''t get DC.", true );
return GDL_BOOL_FAILED;
}*/


static PIXELFORMATDESCRIPTOR pfd= // pfd Tells Windows How We Want Things To Be

{
sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor

1, // Version Number

PFD_DRAW_TO_WINDOW | // Format Must Support Window

PFD_SUPPORT_OPENGL | // Format Must Support OpenGL

PFD_DOUBLEBUFFER, // Must Support Double Buffering

PFD_TYPE_RGBA, // Request An RGBA Format

DisplayBPP, // Select Our Color Depth

0, 0, 0, 0, 0, 0, // Color Bits Ignored

0, // No Alpha Buffer

0, // Shift Bit Ignored

0, // No Accumulation Buffer

0, 0, 0, 0, // Accumulation Bits Ignored

16, // 16Bit Z-Buffer (Depth Buffer)

0, // No Stencil Buffer

0, // No Auxiliary Buffer

PFD_MAIN_PLANE, // Main Drawing Layer

0, // Reserved

0, 0, 0 // Layer Masks Ignored

};

// Get the DC

GLDC = GetDC( g_hWnd );

// Check the DC

if ( GLDC == NULL )
{
GDLGlobal->ShowError( "Couldn''t get DC.", true );
return GDL_BOOL_FAILED;
}

// Choose a pixel format

if ( !( PixelFormat = ChoosePixelFormat( GLDC, &pfd ) ) )
{
GDLGlobal->ShowError( "Couldn''t find a pixel format.", true );
return GDL_BOOL_FAILED;
}

// Set the pixel format

if ( !SetPixelFormat( GLDC, PixelFormat, &pfd ) )
{
GDLGlobal->ShowError( "Couldn''t set pixel format.", true );
return GDL_BOOL_FAILED;
}

result = GetLastError();
errornum++;
sprintf( msg2, "%d", errornum );
FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, result, 0, message, 256, NULL );
strcat( message, " Error number: " );
strcat( message, msg2 );
sprintf( msg2, "%d", result );
strcat( message, "\n Error code: " );
strcat( message, msg2 );
MessageBox( g_hWnd, message, message, MB_OK );

// Activate rendering context

if ( !wglMakeCurrent( GLDC, GLRC ) )
{
GDLGlobal->ShowError( "Couldn''t activate rendering context.", true );
return GDL_BOOL_FAILED;
}

// Resize the viewport

glViewport( 0, 0, DisplayWidth, DisplayHeight );

// Set perspective

SetPerspective( 45.0f, (GLfloat)DisplayWidth / (GLfloat)DisplayHeight, 1.0f, 500.0f );

// Check for ZBuffer

if ( ( DisplayZBuffer = zbuffer ) == true )
{
glEnable( GL_DEPTH_TEST );
glDepthFunc( GL_LEQUAL );
}
else
{
glDisable( GL_DEPTH_TEST );
}

// Reset modelview matrix

glMatrixMode( GL_MODELVIEW );
glLoadIdentity();

// Set smooth shading

glShadeModel( GL_SMOOTH );

return GDL_BOOL_SUCCESS;
- fyhuang [ site ]
merlin9x9
merlin9x9
*shrugs*
I don''t know. It looks fine to me right now...
fyhuang
fyhuang
That''s what I think too...

It just doesn''t render anything!!!
- fyhuang [ site ]
fyhuang
fyhuang
Wow, I just found the most obvious mistake -- I wasn''t creating a context! It works now.

Thanks for all your help!
- fyhuang [ site ]

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.