Offscreen GL contexts and hardware acceleration

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Jul 30, 2009 at 06:02

Today, I was working on a project where I wanted to do some OpenGL rendering to an offscreen buffer and save it as a bitmap. I googled around a bit for information on creating an offscreen rendering context (in Windows). The usual way to do it, according to the Internet (see for example this GameDev post) seems to be to create an offscreen device context with CreateCompatibleDC, and use a PFD_DRAW_TO_BITMAP pixel format.

However, when I tried this I found that the GL context thus created is not hardware accelerated, but falls back to Windows’ built-in GL 1.1 software driver.

The other obvious way to do it is create a hidden window and set up GL normally, with PFD_DRAW_TO_WINDOW. This gets hardware accelerated and works (you can draw and retrieve the image without ever showing the window on-screen). It seems a bit extravagant though. Both methods “should” work equally well since it just comes down to drawing to a memory area that doesn’t happen to be on-screen.

I’m curious about other peoples’ experience here - if you’ve built a program that uses offscreen rendering, have you encountered similar issues? Are there configurations out there for which offscreen-DC does work, or hidden-window doesn’t? Is there a different way of setting up the offscreen DC to make this work?

9 Replies

Please log in or register to post a reply.

Fdbdc4176840d77fe6a8deca457595ab
0
dk 158 Jul 30, 2009 at 07:50

What about a framebuffer object (EXT_framebuffer_object), which can render to an offscreen framebuffer? Since it’s windowing-system-agnostic, you wouldn’t need any window-specific information and it should be accelerated by the hardware if it’s supported. You can then use glReadPixels or glCopyTexImage2D to read back the contents of the render buffer. Seems like this should work in your case, unless I misunderstood what you’re trying to do.

This page has good information about them.

8fd4a055522ce713cde7dd1cb4083cb2
0
martinsm 101 Jul 30, 2009 at 10:50

You anyway need valid OpenGL context to create and use framebuffer objects.

6837d514b487de395be51432d9cdd078
0
TheNut 179 Jul 30, 2009 at 12:56

Like what Dia said, I too would recommend just using a render to texture option and then you can pull / save the bitmap data however you like. It’s pretty fast too, since I’ve recorded video feeds in real-time using it (if you’re worried about glCopyTeximage being slow). Its my preferred choice over off-screen rendering / p-buffers, which also runs into numerous portability issues.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Jul 30, 2009 at 16:08

I’m aware of framebuffer objects, but those don’t solve my problem - like martinsm says, you need to have an OpenGL context already to use them. My question is about how you get the OpenGL context in the first place. If you are writing an interactive app or game and already have a context for your main window, great, but I’m writing a console app to do some offline processing and am not showing anything on the screen, so I don’t have a main window.

Update: I found that while the hidden-window method works in Vista, it does not seem to work in XP - with the window hidden, attempting to read back the framebuffer just grabs whatever is on-screen at the window’s location.

I can probably still use a framebuffer object together with the hidden window to get hardware offscreen rendering. It’s even more rigmarole, though.

8fd4a055522ce713cde7dd1cb4083cb2
0
martinsm 101 Jul 30, 2009 at 19:51

I usually use hidden window + FBO approach for some GPGPU stuff I do. Now of course I wait for OpenCL :)

6837d514b487de395be51432d9cdd078
0
TheNut 179 Jul 30, 2009 at 23:34

Yep, a hidden window is the only way to go. Here’s some code, which works fine in XP. Resolutions are of course limited to your desktop, but you can add a quick FBO in there and you’re well off into the races.

LRESULT CALLBACK WndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd, Message, wParam, lParam);
}

int main (int argc, char **argv)
{
    // Window properties
    int width = 640;
    int height = 480;

    WNDCLASSEX wndClass;
    wndClass.cbSize         = sizeof(WNDCLASSEX);
    wndClass.style          = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
    wndClass.lpfnWndProc        = WndProc;
    wndClass.cbClsExtra     = 0;
    wndClass.cbWndExtra     = 0;
    wndClass.hInstance      = 0;
    wndClass.hIcon          = 0;
    wndClass.hCursor        = LoadCursor(0, IDC_ARROW);
    wndClass.hbrBackground  = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wndClass.lpszMenuName   = 0;
    wndClass.lpszClassName  = "WndClass";
    wndClass.hIconSm        = 0;
    RegisterClassEx(&wndClass);

    // Style the window and remove the caption bar (WS_POPUP)
    DWORD style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP;
    
    // Create the window. Position and size it.
    HWND wnd = CreateWindowEx(0,
                  "WndClass",
                  "",
                  style,
                  CW_USEDEFAULT, CW_USEDEFAULT, width, height,
                  0, 0, 0, 0);

    HDC dc = GetDC(wnd);

    // Setup OpenGL
    PIXELFORMATDESCRIPTOR pfd;
    memset(&pfd, 0, sizeof(PIXELFORMATDESCRIPTOR));

    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 32;
    pfd.cDepthBits = 16;
    pfd.cStencilBits = 8;
    pfd.iLayerType = PFD_MAIN_PLANE;

    int pixelFormat = ChoosePixelFormat(dc, &pfd);
    SetPixelFormat(dc, pixelFormat, &pfd);

    HGLRC rc = wglCreateContext(dc);
    wglMakeCurrent(dc, rc);

    // Initialize your OpenGL properties and states here.
            
    // Do a loop or call display() once, whatever rocks your boat
    while ( true )
    {
        display();
        Sleep(33);

        SwapBuffers(dc);

        // Save yer image here.
    }
}
A8433b04cb41dd57113740b779f61acb
0
Reedbeta 168 Jul 31, 2009 at 02:11

Thanks. BTW, you can actually specify DefWindowProc directly as the window procedure, with no need for the passthrough routine. :)

6837d514b487de395be51432d9cdd078
0
TheNut 179 Jul 31, 2009 at 10:08

I just ripped parts and pieces from my engine so I didn’t bother cleaning it up much ;)

1debcb5848ab9637cc01646ba6ae50d3
0
dpatte 101 Apr 19, 2010 at 19:32

Hi ‘TheNut’

I’m slightly confused with the idea of rendering openGL to an off-screen buffer.

I am trying to generate a skin using openGL that will be used as a source bitmap by UpdateLayeredWindow. It takes a source HDC as an input.

I used the code you provided above, and it works fine, but only if I leave the new openGL window visible (SW_SHOW). If I hide the new openGL window the hdc doesnt seem to contain the image I generated, and updateLayeredWindow is not what I expect.

Am I missing something?