//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "platform/platform.h"
#include "gfx/bitmap/ddsFile.h"
#include "gfx/bitmap/ddsData.h"
#include "gfx/bitmap/bitmapUtils.h"
#include "gfx/bitmap/imageUtils.h"
#include "gfx/gfxDevice.h"
#include "core/util/fourcc.h"
#include "console/console.h"
#include "core/resourceManager.h"
#include "core/stream/fileStream.h"
#include "gfx/bitmap/gBitmap.h"
#include "console/engineAPI.h"

#include <squish.h>

S32 DDSFile::smActiveCopies = 0;
U32 DDSFile::smDropMipCount = 0;

DDSFile::DDSFile( const DDSFile &dds )
   :  mFlags( dds.mFlags ),
      mHeight( dds.mHeight ),
      mWidth( dds.mWidth ),
      mDepth( dds.mDepth ),
      mPitchOrLinearSize( dds.mPitchOrLinearSize ),
      mMipMapCount( dds.mMipMapCount ),
      mFormat( dds.mFormat ),
      mBytesPerPixel( dds.mBytesPerPixel ),
      mFourCC( dds.mFourCC ),
      mCacheString( dds.mCacheString ),
      mSourcePath( dds.mSourcePath ),
      mHasTransparency( dds.mHasTransparency )
{
   VECTOR_SET_ASSOCIATION( mSurfaces );
   smActiveCopies++;
   
   for ( U32 i=0; i < dds.mSurfaces.size(); i++ )
   {
      SurfaceData *surface = NULL;
      if ( dds.mSurfaces[i] )
         surface = new SurfaceData;

      mSurfaces.push_back( surface );

      if ( !surface )
         continue;

      for ( U32 m=0; m < dds.mSurfaces[i]->mMips.size(); m++ )
      {
         U32 size = dds.getSurfaceSize( m );
         surface->mMips.push_back(new U8[size]);         
         dMemcpy( surface->mMips.last(), dds.mSurfaces[i]->mMips[m], size );
      }
   }
}

void DDSFile::clear()
{
   mFlags = 0;
   mHeight = mWidth = mDepth = mPitchOrLinearSize = mMipMapCount = 0;
   mFormat = GFXFormatR8G8B8;
}

U32 DDSFile::getSurfacePitch( U32 mipLevel ) const
{
   if(mFlags.test(CompressedData))
   {
      U32 sizeMultiple = 0;

      switch(mFormat)
      {
      case GFXFormatBC1:
      case GFXFormatBC4:
         sizeMultiple = 8;
         break;
      case GFXFormatBC2:
      case GFXFormatBC3:      
      case GFXFormatBC5:
         sizeMultiple = 16;
         break;
      default:
         AssertISV(false, "DDSFile::getPitch - invalid compressed texture format, we only support DXT1-5 right now.");
         break;
      }

      // Maybe need to be DWORD aligned?
      U32 align = getMax(U32(1), getWidth(mipLevel)/4) * sizeMultiple;
      align += 3; align >>=2; align <<=2;
      return align;

   }
   else
      return getWidth(mipLevel) * mBytesPerPixel;
}

U32 DDSFile::getSurfaceSize( U32 height, U32 width, U32 mipLevel ) const 
{
   // Bump by the mip level.
   height = getMax(U32(1), height >> mipLevel);
   width  = getMax(U32(1), width >> mipLevel);

   if(mFlags.test(CompressedData))
   {
      // From the directX docs:
      // max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5)

      U32 sizeMultiple = 0;

      switch(mFormat)
      {
      case GFXFormatBC1:
      case GFXFormatBC4:
         sizeMultiple = 8;
         break;
      case GFXFormatBC2:
      case GFXFormatBC3:      
      case GFXFormatBC5:
         sizeMultiple = 16;
         break;
      default:
         AssertISV(false, "DDSFile::getSurfaceSize - invalid compressed texture format, we only support DXT1-5 right now.");
         break;
      }

      return getMax(U32(1), width/4) * getMax(U32(1), height/4) * sizeMultiple;
   }
   else
   {
      return height * width* mBytesPerPixel;
   }
}

U32 DDSFile::getSizeInBytes() const
{
   // TODO: This doesn't take mDepth into account, so
   // it doesn't work right for volume textures!

   U32 bytes = 0;
   if (mFlags.test(CubeMapFlag))
   {
      for(U32 cubeFace=0;cubeFace < Cubemap_Surface_Count;cubeFace++)
         for (U32 i = 0; i < mMipMapCount; i++)
            bytes += getSurfaceSize(mHeight, mWidth, i);
   }
   else
   {
      for (U32 i = 0; i < mMipMapCount; i++)
         bytes += getSurfaceSize(mHeight, mWidth, i);
   }

   return bytes;
}

U32 DDSFile::getSizeInBytes( GFXFormat format, U32 height, U32 width, U32 mipLevels )
{
   AssertFatal( ImageUtil::isCompressedFormat(format), 
      "DDSFile::getSizeInBytes - Must be a Block Compression format!" );

   // From the directX docs:
   // max(1, width ÷ 4) x max(1, height ÷ 4) x 8(DXT1) or 16(DXT2-5)

   U32 sizeMultiple = 0;
   if ( format == GFXFormatBC1 || format == GFXFormatBC1_SRGB || format == GFXFormatBC4)
      sizeMultiple = 8;
   else
      sizeMultiple = 16;

   U32 mipHeight, mipWidth; 
   U32 bytes = 0;
   for ( U32 m=0; m < mipLevels; m++ )
   {
      mipHeight = getMax( U32(1), height >> m );
      mipWidth  = getMax( U32(1), width >> m );

      bytes += getMax( U32(1), mipWidth / 4 ) * 
               getMax( U32(1), mipHeight / 4 ) * sizeMultiple;
   }

   return bytes;
}

bool DDSFile::readHeader(Stream &s)
{
   U32 fourcc;
   // Read the FOURCC
   s.read(&fourcc);

   if(fourcc != DDS_MAGIC)
   {
      Con::errorf("DDSFile::readHeader - unexpected magic number, wanted 'DDS '!");
      return false;
   }

   //dds headers
   dds::DDS_HEADER header = {};
   dds::DDS_HEADER_DXT10 dx10header = {};
   //todo DX10 formats
   bool hasDx10Header = false;

   //read in header
   s.read(DDS_HEADER_SIZE, &header);
   //check for dx10 header support
   if ((header.ddspf.flags & DDS_FOURCC) && (header.ddspf.fourCC == dds::D3DFMT_DX10))
   {
      //read in dx10 header
      s.read(DDS_HEADER_DX10_SIZE, &dx10header);
      if (!dds::validateHeaderDx10(dx10header))
         return false;

      hasDx10Header = true;
   }

   //make sure our dds header is valid
   if (!dds::validateHeader(header))
      return false;

   // store details
   mPitchOrLinearSize = header.pitchOrLinearSize;
   mMipMapCount = header.mipMapCount ? header.mipMapCount : 1;
   mHeight = header.height;
   mWidth = header.width;
   mDepth = header.depth;
   mFourCC = header.ddspf.fourCC;

   //process dx10 header
   if (hasDx10Header)
   {
      if (dx10header.arraySize > 1)
      {
         Con::errorf("DDSFile::readHeader - DX10 arrays not supported");
         return false;
      }

      mFormat = dds::getGFXFormat(dx10header.dxgiFormat);
      //make sure getGFXFormat gave us a valid format
      if (mFormat == GFXFormat_FIRST)
         return false;
      //cubemap
      if (dx10header.miscFlag & dds::D3D10_RESOURCE_MISC_TEXTURECUBE)
      {
         mFlags.set(CubeMap_All_Flags | ComplexFlag);
      }

      mHasTransparency = ImageUtil::isAlphaFormat(mFormat);

      //mip map flag
      if (mMipMapCount > 1)
         mFlags.set(MipMapsFlag | ComplexFlag);

      if (ImageUtil::isCompressedFormat(mFormat))
         mFlags.set(CompressedData);
      else
      {
         mBytesPerPixel = dds::getBitsPerPixel(dx10header.dxgiFormat) / 8;
         mFlags.set(RGBData);
      }

      // we finished now
      return true;
   }

   //process regular header

   //D3DFMT_DX10 is caught above, no need to check now
   if (header.ddspf.flags & DDS_FOURCC)
   {
      mFormat = dds::getGFXFormat(mFourCC);
      //make sure getGFXFormat gave us a valid format
      if (mFormat == GFXFormat_FIRST)
         return false;

      if (ImageUtil::isCompressedFormat(mFormat))
         mFlags.set(CompressedData);
      else
      {
         switch (header.ddspf.fourCC)
         {
         case 36: // D3DFMT_A16B16G16R16
            mBytesPerPixel = 8;
            break;
         case 110: // D3DFMT_Q16W16V16U16
            mBytesPerPixel = 8;
            break;
         case 111: // D3DFMT_R16F
            mBytesPerPixel = 2;
            break;
         case 112: // D3DFMT_G16R16F
            mBytesPerPixel = 4;
            break;
         case 113: // D3DFMT_A16B16G16R16F
            mBytesPerPixel = 8;
            break;
         case 114: // D3DFMT_R32F
            mBytesPerPixel = 4;
            break;
         case 115: // D3DFMT_G32R32F
            mBytesPerPixel = 8;
            break;
         case 116: // D3DFMT_A32B32G32R32F
            mBytesPerPixel = 16;
            break;
         }

         mFlags.set(RGBData);
      }
   }
   else
   {
      mFormat = dds::getGFXFormat(header.ddspf);
      //make sure getGFXFormat gave us a valid format
      if (mFormat == GFXFormat_FIRST)
         return false;

      mBytesPerPixel = header.ddspf.bpp / 8;
      mFlags.set(RGBData);
   }

   //mip map flag
   if (mMipMapCount > 1)
      mFlags.set(MipMapsFlag | ComplexFlag);

   //set transparency flag
   mHasTransparency = (header.ddspf.flags & DDS_ALPHAPIXELS);

   if (header.flags & DDS_HEADER_FLAGS_LINEARSIZE)
      mFlags.set(LinearSizeFlag);
   else if (header.flags & DDS_HEADER_FLAGS_PITCH)
      mFlags.set(PitchSizeFlag);

   //set cubemap flags
   if (header.cubemapFlags & DDS_CUBEMAP)
   {
      mFlags.set(CubeMapFlag | ComplexFlag);
      // Store the face flags too.
      if (header.cubemapFlags & DDS_CUBEMAP_POSITIVEX) mFlags.set(CubeMap_PosX_Flag);
      if (header.cubemapFlags & DDS_CUBEMAP_NEGATIVEX) mFlags.set(CubeMap_NegX_Flag);
      if (header.cubemapFlags & DDS_CUBEMAP_POSITIVEY) mFlags.set(CubeMap_PosY_Flag);
      if (header.cubemapFlags & DDS_CUBEMAP_NEGATIVEY) mFlags.set(CubeMap_NegY_Flag);
      if (header.cubemapFlags & DDS_CUBEMAP_POSITIVEZ) mFlags.set(CubeMap_PosZ_Flag);
      if (header.cubemapFlags & DDS_CUBEMAP_NEGATIVEZ) mFlags.set(CubeMap_NegZ_Flag);
   }



   return true;
}

bool DDSFile::read(Stream &s, U32 dropMipCount)
{
   if( !readHeader(s) )
   {
      Con::errorf("DDSFile::read - error reading header!");
      return false;
   }

   // If we're droping mips then make sure we have enough.
   dropMipCount = getMin( dropMipCount, mMipMapCount - 1 );

   // At this point we know what sort of image we contain. So we should
   // allocate some buffers, and read it in.

   // How many surfaces are we talking about?
   if(mFlags.test(CubeMapFlag))
   {
      mSurfaces.setSize( Cubemap_Surface_Count );

      for ( U32 i=0; i < Cubemap_Surface_Count; i++ )
      {
         // Does the cubemap contain this surface?
         if ( mFlags.test( CubeMap_PosX_Flag + ( i << 1 ) ) )
            mSurfaces[i] = new SurfaceData();
         else
         {
            mSurfaces[i] = NULL;
            continue;
         }

         // Load all the mips.
         for(S32 mip=0; mip<mMipMapCount; mip++)
            mSurfaces[i]->readNextMip(this, s, mHeight, mWidth, mip, mip < dropMipCount );
      }

   }
   else if (mFlags.test(VolumeFlag))
   {
      // Do something with volume
   }
   else
   {
      // It's a plain old texture.

      // First allocate a SurfaceData to stick this in.
      mSurfaces.push_back(new SurfaceData());

      // Load however many mips there are.
      for(S32 i=0; i<mMipMapCount; i++)
         mSurfaces.last()->readNextMip(this, s, mHeight, mWidth, i, i < dropMipCount);

      // Ok, we're done.
   }

   // If we're dropping mips then fix up the stats.
   if ( dropMipCount > 0 )
   {
      // Fix up the pitch and/or linear size.
      if( mFlags.test( LinearSizeFlag ) )
         mPitchOrLinearSize = getSurfaceSize( dropMipCount );
      else if ( mFlags.test( PitchSizeFlag ) )
         mPitchOrLinearSize = getSurfacePitch( dropMipCount );

      // Now fix up the rest of the 
      mMipMapCount = getMax( (U32)1, mMipMapCount - dropMipCount );
      mHeight = getHeight( dropMipCount );
      mWidth = getWidth( dropMipCount );
   }

   return true;
}

bool DDSFile::writeHeader( Stream &s )
{
   // write DDS magic
   U32 magic = DDS_MAGIC;
   s.write(magic);

   dds::DDS_HEADER header = {};
   dds::DDS_HEADER_DXT10 dx10header = {};

   bool hasDx10Header = false;
   //flags
   U32 surfaceFlags = DDS_SURFACE_FLAGS_TEXTURE;
   U32 cubemapFlags = 0;
   U32 headerFlags = DDS_HEADER_FLAGS_TEXTURE;

   //pixel format
   const dds::DDS_PIXELFORMAT &format = dds::getDDSFormat(mFormat);

   // todo better dx10 support
   if (format.fourCC == dds::D3DFMT_DX10)
   {
      dx10header.dxgiFormat = dds::getDXGIFormat(mFormat);
      dx10header.arraySize = 1;
      dx10header.resourceDimension = dds::D3D10_RESOURCE_DIMENSION_TEXTURE2D;
      dx10header.miscFlag = 0;
      dx10header.miscFlags2 = 0;
      hasDx10Header = true;
   }

   if (mFlags.test(CompressedData))
      headerFlags |= DDS_HEADER_FLAGS_LINEARSIZE;
   else
      headerFlags |= DDS_HEADER_FLAGS_PITCH;

   if (mMipMapCount > 1)
   {
      surfaceFlags |= DDS_SURFACE_FLAGS_MIPMAP;
      headerFlags |= DDS_HEADER_FLAGS_MIPMAP;
   }   

   //cubemap flags
   if (mFlags.test(CubeMapFlag))
   {
      surfaceFlags |= DDS_SURFACE_FLAGS_CUBEMAP;
      cubemapFlags |= DDS_CUBEMAP_ALLFACES;
      if (hasDx10Header)
         dx10header.miscFlag = dds::D3D10_RESOURCE_MISC_TEXTURECUBE;
   }

   //volume texture
   if (mDepth > 0)
   {
      headerFlags |= DDS_HEADER_FLAGS_VOLUME;
      dx10header.resourceDimension = dds::D3D10_RESOURCE_DIMENSION_TEXTURE3D;
   }

   //main dds header
   header.size = sizeof(dds::DDS_HEADER);
   header.flags = headerFlags;
   header.height = mHeight;
   header.width = mWidth;
   header.pitchOrLinearSize = mPitchOrLinearSize;
   header.depth = mDepth;
   header.ddspf = format;
   header.mipMapCount = mMipMapCount;
   header.surfaceFlags = surfaceFlags;   
   header.cubemapFlags = cubemapFlags;   
   memset(header.reserved1, 0, sizeof(header.reserved1));
   memset(header.reserved2, 0, sizeof(header.reserved2));

   //check our header is ok
   if (!dds::validateHeader(header))
      return false;

   //Write out the header
   s.write(DDS_HEADER_SIZE, &header);
   
   //Write out dx10 header
   if (hasDx10Header)
      s.write(DDS_HEADER_DX10_SIZE, &dx10header);

   return true;
}

bool DDSFile::write( Stream &s )
{
   if(!writeHeader(s))
   {
      Con::errorf("DDSFile::write - error writing header!");
      return false;
   }

   // How many surfaces are we talking about?
   if(mFlags.test(CubeMapFlag))
   {
      // Do something with cubemaps.
      for (U32 cubeFace = 0; cubeFace < Cubemap_Surface_Count; cubeFace++)
      {
         // write the mips
         for (S32 i = 0; i < mMipMapCount; i++)
            mSurfaces[cubeFace]->writeNextMip(this, s, mHeight, mWidth, i);
      }
   }
   else if (mFlags.test(VolumeFlag))
   {
      // Do something with volume
   }
   else
   {
      // It's a plain old texture.

      // Load however many mips there are.
      for ( S32 i = 0; i < mMipMapCount; i++ )
         mSurfaces.last()->writeNextMip(this, s, mHeight, mWidth, i);

      // Ok, we're done.
   }

   return true;
}

void DDSFile::SurfaceData::dumpImage(DDSFile *dds, U32 mip, const char *file)
{
   GBitmap *foo = new GBitmap(dds->mWidth >> mip, dds->mHeight >> mip, false, dds->mFormat);

   // Copy our data in.
   dMemcpy(foo->getWritableBits(), mMips[mip], dds->getSurfaceSize(dds->mHeight, dds->mWidth, mip) );
   
   FileStream  stream;

   stream.open( file, Torque::FS::File::Write );

   if ( stream.getStatus() == Stream::Ok )
   {
      // Write it out.
      foo->writeBitmap("png", stream);
   }

   // Clean up.
   delete foo;
}

void DDSFile::SurfaceData::readNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel, bool skip)
{
   U32 size = dds->getSurfaceSize(height, width, mipLevel);

   // If we're skipping this mip then seek forward.
   if ( skip )
      s.setPosition( s.getPosition() + size );
   else
   {
      mMips.push_back(new U8[size]);
      if(!s.read(size, mMips.last()))
         Con::errorf("DDSFile::SurfaceData::addNextMip - failed to read mip!");
   }
}

void DDSFile::SurfaceData::writeNextMip(DDSFile *dds, Stream &s, U32 height, U32 width, U32 mipLevel)
{
   U32 size = dds->getSurfaceSize(height, width, mipLevel);
   if(!s.write(size, mMips[mipLevel]))
      Con::errorf("DDSFile::SurfaceData::writeNextMip - failed to write mip!");
}

//------------------------------------------------------------------------------

template<> void *Resource<DDSFile>::create( const Torque::Path &path )
{
#ifdef TORQUE_DEBUG_RES_MANAGER
   Con::printf( "Resource<DDSFile>::create - [%s]", path.getFullPath().c_str() );
#endif

   FileStream stream;

   stream.open( path.getFullPath(), Torque::FS::File::Read );

   if ( stream.getStatus() != Stream::Ok )
      return NULL;

   DDSFile *retDDS = new DDSFile;

   if( !retDDS->read( stream, DDSFile::smDropMipCount ) )
   {
      delete retDDS;
      return NULL;
   }
   else
   {
      // Set source file name
      retDDS->mSourcePath = path;
      retDDS->mCacheString = Torque::Path::Join( path.getRoot(), ':', path.getPath() );
      retDDS->mCacheString = Torque::Path::Join( retDDS->mCacheString, '/', path.getFileName() );
   }

   return retDDS;
}

template<> ResourceBase::Signature  Resource<DDSFile>::signature()
{
   return MakeFourCC('D','D','S',' '); // Direct Draw Surface
}

Resource<DDSFile> DDSFile::load( const Torque::Path &path, U32 dropMipCount )
{
   PROFILE_SCOPE( DDSFile_load );
   
   // HACK:  It sucks that we cannot pass parameters into 
   // the resource manager loading system.
   DDSFile::smDropMipCount = dropMipCount;
   Resource<DDSFile> ret = ResourceManager::get().load( path );
   DDSFile::smDropMipCount = 0;

   // Any kind of error checking or path stepping can happen here

   return ret;
}

//------------------------------------------------------------------------------

DDSFile *DDSFile::createDDSFileFromGBitmap( const GBitmap *gbmp )
{
   if( gbmp == NULL )
      return NULL;

   DDSFile *ret = new DDSFile;

   // Set up the DDSFile properties that matter. Since this is a GBitmap, there
   // are assumptions that can be made
   ret->mHeight = gbmp->getHeight();
   ret->mWidth = gbmp->getWidth();
   ret->mDepth = 0;
   ret->mFormat = gbmp->getFormat();
   ret->mFlags.set(RGBData);
   ret->mBytesPerPixel = gbmp->getBytesPerPixel();
   ret->mMipMapCount = gbmp->getNumMipLevels();
   ret->mHasTransparency = gbmp->getHasTransparency();

   // ASSUMPTION!!!
   // This _most likely_ does not belong here, but it is safe to assume that if
   // a GBitmap is 24-bit, and it's being converted to a DDS, it is most likely
   // going to be either:
   // a) Uploaded as a 32-bit texture, and just needs to be padded to RGBX
   // b) Uploaded as a compressed format, and needs to be padded to 32-bits anyway
   if( ret->mFormat == GFXFormatR8G8B8 )
   {
      ret->mFormat = GFXFormatR8G8B8X8;
      ret->mBytesPerPixel = 4;
   }

   if( ret->mMipMapCount > 1 )
      ret->mFlags.set(MipMapsFlag);

   // One surface per GBitmap
   ret->mSurfaces.push_back( new SurfaceData() );

   // Load the mips
   for( S32 i = 0; i < ret->mMipMapCount; i++ )
   {
      const U32 mipSz = ret->getSurfaceSize(i);
      ret->mSurfaces.last()->mMips.push_back( new U8[mipSz] );

      U8 *mipMem = ret->mSurfaces.last()->mMips.last();
      
      // If this is a straight copy, just do it, otherwise (ugh)
      if( ret->mFormat == gbmp->getFormat() )
         dMemcpy( mipMem, gbmp->getBits(i), mipSz );
      else
      {
         // Assumption:
         AssertFatal( gbmp->getBytesPerPixel() + 1 == ret->mBytesPerPixel, "Assumption failed, not 24->32 bit straight convert." );

         for( S32 pxl = 0; pxl < gbmp->getWidth(i) * gbmp->getHeight(i); pxl++ )
         {
            U8 *dst = &mipMem[pxl * ret->mBytesPerPixel];
            const U8 *src = &gbmp->getBits(i)[pxl * gbmp->getBytesPerPixel()];
            dMemcpy( dst, src, gbmp->getBytesPerPixel() * sizeof(U8) );
            dst[ret->mBytesPerPixel - 1] = 255;
         } 
      }

      // Uncomment to debug-dump each mip level
      //ret->mSurfaces.last()->dumpImage( ret, i, avar( "%d_Gbmp_xmip%d", ret, i ) );
   }

   return ret;
}

DDSFile *DDSFile::createDDSCubemapFileFromGBitmaps(GBitmap **gbmps)
{
   if (gbmps == NULL)
      return NULL;

   AssertFatal(gbmps[0], "createDDSCubemapFileFromGBitmaps bitmap 0 is null");
   AssertFatal(gbmps[1], "createDDSCubemapFileFromGBitmaps bitmap 1 is null");
   AssertFatal(gbmps[2], "createDDSCubemapFileFromGBitmaps bitmap 2 is null");
   AssertFatal(gbmps[3], "createDDSCubemapFileFromGBitmaps bitmap 3 is null");
   AssertFatal(gbmps[4], "createDDSCubemapFileFromGBitmaps bitmap 4 is null");
   AssertFatal(gbmps[5], "createDDSCubemapFileFromGBitmaps bitmap 5 is null");

   DDSFile *ret = new DDSFile;
   //all cubemaps have the same dimensions and formats
   GBitmap *pBitmap = gbmps[0];

   GFXFormat fmt = pBitmap->getFormat();
   if (fmt != GFXFormatR8G8B8A8 && fmt != GFXFormatR16G16B16A16F)
   {
      Con::errorf("createDDSCubemapFileFromGBitmaps: unsupported format");
      return NULL;
   }

   // Set up the DDSFile properties that matter. Since this is a GBitmap, there
   // are assumptions that can be made
   ret->mHeight = pBitmap->getHeight();
   ret->mWidth = pBitmap->getWidth();
   ret->mDepth = 0;
   ret->mFormat = pBitmap->getFormat();
   ret->mFlags.set( RGBData | CubeMapFlag | CubeMap_PosX_Flag | CubeMap_NegX_Flag | CubeMap_PosY_Flag |
      CubeMap_NegY_Flag | CubeMap_PosZ_Flag | CubeMap_NegZ_Flag);
   ret->mBytesPerPixel = pBitmap->getBytesPerPixel();
   //todo implement mip mapping
   ret->mMipMapCount = pBitmap->getNumMipLevels();
   ret->mHasTransparency = pBitmap->getHasTransparency();

   for (U32 cubeFace = 0; cubeFace < Cubemap_Surface_Count; cubeFace++)
   {
      ret->mSurfaces.push_back(new SurfaceData());
      // Load the mips
      for (S32 i = 0; i < ret->mMipMapCount; i++)
      {
         const U32 mipSz = ret->getSurfaceSize(i);
         ret->mSurfaces.last()->mMips.push_back(new U8[mipSz]);

         U8 *mipMem = ret->mSurfaces.last()->mMips.last();
         //straight copy
         dMemcpy(mipMem, gbmps[cubeFace]->getBits(i), mipSz);
      }
   }

   return ret;
}

bool DDSFile::decompressToGBitmap(GBitmap *dest)
{
   // TBD: do we support other formats?
   if (mFormat != GFXFormatBC1 && mFormat != GFXFormatBC2 && mFormat != GFXFormatBC3)
      return false;

   dest->allocateBitmapWithMips(getWidth(), getHeight(), getMipLevels(), GFXFormatR8G8B8A8);

   // Decompress and copy mips...

   U32 numMips = getMipLevels();

   for (U32 i = 0; i < numMips; i++)
   {
      U8 *addr = dest->getAddress(0, 0, i);
      const U8 *mipBuffer = mSurfaces[0]->mMips[i];
      ImageUtil::decompress(mipBuffer, addr, getWidth(i), getHeight(i), mFormat);

   }

   return true;
}

DefineEngineFunction( getActiveDDSFiles, S32, (),,
   "Returns the count of active DDSs files in memory.\n"
   "@ingroup Rendering\n" )
{
   return DDSFile::smActiveCopies;
}