From a736f85a834613d12e2b28e14b5a53fa6991b86f Mon Sep 17 00:00:00 2001 From: Kenneth Russel Date: Fri, 29 Dec 2006 16:52:29 +0000 Subject: Fixed Issue 257: RFE: Texture.updateSubImage(x,y,w,h) Changed TextureIO's BufferedImage code paths to no longer copy the data in order to correct for the vertical flip needed between Java 2D's and OpenGL's coordinate systems, but instead to correct for this using a flip of the texture coordinates. This was needed to change the semantics for the BufferedImage from "by-copy" to "by-reference". Made the custom BufferedImage copying code path occur lazily upon a call to TextureData.getBuffer(). Added support for the GL_EXT_abgr extension but disabled the code path currently as it appears there may be bugs in its support on some drivers. Added computation of row width for all BufferedImage types with help and suggestions from Chris Campbell. Improved handling of some BufferedImage types like TYPE_INT_ARGB and TYPE_4BYTE_ABGR. Added Texture.updateSubImage() taking a source rectangle as well as a destination coordinate. Wrote TestSubImage test to show use of the new API which exercises all BufferedImage types. git-svn-id: file:///usr/local/projects/SUN/JOGL/git-svn/svn-server-sync/jogl/trunk@1057 232f8b59-042b-4e1e-8c03-345bb8c30851 --- .../com/sun/opengl/util/texture/Texture.java | 96 +++++-- .../com/sun/opengl/util/texture/TextureData.java | 280 +++++++++++---------- 2 files changed, 233 insertions(+), 143 deletions(-) (limited to 'src/classes/com') 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) { -- cgit v1.2.3