Creating OpenGL Textures from Embedded Win32 Resources
Posted by Tom on 2011-06-10 17:37
So I was faffing around with Those Magnificent Men - one of my after-hours just-simple-enough-to-work game projects. I'd been working quite a lot with Cinder and its slick resource handling and wondered if I could do something similar in my own non-Cinder projects. Rather than loading the game's resources (graphics, sounds and the like) from external files, it's neater as a single, all-encompassing monolithic beast of an executable. Besides, any simple game is going to find itself competing with Flash when it comes to ease of distribution, so anything you can do to smooth the process has got to be good.
Typically you'd embed the data inside the binary as a resource, but there's no mechanism for loading my image format of choice (PNG) from a resource. Only BMP images, which are made from orphans' tears and give kittens cancer. There must be a way to loaded PNGs out of an embedded resource and into an OpenGL texture, right?
Resource Loading . . .
I ended up using the resource type of RCDATA since any of the included image formats come with Visual Studio baggage. Oddly, even calling it PNG causes Visual Studio to try and barge you out of the way in that charming I-know-best-pal manner, despite PNG not being on the list of file types it purports to recognise.
HRSRC resourceBlock = ::FindResource(NULL, MAKEINTRESOURCE(resourceId), RT_RCDATA);
HGLOBAL resourceHandle = ::LoadResource(NULL, resourceBlock);
unsigned char *resourceData = (unsigned char *)::LockResource(resourceHandle);
That's our data, but it's in PNG format. It's time to break out the image loaders.
Image Loading . . .
In the past I've used stb_image for my image loading needs, but since it's all I've ever really used I decided to shop around a bit. In the end I plumped for the hairier libpng instead, but there are plenty of image loading options out there. If you're using another one then do your thing and we'll meet up again at part 3.
While libpng typically takes its input from a FILE pointer, it will allow you to supply your own function to do the data reading. In our case it's going to look like this:
struct PngBuffer
{
PngBuffer(unsigned char *data) : Data(data), Position(0) { }
unsigned char *Data;
unsigned int Position;
};
void ReadData(png_structp pngPtr, png_bytep data, png_size_t length)
{
PngBuffer *buffer = reinterpret_cast<PngBuffer *>(png_get_io_ptr(pngPtr));
memcpy(reinterpret_cast<unsigned char *>(data), buffer->Data + buffer->Position, length);
buffer->Position += length;
}
When setting up our libpng structures we then pass in the above function using png_set_read_fn.
png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop infoPtr = png_create_info_struct(pngPtr);
PngBuffer buffer(resourceData);
png_set_read_fn(pngPtr, rreinterpret_cast<voidp>(&buffer), ReadData);
And then it's business as usual.
png_read_info(pngPtr, infoPtr);
unsigned int width = infoPtr->width;
unsigned int height = infoPtr->height;
short bitDepth = infoPtr->bit_depth;
short channels = infoPtr->channels;
unsigned char *imageData = new unsigned char[width * height * bitDepth * channels / 8];
unsigned char **rowPointers = new unsigned char *[height];
for (unsigned int i = 0; i < height; i ++)
rowPointers[i] = &(imageData[i * width * bitDepth * channels / 8]);
png_read_image(pngPtr, (png_bytepp)rowPointers);
// Insert OpenGL texture loading gubbins here
delete [] imageData;
delete [] rowPointers;
Texture Loading . . .
That's the hard part out of the way. Now we have the raw pixel data we need to loading this into OpenGL as texture data. The following lines will look very familiar to anyone with OpenGL experience.
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, x, y, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
Beer Loading . . .
Job done!
P.S. We're not checking any error codes, return values or doing anything, well, boring here. You're probably going to want to beef this out a little bit, but it illustrates the points. You know the rules.