TextureConverter

Hi!

What you see here is a texture compressor/converter, that is independent from other parts of the engine, and is universaly usable. Lots of you may know the DDS file format. The DDS file format has the texture compressed in the way that the GPU can directly use, without the need of any preprocessing. For creating DDS files, you can use different tools, but there is a feature in OpenGL that makes it possible to create compressed textures without the need of any external tool. What i have here is a Java class that takes an uncompressed texture image and:


  1. Resizes it to power-of-two
  2. Generates mipmaps
  3. Compresses it to DXT compression format
  4. Saves it to a new Image object



    This Image object can then be saved to disk, and loaded from disk when it is needed afterwards. It makes a huge difference when textures are loaded from format already usable by OpenGL. In the case of application i am working on, the loading time has dropped 4 times with loading pre-compressed textures instead of loading PNG textures.



    What i wont post here, is the resource-management part, which is dependant on the application. Note that this code is not much usable as-is, but you can use this code as the base to implement the texture conversion/compression in your own application. If jME devs choose so, it can be included in the core jME too.



    This converter is ment to be used the following ways:


  5. When you package your application for distribution, you do batch conversion from PNG, JPG, or whatever format to DXT compressed textures, then distribute those textures.
  6. When your application downloads an update of the textures, it downloads in original format (PNG, …) but saves converted textures to a local cache folder.
  7. When the application needs a texture for rendering, it should load converted textures and not the original ones.



    You will have to fix the imports, i used different namespace.



    The TextureConverter.java:


import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBTextureCompression;
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.Util;
import org.lwjgl.opengl.glu.GLU;
import org.lwjgl.opengl.glu.MipMap;

/**
 * This class takes an uncompressed image and compresses it using
 * OpenGL. This conversion should be
 * done by the developer in a batch-processing, or during installing
 * and not during the actual rendering.
 *
 * Lots of code taken fron LWJGLTextureState
 * @author vear (Arpad Vekas)
 */
public class TextureConverter {
   
    // the input image componenets we handle
    private static int[] imageComponents = {
            GL11.GL_RGBA4,
            GL11.GL_RGB8,
            GL11.GL_RGB5_A1,
            GL11.GL_RGBA8,
            GL11.GL_LUMINANCE8_ALPHA8,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT,
            GL11.GL_LUMINANCE16 };
    // the corresponding output compressed format
    private static int[] compressedComponents = {
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGB_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_LUMINANCE_ALPHA_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGB_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_RGBA_ARB,
            ARBTextureCompression.GL_COMPRESSED_LUMINANCE_ARB };
   
    private static int[] imageFormats = {
            GL11.GL_RGBA,
            GL11.GL_RGB,
            GL11.GL_RGBA,
            GL11.GL_RGBA,
            GL11.GL_LUMINANCE_ALPHA,
            GL11.GL_RGB,
            GL11.GL_RGBA,
            GL11.GL_RGBA,
            GL11.GL_RGBA,
            GL11.GL_LUMINANCE};
   
    private static int[] nativeCompressed = {
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
            EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
    };

    public TextureConverter() {};

    public Image convertTexture(Image image) {
        int imageHeight = image.getHeight();
        int imageWidth = image.getWidth();
        int imageType = image.getType();
              
        IntBuffer res = BufferUtils.createIntBuffer(4);
        res.clear();
        GL11.glGenTextures(res);
        int textureid = res.get(0);

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid);
       
        GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
       
        if(!FastMath.isPowerOfTwo(imageWidth)
             || !FastMath.isPowerOfTwo(imageHeight)) {
           
            final int maxSize = LWJGLMipMap.glGetIntegerv(GL11.GL_MAX_TEXTURE_SIZE);
            int w = LWJGLMipMap.nearestPower(imageWidth);
            if (w > maxSize) {
                w = maxSize;
            }

            int h = LWJGLMipMap.nearestPower(imageHeight);
            if (h > maxSize) {
                h = maxSize;
            }
            int format = LWJGLTextureState.getImageFormat(imageType);
            int type = GL11.GL_UNSIGNED_BYTE;
            int bpp = LWJGLMipMap.bytesPerPixel(format, type);
            int size = (w + 4) * h * bpp;
            ByteBuffer scaledImage = BufferUtils.createByteBuffer(size);
            int error = MipMap.gluScaleImage(format, imageWidth,
                    imageHeight, type, image.getData(), w, h, type,
                    scaledImage);
            if (error != 0) {
                Util.checkGLError();
            }

            image.setWidth(w);
            image.setHeight(h);
            image.setData(scaledImage);
        }
        imageHeight = image.getHeight();
        imageWidth = image.getWidth();
       
        ByteBuffer data = image.getData();

        // do the compression
        GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
                compressedComponents[imageType],
                imageWidth,
                imageHeight,
            imageFormats[imageType],
            GL11.GL_UNSIGNED_BYTE,
            data);

        // calculate the number of mips
        int nummips = (int) FastMath.log2(FastMath.max(imageHeight, imageWidth)) +1;
       
        // retrieve the compressed mips of the image
       
        // create the array to hold data of the mips
        int[] internal_format = new int[nummips];
        int[] mip_size = new int[nummips];
        ByteBuffer[] mip_data = new ByteBuffer[nummips];
       
        int total_size = 0;

        // for each mip level
        for(int mip=0; mip<nummips; mip++) {
            res.clear();
            GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip, ARBTextureCompression.GL_TEXTURE_COMPRESSED_ARB, res);
            res.rewind();
            int compressed = res.get();
            //if(compressed == GL11.GL_TRUE) {
                // the compression was succesfull
                res.clear();
                GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip, GL11.GL_TEXTURE_INTERNAL_FORMAT, res);
                res.rewind();
                internal_format[mip] = res.get();
                res.clear();
                GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip,
                        ARBTextureCompression.GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB,
                        res);
                res.rewind();
                mip_size[mip] = res.get();
                if(mip_size[mip] > 0) {
                    total_size += mip_size[mip];
                    // allocate buffer for the mip
                    mip_data[mip] = BufferUtils.createByteBuffer(mip_size[mip]);
                    mip_data[mip].clear();
                    // get the compressed image data
                    ARBTextureCompression.glGetCompressedTexImageARB(GL11.GL_TEXTURE_2D, mip, mip_data[mip]);
                }
            //}
        }
       
        // release the texture
        res.clear();
        res.put(textureid);
        res.rewind();
        GL11.glDeleteTextures(res);
       
        // save the compressed texture with all its mips into a file
        // figure out the new compressed image type
        int components = internal_format[0];
        int format = 0;
        for(int i=0; i<nativeCompressed.length; i++) {
            if(components == nativeCompressed[i] ) {
                // this is it
                format = Image.LAST_UNCOMPRESSED_TYPE+1+i;
            }
        }
        if(format == 0) {
            // not found among compressed, try to find
            // among normal ones
            for(int i=0; i<imageComponents.length; i++) {
                if(components == imageComponents[i] ) {
                    // this is it
                    format = i;
                }
            }
        }
        if(format==0) {
            // other way to find out?
        }
        if(format!=0) {
            // create a new image
            Image img = new Image();
            img.setHeight(imageHeight);
            img.setWidth(imageWidth);
            img.setType(format);
            img.setMipMapSizes(mip_size);
            // create a buffer to hold all the data
            ByteBuffer img_data = BufferUtils.createByteBuffer(total_size);
            // put all the mips in
            img_data.clear();
            for(int i=0;i<nummips; i++) {
                if(mip_data[i]!=null) {
                    mip_data[i].rewind();
                    img_data.put(mip_data[i]);
                }
            }
            img.setData(img_data);
            return img;
        }
        // still not found, huh?
        // TODO: save the OGL component type?
        return null;
    }

    /**
     * override MipMap to access helper methods
     */
    protected static class LWJGLMipMap extends MipMap {
        /**
         * @see MipMap#glGetIntegerv(int)
         */
        protected static int glGetIntegerv(int what) {
            return org.lwjgl.opengl.glu.Util.glGetIntegerv(what);
        }

        /**
         * @see MipMap#nearestPower(int)
         */
        protected static int nearestPower(int value) {
            return org.lwjgl.opengl.glu.Util.nearestPower(value);
        }

        /**
         * @see MipMap#bytesPerPixel(int, int)
         */
        protected static int bytesPerPixel(int format, int type) {
            return org.lwjgl.opengl.glu.Util.bytesPerPixel(format, type);
        }
    }
}



After you have the new (converted) Image object, you can save it to a file easily. Here are the save() and load() methods i use for saving and loading of textures. If you have your own image saving/loading methods, just disregard the following code. If you take the time you could make a proper DDS file format save routine too.


     /**
     * Saves the given image to a ByteBuffer, which can later be
     * saved to a file.
     * @return The image data ready to be saved
     */
    public ByteBuffer save() {
        if(data==null)
            return null;
        // compute, how big a buffer we will need
        int buffsize = 4 + //header
                       4 + //type
                       4 + //width
                       4 + //height
                       4 + //number of mips
                       ( mipMapSizes == null ? 4 : mipMapSizes.length*4 ) + // size for each mip
                       data.limit(); // the total data buffer length
               
        // create the buffer
        DataBuffer saved = DataBuffer.allocate(buffsize);
        saved.clear();
        // header "VLT1"
        saved.put("VLT1");

        // type
        saved.put(type);
        // width
        saved.put(width);
        // height
        saved.put(height);
        // number of mips
        saved.put(mipMapSizes == null ? 1 : mipMapSizes.length);
        // for each mip, the size of the mip in bytes
        if(mipMapSizes == null || mipMapSizes.length == 1) {
            // if only one mip, the put in the data lenght
            saved.put(data.limit());
        } else {
            // put in all the mip sizes
            for(int i=0; i<mipMapSizes.length; i++)
                saved.put(mipMapSizes[i]);
        }
        // image data
        data.rewind();
        saved.put(data);
       
        return saved.getData();
    }
   
    /**
     * Loads an Image object from the given buffer.
     * @param buf The buffer containing the image data
     */
    public boolean load(ByteBuffer buf) {
        // create a DataBuffer
        DataBuffer load = new DataBuffer();
        load.setData(buf);
        load.rewind();
       
        // read in the header
        if(!"VLT1".equals(load.getChars(4))) {
            // not a texture file
            return false;
        }
        // type
        type = load.getInt();
        // width
        width = load.getInt();
        // height
        height = load.getInt();
        // number of mips
        int loadmips = load.getInt();
        int mips = loadmips;
        if(mips<1)
            mips = 1;
        mipMapSizes = new int[mips];
       
        int size = 0;
        if( loadmips == 0) {
            size = load.remaining();
            mipMapSizes[0] = size;
        } else {
            // load all the mips sizes
            for(int i=0; i<loadmips; i++) {
                mipMapSizes[i] = load.getInt();
                size += mipMapSizes[i];
            }
        }
       
        if( size != load.remaining()) {
            // the file lenght does not match the total mipmap size
            return false;
        }
        // create the data buffer
        data = BufferUtils.createByteBuffer(size);
        data.clear();
        // put the remaining bytes into the buffer
        data.put(load.getData());
       
        return true;
    }



Just for completeness, the DataBuffer class used for manipulating ByteBuffers:


import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 *
 * @author vear
 */
public class DataBuffer {

    public static DataBuffer allocate(int bytes) {
        ByteBuffer data = ByteBuffer.allocate(bytes);
        DataBuffer db = new DataBuffer();
        db.setData(data);
        return db;
    }
   
    public static DataBuffer allocateDirect(int bytes) {
        ByteBuffer data = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
        DataBuffer db = new DataBuffer();
        db.setData(data);
        return db;
    }
   
    private ByteBuffer data;

    public void clear() {
        data.clear();
    }

    public int getInt() {
        return data.getInt();
    }

    public int remaining() {
        return data.remaining();
    }

    public void rewind() {
        data.rewind();
    }
   
    public void put(int type) {
        data.putInt(type);
    }

    public void put(ByteBuffer toput) {
        data.put(toput);
    }
   
    public void put(String str) {
        data.put(str.getBytes());
    }
   
    public String getChars(int len) {
        byte b[]=new byte[len];
        data.get(b, data.position(), len);
        String hdr=new String(b);
        return hdr;
    }
   
    public void setData(ByteBuffer data) {
        this.data = data;
    }
   
    public ByteBuffer getData() {
        return data;
    }



The X-Shift project still needs more people:
http://www.jmonkeyengine.com/jmeforum/index.php?topic=6964.0

Awesome, I once made a crappy attempt at that using the lwjgl optional libs, but something that can run with the libs that come with jME is way more useful!

Thank you for sharing!

Thanks a lot for sharing the code! I've been trying to use it, however I can't get it to compile with jME 1.0 - the compiler complains about missing method LWJGLTextureState.getImageFormat. I browsed through the source in jME 2.0 code repository but there is also no such method in LWJGLTextureState class… Am I missing something fundamental?



Many thanks again!

Replace:


LWJGLTextureState.getImageFormat(imageType);



with


imageFormats[imageType];



jMe1.0 does not have LUMINANCE image type, so remove that if you are using 1.0. The image types changed in jMe2.0, you will need to adjust the imageTypes list to match those. Same goes for imageComponents and compressedComponents too.

Planning to do a new release for this? 

this looks awesome. thx for sharing  :smiley:

Mindgamer said:

Planning to do a new release for this? 


You mean for jME 2.0? Has someone ported this code to jME 2.0 and is willling to post here? Although its a 10 minute work to change it to work with jME 2.0 which you can do yourself, if there is interest in it, i'll get to port it to jME 2.0. Feature wise i dont see much possibility for update. I thought of possibility to compress normal maps with 3DC, but 3DC seems not much supported, and from what i read somewhere, it will be even less supported in the future.

Actually yes I did mean 2.0 - I looked briefly into it… but after not being able to solve the problems in 10 minutes I rather posted here and begged for an update…



I am such a leech…

Here ya go, jME 2.0 compatible version, far from beeing extensively tested, but at least you got a go with those image type tables:



/**
 *
 * @author vear (Arpad Vekas)
 */
import com.jme.image.Image;
import com.jme.math.FastMath;
import com.jme.scene.state.lwjgl.records.TextureStateRecord;
import com.jme.util.geom.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import org.lwjgl.opengl.ARBTextureCompression;
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.Util;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.glu.MipMap;

/**
 * This class takes an uncompressed image and compresses it using
 * OpenGL to a file for later loading. This conversion should be
 * done by the developer in a batch-processing, or during installing
 * and not during the rendering.
 *
 * Lots of code taken fron LWJGLTextureState
 *
 * @author vear (Arpad Vekas)
 */
public class TextureConverter {
   
    public TextureConverter() {};

    protected int getCompressComponent(Image.Format imageType) {
        switch(imageType) {
            case Alpha8 :
                      return ARBTextureCompression.GL_COMPRESSED_ALPHA_ARB;
            case Luminance8 :
                     return ARBTextureCompression.GL_COMPRESSED_LUMINANCE_ARB;
            case Luminance8Alpha8 :
                     return ARBTextureCompression.GL_COMPRESSED_LUMINANCE_ALPHA_ARB;
            case Intensity8 :
                     return ARBTextureCompression.GL_COMPRESSED_INTENSITY_ARB;
            case RGB8 :
                    return ARBTextureCompression.GL_COMPRESSED_RGB_ARB;
            case RGBA8 :
                return ARBTextureCompression.GL_COMPRESSED_RGBA_ARB;
            case RGB_TO_DXT1:
                return ARBTextureCompression.GL_COMPRESSED_RGB_ARB;
            case RGBA_TO_DXT1:
                return ARBTextureCompression.GL_COMPRESSED_RGBA_ARB;
            case RGBA_TO_DXT3:
                return ARBTextureCompression.GL_COMPRESSED_RGBA_ARB;
            case RGBA_TO_DXT5:
                return ARBTextureCompression.GL_COMPRESSED_RGBA_ARB;
            default :
                return 0;
            // TODO: if you have some exotic image format, try adding it to the proper place based on its type
            // may or may not work
        }
    }
   
    protected Image.Format getCompressedImageType(int compressedGLComponennntType) {
        switch(compressedGLComponennntType) {
            case EXTTextureCompressionS3TC.GL_COMPRESSED_RGB_S3TC_DXT1_EXT :
                return Image.Format.NativeDXT1;
            case EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT :
                return Image.Format.NativeDXT1A;
            case EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT :
                return Image.Format.NativeDXT3;
            case EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT :
                return Image.Format.NativeDXT5;
        }
        // TODO: the texture did not compress, here another check could be made
        // against non-compressed types if we allow to have non-compressed textures
        return null;
    }
   
    /**
     * Rescales the Image to power-of-two size, generates mipmaps, and compresses the image to DXT
     * @param image     The input image to process
     * @return          The processed image, null if the compression did not succeed
     */
    public Image convertTexture(Image image) {
        int imageHeight = image.getHeight();
        int imageWidth = image.getWidth();
        Image.Format imageType = image.getFormat();
        int compressGLComponent = getCompressComponent(imageType);
        int imageGLFormat = TextureStateRecord.getGLPixelFormat(imageType);
        if(compressGLComponent==0) {
            // format not supported, see the TODO
            return null;
        }
              
        IntBuffer res = BufferUtils.createIntBuffer(4);
        res.clear();
        GL11.glGenTextures(res);
        int textureid = res.get(0);

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, textureid);
       
        GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
       
        if(!FastMath.isPowerOfTwo(imageWidth)
             || !FastMath.isPowerOfTwo(imageHeight)) {
           
            final int maxSize = LWJGLMipMap.glGetIntegerv(GL11.GL_MAX_TEXTURE_SIZE);
            int w = LWJGLMipMap.nearestPower(imageWidth);
            if (w > maxSize) {
                w = maxSize;
            }

            int h = LWJGLMipMap.nearestPower(imageHeight);
            if (h > maxSize) {
                h = maxSize;
            }
           
                    //imageFormats[imageType];//LWJGLTextureState.getImageFormat(imageType);
            int type = GL11.GL_UNSIGNED_BYTE;
            int bpp = LWJGLMipMap.bytesPerPixel(imageGLFormat, type);
            int size = (w + 4) * h * bpp;
            ByteBuffer scaledImage = BufferUtils.createByteBuffer(size);
            int error = MipMap.gluScaleImage(imageGLFormat, imageWidth,
                    imageHeight, type, image.getData().get(0), w, h, type,
                    scaledImage);
            if (error != 0) {
                Util.checkGLError();
            }

            image.setWidth(w);
            image.setHeight(h);
            image.setData(scaledImage);
        }
        imageHeight = image.getHeight();
        imageWidth = image.getWidth();
       
        ByteBuffer data = image.getData().get(0);

       
        // do the compression
        GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
                compressGLComponent,
                imageWidth,
                imageHeight,
            imageGLFormat,
            GL11.GL_UNSIGNED_BYTE,
            data);

        // calculate the number of mips
        int nummips = (int) ((Math.log(Math.max(imageHeight, imageWidth)) / Math.log(2)) + 1);
       
        // retrieve the compressed mips of the image
       
        // create the array to hold data of the mips
        int[] internal_format = new int[nummips];
        int[] mip_size = new int[nummips];
        //ByteBuffer[] mip_data = new ByteBuffer[nummips];
       
        int total_size = 0;

        ByteBuffer[] mip_data = new ByteBuffer[nummips];
       
        // for each mip level
        for(int mip=0; mip<nummips; mip++) {
            res.clear();
            GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip, ARBTextureCompression.GL_TEXTURE_COMPRESSED_ARB, res);
            res.rewind();
            int compressed = res.get();
            //if(compressed == GL11.GL_TRUE) {
                // the compression was succesfull
                res.clear();
                GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip, GL11.GL_TEXTURE_INTERNAL_FORMAT, res);
                res.rewind();
                internal_format[mip] = res.get();
                res.clear();
                GL11.glGetTexLevelParameter(GL11.GL_TEXTURE_2D, mip,
                        ARBTextureCompression.GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB,
                        res);
                res.rewind();
                mip_size[mip] = res.get();
                if(mip_size[mip] > 0) {
                    total_size += mip_size[mip];
                    // allocate buffer for the mip
                    mip_data[mip] = BufferUtils.createByteBuffer(mip_size[mip]);
                    mip_data[mip].clear();
                    // get the compressed image data
                    ARBTextureCompression.glGetCompressedTexImageARB(GL11.GL_TEXTURE_2D, mip, mip_data[mip]);
                }
            //}
        }
       
        // release the texture
        res.clear();
        res.put(textureid);
        res.rewind();
        GL11.glDeleteTextures(res);
       
        // save the compressed texture with all its mips into a new Image object
        // figure out the new compressed image type
        int components = internal_format[0];
        Image.Format format = getCompressedImageType(components);
        if(format==null) {
            // other way to find out?
        }
        if(format!=null) {
            // create a new image
            Image img = new Image();
            img.setHeight(imageHeight);
            img.setWidth(imageWidth);
            img.setFormat(format);
            img.setMipMapSizes(mip_size);
            // create a buffer to hold all the data
            ByteBuffer img_data = BufferUtils.createByteBuffer(total_size);
            // put all the mips in
            img_data.clear();
            for(int i=0;i<nummips; i++) {
                if(mip_data[i]!=null) {
                    mip_data[i].rewind();
                    img_data.put(mip_data[i]);
                }
            }
            img.setData(img_data);
            return img;
        }
        return null;
    }

    /**
     * override MipMap to access helper methods
     */
    protected static class LWJGLMipMap extends MipMap {
        /**
         * @see MipMap#glGetIntegerv(int)
         */
        protected static int glGetIntegerv(int what) {
            return org.lwjgl.util.glu.MipMap.glGetIntegerv(what);
        }

        /**
         * @see MipMap#nearestPower(int)
         */
        protected static int nearestPower(int value) {
            return org.lwjgl.util.glu.MipMap.nearestPower(value);
        }

        /**
         * @see MipMap#bytesPerPixel(int, int)
         */
        protected static int bytesPerPixel(int format, int type) {
            return org.lwjgl.util.glu.MipMap.bytesPerPixel(format, type);
        }
    }

}



And you were right, it took more than 10 mins, all those enum-ification turned stuff on their heads.

Thanks vear… This is some good code you have given to the community here…