diff options
Diffstat (limited to 'src')
-rwxr-xr-x | src/classes/com/sun/opengl/util/texture/Texture.java | 96 | ||||
-rwxr-xr-x | src/classes/com/sun/opengl/util/texture/TextureData.java | 280 |
2 files changed, 233 insertions, 143 deletions
diff --git a/src/classes/com/sun/opengl/util/texture/Texture.java b/src/classes/com/sun/opengl/util/texture/Texture.java index d3d9ac224..d22aabc9a 100755 --- a/src/classes/com/sun/opengl/util/texture/Texture.java +++ b/src/classes/com/sun/opengl/util/texture/Texture.java @@ -138,6 +138,7 @@ public class Texture { private int estimatedMemorySize; private static final boolean DEBUG = Debug.debug("Texture"); + private static final boolean VERBOSE = Debug.verbose(); // For now make Texture constructor package-private to limit the // number of public APIs we commit to @@ -453,7 +454,7 @@ public class Texture { data.getWidth(), data.getHeight(), data.getPixelFormat(), data.getPixelType(), data.getBuffer()); } finally { - gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore align + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment } } else { checkCompressedTextureExtensions(data); @@ -472,7 +473,7 @@ public class Texture { gl.glTexImage2D(newTarget, i, data.getInternalFormat(), width, height, data.getBorder(), data.getPixelFormat(), data.getPixelType(), null); - updateSubImageImpl(data, newTarget, i, 0, 0); + updateSubImageImpl(data, newTarget, i, 0, 0, 0, 0, data.getWidth(), data.getHeight()); } width /= 2; @@ -488,7 +489,7 @@ public class Texture { gl.glTexImage2D(newTarget, 0, data.getInternalFormat(), texWidth, texHeight, data.getBorder(), data.getPixelFormat(), data.getPixelType(), null); - updateSubImageImpl(data, newTarget, 0, 0, 0); + updateSubImageImpl(data, newTarget, 0, 0, 0, 0, 0, data.getWidth(), data.getHeight()); } } } @@ -507,9 +508,8 @@ public class Texture { /** * Updates a subregion of the content area of this texture using the - * data in the given image. Only updates the specified mipmap level - * and does not re-generate mipmaps if they were originally produced - * or loaded. + * given data. Only updates the specified mipmap level and does not + * re-generate mipmaps if they were originally produced or loaded. * * @param data the image data to be uploaded to this texture * @param mipmapLevel the mipmap level of the texture to set. If @@ -524,7 +524,42 @@ public class Texture { * OpenGL-related errors occurred */ public void updateSubImage(TextureData data, int mipmapLevel, int x, int y) throws GLException { - updateSubImageImpl(data, target, mipmapLevel, x, y); + updateSubImageImpl(data, target, mipmapLevel, x, y, 0, 0, data.getWidth(), data.getHeight()); + } + + /** + * Updates a subregion of the content area of this texture using the + * specified sub-region of the given data. Only updates the + * specified mipmap level and does not re-generate mipmaps if they + * were originally produced or loaded. This method is only supported + * for uncompressed TextureData sources. + * + * @param data the image data to be uploaded to this texture + * @param mipmapLevel the mipmap level of the texture to set. If + * this is non-zero and the TextureData contains mipmap data, the + * appropriate mipmap level will be selected. + * @param dstx the x offset (in pixels) relative to the lower-left corner + * of this texture where the update will be applied + * @param dsty the y offset (in pixels) relative to the lower-left corner + * of this texture where the update will be applied + * @param srcx the x offset (in pixels) relative to the lower-left corner + * of the supplied TextureData from which to fetch the update rectangle + * @param srcy the y offset (in pixels) relative to the lower-left corner + * of the supplied TextureData from which to fetch the update rectangle + * @param width the width (in pixels) of the rectangle to be updated + * @param height the height (in pixels) of the rectangle to be updated + * + * @throws GLException if no OpenGL context was current or if any + * OpenGL-related errors occurred + */ + public void updateSubImage(TextureData data, int mipmapLevel, + int dstx, int dsty, + int srcx, int srcy, + int width, int height) throws GLException { + if (data.isDataCompressed()) { + throw new GLException("updateSubImage specifying a sub-rectangle is not supported for compressed TextureData"); + } + updateSubImageImpl(data, target, mipmapLevel, dstx, dsty, srcx, srcy, width, height); } /** @@ -693,23 +728,31 @@ public class Texture { } } - private void updateSubImageImpl(TextureData data, int newTarget, int mipmapLevel, int x, int y) throws GLException { + private void updateSubImageImpl(TextureData data, int newTarget, int mipmapLevel, + int dstx, int dsty, + int srcx, int srcy, int width, int height) throws GLException { + GL gl = GLU.getCurrentGL(); + if (gl.isExtensionAvailable("GL_EXT_abgr")) { + data.setHaveEXTABGR(true); + } + Buffer buffer = data.getBuffer(); if (buffer == null && data.getMipmapData() == null) { // Assume user just wanted to get the Texture object allocated return; } - GL gl = GLU.getCurrentGL(); gl.glBindTexture(newTarget, texID); - int width = data.getWidth(); - int height = data.getHeight(); + int rowlen = data.getRowLength(); if (data.getMipmapData() != null) { - // Compute the width and height at the specified mipmap level + // Compute the width, height and row length at the specified mipmap level + // Note we do not support specification of the row length for + // mipmapped textures at this point for (int i = 0; i < mipmapLevel; i++) { width /= 2; height /= 2; } + rowlen = 0; buffer = data.getMipmapData()[mipmapLevel]; } @@ -717,19 +760,40 @@ public class Texture { if (data.isDataCompressed()) { gl.glCompressedTexSubImage2D(newTarget, mipmapLevel, - x, y, width, height, + dstx, dsty, width, height, data.getInternalFormat(), buffer.remaining(), buffer); } else { int[] align = new int[1]; - gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment + int[] rowLength = new int[1]; + int[] skipRows = new int[1]; + int[] skipPixels = new int[1]; + gl.glGetIntegerv(GL.GL_UNPACK_ALIGNMENT, align, 0); // save alignment + gl.glGetIntegerv(GL.GL_UNPACK_ROW_LENGTH, rowLength, 0); // save row length + gl.glGetIntegerv(GL.GL_UNPACK_SKIP_ROWS, skipRows, 0); // save skipped rows + gl.glGetIntegerv(GL.GL_UNPACK_SKIP_PIXELS, skipPixels, 0); // save skipped pixels gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, data.getAlignment()); + if (DEBUG && VERBOSE) { + System.out.println("Row length = " + rowlen); + System.out.println("skip pixels = " + srcx); + System.out.println("skip rows = " + srcy); + System.out.println("dstx = " + dstx); + System.out.println("dsty = " + dsty); + System.out.println("width = " + width); + System.out.println("height = " + height); + } + gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowlen); + gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, srcy); + gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, srcx); gl.glTexSubImage2D(newTarget, mipmapLevel, - x, y, width, height, + dstx, dsty, width, height, data.getPixelFormat(), data.getPixelType(), buffer); - gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore align + gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, align[0]); // restore alignment + gl.glPixelStorei(GL.GL_UNPACK_ROW_LENGTH, rowLength[0]); // restore row length + gl.glPixelStorei(GL.GL_UNPACK_SKIP_ROWS, skipRows[0]); // restore skipped rows + gl.glPixelStorei(GL.GL_UNPACK_SKIP_PIXELS, skipPixels[0]); // restore skipped pixels } } diff --git a/src/classes/com/sun/opengl/util/texture/TextureData.java b/src/classes/com/sun/opengl/util/texture/TextureData.java index d03faa697..39075e387 100755 --- a/src/classes/com/sun/opengl/util/texture/TextureData.java +++ b/src/classes/com/sun/opengl/util/texture/TextureData.java @@ -73,9 +73,18 @@ public class TextureData { private Buffer buffer; // the actual data... private Buffer[] mipmapData; // ...or a series of mipmaps private Flusher flusher; + private int rowLength; private int alignment; // 1, 2, or 4 bytes private int estimatedMemorySize; + // Mechanism for lazily converting input BufferedImages with custom + // ColorModels to standard ones for uploading to OpenGL, as well as + // backing off from the optimization of hoping that GL_EXT_abgr is + // present + private BufferedImage imageForLazyCustomConversion; + private boolean expectingEXTABGR; + private boolean haveEXTABGR; + private static final ColorModel rgbaColorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8}, true, true, @@ -220,12 +229,12 @@ public class TextureData { /** * Constructs a new TextureData object with the specified parameters - * and data contained in the given BufferedImage. Note that - * subsequent modifications to the BufferedImage after the - * construction of the TextureData object are not guaranteed to be - * visible in any Texture object created from the TextureData (and, - * in fact, the expectation should be that they will not be visible, - * although this behavior is explicitly left undefined). + * and data contained in the given BufferedImage. The resulting + * TextureData "wraps" the contents of the BufferedImage, so if a + * modification is made to the BufferedImage between the time the + * TextureData is constructed and when a Texture is made from the + * TextureData, that modification will be visible in the resulting + * Texture. * * @param internalFormat the OpenGL internal format for the * resulting texture; may be 0, in which case @@ -273,12 +282,26 @@ public class TextureData { vertically for proper display. */ public boolean getMustFlipVertically() { return mustFlipVertically; } /** Returns the texture data, or null if it is specified as a set of mipmaps. */ - public Buffer getBuffer() { return buffer; } + public Buffer getBuffer() { + if (imageForLazyCustomConversion != null) { + if (!expectingEXTABGR || + (expectingEXTABGR && !haveEXTABGR)) { + // Must present the illusion to the end user that we are simply + // wrapping the input BufferedImage + createFromCustom(imageForLazyCustomConversion); + } + } + return buffer; + } /** Returns all mipmap levels for the texture data, or null if it is specified as a single image. */ public Buffer[] getMipmapData() { return mipmapData; } /** Returns the required byte alignment for the texture data. */ public int getAlignment() { return alignment; } + /** Returns the row length needed for correct GL_UNPACK_ROW_LENGTH + specification. This is currently only supported for + non-mipmapped, non-compressed textures. */ + public int getRowLength() { return rowLength; } /** Sets the width in pixels of the texture data. */ public void setWidth(int width) { this.width = width; } @@ -303,6 +326,16 @@ public class TextureData { public void setBuffer(Buffer buffer) { this.buffer = buffer; } /** Sets the required byte alignment for the texture data. */ public void setAlignment(int alignment) { this.alignment = alignment; } + /** Sets the row length needed for correct GL_UNPACK_ROW_LENGTH + specification. This is currently only supported for + non-mipmapped, non-compressed textures. */ + public void setRowLength(int rowLength) { this.rowLength = rowLength; } + /** Indicates to this TextureData whether the GL_EXT_abgr extension + is available. Used for optimization along some code paths to + avoid data copies. */ + public void setHaveEXTABGR(boolean haveEXTABGR) { + this.haveEXTABGR = haveEXTABGR; + } /** Returns an estimate of the amount of memory in bytes this TextureData will consume once uploaded to the graphics card. It @@ -335,176 +368,145 @@ public class TextureData { // Internals only below this point // - private void createNIOBufferFromImage(BufferedImage image, boolean flipVertically) { - if (flipVertically) { - ImageUtil.flipImageVertically(image); - } - - try { - // - // Note: Grabbing the DataBuffer will defeat Java2D's image - // management mechanism (as of JDK 5/6, at least). This shouldn't - // be a problem for most JOGL apps, but those that try to upload - // the image into an OpenGL texture and then use the same image in - // Java2D rendering might find the 2D rendering is not as fast as - // it could be. - // - - // Allow previously-selected pixelType (if any) to override that - // we can infer from the DataBuffer - DataBuffer data = image.getRaster().getDataBuffer(); - if (data instanceof DataBufferByte) { - if (pixelType == 0) pixelType = GL.GL_UNSIGNED_BYTE; - buffer = ByteBuffer.wrap(copyIfNecessary(((DataBufferByte) data).getData(), flipVertically)); - } else if (data instanceof DataBufferDouble) { - throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL"); - } else if (data instanceof DataBufferFloat) { - if (pixelType == 0) pixelType = GL.GL_FLOAT; - buffer = FloatBuffer.wrap(copyIfNecessary(((DataBufferFloat) data).getData(), flipVertically)); - } else if (data instanceof DataBufferInt) { - // FIXME: should we support signed ints? - if (pixelType == 0) pixelType = GL.GL_UNSIGNED_INT; - buffer = IntBuffer.wrap(copyIfNecessary(((DataBufferInt) data).getData(), flipVertically)); - } else if (data instanceof DataBufferShort) { - if (pixelType == 0) pixelType = GL.GL_SHORT; - buffer = ShortBuffer.wrap(copyIfNecessary(((DataBufferShort) data).getData(), flipVertically)); - } else if (data instanceof DataBufferUShort) { - if (pixelType == 0) pixelType = GL.GL_UNSIGNED_SHORT; - buffer = ShortBuffer.wrap(copyIfNecessary(((DataBufferShort) data).getData(), flipVertically)); - } else { - throw new RuntimeException("Unexpected DataBuffer type?"); - } - } finally { - // Put image back right-side up if necessary - if (flipVertically) { - ImageUtil.flipImageVertically(image); - } - } - } - - private byte[] copyIfNecessary(byte[] data, boolean needsCopy) { - if (needsCopy) { - return (byte[]) data.clone(); - } - return data; - } - - private short[] copyIfNecessary(short[] data, boolean needsCopy) { - if (needsCopy) { - return (short[]) data.clone(); - } - return data; - } - - private int[] copyIfNecessary(int[] data, boolean needsCopy) { - if (needsCopy) { - return (int[]) data.clone(); - } - return data; - } - - private float[] copyIfNecessary(float[] data, boolean needsCopy) { - if (needsCopy) { - return (float[]) data.clone(); - } - return data; - } - - private double[] copyIfNecessary(double[] data, boolean needsCopy) { - if (needsCopy) { - return (double[]) data.clone(); + private void createNIOBufferFromImage(BufferedImage image) { + // + // Note: Grabbing the DataBuffer will defeat Java2D's image + // management mechanism (as of JDK 5/6, at least). This shouldn't + // be a problem for most JOGL apps, but those that try to upload + // the image into an OpenGL texture and then use the same image in + // Java2D rendering might find the 2D rendering is not as fast as + // it could be. + // + + DataBuffer data = image.getRaster().getDataBuffer(); + if (data instanceof DataBufferByte) { + buffer = ByteBuffer.wrap(((DataBufferByte) data).getData()); + } else if (data instanceof DataBufferDouble) { + throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL"); + } else if (data instanceof DataBufferFloat) { + buffer = FloatBuffer.wrap(((DataBufferFloat) data).getData()); + } else if (data instanceof DataBufferInt) { + buffer = IntBuffer.wrap(((DataBufferInt) data).getData()); + } else if (data instanceof DataBufferShort) { + buffer = ShortBuffer.wrap(((DataBufferShort) data).getData()); + } else if (data instanceof DataBufferUShort) { + buffer = ShortBuffer.wrap(((DataBufferUShort) data).getData()); + } else { + throw new RuntimeException("Unexpected DataBuffer type?"); } - return data; } - private void createFromImage(BufferedImage image) { pixelType = 0; // Determine from image + mustFlipVertically = true; width = image.getWidth(); height = image.getHeight(); + int scanlineStride; + SampleModel sm = image.getRaster().getSampleModel(); + if (sm instanceof SinglePixelPackedSampleModel) { + scanlineStride = + ((SinglePixelPackedSampleModel)sm).getScanlineStride(); + } else if (sm instanceof MultiPixelPackedSampleModel) { + scanlineStride = + ((MultiPixelPackedSampleModel)sm).getScanlineStride(); + } else if (sm instanceof ComponentSampleModel) { + scanlineStride = + ((ComponentSampleModel)sm).getScanlineStride(); + } else { + // This will only happen for TYPE_CUSTOM anyway + setupLazyCustomConversion(image); + return; + } + switch (image.getType()) { case BufferedImage.TYPE_INT_RGB: pixelFormat = GL.GL_BGRA; pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV; + rowLength = scanlineStride; alignment = 4; break; + case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_INT_ARGB_PRE: pixelFormat = GL.GL_BGRA; pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV; + rowLength = scanlineStride; alignment = 4; break; case BufferedImage.TYPE_INT_BGR: pixelFormat = GL.GL_RGBA; pixelType = GL.GL_UNSIGNED_INT_8_8_8_8_REV; + rowLength = scanlineStride; alignment = 4; break; case BufferedImage.TYPE_3BYTE_BGR: { - Raster raster = image.getRaster(); - ComponentSampleModel csm = - (ComponentSampleModel)raster.getSampleModel(); // we can pass the image data directly to OpenGL only if - // the raster is tightly packed (i.e. there is no extra - // space at the end of each scanline) - if ((csm.getScanlineStride() / 3) == csm.getWidth()) { + // we have an integral number of pixels in each scanline + if ((scanlineStride % 3) == 0) { pixelFormat = GL.GL_BGR; pixelType = GL.GL_UNSIGNED_BYTE; + rowLength = scanlineStride / 3; alignment = 1; } else { - createFromCustom(image); + setupLazyCustomConversion(image); return; } } break; + case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_4BYTE_ABGR_PRE: { - Raster raster = image.getRaster(); - ComponentSampleModel csm = - (ComponentSampleModel)raster.getSampleModel(); // we can pass the image data directly to OpenGL only if - // the raster is tightly packed (i.e. there is no extra - // space at the end of each scanline) and only if the - // GL_EXT_abgr extension is present - - // FIXME: with the way this is currently organized we can't - // probe for the existence of the GL_EXT_abgr extension - // here; disable this code path for now - if (((csm.getScanlineStride() / 4) == csm.getWidth()) && - /* gl.isExtensionAvailable("GL_EXT_abgr") */ false) - { - pixelFormat = GL.GL_ABGR_EXT; - pixelType = GL.GL_UNSIGNED_BYTE; - alignment = 4; - } else { - createFromCustom(image); - return; - } + // we have an integral number of pixels in each scanline + // and only if the GL_EXT_abgr extension is present + + // NOTE: disabling this code path for now as it appears it's + // buggy at least on some NVidia drivers and doesn't perform + // the necessary byte swapping (FIXME: needs more + // investigation) + if ((scanlineStride % 4) == 0 && false) { + pixelFormat = GL.GL_ABGR_EXT; + pixelType = GL.GL_UNSIGNED_BYTE; + rowLength = scanlineStride / 4; + alignment = 4; + + // Store a reference to the original image for later in + // case it turns out that we don't have GL_EXT_abgr at the + // time we're going to do the texture upload to OpenGL + setupLazyCustomConversion(image); + expectingEXTABGR = true; + break; + } else { + setupLazyCustomConversion(image); + return; + } } - break; case BufferedImage.TYPE_USHORT_565_RGB: pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_SHORT_5_6_5; + rowLength = scanlineStride; alignment = 2; break; case BufferedImage.TYPE_USHORT_555_RGB: pixelFormat = GL.GL_BGRA; pixelType = GL.GL_UNSIGNED_SHORT_1_5_5_5_REV; + rowLength = scanlineStride; alignment = 2; break; case BufferedImage.TYPE_BYTE_GRAY: pixelFormat = GL.GL_LUMINANCE; pixelType = GL.GL_UNSIGNED_BYTE; + rowLength = scanlineStride; alignment = 1; break; case BufferedImage.TYPE_USHORT_GRAY: pixelFormat = GL.GL_LUMINANCE; pixelType = GL.GL_UNSIGNED_SHORT; + rowLength = scanlineStride; alignment = 2; break; - case BufferedImage.TYPE_INT_ARGB: - case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_BYTE_BINARY: case BufferedImage.TYPE_BYTE_INDEXED: case BufferedImage.TYPE_CUSTOM: @@ -513,19 +515,49 @@ public class TextureData { if (cm.equals(rgbColorModel)) { pixelFormat = GL.GL_RGB; pixelType = GL.GL_UNSIGNED_BYTE; + rowLength = scanlineStride / 4; // FIXME: correct? alignment = 1; } else if (cm.equals(rgbaColorModel)) { pixelFormat = GL.GL_RGBA; pixelType = GL.GL_UNSIGNED_BYTE; + rowLength = scanlineStride / 4; // FIXME: correct? alignment = 4; } else { - createFromCustom(image); + setupLazyCustomConversion(image); return; } break; } - createNIOBufferFromImage(image, true); + createNIOBufferFromImage(image); + } + + private void setupLazyCustomConversion(BufferedImage image) { + imageForLazyCustomConversion = image; + boolean hasAlpha = image.getColorModel().hasAlpha(); + pixelFormat = hasAlpha ? GL.GL_RGBA : GL.GL_RGB; + alignment = 1; // FIXME: do we need better? + rowLength = width; // FIXME: correct in all cases? + + // Allow previously-selected pixelType (if any) to override that + // we can infer from the DataBuffer + DataBuffer data = image.getRaster().getDataBuffer(); + if (data instanceof DataBufferByte) { + if (pixelType == 0) pixelType = GL.GL_UNSIGNED_BYTE; + } else if (data instanceof DataBufferDouble) { + throw new RuntimeException("DataBufferDouble rasters not supported by OpenGL"); + } else if (data instanceof DataBufferFloat) { + if (pixelType == 0) pixelType = GL.GL_FLOAT; + } else if (data instanceof DataBufferInt) { + // FIXME: should we support signed ints? + if (pixelType == 0) pixelType = GL.GL_UNSIGNED_INT; + } else if (data instanceof DataBufferShort) { + if (pixelType == 0) pixelType = GL.GL_SHORT; + } else if (data instanceof DataBufferUShort) { + if (pixelType == 0) pixelType = GL.GL_UNSIGNED_SHORT; + } else { + throw new RuntimeException("Unexpected DataBuffer type?"); + } } private void createFromCustom(BufferedImage image) { @@ -560,17 +592,11 @@ public class TextureData { // copy the source image into the temporary image Graphics2D g = texImage.createGraphics(); g.setComposite(AlphaComposite.Src); - // Flip image vertically as long as we're at it - g.drawImage(image, - 0, height, width, 0, - 0, 0, width, height, - null); + g.drawImage(image, 0, 0, null); g.dispose(); // Wrap the buffer from the temporary image - createNIOBufferFromImage(texImage, false); - pixelFormat = hasAlpha ? GL.GL_RGBA : GL.GL_RGB; - alignment = 1; // FIXME: do we need better? + createNIOBufferFromImage(texImage); } private int estimatedMemorySize(Buffer buffer) { |