« Emulating Bilinear Filtering | Main | Fragment Program Reference »
May 17, 2005
Mac PBuffers
If you ever need to do more image processing than you can with a single fragment program pass, you'll need to render to a texture. The best way to do that on OS X is using a pbuffer, here's some example code for creating and using them.
I've included the code inline below, or you can get a zip of the code from PBufferUtils.zip here
To use it, call PBuffer_Create(sharedContext, width, height, 8, ePBufferFlag_ZBuffer) to create a pbuffer with a depth buffer attached, or 0 as the last argument for no z buffer. The 'sharedContext' is a housekeeping device to let texture IDs be shared between multiple contexts, rather than being only usable within a single context which is the default. If you have a single shared context in your app, this means you can create a texture in one pbuffer's context, whether by uploading pixel data or referencing a pbuffer, and use it in any other pbuffer's context in your app. I go into detail on contexts here
Use PBuffer_Begin() before you start drawing into it, and PBuffer_End() once you're done. When you're ready to use it as a texture, call PBuffer_Use() in the same place you'd normally do a
glEnable(GL_TEXTURE_RECTANGLE_EXT);
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, someTextureID);
and do a
glDisable(GL_TEXTURE_RECTANGLE_EXT);
when you're done with it.
PBuffer_Begin() clears the pbuffer, sets up an orthographic view and resets some common gl state, but you need to be careful to reset gl state manually, a lot of our bugs have been caused by state left hanging around.
You'll get an error if you try to create a pbuffer with different pixel attributes than the shared context, even if the difference doesn't seem relevant. For example, you might need to add kCGLPFANoRecovery to the list of attributes if that's what the shared context has been created with.
Creating and destroying pbuffers is expensive, so we tend to carry them over from frame to frame unless they need to change in size. If size changes are needed, we try to over-allocate and create bigger pbuffers than we need, to avoid frequent reallocations for growing objects.
Destroying a pbuffer immediately after using it as a texture also seems to cause rendering problems, as if the surface is destroyed before the renderer uses it. Waiting a little while, for example until the next time you need to render, seems to prevent this.
Here's the code to go into PBufferUtils.h:
------------
#ifndef INCLUDE_PBUFFER_UTILS_H
#define INCLUDE_PBUFFER_UTILS_H
#include
#include
enum EPBufferFlags
{
ePBufferFlag_ZBuffer=(1<<0),
};
typedef struct PetePBuffer_tag {
CGLPBufferObj pbuffer;
CGLContextObj pbufferContext;
CGLContextObj previousContext;
int width;
int height;
GLuint textureID;
bool needsClearing;
bool needsFlush;
int createdWidth;
int createdHeight;
} PetePBuffer;
PetePBuffer* PBuffer_Create(CGLContextObj sharedContext,int nWidth,int nHeight,int colorDepth, int flags);
void PBuffer_Destroy(PetePBuffer* pbuffer);
// Surround rendering with these to draw into the pbuffer, they handle pushing and popping the old
// context for you
void PBuffer_Begin(PetePBuffer* pbuffer);
void PBuffer_End(PetePBuffer* pbuffer);
// Binds the pbuffer as a texture
void PBuffer_Use(PetePBuffer* pbuffer);
#endif // INCLUDE_PBUFFER_UTILS_H
------
and here's the PBufferUtils.cpp code:
------
#include "PBufferUtils.h"
#include
#include
static void checkCGLErrorImplementation(CGLError error, char* sourceFile, int sourceLine)
{
if (error) {
const char* errStr;
errStr = CGLErrorString(error);
fprintf(stderr, "CGL Error: %s at %s:%d\n", errStr, sourceFile, sourceLine);
assert(false);
}
}
#define checkCGLError(error) checkCGLErrorImplementation(error,__FILE__,__LINE__)
PetePBuffer* PBuffer_Create(CGLContextObj sharedContext,int width,int height,int colorDepth, int flags)
{
PetePBuffer* pbuffer=new PetePBuffer;
pbuffer->pbuffer=NULL;
pbuffer->pbufferContext=NULL;
pbuffer->previousContext=NULL;
pbuffer->width=width;
pbuffer->height=height;
pbuffer->textureID=0;
pbuffer->needsClearing=true;
pbuffer->needsFlush=false;
const int bitsPerPixel = (colorDepth*4);
const bool hasZBuffer = (flags&ePBufferFlag_ZBuffer);
int i = 0;
CGLPixelFormatAttribute pixelFormatAttributes[32];
pixelFormatAttributes[i++] = kCGLPFAAccelerated;
pixelFormatAttributes[i++] = kCGLPFAWindow;
pixelFormatAttributes[i++] = kCGLPFAColorSize;
pixelFormatAttributes[i++] = (CGLPixelFormatAttribute)(bitsPerPixel);
if (colorDepth>8)
pixelFormatAttributes[i++] = kCGLPFAColorFloat;
if (hasZBuffer)
{
pixelFormatAttributes[i++] = kCGLPFADepthSize;
pixelFormatAttributes[i++] = (CGLPixelFormatAttribute)(16);
}
pixelFormatAttributes[i++] = (CGLPixelFormatAttribute)0;
long numPixelFormats = 0;
CGLPixelFormatObj pixelFormat = NULL;
CGLError error = CGLChoosePixelFormat(pixelFormatAttributes, &pixelFormat, &numPixelFormats);
checkCGLError(error);
error = CGLCreateContext(pixelFormat, sharedContext, &pbuffer->pbufferContext);
checkCGLError(error);
CGLDestroyPixelFormat(pixelFormat);
error = CGLCreatePBuffer(pbuffer->width,pbuffer->height,GL_TEXTURE_RECTANGLE_EXT,GL_RGBA,0,&pbuffer->pbuffer);
checkCGLError(error);
return pbuffer;
}
void PBuffer_Destroy(PetePBuffer* pbuffer) {
if (pbuffer!=NULL)
{
CGLDestroyPBuffer(pbuffer->pbuffer);
CGLDestroyContext(pbuffer->pbufferContext);
}
delete pbuffer;
}
void PBuffer_Begin(PetePBuffer* pbuffer) {
// Pete- check to ensure begin() hasn't already been called for this pbuffer
assert(pbuffer->previousContext==NULL);
pbuffer->previousContext=CGLGetCurrentContext();
long screen;
CGLError error=CGLGetVirtualScreen(pbuffer->previousContext,&screen);
assert(!error);
error=CGLSetCurrentContext(pbuffer->pbufferContext);
checkCGLError(error);
error=CGLSetPBuffer(pbuffer->pbufferContext,pbuffer->pbuffer,0,0,screen);
checkCGLError(error);
glClearColor(0.0f,0.0f,0.0f,0.0f);
if (pbuffer->needsClearing)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
glDisable(GL_FRAGMENT_PROGRAM_ARB);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
glColor4f(1.0f,1.0f,1.0f,1.0f);
glOrtho(0, pbuffer->width, 0, pbuffer->height, -1.0, 1.0);
glActiveTexture(GL_TEXTURE0);
}
void PBuffer_End(PetePBuffer* pbuffer) {
glFlush();
pbuffer->needsFlush=true;
assert(pbuffer->previousContext!=NULL);
CGLError error=CGLSetCurrentContext(pbuffer->previousContext);
checkCGLError(error);
pbuffer->previousContext=NULL;
}
void PBuffer_Use(PetePBuffer* pbuffer) {
if (pbuffer->needsFlush)
{
CGLContextObj currentContext=CGLGetCurrentContext();
CGLError error=CGLSetCurrentContext(pbuffer->pbufferContext);
checkCGLError(error);
glFlush();
error=CGLSetCurrentContext(currentContext);
checkCGLError(error);
pbuffer->needsFlush=false;
}
if (pbuffer->textureID==0)
{
CGLContextObj currentContext=CGLGetCurrentContext();
glGenTextures(1,&pbuffer->textureID);
glBindTexture(GL_TEXTURE_RECTANGLE_EXT,pbuffer->textureID);
glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_RECTANGLE_EXT,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
CGLError error=CGLTexImagePBuffer(currentContext,pbuffer->pbuffer,GL_FRONT_LEFT);
checkCGLError(error);
}
else
{
glBindTexture(GL_TEXTURE_RECTANGLE_EXT,pbuffer->textureID);
}
glEnable(GL_TEXTURE_RECTANGLE_EXT);
}
Posted by petewarden at May 17, 2005 05:25 PM