- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

# nv_dds.cpp

    //
    // Copyright (C) 2002-2003 NVIDIA
    //
    // File: nv_dds.cpp
    //-
    // ==========================================================================
    // Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
    //
    // Use of this software is subject to the terms of the Autodesk
    // license agreement provided at the time of installation or download,
    // or which otherwise accompanies this software in either electronic
    // or hard copy form.
    // ==========================================================================
    //+

    //
    // Description:
    //
    // Loads DDS images (DXTC1, DXTC3, DXTC5, RGB (888, 888X), and RGBA (8888) are
    // supported) for use in OpenGL. Image is flipped when its loaded as DX images
    // are stored with different coordinate system. If file has mipmaps and/or
    // cubemaps then these are loaded as well. Volume textures can be loaded as
    // well but they must be uncompressed.
    //
    // When multiple textures are loaded (i.e a volume or cubemap texture),
    // additional faces can be accessed using the array operator.
    //
    // The mipmaps for each face are also stored in a list and can be accessed like
    // so: image.get_mipmap() (which accesses the first mipmap of the first
    // image). To get the number of mipmaps call the get_num_mipmaps function for
    // a given texture.
    //
    // Call the is_volume() or is_cubemap() function to check that a loaded image
    // is a volume or cubemap texture respectively. If a volume texture is loaded
    // then the get_depth() function should return a number greater than 1.
    // Mipmapped volume textures and DXTC compressed volume textures are supported.
    //
    //
    // Update: 6/11/2002
    //
    // Added some convenience functions to handle uploading textures to OpenGL. The
    // following functions have been added:
    //
    //     bool upload_texture1D();
    //     bool upload_texture2D(int imageIndex = 0, GLenum target = GL_TEXTURE_2D);
    //     bool upload_textureRectangle();
    //     bool upload_texture3D();
    //     bool upload_textureCubemap();
    //
    // See function implementation below for instructions/comments on using each
    // function.
    //
    // The open function has also been updated to take an optional second parameter
    // specifying whether the image should be flipped on load. This defaults to
    // true.
    //
    // Sample usage
    //
    // Loading a compressed texture:
    //
    // CDDSImage image;
    // GLuint texobj;
    //
    // image.load("compressed.dds");
    //
    // glGenTextures(1, &texobj);
    // glEnable(GL_TEXTURE_2D);
    // glBindTexture(GL_TEXTURE_2D, texobj);
    //
    // glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, image.get_format(),
    //     image.get_width(), image.get_height(), 0, image.get_size(),
    //     image);
    //
    // for (int i = 0; i < image.get_num_mipmaps(); i++)
    // {
    //     glCompressedTexImage2DARB(GL_TEXTURE_2D, i+1, image.get_format(),
    //         image.get_mipmap(i).get_width(), image.get_mipmap(i).get_height(), 0,
    //         image.get_mipmap(i).get_size(), image.get_mipmap(i));
    // }
    //
    // Loading an uncompressed texture:
    //
    // CDDSImage image;
    // GLuint texobj;
    //
    // image.load("uncompressed.dds");
    //
    // glGenTextures(1, &texobj);
    // glEnable(GL_TEXTURE_2D);
    // glBindTexture(GL_TEXTURE_2D, texobj);
    //
    // glTexImage2D(GL_TEXTURE_2D, 0, image.get_components(), image.get_width(),
    //     image.get_height(), 0, image.get_format(), GL_UNSIGNED_BYTE, image);
    //
    // for (int i = 0; i < image.get_num_mipmaps(); i++)
    // {
    //     glTexImage2D(GL_TEXTURE_2D, i+1, image.get_components(),
    //         image.get_mipmap(i).get_width(), image.get_mipmap(i).get_height(),
    //         0, image.get_format(), GL_UNSIGNED_BYTE, image.get_mipmap(i));
    // }
    //
    //
    // Loading an uncompressed cubemap texture:
    //
    // CDDSImage image;
    // GLuint texobj;
    // GLenum target;
    //
    // image.load("cubemap.dds");
    //
    // glGenTextures(1, &texobj);
    // glEnable(GL_TEXTURE_CUBE_MAP_ARB);
    // glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, texobj);
    //
    // for (int n = 0; n < 6; n++)
    // {
    //     target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+n;
    //
    //     glTexImage2D(target, 0, image.get_components(), image[n].get_width(),
    //         image[n].get_height(), 0, image.get_format(), GL_UNSIGNED_BYTE,
    //         image[n]);
    //
    //     for (int i = 0; i < image[n].get_num_mipmaps(); i++)
    //     {
    //         glTexImage2D(target, i+1, image.get_components(),
    //             image[n].get_mipmap(i).get_width(),
    //             image[n].get_mipmap(i).get_height(), 0,
    //             image.get_format(), GL_UNSIGNED_BYTE, image[n].get_mipmap(i));
    //     }
    // }
    //
    //
    // Loading a volume texture:
    //
    // CDDSImage image;
    // GLuint texobj;
    //
    // image.load("volume.dds");
    //
    // glGenTextures(1, &texobj);
    // glEnable(GL_TEXTURE_3D);
    // glBindTexture(GL_TEXTURE_3D, texobj);
    //
    // PFNGLTEXIMAGE3DPROC glTexImage3D;
    // glTexImage3D(GL_TEXTURE_3D, 0, image.get_components(), image.get_width(),
    //     image.get_height(), image.get_depth(), 0, image.get_format(),
    //     GL_UNSIGNED_BYTE, image);
    //
    // for (int i = 0; i < image.get_num_mipmaps(); i++)
    // {
    //     glTexImage3D(GL_TEXTURE_3D, i+1, image.get_components(),
    //         image[0].get_mipmap(i).get_width(),
    //         image[0].get_mipmap(i).get_height(),
    //         image[0].get_mipmap(i).get_depth(), 0, image.get_format(),
    //         GL_UNSIGNED_BYTE, image[0].get_mipmap(i));
    // }

    #if defined(WIN32)
    #  include <windows.h>
    #  define GET_EXT_POINTER(name, type) \
          name = (type)wglGetProcAddress(#name)

    #elif defined(LINUX)
    #  include <GL/glx.h>

    extern "C" void (*glXGetProcAddressARB(const GLubyte *procName))( void );
    //extern "C" void (*glXGetProcAddress(const GLubyte *procName))( void );

    //#if defined(Bits64_)
    //# define useGetProcAddress ::glXGetProcAddress
    //#else
    #   define useGetProcAddress ::glXGetProcAddressARB
    //#endif

    #  define GET_EXT_POINTER(name, type) \
          name = (type)useGetProcAddress((const GLubyte*)#name)

    #else // MACOS
    #if !OSMac_X86_64_
    #   include <AGL/agl.h>
    #endif
    #   define GET_EXT_POINTER(glname, gltype)
    #   include <GL/glext.h>
    #endif

    #if defined(WIN32) || defined(LINUX)
    #include <GL/gl.h>
    #include <GL/glext.h>
    #endif

    #include <stdio.h>
    #include <string.h>
    #include <assert.h>
    #include "nv_dds.h"

    using namespace std;
    using namespace nv_dds;

    // static function pointers for uploading 3D textures and compressed 1D, 2D
    // and 3D textures.
    #if defined(WIN32) || defined(LINUX)
    PFNGLTEXIMAGE3DEXTPROC CDDSImage::glTexImage3D = NULL;
    PFNGLCOMPRESSEDTEXIMAGE1DARBPROC CDDSImage::glCompressedTexImage1DARB = NULL;
    PFNGLCOMPRESSEDTEXIMAGE2DARBPROC CDDSImage::glCompressedTexImage2DARB = NULL;
    PFNGLCOMPRESSEDTEXIMAGE3DARBPROC CDDSImage::glCompressedTexImage3DARB = NULL;
    #endif

    // CDDSImage public functions

    // default constructor
    CDDSImage::CDDSImage()
      : format(0),
        components(0),
        compressed(false),
        cubemap(false),
        volume(false),
        valid(false)
    {
    }

    CDDSImage::~CDDSImage()
    {
    }

    // loads DDS image
    //
    // filename - fully qualified name of DDS image
    // flipImage - specifies whether image is flipped on load, default is true
    bool CDDSImage::load(string filename, bool flipImage)
    {
        DDS_HEADER ddsh;
        char filecode[4];
        FILE *fp;
        int width, height, depth;
        int (CDDSImage::*sizefunc)(int, int);

        // clear any previously loaded images
        clear();

        // open file
        fp = fopen(filename.data(), "rb");
        if (fp == NULL)
            return false;

        // read in file marker, make sure its a DDS file
        fread(filecode, 1, 4, fp);
        if (strncmp(filecode, "DDS ", 4) != 0)
        {
            fclose(fp);
            return false;
        }

        // read in DDS header
        fread(&ddsh, sizeof(ddsh), 1, fp);

        swap_endian(&ddsh.dwSize);
        swap_endian(&ddsh.dwFlags);
        swap_endian(&ddsh.dwHeight);
        swap_endian(&ddsh.dwWidth);
        swap_endian(&ddsh.dwPitchOrLinearSize);
        swap_endian(&ddsh.dwMipMapCount);
        swap_endian(&ddsh.ddspf.dwSize);
        swap_endian(&ddsh.ddspf.dwFlags);
        swap_endian(&ddsh.ddspf.dwFourCC);
        swap_endian(&ddsh.ddspf.dwRGBBitCount);
        swap_endian(&ddsh.dwCaps1);
        swap_endian(&ddsh.dwCaps2);

        // check if image is a cubempa
        if (ddsh.dwCaps2 & DDS_CUBEMAP)
            cubemap = true;

        // check if image is a volume texture
        if ((ddsh.dwCaps2 & DDS_VOLUME) && (ddsh.dwDepth > 0))
            volume = true;

        // figure out what the image format is
        if (ddsh.ddspf.dwFlags & DDS_FOURCC)
        {
            switch(ddsh.ddspf.dwFourCC)
            {
                case FOURCC_DXT1:
                    format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
                    components = 3;
                    compressed = true;
                    break;
                case FOURCC_DXT3:
                    format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
                    components = 4;
                    compressed = true;
                    break;
                case FOURCC_DXT5:
                    format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
                    components = 4;
                    compressed = true;
                    break;
                default:
                    fclose(fp);
                    return false;
            }
        }
        else if (ddsh.ddspf.dwFlags == DDS_RGBA && ddsh.ddspf.dwRGBBitCount == 32)
        {
            format = GL_BGRA_EXT;
            compressed = false;
            components = 4;
        }
        else if (ddsh.ddspf.dwFlags == DDS_RGB  && ddsh.ddspf.dwRGBBitCount == 32)
        {
            format = GL_BGRA_EXT;
            compressed = false;
            components = 4;
        }
        else if (ddsh.ddspf.dwFlags == DDS_RGB  && ddsh.ddspf.dwRGBBitCount == 24)
        {
            format = GL_BGR_EXT;
            compressed = false;
            components = 3;
        }
        else if (ddsh.ddspf.dwRGBBitCount == 8)
        {
            format = GL_LUMINANCE;
            compressed = false;
            components = 1;
        }
        else
        {
            fclose(fp);
            return false;
        }

        // store primary surface width/height/depth
        width = ddsh.dwWidth;
        height = ddsh.dwHeight;
        depth = clamp_size(ddsh.dwDepth);   // set to 1 if 0

        // use correct size calculation function depending on whether image is
        // compressed
        sizefunc = (compressed ? &CDDSImage::size_dxtc : &CDDSImage::size_rgb);

        // load all surfaces for the image (6 surfaces for cubemaps)
        for (int n = 0; n < (cubemap ? 6 : 1); n++)
        {
            int size;

            // calculate surface size
            size = (this->*sizefunc)(width, height)*depth;

            // load surface
            CTexture img(width, height, depth, size);
            fread(img, 1, img.size, fp);

            align_memory(&img);

            if (!cubemap && flipImage)
                flip(img, img.width, img.height, img.depth, img.size);

            int w = clamp_size(width >> 1);
            int h = clamp_size(height >> 1);
            int d = clamp_size(depth >> 1);

            // store number of mipmaps
            int numMipmaps = ddsh.dwMipMapCount;

            // number of mipmaps in file includes main surface so decrease count
            // by one
            if (numMipmaps != 0)
                numMipmaps--;

            // load all mipmaps for current surface
            for (int i = 0; i < numMipmaps && (w || h); i++)
            {
                // calculate mipmap size
                size = (this->*sizefunc)(w, h)*d;

                CSurface mipmap(w, h, d, size);
                fread(mipmap, 1, mipmap.size, fp);

                if (!cubemap && flipImage)
                {
                    flip(mipmap, mipmap.width, mipmap.height, mipmap.depth,
                        mipmap.size);
                }

                img.mipmaps.push_back(mipmap);

                // shrink to next power of 2
                w = clamp_size(w >> 1);
                h = clamp_size(h >> 1);
                d = clamp_size(d >> 1);
            }

            images.push_back(img);
        }

        // swap cubemaps on y axis (since image is flipped in OGL)
        if (cubemap && flipImage)
        {
            CTexture tmp;
            tmp = images[3];
            images[3] = images[2];
            images[2] = tmp;
        }

        fclose(fp);

        valid = true;

        return true;
    }

    // free image memory
    void CDDSImage::clear()
    {
        components = 0;
        format = 0;
        compressed = false;
        cubemap = false;
        volume = false;
        valid = false;

        images.clear();
    }

    // returns individual texture when multiple textures are loaded (as is the case
    // with volume textures and cubemaps)
    CTexture &CDDSImage::operator[](int index)
    {
        // make sure an image has been loaded
        assert(valid);
        assert(index < (int)images.size());

        return images[index];
    }

    // returns pointer to main image
    CDDSImage::operator char*()
    {
        assert(valid);

        return images[0];
    }

    // uploads a compressed/uncompressed 1D texture
    bool CDDSImage::upload_texture1D()
    {
        assert(valid);
        assert(images[0].height == 1);
        assert(images[0].width > 0);
        assert(images[0]);

        if (compressed)
        {
    #if defined(WIN32) || defined(LINUX)
            // get function pointer if needed
            if (glCompressedTexImage1DARB == NULL)
            {
                GET_EXT_POINTER(glCompressedTexImage1DARB,
                                PFNGLCOMPRESSEDTEXIMAGE1DARBPROC);
            }

            if (glCompressedTexImage1DARB == NULL)
                return false;

            glCompressedTexImage1DARB(GL_TEXTURE_1D, 0, format,
                images[0].width, 0, images[0].size,
                images[0]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage1DARB(GL_TEXTURE_1D, i+1, format,
                        images[0].mipmaps[i].width, 0,
                        images[0].mipmaps[i].size,
                        images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
    #else
            glCompressedTexImage1D(GL_TEXTURE_1D, 0, format,
                images[0].width, 0, images[0].size,
                images[0]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage1D(GL_TEXTURE_1D, i+1, format,
                        images[0].mipmaps[i].width, 0,
                        images[0].mipmaps[i].size,
                        images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
    #endif
        }
        else
        {
            glTexImage1D(GL_TEXTURE_1D, 0, format, images[0].width, 0,
                format, GL_UNSIGNED_BYTE, images[0]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glTexImage1D(GL_TEXTURE_1D, i+1, components,
                        images[0].mipmaps[i].width, 0, format,
                        GL_UNSIGNED_BYTE, images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
        }

        return true;
    }

    // uploads a compressed/uncompressed 2D texture
    //
    // imageIndex - allows you to optionally specify other loaded surfaces for 2D
    //              textures such as a face in a cubemap or a slice in a volume
    //
    //              default: 0
    //
    // target     - allows you to optionally specify a different texture target for
    //              the 2D texture such as a specific face of a cubemap
    //
    //              default: GL_TEXTURE_2D
    bool CDDSImage::upload_texture2D(int imageIndex, GLenum target)
    {
        assert(valid);
        assert(imageIndex >= 0);
        assert(imageIndex < (int)images.size());
        assert(images[imageIndex].height > 0);
        assert(images[imageIndex].width > 0);
        assert(images[imageIndex]);
        assert(target == GL_TEXTURE_2D ||
            (target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB &&
             target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB));

        if (compressed)
        {
    #if defined(WIN32) || defined(LINUX)
            // load function pointer if needed
            if (glCompressedTexImage2DARB == NULL)
            {
                GET_EXT_POINTER(glCompressedTexImage2DARB,
                                PFNGLCOMPRESSEDTEXIMAGE2DARBPROC);
            }

            if (glCompressedTexImage2DARB == NULL)
                return false;

            glCompressedTexImage2DARB(target, 0, format,
                images[imageIndex].width, images[imageIndex].height, 0,
                images[imageIndex].size, images[imageIndex]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[imageIndex].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage2DARB(target, i+1, format,
                        images[imageIndex].mipmaps[i].width,
                        images[imageIndex].mipmaps[i].height, 0,
                        images[imageIndex].mipmaps[i].size,
                        images[imageIndex].mipmaps[i]);
                }
                glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
    #else
            glCompressedTexImage2D(target, 0, format,
                images[imageIndex].width, images[imageIndex].height, 0,
                images[imageIndex].size, images[imageIndex]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[imageIndex].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage2D(target, i+1, format,
                        images[imageIndex].mipmaps[i].width,
                        images[imageIndex].mipmaps[i].height, 0,
                        images[imageIndex].mipmaps[i].size,
                        images[imageIndex].mipmaps[i]);
                }
                glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
    #endif
        }
        else
        {
            glTexImage2D(target, 0, components, images[imageIndex].width,
                images[imageIndex].height, 0, format, GL_UNSIGNED_BYTE,
                images[imageIndex]);

            // load all mipmaps
            unsigned int numMipMaps = (unsigned int)images[imageIndex].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glTexImage2D(target, i+1, components,
                        images[imageIndex].mipmaps[i].width,
                        images[imageIndex].mipmaps[i].height, 0, format,
                        GL_UNSIGNED_BYTE, images[imageIndex].mipmaps[i]);
                }
                glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
        }

        return true;
    }

    #ifdef GL_NV_texture_rectangle
    bool CDDSImage::upload_textureRectangle()
    {
        assert(valid);
        assert(images.size() >= 1);

        if (!upload_texture2D(0, GL_TEXTURE_RECTANGLE_NV))
            return false;

        return true;
    }
    #endif

    // uploads a compressed/uncompressed cubemap texture
    bool CDDSImage::upload_textureCubemap()
    {
        assert(valid);
        assert(cubemap);
        assert(images.size() == 6);

        GLenum target;

        // loop through cubemap faces and load them as 2D textures
        for (int n = 0; n < 6; n++)
        {
            // specify cubemap face
            target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+n;
            if (!upload_texture2D(n, target))
                return false;
        }

        return true;
    }

    // uploads a compressed/uncompressed 3D texture
    bool CDDSImage::upload_texture3D()
    {
        assert(valid);
        assert(volume);
        assert(images[0].depth >= 1);

        if (compressed)
        {
    #if defined(WIN32) || defined(LINUX)
            // retrieve function pointer if needed
            if (glCompressedTexImage3DARB == NULL)
            {
                GET_EXT_POINTER(glCompressedTexImage3DARB,
                                PFNGLCOMPRESSEDTEXIMAGE3DARBPROC);
            }

            if (glCompressedTexImage3DARB == NULL)
                return false;

            glCompressedTexImage3DARB(GL_TEXTURE_3D, 0, format,  images[0].width,
                images[0].height, images[0].depth, 0, images[0].size, images[0]);

            // load all mipmap volumes
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage3DARB(GL_TEXTURE_3D, i+1, format,
                        images[0].mipmaps[i].width, images[0].mipmaps[i].height,
                        images[0].depth, 0, images[0].mipmaps[i].size,
                        images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
    #else
            glCompressedTexImage3D(GL_TEXTURE_3D, 0, format,  images[0].width,
                images[0].height, images[0].depth, 0, images[0].size, images[0]);

            // load all mipmap volumes
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glCompressedTexImage3D(GL_TEXTURE_3D, i+1, format,
                        images[0].mipmaps[i].width, images[0].mipmaps[i].height,
                        images[0].depth, 0, images[0].mipmaps[i].size,
                        images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }

    #endif
        }
        else
        {
    #if defined(WIN32) || defined(LINUX)
            // retrieve function pointer if needed
            if (glTexImage3D == NULL)
            {
                GET_EXT_POINTER(glTexImage3D, PFNGLTEXIMAGE3DEXTPROC);
            }

            if (glTexImage3D == NULL)
                return false;
    #endif

            glTexImage3D(GL_TEXTURE_3D, 0, components, images[0].width,
                images[0].height, images[0].depth, 0, format, GL_UNSIGNED_BYTE,
                images[0]);

            // load all mipmap volumes
            unsigned int numMipMaps = (unsigned int)images[0].mipmaps.size();
            if( numMipMaps)
            {
                for (unsigned int i = 0; i < numMipMaps; i++)
                {
                    glTexImage3D(GL_TEXTURE_3D, i+1, components,
                        images[0].mipmaps[i].width, images[0].mipmaps[i].height,
                        images[0].mipmaps[i].depth, 0, format, GL_UNSIGNED_BYTE,
                        images[0].mipmaps[i]);
                }
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0 );
                glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, numMipMaps);
            }
        }
        return true;
    }

    // clamps input size to [1-size]
    inline int CDDSImage::clamp_size(int size)
    {
        if (size <= 0)
            size = 1;

        return size;
    }

    // CDDSImage private functions

    // calculates 4-byte aligned width of image
    inline int CDDSImage::get_line_width(int width, int bpp)
    {
        return ((width * bpp + 31) & -32) >> 3;
    }

    // calculates size of DXTC texture in bytes
    inline int CDDSImage::size_dxtc(int width, int height)
    {
        return ((width+3)/4)*((height+3)/4)*
            (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
    }

    // calculates size of uncompressed RGB texture in bytes
    inline int CDDSImage::size_rgb(int width, int height)
    {
        return width*height*components;
    }

    // Swap the bytes in a 32 bit value
    inline void CDDSImage::swap_endian(void *val)
    {
    #ifdef MACOS
        unsigned int *ival = (unsigned int *)val;

        *ival = ((*ival >> 24) & 0x000000ff) |
                ((*ival >>  8) & 0x0000ff00) |
                ((*ival <<  8) & 0x00ff0000) |
                ((*ival << 24) & 0xff000000);
    #endif
    }

    // align to 4 byte boundary (add pad bytes to end of each line in the image)
    void CDDSImage::align_memory(CTexture *surface)
    {
        // don't bother with compressed images, volume textures, or cubemaps
        if (compressed || volume || cubemap)
            return;

        // calculate new image size
        int linesize = get_line_width(surface->width, components*8);
        int imagesize = linesize*surface->height;

        // exit if already aligned
        if (surface->size == imagesize)
            return;

        // create new image of new size
        CTexture newSurface(surface->width, surface->height, surface->depth,
            imagesize);

        // add pad bytes to end of each line
        char *srcimage = (char*)*surface;
        char *dstimage = (char*)newSurface;
        for (int n = 0; n < surface->depth; n++)
        {
            char *curline = srcimage;
            char *newline = dstimage;

            int imsize = surface->size / surface->depth;
            int lnsize = imsize / surface->height;

            for (int i = 0; i < surface->height; i++)
            {
                memcpy(newline, curline, lnsize);
                newline += linesize;
                curline += lnsize;
            }
        }

        // save padded image
        *surface = newSurface;
    }

    // flip image around X axis
    void CDDSImage::flip(char *image, int width, int height, int depth, int size)
    {
        int linesize;
        int offset;

        if (!compressed)
        {
            assert(depth > 0);

            int imagesize = size/depth;
            linesize = imagesize / height;

            for (int n = 0; n < depth; n++)
            {
                offset = imagesize*n;
                char *top = image + offset;
                char *bottom = top + (imagesize-linesize);

                for (int i = 0; i < (height >> 1); i++)
                {
                    swap(bottom, top, linesize);

                    top += linesize;
                    bottom -= linesize;
                }
            }
        }
        else
        {
            void (CDDSImage::*flipblocks)(DXTColBlock*, int);
            int xblocks = width / 4;
            int yblocks = height / 4;
            int blocksize;

            switch (format)
            {
                case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
                    blocksize = 8;
                    flipblocks = &CDDSImage::flip_blocks_dxtc1;
                    break;
                case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
                    blocksize = 16;
                    flipblocks = &CDDSImage::flip_blocks_dxtc3;
                    break;
                case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
                    blocksize = 16;
                    flipblocks = &CDDSImage::flip_blocks_dxtc5;
                    break;
                default:
                    return;
            }

            linesize = xblocks * blocksize;

            DXTColBlock *top;
            DXTColBlock *bottom;

            for (int j = 0; j < (yblocks >> 1); j++)
            {
                top = (DXTColBlock*)(image + j * linesize);
                bottom = (DXTColBlock*)(image + (((yblocks-j)-1) * linesize));

                (this->*flipblocks)(top, xblocks);
                (this->*flipblocks)(bottom, xblocks);

                swap(bottom, top, linesize);
            }
        }
    }

    // swap to sections of memory
    void CDDSImage::swap(void *byte1, void *byte2, int size)
    {
        unsigned char *tmp = new unsigned char[size];

        memcpy(tmp, byte1, size);
        memcpy(byte1, byte2, size);
        memcpy(byte2, tmp, size);

        delete [] tmp;
    }

    // flip a DXT1 color block
    void CDDSImage::flip_blocks_dxtc1(DXTColBlock *line, int numBlocks)
    {
        DXTColBlock *curblock = line;

        for (int i = 0; i < numBlocks; i++)
        {
            swap(&curblock->row[0], &curblock->row[3], sizeof(unsigned char));
            swap(&curblock->row[1], &curblock->row[2], sizeof(unsigned char));

            curblock++;
        }
    }

    // flip a DXT3 color block
    void CDDSImage::flip_blocks_dxtc3(DXTColBlock *line, int numBlocks)
    {
        DXTColBlock *curblock = line;
        DXT3AlphaBlock *alphablock;

        for (int i = 0; i < numBlocks; i++)
        {
            alphablock = (DXT3AlphaBlock*)curblock;

            swap(&alphablock->row[0], &alphablock->row[3], sizeof(unsigned short));
            swap(&alphablock->row[1], &alphablock->row[2], sizeof(unsigned short));

            curblock++;

            swap(&curblock->row[0], &curblock->row[3], sizeof(unsigned char));
            swap(&curblock->row[1], &curblock->row[2], sizeof(unsigned char));

            curblock++;
        }
    }

    // flip a DXT5 alpha block
    void CDDSImage::flip_dxt5_alpha(DXT5AlphaBlock *block)
    {
        unsigned char gBits[4][4];

        const unsigned int mask = 0x00000007;          // bits = 00 00 01 11
        unsigned int bits = 0;
        memcpy(&bits, &block->row[0], sizeof(unsigned char) * 3);

        gBits[0][0] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[0][1] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[0][2] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[0][3] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[1][0] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[1][1] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[1][2] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[1][3] = (unsigned char)(bits & mask);

        bits = 0;
        memcpy(&bits, &block->row[3], sizeof(unsigned char) * 3);

        gBits[2][0] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[2][1] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[2][2] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[2][3] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[3][0] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[3][1] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[3][2] = (unsigned char)(bits & mask);
        bits >>= 3;
        gBits[3][3] = (unsigned char)(bits & mask);

        // clear existing alpha bits
        memset(block->row, 0, sizeof(unsigned char) * 6);

        unsigned int *pBits = ((unsigned int*) &(block->row[0]));

        *pBits = *pBits | (gBits[3][0] << 0);
        *pBits = *pBits | (gBits[3][1] << 3);
        *pBits = *pBits | (gBits[3][2] << 6);
        *pBits = *pBits | (gBits[3][3] << 9);

        *pBits = *pBits | (gBits[2][0] << 12);
        *pBits = *pBits | (gBits[2][1] << 15);
        *pBits = *pBits | (gBits[2][2] << 18);
        *pBits = *pBits | (gBits[2][3] << 21);

        pBits = ((unsigned int*) &(block->row[3]));

        *pBits = *pBits | (gBits[1][0] << 0);
        *pBits = *pBits | (gBits[1][1] << 3);
        *pBits = *pBits | (gBits[1][2] << 6);
        *pBits = *pBits | (gBits[1][3] << 9);

        *pBits = *pBits | (gBits[0][0] << 12);
        *pBits = *pBits | (gBits[0][1] << 15);
        *pBits = *pBits | (gBits[0][2] << 18);
        *pBits = *pBits | (gBits[0][3] << 21);
    }

    // flip a DXT5 color block
    void CDDSImage::flip_blocks_dxtc5(DXTColBlock *line, int numBlocks)
    {
        DXTColBlock *curblock = line;
        DXT5AlphaBlock *alphablock;

        for (int i = 0; i < numBlocks; i++)
        {
            alphablock = (DXT5AlphaBlock*)curblock;

            flip_dxt5_alpha(alphablock);

            curblock++;

            swap(&curblock->row[0], &curblock->row[3], sizeof(unsigned char));
            swap(&curblock->row[1], &curblock->row[2], sizeof(unsigned char));

            curblock++;
        }
    }

    // CTexture implementation

    // default constructor
    CTexture::CTexture()
      : CSurface()  // initialize base class part
    {
    }

    // creates an empty texture
    CTexture::CTexture(int w, int h, int d, int imgSize)
      : CSurface(w, h, d, imgSize)  // initialize base class part
    {
    }

    // copy constructor
    CTexture::CTexture(const CTexture &copy)
      : CSurface(copy)
    {
        for (unsigned int i = 0; i < copy.mipmaps.size(); i++)
            mipmaps.push_back(copy.mipmaps[i]);
    }

    // assignment operator
    CTexture &CTexture::operator= (const CTexture &rhs)
    {
        if (this != &rhs)
        {
            CSurface::operator = (rhs);

            mipmaps.clear();
            for (unsigned int i = 0; i < rhs.mipmaps.size(); i++)
            {
                mipmaps.push_back(rhs.mipmaps[i]);
            }
        }

        return *this;
    }

    // clean up texture memory
    CTexture::~CTexture()
    {
        mipmaps.clear();
    }

    // CSurface implementation

    // default constructor
    CSurface::CSurface()
      : width(0),
        height(0),
        depth(0),
        size(0),
        pixels(NULL)
    {
    }

    // creates an empty image
    CSurface::CSurface(int w, int h, int d, int imgsize)
    {
        pixels = NULL;
        create(w, h, d, imgsize);
    }

    // copy constructor
    CSurface::CSurface(const CSurface &copy)
      : width(0),
        height(0),
        depth(0),
        size(0),
        pixels(NULL)
    {

        if (copy.pixels)
        {
            size = copy.size;
            width = copy.width;
            height = copy.height;
            depth = copy.depth;
            pixels = new char[size];
            memcpy(pixels, copy.pixels, copy.size);
        }
    }

    // assignment operator
    CSurface &CSurface::operator= (const CSurface &rhs)
    {
        if (this != &rhs)
        {
            clear();

            if (rhs.pixels)
            {
                size = rhs.size;
                width = rhs.width;
                height = rhs.height;
                depth = rhs.depth;

                pixels = new char[size];
                memcpy(pixels, rhs.pixels, size);
            }
        }

        return *this;
    }

    // clean up image memory
    CSurface::~CSurface()
    {
        clear();
    }

    // returns a pointer to image
    CSurface::operator char*()
    {
        return pixels;
    }

    // creates an empty image
    void CSurface::create(int w, int h, int d, int imgsize)
    {
        clear();

        width = w;
        height = h;
        depth = d;
        size = imgsize;
        pixels = new char[imgsize];
    }

    // free surface memory
    void CSurface::clear()
    {
        delete [] pixels;
        pixels = NULL;
    }