FBO -> PBO -> VBO for render to vertex array

I’ve got some particle positions that I adjust and render out to a framebuffer object. From there, I would like to get the positions into a vertex array for rendering. I’ve heard that PBO to VBO is the best way to do that. However, I cannot get my code to work. Can anyone help me out?

I’ve omitted a lot of the code that is not pertinent to this question. Basically, I set up the textures, framebuffer object, and pixel buffer object in the init function. In the main program loop, I update the particle positions in the simulate() function (not shown). Then I transfer the data from the FBO to the PBO for display in the drawParticles() function.

The result is that no points are drawn to the screen at all. If I draw any geometry right after the call to drawParticles(), it comes out yellow even if I say glColor3f(0.1, 0.1, 0.1).

#define MY_TEXTURE_TARGET  GL_TEXTURE_RECTANGLE_ARB

#define NUM_FRAMEBUFFERS 1
#define NUM_TEXTURES     2

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

//----------------------------------------------------------------
//  drawParticles()
//----------------------------------------------------------------
void drawParticles()
{
	glColor3f(0.5, 1.0, 1.0);

	// Bind the FBO so you can read the data from it
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbID[0]);
	// Do I need to set up viewport to match the size of the textures
	// in my FBO?  
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, pPosTexW, 0.0, pPosTexH);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, pPosTexW, pPosTexH);

	// Bind the PBO so you can put data into it
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, vertBufID[0]);
	// initialize the state of the data store
	glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, pPosTexW*pPosTexH*4*sizeof(GLfloat),
					 NULL, GL_STREAM_DRAW);


	// select which buffer (FBO attachment) to read from
	if (readFromEven) // wrote to oddbuffers
		glReadBuffer(GL_COLOR_ATTACHMENT1_EXT);
	else
		glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
	// transfer the data from the FBO to the PBO
	glReadPixels(0,0,pPosTexW, pPosTexH, GL_RGBA, GL_FLOAT, BUFFER_OFFSET(0));
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);

	// set the viewport back to what it was for drawing to the window
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, numCols, 0.0, numRows);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, windowW, windowH);

	// unbind the framebuffer object
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	// change the binding point of the buffer object to the
	// vertex array binding point
	glBindBufferARB(GL_ARRAY_BUFFER, vertBufID[0]);

	// draw the vertices
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(4, GL_FLOAT, 0, NULL);
	glDrawArrays(GL_POINTS, 0, numParticles);
	glDisableClientState(GL_VERTEX_ARRAY);

	glBindBufferARB(GL_ARRAY_BUFFER, 0);
}


//--------------------------------------------------------------
//  myInit()
//--------------------------------------------------------------
void myinit(void)
{
	// set up the CG programs
	// set the width and height for the particle texture
	// create an array to hold the initial particle positions
	// create some randomly placed massless particles
	// create the particle position textures

	// create buffer object for render-to-vertex-array
	glGenBuffers(1, &vertBufID[0]);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, vertBufID[0]);
	glBufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, pPosTexW*pPosTexH*4*sizeof(GLfloat),
					 NULL, GL_STREAM_DRAW);
	glBindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
}

//------------------------------------------------------------------
//  display()
//------------------------------------------------------------------
void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	drawParticles();
	glutSwapBuffers();
}

//------------------------------------------------------------------
//  TimerCallback(int)
//
// This routine controls the maximum framerate of the program.
// Otherwise it might go too fast on some processors.
//------------------------------------------------------------------
void TimerCallback(int value)
{
	glutTimerFunc(TimerDelay, TimerCallback, 0);
	Simulate();
	glutPostRedisplay();
}

I think some early versions of GL_EXT_framebuffer_blit were broken in that binding GL_FRAMEBUFFER_EXT would only bind the draw framebuffer and not the read framebuffer. If your driver supports this extension, try explicitly binding GL_READ_FRAMEBUFFER_EXT before doing the readback.

It’s not a bug, but you should perhaps change the usage from GL_STREAM_DRAW to GL_STREAM_COPY for better performance.

I’m a bit confused by that answer. I’m not using GL_EXT_framebuffer_blit; I’m just using GL_EXT_framebuffer_object. Just now, I looked at the framebuffer_blit spec, and it said that it is used for writing data from one framebuffer to another. I am trying to move data from an FBO to a PBO. Would that be what I need?

I did try changing the GL_FRAMEBUFFER_EXT to GL_READ_FRAMEBUFFER_EXT before doing readback, but it threw out an “invalid enumerant” error. (Though I just checked, and it looks like my driver does not support the framebuffer_blit extension; so that could explain the invalid enumerant error.)

The only thing I can think of to change based on the code shown is to make sure that you’re reading from the correct buffer. Maybe posting the texture loading code as well as the simulate functions would give some more insights.

Another thing, you might consider moving the code that copies the FBO data into the PBO into the simulate() function that you mention. This would save you the unecessary changing of the projection and modelview matrices. Also, you only really need to update the data in the buffer when the positions move which (should) only occur during the simulation step. Rendering of the particle system should consist of rendering the vertex array that has already been set. But that’s just my opinion on it and getting it to work is much more important than saving a few milliseconds.

Also, maybe try to isolate the render to vertex array behavior, make a small application that renders a 2D image that has the colors black, red and green into a texture, then try to read that image into a PBO and render as a VBO. Make the projection matrix glOrtho2D(-2,2,-2,2) and you should get a dot in the middle, and two others at (1,0) and (0,1). Once the behavior works for you it should be a lot easier to integrate into a larger application.

Hope some of this helps.

Apple has a bit of sample code showing FBO->PBO->VBO.

I did find one error, which was that I had forgotten to turn off fragment programs at the end of my simulate function. That solved the “everything comes out yellow” problem. Also, taking a hint from another post, I have tried changing my texture target from GL_TEXTURE_RECTANGLE_ARB to GL_TEXTURE_2D.

The reason that the code segment that copies the FBO data into the PBO is not in the simulate code is that I am also currently playing with a couple of other methods of getting the points to be drawn to screen. Like you said, optimization can come later, but I’ll definitely keep your suggestion in mind if I ever get PBOs working.

Interesting idea about the black, red, and green texture. I’ll try it out and let you know if I get it working.

As requested, here is the simulate code():

//----------------------------------------------------------------
//  Simulate()
//----------------------------------------------------------------
void Simulate()
{
	CGprogram fragProg;

	cgGLEnableProfile(FragmentProfile);

	cgGLBindProgram(FragGravity);
	fragProg = FragGravity;
	// Enable writing to the framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbID[0]);

	// Set up viewport to match the size of the textures
	// in your framebuffer.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, pPosTexW, 0.0, pPosTexH);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, pPosTexW, pPosTexH);


	// Set the texture as the render target
	if (readFromEven) // write to odd FBO attachment
		glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
	else
		glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);

	// pass parameters from OpenGL to Cg
	cgGLSetParameter1d(cgGetNamedParameter(fragProg, "timestep"), TimeStep);
	cgGLSetParameter1f(cgGetNamedParameter(fragProg, "numRows"), numRows);
	cgGLSetParameter1f(cgGetNamedParameter(fragProg, "numCols"), numCols);
	if (readFromEven) // read from even FBO attachment
	{
	cgGLSetTextureParameter(cgGetNamedParameter(fragProg, "currPos"), textureID[0]);
	cgGLEnableTextureParameter(cgGetNamedParameter(fragProg, "currPos"));
	}
	else  // read from odd FBO attachment
	{
	cgGLSetTextureParameter(cgGetNamedParameter(fragProg, "currPos"), textureID[1]);
	cgGLEnableTextureParameter(cgGetNamedParameter(fragProg, "currPos"));
	}

	// draw a quad representing the texture
	glBegin(GL_QUADS);
		glTexCoord2f(0,0);	glVertex2f(0,0);
		glTexCoord2f(1,0);	glVertex2f(pPosTexW,0);
		glTexCoord2f(1,1);	glVertex2f(pPosTexW,pPosTexH);
		glTexCoord2f(0,1);	glVertex2f(0,pPosTexH);
	glEnd();

	cgGLDisableTextureParameter(cgGetNamedParameter(fragProg, "currPos"));

	// set the viewport back to what it was for drawing to the window
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, numCols, 0.0, numRows);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glViewport(0, 0, windowW, windowH);

	// unbind the framebuffer object so the program can draw to the window
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	cgGLDisableProfile(FragmentProfile);


	if (readFromEven)
		readFromEven = false;
	else
		readFromEven = true;

  	Time += TimeStep;
}

And the create textures code:

//----------------------------------------------------------------
//  createTextures()
//----------------------------------------------------------------
void createTextures()
{
	glEnable(MY_TEXTURE_TARGET);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	// reserve IDs for the framebuffer object and textures
	glGenFramebuffersEXT(NUM_FRAMEBUFFERS, &fbID[0]);
	glGenTextures(NUM_TEXTURES, &textureID[0]);

	// initialize the framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbID[0]);

	// --- Texture 0 : ParticlePositions1
	glBindTexture(MY_TEXTURE_TARGET, textureID[0]);
	glTexParameteri(MY_TEXTURE_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(MY_TEXTURE_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameterf(MY_TEXTURE_TARGET, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(MY_TEXTURE_TARGET, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	// read the data into the texture
	glTexImage2D(MY_TEXTURE_TARGET, 0, GL_RGBA32F_ARB,
		pPosTexW, pPosTexH, 0, GL_RGBA, GL_FLOAT, pPosInit[0]);
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 
                             MY_TEXTURE_TARGET, textureID[0], 0);

	// --- Texture 1 : ParticlePositions2
	glBindTexture(MY_TEXTURE_TARGET, textureID[1]);
	glTexParameteri(MY_TEXTURE_TARGET, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(MY_TEXTURE_TARGET, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameterf(MY_TEXTURE_TARGET, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(MY_TEXTURE_TARGET, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexImage2D(MY_TEXTURE_TARGET, 0, GL_RGBA32F_ARB,
		pPosTexW, pPosTexH, 0, GL_RGBA, GL_FLOAT, 0);
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, 
                             MY_TEXTURE_TARGET, textureID[1], 0);

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	glDisable(MY_TEXTURE_TARGET);
}

Thanks for the sample code arekkusu. Unfortunately, I still can’t find the problem.

Differences I found between code:

  • Example code doesn’t specify glReadBuffer right before glReadPixels to transfer data from FBO to PBO. I’m not sure how it knows where to read from.
  • Example code seems to be using multitexturing.
  • Example code uses GL_TEXTURE_RECTANGLE_ARB for texture target.
  • Example code uses glBindBuffer instead of glBindBufferARB.
  • Example code’s framebuffer has a depth attachment in addition to a color attachment.
  • When initializing the data store with glBufferData, the example code calls GL_ARRAY_BUFFER instead of GL_PIXEL_PACK_BUFFER_ARB. (Though all other examples I’ve seen use GL_PIXEL_PACK_BUFFER_ARB.)
  • Draw and read buffer are framebuffer-dependent state; glBindFramebuffer sets READ_BUFFER to whatever it was last set to for that FBO, (it defaults to COLOR_ATTACHMENT_0);
  • No multitexturing here, although one of the vertex shaders uses a second texcoord to avoid a multiply per pixel in the fragment shader.
  • TEXTURE_RECTANGLE is best for this demo because it prefers to use float textures and the GeForce 5200 only support float textures with RECTANGLE target. On other cards, 2D would work too.
  • Ouch, you are right. On Mac OS X the GLEngine core entry point for glBindBuffer is always available, but it should be using glBindBufferARB to be completely safe (i.e. GMA 950 doesn’t support 1.5 core…)
  • This demo needs a depth buffer to visualize some rotating torii.
  • glBufferData is doing the one-time storage setup for the VBO. It shouldn’t matter if you bind to a VBO or PBO target; it’s all the same memory.

Okay, I finally made the simple program with the green, black, and red texture. (Thanks for the suggestion, brtnrdr.) It works! I even tried alternating between two textures, and that worked, too.

So I guess the problem lies somewhere in the code where I’m copying the texture from the FBO to the PBO.

All hail the queen of the idiots. Here’s a hint for anyone else as stupid as I am: If you are going to render from a vertex array, make sure the w-coordinate of your vertices is 1, not 0!!!

Thanks to everyone that helped me on this problem. I knew that somewhere along the line I would figure out I had made some stupid error;I just never figured it would be this stupid. Bleh.