PDA

View Full Version : Copy frame data to texture



Klarkshroder
03-12-2011, 06:04 PM
I would like to copy the image currently drawn onscreen into a texture. It seems like glCopyTexSubImage2D is the best way to accomplish this, but it doesn't seem to be working.

I'm trying to get this to work first in a simple case. At the first frame I simply draw a red circle in the corner of the screen and store the current frame in a texture. In subsequent frames I draw the stored texture, then draw another red circle at an offset which increases with time, and then copy the current frame back to the texture.

If this were working correctly then I would expect to see all of the red circles which had been drawn in all earlier frames, which would show up as a line which gets longer as the animation proceeds. Instead I simply see a red circle marching across the screen, which seems to indicate that I am not correctly storing the frame data into my texture.

I would very much appreciate any help that anyone could offer.

I'm writing in Java using JOGL, and the relevant code is as follows:



public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();

gl.glDisable(GL2.GL_DEPTH_TEST);

gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, width, 0.0, height, 0.0, 1.0);
gl.glViewport(0, 0, width, height);

gl.glGenTextures(1, frameTexture, 0);
}

public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
GLU glu = new GLU();
GLUquadric quad = glu.gluNewQuadric();

gl.glClear(GL2.GL_COLOR_BUFFER_BIT);

if(frameNumber > 0) {
// draw the texture stored in frameTexture
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, frameTexture[0]);
gl.glBegin(GL2.GL_QUADS);
gl.glBegin(GL2.GL_QUADS);
gl.glTexCoord2f(0, 0);
gl.glVertex2d(0, 0);
gl.glTexCoord2f(width, 0);
gl.glVertex2d(width, 0);
gl.glTexCoord2f(width, height);
gl.glVertex2d(width, height);
gl.glTexCoord2f(0, height);
gl.glVertex2d(0, height);
gl.glEnd();
}

// draw a red circle
double[] oldColor = new double[4];
gl.glGetDoublev(GL2.GL_CURRENT_COLOR, oldColor, 0);
gl.glColor3dv(new double[] {1.0, 0.0, 0.0}, 0);
gl.glPushMatrix();
gl.glTranslated(frameNumber, frameNumber, 0);
glu.gluDisk(quad, 0, 15, 20, 2);
gl.glPopMatrix();
gl.glColor3dv(oldColor, 0);

// update frameTexture with the current frame buffer
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glBindTexture(GL2.GL_TEXTURE_2D, frameTexture[0]);
gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);

frameNumber++;
}

McLeary
03-12-2011, 06:51 PM
always when you wanna use a rendered image as texture, consider the use of Frame Buffer Objects (http://www.opengl.org/wiki/Framebuffer_Object). This kind off objects allows the use of techniques such as render-to-texture (http://www.songho.ca/opengl/gl_fbo.html#example), which can very useful for what you trying to accomplish.

Alfonse Reinheart
03-12-2011, 06:57 PM
gl.glGenTextures(1, frameTexture, 0);

This is not sufficient to create a texture. It creates the name for a texture, but that doesn't actually allocate the storage for any of the image data.

Therefore, you need to call glTexImage2D with the proper width and height, as well as the desired internal format.

Also, you seem to call glBegin twice. Is that a typo, or is that actually in your code?

Klarkshroder
03-12-2011, 07:34 PM
Thanks for the quick replies. I removed the extra call to glBegin() and changed my initialization to the following:



public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();

gl.glDisable(GL2.GL_DEPTH_TEST);

gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, width, 0.0, height, 0.0, 1.0);
gl.glViewport(0, 0, width, height);

gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glGenTextures(1, frameTexture, 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, frameTexture[0]);
Buffer texData = FloatBuffer.allocate(4 * width * height);
gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGBA, width, height, 0, GL2.GL_RGBA, GL2.GL_FLOAT, texData);
}


but this gives the same behavior as before.

If I can't get this working then I'll check out frame buffer objects, but for this application it seems that glCopyTexSubImage2D would be simpler although possibly slower.

Is there anything else that I'm doing blatantly wrong?

Alfonse Reinheart
03-12-2011, 07:50 PM
What happens if you just draw a white quad instead of trying to draw a textured quad?

Klarkshroder
03-12-2011, 10:08 PM
Commenting out all of the texturing calls doesn't change the observed behavior at all; I still see a red circle marching diagonally across the screen. As a sanity check I also tried adding a call



gl.glColor3d(0.8, 0.8, 0.8);


before starting the quad. This gives a gray background with a red circle marching diagonally across the screen, as expected; it looks basically the same as it did before.

mbentrup
03-12-2011, 11:06 PM
I guess your texture coordinates are wrong:



gl.glTexCoord2f(width, height);


should be



gl.glTexCoord2f(1.0f, 1.0f);


The texture is in the range 0.0 to 1.0, you're just filling the screen with the texture's border color (except one corner pixel).

Klarkshroder
03-13-2011, 12:54 AM
I guess your texture coordinates are wrong
I thought that might be the problem at first too, but changing width and height to 1.0f does not have the desired effect.

Moreover, I have another texture (from a file, not from the color buffer) of the same size as the texture I'm trying to create, and this other texture doesn't render correctly unless I use width and height for texture coordinates; using 1.0f just fills the screen with a single pixel.

_arts_
03-13-2011, 03:40 AM
Try with using non rectangular textures (so with width=height), use unsigned byte instead of float (no need to try float here), and stuck your texture coordinates between 0 and 1.

Dan Bartlett
03-13-2011, 04:42 AM
It could also be a mip-mapping issue, try disabling them, or calling glGenerateMipmap(GL_TEXTURE_2D) after glCopyTexSubImage2D if you do actually want mipmaps.

Klarkshroder
03-13-2011, 04:12 PM
After switching to unsigned bytes, changing texture coordinates back to 1.0f and disabling mipmaps, it almost works! My code now looks like this:



public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();

gl.glDisable(GL2.GL_DEPTH_TEST);

gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, width, 0.0, height, 0.0, 1.0);
gl.glViewport(0, 0, width, height);

gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glGenTextures(1, frameTexture, 0);
gl.glBindTexture(GL2.GL_TEXTURE_2D, frameTexture[0]);
Buffer texData = ByteBuffer.allocate(3 * width * height);
gl.glTexImage2D(GL2.GL_TEXTURE_2D, 0, GL2.GL_RGB, width, height, 0, GL2.GL_RGB, GL2.GL_UNSIGNED_BYTE, texData);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL2.GL_LINEAR);
gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL2.GL_LINEAR);
}

public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
GLU glu = new GLU();
GLUquadric quad = glu.gluNewQuadric();

gl.glClear(GL2.GL_COLOR_BUFFER_BIT);

if(frameNumber > 0) {
// draw the texture stored in frameTexture
gl.glEnable(GL2.GL_TEXTURE_2D);
gl.glBindTexture(GL.GL_TEXTURE_2D, frameTexture[0]);
gl.glBegin(GL2.GL_QUADS);
gl.glTexCoord2f(0, 0);
gl.glVertex2d(0, 0);
gl.glTexCoord2f(1.0f, 0);
gl.glVertex2d(width, 0);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex2d(width, height);
gl.glTexCoord2f(0, 1.0f);
gl.glVertex2d(0, height);
gl.glEnd();
}

// draw a red circle
gl.glDisable(GL2.GL_TEXTURE_2D);
gl.glColor4d(1.0, 0.0, 0.0, 1.0);
gl.glPushMatrix();
gl.glTranslated(frameNumber, frameNumber, 0);
glu.gluDisk(quad, 0, 10, 20, 2);
gl.glPopMatrix();

// update frameTexture with the current frame buffer
gl.glBindTexture(GL2.GL_TEXTURE_2D, frameTexture[0]);
gl.glCopyTexSubImage2D(GL2.GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);

frameNumber++;
}


Setting width = height = 256 and letting it run for a few frames, I get the following:

http://i.imgur.com/QPfx6.png

This seems to indicate that the frame data is indeed being stored in the texture, although it looks like it is somehow getting resized at each frame, giving the nonlinear tail.

Changing GL_TEXTURE_MIN_FILTER to GL_NEAREST gives the following, which also doesn't look quite right:

http://i.imgur.com/Rr9f9.png

mhagain
03-13-2011, 05:06 PM
Not really relevant, but if you want this to perform well you probably shouldn't use GL_RGB.

See:
http://www.opengl.org/wiki/Common_Mistakes#Texture_upload_and_pixel_reads
http://www.opengl.org/wiki/Common_Mistakes#Image_precision
http://www.opengl.org/wiki/Common_Mistakes#Unsupported_formats_.233
http://www.opengl.org/wiki/Common_Mistakes#Unsupported_formats_.234

Alfonse Reinheart
03-13-2011, 05:29 PM
Not really relevant, but if you want this to perform well you probably shouldn't use GL_RGB.

He's just allocating the texture's storage. By all rights, he should just be passing NULL (or the Java equivalent) rather than an actual pointer.

Klarkshroder
03-13-2011, 07:37 PM
Passing null works as well, but I think that mhagain has a valid point - according to the wiki, using GL_RGB instead of GL_ARGB takes a performance hit because most hardware doesn't work nicely with groups of 24 bits.

Klarkshroder
03-15-2011, 02:43 AM
After playing around a bit more, it works perfectly now; the stretching occurred because the viewport wasn't actually width by height, so I was mapping the texture back onto a region that didn't correspond exactly to the window. Adding the following in init() fixed the problem:



int[] viewport = new int[4];
gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
gl.glOrtho(0.0, viewport[2], 0.0, viewport[3], 0.0, 1.0);

Thanks again to everyone who offered suggestions!