/*
 * License Applicability. Except to the extent portions of this file are
 * made subject to an alternative license as permitted in the SGI Free
 * Software License B, Version 1.1 (the "License"), the contents of this
 * file are subject only to the provisions of the License. You may not use
 * this file except in compliance with the License. You may obtain a copy
 * of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
 * Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
 * 
 * http://oss.sgi.com/projects/FreeB
 * 
 * Note that, as provided in the License, the Software is distributed on an
 * "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
 * DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
 * CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 * 
 * Original Code. The Original Code is: OpenGL Sample Implementation,
 * Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
 * Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
 * Copyright in any portions created by third parties is as indicated
 * elsewhere herein. All Rights Reserved.
 * 
 * Additional Notice Provisions: The application programming interfaces
 * established by SGI in conjunction with the Original Code are The
 * OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
 * April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
 * 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
 * Window System(R) (Version 1.3), released October 19, 1998. This software
 * was created using the OpenGL(R) version 1.2.1 Sample Implementation
 * published by SGI, but has not been independently verified as being
 * compliant with the OpenGL(R) version 1.2.1 Specification.
 */

package net.java.games.jogl.impl.mipmap;

import net.java.games.jogl.GL;
import net.java.games.jogl.GLU;
import java.nio.*;
import java.io.*;

/**
 *
 * @author  Administrator
 */
public class BuildMipmap {
  
  /** Creates a new instance of BuildMipmap */
  public BuildMipmap() {
  }
  
  public static int gluBuild1DMipmapLevelsCore( GL gl, int target, int internalFormat,
                  int width, int widthPowerOf2, int format, int type, int userLevel,
                  int baseLevel, int maxLevel, ByteBuffer data ) {
    int newwidth;
    int level, levels;
    ShortBuffer newImage = null;
    int newImage_width;
    ShortBuffer otherImage = null;
    ShortBuffer imageTemp = null;
    int memreq;
    int maxsize;
    int cmpts;
    PixelStorageModes psm = new PixelStorageModes();
    
    assert( Mipmap.checkMipmapArgs( internalFormat, format, type ) == 0 );
    assert( width >= 1 );
    
    newwidth = widthPowerOf2;
    levels = Mipmap.computeLog( newwidth );
    
    levels += userLevel;
    
    Mipmap.retrieveStoreModes( gl, psm );
    newImage = ByteBuffer.allocateDirect( Mipmap.image_size( width, 1, format, 
            GL.GL_UNSIGNED_SHORT ) ).order( ByteOrder.nativeOrder() ).asShortBuffer();
    newImage_width = width;
    if( newImage == null ) {
      return( GLU.GLU_OUT_OF_MEMORY );
    }
    Image.fill_image( psm, width, 1, format, type, Mipmap.is_index( format ), data, newImage );
    cmpts = Mipmap.elements_per_group( format, type );
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, 2 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, 0 );
    
    // if swap_bytes was set, swapping occurred in fill_image
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE );
    
    for( level = userLevel; level <= levels; level++ ) {
      if( newImage_width == newwidth ) {
        // user newimage for this level
        if( baseLevel <= level && level <= maxLevel ) {
          gl.glTexImage1D( target, level, internalFormat, newImage_width, 0, format,
                        GL.GL_UNSIGNED_SHORT, newImage );
        }
      } else {
        if( otherImage == null ) {
          memreq = Mipmap.image_size( newwidth, 1, format, GL.GL_UNSIGNED_SHORT );
          otherImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() ).asShortBuffer();
          if( otherImage == null ) {
            gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
            gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
            gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
            return( GLU.GLU_OUT_OF_MEMORY );
          }
        }
        ScaleInternal.scale_internal( cmpts, newImage_width, 1, newImage, newwidth, 1, otherImage );
        // swap newImage and otherImage
        imageTemp = otherImage;
        otherImage = newImage;
        newImage = imageTemp;
        
        newImage_width = newwidth;
        if( baseLevel <= level && level <= maxLevel ) {
          gl.glTexImage1D( target, level, internalFormat, newImage_width, 0, 
                            format, GL.GL_UNSIGNED_SHORT, newImage );
        }
      }
      if( newwidth > 1 ) {
        newwidth /= 2;
      }
    }
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
    
    return( 0 );
  }
  
  public static int bitmapBuild2DMipmaps( GL gl, int target, int internalFormat,
            int width, int height, int format, int type, ByteBuffer data ) {
    int newwidth[] = new int[1];
    int newheight[] = new int[1];
    int level, levels;
    ShortBuffer newImage = null;
    int newImage_width;
    int newImage_height;
    ShortBuffer otherImage = null;
    ShortBuffer tempImage = null;
    int memreq;
    int maxsize;
    int cmpts;
    PixelStorageModes psm = new PixelStorageModes();
    
    Mipmap.retrieveStoreModes( gl, psm );
    
    Mipmap.closestFit( gl, target, width, height, internalFormat, format, type, newwidth, newheight );
    
    levels = Mipmap.computeLog( newwidth[0] );
    level = Mipmap.computeLog( newheight[0] );
    if( level > levels ) {
      levels = level;
    }
    
    newImage = ByteBuffer.allocateDirect( Mipmap.image_size( width, height, 
            format, GL.GL_UNSIGNED_SHORT ) ).order( ByteOrder.nativeOrder() ).asShortBuffer();
    newImage_width = width;
    newImage_height = height;
    if( newImage == null ) {
      return( GLU.GLU_OUT_OF_MEMORY );
    }
    
    Image.fill_image( psm, width, height, format, type, Mipmap.is_index( format ), data, newImage );
    
    cmpts = Mipmap.elements_per_group( format, type );
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, 2 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, 0 );
    
    // if swap_bytes is set, swapping occurred in fill_image
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE );
    
    for( level = 0; level < levels; level++ ) {
      if( newImage_width == newwidth[0] && newImage_height == newheight[0] ) {
        gl.glTexImage2D( target, level, internalFormat, newImage_width,
            newImage_height, 0, format, GL.GL_UNSIGNED_SHORT, newImage );
      } else {
        if( otherImage == null ) {
          memreq = Mipmap.image_size( newwidth[0], newheight[0], format, GL.GL_UNSIGNED_SHORT );
          otherImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() ).asShortBuffer();
          if( otherImage == null ) {
            gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
            gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
            gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
            return( GLU.GLU_OUT_OF_MEMORY );
          }
        }
        ScaleInternal.scale_internal( cmpts, newImage_width, newImage_height, 
                              newImage, newwidth[0], newheight[0], otherImage );
        // swap newImage and otherImage
        tempImage = otherImage;
        otherImage = newImage;
        newImage = tempImage;
        
        newImage_width = newwidth[0];
        newImage_height = newheight[0];
        gl.glTexImage2D( target, level, internalFormat, newImage_width, newImage_height,
                                    0, format, GL.GL_UNSIGNED_SHORT, newImage );
      }
      if( newheight[0] > 1 ) {
        newwidth[0] /= 2;
      }
      if( newheight[0] > 1 ) {
        newheight[0] /= 2;
      }
    }
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
    
    return( 0 );
  }
  
  public static int gluBuild2DMipmapLevelsCore( GL gl, int target, int internalFormat,
                int width, int height, int widthPowerOf2, int heightPowerOf2,
                int format, int type, int userLevel, int baseLevel, int maxLevel,
                ByteBuffer data ) { // PointerWrapper data
    int newwidth;
    int newheight;
    int level, levels;
    int usersImage;
    ByteBuffer srcImage = null;
    ByteBuffer dstImage = null;
    ByteBuffer tempImage = null;
    int newImage_width;
    int newImage_height;
    short[] SWAP_IMAGE = null;
    int memreq;
    int maxsize;
    int cmpts;
    
    boolean myswap_bytes;
    int groups_per_line, element_size, group_size;
    int rowsize, padding;
    PixelStorageModes psm = new PixelStorageModes();
    
    assert( Mipmap.checkMipmapArgs( internalFormat, format, type ) == 0 );
    assert( width >= 1 && height >= 1 );
    
    if( type == GL.GL_BITMAP ) {
      return( bitmapBuild2DMipmaps( gl, target, internalFormat, width, height, format, type, data ) );
    }
    
    newwidth = widthPowerOf2;
    newheight = heightPowerOf2;
    levels = Mipmap.computeLog( newwidth );
    level = Mipmap.computeLog( newheight );
    if( level > levels ) {
      levels = level;
    }
    
    levels += userLevel;
    
    Mipmap.retrieveStoreModes( gl, psm );
    myswap_bytes = psm.getUnpackSwapBytes();
    cmpts = Mipmap.elements_per_group( format, type );
    if( psm.getUnpackRowLength() > 0 ) {
      groups_per_line = psm.getUnpackRowLength();
    } else {
      groups_per_line = width;
    }
    
    element_size = Mipmap.bytes_per_element( type );
    group_size = element_size * cmpts;
    if( element_size == 1 ) {
      myswap_bytes = false;
    }
    
    rowsize = groups_per_line * group_size;
    padding = ( rowsize % psm.getUnpackAlignment() );
    if( padding != 0 ) {
      rowsize += psm.getUnpackAlignment() - padding;
    }
    //data.mark();
    //data.setIndexInBytes( psm.getUnpackSkipRows() * rowsize + psm.getUnpackSkipPixels() * group_size );
    //data.mark();
    data.position( psm.getUnpackSkipRows() * rowsize + psm.getUnpackSkipPixels() * group_size );
    data.mark();
    
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, 0 );
    
    level = userLevel;
    
    // already power of two square
    if( width == newwidth && height == newheight ) {
      // use usersImage for level userLevel
      if( baseLevel <= level && level <= maxLevel ) {
        gl.glTexImage2D( target, level, internalFormat, width, height, 0, format, type, data );
      }
      if( levels == 0 ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
      }
      int nextWidth = newwidth / 2;
      int nextHeight = newheight / 2;
      
      // clamp to 1
      if( nextWidth < 1 ) {
        nextWidth = 1;
      }
      if( nextHeight < 1 ) {
        nextHeight = 1;
      }
      memreq = Mipmap.image_size( nextWidth, nextHeight, format, type );
      
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
        case( GL.GL_BYTE ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_UNSIGNED_SHORT ):
        case( GL.GL_SHORT ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_UNSIGNED_INT ):
        case( GL.GL_INT ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_FLOAT ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq );
          break;
        default:
          return( GLU.GLU_INVALID_ENUM );
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
        return( GLU.GLU_OUT_OF_MEMORY );
      } else {
        switch( type ) {
          case( GL.GL_UNSIGNED_BYTE ):
            HalveImage.halveImage_ubyte( cmpts, width, height, data, dstImage, element_size, rowsize, group_size );
            break;
          case( GL.GL_BYTE ):
            HalveImage.halveImage_byte( cmpts, width, height, data, dstImage, element_size, rowsize, group_size );
            break;
          case( GL.GL_UNSIGNED_SHORT ):
            HalveImage.halveImage_ushort( cmpts, width, height, data, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
            break;
          case( GL.GL_SHORT ):
            HalveImage.halveImage_short( cmpts, width, height, data, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_INT ):
            HalveImage.halveImage_uint( cmpts, width, height, data, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
            break;
          case( GL.GL_INT ):
            HalveImage.halveImage_int( cmpts, width, height, data, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
            break;
          case( GL.GL_FLOAT ):
            HalveImage.halveImage_float( cmpts, width, height, data, dstImage.asFloatBuffer(), element_size, rowsize, group_size, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
            assert( format == GL.GL_RGB );
            HalveImage.halveImagePackedPixel( 3, new Extract332(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            assert( format == GL.GL_RGB );
            HalveImage.halveImagePackedPixel( 3, new Extract233rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
            HalveImage.halveImagePackedPixel( 3, new Extract565(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
            HalveImage.halveImagePackedPixel( 3, new Extract565rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
            HalveImage.halveImagePackedPixel( 4, new Extract4444(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
            HalveImage.halveImagePackedPixel( 4, new Extract4444rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
            HalveImage.halveImagePackedPixel( 4, new Extract5551(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            HalveImage.halveImagePackedPixel( 4, new Extract1555rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
            HalveImage.halveImagePackedPixel( 4, new Extract8888(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
            HalveImage.halveImagePackedPixel( 4, new Extract8888rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
            HalveImage.halveImagePackedPixel( 4, new Extract1010102(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            HalveImage.halveImagePackedPixel( 4, new Extract2101010rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
          default:
            assert( false );
            break;
        }
      }
      newwidth = width / 2;
      newheight = height / 2;
      // clamp to 1
      if( newwidth < 1 ) {
        newwidth = 1;
      }
      if( newheight < 1 ) {
        newheight = 1;
      }
      
      myswap_bytes = false;
      rowsize = newwidth * group_size;
      memreq = Mipmap.image_size( newwidth, newheight, format, type );
      // swap srcImage and dstImage
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
        case( GL.GL_BYTE ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_SHORT ):
        case( GL.GL_SHORT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_INT ):
        case( GL.GL_INT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_FLOAT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        default:
          return( GLU.GLU_INVALID_ENUM );
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
        return( GLU.GLU_OUT_OF_MEMORY );
      }
      // level userLevel+1 is in srcImage; level userLevel already saved
      level = userLevel + 1;
    } else { // user's image is not nice powerof2 size square
      memreq = Mipmap.image_size( newwidth, newheight, format, type );
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
        case( GL.GL_BYTE ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_SHORT ):
        case( GL.GL_SHORT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_INT ):
        case( GL.GL_INT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_FLOAT ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          break;
        default:
          return( GLU.GLU_INVALID_ENUM );
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
        return( GLU.GLU_OUT_OF_MEMORY );
      }
      data.reset();
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
          ScaleInternal.scale_internal_ubyte( cmpts, width, height, data, 
                newwidth, newheight, dstImage, element_size, rowsize, group_size );
          break;
        case( GL.GL_BYTE ):
          ScaleInternal.scale_internal_byte( cmpts, width, height, data, newwidth, 
                  newheight, dstImage, element_size, rowsize, group_size );
          break;
        case( GL.GL_UNSIGNED_SHORT ):
          ScaleInternal.scale_internal_ushort( cmpts, width, height, data, newwidth, 
                  newheight, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_SHORT ):
          ScaleInternal.scale_internal_ushort( cmpts, width, height, data, newwidth,
                  newheight, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT ):
          ScaleInternal.scale_internal_uint( cmpts, width, height, data, newwidth,
                  newheight, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_INT ):
          ScaleInternal.scale_internal_int( cmpts, width, height, data, newwidth,
                  newheight, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_FLOAT ):
          ScaleInternal.scale_internal_float( cmpts, width, height, data, newwidth,
                  newheight, dstImage.asFloatBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          ScaleInternal.scaleInternalPackedPixel( 3, new Extract332(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          ScaleInternal.scaleInternalPackedPixel( 3, new Extract233rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          ScaleInternal.scaleInternalPackedPixel( 3, new Extract565(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          ScaleInternal.scaleInternalPackedPixel( 3, new Extract565rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract4444(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract4444rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract5551(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract1555rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract8888(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract8888rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract1010102(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          ScaleInternal.scaleInternalPackedPixel( 4, new Extract2101010rev(), width, height, data, newwidth,
              newheight, dstImage, element_size, rowsize, myswap_bytes );
          break;
        default:
          assert( false );
          break;
      }
      myswap_bytes = false;
      rowsize = newwidth * group_size;
      // swap dstImage and srcImage
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      
      if( levels != 0 ) { // use as little memory as possible
        int nextWidth = newwidth / 2;
        int nextHeight = newheight / 2;
        if( nextWidth < 1 ) {
          nextWidth = 1;
        }
        if( nextHeight < 1 ) {
          nextHeight = 1;
        }
        
        memreq = Mipmap.image_size( nextWidth, nextHeight, format, type );
      
        switch( type ) {
          case( GL.GL_UNSIGNED_BYTE ):
          case( GL.GL_BYTE ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_UNSIGNED_SHORT ):
          case( GL.GL_SHORT ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_UNSIGNED_INT ):
          case( GL.GL_INT ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_FLOAT ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            dstImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
            break;
          default:
            return( GLU.GLU_INVALID_ENUM );
        }
        if( dstImage == null ) {
          gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
          gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
          gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
          return( GLU.GLU_OUT_OF_MEMORY );
        }
      }
      // level userLevel is in srcImage; nothing saved yet
      level = userLevel;
    }
    
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE );
    if( baseLevel <= level && level <= maxLevel ) {
      srcImage.rewind();
      System.err.println("GL Error(" + level + "): " + gl.glGetError() );
      gl.glTexImage2D( target, level, internalFormat, newwidth, newheight, 0, format, type, srcImage );
      System.err.println("GL Error(" + level + "): " + gl.glGetError() );
      try {
        File file = new File( "glu2DMipmapJ" + level + ".bin" );
        FileOutputStream fos = new FileOutputStream( file );
        srcImage.limit( Mipmap.image_size( newwidth, newheight, format, type ) );
        fos.getChannel().write( srcImage );
        srcImage.clear();
        fos.close();
      } catch( IOException e ) {
        System.err.println("IOException");
        System.err.println(e.getMessage());
      }
    }
    
    level++;  // update current level for the loop
    for( ; level <= levels; level++ ) {
      srcImage.rewind();
      dstImage.rewind();
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
          HalveImage.halveImage_ubyte( cmpts, newwidth, newheight, srcImage, dstImage, element_size, rowsize, group_size );
          break;
        case( GL.GL_BYTE ):
          HalveImage.halveImage_byte( cmpts, newwidth, newheight, srcImage, dstImage, element_size, rowsize, group_size );
          break;
        case( GL.GL_UNSIGNED_SHORT ):
          HalveImage.halveImage_ushort( cmpts, newwidth, newheight, srcImage, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_SHORT ):
          HalveImage.halveImage_short( cmpts, newwidth, newheight, srcImage, dstImage.asShortBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_INT ):
          HalveImage.halveImage_uint( cmpts, newwidth, newheight, srcImage, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_INT ):
          HalveImage.halveImage_int( cmpts, newwidth, newheight, srcImage, dstImage.asIntBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_FLOAT ):
          HalveImage.halveImage_float( cmpts, newwidth, newheight, srcImage, dstImage.asFloatBuffer(), element_size, rowsize, group_size, myswap_bytes );
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          assert( format == GL.GL_RGB );
          HalveImage.halveImagePackedPixel( 3, new Extract332(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          assert( format == GL.GL_RGB );
          HalveImage.halveImagePackedPixel( 3, new Extract233rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          HalveImage.halveImagePackedPixel( 3, new Extract565(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          HalveImage.halveImagePackedPixel( 3, new Extract565rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          HalveImage.halveImagePackedPixel( 4, new Extract4444(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          HalveImage.halveImagePackedPixel( 4, new Extract4444rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          HalveImage.halveImagePackedPixel( 4, new Extract5551(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          HalveImage.halveImagePackedPixel( 4, new Extract1555rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          HalveImage.halveImagePackedPixel( 4, new Extract8888(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          HalveImage.halveImagePackedPixel( 4, new Extract8888rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          HalveImage.halveImagePackedPixel( 4, new Extract1010102(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          HalveImage.halveImagePackedPixel( 4, new Extract2101010rev(), width, height, data, dstImage, element_size, rowsize, myswap_bytes );
            break;
        default:
          assert( false );
          break;
      }
      
      // swap dstImage and srcImage
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      
      if( newwidth > 1 ) {
        newwidth /= 2;
        rowsize /= 2;
      }
      if( newheight > 1 ) {
        newheight /= 2;
      }
      // compute amount to pad per row if any
      int rowPad = rowsize % psm.getUnpackAlignment();
      
      // should row be padded
      if( rowPad == 0 ) {
        // call teximage with srcImage untouched since its not padded
        if( baseLevel <= level && level <= maxLevel ) {
          srcImage.rewind();
          gl.glTexImage2D( target, level, internalFormat, newwidth, newheight, 0, format, type, srcImage );
          System.err.println("GL Error(" + level + "): " + gl.glGetError() );
          try {
            File file = new File( "glu2DMipmapJ" + level + ".bin" );
            FileOutputStream fos = new FileOutputStream( file );
            srcImage.limit( Mipmap.image_size( newwidth, newheight, format, type ) );
            fos.getChannel().write( srcImage );
            srcImage.clear();
          } catch( IOException e ) {
            System.err.println("IOException");
            System.err.println(e.getMessage());
          }
        }
      } else {
        // compute length of new row in bytes, including padding
        int newRowLength = rowsize + psm.getUnpackAlignment() - rowPad;
        int ii, jj;
        int dstTrav;
        int srcTrav;
        
        // allocate new image for mipmap of size newRowLength x newheight
        ByteBuffer newMipmapImage = ByteBuffer.allocateDirect( newRowLength * newheight );
        if( newMipmapImage == null ) {
          gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
          gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
          gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
          return( GLU.GLU_OUT_OF_MEMORY );
        }
        srcImage.rewind();
        // copy image from srcImage into newMipmapImage by rows
        for( ii = 0; ii < newheight; ii++ ) {
          for( jj = 0; jj < rowsize; jj++ ) {
            newMipmapImage.put( srcImage.get() );
          }
          if( jj < rowsize - 1 ) {
            newMipmapImage.position( newMipmapImage.position() + rowPad );
          }
        }
        
        // and use this new image for mipmapping instead
        if( baseLevel <= level && level <= maxLevel ) {
          newMipmapImage.rewind();
          gl.glTexImage2D( target, level, internalFormat, newwidth, newheight, 0, format, type, newMipmapImage );
          System.err.println("GL Error: " + gl.glGetError() );
          try {
            File file = new File( "glu2DMipmapJ" + level + ".bin" );
            FileOutputStream fos = new FileOutputStream( file );
            srcImage.limit( Mipmap.image_size( newwidth, newheight, format, type ) );
            fos.getChannel().write( newMipmapImage );
            srcImage.clear();
          } catch( IOException e ) {
            System.err.println("IOException");
            System.err.println(e.getMessage());
          }
        }
      }
    }
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, (psm.getUnpackSwapBytes() ? 1 : 0) );
    
    return( 0 );
  }
  
  public static int fastBuild2DMipmaps( GL gl, PixelStorageModes psm, int target,
          int components, int width, int height, int format, int type, ByteBuffer data ) {
    int[] newwidth = new int[1];
    int[] newheight = new int[1];
    int level, levels;
    ByteBuffer newImage;
    int newImage_width;
    int newImage_height;
    ByteBuffer otherImage;
    ByteBuffer imageTemp;
    int memreq;
    int maxsize;
    int cmpts;
    
    Mipmap.closestFit( gl, target, width, height, components, format, type, newwidth, 
            newheight );
    
    levels = Mipmap.computeLog( newwidth[0] );
    level = Mipmap.computeLog( newheight[0] );
    if( level > levels ) {
      levels = level;
    }
    
    cmpts = Mipmap.elements_per_group( format, type );
    
    otherImage = null;
    //  No need to copy the user data if its packed correctly.
    //  Make sure that later routines don't change that data.
    
    if( psm.getUnpackSkipRows() == 0 && psm.getUnpackSkipPixels() == 0 ) {
      newImage = data;
      newImage_width = width;
      newImage_height = height;
    } else {
      int rowsize;
      int group_per_line;
      int elements_per_line;
      int start;
      int iter;
      int iter2;
      int i, j;
      
      try {
        newImage = ByteBuffer.allocateDirect( Mipmap.image_size( 
              width, height, format, GL.GL_UNSIGNED_BYTE ) ).order( ByteOrder.nativeOrder() );
        newImage_width = width;
        newImage_height = height;
      } catch( OutOfMemoryError err ) {
        return( GLU.GLU_OUT_OF_MEMORY );
      }
      
      // Abbreviated version of fill_image for the restricted case.
      if( psm.getUnpackRowLength() > 0 ) {
        group_per_line = psm.getUnpackRowLength();
      } else {
        group_per_line = width;
      }
      rowsize = group_per_line * cmpts;
      elements_per_line = width * cmpts;
      start = psm.getUnpackSkipRows() * rowsize + psm.getUnpackSkipPixels() * cmpts;
      
      for( i = 0; i < height; i++ ) {
        iter = start;
        data.position( iter );
        for( j = 0; j < elements_per_line; j++ ) {
          newImage.put( data.get() );
        }
        start += rowsize;
      }
    }
    
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, 1 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE );
    
    for( level = 0; level <= levels; level++ ) {
      if( newImage_width == newwidth[0] && newImage_height == newheight[0] ) {
        // use newImage for this level
        gl.glTexImage2D( target, level, components, newImage_width, newImage_height,
                0, format, GL.GL_UNSIGNED_BYTE, newImage );
      } else {
        if( otherImage == null ) {
          memreq = Mipmap.image_size( newwidth[0], newheight[0], format, GL.GL_UNSIGNED_BYTE );
          try {
            otherImage = ByteBuffer.allocateDirect( memreq ).order( ByteOrder.nativeOrder() );
          } catch( OutOfMemoryError err ) {
            gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
            gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
            gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
            gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, ( psm.getUnpackSwapBytes() ? 1 : 0 ) ) ;
            return( GLU.GLU_OUT_OF_MEMORY );
          }
        }
        // swap newImage and otherImage
        imageTemp = otherImage;
        otherImage = newImage;
        newImage = imageTemp;
        
        newImage_width = newwidth[0];
        newImage_height = newheight[0];
        gl.glTexImage2D( target, level, components, newImage_width, newImage_height,
                0, format, GL.GL_UNSIGNED_BYTE, newImage );
      }
      if( newwidth[0] > 1 ) {
        newwidth[0] /= 2;
      }
      if( newheight[0] > 1 ) {
        newheight[0] /= 2;
      }
    }
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, ( psm.getUnpackSwapBytes() ? 1 : 0 ) ) ;
    
    return( 0 );
  }
  
  public static int gluBuild3DMipmapLevelsCore( GL gl, int target, int internalFormat,
          int width, int height, int depth, int widthPowerOf2, int heightPowerOf2,
          int depthPowerOf2, int format, int type, int userLevel, int baseLevel,
          int maxLevel, ByteBuffer data ) {
    int newWidth;
    int newHeight;
    int newDepth;
    int level, levels;
    ByteBuffer usersImage;
    ByteBuffer srcImage, dstImage, tempImage;
    int newImageWidth;
    int newImageHeight;
    int newImageDepth;
    int memReq;
    int maxSize;
    int cmpts;
    
    boolean myswapBytes;
    int groupsPerLine, elementSize, groupSize;
    int rowsPerImage, imageSize;
    int rowSize, padding;
    PixelStorageModes psm = new PixelStorageModes();
    
    assert( Mipmap.checkMipmapArgs( internalFormat, format, type ) == 0 );
    assert( width >= 1 && height >= 1 && depth >= 1 );
    assert( type != GL.GL_BITMAP );
    
    srcImage = dstImage = null;
    
    newWidth = widthPowerOf2;
    newHeight = heightPowerOf2;
    newDepth = depthPowerOf2;
    levels = Mipmap.computeLog( newWidth );
    level = Mipmap.computeLog( newHeight );
    if( level > levels ) {
      levels = level;
    }
    level = Mipmap.computeLog( newDepth );
    if( level > levels ) {
      levels = level;
    }
    
    levels += userLevel;
    
    Mipmap.retrieveStoreModes3D( gl, psm );
    myswapBytes = psm.getUnpackSwapBytes();
    cmpts = Mipmap.elements_per_group( format, type );
    if( psm.getUnpackRowLength() > 0 ) {
      groupsPerLine = psm.getUnpackRowLength();
    } else {
      groupsPerLine = width;
    }
    
    elementSize = Mipmap.bytes_per_element( type );
    groupSize = elementSize * cmpts;
    if( elementSize == 1 ) {
      myswapBytes = false;
    }
    
    // 3dstuff
    if( psm.getUnpackImageHeight() > 0 ) {
      rowsPerImage = psm.getUnpackImageHeight();
    } else {
      rowsPerImage = height;
    }
    
    rowSize = groupsPerLine * groupSize;
    padding = ( rowSize % psm.getUnpackAlignment() );
    if( padding != 0 ) {
      rowSize += psm.getUnpackAlignment() - padding;
    }
    
    imageSize = rowsPerImage * rowSize;
    
    usersImage = data.duplicate();
    usersImage.position( psm.getUnpackSkipRows() * rowSize +
                         psm.getUnpackSkipPixels() * groupSize +
                         psm.getUnpackSkipImages() * imageSize );
    usersImage.mark();
    
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, 0 );
    gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, 0 );
    
    level = userLevel;
    
    if( width == newWidth && height == newHeight && depth == newDepth ) {
      // use usersImage for level userlevel
      if( baseLevel <= level && level <= maxLevel ) {
        gl.glTexImage3D( target, level, internalFormat, width, height, depth,
                0, format, type, usersImage );
      }
      if( levels == 0 ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
        gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
        return( 0 );
      }
      int nextWidth = newWidth / 2;
      int nextHeight = newHeight / 2;
      int nextDepth = newDepth / 2;
      
      // clamp to one
      if( nextWidth < 1 ) {
        nextWidth = 1;
      }
      if( nextHeight < 1 ) {
        nextHeight = 1;
      }
      if( nextDepth < 1 ) {
        nextDepth = 1;
      }
      memReq = Mipmap.imageSize3D( nextWidth, nextHeight, nextDepth, format, type );
      try {
        switch( type ) {
          case( GL.GL_UNSIGNED_BYTE ):
          case( GL.GL_BYTE ):
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_SHORT ):
          case( GL.GL_UNSIGNED_SHORT ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_INT ):
          case( GL.GL_UNSIGNED_INT ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          default:
            return( GLU.GLU_INVALID_ENUM );
        }
      } catch( OutOfMemoryError err ) {
        dstImage = null;
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
        gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
        return( GLU.GLU_OUT_OF_MEMORY );
      } else {
        switch( type ) {
          case( GL.GL_UNSIGNED_BYTE ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractUByte(), width, height, depth,
                      usersImage, dstImage, elementSize, 
                      groupSize, rowSize, imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_ubyte( cmpts, width, height, usersImage,
                      dstImage, elementSize, rowSize, groupSize );
            }
            break;
          case( GL.GL_BYTE ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractSByte(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_byte( cmpts, width, height, usersImage,
                      dstImage, elementSize, rowSize, groupSize );
            }
            break;
          case( GL.GL_UNSIGNED_SHORT ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractUShort(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_ushort( cmpts, width, height, usersImage, 
                      dstImage.asShortBuffer(), elementSize, rowSize, groupSize, myswapBytes );
            }
            break;
          case( GL.GL_SHORT ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractSShort(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_short( cmpts, width, height, usersImage,
                      dstImage.asShortBuffer(), elementSize, rowSize, groupSize, myswapBytes );
            }
            break;
          case( GL.GL_UNSIGNED_INT ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractUInt(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_uint( cmpts, width, height, usersImage,
                      dstImage.asIntBuffer(), elementSize, rowSize, groupSize, myswapBytes );
            }
            break;
          case( GL.GL_INT ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractSInt(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_int( cmpts, width, height, usersImage,
                      dstImage.asIntBuffer(), elementSize, rowSize, groupSize, myswapBytes );
            }
            break;
          case( GL.GL_FLOAT ):
            if( depth > 1 ) {
              HalveImage.halveImage3D( cmpts, new ExtractFloat(), width, height, depth,
                      usersImage, dstImage, elementSize, groupSize, rowSize,
                      imageSize, myswapBytes );
            } else {
              HalveImage.halveImage_float( cmpts, width, height, usersImage,
                      dstImage.asFloatBuffer(), elementSize, rowSize, groupSize, myswapBytes );
            }
            break;
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
            assert( format == GL.GL_RGB );
            HalveImage.halveImagePackedPixel3D( 3, new Extract332(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            assert( format == GL.GL_RGB );
            HalveImage.halveImagePackedPixel3D( 3, new Extract233rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
            HalveImage.halveImagePackedPixel3D( 3, new Extract565(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
            HalveImage.halveImagePackedPixel3D( 3, new Extract565rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract4444(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract4444rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract5551(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract1555rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract8888(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract8888rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract1010102(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            HalveImage.halveImagePackedPixel3D( 4, new Extract2101010rev(), width, height, depth, usersImage,
                    dstImage, elementSize, rowSize, imageSize, myswapBytes );
            break;
          default:
            assert( false );
            break;
        }
      }
      newWidth = width / 2;
      newHeight = height / 2;
      newDepth = depth / 2;
      // clamp to 1
      if( newWidth < 1 ) {
        newWidth = 1;
      }
      if( newHeight < 1 ) {
        newHeight = 1;
      }
      if( newDepth < 1 ) {
        newDepth = 1;
      }
      
      myswapBytes = false;
      rowSize = newWidth * groupSize;
      imageSize = rowSize * newHeight;
      memReq = Mipmap.imageSize3D( newWidth, newHeight, newDepth, format, type );
      // swap srcImage and dstImage
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      try {
        switch( type ) {
          case( GL.GL_BYTE ):
          case( GL.GL_UNSIGNED_BYTE ):
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_SHORT ):
          case( GL.GL_UNSIGNED_SHORT ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_INT ):
          case( GL.GL_UNSIGNED_INT ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_FLOAT ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          default:
            return( GLU.GLU_INVALID_ENUM );
        }
      } catch( OutOfMemoryError err ) {
        dstImage = null;
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
        gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
        return( GLU.GLU_OUT_OF_MEMORY );
      }
      // level userLevel + 1 is in srcImage; level userLevel already saved
      level = userLevel + 1;
    } else {
      memReq = Mipmap.imageSize3D( newWidth, newHeight, newDepth, format, type );
      try {
        switch( type ) {
          case( GL.GL_BYTE ):
          case( GL.GL_UNSIGNED_BYTE ):
          case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_SHORT ):
          case( GL.GL_UNSIGNED_SHORT ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_INT ):
          case( GL.GL_UNSIGNED_INT ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          case( GL.GL_FLOAT ):
            dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
            break;
          default:
            return( GLU.GLU_INVALID_ENUM );
        }
      } catch( OutOfMemoryError err ) {
        dstImage = null;
      }
      if( dstImage == null ) {
        gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
        gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
        gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
        gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
        gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
        return( GLU.GLU_OUT_OF_MEMORY );
      }
      
      ScaleInternal.gluScaleImage3D( gl, format, width, height, depth, type,
              usersImage, newWidth, newHeight, newDepth, type, dstImage );
      
      myswapBytes = false;
      rowSize = newWidth * groupSize;
      imageSize = rowSize * newHeight;
      // swap dstImage and srcImage
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      
      if( levels != 0 ) {
        int nextWidth = newWidth / 2;
        int nextHeight = newHeight / 2;
        int nextDepth = newDepth / 2;
        if( nextWidth < 1 ) {
          nextWidth = 1;
        }
        if( nextHeight < 1 ) {
          nextHeight = 1;
        }
        if( nextDepth < 1 ) {
          nextDepth = 1;
        }
        memReq = Mipmap.imageSize3D( nextWidth, nextHeight, nextDepth, format, type );
        try {
          switch( type ) {
            case( GL.GL_BYTE ):
            case( GL.GL_UNSIGNED_BYTE ):
            case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
            case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
              dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
              break;
            case( GL.GL_SHORT ):
            case( GL.GL_UNSIGNED_SHORT ):
            case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
            case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
            case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
            case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
            case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
            case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
              dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
              break;
            case( GL.GL_INT ):
            case( GL.GL_UNSIGNED_INT ):
            case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
            case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
            case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
            case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
              dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
              break;
            case( GL.GL_FLOAT ):
              dstImage = ByteBuffer.allocateDirect( memReq ).order( ByteOrder.nativeOrder() );
              break;
            default:
              return( GLU.GLU_INVALID_ENUM );
          }
        } catch( OutOfMemoryError err ) {
          dstImage = null;
        }
        if( dstImage == null ) {
          gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
          gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
          gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
          gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
          gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
          return( GLU.GLU_OUT_OF_MEMORY );
        }
      }
      // level userLevel is in srcImage; nothing saved yet
      level = userLevel;
    }
    
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, GL.GL_FALSE );
    if( baseLevel <= level && level <= maxLevel ) {
      usersImage.reset();
      gl.glTexImage3D( target, level, internalFormat, width, height, depth,
              0, format, type, usersImage );
    }
    level++;
    for( ; level <= levels; level++ ) {
      switch( type ) {
        case( GL.GL_UNSIGNED_BYTE ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractUByte(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_ubyte( cmpts, width, height, usersImage,
                    dstImage, elementSize, rowSize, groupSize );
          }
          break;
        case( GL.GL_BYTE ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractSByte(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_byte( cmpts, width, height, usersImage,
                    dstImage, elementSize, rowSize, groupSize );
          }
          break;
        case( GL.GL_UNSIGNED_SHORT ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractUShort(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_ushort( cmpts, width, height, usersImage,
                    dstImage.asShortBuffer(), elementSize, rowSize, groupSize, myswapBytes );
          }
          break;
        case( GL.GL_SHORT ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractSShort(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_short( cmpts, width, height, usersImage,
                    dstImage.asShortBuffer(), elementSize, rowSize, groupSize, myswapBytes );
          }
          break;
        case( GL.GL_UNSIGNED_INT ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractUInt(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_uint( cmpts, width, height, usersImage,
                    dstImage.asIntBuffer(), elementSize, rowSize, groupSize, myswapBytes );
          }
          break;
        case( GL.GL_INT ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractSInt(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_int( cmpts, width, height, usersImage,
                    dstImage.asIntBuffer(), elementSize, rowSize, groupSize, myswapBytes );
          }
          break;
        case( GL.GL_FLOAT ):
          if( depth > 1 ) {
            HalveImage.halveImage3D( cmpts, new ExtractFloat(), width, height, depth,
                    usersImage, dstImage, elementSize, groupSize, rowSize,
                    imageSize, myswapBytes );
          } else {
            HalveImage.halveImage_float( cmpts, width, height, usersImage,
                    dstImage.asFloatBuffer(), elementSize, rowSize, groupSize, myswapBytes );
          }
          break;
        case( GL.GL_UNSIGNED_BYTE_3_3_2 ):
          HalveImage.halveImagePackedPixel3D( 3, new Extract332(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_BYTE_2_3_3_REV ):
          HalveImage.halveImagePackedPixel3D( 3, new Extract233rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5 ):
          HalveImage.halveImagePackedPixel3D( 3, new Extract565(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_6_5_REV ):
          HalveImage.halveImagePackedPixel3D( 3, new Extract565rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4 ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract4444(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_4_4_4_4_REV ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract4444rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_5_5_5_1 ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract5551(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_SHORT_1_5_5_5_REV ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract1555rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8 ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract8888(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_INT_8_8_8_8_REV ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract8888rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_INT_10_10_10_2 ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract1010102(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        case( GL.GL_UNSIGNED_INT_2_10_10_10_REV ):
          HalveImage.halveImagePackedPixel3D( 4, new Extract2101010rev(), width, height, depth, usersImage,
                  dstImage, elementSize, rowSize, imageSize, myswapBytes );
          break;
        default:
          assert( false );
          break;
      }
      
      tempImage = srcImage;
      srcImage = dstImage;
      dstImage = tempImage;
      
      if( newWidth > 1 ) {
        newWidth /= 2;
        rowSize /= 2;
      }
      if( newHeight > 1 ) {
        newHeight /= 2;
        imageSize = rowSize * newHeight;
      }
      if( newDepth > 1 ) {
        newDepth /= 2;
      }
      if( baseLevel <= level && level <= maxLevel ) {
        usersImage.reset();
        gl.glTexImage3D( target, level, internalFormat, width, height, depth,
                0, format, type, usersImage );
      }
    }
    gl.glPixelStorei( GL.GL_UNPACK_ALIGNMENT, psm.getUnpackAlignment() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_ROWS, psm.getUnpackSkipRows() );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_PIXELS, psm.getUnpackSkipPixels() );
    gl.glPixelStorei( GL.GL_UNPACK_ROW_LENGTH, psm.getUnpackRowLength() );
    gl.glPixelStorei( GL.GL_UNPACK_SWAP_BYTES, psm.getUnpackSwapBytes() ? 1 : 0 );
    gl.glPixelStorei( GL.GL_UNPACK_SKIP_IMAGES, psm.getUnpackSkipImages() );
    gl.glPixelStorei( GL.GL_UNPACK_IMAGE_HEIGHT, psm.getUnpackImageHeight() );
    return( 0 );
  }
}