
Today I added fast previewing to the art pipeline, so that you don't have to wait for a render farm distribution to use placeholder images for various scenes. I wanted to use 3D hardware acceleration to do the rendering so it would be zippy, given the high polygon counts of the scenes... even though the rendering is just basic solid-shaded polygons, it still would be quite slow at 1920x1080 in software with multi-million polygon count scenes, to be sure.
Unfortunately, it appears that you can't actually render using hardware-accelerated OpenGL without having an actual HWND. I'm not 100% clear on this, so that may be just internet misinformation. But apparently if you OpenGL rendering to a DIB section, you're probably not going to get hardware acceleration. I didn't do any testing to see if these were filthy lies, though, so maybe someone out there has more information.
No big deal, though - I just use an HWND that isn't actually displayed. This way it's basically the same as not having one at all as far as the user is concerned. But there's one nasty gotcha: if you just use the standard OpenGL buffers for drawing, you will get black, since, at least according to nVidia, OpenGL doesn't "own" those pixels if they are not displayed in an actual window, so they don't pass the "inclusion test". This happens regardless of whether or not you are using the GL_BACK or GL_FRONT buffers, or any other standard GL_ buffers. I'm sure there's some fancy spec lawyering going on here, but I try to stay clear of such things :)
The solution turns out to be using the OpenGL 2.0 framebuffer interface. If you define your own framebuffer, then you can draw to it anytime you want, regardless of whether or not it could potentially be displayed. Since this code seems hard to come by as-is on the net, I thought I'd post it. I've seen code snippets out there that almost do this, but they all rely on selecting a texture into the color channel, rather than just using renderbuffer storage. So here is the actual code that you need to use renderbuffer storage for everything:
GLsizei Width = 1920;
GLsizei Height = 1080;
GLuint Framebuffer;
glGenFramebuffers(1, &Framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, Framebuffer);
GLuint ColorBuffer, DepthBuffer;
glGenRenderbuffers(1, &ColorBuffer);
glGenRenderbuffers(1, &DepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, DepthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, Width, Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_RENDERBUFFER, DepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, ColorBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, Width, Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, ColorBuffer);
GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(Status == GL_FRAMEBUFFER_COMPLETE)
{
// You have succeeded!
}
else
{
// You are all about failure.
}
Once you've done that, you're good to go. You can render all you want, and then do a glReadPixels to recover the rendered image, no visible window necessary.
But one word to the wise: at least on my driver, you need to do a smidgen of cleanup before exiting the app, even if it's just a hacky app:
glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteFramebuffers(1, &Framebuffer);
If I didn't delete the framebuffer, I got faults in the nVidia driver on app shutdown. So AFAICT, leaving a bound framebuffer is not something that the nVidia driver cleans up properly.
(no contents)