[JOGL] Update a texture with a sub-image
There is currently no elegant way to update a sub-portion of an OpenGL texture in the JOGL API (you can only update the texture with a full BufferedImage positioned at (x,y) without being able to provide a custom width and height).
As I’m trying to render Swing components inside a 3D environment (a la Looking Glass but in much, much less ambitious :-D), I needed such a functionality…
I compared 3 update methods (update full image, update line by line or filling a reusable direct buffer with the sub-image data before setting it with a single glTexSubImage2D call), and found that the update-line-by-line method was faster on my machine (PowerBook G4, Mac OS X 10.4.8, NVIDIA 5200 something with 1.2 GB RAM), for sub-images reasonably smaller than the original (haven’t tested after which sub-size this becomes more costly than the whole texture update method, though).
Here’s a public-domain code snippet that does the job (copy-paste friendly, no warranty):
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.nio.IntBuffer;
import javax.media.opengl.GL;
import com.sun.opengl.util.texture.Texture;
...
/**
* Update a portion of a texture beginning at a given offset,
* using a BufferedImage.
* The provided BufferedImage can be a sub-image created with
* BufferedImage.getSubImage(int,int,int,int),
* in which case the (x,y) start point and the dimension of the sub-image
* will be honoured.
*
* Example:
* updateSubTexture(gl, texture, xTexture, yTexture,
* image.getSubImage(xImage, yImage, width, height));
*
* @see BufferedImage.getSubimage(int,int,int,int)
* @param gl
* @param texture
* @param x X coordinate of subtexture
* @param y Y coordinate of subtexture
* @param sourceImage image to update the subtexture with.
* @throws IllegalArgumentException if the image type is different
* from TYPE_INT_ARGB
*/
public void updateSubTexture(
GL gl,
Texture texture, int x, int y,
BufferedImage sourceImage)
{
if (sourceImage.getType() != BufferedImage.TYPE_INT_ARGB)
throw new IllegalArgumentException(
"Cannot update subtexture with anything else "+
"than TYPE_INT_ARGB buffered images yet !");
WritableRaster raster = sourceImage.getRaster();
IntBuffer dataBuffer = IntBuffer.wrap(
((DataBufferInt)raster.getDataBuffer()).getData());
Rectangle bounds = raster.getBounds();
// Get the scanline of the array that backs all the writable rasters
// from this raster's hierarchy.
// Also absolutize bounds.x and bounds.y
int scanline = bounds.width;
while ((raster = raster.getWritableParent()) != null) {
Rectangle parentBounds = raster.getBounds();
bounds.x += parentBounds.x;
bounds.y += parentBounds.y;
scanline = parentBounds.width;
}
// Update the sub texture row by row
// not tested yet on little endian platforms : feedback welcome
int type = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ?
GL.GL_UNSIGNED_INT_8_8_8_8_REV :
GL.GL_UNSIGNED_INT_8_8_8_8;
texture.bind();
for (int row = bounds.height; row-- != 0;) {
dataBuffer.position((row + bounds.y) * scanline + bounds.x);
gl.glTexSubImage2D(texture.getTarget(),
0, // no support for mipmapping
x, y + row, // in texture
bounds.width, 1,
GL.GL_BGRA,
type,
dataBuffer
);
}
gl.glBindTexture(texture.getTarget(), 0);
}
If colours are messed up on Intel / little endian platforms, please tell me, as I haven’t tested on anything else than on my Mac/PPC laptop…
[Edit Dec. 19th 2006] : In a RFE submitted to the JOGL project, Chris Campbell referred to this entry and proposed a far better approach than the one I used : it relies on GL_PACK_ROW_LENGTH, GL_PACK_SKIP_ROWS and GL_PACK_SKIP_PIXELS. I haven’t tested it yet but it definitely seems like there won’t be any better solution. I encourage you to vote for this RFE, if you’re on the project’s watch-list…