com.ardor3d.scene.state.lwjgl.LwjglTextureStateUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.ardor3d.scene.state.lwjgl.LwjglTextureStateUtil.java

Source

/**
 * Copyright (c) 2008-2012 Ardor Labs, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it 
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
 */

package com.ardor3d.scene.state.lwjgl;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Collection;
import java.util.logging.Logger;

import org.lwjgl.opengl.ARBDepthTexture;
import org.lwjgl.opengl.ARBMultitexture;
import org.lwjgl.opengl.ARBShadow;
import org.lwjgl.opengl.ARBTextureBorderClamp;
import org.lwjgl.opengl.ARBTextureCompression;
import org.lwjgl.opengl.ARBTextureCubeMap;
import org.lwjgl.opengl.ARBTextureEnvCombine;
import org.lwjgl.opengl.ARBTextureMirroredRepeat;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.EXTTextureLODBias;
import org.lwjgl.opengl.EXTTextureMirrorClamp;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.SGISGenerateMipmap;
import org.lwjgl.opengl.Util;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.glu.MipMap;

import com.ardor3d.image.Image;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture.ApplyMode;
import com.ardor3d.image.Texture.CombinerFunctionAlpha;
import com.ardor3d.image.Texture.CombinerFunctionRGB;
import com.ardor3d.image.Texture.CombinerOperandAlpha;
import com.ardor3d.image.Texture.CombinerOperandRGB;
import com.ardor3d.image.Texture.CombinerSource;
import com.ardor3d.image.Texture.Type;
import com.ardor3d.image.Texture.WrapAxis;
import com.ardor3d.image.Texture.WrapMode;
import com.ardor3d.image.Texture1D;
import com.ardor3d.image.Texture2D;
import com.ardor3d.image.Texture3D;
import com.ardor3d.image.TextureCubeMap;
import com.ardor3d.image.util.ImageUtils;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.renderer.ContextCapabilities;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.state.RenderState.StateType;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.renderer.state.record.RendererRecord;
import com.ardor3d.renderer.state.record.TextureRecord;
import com.ardor3d.renderer.state.record.TextureStateRecord;
import com.ardor3d.renderer.state.record.TextureUnitRecord;
import com.ardor3d.scene.state.lwjgl.util.LwjglRendererUtil;
import com.ardor3d.scene.state.lwjgl.util.LwjglTextureUtil;
import com.ardor3d.util.Constants;
import com.ardor3d.util.TextureManager;
import com.ardor3d.util.geom.BufferUtils;
import com.ardor3d.util.stat.StatCollector;
import com.ardor3d.util.stat.StatType;

public abstract class LwjglTextureStateUtil {
    private static final Logger logger = Logger.getLogger(LwjglTextureStateUtil.class.getName());

    public static void load(final Texture texture, final int unit) {
        if (texture == null) {
            return;
        }

        final RenderContext context = ContextManager.getCurrentContext();
        if (context == null) {
            logger.warning("RenderContext is null for texture: " + texture);
            return;
        }

        final ContextCapabilities caps = context.getCapabilities();
        final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(StateType.Texture);

        // Check we are in the right unit
        if (record != null) {
            checkAndSetUnit(unit, record, caps);
        }

        // Create the texture...
        // First, look for a texture in the cache just like ours
        final Texture cached = TextureManager.findCachedTexture(texture.getTextureKey());

        if (cached == null) {
            TextureManager.addToCache(texture);
        } else {
            final int textureId = cached.getTextureIdForContext(context.getGlContextRep());
            if (textureId != 0) {
                doTextureBind(cached, unit, false);
                return;
            }
        }

        // Create a new texture id for this texture
        final IntBuffer id = BufferUtils.createIntBuffer(1);
        id.clear();
        GL11.glGenTextures(id);
        final int textureId = id.get(0);

        // store the new id by our current gl context.
        texture.setTextureIdForContext(context.getGlContextRep(), textureId);

        update(texture, unit);
    }

    /**
     * bind texture and upload image data to card
     */
    public static void update(final Texture texture, final int unit) {
        final RenderContext context = ContextManager.getCurrentContext();
        final ContextCapabilities caps = context.getCapabilities();

        texture.getTextureKey().setClean(context.getGlContextRep());

        // our texture type:
        final Texture.Type type = texture.getType();

        // bind our texture id to this unit.
        doTextureBind(texture, unit, false);

        // pass image data to OpenGL
        final Image image = texture.getImage();
        final boolean hasBorder = texture.hasBorder();
        if (image == null) {
            logger.warning("Image data for texture is null.");
        }

        // set alignment to support images with width % 4 != 0, as images are
        // not aligned
        GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);

        // Get texture image data. Not all textures have image data.
        // For example, ApplyMode.Combine modes can use primary colors,
        // texture output, and constants to modify fragments via the
        // texture units.
        if (image != null) {
            final int maxSize = caps.getMaxTextureSize();
            final int actualWidth = image.getWidth();
            final int actualHeight = image.getHeight();

            final boolean needsPowerOfTwo = !caps.isNonPowerOfTwoTextureSupported()
                    && (!MathUtils.isPowerOfTwo(image.getWidth()) || !MathUtils.isPowerOfTwo(image.getHeight()));
            if (actualWidth > maxSize || actualHeight > maxSize || needsPowerOfTwo) {
                if (needsPowerOfTwo) {
                    logger.warning(
                            "(card unsupported) Attempted to apply texture with size that is not power of 2: "
                                    + image.getWidth() + " x " + image.getHeight());
                }
                if (actualWidth > maxSize || actualHeight > maxSize) {
                    logger.warning(
                            "(card unsupported) Attempted to apply texture with size bigger than max texture size ["
                                    + maxSize + "]: " + image.getWidth() + " x " + image.getHeight());
                }

                int w = actualWidth;
                if (needsPowerOfTwo) {
                    w = MathUtils.nearestPowerOfTwo(actualWidth);
                }
                if (w > maxSize) {
                    w = maxSize;
                }

                int h = actualHeight;
                if (needsPowerOfTwo) {
                    h = MathUtils.nearestPowerOfTwo(actualHeight);
                }
                if (h > maxSize) {
                    h = maxSize;
                }
                logger.warning("Rescaling image to " + w + " x " + h + " !!!");

                // must rescale image to get "top" mipmap texture image
                final int pixFormat = LwjglTextureUtil.getGLPixelFormat(image.getDataFormat());
                final int pixDataType = LwjglTextureUtil.getGLPixelDataType(image.getDataType());
                final int bpp = ImageUtils.getPixelByteSize(image.getDataFormat(), image.getDataType());
                final ByteBuffer scaledImage = BufferUtils.createByteBuffer((w + 4) * h * bpp);
                final int error = MipMap.gluScaleImage(pixFormat, actualWidth, actualHeight, pixDataType,
                        image.getData(0), w, h, pixDataType, scaledImage);
                if (error != 0) {
                    Util.checkGLError();
                }

                image.setWidth(w);
                image.setHeight(h);
                image.setData(scaledImage);
            }

            if (!texture.getMinificationFilter().usesMipMapLevels()
                    && !texture.getTextureStoreFormat().isCompressed()) {

                // Load textures which do not need mipmap auto-generating and
                // which aren't using compressed images.

                switch (texture.getType()) {
                case TwoDimensional:
                    // ensure the buffer is ready for reading
                    image.getData(0).rewind();
                    // send top level to card
                    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0,
                            LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()), image.getWidth(),
                            image.getHeight(), hasBorder ? 1 : 0,
                            LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                            LwjglTextureUtil.getGLPixelDataType(image.getDataType()), image.getData(0));
                    break;
                case OneDimensional:
                    // ensure the buffer is ready for reading
                    image.getData(0).rewind();
                    // send top level to card
                    GL11.glTexImage1D(GL11.GL_TEXTURE_1D, 0,
                            LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()), image.getWidth(),
                            hasBorder ? 1 : 0, LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                            LwjglTextureUtil.getGLPixelDataType(image.getDataType()), image.getData(0));
                    break;
                case ThreeDimensional:
                    if (caps.isTexture3DSupported()) {
                        // concat data into single buffer:
                        int dSize = 0;
                        int count = 0;
                        ByteBuffer data = null;
                        for (int x = 0; x < image.getData().size(); x++) {
                            if (image.getData(x) != null) {
                                data = image.getData(x);
                                dSize += data.limit();
                                count++;
                            }
                        }
                        // reuse buffer if we can.
                        if (count != 1) {
                            data = BufferUtils.createByteBuffer(dSize);
                            for (int x = 0; x < image.getData().size(); x++) {
                                if (image.getData(x) != null) {
                                    data.put(image.getData(x));
                                }
                            }
                            // ensure the buffer is ready for reading
                            data.flip();
                        }
                        // send top level to card
                        GL12.glTexImage3D(GL12.GL_TEXTURE_3D, 0,
                                LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                image.getWidth(), image.getHeight(), image.getDepth(), hasBorder ? 1 : 0,
                                LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                    } else {
                        logger.warning("This card does not support Texture3D.");
                    }
                    break;
                case CubeMap:
                    // NOTE: Cubemaps MUST be square, so height is ignored
                    // on purpose.
                    if (caps.isTextureCubeMapSupported()) {
                        for (final TextureCubeMap.Face face : TextureCubeMap.Face.values()) {
                            // ensure the buffer is ready for reading
                            image.getData(face.ordinal()).rewind();
                            // send top level to card
                            GL11.glTexImage2D(getGLCubeMapFace(face), 0,
                                    LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                    image.getWidth(), image.getWidth(), hasBorder ? 1 : 0,
                                    LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                    LwjglTextureUtil.getGLPixelDataType(image.getDataType()),
                                    image.getData(face.ordinal()));
                        }
                    } else {
                        logger.warning("This card does not support Cubemaps.");
                    }
                    break;
                }
            } else if (texture.getMinificationFilter().usesMipMapLevels() && !image.hasMipmaps()
                    && !texture.getTextureStoreFormat().isCompressed()) {

                // For textures which need mipmaps auto-generating and which
                // aren't using compressed images, generate the mipmaps.
                // A new mipmap builder may be needed to build mipmaps for
                // compressed textures.

                if (caps.isAutomaticMipmapsSupported()) {
                    // Flag the card to generate mipmaps
                    GL11.glTexParameteri(getGLType(type), SGISGenerateMipmap.GL_GENERATE_MIPMAP_SGIS, GL11.GL_TRUE);
                }

                switch (type) {
                case TwoDimensional:
                    // ensure the buffer is ready for reading
                    image.getData(0).rewind();
                    if (caps.isAutomaticMipmapsSupported()) {
                        // send top level to card
                        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0,
                                LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                image.getWidth(), image.getHeight(), hasBorder ? 1 : 0,
                                LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                LwjglTextureUtil.getGLPixelDataType(image.getDataType()), image.getData(0));
                    } else {
                        // send to card
                        GLU.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D,
                                LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                image.getWidth(), image.getHeight(),
                                LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                LwjglTextureUtil.getGLPixelDataType(image.getDataType()), image.getData(0));
                    }
                    break;
                case OneDimensional:
                    // ensure the buffer is ready for reading
                    image.getData(0).rewind();
                    if (caps.isAutomaticMipmapsSupported()) {
                        // send top level to card
                        GL11.glTexImage1D(GL11.GL_TEXTURE_1D, 0,
                                LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                image.getWidth(), hasBorder ? 1 : 0,
                                LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                LwjglTextureUtil.getGLPixelDataType(image.getDataType()), image.getData(0));
                    } else {
                        // Note: LWJGL's GLU class does not support
                        // gluBuild1DMipmaps.
                        logger.warning(
                                "non-fbo 1d mipmap generation is not currently supported.  Use DDS or a non-mipmap minification filter.");
                        return;
                    }
                    break;
                case ThreeDimensional:
                    if (caps.isTexture3DSupported()) {
                        if (caps.isAutomaticMipmapsSupported()) {
                            // concat data into single buffer:
                            int dSize = 0;
                            int count = 0;
                            ByteBuffer data = null;
                            for (int x = 0; x < image.getData().size(); x++) {
                                if (image.getData(x) != null) {
                                    data = image.getData(x);
                                    dSize += data.limit();
                                    count++;
                                }
                            }
                            // reuse buffer if we can.
                            if (count != 1) {
                                data = BufferUtils.createByteBuffer(dSize);
                                for (int x = 0; x < image.getData().size(); x++) {
                                    if (image.getData(x) != null) {
                                        data.put(image.getData(x));
                                    }
                                }
                                // ensure the buffer is ready for reading
                                data.flip();
                            }
                            // send top level to card
                            GL12.glTexImage3D(GL12.GL_TEXTURE_3D, 0,
                                    LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                    image.getWidth(), image.getHeight(), image.getDepth(), hasBorder ? 1 : 0,
                                    LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                    LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                        } else {
                            // Note: LWJGL's GLU class does not support
                            // gluBuild3DMipmaps.
                            logger.warning(
                                    "non-fbo 3d mipmap generation is not currently supported.  Use DDS or a non-mipmap minification filter.");
                            return;
                        }
                    } else {
                        logger.warning("This card does not support Texture3D.");
                        return;
                    }
                    break;
                case CubeMap:
                    // NOTE: Cubemaps MUST be square, so height is ignored
                    // on purpose.
                    if (caps.isTextureCubeMapSupported()) {
                        if (caps.isAutomaticMipmapsSupported()) {
                            for (final TextureCubeMap.Face face : TextureCubeMap.Face.values()) {
                                // ensure the buffer is ready for reading
                                image.getData(face.ordinal()).rewind();
                                // send top level to card
                                GL11.glTexImage2D(getGLCubeMapFace(face), 0,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        image.getWidth(), image.getWidth(), hasBorder ? 1 : 0,
                                        LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                        LwjglTextureUtil.getGLPixelDataType(image.getDataType()),
                                        image.getData(face.ordinal()));
                            }
                        } else {
                            for (final TextureCubeMap.Face face : TextureCubeMap.Face.values()) {
                                // ensure the buffer is ready for reading
                                image.getData(face.ordinal()).rewind();
                                // send to card
                                GLU.gluBuild2DMipmaps(getGLCubeMapFace(face),
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        image.getWidth(), image.getWidth(),
                                        LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                        LwjglTextureUtil.getGLPixelDataType(image.getDataType()),
                                        image.getData(face.ordinal()));
                            }
                        }
                    } else {
                        logger.warning("This card does not support Cubemaps.");
                        return;
                    }
                    break;
                }

                if (texture.getTextureMaxLevel() >= 0) {
                    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL,
                            texture.getTextureMaxLevel());
                }
            } else {
                // Here we handle textures that are either compressed or have predefined mipmaps.
                // Get mipmap data sizes and amount of mipmaps to send to opengl. Then loop through all mipmaps and send
                // them.
                int[] mipSizes = image.getMipMapByteSizes();
                ByteBuffer data = null;

                if (type == Type.CubeMap) {
                    if (caps.isTextureCubeMapSupported()) {
                        for (final TextureCubeMap.Face face : TextureCubeMap.Face.values()) {
                            data = image.getData(face.ordinal());
                            int pos = 0;
                            int max = 1;

                            if (mipSizes == null) {
                                mipSizes = new int[] { data.capacity() };
                            } else if (texture.getMinificationFilter().usesMipMapLevels()) {
                                max = mipSizes.length;
                            }

                            // set max mip level
                            GL11.glTexParameteri(getGLCubeMapFace(face), GL12.GL_TEXTURE_MAX_LEVEL, max - 1);

                            for (int m = 0; m < max; m++) {
                                final int width = Math.max(1, image.getWidth() >> m);
                                final int height = Math.max(1, image.getHeight() >> m);

                                data.position(pos);
                                data.limit(pos + mipSizes[m]);

                                if (texture.getTextureStoreFormat().isCompressed()) {
                                    ARBTextureCompression.glCompressedTexImage2DARB(getGLCubeMapFace(face), m,
                                            LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                            width, height, hasBorder ? 1 : 0, data);
                                } else {
                                    GL11.glTexImage2D(getGLCubeMapFace(face), m,
                                            LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                            width, height, hasBorder ? 1 : 0,
                                            LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                            LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                                }
                                pos += mipSizes[m];
                            }
                        }
                    } else {
                        logger.warning("This card does not support CubeMaps.");
                        return;
                    }
                } else {
                    data = image.getData(0);
                    int pos = 0;
                    int max = 1;

                    if (mipSizes == null) {
                        mipSizes = new int[] { data.capacity() };
                    } else if (texture.getMinificationFilter().usesMipMapLevels()) {
                        max = mipSizes.length;
                    }

                    // Set max mip level
                    switch (type) {
                    case TwoDimensional:
                        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, max - 1);
                        break;
                    case ThreeDimensional:
                        GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_MAX_LEVEL, max - 1);
                        break;
                    case OneDimensional:
                        GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL12.GL_TEXTURE_MAX_LEVEL, max - 1);
                        break;
                    }

                    if (type == Type.ThreeDimensional) {
                        if (caps.isTexture3DSupported()) {
                            // concat data into single buffer:
                            int dSize = 0;
                            int count = 0;
                            for (int x = 0; x < image.getData().size(); x++) {
                                if (image.getData(x) != null) {
                                    data = image.getData(x);
                                    dSize += data.limit();
                                    count++;
                                }
                            }
                            // reuse buffer if we can.
                            if (count != 1) {
                                data = BufferUtils.createByteBuffer(dSize);
                                for (int x = 0; x < image.getData().size(); x++) {
                                    if (image.getData(x) != null) {
                                        data.put(image.getData(x));
                                    }
                                }
                                // ensure the buffer is ready for reading
                                data.flip();
                            }
                        } else {
                            logger.warning("This card does not support Texture3D.");
                            return;
                        }
                    }

                    for (int m = 0; m < max; m++) {
                        final int width = Math.max(1, image.getWidth() >> m);
                        final int height = Math.max(1, image.getHeight() >> m);

                        data.position(pos);
                        data.limit(pos + mipSizes[m]);

                        switch (type) {
                        case TwoDimensional:
                            if (texture.getTextureStoreFormat().isCompressed()) {
                                ARBTextureCompression.glCompressedTexImage2DARB(GL11.GL_TEXTURE_2D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, height, hasBorder ? 1 : 0, data);
                            } else {
                                GL11.glTexImage2D(GL11.GL_TEXTURE_2D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, height, hasBorder ? 1 : 0,
                                        LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                        LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                            }
                            break;
                        case OneDimensional:
                            if (texture.getTextureStoreFormat().isCompressed()) {
                                ARBTextureCompression.glCompressedTexImage1DARB(GL11.GL_TEXTURE_1D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, hasBorder ? 1 : 0, data);
                            } else {
                                GL11.glTexImage1D(GL11.GL_TEXTURE_1D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, hasBorder ? 1 : 0,
                                        LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                        LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                            }
                            break;
                        case ThreeDimensional:
                            final int depth = Math.max(1, image.getDepth() >> m);
                            // already checked for support above...
                            if (texture.getTextureStoreFormat().isCompressed()) {
                                ARBTextureCompression.glCompressedTexImage3DARB(GL12.GL_TEXTURE_3D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, height, depth, hasBorder ? 1 : 0, data);
                            } else {
                                GL12.glTexImage3D(GL12.GL_TEXTURE_3D, m,
                                        LwjglTextureUtil.getGLInternalFormat(texture.getTextureStoreFormat()),
                                        width, height, depth, hasBorder ? 1 : 0,
                                        LwjglTextureUtil.getGLPixelFormat(image.getDataFormat()),
                                        LwjglTextureUtil.getGLPixelDataType(image.getDataType()), data);
                            }
                            break;
                        }
                        pos += mipSizes[m];
                    }
                }
                if (data != null) {
                    data.clear();
                }
            }
        }
    }

    public static void apply(final TextureState state) {
        // ask for the current state record
        final RenderContext context = ContextManager.getCurrentContext();
        final ContextCapabilities caps = context.getCapabilities();
        final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(StateType.Texture);
        context.setCurrentState(StateType.Texture, state);

        if (state.isEnabled()) {

            Texture texture;
            Texture.Type type;
            TextureUnitRecord unitRecord;
            TextureRecord texRecord;

            final int glHint = LwjglTextureUtil.getPerspHint(state.getCorrectionType());
            if (!record.isValid() || record.hint != glHint) {
                // set up correction mode
                GL11.glHint(GL11.GL_PERSPECTIVE_CORRECTION_HINT, glHint);
                record.hint = glHint;
            }

            // loop through all available texture units...
            for (int i = 0; i < caps.getNumberOfTotalTextureUnits(); i++) {
                unitRecord = record.units[i];

                // grab a texture for this unit, if available
                texture = state.getTexture(i);

                // pull our texture id for this texture, for this context.
                int textureId = texture != null ? texture.getTextureIdForContext(context.getGlContextRep()) : 0;

                // check for invalid textures - ones that have no opengl id and
                // no image data
                if (texture != null && textureId == 0 && texture.getImage() == null) {
                    texture = null;
                }

                // null textures above fixed limit do not need to be disabled
                // since they are not really part of the pipeline.
                if (texture == null) {
                    if (i >= caps.getNumberOfFixedTextureUnits()) {
                        continue;
                    } else {
                        // a null texture indicates no texturing at this unit
                        // Disable texturing on this unit if enabled.
                        disableTexturing(unitRecord, record, i, caps);

                        if (i < state._keyCache.length) {
                            state._keyCache[i] = null;
                        }

                        // next texture!
                        continue;
                    }
                }

                type = texture.getType();

                // disable other texturing types for this unit, if enabled.
                disableTexturing(unitRecord, record, i, type, caps);

                // Time to bind the texture, so see if we need to load in image
                // data for this texture.
                if (textureId == 0) {
                    // texture not yet loaded.
                    // this will load and bind and set the records...
                    load(texture, i);
                    textureId = texture.getTextureIdForContext(context.getGlContextRep());
                    if (textureId == 0) {
                        continue;
                    }
                } else if (texture.isDirty(context.getGlContextRep())) {
                    update(texture, i);
                    textureId = texture.getTextureIdForContext(context.getGlContextRep());
                    if (textureId == 0) {
                        continue;
                    }
                } else {
                    // texture already exists in OpenGL, just bind it if needed
                    if (!unitRecord.isValid() || unitRecord.boundTexture != textureId) {
                        checkAndSetUnit(i, record, caps);
                        GL11.glBindTexture(getGLType(type), textureId);
                        if (Constants.stats) {
                            StatCollector.addStat(StatType.STAT_TEXTURE_BINDS, 1);
                        }
                        unitRecord.boundTexture = textureId;
                    }
                }

                // Use the Java Integer object for the getTextureRecord call to avoid
                // boxing/unboxing ints for map lookups.
                final Integer textureIdInteger = texture.getTextureIdForContextAsInteger(context.getGlContextRep());

                // Grab our record for this texture
                texRecord = record.getTextureRecord(textureIdInteger, texture.getType());

                // Set the keyCache value for this unit of this texture state
                // This is done so during state comparison we don't have to
                // spend a lot of time pulling out classes and finding field
                // data.
                state._keyCache[i] = texture.getTextureKey();

                // Some texture things only apply to fixed function pipeline
                if (i < caps.getNumberOfFixedTextureUnits()) {

                    // Enable 2D texturing on this unit if not enabled.
                    if (!unitRecord.isValid() || !unitRecord.enabled[type.ordinal()]) {
                        checkAndSetUnit(i, record, caps);
                        GL11.glEnable(getGLType(type));
                        unitRecord.enabled[type.ordinal()] = true;
                    }

                    // Set our blend color, if needed.
                    applyBlendColor(texture, unitRecord, i, record, caps);

                    // Set the texture environment mode if this unit isn't
                    // already set properly
                    applyEnvMode(texture.getApply(), unitRecord, i, record, caps);

                    // If our mode is combine, and we support multitexturing
                    // apply combine settings.
                    if (texture.getApply() == ApplyMode.Combine && caps.isMultitextureSupported()
                            && caps.isEnvCombineSupported()) {
                        applyCombineFactors(texture, unitRecord, i, record, caps);
                    }
                }

                // Other items only apply to textures below the frag unit limit
                if (i < caps.getNumberOfFragmentTextureUnits()) {

                    // texture specific params
                    applyFilter(texture, texRecord, i, record, caps);
                    applyWrap(texture, texRecord, i, record, caps);
                    applyShadow(texture, texRecord, i, record, caps);

                    // Set our border color, if needed.
                    applyBorderColor(texture, texRecord, i, record);

                    // all states have now been applied for a tex record, so we
                    // can safely make it valid
                    if (!texRecord.isValid()) {
                        texRecord.validate();
                    }

                }

                // Other items only apply to textures below the frag tex coord
                // unit limit
                if (i < caps.getNumberOfFragmentTexCoordUnits()) {

                    // Now time to play with texture matrices
                    // Determine which transforms to do.
                    applyTextureTransforms(texture, i, record, caps);

                    // Now let's look at automatic texture coordinate
                    // generation.
                    applyTexCoordGeneration(texture, unitRecord, i, record, caps);

                    // Set our texture lod bias, if needed.
                    applyLodBias(texture, unitRecord, i, record, caps);
                }

            }

        } else {
            // turn off texturing
            TextureUnitRecord unitRecord;

            if (caps.isMultitextureSupported()) {
                for (int i = 0; i < caps.getNumberOfFixedTextureUnits(); i++) {
                    unitRecord = record.units[i];
                    disableTexturing(unitRecord, record, i, caps);
                }
            } else {
                unitRecord = record.units[0];
                disableTexturing(unitRecord, record, 0, caps);
            }
        }

        if (!record.isValid()) {
            record.validate();
        }
    }

    private static void disableTexturing(final TextureUnitRecord unitRecord, final TextureStateRecord record,
            final int unit, final Type exceptedType, final ContextCapabilities caps) {
        if (exceptedType != Type.TwoDimensional) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.TwoDimensional.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(GL11.GL_TEXTURE_2D);
                unitRecord.enabled[Type.TwoDimensional.ordinal()] = false;
            }
        }

        if (exceptedType != Type.OneDimensional) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.OneDimensional.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(GL11.GL_TEXTURE_1D);
                unitRecord.enabled[Type.OneDimensional.ordinal()] = false;
            }
        }

        if (caps.isTexture3DSupported() && exceptedType != Type.ThreeDimensional) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.ThreeDimensional.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(GL12.GL_TEXTURE_3D);
                unitRecord.enabled[Type.ThreeDimensional.ordinal()] = false;
            }
        }

        if (caps.isTextureCubeMapSupported() && exceptedType != Type.CubeMap) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.CubeMap.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB);
                unitRecord.enabled[Type.CubeMap.ordinal()] = false;
            }
        }

    }

    private static void disableTexturing(final TextureUnitRecord unitRecord, final TextureStateRecord record,
            final int unit, final ContextCapabilities caps) {
        if (!unitRecord.isValid() || unitRecord.enabled[Type.TwoDimensional.ordinal()]) {
            // Check we are in the right unit
            checkAndSetUnit(unit, record, caps);
            GL11.glDisable(GL11.GL_TEXTURE_2D);
            unitRecord.enabled[Type.TwoDimensional.ordinal()] = false;
        }

        if (!unitRecord.isValid() || unitRecord.enabled[Type.OneDimensional.ordinal()]) {
            // Check we are in the right unit
            checkAndSetUnit(unit, record, caps);
            GL11.glDisable(GL11.GL_TEXTURE_1D);
            unitRecord.enabled[Type.OneDimensional.ordinal()] = false;
        }

        if (caps.isTexture3DSupported()) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.ThreeDimensional.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(GL12.GL_TEXTURE_3D);
                unitRecord.enabled[Type.ThreeDimensional.ordinal()] = false;
            }
        }

        if (caps.isTextureCubeMapSupported()) {
            if (!unitRecord.isValid() || unitRecord.enabled[Type.CubeMap.ordinal()]) {
                // Check we are in the right unit
                checkAndSetUnit(unit, record, caps);
                GL11.glDisable(ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB);
                unitRecord.enabled[Type.CubeMap.ordinal()] = false;
            }
        }

    }

    public static void applyCombineFactors(final Texture texture, final TextureUnitRecord unitRecord,
            final int unit, final TextureStateRecord record, final ContextCapabilities caps) {
        // check that this is a valid fixed function unit. glTexEnv is only
        // supported for unit < GL_MAX_TEXTURE_UNITS
        if (unit >= caps.getNumberOfFixedTextureUnits()) {
            return;
        }

        // first thing's first... if we are doing dot3 and don't
        // support it, disable this texture.
        boolean checked = false;
        if (!caps.isEnvDot3TextureCombineSupported() && (texture.getCombineFuncRGB() == CombinerFunctionRGB.Dot3RGB
                || texture.getCombineFuncRGB() == CombinerFunctionRGB.Dot3RGBA)) {

            // disable
            disableTexturing(unitRecord, record, unit, caps);

            // No need to continue
            return;
        }

        // Okay, now let's set our scales if we need to:
        // First RGB Combine scale
        if (!unitRecord.isValid() || unitRecord.envRGBScale != texture.getCombineScaleRGB()) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_RGB_SCALE_ARB,
                    texture.getCombineScaleRGB().floatValue());
            unitRecord.envRGBScale = texture.getCombineScaleRGB();
        }
        // Then Alpha Combine scale
        if (!unitRecord.isValid() || unitRecord.envAlphaScale != texture.getCombineScaleAlpha()) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_ALPHA_SCALE, texture.getCombineScaleAlpha().floatValue());
            unitRecord.envAlphaScale = texture.getCombineScaleAlpha();
        }

        // Time to set the RGB combines
        final CombinerFunctionRGB rgbCombineFunc = texture.getCombineFuncRGB();
        if (!unitRecord.isValid() || unitRecord.rgbCombineFunc != rgbCombineFunc) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_COMBINE_RGB_ARB,
                    LwjglTextureUtil.getGLCombineFuncRGB(rgbCombineFunc));
            unitRecord.rgbCombineFunc = rgbCombineFunc;
        }

        CombinerSource combSrcRGB = texture.getCombineSrc0RGB();
        if (!unitRecord.isValid() || unitRecord.combSrcRGB0 != combSrcRGB) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE0_RGB_ARB,
                    LwjglTextureUtil.getGLCombineSrc(combSrcRGB));
            unitRecord.combSrcRGB0 = combSrcRGB;
        }

        CombinerOperandRGB combOpRGB = texture.getCombineOp0RGB();
        if (!unitRecord.isValid() || unitRecord.combOpRGB0 != combOpRGB) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND0_RGB_ARB,
                    LwjglTextureUtil.getGLCombineOpRGB(combOpRGB));
            unitRecord.combOpRGB0 = combOpRGB;
        }

        // We only need to do Arg1 or Arg2 if we aren't in Replace mode
        if (rgbCombineFunc != CombinerFunctionRGB.Replace) {

            combSrcRGB = texture.getCombineSrc1RGB();
            if (!unitRecord.isValid() || unitRecord.combSrcRGB1 != combSrcRGB) {
                if (!checked) {
                    checkAndSetUnit(unit, record, caps);
                    checked = true;
                }
                GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE1_RGB_ARB,
                        LwjglTextureUtil.getGLCombineSrc(combSrcRGB));
                unitRecord.combSrcRGB1 = combSrcRGB;
            }

            combOpRGB = texture.getCombineOp1RGB();
            if (!unitRecord.isValid() || unitRecord.combOpRGB1 != combOpRGB) {
                if (!checked) {
                    checkAndSetUnit(unit, record, caps);
                    checked = true;
                }
                GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND1_RGB_ARB,
                        LwjglTextureUtil.getGLCombineOpRGB(combOpRGB));
                unitRecord.combOpRGB1 = combOpRGB;
            }

            // We only need to do Arg2 if we are in Interpolate mode
            if (rgbCombineFunc == CombinerFunctionRGB.Interpolate) {

                combSrcRGB = texture.getCombineSrc2RGB();
                if (!unitRecord.isValid() || unitRecord.combSrcRGB2 != combSrcRGB) {
                    if (!checked) {
                        checkAndSetUnit(unit, record, caps);
                        checked = true;
                    }
                    GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE2_RGB_ARB,
                            LwjglTextureUtil.getGLCombineSrc(combSrcRGB));
                    unitRecord.combSrcRGB2 = combSrcRGB;
                }

                combOpRGB = texture.getCombineOp2RGB();
                if (!unitRecord.isValid() || unitRecord.combOpRGB2 != combOpRGB) {
                    if (!checked) {
                        checkAndSetUnit(unit, record, caps);
                        checked = true;
                    }
                    GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND2_RGB_ARB,
                            LwjglTextureUtil.getGLCombineOpRGB(combOpRGB));
                    unitRecord.combOpRGB2 = combOpRGB;
                }

            }
        }

        // Now Alpha combines
        final CombinerFunctionAlpha alphaCombineFunc = texture.getCombineFuncAlpha();
        if (!unitRecord.isValid() || unitRecord.alphaCombineFunc != alphaCombineFunc) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_COMBINE_ALPHA_ARB,
                    LwjglTextureUtil.getGLCombineFuncAlpha(alphaCombineFunc));
            unitRecord.alphaCombineFunc = alphaCombineFunc;
        }

        CombinerSource combSrcAlpha = texture.getCombineSrc0Alpha();
        if (!unitRecord.isValid() || unitRecord.combSrcAlpha0 != combSrcAlpha) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE0_ALPHA_ARB,
                    LwjglTextureUtil.getGLCombineSrc(combSrcAlpha));
            unitRecord.combSrcAlpha0 = combSrcAlpha;
        }

        CombinerOperandAlpha combOpAlpha = texture.getCombineOp0Alpha();
        if (!unitRecord.isValid() || unitRecord.combOpAlpha0 != combOpAlpha) {
            if (!checked) {
                checkAndSetUnit(unit, record, caps);
                checked = true;
            }
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND0_ALPHA_ARB,
                    LwjglTextureUtil.getGLCombineOpAlpha(combOpAlpha));
            unitRecord.combOpAlpha0 = combOpAlpha;
        }

        // We only need to do Arg1 or Arg2 if we aren't in Replace mode
        if (alphaCombineFunc != CombinerFunctionAlpha.Replace) {

            combSrcAlpha = texture.getCombineSrc1Alpha();
            if (!unitRecord.isValid() || unitRecord.combSrcAlpha1 != combSrcAlpha) {
                if (!checked) {
                    checkAndSetUnit(unit, record, caps);
                    checked = true;
                }
                GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE1_ALPHA_ARB,
                        LwjglTextureUtil.getGLCombineSrc(combSrcAlpha));
                unitRecord.combSrcAlpha1 = combSrcAlpha;
            }

            combOpAlpha = texture.getCombineOp1Alpha();
            if (!unitRecord.isValid() || unitRecord.combOpAlpha1 != combOpAlpha) {
                if (!checked) {
                    checkAndSetUnit(unit, record, caps);
                    checked = true;
                }
                GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND1_ALPHA_ARB,
                        LwjglTextureUtil.getGLCombineOpAlpha(combOpAlpha));
                unitRecord.combOpAlpha1 = combOpAlpha;
            }

            // We only need to do Arg2 if we are in Interpolate mode
            if (alphaCombineFunc == CombinerFunctionAlpha.Interpolate) {

                combSrcAlpha = texture.getCombineSrc2Alpha();
                if (!unitRecord.isValid() || unitRecord.combSrcAlpha2 != combSrcAlpha) {
                    if (!checked) {
                        checkAndSetUnit(unit, record, caps);
                        checked = true;
                    }
                    GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_SOURCE2_ALPHA_ARB,
                            LwjglTextureUtil.getGLCombineSrc(combSrcAlpha));
                    unitRecord.combSrcAlpha2 = combSrcAlpha;
                }

                combOpAlpha = texture.getCombineOp2Alpha();
                if (!unitRecord.isValid() || unitRecord.combOpAlpha2 != combOpAlpha) {
                    if (!checked) {
                        checkAndSetUnit(unit, record, caps);
                        checked = true;
                    }
                    GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, ARBTextureEnvCombine.GL_OPERAND2_ALPHA_ARB,
                            LwjglTextureUtil.getGLCombineOpAlpha(combOpAlpha));
                    unitRecord.combOpAlpha2 = combOpAlpha;
                }
            }
        }
    }

    public static void applyEnvMode(final ApplyMode mode, final TextureUnitRecord unitRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        if (!unitRecord.isValid() || unitRecord.envMode != mode) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, LwjglTextureUtil.getGLEnvMode(mode));
            unitRecord.envMode = mode;
        }
    }

    public static void applyBlendColor(final Texture texture, final TextureUnitRecord unitRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final ReadOnlyColorRGBA texBlend = texture.getConstantColor();
        if (!unitRecord.isValid() || !unitRecord.blendColor.equals(texBlend)) {
            checkAndSetUnit(unit, record, caps);
            TextureRecord.colorBuffer.clear();
            TextureRecord.colorBuffer.put(texBlend.getRed()).put(texBlend.getGreen()).put(texBlend.getBlue())
                    .put(texBlend.getAlpha());
            TextureRecord.colorBuffer.rewind();
            GL11.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, TextureRecord.colorBuffer);
            unitRecord.blendColor.set(texBlend);
        }
    }

    public static void applyLodBias(final Texture texture, final TextureUnitRecord unitRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        if (caps.isTextureLodBiasSupported()) {
            final float bias = texture.getLodBias() < caps.getMaxLodBias() ? texture.getLodBias()
                    : caps.getMaxLodBias();
            if (!unitRecord.isValid() || unitRecord.lodBias != bias) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexEnvf(EXTTextureLODBias.GL_TEXTURE_FILTER_CONTROL_EXT,
                        EXTTextureLODBias.GL_TEXTURE_LOD_BIAS_EXT, bias);
                unitRecord.lodBias = bias;
            }
        }
    }

    public static void applyBorderColor(final Texture texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record) {
        final ReadOnlyColorRGBA texBorder = texture.getBorderColor();
        if (!texRecord.isValid() || !texRecord.borderColor.equals(texBorder)) {
            TextureRecord.colorBuffer.clear();
            TextureRecord.colorBuffer.put(texBorder.getRed()).put(texBorder.getGreen()).put(texBorder.getBlue())
                    .put(texBorder.getAlpha());
            TextureRecord.colorBuffer.rewind();
            GL11.glTexParameter(getGLType(texture.getType()), GL11.GL_TEXTURE_BORDER_COLOR,
                    TextureRecord.colorBuffer);
            texRecord.borderColor.set(texBorder);
        }
    }

    public static void applyTextureTransforms(final Texture texture, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final boolean needsReset = !record.units[unit].identityMatrix;

        // Should we apply the transform?
        final boolean doTrans = !texture.getTextureMatrix().isIdentity();

        // Now do them.
        final RendererRecord matRecord = ContextManager.getCurrentContext().getRendererRecord();
        if (doTrans) {
            checkAndSetUnit(unit, record, caps);
            LwjglRendererUtil.switchMode(matRecord, GL11.GL_TEXTURE);

            record.tmp_matrixBuffer.rewind();
            texture.getTextureMatrix().toDoubleBuffer(record.tmp_matrixBuffer, true);
            record.tmp_matrixBuffer.rewind();
            GL11.glLoadMatrix(record.tmp_matrixBuffer);

            record.units[unit].identityMatrix = false;
        } else if (needsReset) {
            checkAndSetUnit(unit, record, caps);
            LwjglRendererUtil.switchMode(matRecord, GL11.GL_TEXTURE);
            GL11.glLoadIdentity();
            record.units[unit].identityMatrix = true;
        }
        // Switch back to the modelview matrix for further operations
        LwjglRendererUtil.switchMode(matRecord, GL11.GL_MODELVIEW);
    }

    public static void applyTexCoordGeneration(final Texture texture, final TextureUnitRecord unitRecord,
            final int unit, final TextureStateRecord record, final ContextCapabilities caps) {
        switch (texture.getEnvironmentalMapMode()) {
        case None:
            // No coordinate generation
            setTextureGen(unitRecord, unit, record, caps, false, false, false, false);
            break;
        case SphereMap:
            // generate spherical texture coordinates
            if (!unitRecord.isValid() || unitRecord.textureGenSMode != GL11.GL_SPHERE_MAP) {
                checkAndSetUnit(unit, record, caps);

                GL11.glTexGeni(GL11.GL_S, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_SPHERE_MAP);
                unitRecord.textureGenSMode = GL11.GL_SPHERE_MAP;

                GL11.glTexGeni(GL11.GL_T, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_SPHERE_MAP);
                unitRecord.textureGenTMode = GL11.GL_SPHERE_MAP;
            }

            setTextureGen(unitRecord, unit, record, caps, true, true, false, false);
            break;
        case NormalMap:
            // generate normals based texture coordinates
            if (!unitRecord.isValid() || unitRecord.textureGenSMode != ARBTextureCubeMap.GL_NORMAL_MAP_ARB) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexGeni(GL11.GL_S, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_NORMAL_MAP_ARB);
                unitRecord.textureGenSMode = ARBTextureCubeMap.GL_NORMAL_MAP_ARB;

                GL11.glTexGeni(GL11.GL_T, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_NORMAL_MAP_ARB);
                unitRecord.textureGenTMode = ARBTextureCubeMap.GL_NORMAL_MAP_ARB;

                GL11.glTexGeni(GL11.GL_R, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_NORMAL_MAP_ARB);
                unitRecord.textureGenRMode = ARBTextureCubeMap.GL_NORMAL_MAP_ARB;
            }

            setTextureGen(unitRecord, unit, record, caps, true, true, true, false);
            break;
        case ReflectionMap:
            // generate reflection texture coordinates
            if (!unitRecord.isValid() || unitRecord.textureGenSMode != ARBTextureCubeMap.GL_REFLECTION_MAP_ARB) {
                checkAndSetUnit(unit, record, caps);

                GL11.glTexGeni(GL11.GL_S, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_REFLECTION_MAP_ARB);
                unitRecord.textureGenSMode = ARBTextureCubeMap.GL_REFLECTION_MAP_ARB;

                GL11.glTexGeni(GL11.GL_T, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_REFLECTION_MAP_ARB);
                unitRecord.textureGenTMode = ARBTextureCubeMap.GL_REFLECTION_MAP_ARB;

                GL11.glTexGeni(GL11.GL_R, GL11.GL_TEXTURE_GEN_MODE, ARBTextureCubeMap.GL_REFLECTION_MAP_ARB);
                unitRecord.textureGenRMode = ARBTextureCubeMap.GL_REFLECTION_MAP_ARB;
            }

            setTextureGen(unitRecord, unit, record, caps, true, true, true, false);
            break;
        case EyeLinear:
            // do here because we don't check planes
            checkAndSetUnit(unit, record, caps);

            // generate eye linear texture coordinates
            if (!unitRecord.isValid() || unitRecord.textureGenSMode != GL11.GL_EYE_LINEAR) {
                GL11.glTexGeni(GL11.GL_S, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_EYE_LINEAR);
                unitRecord.textureGenSMode = GL11.GL_EYE_LINEAR;

                GL11.glTexGeni(GL11.GL_T, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_EYE_LINEAR);
                unitRecord.textureGenTMode = GL11.GL_EYE_LINEAR;

                GL11.glTexGeni(GL11.GL_R, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_EYE_LINEAR);
                unitRecord.textureGenRMode = GL11.GL_EYE_LINEAR;

                GL11.glTexGeni(GL11.GL_Q, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_EYE_LINEAR);
                unitRecord.textureGenQMode = GL11.GL_EYE_LINEAR;
            }

            record.prepPlane(texture.getEnvPlaneS(), TextureStateRecord.DEFAULT_S_PLANE);
            GL11.glTexGen(GL11.GL_S, GL11.GL_EYE_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneT(), TextureStateRecord.DEFAULT_T_PLANE);
            GL11.glTexGen(GL11.GL_T, GL11.GL_EYE_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneR(), TextureStateRecord.DEFAULT_R_PLANE);
            GL11.glTexGen(GL11.GL_R, GL11.GL_EYE_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneQ(), TextureStateRecord.DEFAULT_Q_PLANE);
            GL11.glTexGen(GL11.GL_Q, GL11.GL_EYE_PLANE, record.plane);

            setTextureGen(unitRecord, unit, record, caps, true, true, true, true);
            break;
        case ObjectLinear:
            // do here because we don't check planes
            checkAndSetUnit(unit, record, caps);

            // generate object linear texture coordinates
            if (!unitRecord.isValid() || unitRecord.textureGenSMode != GL11.GL_OBJECT_LINEAR) {
                GL11.glTexGeni(GL11.GL_S, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_OBJECT_LINEAR);
                unitRecord.textureGenSMode = GL11.GL_OBJECT_LINEAR;

                GL11.glTexGeni(GL11.GL_T, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_OBJECT_LINEAR);
                unitRecord.textureGenTMode = GL11.GL_OBJECT_LINEAR;

                GL11.glTexGeni(GL11.GL_R, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_OBJECT_LINEAR);
                unitRecord.textureGenRMode = GL11.GL_OBJECT_LINEAR;

                GL11.glTexGeni(GL11.GL_Q, GL11.GL_TEXTURE_GEN_MODE, GL11.GL_OBJECT_LINEAR);
                unitRecord.textureGenQMode = GL11.GL_OBJECT_LINEAR;
            }

            record.prepPlane(texture.getEnvPlaneS(), TextureStateRecord.DEFAULT_S_PLANE);
            GL11.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneT(), TextureStateRecord.DEFAULT_T_PLANE);
            GL11.glTexGen(GL11.GL_T, GL11.GL_OBJECT_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneR(), TextureStateRecord.DEFAULT_R_PLANE);
            GL11.glTexGen(GL11.GL_R, GL11.GL_OBJECT_PLANE, record.plane);
            record.prepPlane(texture.getEnvPlaneQ(), TextureStateRecord.DEFAULT_Q_PLANE);
            GL11.glTexGen(GL11.GL_Q, GL11.GL_OBJECT_PLANE, record.plane);

            setTextureGen(unitRecord, unit, record, caps, true, true, true, true);
            break;
        }
    }

    private static void setTextureGen(final TextureUnitRecord unitRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps, final boolean genS, final boolean genT,
            final boolean genR, final boolean genQ) {
        if (!unitRecord.isValid()) {
            checkAndSetUnit(unit, record, caps);

            if (genS) {
                GL11.glEnable(GL11.GL_TEXTURE_GEN_S);
            } else {
                GL11.glDisable(GL11.GL_TEXTURE_GEN_S);
            }
            if (genT) {
                GL11.glEnable(GL11.GL_TEXTURE_GEN_T);
            } else {
                GL11.glDisable(GL11.GL_TEXTURE_GEN_T);
            }
            if (genR) {
                GL11.glEnable(GL11.GL_TEXTURE_GEN_R);
            } else {
                GL11.glDisable(GL11.GL_TEXTURE_GEN_R);
            }
            if (genQ) {
                GL11.glEnable(GL11.GL_TEXTURE_GEN_Q);
            } else {
                GL11.glDisable(GL11.GL_TEXTURE_GEN_Q);
            }
        } else {
            if (genS != unitRecord.textureGenS) {
                checkAndSetUnit(unit, record, caps);
                if (genS) {
                    GL11.glEnable(GL11.GL_TEXTURE_GEN_S);
                } else {
                    GL11.glDisable(GL11.GL_TEXTURE_GEN_S);
                }
            }
            if (genT != unitRecord.textureGenT) {
                checkAndSetUnit(unit, record, caps);
                if (genT) {
                    GL11.glEnable(GL11.GL_TEXTURE_GEN_T);
                } else {
                    GL11.glDisable(GL11.GL_TEXTURE_GEN_T);
                }
            }
            if (genR != unitRecord.textureGenR) {
                checkAndSetUnit(unit, record, caps);
                if (genR) {
                    GL11.glEnable(GL11.GL_TEXTURE_GEN_R);
                } else {
                    GL11.glDisable(GL11.GL_TEXTURE_GEN_R);
                }
            }
            if (genQ != unitRecord.textureGenQ) {
                checkAndSetUnit(unit, record, caps);
                if (genQ) {
                    GL11.glEnable(GL11.GL_TEXTURE_GEN_Q);
                } else {
                    GL11.glDisable(GL11.GL_TEXTURE_GEN_Q);
                }
            }
        }

        unitRecord.textureGenS = genS;
        unitRecord.textureGenT = genT;
        unitRecord.textureGenR = genR;
        unitRecord.textureGenQ = genQ;
    }

    // If we support multtexturing, specify the unit we are affecting.
    public static void checkAndSetUnit(final int unit, final TextureStateRecord record,
            final ContextCapabilities caps) {
        // No need to worry about valid record, since invalidate sets record's
        // currentUnit to -1.
        if (record.currentUnit != unit) {
            if (unit >= caps.getNumberOfTotalTextureUnits() || !caps.isMultitextureSupported() || unit < 0) {
                // ignore this request as it is not valid for the user's hardware.
                return;
            }
            ARBMultitexture.glActiveTextureARB(ARBMultitexture.GL_TEXTURE0_ARB + unit);
            record.currentUnit = unit;
        }
    }

    /**
     * Check if the filter settings of this particular texture have been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the texture in gl
     * @param record
     */
    public static void applyShadow(final Texture texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final Type type = texture.getType();

        if (caps.isDepthTextureSupported()) {
            final int depthMode = LwjglTextureUtil.getGLDepthTextureMode(texture.getDepthMode());
            // set up magnification filter
            if (!texRecord.isValid() || texRecord.depthTextureMode != depthMode) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexParameteri(getGLType(type), ARBDepthTexture.GL_DEPTH_TEXTURE_MODE_ARB, depthMode);
                texRecord.depthTextureMode = depthMode;
            }
        }

        if (caps.isARBShadowSupported()) {
            final int depthCompareMode = LwjglTextureUtil
                    .getGLDepthTextureCompareMode(texture.getDepthCompareMode());
            // set up magnification filter
            if (!texRecord.isValid() || texRecord.depthTextureCompareMode != depthCompareMode) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexParameteri(getGLType(type), ARBShadow.GL_TEXTURE_COMPARE_MODE_ARB, depthCompareMode);
                texRecord.depthTextureCompareMode = depthCompareMode;
            }

            final int depthCompareFunc = LwjglTextureUtil
                    .getGLDepthTextureCompareFunc(texture.getDepthCompareFunc());
            // set up magnification filter
            if (!texRecord.isValid() || texRecord.depthTextureCompareFunc != depthCompareFunc) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexParameteri(getGLType(type), ARBShadow.GL_TEXTURE_COMPARE_FUNC_ARB, depthCompareFunc);
                texRecord.depthTextureCompareFunc = depthCompareFunc;
            }
        }
    }

    /**
     * Check if the filter settings of this particular texture have been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the texture in gl
     * @param record
     */
    public static void applyFilter(final Texture texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final Type type = texture.getType();

        final int magFilter = LwjglTextureUtil.getGLMagFilter(texture.getMagnificationFilter());
        // set up magnification filter
        if (!texRecord.isValid() || texRecord.magFilter != magFilter) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(getGLType(type), GL11.GL_TEXTURE_MAG_FILTER, magFilter);
            texRecord.magFilter = magFilter;
        }

        final int minFilter = LwjglTextureUtil.getGLMinFilter(texture.getMinificationFilter());
        // set up mipmap filter
        if (!texRecord.isValid() || texRecord.minFilter != minFilter) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(getGLType(type), GL11.GL_TEXTURE_MIN_FILTER, minFilter);
            texRecord.minFilter = minFilter;
        }

        // set up aniso filter
        if (caps.isAnisoSupported()) {
            float aniso = texture.getAnisotropicFilterPercent() * (caps.getMaxAnisotropic() - 1.0f);
            aniso += 1.0f;
            if (!texRecord.isValid() || (texRecord.anisoLevel - aniso > MathUtils.ZERO_TOLERANCE)) {
                checkAndSetUnit(unit, record, caps);
                GL11.glTexParameterf(getGLType(type), EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT,
                        aniso);
                texRecord.anisoLevel = aniso;
            }
        }
    }

    /**
     * Check if the wrap mode of this particular texture has been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the unit in gl
     * @param record
     */
    public static void applyWrap(final Texture3D texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        if (!caps.isTexture3DSupported()) {
            return;
        }

        final int wrapS = getGLWrap(texture.getWrap(WrapAxis.S), caps);
        final int wrapT = getGLWrap(texture.getWrap(WrapAxis.T), caps);
        final int wrapR = getGLWrap(texture.getWrap(WrapAxis.R), caps);

        if (!texRecord.isValid() || texRecord.wrapS != wrapS) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_WRAP_S, wrapS);
            texRecord.wrapS = wrapS;
        }
        if (!texRecord.isValid() || texRecord.wrapT != wrapT) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_WRAP_T, wrapT);
            texRecord.wrapT = wrapT;
        }
        if (!texRecord.isValid() || texRecord.wrapR != wrapR) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL12.GL_TEXTURE_WRAP_R, wrapR);
            texRecord.wrapR = wrapR;
        }

    }

    /**
     * Check if the wrap mode of this particular texture has been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the unit in gl
     * @param record
     */
    public static void applyWrap(final Texture1D texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final int wrapS = getGLWrap(texture.getWrap(WrapAxis.S), caps);

        if (!texRecord.isValid() || texRecord.wrapS != wrapS) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_WRAP_S, wrapS);
            texRecord.wrapS = wrapS;
        }
    }

    /**
     * Check if the wrap mode of this particular texture has been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the unit in gl
     * @param record
     */
    public static void applyWrap(final Texture texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        if (texture instanceof Texture2D) {
            applyWrap((Texture2D) texture, texRecord, unit, record, caps);
        } else if (texture instanceof Texture1D) {
            applyWrap((Texture1D) texture, texRecord, unit, record, caps);
        } else if (texture instanceof Texture3D) {
            applyWrap((Texture3D) texture, texRecord, unit, record, caps);
        } else if (texture instanceof TextureCubeMap) {
            applyWrap((TextureCubeMap) texture, texRecord, unit, record, caps);
        }
    }

    /**
     * Check if the wrap mode of this particular texture has been changed and apply as needed.
     * 
     * @param texture
     *            our texture object
     * @param texRecord
     *            our record of the last state of the unit in gl
     * @param record
     */
    public static void applyWrap(final Texture2D texture, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        final int wrapS = getGLWrap(texture.getWrap(WrapAxis.S), caps);
        final int wrapT = getGLWrap(texture.getWrap(WrapAxis.T), caps);

        if (!texRecord.isValid() || texRecord.wrapS != wrapS) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrapS);
            texRecord.wrapS = wrapS;
        }
        if (!texRecord.isValid() || texRecord.wrapT != wrapT) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrapT);
            texRecord.wrapT = wrapT;
        }

    }

    /**
     * Check if the wrap mode of this particular texture has been changed and apply as needed.
     * 
     * @param cubeMap
     *            our texture object
     * @param texRecord
     *            our record of the last state of the unit in gl
     * @param record
     */
    public static void applyWrap(final TextureCubeMap cubeMap, final TextureRecord texRecord, final int unit,
            final TextureStateRecord record, final ContextCapabilities caps) {
        if (!caps.isTextureCubeMapSupported()) {
            return;
        }

        final int wrapS = getGLWrap(cubeMap.getWrap(WrapAxis.S), caps);
        final int wrapT = getGLWrap(cubeMap.getWrap(WrapAxis.T), caps);
        final int wrapR = getGLWrap(cubeMap.getWrap(WrapAxis.R), caps);

        if (!texRecord.isValid() || texRecord.wrapS != wrapS) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB, GL11.GL_TEXTURE_WRAP_S, wrapS);
            texRecord.wrapS = wrapS;
        }
        if (!texRecord.isValid() || texRecord.wrapT != wrapT) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB, GL11.GL_TEXTURE_WRAP_T, wrapT);
            texRecord.wrapT = wrapT;
        }
        if (!texRecord.isValid() || texRecord.wrapR != wrapR) {
            checkAndSetUnit(unit, record, caps);
            GL11.glTexParameteri(ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB, GL12.GL_TEXTURE_WRAP_R, wrapR);
            texRecord.wrapR = wrapR;
        }
    }

    public static void deleteTexture(final Texture texture) {
        // ask for the current state record
        final RenderContext context = ContextManager.getCurrentContext();
        final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(StateType.Texture);

        final Integer id = texture.getTextureIdForContextAsInteger(context.getGlContextRep());
        if (id.intValue() == 0) {
            // Not on card... return.
            return;
        }

        final IntBuffer idBuffer = BufferUtils.createIntBuffer(1);
        idBuffer.clear();
        idBuffer.put(id.intValue());
        idBuffer.rewind();
        GL11.glDeleteTextures(idBuffer);
        record.removeTextureRecord(id);
        texture.removeFromIdCache(context.getGlContextRep());
    }

    public static void deleteTextureIds(final Collection<Integer> ids) {
        // ask for the current state record
        final RenderContext context = ContextManager.getCurrentContext();
        final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(StateType.Texture);

        final IntBuffer idBuffer = BufferUtils.createIntBuffer(ids.size());
        idBuffer.clear();
        for (final Integer i : ids) {
            if (i != null) {
                idBuffer.put(i);
                record.removeTextureRecord(i);
            }
        }
        idBuffer.flip();
        if (idBuffer.remaining() > 0) {
            GL11.glDeleteTextures(idBuffer);
        }
    }

    /**
     * Useful for external lwjgl based classes that need to safely set the current texture.
     */
    public static void doTextureBind(final Texture texture, final int unit, final boolean invalidateState) {
        // ask for the current state record
        final RenderContext context = ContextManager.getCurrentContext();
        final ContextCapabilities caps = context.getCapabilities();
        final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(StateType.Texture);
        if (invalidateState) {
            // Set this to null because no current state really matches anymore
            context.setCurrentState(StateType.Texture, null);
        }
        checkAndSetUnit(unit, record, caps);

        final int id = texture.getTextureIdForContext(context.getGlContextRep());
        GL11.glBindTexture(getGLType(texture.getType()), id);
        if (Constants.stats) {
            StatCollector.addStat(StatType.STAT_TEXTURE_BINDS, 1);
        }
        if (record != null) {
            record.units[unit].boundTexture = id;
        }
    }

    public static int getGLType(final Type type) {
        switch (type) {
        case TwoDimensional:
            return GL11.GL_TEXTURE_2D;
        case OneDimensional:
            return GL11.GL_TEXTURE_1D;
        case ThreeDimensional:
            return GL12.GL_TEXTURE_3D;
        case CubeMap:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_ARB;
        }
        throw new IllegalArgumentException("invalid texture type: " + type);
    }

    public static int getGLCubeMapFace(final TextureCubeMap.Face face) {
        switch (face) {
        case PositiveX:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
        case NegativeX:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
        case PositiveY:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
        case NegativeY:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
        case PositiveZ:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
        case NegativeZ:
            return ARBTextureCubeMap.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;
        }
        throw new IllegalArgumentException("invalid cubemap face: " + face);
    }

    public static int getGLWrap(final WrapMode wrap, final ContextCapabilities caps) {
        switch (wrap) {
        case Repeat:
            return GL11.GL_REPEAT;
        case MirroredRepeat:
            if (caps.isTextureMirroredRepeatSupported()) {
                return ARBTextureMirroredRepeat.GL_MIRRORED_REPEAT_ARB;
            } else {
                return GL11.GL_REPEAT;
            }
        case MirrorClamp:
            if (caps.isTextureMirrorClampSupported()) {
                return EXTTextureMirrorClamp.GL_MIRROR_CLAMP_EXT;
            }
            // FALLS THROUGH
        case Clamp:
            return GL11.GL_CLAMP;
        case MirrorBorderClamp:
            if (caps.isTextureMirrorBorderClampSupported()) {
                return EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_BORDER_EXT;
            }
            // FALLS THROUGH
        case BorderClamp:
            if (caps.isTextureBorderClampSupported()) {
                return ARBTextureBorderClamp.GL_CLAMP_TO_BORDER_ARB;
            } else {
                return GL11.GL_CLAMP;
            }
        case MirrorEdgeClamp:
            if (caps.isTextureMirrorEdgeClampSupported()) {
                return EXTTextureMirrorClamp.GL_MIRROR_CLAMP_TO_EDGE_EXT;
            }
            // FALLS THROUGH
        case EdgeClamp:
            if (caps.isTextureEdgeClampSupported()) {
                return GL12.GL_CLAMP_TO_EDGE;
            } else {
                return GL11.GL_CLAMP;
            }
        }
        throw new IllegalArgumentException("invalid WrapMode type: " + wrap);
    }
}