Skip to main content
gamedev.net
🔒 Locked

Multiple RC's

Started by neemo_t Jun 18, 2007 at 5:55 PM 9 replies 2.2k views
Original Post
neemo_t
neemo_t
I'm having problems getting multiple threads to work with two openGL rendering contexts. I've read all the previous threads on this and there is a wealth of information. But... for some reason I just can't get it to work. The best my search found was the tutorial made by zppz: http://zppz.homelinux.net/opengl/dataSharing/ I downloaded the example code, compiled it and ran it. But when I execute the new thread (by pressing 'L') the program crashes :X Does this happen for anyone else? From what i've understood, the process is something like this: (using windows by the way) Main thread: 1. Create DC 2. Create your own RC (mainRC) 3. Create another RC (secondRC) for the thread 4. wglShareLists(mainRC, secondRC) 5. wglMakeCurrent(DC, mainRC) Background thread: (after above steps) 1. wglMakeCurrent(DC, secondRC) 2. << loading, etc >> This all seems to make sense, and from what I've read it works for other people.. I'm not sure what my problem is but after messing around with zppz example I've found some weird behaviour. Instead of doing all the steps above. I setup openGL for a single thread like I usually would. Then out of curiosity, I made a second RC, but never used it at all. In other words: Main thread: 1. Create DC 2. Create your own RC (mainRC) 3. Create another RC (secondRC) for the thread (don't use it though) 4. wglMakeCurrent(DC, mainRC) 5. << do rendering, etc >> Program runs fine. BUT, when I'm ready to quit and I do wglDeleteContext(secondRC) the program crashes! Why? I never made it current. Also, if I remove wglDeleteContext(secondRC) from the cleanup routine, instead of crashing I get a NULL pointer error when the main thread returns... as if an unseen destructor is being called?? As soon as remove step 3 above, everything works fine. But if I ever call wglCreateContext() more than once from the main thread, I get this strange dereferencing of a NULL pointer error. I'm using Visual C++ 2005 Express Edition. Is my understanding wrong? Even if it is, why wouldn't zppz's example code work when I didn't modify it :X Maybe I have an old version of openGL? If anyone has any ideas about this I would really appreciatte it :) neemo
Omega147
Omega147
Both threads have to have a unique RC _and_ a unique DC to work.

When you delete the RC, you must delete it within the thread to which that RC is current, or this will result in an error (probably the error you're seeing on closing).

Finally, if you're doing any drawing in these threads, only have one thread call SwapBuffers after all threads have finished drawing.


Hope that clears some things up. :)
neemo_t
neemo_t
Quote:
Original post by Omega147
Both threads have to have a unique RC _and_ a unique DC to work.


I've tried this as well and it causes similar problems. I create two DC's and two RC's from the main thread. Make current the second RC in the background thread, and everything crashes. I also tried having the background thread wglCreateContext() its own RC on the second DC made in the main thread. That also crashed. I also had the background thread make its own DC and own RC, without linking the two RC's and it still crashes. I feel like I've tried endless combinations that don't work :P

Does anyone have a simple example that does this and works? >.<

EDIT: After more mindless testing I've learned 2 things:

1. The null pointer error WAS caused by calling wglCreateContext() twice in the same thread. Apparently.. if you don't ever use the return value in a call to wglMakeCurrent() it generates this error when the program terminates. Otherwise there is no null pointer error.

2. When I call wglMakeCurrent() with a DC and RC DIFFERENT from the main thread, gl commands work fine in the background thread, but the main thread stops rendering, as if it has lost its context. If I put wglMakeCurrent() in the rendering loop of the main thread through (with its own DC and RC) the rendering seems to continue and the background thread still functions.

This solution doesn't satisfy me though. I thought you only had to call wglMakeCurrent() once in each thread, and then never again?

[Edited by - neemo_t on June 18, 2007 11:02:36 PM]
Omega147
Omega147
Quote:
Original post by neemo_t
1. The null pointer error WAS caused by calling wglCreateContext() twice in the same thread. Apparently.. if you don't ever use the return value in a call to wglMakeCurrent() it generates this error when the program terminates. Otherwise there is no null pointer error.

The return call to wglMakeCurrent() is only a boolean value. You don't have to use it.
Quote:
Original post by neemo_t
2. When I call wglMakeCurrent() with a DC and RC DIFFERENT from the main thread, gl commands work fine in the background thread, but the main thread stops rendering, as if it has lost its context. If I put wglMakeCurrent() in the rendering loop of the main thread through (with its own DC and RC) the rendering seems to continue and the background thread still functions.

This solution doesn't satisfy me though. I thought you only had to call wglMakeCurrent() once in each thread, and then never again?

You do only have to call wglMakeCurrent() once in each thread. I call it right after I create the RC, and everything works fine for me from there.

To fix your error on closing, be certain to call wglMakeCurrent(NULL, NULL) just before you delete the RC. Calling wglMakeCurrent() with a NULL RC value will release the DC, which should then allow wglDeleteContext() to return successfully. If that doesn't work, I suspect your error might be something else.
neemo_t
neemo_t
Quote:
Original post by Omega147
Quote:
Original post by neemo_t
1. The null pointer error WAS caused by calling wglCreateContext() twice in the same thread. Apparently.. if you don't ever use the return value in a call to wglMakeCurrent() it generates this error when the program terminates. Otherwise there is no null pointer error.

The return call to wglMakeCurrent() is only a boolean value. You don't have to use it.


Sorry, after reading that back it does seem very unclear what I meant, even to me. What I meant was the return value of wglCreateContext(). If I make a call to wglCreateContext(), then don't use the produced RC in a wglMakeCurrent() call, when the program exits I get the error message:

"The instruction at '0x00000000' referenced memory at '0x0000000'. The Memory could not be 'read'.
Click on OK to terminate the program"


Anyway thats not whats bothering me, I was just curious that maybe it was related to the problem. I can't seem to get two RC's to work at all, regardless of error messages.

Without posting all the code, I'll try to explain exactly what I'm doing with some pseudo code:

GOAL: Have a main thread render a spinning square bound to a texture (default white texture). Upon pressing any key, a new thread is created which loads the texture into memory, so that the main thread starts rendering a spinning square with an actual texture (non-white).

PSUEDO CODE: (skipping lots of detail, hopefully it makes sense)

For the sake of simplicity almost all variables are global. Also, this code is basically an adaption of NeHe lesson6 code and also the source code found here:
http://zppz.homelinux.net/opengl/dataSharing/

main thread
1. Create window
2. hdc = GetDC(hWnd) and set the appropriate pixelformat, etc.
3. hrc1 = wglCreateContext(hdc)
4. hrc2 = wglCreateContext(hdc)
5. wglShareLists(hrc1, hrc2)
6. wglMakeCurrent(hdc, hrc1)
7. Initialize glStates and glGenTextures(1, texAddress)
8. while still running
{
if( there are messages )
else
{
drawScene(); // glBindTextures(GL_TEXTURE_2D, texAddress[0])
swapBuffers(hdc);

if ( any key was pressed )

}
}
9. cleanup routines

background thread
1. wglMakeCurrent(hdc, hrc2)
2. loadTexture()
3. wglMakeCurrent(NULL, NULL)
4. return


RESULTS:
1. If I run the program and don't press any key, the spinning square renders white, like planned. When I close the program. I get the above error:

"The instruction at '0x00000000' referenced memory at '0x0000000'. The Memory could not be 'read'.
Click on OK to terminate the program"


2. If I run the program then press a key, the background thread procedure executes and the spinning square stops rendering. The result is what looks like a window that never had any openGL calls made to it. No error when quitting though.

I've also tried in the main thread to produce two hdc objects as suggested (hdc1 and hdc2), then in the background thread do wglMakeCurrent(hdc2, hrc2). This didn't work either. The square continues to render when I press a key, as if the background thread had no effect at all.

I guess I have just done some things in the wrong order. Hopefully someone can point this out for me :S
Omega147
Omega147
Well, short of looking at the actual code, I'm not sure what's holding you back. I'd be willing to take a look at your source to see if I could figure it out, if that's something of interest to you. Drop me a PM if you wanna do that, and we'll go from there.
Deception666
Deception666
Quote:
Original post by neemo_t
main thread
1. Create window
2. hdc = GetDC(hWnd) and set the appropriate pixelformat, etc.
3. hrc1 = wglCreateContext(hdc)
4. hrc2 = wglCreateContext(hdc)
5. wglShareLists(hrc1, hrc2)
6. wglMakeCurrent(hdc, hrc1)
7. Initialize glStates and glGenTextures(1, texAddress)
8. while still running
{
if( there are messages )
else
{
drawScene(); // glBindTextures(GL_TEXTURE_2D, texAddress[0])
swapBuffers(hdc);

if ( any key was pressed )

}
}
9. cleanup routines

background thread
1. wglMakeCurrent(hdc, hrc2)
2. loadTexture()
3. wglMakeCurrent(NULL, NULL)
4. return



I see one problem with this approach. If you look at your step 5 of your main thread, you are sharing the lists between the two RCs, but what you don't realize is that this sharing is not dynamic. Whatever is currently in one RC is shared with the other RC. Anything new created after that is not known to the other RC.

Here is what I think is happening. You are rendering your box without a texture applied to it. The material applied to your box is white and the background color is set to black. In your main loop, you are determining if the worker thread has loaded the texture yet, and if the texture has been loaded, you branch and begin to display it. What I think is happening is that since no texture is known for your main RC, GL is issuing errors and is ignoring the commands that proceed this texture change.

A better approach would be to acquire the main RC from the worker thread by blocking the main thread, allowing the worker thread to successfully load the texture into the main RC. This is much safer and does away with the multi RC problem you are experiencing. I have dealt with multiple RCs before in the past, but I have only used it as a template, never as a renderable device.
Deception666
Deception666
Quote:
Original post by neemo_t
I can see how a blocking approach would work, but its seems to defeat the purpose of having a separate rendering thread. Also, from what I've read it seems as though the sharing is 'dynamic.' I got most of my understanding from this thread:

http://www.gamedev.net/community/forums/topic.asp?topic_id=394701&whichpage=1%EF%BF%BD


I stand corrected. I must have misunderstood this then. Anyway, I just created a test program to kind of understand this as well and I am not getting any crashes when closing the program and the display list shows up properly. This is very interesting, to say the least.

// test.cpp : Defines the entry point for the application.//#include "stdafx.h"#include "test.h"#include <gl/gl.h>#include <process.h>#pragma comment( lib, "opengl32.lib" )#define MAX_LOADSTRING 100// Global Variables:HINSTANCE hInst;								// current instanceTCHAR szTitle[MAX_LOADSTRING];					// The title bar textTCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name// Forward declarations of functions included in this code module:ATOM				MyRegisterClass(HINSTANCE hInstance);BOOL				InitInstance(HINSTANCE, int);LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);HGLRC hGlrc1;HGLRC hGlrc2;HWND hWnd;unsigned int list = 0;bool run_app = true;volatile bool loaded = false;int APIENTRY _tWinMain(HINSTANCE hInstance,                     HINSTANCE hPrevInstance,                     LPTSTR    lpCmdLine,                     int       nCmdShow){	UNREFERENCED_PARAMETER(hPrevInstance);	UNREFERENCED_PARAMETER(lpCmdLine); 	// TODO: Place code here.	MSG msg;	HACCEL hAccelTable;	// Initialize global strings	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);	LoadString(hInstance, IDC_TEST, szWindowClass, MAX_LOADSTRING);	MyRegisterClass(hInstance);	// Perform application initialization:	if (!InitInstance (hInstance, nCmdShow))	{		return FALSE;	}	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST));	// Main message loop:    while (run_app)	{		if (::PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE) &&            !TranslateAccelerator(msg.hwnd, hAccelTable, &msg))		{			TranslateMessage(&msg);			DispatchMessage(&msg);		}        glClear(GL_COLOR_BUFFER_BIT);        glMatrixMode(GL_PROJECTION);        glLoadIdentity();        glOrtho(0, 500, 0, 500, -1, 1);        glMatrixMode(GL_MODELVIEW);        glLoadIdentity();                if (loaded)        {            glTranslatef(125, 125, 0);            glCallList(list);        }        SwapBuffers(GetDC(hWnd));	}	return (int) msg.wParam;}void ThreadEntry( void * p ){    wglMakeCurrent(GetDC(hWnd), hGlrc2);    list = glGenLists(1);    glNewList(list, GL_COMPILE);    glBegin(GL_LINE_LOOP);    glColor3f(1,1,1);    glVertex3f(-125, -125, 0);    glVertex3f(125, -125, 0);    glVertex3f(125, 125, 0);    glVertex3f(-125, 125, 0);    glEnd();    glEndList();    wglMakeCurrent(0, 0);    loaded = true;}////  FUNCTION: MyRegisterClass()////  PURPOSE: Registers the window class.////  COMMENTS:////    This function and its usage are only necessary if you want this code//    to be compatible with Win32 systems prior to the 'RegisterClassEx'//    function that was added to Windows 95. It is important to call this function//    so that the application will get 'well formed' small icons associated//    with it.//ATOM MyRegisterClass(HINSTANCE hInstance){	WNDCLASSEX wcex;	wcex.cbSize = sizeof(WNDCLASSEX);	wcex.style			= CS_HREDRAW | CS_VREDRAW;	wcex.lpfnWndProc	= WndProc;	wcex.cbClsExtra		= 0;	wcex.cbWndExtra		= 0;	wcex.hInstance		= hInstance;	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST));	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_TEST);	wcex.lpszClassName	= szWindowClass;	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));	return RegisterClassEx(&wcex);}////   FUNCTION: InitInstance(HINSTANCE, int)////   PURPOSE: Saves instance handle and creates main window////   COMMENTS:////        In this function, we save the instance handle in a global variable and//        create and display the main program window.//BOOL InitInstance(HINSTANCE hInstance, int nCmdShow){   hInst = hInstance; // Store instance handle in our global variable   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);   if (!hWnd)   {      return FALSE;   }   ShowWindow(hWnd, nCmdShow);   UpdateWindow(hWnd);   PIXELFORMATDESCRIPTOR pfd;   // set the pixel format   pfd.nSize           = sizeof(PIXELFORMATDESCRIPTOR);   pfd.nVersion        = 1;   pfd.dwFlags         = PFD_DRAW_TO_WINDOW |       PFD_SUPPORT_OPENGL |       PFD_DOUBLEBUFFER;   pfd.iPixelType      = PFD_TYPE_RGBA;   pfd.cColorBits      = 32;   pfd.cRedBits        = 0;   pfd.cRedShift       = 0;   pfd.cGreenBits      = 0;   pfd.cGreenShift     = 0;   pfd.cBlueBits       = 0;   pfd.cBlueShift      = 0;   pfd.cAlphaBits      = 0;   pfd.cAlphaShift     = 0;   pfd.cAccumBits      = 0;   pfd.cAccumRedBits   = 0;   pfd.cAccumGreenBits = 0;   pfd.cAccumBlueBits  = 0;   pfd.cAccumAlphaBits = 0;   pfd.cDepthBits      = 32;   pfd.cStencilBits    = 8;   pfd.cAuxBuffers     = 0;   pfd.iLayerType      = PFD_MAIN_PLANE;   pfd.bReserved       = 0;   pfd.dwLayerMask     = 0;   pfd.dwVisibleMask   = 0;   pfd.dwDamageMask    = 0;   HDC m_hDC = GetDC(hWnd);   int nPixelFormat = ChoosePixelFormat(m_hDC, &pfd);   SetPixelFormat(m_hDC, nPixelFormat, &pfd);   hGlrc1 = wglCreateContext(m_hDC);   hGlrc2 = wglCreateContext(m_hDC);   wglShareLists(hGlrc1, hGlrc2);   wglMakeCurrent(m_hDC, hGlrc1);   return TRUE;}////  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)////  PURPOSE:  Processes messages for the main window.////  WM_COMMAND	- process the application menu//  WM_PAINT	- Paint the main window//  WM_DESTROY	- post a quit message and return////LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){	int wmId, wmEvent;	PAINTSTRUCT ps;	HDC hdc;	switch (message)	{	case WM_COMMAND:		wmId    = LOWORD(wParam);		wmEvent = HIWORD(wParam);		// Parse the menu selections:		switch (wmId)		{		case IDM_ABOUT:            {                _beginthread(ThreadEntry, 0, 0);            }			break;		case IDM_EXIT:			DestroyWindow(hWnd);			break;		default:			return DefWindowProc(hWnd, message, wParam, lParam);		}		break;	case WM_PAINT:		hdc = BeginPaint(hWnd, &ps);		// TODO: Add any drawing code here...		EndPaint(hWnd, &ps);		break;	case WM_DESTROY:		PostQuitMessage(0);        wglMakeCurrent(0, 0);        wglDeleteContext(hGlrc1);        wglDeleteContext(hGlrc2);        run_app = false;		break;	default:		return DefWindowProc(hWnd, message, wParam, lParam);	}	return 0;}// Message handler for about box.INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){	UNREFERENCED_PARAMETER(lParam);	switch (message)	{	case WM_INITDIALOG:		return (INT_PTR)TRUE;	case WM_COMMAND:		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)		{			EndDialog(hDlg, LOWORD(wParam));			return (INT_PTR)TRUE;		}		break;	}	return (INT_PTR)FALSE;}
neemo_t
neemo_t
Yeah.. this works. Very strange why the other way was causing problems. I will experiment [grin]

Thanks deception
zppz
zppz
Quote:
Original post by neemo_t
I downloaded the example code, compiled it and ran it. But when I execute the new thread (by pressing 'L') the program crashes :X
If you step through the code, at what point does it crash?

Deception666's code seems pretty much the same as mine, except that it creates a display list instead of loading a texture. I wonder if the texture load is the problem...?

As you've noticed, my code is missing a wglMakeCurrent(NULL, NULL); after loading the texture in the background thread, but I have not found this to be a problem.

As far as I am aware, there is no need to have more than one device context. At least I've never seen any documentation stating that.

Topic Locked

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

Sign in to reply to this topic.