Andy
Andy

Reputation: 3789

How can I draw inside two separate 3D windows within the same application on Windows using OpenGL?

I am implementing a plug-in inside a 3rd party program in C++ on Windows.

The 3rd party program has a window that displays 3D graphics using OpenGL. However I need the plug-in to create another window that also displays 3D graphics using OpenGL.

Do I need to create a new OpenGL rendering context for my window or is there some way that I can "reuse" the OpenGL rendering context used by the 3rd party program?

I assumed that I had to create a new OpenGL rendering context and tried the following:

// create a rendering context  
hglrc = wglCreateContext (hdc); 

// make it the calling thread's current rendering context 
wglMakeCurrent (hdc, hglrc);

However the last function failed. Reading the documentation of wglMakeCurrent I notice that

A thread can have one current rendering context. A process can have multiple rendering contexts by means of multithreading.

Does this mean that my window need to run in a separate thread from the 3rd party program?

Upvotes: 1

Views: 826

Answers (2)

Mateusz Grzejek
Mateusz Grzejek

Reputation: 12058

You didn't post error code generated by wglMakeCurrent(), so I won't be guessing the reason. It's not the binding itself, however. Sentence 'A thread can have one current rendering context' means, that new context will 'replace' the old one and become the current. I don't know why are you trying to set two contexts as current (or run another thread), but it's not the way to go. Avoid multithreading in rendering unless it's absolutely necessary. So, answering your question:

Yes, you CAN 'reuse' OpenGL rendering context.

Why, you may ask? Rendering context is created for specific device context (HDC), which is exclusive property of each window (HWND)! How is this possible, then?!

Well, it seems somehow impossible because of function prototypes:


    HWND my_window = CreateWindow(...);
    HDC my_dc = GetDC(my_new_window);

    //Setup pixel format for 'my_dc'...

    HGLRC my_rc = wglCreateContext(my_dc);
    wglMakeCurrent(my_dc, my_rc);

This really lets you think that rendering context is bound to this specific device context and valid only for it. But it's not. The critical part is the comment (setup pixel format). Rendering context is created for specific CLASS of DCs, to be more precise: for DCs with the same pixel format. So code below is perfectly valid:


    //window_1 = main window, window_2 = your window

    HDC dc_1 = GetDC(window_1);
    Set_pixel_format_for_dc_1(); //Usual stuff
    HGLRC rc = wglCreateContext(dc_1);

    wglMakeCurrent(dc_1, rc);
    ultra_super_draw();

    //.....

    HDC dc_2 = GetDC(window_2);

    //Get dc_1's PF to make sure it's compatible with rc.
    int pf_index = GetPixelFormat(dc_1);
    PIXELFORMATDESCRIPTOR pfd;
    ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
    DescribePixelFormat(dc_1, pf_index, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    SetPixelFormat(dc_2, pf_index, &pfd);

    wglMakeCurrent(dc_2, rc);
    another_awesome_render();

    wglMakeCurrent(NULL, NULL);

If you are still not convinced, MSDN:

wglMakeCurrent(hdc, hglrc): The hdc parameter must refer to a drawing surface supported by OpenGL. It need not be the same hdc that was passed to wglCreateContext when hglrc was created, but it must be on the same device and have the same pixel format.

I guess you are already familiar with these calls. Now, I don't know what are the conditions that your rendering must meet, but without additional requirements, I don't see any difficulties from this point:


    HDC my_dc = Create_my_DC();

    //...

    void my_new_render
    {
        //Probably you want to save current binding:
        HDC current_dc = wglGetCurrentDC();
        HGLRC current_context = wglGetCurrentContext();

        wglMakeCurrent(my_dc, current_context);

        MyUltraSuperRender(...);

        wglMakeCurrent(current_dc, current_context);
    }

Hope this helps :)

Upvotes: 1

datenwolf
datenwolf

Reputation: 162164

First things first, you actually should create a separate OpenGL context for your plugin, for the simple reason that it gives you a separate state space that doesn't interfere with the main programs OpenGL context.

You misunderstood the part about multiple rendering contexts though. It's perfectly possible to have an arbitrary number of OpenGL contexts for a process. But each thread of the process can bind only one context at a time. That one binding also includes the window DC the context is bound to. It is however perfectly legal change a context binding at any time. Either you change the window a given context is bound to, or you switch the context or you do both at the same time.

So in your situation I suggest you create a custom context for your plug-in, that you use for all the windows your plug-in creates.

That your simple context "creation" code fails has one simple reason: Your window will most likely not have a pixel format descriptor set.

I suggest you use the following method to create your new windows and contexts:

/* first get hold of the HDC/HRC of the parent */
HDC parentDC = wglGetCurrentDC();
HRC parentRC = wglGetCurrentContext();
int pixelformatID = GetPixelFormat(parentDC);

/* we use the same PFD as the parent */
PIXELFORMATDESCRIPTOR pixelformat;
memset(pixelformat, 0, sizeof(pixelformat);
DescribePixelFormat(parentDC, pixelformatID, sizeof(pixelformat), &pixelformat);

/* create a window and set it's pixelformat to the parent one's */
HWND myWND = create_my_window();
HDC  myDC = GetDC(myWND);
SetPixelFormat(myDC, pixelformatID, &pixelformat);

/* finally we can create a rendering context
 * it doesn't matter if we create it against
 * the parent or our own DC.
 */
HRC myRC = wglCreateContext(myDC);

/* we're done here... */

Now whenever your plugin wants to render something it should bind its own context, do its thing and bind the context that was bound before:

HDC prevDC = wglGetCurrentDC();
HRC prevRC = wglGetCurrentContext();

wglMakeCurrent(myDC, myRC);

/* do OpenGL stuff */

wglMakeCurrent(prevDC, prevRC);

Upvotes: 1

Related Questions