Fixing multi-window opengl on windows

Hey,

Apologies if this isn't the correct place to post but I came across one of casey's blog posts about multi-window applications with opengl:
https://caseymuratori.com/blog_0026

I'm currently trying to implement opengl rendering that needs to target multiple windows. I've tried to use the functions mentioned in the blog:
1
2
CGLuint wglAcquireDCFramebuffer(HDC hDC);
void wglReleaseDCFramebuffer(HDC hDC, CGLuint framebuffer);


But can't find documentation anywhere and the functions don't seem to exist in the windows opengl extension string. I'm guessing this was more of a suggestion on how this problem could be fixed rather than a how to on how to go about this? With that in mind and with the assumption the previously mentioned functions aren't available, I was wondering if anyone has any insight on how best to approach this? My initial idea was as follows:

* Create a single opengl context through a "primary" window.
* Create a render target texture large enough to include all back buffers of the created windows.
* Render all window graphics in a single pass to the all encompassing render target.
* For each window, copy the appropriate regions of the large render target to the window's back buffer and display.

Now this might be a stupid idea/not even possible, but my main motivation was to try and reduce the number of wglMakeCurrent calls and to reduce the number of contexts I need to create at start up and use throughout the app. It was my understanding that switching between contexts could be expensive, hence the want to reduce the number of contexts as much as possible.

Any info/advice/tips on the subject would be greatly appreciated.

Edited by TheRarebit on Reason: Initial post
Those functions are mentioned as hypothetical extensions that would solve problems. They are not real.

Currently what you need to do to render to multiple windows is to create HGLRC rendering context for each of them. Then call wglMakeCurrent with one of them to make it active. Do rendering. Then call wglMakeCurrent with other one, do rendering to it. And so on... That's the only way to do rendering to multiple windows. Of course "rendering" here can be just a blit from existing texture, you can do all actual 2d/3d rendering in first context.

Regardless of how you do rendering, you will need to call wglMakeCurrent for each window of them to do actual rendering. You can try experimenting by creating separate thread for each window, and each thread has their own HGLRC activated with wglMakeCurrent. Then there would be no need to switch contexts - all you need would be to wake up thread on some condition to do the work. But multi-threading in GL is a not very stable, you might get different issues...

You might consider using Direct3D to get surface buffer for each window, and then share them with OpenGL. You can do this with WGL_NV_DX_interop2 extension (it is supported by newer Intel and AMD GPU's too). This way you should be able to blit GL textures from single GL context to multiple D3D11 textures/backbfufers.

Here's example that uses OpenGL to render into D3D11 texture (which is backbuffer of window): https://gist.github.com/mmozeiko/c99f9891ce723234854f0919bfd88eae

Edited by Mārtiņš Možeiko on
Cheers for the reply and the insight. I was afraid that would be the case but I guess having more than one context isn't exactly the end of the world. I did a bit of experimenting and it turned out I could use a single rendering context with multiple window DC's provided each DC's pixel format matches the one that the rendering context was created with (at least I think that's the requirement). So something along these lines seems to work:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// Creating global rendering context with a window dc:
for (u32 i = 0; i < numWindows; ++i)
{
    Window *win = createWindow(i);
    setPixelFormat(win->dc);

    // Only create a single context with the first window
    if (!globalOpenGLRC)
    {
        globalOpenGLRC = wglCreateContextAttribsARB(win->dc, 0, globalOpenGLAttribs);
    }
}

// Then later when rendering all windows this seems to work:
for (u32 i = 0; i < numWindows; ++i)
{
    Window *win = windowStorage + i;
    wglMakeCurrent(win->dc, globalOpengGLAttribs);
    // do actual rendering...
}


I'm sure this has some caveats and I'm yet to do any actual profiling to see what benefits if any this might have. As you say you can't get around having to make the wglMakeCurrent call per window so the only benefit I can see with the above approach is the fact you only need to create one context and only have the one context to manage. But again that probably has its pros and cons.

I had no idea you could do that with Direct3D! That's really interesting I'll look in to that as well as the example provided. Again thanks for the reply and the insight, very much appreciated!
That's a very risky "solution". While it's ok to use it for development/testing if it works for you, I would strongly not recommend shipping this.

This is very undocumented behavior - using GL context for DC that did not create it. It may work and it may not work.

I would create just bunch of context shared with each other. That's not where performance costs you, the performance will be lost with wglMakeCurrent anyways. So at that point don't rely on undocumented behavior, just share your context's with your primary one.

Edited by Mārtiņš Možeiko on
Ah I hadn't considered that it wasn't documented behaviour, it could very well be the case that it just happens to work on my exact machine which of course isn't what you want. I just assumed it was a niche use case that you rarely see in game/graphics development. Cheers for the help!
Oops, I just noticed that you are using wglCreateContextAttribsARB. Turns out for this function that is documented behavior - exactly how you intend to use it. I was thinking of old wglCreateContext function for which this not documented. So using ARB function is fine, sorry.
Oh cool, might be something we can use in production then. We're still early days in development though so we may find reasons in the future that this isn't feasible/optimal enough. No need to apologise, I appreciate the discussion and feedback :)