🔒 Locked
Multithreaded OpenGL
Advertisement
Original Post
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?
If you initialize OpenGL in one thread, you can only use it in that thread, so you must design your program so that even though the messaging thread pumps messages, the OpenGL thread creates the window and initializes OpenGL.
[ Galactic Conquest | Bananas | My dead site | www.sgi.com | Goegel ]
[ Galactic Conquest | Bananas | My dead site | www.sgi.com | Goegel ]
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]
[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]
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]
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]
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.
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 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).
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).
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:
I''ve cut/pasted the one from NeHe''s tutorial:
And it still gives me the ''printer name invalid'' error...
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...
Okay, I''m not one to freely post source code, but here:
This is the window class:
This is the main loop:
GameFunction() is a bool function pointer:
Here''s the init function:
And finally, here''s the set mode function:
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;
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement