summaryrefslogtreecommitdiff
path: root/vtf
diff options
context:
space:
mode:
Diffstat (limited to 'vtf')
-rw-r--r--vtf/convert_x360.cpp891
-rw-r--r--vtf/cvtf.h441
-rw-r--r--vtf/s3tc_decode.cpp395
-rw-r--r--vtf/s3tc_decode.h109
-rw-r--r--vtf/vtf.cpp3493
-rw-r--r--vtf/vtf.vpc46
-rw-r--r--vtf/vtf_x360.cpp347
7 files changed, 5722 insertions, 0 deletions
diff --git a/vtf/convert_x360.cpp b/vtf/convert_x360.cpp
new file mode 100644
index 0000000..b0e0ee3
--- /dev/null
+++ b/vtf/convert_x360.cpp
@@ -0,0 +1,891 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//
+// Purpose: Force pc .VTF to preferred .VTF 360 format conversion
+//
+//=====================================================================================//
+
+#include "tier1/utlvector.h"
+#include "mathlib/mathlib.h"
+#include "tier1/strtools.h"
+#include "cvtf.h"
+#include "tier1/utlbuffer.h"
+#include "tier0/dbg.h"
+#include "tier1/utlmemory.h"
+#include "bitmap/imageformat.h"
+
+// if the entire vtf file is smaller than this threshold, add entirely to preload
+#define PRELOAD_VTF_THRESHOLD 2048
+
+struct ResourceCopy_t
+{
+ void *m_pData;
+ int m_DataLength;
+ ResourceEntryInfo m_EntryInfo;
+};
+
+//-----------------------------------------------------------------------------
+// Converts to an alternate format
+//-----------------------------------------------------------------------------
+ImageFormat PreferredFormat( IVTFTexture *pVTFTexture, ImageFormat fmt, int width, int height, int mipCount, int faceCount )
+{
+ switch ( fmt )
+ {
+ case IMAGE_FORMAT_RGBA8888:
+ case IMAGE_FORMAT_ABGR8888:
+ case IMAGE_FORMAT_ARGB8888:
+ case IMAGE_FORMAT_BGRA8888:
+ return IMAGE_FORMAT_BGRA8888;
+
+ // 24bpp gpu formats don't exist, must convert
+ case IMAGE_FORMAT_BGRX8888:
+ case IMAGE_FORMAT_RGB888:
+ case IMAGE_FORMAT_BGR888:
+ case IMAGE_FORMAT_RGB888_BLUESCREEN:
+ case IMAGE_FORMAT_BGR888_BLUESCREEN:
+ return IMAGE_FORMAT_BGRX8888;
+
+ case IMAGE_FORMAT_BGRX5551:
+ case IMAGE_FORMAT_RGB565:
+ case IMAGE_FORMAT_BGR565:
+ return IMAGE_FORMAT_BGR565;
+
+ // no change
+ case IMAGE_FORMAT_I8:
+ case IMAGE_FORMAT_IA88:
+ case IMAGE_FORMAT_A8:
+ case IMAGE_FORMAT_BGRA4444:
+ case IMAGE_FORMAT_BGRA5551:
+ case IMAGE_FORMAT_UV88:
+ case IMAGE_FORMAT_UVWQ8888:
+ case IMAGE_FORMAT_RGBA16161616:
+ case IMAGE_FORMAT_UVLX8888:
+ case IMAGE_FORMAT_DXT1_ONEBITALPHA:
+ case IMAGE_FORMAT_DXT1:
+ case IMAGE_FORMAT_DXT3:
+ case IMAGE_FORMAT_DXT5:
+ case IMAGE_FORMAT_ATI1N:
+ case IMAGE_FORMAT_ATI2N:
+ break;
+
+ case IMAGE_FORMAT_RGBA16161616F:
+ return IMAGE_FORMAT_RGBA16161616;
+ }
+
+ return fmt;
+}
+
+//-----------------------------------------------------------------------------
+// Determines target dimensions
+//-----------------------------------------------------------------------------
+bool ComputeTargetDimensions( const char *pDebugName, IVTFTexture *pVTFTexture, int picmip, int &width, int &height, int &mipCount, int &mipSkipCount, bool &bNoMip )
+{
+ width = pVTFTexture->Width();
+ height = pVTFTexture->Height();
+
+ // adhere to texture's internal lod setting
+ int nClampX = 1<<30;
+ int nClampY = 1<<30;
+ TextureLODControlSettings_t const *pLODInfo = reinterpret_cast<TextureLODControlSettings_t const *> ( pVTFTexture->GetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, NULL ) );
+ if ( pLODInfo )
+ {
+ if ( pLODInfo->m_ResolutionClampX )
+ {
+ nClampX = min( nClampX, 1 << pLODInfo->m_ResolutionClampX );
+ }
+ if ( pLODInfo->m_ResolutionClampX_360 )
+ {
+ nClampX = min( nClampX, 1 << pLODInfo->m_ResolutionClampX_360 );
+ }
+ if ( pLODInfo->m_ResolutionClampY )
+ {
+ nClampY = min( nClampY, 1 << pLODInfo->m_ResolutionClampY );
+ }
+ if ( pLODInfo->m_ResolutionClampY_360 )
+ {
+ nClampY = min( nClampY, 1 << pLODInfo->m_ResolutionClampY_360 );
+ }
+ }
+
+ // spin down to desired texture size
+ mipSkipCount = 0;
+ while ( mipSkipCount < picmip || width > nClampX || height > nClampY )
+ {
+ if ( width == 1 && height == 1 )
+ break;
+ width >>= 1;
+ height >>= 1;
+ if ( width < 1 )
+ width = 1;
+ if ( height < 1 )
+ height = 1;
+ mipSkipCount++;
+ }
+
+ bNoMip = false;
+ if ( pVTFTexture->Flags() & TEXTUREFLAGS_NOMIP )
+ {
+ bNoMip = true;
+ }
+
+ // determine mip quantity based on desired width/height
+ if ( bNoMip )
+ {
+ // avoid serializing unused mips
+ mipCount = 1;
+ }
+ else
+ {
+ mipCount = ImageLoader::GetNumMipMapLevels( width, height );
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Align the buffer to specified boundary
+//-----------------------------------------------------------------------------
+int AlignBuffer( CUtlBuffer &buf, int alignment )
+{
+ int curPosition;
+ int newPosition;
+ byte padByte = 0;
+
+ // advance to aligned position
+ buf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
+ curPosition = buf.TellPut();
+ newPosition = AlignValue( curPosition, alignment );
+ buf.EnsureCapacity( newPosition );
+
+ // write empty
+ for ( int i=0; i<newPosition-curPosition; i++ )
+ {
+ buf.Put( &padByte, 1 );
+ }
+
+ return newPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Convert the x86 image data to 360
+//-----------------------------------------------------------------------------
+bool ConvertImageFormatEx(
+ unsigned char *pSourceImage,
+ int sourceImageSize,
+ ImageFormat sourceFormat,
+ unsigned char *pTargetImage,
+ int targetImageSize,
+ ImageFormat targetFormat,
+ int width,
+ int height,
+ bool bSrgbGammaConvert )
+{
+ // format conversion expects pc oriented data
+ // but, formats that are >8 bits per channels need to be element pre-swapped
+ ImageLoader::PreConvertSwapImageData( pSourceImage, sourceImageSize, sourceFormat );
+
+ bool bRetVal = ImageLoader::ConvertImageFormat(
+ pSourceImage,
+ sourceFormat,
+ pTargetImage,
+ targetFormat,
+ width,
+ height );
+ if ( !bRetVal )
+ {
+ return false;
+ }
+
+ // convert to proper channel order for 360 d3dformats
+ ImageLoader::PostConvertSwapImageData( pTargetImage, targetImageSize, targetFormat );
+
+ // Convert colors from sRGB gamma space into 360 piecewise linear gamma space
+ if ( bSrgbGammaConvert == true )
+ {
+ if ( targetFormat == IMAGE_FORMAT_BGRA8888 || targetFormat == IMAGE_FORMAT_BGRX8888 )
+ {
+ //Msg( " Converting 8888 texture from sRGB gamma to 360 PWL gamma *** %dx%d\n", width, height );
+ for ( int i = 0; i < ( targetImageSize / 4 ); i++ ) // targetImageSize is the raw data length in bytes
+ {
+ unsigned char *pRGB[3] = { NULL, NULL, NULL };
+
+ if ( IsPC() )
+ {
+ // pTargetImage is the raw image data
+ pRGB[0] = &( pTargetImage[ ( i * 4 ) + 1 ] ); // Red
+ pRGB[1] = &( pTargetImage[ ( i * 4 ) + 2 ] ); // Green
+ pRGB[2] = &( pTargetImage[ ( i * 4 ) + 3 ] ); // Blue
+ }
+ else // 360
+ {
+ // pTargetImage is the raw image data
+ pRGB[0] = &( pTargetImage[ ( i * 4 ) + 1 ] ); // Red
+ pRGB[1] = &( pTargetImage[ ( i * 4 ) + 2 ] ); // Green
+ pRGB[2] = &( pTargetImage[ ( i * 4 ) + 3 ] ); // Blue
+ }
+
+ // Modify RGB data in place
+ for ( int j = 0; j < 3; j++ ) // For red, green, blue
+ {
+ float flSrgbGamma = float( *( pRGB[j] ) ) / 255.0f;
+ float fl360Gamma = SrgbGammaTo360Gamma( flSrgbGamma );
+
+ fl360Gamma = clamp( fl360Gamma, 0.0f, 1.0f );
+ *( pRGB[j] ) = ( unsigned char ) ( clamp( ( ( fl360Gamma * 255.0f ) + 0.5f ), 0.0f, 255.0f ) );
+ }
+ }
+ }
+ else if ( ( targetFormat == IMAGE_FORMAT_DXT1_ONEBITALPHA ) || ( targetFormat == IMAGE_FORMAT_DXT1 ) || ( targetFormat == IMAGE_FORMAT_DXT3 ) || ( targetFormat == IMAGE_FORMAT_DXT5 ) )
+ {
+ //Msg( " Converting DXT texture from sRGB gamma to 360 PWL gamma *** %dx%d\n", width, height );
+ int nStrideBytes = 8;
+ int nOffsetBytes = 0;
+ if ( ( targetFormat == IMAGE_FORMAT_DXT3 ) || ( targetFormat == IMAGE_FORMAT_DXT5 ) )
+ {
+ nOffsetBytes = 8;
+ nStrideBytes = 16;
+ }
+
+ for ( int i = 0; i < ( targetImageSize / nStrideBytes ); i++ ) // For each color or color/alpha block
+ {
+ // Get 16bit 565 colors into an unsigned short
+ unsigned short n565Color0 = 0;
+ n565Color0 |= ( ( unsigned short )( unsigned char )( pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 0 ] ) ) << 8;
+ n565Color0 |= ( ( unsigned short )( unsigned char )( pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 1 ] ) );
+
+ unsigned short n565Color1 = 0;
+ n565Color1 |= ( ( unsigned short )( unsigned char )( pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 2 ] ) ) << 8;
+ n565Color1 |= ( ( unsigned short )( unsigned char )( pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 3 ] ) );
+
+ // Convert to 888
+ unsigned char v888Color0[3];
+ v888Color0[0] = ( ( ( n565Color0 >> 11 ) & 0x1f ) << 3 );
+ v888Color0[1] = ( ( ( n565Color0 >> 5 ) & 0x3f ) << 2 );
+ v888Color0[2] = ( ( n565Color0 & 0x1f ) << 3 );
+
+ // Since we have one bit less of red and blue, add some of the error back in
+ if ( v888Color0[0] != 0 ) // Don't mess with black pixels
+ v888Color0[0] |= 0x04; // Add 0.5 of the error
+ if ( v888Color0[2] != 0 ) // Don't mess with black pixels
+ v888Color0[2] |= 0x04; // Add 0.5 of the error
+
+ unsigned char v888Color1[3];
+ v888Color1[0] = ( ( ( n565Color1 >> 11 ) & 0x1f ) << 3 );
+ v888Color1[1] = ( ( ( n565Color1 >> 5 ) & 0x3f ) << 2 );
+ v888Color1[2] = ( ( n565Color1 & 0x1f ) << 3 );
+
+ // Since we have one bit less of red and blue, add some of the error back in
+ if ( v888Color1[0] != 0 ) // Don't mess with black pixels
+ v888Color1[0] |= 0x04; // Add 0.5 of the error
+ if ( v888Color1[2] != 0 ) // Don't mess with black pixels
+ v888Color1[2] |= 0x04; // Add 0.5 of the error
+
+ // Convert to float
+ float vFlColor0[3];
+ vFlColor0[0] = float( v888Color0[0] ) / 255.0f;
+ vFlColor0[1] = float( v888Color0[1] ) / 255.0f;
+ vFlColor0[2] = float( v888Color0[2] ) / 255.0f;
+
+ float vFlColor1[3];
+ vFlColor1[0] = float( v888Color1[0] ) / 255.0f;
+ vFlColor1[1] = float( v888Color1[1] ) / 255.0f;
+ vFlColor1[2] = float( v888Color1[2] ) / 255.0f;
+
+ // Modify float RGB data and write to output 888 colors
+ unsigned char v888Color0New[3];
+ unsigned char v888Color1New[3];
+ for ( int j = 0; j < 3; j++ ) // For red, green, blue
+ {
+ for ( int k = 0; k < 2; k++ ) // For color0 and color1
+ {
+ float *pFlValue = ( k == 0 ) ? &( vFlColor0[j] ) : &( vFlColor1[j] );
+ unsigned char *p8BitValue = ( k == 0 ) ? &( v888Color0New[j] ) : &( v888Color1New[j] );
+
+ float flSrgbGamma = *pFlValue;
+ float fl360Gamma = SrgbGammaTo360Gamma( flSrgbGamma );
+
+ fl360Gamma = clamp( fl360Gamma, 0.0f, 1.0f );
+ //*p8BitValue = ( unsigned char ) ( clamp( ( ( fl360Gamma * 255.0f ) + 0.5f ), 0.0f, 255.0f ) );
+ *p8BitValue = ( unsigned char ) ( clamp( ( ( fl360Gamma * 255.0f ) ), 0.0f, 255.0f ) );
+ }
+ }
+
+ // Convert back to 565
+ v888Color0New[0] &= 0xf8; // 5 bits
+ v888Color0New[1] &= 0xfc; // 6 bits
+ v888Color0New[2] &= 0xf8; // 5 bits
+ unsigned short n565Color0New = ( ( unsigned short )v888Color0New[0] << 8 ) | ( ( unsigned short )v888Color0New[1] << 3 ) | ( ( unsigned short )v888Color0New[2] >> 3 );
+
+ v888Color1New[0] &= 0xf8; // 5 bits
+ v888Color1New[1] &= 0xfc; // 6 bits
+ v888Color1New[2] &= 0xf8; // 5 bits
+ unsigned short n565Color1New = ( ( unsigned short )v888Color1New[0] << 8 ) | ( ( unsigned short )v888Color1New[1] << 3 ) | ( ( unsigned short )v888Color1New[2] >> 3 );
+
+ // If we're targeting DXT1, make sure we haven't made a non transparent color block transparent
+ if ( ( targetFormat == IMAGE_FORMAT_DXT1 ) || ( targetFormat == IMAGE_FORMAT_DXT1_ONEBITALPHA ) )
+ {
+ // If new block is transparent but old block wasn't
+ if ( ( n565Color0New <= n565Color1New ) && ( n565Color0 > n565Color1 ) )
+ {
+ if ( ( v888Color0New[0] == v888Color1New[0] ) && ( v888Color0[0] != v888Color1[0] ) )
+ {
+ if ( v888Color0New[0] == 0xf8 )
+ v888Color1New[0] -= 0x08;
+ else
+ v888Color0New[0] += 0x08;
+ }
+
+ if ( ( v888Color0New[1] == v888Color1New[1] ) && ( v888Color0[1] != v888Color1[1] ) )
+ {
+ if ( v888Color0New[1] == 0xfc )
+ v888Color1New[1] -= 0x04;
+ else
+ v888Color0New[1] += 0x04;
+ }
+
+ if ( ( v888Color0New[2] == v888Color1New[2] ) && ( v888Color0[2] != v888Color1[2] ) )
+ {
+ if ( v888Color0New[2] == 0xf8 )
+ v888Color1New[2] -= 0x08;
+ else
+ v888Color0New[2] += 0x08;
+ }
+
+ n565Color0New = ( ( unsigned short )v888Color0New[0] << 8 ) | ( ( unsigned short )v888Color0New[1] << 3 ) | ( ( unsigned short )v888Color0New[2] >> 3 );
+ n565Color1New = ( ( unsigned short )v888Color1New[0] << 8 ) | ( ( unsigned short )v888Color1New[1] << 3 ) | ( ( unsigned short )v888Color1New[2] >> 3 );
+ }
+ }
+
+ // Copy new colors back to color block
+ pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 0 ] = ( unsigned char )( ( n565Color0New >> 8 ) & 0x00ff );
+ pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 1 ] = ( unsigned char )( n565Color0New & 0x00ff );
+
+ pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 2 ] = ( unsigned char )( ( n565Color1New >> 8 ) & 0x00ff );
+ pTargetImage[ ( i * nStrideBytes ) + nOffsetBytes + 3 ] = ( unsigned char )( n565Color1New & 0x00ff );
+ }
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Write the source data as the desired format into a target buffer
+//-----------------------------------------------------------------------------
+bool SerializeImageData( IVTFTexture *pSourceVTF, int frame, int face, int mip, ImageFormat targetFormat, CUtlBuffer &targetBuf )
+{
+ int width;
+ int height;
+ int targetImageSize;
+ byte *pSourceImage;
+ int sourceImageSize;
+ int targetSize;
+ CUtlMemory<byte> targetImage;
+
+ width = pSourceVTF->Width() >> mip;
+ height = pSourceVTF->Height() >> mip;
+ if ( width < 1 )
+ width = 1;
+ if ( height < 1)
+ height = 1;
+
+ sourceImageSize = ImageLoader::GetMemRequired( width, height, 1, pSourceVTF->Format(), false );
+ pSourceImage = pSourceVTF->ImageData( frame, face, mip );
+
+ targetImageSize = ImageLoader::GetMemRequired( width, height, 1, targetFormat, false );
+ targetImage.EnsureCapacity( targetImageSize );
+ byte *pTargetImage = (byte*)targetImage.Base();
+
+ // conversion may skip bytes, ensure all bits initialized
+ memset( pTargetImage, 0xFF, targetImageSize );
+
+ // format conversion expects pc oriented data
+ bool bRetVal = ConvertImageFormatEx(
+ pSourceImage,
+ sourceImageSize,
+ pSourceVTF->Format(),
+ pTargetImage,
+ targetImageSize,
+ targetFormat,
+ width,
+ height,
+ ( pSourceVTF->Flags() & TEXTUREFLAGS_SRGB ) ? true : false );
+ if ( !bRetVal )
+ {
+ return false;
+ }
+
+//X360TBD: incorrect byte order
+// // fixup mip dependent data
+// if ( ( pSourceVTF->Flags() & TEXTUREFLAGS_ONEOVERMIPLEVELINALPHA ) && ( targetFormat == IMAGE_FORMAT_BGRA8888 ) )
+// {
+// unsigned char ooMipLevel = ( unsigned char )( 255.0f * ( 1.0f / ( float )( 1 << mip ) ) );
+// int i;
+//
+// for ( i=0; i<width*height; i++ )
+// {
+// pTargetImage[i*4+3] = ooMipLevel;
+// }
+// }
+
+ targetSize = targetBuf.Size() + targetImageSize;
+ targetBuf.EnsureCapacity( targetSize );
+ targetBuf.Put( pTargetImage, targetImageSize );
+ if ( !targetBuf.IsValid() )
+ {
+ return false;
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Generate the 360 target into a buffer
+//-----------------------------------------------------------------------------
+bool ConvertVTFTo360Format( const char *pDebugName, CUtlBuffer &sourceBuf, CUtlBuffer &targetBuf, CompressFunc_t pCompressFunc )
+{
+ bool bRetVal;
+ IVTFTexture *pSourceVTF;
+ int targetWidth;
+ int targetHeight;
+ int targetMipCount;
+ VTFFileHeaderX360_t targetHeader;
+ int frame;
+ int face;
+ int mip;
+ ImageFormat targetFormat;
+ int targetLowResWidth;
+ int targetLowResHeight;
+ int targetFlags;
+ int mipSkipCount;
+ int targetFaceCount;
+ int preloadDataSize;
+ int targetImageDataOffset;
+ int targetFrameCount;
+ VTFFileHeaderV7_1_t *pVTFHeader71;
+ bool bNoMip;
+ CByteswap byteSwapWriter;
+ CUtlVector< ResourceCopy_t > targetResources;
+ bool bHasLowResData = false;
+ unsigned int resourceTypes[MAX_RSRC_DICTIONARY_ENTRIES];
+ unsigned char targetLowResSample[4];
+ int numTypes;
+
+ // Only need to byte swap writes if we are running the coversion on the PC, and data will be read from 360
+ byteSwapWriter.ActivateByteSwapping( !IsX360() );
+
+ // need mathlib
+ MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f );
+
+ // default failure
+ bRetVal = false;
+
+ pSourceVTF = NULL;
+
+ // unserialize the vtf with just the header
+ pSourceVTF = CreateVTFTexture();
+ if ( !pSourceVTF->Unserialize( sourceBuf, true, 0 ) )
+ goto cleanUp;
+
+ // volume textures not supported
+ if ( pSourceVTF->Depth() != 1 )
+ goto cleanUp;
+
+ if ( !ImageLoader::IsFormatValidForConversion( pSourceVTF->Format() ) )
+ goto cleanUp;
+
+ if ( !ComputeTargetDimensions( pDebugName, pSourceVTF, 0, targetWidth, targetHeight, targetMipCount, mipSkipCount, bNoMip ) )
+ goto cleanUp;
+
+ // must crack vtf file to determine if mip levels exist from header
+ // vtf interface does not expose the true presence of this data
+ pVTFHeader71 = (VTFFileHeaderV7_1_t*)sourceBuf.Base();
+ if ( mipSkipCount >= pVTFHeader71->numMipLevels )
+ {
+ // can't skip mips that aren't there
+ // ideally should just reconstruct them
+ goto cleanUp;
+ }
+
+ // unserialize the vtf with all the data configured with the desired starting mip
+ sourceBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ if ( !pSourceVTF->Unserialize( sourceBuf, false, mipSkipCount ) )
+ {
+ Msg( "ConvertVTFTo360Format: Error reading in %s\n", pDebugName );
+ goto cleanUp;
+ }
+
+ // add the default resource image
+ ResourceCopy_t resourceCopy;
+ resourceCopy.m_EntryInfo.eType = VTF_LEGACY_RSRC_IMAGE;
+ resourceCopy.m_EntryInfo.resData = 0;
+ resourceCopy.m_pData = NULL;
+ resourceCopy.m_DataLength = 0;
+ targetResources.AddToTail( resourceCopy );
+
+ // get the resources
+ numTypes = pSourceVTF->GetResourceTypes( resourceTypes, MAX_RSRC_DICTIONARY_ENTRIES );
+ for ( int i=0; i<numTypes; i++ )
+ {
+ size_t resourceLength;
+ void *pResourceData;
+
+ switch ( resourceTypes[i] & ~RSRCF_MASK )
+ {
+ case VTF_LEGACY_RSRC_LOW_RES_IMAGE:
+ case VTF_LEGACY_RSRC_IMAGE:
+ case VTF_RSRC_TEXTURE_LOD_SETTINGS:
+ case VTF_RSRC_TEXTURE_SETTINGS_EX:
+ case VTF_RSRC_TEXTURE_CRC:
+ // not needed, presence already folded into conversion
+ continue;
+
+ default:
+ pResourceData = pSourceVTF->GetResourceData( resourceTypes[i], &resourceLength );
+ if ( pResourceData )
+ {
+ resourceCopy.m_EntryInfo.eType = resourceTypes[i] & ~RSRCF_MASK;
+ resourceCopy.m_EntryInfo.resData = 0;
+ resourceCopy.m_pData = new char[resourceLength];
+ resourceCopy.m_DataLength = resourceLength;
+ V_memcpy( resourceCopy.m_pData, pResourceData, resourceLength );
+ targetResources.AddToTail( resourceCopy );
+ }
+ break;
+ }
+ }
+
+ if ( targetResources.Count() > MAX_X360_RSRC_DICTIONARY_ENTRIES )
+ {
+ Msg( "ConvertVTFTo360Format: More resources than expected in %s\n", pDebugName );
+ goto cleanUp;
+ }
+
+ targetFlags = pSourceVTF->Flags();
+ targetFrameCount = pSourceVTF->FrameCount();
+
+ // skip over spheremap
+ targetFaceCount = pSourceVTF->FaceCount();
+ if ( targetFaceCount == CUBEMAP_FACE_COUNT )
+ {
+ targetFaceCount = CUBEMAP_FACE_COUNT-1;
+ }
+
+ // determine target format
+ targetFormat = PreferredFormat( pSourceVTF, pSourceVTF->Format(), targetWidth, targetHeight, targetMipCount, targetFaceCount );
+
+ // reset nomip flags
+ if ( bNoMip )
+ {
+ targetFlags |= TEXTUREFLAGS_NOMIP;
+ }
+ else
+ {
+ targetFlags &= ~TEXTUREFLAGS_NOMIP;
+ }
+
+ // the lowres texture is used for coarse light sampling lookups
+ bHasLowResData = ( pSourceVTF->LowResFormat() != -1 ) && pSourceVTF->LowResWidth() && pSourceVTF->LowResHeight();
+ if ( bHasLowResData )
+ {
+ // ensure lowres data is serialized in preferred runtime expected format
+ targetLowResWidth = pSourceVTF->LowResWidth();
+ targetLowResHeight = pSourceVTF->LowResHeight();
+ }
+ else
+ {
+ // discarding low res data, ensure lowres data is culled
+ targetLowResWidth = 0;
+ targetLowResHeight = 0;
+ }
+
+ // start serializing output data
+ // skip past header
+ // serialize in order, 0) Header 1) ResourceDictionary, 3) Resources, 4) image
+ // preload may extend into image
+ targetBuf.EnsureCapacity( sizeof( VTFFileHeaderX360_t ) + targetResources.Count() * sizeof( ResourceEntryInfo ) );
+ targetBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, sizeof( VTFFileHeaderX360_t ) + targetResources.Count() * sizeof( ResourceEntryInfo ) );
+
+ // serialize low res
+ if ( targetLowResWidth && targetLowResHeight )
+ {
+ CUtlMemory<byte> targetLowResImage;
+
+ int sourceLowResImageSize = ImageLoader::GetMemRequired( pSourceVTF->LowResWidth(), pSourceVTF->LowResHeight(), 1, pSourceVTF->LowResFormat(), false );
+ int targetLowResImageSize = ImageLoader::GetMemRequired( targetLowResWidth, targetLowResHeight, 1, IMAGE_FORMAT_RGB888, false );
+
+ // conversion may skip bytes, ensure all bits initialized
+ targetLowResImage.EnsureCapacity( targetLowResImageSize );
+ byte* pTargetLowResImage = (byte*)targetLowResImage.Base();
+ memset( pTargetLowResImage, 0xFF, targetLowResImageSize );
+
+ // convert and save lowres image in final format
+ bRetVal = ConvertImageFormatEx(
+ pSourceVTF->LowResImageData(),
+ sourceLowResImageSize,
+ pSourceVTF->LowResFormat(),
+ pTargetLowResImage,
+ targetLowResImageSize,
+ IMAGE_FORMAT_RGB888,
+ targetLowResWidth,
+ targetLowResHeight,
+ false );
+ if ( !bRetVal )
+ {
+ goto cleanUp;
+ }
+
+ // boil to a single linear color
+ Vector linearColor;
+ linearColor.x = linearColor.y = linearColor.z = 0;
+ for ( int j = 0; j < targetLowResWidth * targetLowResHeight; j++ )
+ {
+ linearColor.x += SrgbGammaToLinear( pTargetLowResImage[j*3+0] * 1.0f/255.0f );
+ linearColor.y += SrgbGammaToLinear( pTargetLowResImage[j*3+1] * 1.0f/255.0f );
+ linearColor.z += SrgbGammaToLinear( pTargetLowResImage[j*3+2] * 1.0f/255.0f );
+ }
+ VectorScale( linearColor, 1.0f/(targetLowResWidth * targetLowResHeight), linearColor );
+
+ // serialize as a single texel
+ targetLowResSample[0] = 255.0f * SrgbLinearToGamma( linearColor[0] );
+ targetLowResSample[1] = 255.0f * SrgbLinearToGamma( linearColor[1] );
+ targetLowResSample[2] = 255.0f * SrgbLinearToGamma( linearColor[2] );
+
+ // identifies color presence
+ targetLowResSample[3] = 0xFF;
+ }
+ else
+ {
+ targetLowResSample[0] = 0;
+ targetLowResSample[1] = 0;
+ targetLowResSample[2] = 0;
+ targetLowResSample[3] = 0;
+ }
+
+ // serialize resource data
+ for ( int i=0; i<targetResources.Count(); i++ )
+ {
+ int resourceDataLength = targetResources[i].m_DataLength;
+ if ( resourceDataLength == 4 )
+ {
+ // data goes directly into structure, as is
+ targetResources[i].m_EntryInfo.eType |= RSRCF_HAS_NO_DATA_CHUNK;
+ V_memcpy( &targetResources[i].m_EntryInfo.resData, targetResources[i].m_pData, 4 );
+ }
+ else if ( resourceDataLength != 0 )
+ {
+ targetResources[i].m_EntryInfo.resData = targetBuf.TellPut();
+ int swappedLength = 0;
+ byteSwapWriter.SwapBufferToTargetEndian( &swappedLength, &resourceDataLength );
+ targetBuf.PutInt( swappedLength );
+ if ( !targetBuf.IsValid() )
+ {
+ goto cleanUp;
+ }
+
+ // put the data
+ targetBuf.Put( targetResources[i].m_pData, resourceDataLength );
+ if ( !targetBuf.IsValid() )
+ {
+ goto cleanUp;
+ }
+ }
+ }
+
+ // mark end of preload data
+ // preload data might be updated and pushed to extend into the image data mip chain
+ preloadDataSize = targetBuf.TellPut();
+
+ // image starts on an aligned boundary
+ AlignBuffer( targetBuf, 4 );
+
+ // start of image data
+ targetImageDataOffset = targetBuf.TellPut();
+ if ( targetImageDataOffset >= 65536 )
+ {
+ // possible bug, or may have to offset to 32 bits
+ Msg( "ConvertVTFTo360Format: non-image portion exceeds 16 bit boundary %s\n", pDebugName );
+ goto cleanUp;
+ }
+
+ // format conversion, data is stored by ascending mips, 1x1 up to NxN
+ // data is stored ascending to allow picmipped loads
+ for ( mip = targetMipCount - 1; mip >= 0; mip-- )
+ {
+ for ( frame = 0; frame < targetFrameCount; frame++ )
+ {
+ for ( face = 0; face < targetFaceCount; face++ )
+ {
+ if ( !SerializeImageData( pSourceVTF, frame, face, mip, targetFormat, targetBuf ) )
+ {
+ goto cleanUp;
+ }
+ }
+ }
+ }
+
+ if ( preloadDataSize < VTFFileHeaderSize( VTF_X360_MAJOR_VERSION, VTF_X360_MINOR_VERSION ) )
+ {
+ // preload size must be at least what game attempts to initially read
+ preloadDataSize = VTFFileHeaderSize( VTF_X360_MAJOR_VERSION, VTF_X360_MINOR_VERSION );
+ }
+
+ if ( targetBuf.TellPut() <= PRELOAD_VTF_THRESHOLD )
+ {
+ // the entire file is too small, preload entirely
+ preloadDataSize = targetBuf.TellPut();
+ }
+
+ if ( preloadDataSize >= 65536 )
+ {
+ // possible overflow due to large frames, faces, and format, may have to offset to 32 bits
+ Msg( "ConvertVTFTo360Format: preload portion exceeds 16 bit boundary %s\n", pDebugName );
+ goto cleanUp;
+ }
+
+ // finalize header
+ V_memset( &targetHeader, 0, sizeof( VTFFileHeaderX360_t ) );
+
+ V_memcpy( targetHeader.fileTypeString, "VTFX", 4 );
+ targetHeader.version[0] = VTF_X360_MAJOR_VERSION;
+ targetHeader.version[1] = VTF_X360_MINOR_VERSION;
+ targetHeader.headerSize = sizeof( VTFFileHeaderX360_t ) + targetResources.Count() * sizeof( ResourceEntryInfo );
+
+ targetHeader.flags = targetFlags;
+ targetHeader.width = targetWidth;
+ targetHeader.height = targetHeight;
+ targetHeader.depth = 1;
+ targetHeader.numFrames = targetFrameCount;
+ targetHeader.preloadDataSize = preloadDataSize;
+ targetHeader.mipSkipCount = mipSkipCount;
+ targetHeader.numResources = targetResources.Count();
+ VectorCopy( pSourceVTF->Reflectivity(), targetHeader.reflectivity );
+ targetHeader.bumpScale = pSourceVTF->BumpScale();
+ targetHeader.imageFormat = targetFormat;
+ targetHeader.lowResImageSample[0] = targetLowResSample[0];
+ targetHeader.lowResImageSample[1] = targetLowResSample[1];
+ targetHeader.lowResImageSample[2] = targetLowResSample[2];
+ targetHeader.lowResImageSample[3] = targetLowResSample[3];
+
+ if ( !IsX360() )
+ {
+ byteSwapWriter.SwapFieldsToTargetEndian( &targetHeader );
+ }
+
+ // write out finalized header
+ targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
+ targetBuf.Put( &targetHeader, sizeof( VTFFileHeaderX360_t ) );
+ if ( !targetBuf.IsValid() )
+ {
+ goto cleanUp;
+ }
+
+ // fixup and write out finalized resource dictionary
+ for ( int i=0; i<targetResources.Count(); i++ )
+ {
+ switch ( targetResources[i].m_EntryInfo.eType & ~RSRCF_MASK )
+ {
+ case VTF_LEGACY_RSRC_IMAGE:
+ targetResources[i].m_EntryInfo.resData = targetImageDataOffset;
+ break;
+ }
+
+ if ( !( targetResources[i].m_EntryInfo.eType & RSRCF_HAS_NO_DATA_CHUNK ) )
+ {
+ // swap the offset holders only
+ byteSwapWriter.SwapBufferToTargetEndian( &targetResources[i].m_EntryInfo.resData );
+ }
+
+ targetBuf.Put( &targetResources[i].m_EntryInfo, sizeof( ResourceEntryInfo ) );
+ if ( !targetBuf.IsValid() )
+ {
+ goto cleanUp;
+ }
+ }
+
+ targetBuf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
+
+ if ( preloadDataSize < targetBuf.TellPut() && pCompressFunc )
+ {
+ // only compress files that are not entirely in preload
+ CUtlBuffer compressedBuffer;
+ targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, targetImageDataOffset );
+ bool bCompressed = pCompressFunc( targetBuf, compressedBuffer );
+ if ( bCompressed )
+ {
+ // copy all the header data off
+ CUtlBuffer headerBuffer;
+ headerBuffer.EnsureCapacity( targetImageDataOffset );
+ headerBuffer.Put( targetBuf.Base(), targetImageDataOffset );
+
+ // reform the target with the header and then the compressed data
+ targetBuf.Clear();
+ targetBuf.Put( headerBuffer.Base(), targetImageDataOffset );
+ targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() );
+
+ VTFFileHeaderX360_t *pHeader = (VTFFileHeaderX360_t *)targetBuf.Base();
+ if ( !IsX360() )
+ {
+ // swap it back into pc space
+ byteSwapWriter.SwapFieldsToTargetEndian( pHeader );
+ }
+
+ pHeader->compressedSize = compressedBuffer.TellPut();
+
+ if ( !IsX360() )
+ {
+ // swap it back into 360 space
+ byteSwapWriter.SwapFieldsToTargetEndian( pHeader );
+ }
+ }
+
+ targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ }
+
+ // success
+ bRetVal = true;
+
+cleanUp:
+ if ( pSourceVTF )
+ {
+ DestroyVTFTexture( pSourceVTF );
+ }
+
+ for ( int i=0; i<targetResources.Count(); i++ )
+ {
+ delete [] (char *)targetResources[i].m_pData;
+ targetResources[i].m_pData = NULL;
+ }
+
+ return bRetVal;
+}
+
+//-----------------------------------------------------------------------------
+// Copy the 360 preload data into a buffer. Used by tools to request the preload,
+// as part of the preload build process. Caller doesn't have to know cracking details.
+// Not to be used at gametime.
+//-----------------------------------------------------------------------------
+bool GetVTFPreload360Data( const char *pDebugName, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut )
+{
+ preloadBufferOut.Purge();
+
+ fileBufferIn.ActivateByteSwapping( IsPC() );
+
+ VTFFileHeaderX360_t header;
+ fileBufferIn.GetObjects( &header );
+
+ if ( V_strnicmp( header.fileTypeString, "VTFX", 4 ) ||
+ header.version[0] != VTF_X360_MAJOR_VERSION ||
+ header.version[1] != VTF_X360_MINOR_VERSION )
+ {
+ // bad format
+ return false;
+ }
+
+ preloadBufferOut.EnsureCapacity( header.preloadDataSize );
+ preloadBufferOut.Put( fileBufferIn.Base(), header.preloadDataSize );
+
+ return true;
+}
diff --git a/vtf/cvtf.h b/vtf/cvtf.h
new file mode 100644
index 0000000..3965de9
--- /dev/null
+++ b/vtf/cvtf.h
@@ -0,0 +1,441 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Local header for CVTFTexture class declaration - allows platform-specific
+// implementation to be placed in separate cpp files.
+//
+// $NoKeywords: $
+//===========================================================================//
+
+#ifndef CVTF_H
+#define CVTF_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "s3tc_decode.h"
+#include "vtf/vtf.h"
+#include "byteswap.h"
+#include "filesystem.h"
+
+class CEdgePos
+{
+public:
+ CEdgePos() {}
+ CEdgePos( int ix, int iy )
+ {
+ x = ix;
+ y = iy;
+ }
+
+ void operator +=( const CEdgePos &other )
+ {
+ x += other.x;
+ y += other.y;
+ }
+
+ void operator /=( int val )
+ {
+ x /= val;
+ y /= val;
+ }
+
+ CEdgePos operator >>( int shift )
+ {
+ return CEdgePos( x >> shift, y >> shift );
+ }
+
+ CEdgePos operator *( int shift )
+ {
+ return CEdgePos( x * shift, y * shift );
+ }
+
+ CEdgePos operator -( const CEdgePos &other )
+ {
+ return CEdgePos( x - other.x, y - other.y );
+ }
+
+ CEdgePos operator +( const CEdgePos &other )
+ {
+ return CEdgePos( x + other.x, y + other.y );
+ }
+
+ bool operator!=( const CEdgePos &other )
+ {
+ return !( *this == other );
+ }
+
+ bool operator==( const CEdgePos &other )
+ {
+ return x==other.x && y==other.y;
+ }
+
+ int x, y;
+};
+
+
+class CEdgeIncrements
+{
+public:
+ CEdgePos iFace1Start, iFace1End;
+ CEdgePos iFace1Inc, iFace2Inc;
+ CEdgePos iFace2Start, iFace2End;
+};
+
+
+class CEdgeMatch
+{
+public:
+ int m_iFaces[2]; // Which faces are touching.
+ int m_iEdges[2]; // Which edge on each face is touching.
+ int m_iCubeVerts[2];// Which of the cube's verts comprise this edge?
+ bool m_bFlipFace2Edge;
+};
+
+
+class CCornerMatch
+{
+public:
+ // The info for the 3 edges that match at this corner.
+ int m_iFaces[3];
+ int m_iFaceEdges[3];
+};
+
+
+class CEdgeFaceIndex
+{
+public:
+ int m_iEdge;
+ int m_iFace;
+};
+
+
+#define NUM_EDGE_MATCHES 12
+#define NUM_CORNER_MATCHES 8
+
+
+//-----------------------------------------------------------------------------
+// Implementation of the VTF Texture
+//-----------------------------------------------------------------------------
+class CVTFTexture : public IVTFTexture
+{
+public:
+ CVTFTexture();
+ virtual ~CVTFTexture();
+
+ virtual bool Init( int nWidth, int nHeight, int nDepth, ImageFormat fmt, int iFlags, int iFrameCount, int nForceMipCount );
+
+ // Methods to initialize the low-res image
+ virtual void InitLowResImage( int nWidth, int nHeight, ImageFormat fmt );
+
+ virtual void *SetResourceData( uint32 eType, void const *pData, size_t nDataSize );
+ virtual void *GetResourceData( uint32 eType, size_t *pDataSize ) const;
+
+ // Locates the resource entry info if it's present, easier than crawling array types
+ virtual bool HasResourceEntry( uint32 eType ) const;
+
+ // Retrieve available resource types of this IVTFTextures
+ // arrTypesBuffer buffer to be filled with resource types available.
+ // numTypesBufferElems how many resource types the buffer can accomodate.
+ // Returns:
+ // number of resource types available (can be greater than "numTypesBufferElems"
+ // in which case only first "numTypesBufferElems" are copied to "arrTypesBuffer")
+ virtual unsigned int GetResourceTypes( uint32 *arrTypesBuffer, int numTypesBufferElems ) const;
+
+ // Methods to set other texture fields
+ virtual void SetBumpScale( float flScale );
+ virtual void SetReflectivity( const Vector &vecReflectivity );
+
+ // These are methods to help with optimization of file access
+ virtual void LowResFileInfo( int *pStartLocation, int *pSizeInBytes ) const;
+ virtual void ImageFileInfo( int nFrame, int nFace, int nMip, int *pStartLocation, int *pSizeInBytes) const;
+ virtual int FileSize( int nMipSkipCount = 0 ) const;
+
+ // When unserializing, we can skip a certain number of mip levels,
+ // and we also can just load everything but the image data
+ virtual bool Unserialize( CUtlBuffer &buf, bool bBufferHeaderOnly = false, int nSkipMipLevels = 0 );
+ virtual bool UnserializeEx( CUtlBuffer &buf, bool bHeaderOnly = false, int nForceFlags = 0, int nSkipMipLevels = 0 );
+ virtual bool Serialize( CUtlBuffer &buf );
+
+ virtual void GetMipmapRange( int* pOutFinest, int* pOutCoarsest );
+
+ // Attributes...
+ virtual int Width() const;
+ virtual int Height() const;
+ virtual int Depth() const;
+ virtual int MipCount() const;
+
+ virtual int RowSizeInBytes( int nMipLevel ) const;
+ virtual int FaceSizeInBytes( int nMipLevel ) const;
+
+ virtual ImageFormat Format() const;
+ virtual int FaceCount() const;
+ virtual int FrameCount() const;
+ virtual int Flags() const;
+
+ virtual float BumpScale() const;
+ virtual const Vector &Reflectivity() const;
+
+ virtual bool IsCubeMap() const;
+ virtual bool IsNormalMap() const;
+ virtual bool IsVolumeTexture() const;
+
+ virtual int LowResWidth() const;
+ virtual int LowResHeight() const;
+ virtual ImageFormat LowResFormat() const;
+
+ // Computes the size (in bytes) of a single mipmap of a single face of a single frame
+ virtual int ComputeMipSize( int iMipLevel ) const;
+
+ // Computes the size (in bytes) of a single face of a single frame
+ // All mip levels starting at the specified mip level are included
+ virtual int ComputeFaceSize( int iStartingMipLevel = 0 ) const;
+
+ // Computes the total size of all faces, all frames
+ virtual int ComputeTotalSize( ) const;
+
+ // Computes the dimensions of a particular mip level
+ virtual void ComputeMipLevelDimensions( int iMipLevel, int *pWidth, int *pHeight, int *pMipDepth ) const;
+
+ // Computes the size of a subrect (specified at the top mip level) at a particular lower mip level
+ virtual void ComputeMipLevelSubRect( Rect_t* pSrcRect, int nMipLevel, Rect_t *pSubRect ) const;
+
+ // Returns the base address of the image data
+ virtual unsigned char *ImageData();
+
+ // Returns a pointer to the data associated with a particular frame, face, and mip level
+ virtual unsigned char *ImageData( int iFrame, int iFace, int iMipLevel );
+
+ // Returns a pointer to the data associated with a particular frame, face, mip level, and offset
+ virtual unsigned char *ImageData( int iFrame, int iFace, int iMipLevel, int x, int y, int z );
+
+ // Returns the base address of the low-res image data
+ virtual unsigned char *LowResImageData();
+
+ // Converts the texture's image format. Use IMAGE_FORMAT_DEFAULT
+ virtual void ConvertImageFormat( ImageFormat fmt, bool bNormalToDUDV );
+
+ // Generate spheremap based on the current cube faces (only works for cubemaps)
+ // The look dir indicates the direction of the center of the sphere
+ virtual void GenerateSpheremap( LookDir_t lookDir );
+
+ virtual void GenerateHemisphereMap( unsigned char *pSphereMapBitsRGBA, int targetWidth,
+ int targetHeight, LookDir_t lookDir, int iFrame );
+
+ // Fixes the cubemap faces orientation from our standard to the
+ // standard the material system needs.
+ virtual void FixCubemapFaceOrientation( );
+
+ // Normalize the top mip level if necessary
+ virtual void NormalizeTopMipLevel();
+
+ // Generates mipmaps from the base mip levels
+ virtual void GenerateMipmaps();
+
+ // Put 1/miplevel (1..n) into alpha.
+ virtual void PutOneOverMipLevelInAlpha();
+
+ // Computes the reflectivity
+ virtual void ComputeReflectivity( );
+
+ // Computes the alpha flags
+ virtual void ComputeAlphaFlags();
+
+ // Gets the texture all internally consistent assuming you've loaded
+ // mip 0 of all faces of all frames
+ virtual void PostProcess(bool bGenerateSpheremap, LookDir_t lookDir = LOOK_DOWN_Z, bool bAllowFixCubemapOrientation = true);
+ virtual void SetPostProcessingSettings( VtfProcessingOptions const *pOptions );
+
+ // Generate the low-res image bits
+ virtual bool ConstructLowResImage();
+
+ virtual void MatchCubeMapBorders( int iStage, ImageFormat finalFormat, bool bSkybox );
+
+ // Sets threshhold values for alphatest mipmapping
+ virtual void SetAlphaTestThreshholds( float flBase, float flHighFreq );
+
+#if defined( _X360 )
+ virtual int UpdateOrCreate( const char *pFilename, const char *pPathID = NULL, bool bForce = false );
+ virtual int FileSize( bool bPreloadOnly, int nMipSkipCount ) const;
+ virtual bool UnserializeFromBuffer( CUtlBuffer &buf, bool bBufferIsVolatile, bool bHeaderOnly, bool bPreloadOnly, int nMipSkipCount );
+ virtual bool IsPreTiled() const;
+ virtual int MappingWidth() const;
+ virtual int MappingHeight() const;
+ virtual int MappingDepth() const;
+ virtual int MipSkipCount() const;
+ virtual unsigned char *LowResImageSample();
+ virtual void ReleaseImageMemory();
+#endif
+
+private:
+ // Unserialization
+ bool ReadHeader( CUtlBuffer &buf, VTFFileHeader_t &header );
+
+ void BlendCubeMapEdgePalettes(
+ int iFrame,
+ int iMipLevel,
+ const CEdgeMatch *pMatch );
+
+ void BlendCubeMapCornerPalettes(
+ int iFrame,
+ int iMipLevel,
+ const CCornerMatch *pMatch );
+
+ void MatchCubeMapS3TCPalettes(
+ CEdgeMatch edgeMatches[NUM_EDGE_MATCHES],
+ CCornerMatch cornerMatches[NUM_CORNER_MATCHES]
+ );
+
+ void SetupFaceVert( int iMipLevel, int iVert, CEdgePos &out );
+ void SetupEdgeIncrement( CEdgePos &start, CEdgePos &end, CEdgePos &inc );
+
+ void SetupTextureEdgeIncrements(
+ int iMipLevel,
+ int iFace1Edge,
+ int iFace2Edge,
+ bool bFlipFace2Edge,
+ CEdgeIncrements *incs );
+
+ void BlendCubeMapFaceEdges(
+ int iFrame,
+ int iMipLevel,
+ const CEdgeMatch *pMatch );
+
+ void BlendCubeMapFaceCorners(
+ int iFrame,
+ int iMipLevel,
+ const CCornerMatch *pMatch );
+
+ void BuildCubeMapMatchLists( CEdgeMatch edgeMatches[NUM_EDGE_MATCHES], CCornerMatch cornerMatches[NUM_CORNER_MATCHES], bool bSkybox );
+
+ // Allocate image data blocks with an eye toward re-using memory
+ bool AllocateImageData( int nMemorySize );
+ bool AllocateLowResImageData( int nMemorySize );
+
+ // Compute the mip count based on the size + flags
+ int ComputeMipCount( ) const;
+
+ // Unserialization of low-res data
+ bool LoadLowResData( CUtlBuffer &buf );
+
+ // Unserialization of new resource data
+ bool LoadNewResources( CUtlBuffer &buf );
+
+ // Unserialization of image data
+ bool LoadImageData( CUtlBuffer &buf, const VTFFileHeader_t &header, int nSkipMipLevels );
+
+ // Shutdown
+ void Shutdown();
+ void ReleaseResources();
+
+ // Makes a single frame of spheremap
+ void ComputeSpheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir );
+
+ // Makes a single frame of spheremap
+ void ComputeHemispheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir );
+
+ // Serialization of image data
+ bool WriteImageData( CUtlBuffer &buf );
+
+ // Computes the size (in bytes) of a single mipmap of a single face of a single frame
+ int ComputeMipSize( int iMipLevel, ImageFormat fmt ) const;
+
+ // Computes the size (in bytes) of a single face of a single frame
+ // All mip levels starting at the specified mip level are included
+ int ComputeFaceSize( int iStartingMipLevel, ImageFormat fmt ) const;
+
+ // Computes the total size of all faces, all frames
+ int ComputeTotalSize( ImageFormat fmt ) const;
+
+ // Computes the location of a particular face, frame, and mip level
+ int GetImageOffset( int iFrame, int iFace, int iMipLevel, ImageFormat fmt ) const;
+
+ // Determines if the vtf or vtfx file needs to be swapped to the current platform
+ bool SetupByteSwap( CUtlBuffer &buf );
+
+ // Locates the resource entry info if it's present
+ ResourceEntryInfo *FindResourceEntryInfo( unsigned int eType );
+ ResourceEntryInfo const *FindResourceEntryInfo( unsigned int eType ) const;
+
+ // Inserts the resource entry info if it's not present
+ ResourceEntryInfo *FindOrCreateResourceEntryInfo( unsigned int eType );
+
+ // Removes the resource entry info if it's present
+ bool RemoveResourceEntryInfo( unsigned int eType );
+
+#if defined( _X360 )
+ bool ReadHeader( CUtlBuffer &buf, VTFFileHeaderX360_t &header );
+ bool LoadImageData( CUtlBuffer &buf, bool bBufferIsVolatile, int nMipSkipCount );
+#endif
+
+private:
+ // This is to make sure old-format .vtf files are read properly
+ int m_nVersion[2];
+
+ int m_nWidth;
+ int m_nHeight;
+ int m_nDepth;
+ ImageFormat m_Format;
+
+ int m_nMipCount;
+ int m_nFaceCount;
+ int m_nFrameCount;
+
+ int m_nImageAllocSize;
+ int m_nFlags;
+ unsigned char *m_pImageData;
+
+ Vector m_vecReflectivity;
+ float m_flBumpScale;
+
+ // FIXME: Do I need this?
+ int m_iStartFrame;
+
+ // Low res data
+ int m_nLowResImageAllocSize;
+ ImageFormat m_LowResImageFormat;
+ int m_nLowResImageWidth;
+ int m_nLowResImageHeight;
+ unsigned char *m_pLowResImageData;
+
+ // Used while fixing mipmap edges.
+ CUtlVector<S3RGBA> m_OriginalData;
+
+ // Alpha threshholds
+ float m_flAlphaThreshhold;
+ float m_flAlphaHiFreqThreshhold;
+
+ CByteswap m_Swap;
+
+ int m_nFinestMipmapLevel;
+ int m_nCoarsestMipmapLevel;
+
+#if defined( _X360 )
+ int m_iPreloadDataSize;
+ int m_iCompressedSize;
+ // resolves actual dimensions to/from mapping dimensions due to pre-picmipping
+ int m_nMipSkipCount;
+ unsigned char m_LowResImageSample[4];
+#endif
+
+ CUtlVector< ResourceEntryInfo > m_arrResourcesInfo;
+
+ struct ResourceMemorySection
+ {
+ ResourceMemorySection() { memset( this, 0, sizeof( *this ) ); }
+
+ int m_nDataAllocSize;
+ int m_nDataLength;
+ unsigned char *m_pData;
+
+ bool AllocateData( int nMemorySize );
+ bool LoadData( CUtlBuffer &buf, CByteswap &byteSwap );
+ bool WriteData( CUtlBuffer &buf ) const;
+ };
+ CUtlVector< ResourceMemorySection > m_arrResourcesData;
+ CUtlVector< ResourceMemorySection > m_arrResourcesData_ForReuse; // Maintained to keep allocated memory blocks when unserializing from files
+
+ VtfProcessingOptions m_Options;
+};
+
+#endif // CVTF_H
diff --git a/vtf/s3tc_decode.cpp b/vtf/s3tc_decode.cpp
new file mode 100644
index 0000000..65909eb
--- /dev/null
+++ b/vtf/s3tc_decode.cpp
@@ -0,0 +1,395 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+#include "bitmap/imageformat.h"
+#include "basetypes.h"
+#include "tier0/dbg.h"
+#include <malloc.h>
+#include <memory.h>
+#include "nvtc.h"
+#include "mathlib/mathlib.h"
+#include "mathlib/vector.h"
+#include "utlmemory.h"
+#include "tier1/strtools.h"
+#include "s3tc_decode.h"
+#include "utlvector.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// This is in s3tc.lib. Nvidia added it specially for us. It can be set to 4, 8, or 12.
+// When set to 8 or 12, it generates a palette given an 8x4 or 12x4 texture.
+extern int S3TC_BLOCK_WIDTH;
+
+
+class S3Palette
+{
+public:
+ S3RGBA m_Colors[4];
+};
+
+
+class S3TCBlock_DXT1
+{
+public:
+ unsigned short m_Ref1; // The two colors that this block blends betwixt.
+ unsigned short m_Ref2;
+ unsigned int m_PixelBits;
+};
+
+
+class S3TCBlock_DXT5
+{
+public:
+ unsigned char m_AlphaRef[2];
+ unsigned char m_AlphaBits[6];
+
+ unsigned short m_Ref1; // The two colors that this block blends betwixt.
+ unsigned short m_Ref2;
+ unsigned int m_PixelBits;
+};
+
+
+
+// ------------------------------------------------------------------------------------------ //
+// S3TCBlock
+// ------------------------------------------------------------------------------------------ //
+
+int ReadBitInt( const char *pBits, int iBaseBit, int nBits )
+{
+ int ret = 0;
+ for ( int i=0; i < nBits; i++ )
+ {
+ int iBit = iBaseBit + i;
+ int val = ((pBits[iBit>>3] >> (iBit&7)) & 1) << i;
+ ret |= val;
+ }
+ return ret;
+}
+
+void WriteBitInt( char *pBits, int iBaseBit, int nBits, int val )
+{
+ for ( int i=0; i < nBits; i++ )
+ {
+ int iBit = iBaseBit + i;
+ pBits[iBit>>3] &= ~(1 << (iBit & 7));
+ if ( (val >> i) & 1 )
+ pBits[iBit>>3] |= (1 << (iBit & 7));
+ }
+}
+
+int S3TC_BytesPerBlock( ImageFormat format )
+{
+ if ( format == IMAGE_FORMAT_DXT1 || format == IMAGE_FORMAT_ATI1N )
+ {
+ return 8;
+ }
+ else
+ {
+ Assert( format == IMAGE_FORMAT_DXT5 || format == IMAGE_FORMAT_ATI2N );
+ return 16;
+ }
+}
+
+
+/*
+
+// We're not using this, but I'll keep it around for reference.
+void S3TC_BuildPalette( ImageFormat format, const char *pS3Block, S3RGBA palette[4] )
+{
+ if ( format == IMAGE_FORMAT_DXT1 )
+ {
+ const S3TCBlock_DXT1 *pBlock = reinterpret_cast<const S3TCBlock_DXT1 *>( pS3Block );
+
+ palette[0] = S3TC_RGBAFrom565( pBlock->m_Ref1, 255 );
+
+ if ( pBlock->m_Ref1 <= pBlock->m_Ref2 )
+ {
+ // Opaque and transparent texels are defined. The lookup is 3 colors. 11 means
+ // a black, transparent pixel.
+ palette[1] = S3TC_RGBAFrom565( pBlock->m_Ref2, 255 );
+ palette[2] = S3TC_RGBABlend( palette[0], palette[1], 1, 1, 2 );
+ palette[3].r = palette[3].g = palette[3].b = palette[3].a = 0;
+ }
+ else
+ {
+ // Only opaque texels are defined. The lookup is 4 colors.
+ palette[1] = S3TC_RGBAFrom565( pBlock->m_Ref2, 255 );
+ palette[2] = S3TC_RGBABlend( palette[0], palette[1], 2, 1, 3 );
+ palette[3] = S3TC_RGBABlend( palette[0], palette[1], 1, 2, 3 );
+ }
+ }
+ else
+ {
+ Assert( format == IMAGE_FORMAT_DXT5 );
+ }
+}
+
+*/
+
+
+S3PaletteIndex S3TC_GetPixelPaletteIndex( ImageFormat format, const char *pS3Block, int x, int y )
+{
+ Assert( x >= 0 && x < 4 );
+ Assert( y >= 0 && y < 4 );
+ int iQuadPixel = y*4 + x;
+ S3PaletteIndex ret = { 0, 0 };
+
+ if ( format == IMAGE_FORMAT_DXT1 )
+ {
+ const S3TCBlock_DXT1 *pBlock = reinterpret_cast<const S3TCBlock_DXT1 *>( pS3Block );
+ ret.m_ColorIndex = (pBlock->m_PixelBits >> (iQuadPixel << 1)) & 3;
+ ret.m_AlphaIndex = 0;
+ }
+ else
+ {
+ Assert( format == IMAGE_FORMAT_DXT5 );
+
+ const S3TCBlock_DXT5 *pBlock = reinterpret_cast<const S3TCBlock_DXT5 *>( pS3Block );
+
+ int64 &alphaBits = *((int64*)pBlock->m_AlphaBits);
+ ret.m_ColorIndex = (unsigned char)((pBlock->m_PixelBits >> (iQuadPixel << 1)) & 3);
+ ret.m_AlphaIndex = (unsigned char)((alphaBits >> (iQuadPixel * 3)) & 7);
+ }
+
+ return ret;
+}
+
+
+void S3TC_SetPixelPaletteIndex( ImageFormat format, char *pS3Block, int x, int y, S3PaletteIndex iPaletteIndex )
+{
+ Assert( x >= 0 && x < 4 );
+ Assert( y >= 0 && y < 4 );
+ Assert( iPaletteIndex.m_ColorIndex >= 0 && iPaletteIndex.m_ColorIndex < 4 );
+ Assert( iPaletteIndex.m_AlphaIndex >= 0 && iPaletteIndex.m_AlphaIndex < 8 );
+
+ int iQuadPixel = y*4 + x;
+ int iColorBit = iQuadPixel * 2;
+
+ if ( format == IMAGE_FORMAT_DXT1 )
+ {
+ S3TCBlock_DXT1 *pBlock = reinterpret_cast<S3TCBlock_DXT1 *>( pS3Block );
+
+ pBlock->m_PixelBits &= ~( 3 << iColorBit );
+ pBlock->m_PixelBits |= (unsigned int)iPaletteIndex.m_ColorIndex << iColorBit;
+ }
+ else
+ {
+ Assert( format == IMAGE_FORMAT_DXT5 );
+
+ S3TCBlock_DXT5 *pBlock = reinterpret_cast<S3TCBlock_DXT5 *>( pS3Block );
+
+ // Copy the color portion in.
+ pBlock->m_PixelBits &= ~( 3 << iColorBit );
+ pBlock->m_PixelBits |= (unsigned int)iPaletteIndex.m_ColorIndex << iColorBit;
+
+ // Copy the alpha portion in.
+ WriteBitInt( (char*)pBlock->m_AlphaBits, iQuadPixel*3, 3, iPaletteIndex.m_AlphaIndex );
+ }
+}
+
+
+const char* S3TC_GetBlock(
+ const void *pCompressed,
+ ImageFormat format,
+ int nBlocksWidth,
+ int xBlock,
+ int yBlock )
+{
+ int nBytesPerBlock = S3TC_BytesPerBlock( format );
+ return &((const char*)pCompressed)[ ((yBlock * nBlocksWidth) + xBlock) * nBytesPerBlock ];
+}
+
+
+char* S3TC_GetBlock(
+ void *pCompressed,
+ ImageFormat format,
+ int nBlocksWidth,
+ int xBlock,
+ int yBlock )
+{
+ return (char*)S3TC_GetBlock( (const void *)pCompressed, format, nBlocksWidth, xBlock, yBlock );
+}
+
+
+void GenerateRepresentativePalette(
+ ImageFormat format,
+ S3RGBA **pOriginals, // Original RGBA colors in the texture. This allows it to avoid doubly compressing.
+ int nBlocks,
+ int lPitch, // (in BYTES)
+ char mergedBlocks[16*MAX_S3TC_BLOCK_BYTES]
+ )
+{
+ Error( "GenerateRepresentativePalette: not implemented" );
+#if 0 // this code was ifdefed out. no idea under what circumstances it was meant to be called.
+
+ Assert( nBlocks == 2 || nBlocks == 3 );
+
+ S3RGBA values[12*4];
+ memset( values, 0xFF, sizeof( values ) );
+ int width = nBlocks * 4;
+ for ( int i=0; i < nBlocks; i++ )
+ {
+ for ( int y=0; y < 4; y++ )
+ {
+ for ( int x=0; x < 4; x++ )
+ {
+ int outIndex = y*width+(i*4+x);
+ values[outIndex] = pOriginals[i][y * (lPitch/4) + x];
+ }
+ }
+ }
+
+ DDSURFACEDESC descIn;
+ DDSURFACEDESC descOut;
+ memset( &descIn, 0, sizeof(descIn) );
+ memset( &descOut, 0, sizeof(descOut) );
+
+ descIn.dwSize = sizeof(descIn);
+ descIn.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_LPSURFACE | DDSD_PIXELFORMAT;
+ descIn.dwWidth = width;
+ descIn.dwHeight = 4;
+ descIn.lPitch = width * 4;
+ descIn.lpSurface = values;
+ descIn.ddpfPixelFormat.dwSize = sizeof( DDPIXELFORMAT );
+
+ descIn.ddpfPixelFormat.dwFlags = DDPF_RGB | DDPF_ALPHAPIXELS;
+ descIn.ddpfPixelFormat.dwRGBBitCount = 32;
+ descIn.ddpfPixelFormat.dwRBitMask = 0xff0000;
+ descIn.ddpfPixelFormat.dwGBitMask = 0x00ff00;
+ descIn.ddpfPixelFormat.dwBBitMask = 0x0000ff;
+ descIn.ddpfPixelFormat.dwRGBAlphaBitMask = 0xff000000;
+
+ descOut.dwSize = sizeof( descOut );
+
+ float weight[3] = {0.3086f, 0.6094f, 0.0820f};
+
+ S3TC_BLOCK_WIDTH = nBlocks * 4;
+
+ DWORD encodeFlags = S3TC_ENCODE_RGB_FULL;
+ if ( format == IMAGE_FORMAT_DXT5 )
+ encodeFlags |= S3TC_ENCODE_ALPHA_INTERPOLATED;
+
+ S3TCencode( &descIn, NULL, &descOut, mergedBlocks, encodeFlags, weight );
+
+ S3TC_BLOCK_WIDTH = 4;
+#endif
+}
+
+void S3TC_MergeBlocks(
+ char **blocks,
+ S3RGBA **pOriginals, // Original RGBA colors in the texture. This allows it to avoid doubly compressing.
+ int nBlocks,
+ int lPitch, // (in BYTES)
+ ImageFormat format
+ )
+{
+ // Figure out a good palette to represent all of these blocks.
+ char mergedBlocks[16*MAX_S3TC_BLOCK_BYTES];
+ GenerateRepresentativePalette( format, pOriginals, nBlocks, lPitch, mergedBlocks );
+
+ // Build a remap table to remap block 2's colors to block 1's colors.
+ if ( format == IMAGE_FORMAT_DXT1 )
+ {
+ // Grab the palette indices that s3tc.lib made for us.
+ const char *pBase = (const char*)mergedBlocks;
+ pBase += 4;
+
+ for ( int iBlock=0; iBlock < nBlocks; iBlock++ )
+ {
+ S3TCBlock_DXT1 *pBlock = ((S3TCBlock_DXT1*)blocks[iBlock]);
+
+ // Remap all of the block's pixels.
+ for ( int x=0; x < 4; x++ )
+ {
+ for ( int y=0; y < 4; y++ )
+ {
+ int iBaseBit = (y*nBlocks*4 + x + iBlock*4) * 2;
+
+ S3PaletteIndex index = {0, 0};
+ index.m_ColorIndex = ReadBitInt( pBase, iBaseBit, 2 );
+
+ S3TC_SetPixelPaletteIndex( format, (char*)pBlock, x, y, index );
+ }
+ }
+
+ // Copy block 1's palette to block 2.
+ pBlock->m_Ref1 = ((S3TCBlock_DXT1*)mergedBlocks)->m_Ref1;
+ pBlock->m_Ref2 = ((S3TCBlock_DXT1*)mergedBlocks)->m_Ref2;
+ }
+ }
+ else
+ {
+ Assert( format == IMAGE_FORMAT_DXT5 );
+
+ // Skip past the alpha palette.
+ const char *pAlphaPalette = mergedBlocks;
+ const char *pAlphaBits = mergedBlocks + 2;
+
+ // Skip past the alpha pixel bits and past the color palette.
+ const char *pColorPalette = pAlphaBits + 6*nBlocks;
+ const char *pColorBits = pColorPalette + 4;
+
+ for ( int iBlock=0; iBlock < nBlocks; iBlock++ )
+ {
+ S3TCBlock_DXT5 *pBlock = ((S3TCBlock_DXT5*)blocks[iBlock]);
+
+ // Remap all of the block's pixels.
+ for ( int x=0; x < 4; x++ )
+ {
+ for ( int y=0; y < 4; y++ )
+ {
+ int iBasePixel = (y*nBlocks*4 + x + iBlock*4);
+
+ S3PaletteIndex index;
+ index.m_ColorIndex = ReadBitInt( pColorBits, iBasePixel * 2, 2 );
+ index.m_AlphaIndex = ReadBitInt( pAlphaBits, iBasePixel * 3, 3 );
+
+ S3TC_SetPixelPaletteIndex( format, (char*)pBlock, x, y, index );
+ }
+ }
+
+ // Copy block 1's palette to block 2.
+ pBlock->m_AlphaRef[0] = pAlphaPalette[0];
+ pBlock->m_AlphaRef[1] = pAlphaPalette[1];
+ pBlock->m_Ref1 = *((unsigned short*)pColorPalette);
+ pBlock->m_Ref2 = *((unsigned short*)(pColorPalette + 2));
+ }
+ }
+}
+
+
+S3PaletteIndex S3TC_GetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y )
+{
+ char *pBlock = S3TC_GetBlock( pFaceData, format, imageWidth>>2, x>>2, y>>2 );
+ return S3TC_GetPixelPaletteIndex( format, pBlock, x&3, y&3 );
+}
+
+
+void S3TC_SetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y,
+ S3PaletteIndex paletteIndex )
+{
+ char *pBlock = S3TC_GetBlock( pFaceData, format, imageWidth>>2, x>>2, y>>2 );
+ S3TC_SetPixelPaletteIndex( format, pBlock, x&3, y&3, paletteIndex );
+}
+
+
+
+
diff --git a/vtf/s3tc_decode.h b/vtf/s3tc_decode.h
new file mode 100644
index 0000000..5cee891
--- /dev/null
+++ b/vtf/s3tc_decode.h
@@ -0,0 +1,109 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef S3TC_DECODE_H
+#define S3TC_DECODE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "bitmap/imageformat.h"
+enum ImageFormat;
+
+
+class S3RGBA
+{
+public:
+ unsigned char b, g, r, a;
+};
+
+class S3PaletteIndex
+{
+public:
+ unsigned char m_AlphaIndex;
+ unsigned char m_ColorIndex;
+};
+
+
+#define MAX_S3TC_BLOCK_BYTES 16
+
+
+S3PaletteIndex S3TC_GetPixelPaletteIndex( ImageFormat format, const char *pS3Block, int x, int y );
+
+void S3TC_SetPixelPaletteIndex( ImageFormat format, char *pS3Block, int x, int y, S3PaletteIndex iPaletteIndex );
+
+
+
+// Note: width, x, and y are in texels, not S3 blocks.
+S3PaletteIndex S3TC_GetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y );
+
+
+// Note: width, x, and y are in texels, not S3 blocks.
+void S3TC_SetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y,
+ S3PaletteIndex paletteIndex );
+
+
+const char* S3TC_GetBlock(
+ const void *pCompressed,
+ ImageFormat format,
+ int nBlocksWide, // How many blocks wide is the image (pixels wide / 4).
+ int xBlock,
+ int yBlock );
+
+
+char* S3TC_GetBlock(
+ void *pCompressed,
+ ImageFormat format,
+ int nBlocksWide, // How many blocks wide is the image (pixels wide / 4).
+ int xBlock,
+ int yBlock );
+
+
+// Merge the two palettes and copy the colors
+void S3TC_MergeBlocks(
+ char **blocks,
+ S3RGBA **pOriginals, // Original RGBA colors in the texture. This allows it to avoid doubly compressing.
+ int nBlocks,
+ int lPitch, // (in BYTES)
+ ImageFormat format
+ );
+
+
+// Convert an RGB565 color to RGBA8888.
+inline S3RGBA S3TC_RGBAFrom565( unsigned short color, unsigned char alphaValue=255 )
+{
+ S3RGBA ret;
+ ret.a = alphaValue;
+ ret.r = (unsigned char)( (color >> 11) << 3 );
+ ret.g = (unsigned char)( ((color >> 5) & 0x3F) << 2 );
+ ret.b = (unsigned char)( (color & 0x1F) << 3 );
+ return ret;
+}
+
+// Blend from one color to another..
+inline S3RGBA S3TC_RGBABlend( const S3RGBA &a, const S3RGBA &b, int aMul, int bMul, int div )
+{
+ S3RGBA ret;
+ ret.r = (unsigned char)(( (int)a.r * aMul + (int)b.r * bMul ) / div );
+ ret.g = (unsigned char)(( (int)a.g * aMul + (int)b.g * bMul ) / div );
+ ret.b = (unsigned char)(( (int)a.b * aMul + (int)b.b * bMul ) / div );
+ ret.a = (unsigned char)(( (int)a.a * aMul + (int)b.a * bMul ) / div );
+ return ret;
+}
+
+
+#endif // S3TC_DECODE_H
diff --git a/vtf/vtf.cpp b/vtf/vtf.cpp
new file mode 100644
index 0000000..d014e12
--- /dev/null
+++ b/vtf/vtf.cpp
@@ -0,0 +1,3493 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The VTF file format I/O class to help simplify access to VTF files
+//
+//=====================================================================================//
+
+#undef fopen
+#include "bitmap/imageformat.h"
+#include "cvtf.h"
+#include "utlbuffer.h"
+#include "tier0/dbg.h"
+#include "mathlib/vector.h"
+#include "mathlib/mathlib.h"
+#include "tier1/strtools.h"
+#include "tier0/mem.h"
+#include "s3tc_decode.h"
+#include "utlvector.h"
+#include "vprof_telemetry.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// byteswap data descriptions
+BEGIN_BYTESWAP_DATADESC( VTFFileBaseHeader_t )
+ DEFINE_ARRAY( fileTypeString, FIELD_CHARACTER, 4 ),
+ DEFINE_ARRAY( version, FIELD_INTEGER, 2 ),
+ DEFINE_FIELD( headerSize, FIELD_INTEGER ),
+END_DATADESC()
+
+BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_1_t, VTFFileBaseHeader_t )
+ DEFINE_FIELD( width, FIELD_SHORT ),
+ DEFINE_FIELD( height, FIELD_SHORT ),
+ DEFINE_FIELD( flags, FIELD_INTEGER ),
+ DEFINE_FIELD( numFrames, FIELD_SHORT ),
+ DEFINE_FIELD( startFrame, FIELD_SHORT ),
+ DEFINE_FIELD( reflectivity, FIELD_VECTOR ),
+ DEFINE_FIELD( bumpScale, FIELD_FLOAT ),
+ DEFINE_FIELD( imageFormat, FIELD_INTEGER ),
+ DEFINE_FIELD( numMipLevels, FIELD_CHARACTER ),
+ DEFINE_FIELD( lowResImageFormat, FIELD_INTEGER ),
+ DEFINE_FIELD( lowResImageWidth, FIELD_CHARACTER ),
+ DEFINE_FIELD( lowResImageHeight, FIELD_CHARACTER ),
+END_DATADESC()
+
+BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_2_t, VTFFileHeaderV7_1_t )
+ DEFINE_FIELD( depth, FIELD_SHORT ),
+END_DATADESC()
+
+BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_3_t, VTFFileHeaderV7_2_t )
+ DEFINE_FIELD( numResources, FIELD_INTEGER ),
+END_DATADESC()
+
+BEGIN_BYTESWAP_DATADESC_( VTFFileHeader_t, VTFFileHeaderV7_2_t )
+END_DATADESC()
+
+BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderX360_t, VTFFileBaseHeader_t )
+ DEFINE_FIELD( flags, FIELD_INTEGER ),
+ DEFINE_FIELD( width, FIELD_SHORT ),
+ DEFINE_FIELD( height, FIELD_SHORT ),
+ DEFINE_FIELD( depth, FIELD_SHORT ),
+ DEFINE_FIELD( numFrames, FIELD_SHORT ),
+ DEFINE_FIELD( preloadDataSize, FIELD_SHORT ),
+ DEFINE_FIELD( mipSkipCount, FIELD_CHARACTER ),
+ DEFINE_FIELD( numResources, FIELD_CHARACTER ),
+ DEFINE_FIELD( reflectivity, FIELD_VECTOR ),
+ DEFINE_FIELD( bumpScale, FIELD_FLOAT ),
+ DEFINE_FIELD( imageFormat, FIELD_INTEGER ),
+ DEFINE_ARRAY( lowResImageSample, FIELD_CHARACTER, 4 ),
+ DEFINE_FIELD( compressedSize, FIELD_INTEGER ),
+END_DATADESC()
+
+#if defined( POSIX ) || defined( _X360 )
+// stub functions
+const char* S3TC_GetBlock(
+ const void *pCompressed,
+ ImageFormat format,
+ int nBlocksWide, // How many blocks wide is the image (pixels wide / 4).
+ int xBlock,
+ int yBlock )
+{
+ return NULL;
+}
+
+char* S3TC_GetBlock(
+ void *pCompressed,
+ ImageFormat format,
+ int nBlocksWide, // How many blocks wide is the image (pixels wide / 4).
+ int xBlock,
+ int yBlock )
+{
+ return NULL;
+}
+
+S3PaletteIndex S3TC_GetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y )
+{
+ S3PaletteIndex nullPalette;
+ memset(&nullPalette, 0x0, sizeof(nullPalette));
+ return nullPalette;
+}
+
+// Merge the two palettes and copy the colors
+void S3TC_MergeBlocks(
+ char **blocks,
+ S3RGBA **pOriginals,
+ int nBlocks,
+ int lPitch, // (in BYTES)
+ ImageFormat format
+ )
+{
+}
+
+// Note: width, x, and y are in texels, not S3 blocks.
+void S3TC_SetPaletteIndex(
+ unsigned char *pFaceData,
+ ImageFormat format,
+ int imageWidth,
+ int x,
+ int y,
+ S3PaletteIndex paletteIndex )
+{
+}
+#endif
+
+// This gives a vertex number to each of the 4 verts on each face.
+// We use this to match the verts and determine which edges need to be blended together.
+// The vert ordering is lower-left, top-left, top-right, bottom-right.
+int g_leftFaceVerts[4] = { 2, 6, 7, 3 };
+int g_frontFaceVerts[4] = { 2, 3, 5, 4 };
+int g_downFaceVerts[4] = { 4, 0, 6, 2 };
+int g_rightFaceVerts[4] = { 5, 1, 0, 4 };
+int g_backFaceVerts[4] = { 7, 6, 0, 1 };
+int g_upFaceVerts[4] = { 3, 7, 1, 5 };
+
+int *g_FaceVerts[6] =
+{
+ g_rightFaceVerts,
+ g_leftFaceVerts,
+ g_backFaceVerts,
+ g_frontFaceVerts,
+ g_upFaceVerts,
+ g_downFaceVerts
+};
+
+// For skyboxes..
+// These were constructed for the engine skybox, which looks like this
+// (assuming X goes forward, Y goes left, and Z goes up).
+//
+// 6 ------------- 5
+// / /
+// / | / |
+// / | / |
+// 2 ------------- 1 |
+// | |
+// | |
+// | 7 ------|------ 4
+// | / | /
+// | / | /
+// / /
+// 3 ------------- 0
+//
+int g_skybox_rightFaceVerts[4] = { 7, 6, 5, 4 };
+int g_skybox_leftFaceVerts[4] = { 0, 1, 2, 3 };
+int g_skybox_backFaceVerts[4] = { 3, 2, 6, 7 };
+int g_skybox_frontFaceVerts[4] = { 4, 5, 1, 0 };
+int g_skybox_upFaceVerts[4] = { 6, 2, 1, 5 };
+int g_skybox_downFaceVerts[4] = { 3, 7, 4, 0 };
+
+int *g_skybox_FaceVerts[6] =
+{
+ g_skybox_rightFaceVerts,
+ g_skybox_leftFaceVerts,
+ g_skybox_backFaceVerts,
+ g_skybox_frontFaceVerts,
+ g_skybox_upFaceVerts,
+ g_skybox_downFaceVerts
+};
+
+//-----------------------------------------------------------------------------
+// Class factory
+//-----------------------------------------------------------------------------
+IVTFTexture *CreateVTFTexture()
+{
+ return new CVTFTexture;
+}
+
+void DestroyVTFTexture( IVTFTexture *pTexture )
+{
+ delete pTexture;
+}
+
+//-----------------------------------------------------------------------------
+// Allows us to only load in the first little bit of the VTF file to get info
+//-----------------------------------------------------------------------------
+int VTFFileHeaderSize( int nMajorVersion, int nMinorVersion )
+{
+ if ( nMajorVersion == -1 )
+ {
+ nMajorVersion = VTF_MAJOR_VERSION;
+ }
+
+ if ( nMinorVersion == -1 )
+ {
+ nMinorVersion = VTF_MINOR_VERSION;
+ }
+
+ switch ( nMajorVersion )
+ {
+ case VTF_MAJOR_VERSION:
+ switch ( nMinorVersion )
+ {
+ case 0: // fall through
+ case 1:
+ return sizeof( VTFFileHeaderV7_1_t );
+ case 2:
+ return sizeof( VTFFileHeaderV7_2_t );
+ case 3:
+ return sizeof( VTFFileHeaderV7_3_t ) + sizeof( ResourceEntryInfo ) * MAX_RSRC_DICTIONARY_ENTRIES;
+ case VTF_MINOR_VERSION:
+ int size1 = sizeof( VTFFileHeader_t );
+ int size2 = sizeof( ResourceEntryInfo ) * MAX_RSRC_DICTIONARY_ENTRIES;
+ int result = size1 + size2;
+ //printf("\n VTFFileHeaderSize (%i %i) is %i + %i -> %i",nMajorVersion,nMinorVersion, size1, size2, result );
+ return result;
+ }
+ break;
+
+ case VTF_X360_MAJOR_VERSION:
+ return sizeof( VTFFileHeaderX360_t ) + sizeof( ResourceEntryInfo ) * MAX_X360_RSRC_DICTIONARY_ENTRIES;
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Constructor, destructor
+//-----------------------------------------------------------------------------
+CVTFTexture::CVTFTexture()
+{
+ m_nVersion[0] = 0;
+ m_nVersion[1] = 0;
+
+ m_nWidth = 0;
+ m_nHeight = 0;
+ m_nDepth = 1;
+ m_Format = IMAGE_FORMAT_UNKNOWN;
+
+ m_nMipCount = 0;
+ m_nFaceCount = 0;
+ m_nFrameCount = 0;
+
+ // FIXME: Is the start frame needed?
+ m_iStartFrame = 0;
+
+ m_flAlphaThreshhold = -1.0f;
+ m_flAlphaHiFreqThreshhold = -1.0f;
+
+ m_flBumpScale = 1.0f;
+ m_vecReflectivity.Init( 1.0, 1.0, 1.0f );
+
+ m_nFlags = 0;
+ m_pImageData = NULL;
+ m_nImageAllocSize = 0;
+
+ // LowRes data
+ m_LowResImageFormat = IMAGE_FORMAT_UNKNOWN;
+ m_nLowResImageWidth = 0;
+ m_nLowResImageHeight = 0;
+ m_pLowResImageData = NULL;
+ m_nLowResImageAllocSize = 0;
+
+#if defined( _X360 )
+ m_nMipSkipCount = 0;
+ *(unsigned int *)m_LowResImageSample = 0;
+#endif
+
+ Assert( m_arrResourcesInfo.Count() == 0 );
+ Assert( m_arrResourcesData.Count() == 0 );
+ Assert( m_arrResourcesData_ForReuse.Count() == 0 );
+
+ memset( &m_Options, 0, sizeof( m_Options ) );
+ m_Options.cbSize = sizeof( m_Options );
+
+ m_nFinestMipmapLevel = 0;
+ m_nCoarsestMipmapLevel = 0;
+}
+
+CVTFTexture::~CVTFTexture()
+{
+ Shutdown();
+}
+
+//-----------------------------------------------------------------------------
+// Compute the mip count based on the size + flags
+//-----------------------------------------------------------------------------
+int CVTFTexture::ComputeMipCount() const
+{
+ if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) && ( m_nFlags & TEXTUREFLAGS_NOMIP ) )
+ {
+ // 360 vtf format culled unused mips at conversion time
+ return 1;
+ }
+
+ // NOTE: No matter what, all mip levels should be created because
+ // we have to worry about various fallbacks
+ return ImageLoader::GetNumMipMapLevels( m_nWidth, m_nHeight, m_nDepth );
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocate data blocks with an eye toward re-using memory
+//-----------------------------------------------------------------------------
+
+static bool GenericAllocateReusableData( unsigned char **ppData, int *pNumAllocated, int numRequested )
+{
+ // If we're asking for memory and we have way more than we expect, free some.
+ if ( *pNumAllocated < numRequested || ( numRequested > 0 && *pNumAllocated > 16 * numRequested ) )
+ {
+ delete [] *ppData;
+ *ppData = new unsigned char[ numRequested ];
+ if ( *ppData )
+ {
+ *pNumAllocated = numRequested;
+ return true;
+ }
+
+ *pNumAllocated = 0;
+ return false;
+ }
+
+ return true;
+}
+
+bool CVTFTexture::AllocateImageData( int nMemorySize )
+{
+ return GenericAllocateReusableData( &m_pImageData, &m_nImageAllocSize, nMemorySize );
+}
+
+bool CVTFTexture::ResourceMemorySection::AllocateData( int nMemorySize )
+{
+ if ( GenericAllocateReusableData( &m_pData, &m_nDataAllocSize, nMemorySize ) )
+ {
+ m_nDataLength = nMemorySize;
+ return true;
+ }
+
+ return false;
+}
+
+bool CVTFTexture::AllocateLowResImageData( int nMemorySize )
+{
+ return GenericAllocateReusableData( &m_pLowResImageData, &m_nLowResImageAllocSize, nMemorySize );
+}
+
+inline bool IsMultipleOf4( int value )
+{
+ // NOTE: This catches powers of 2 less than 4 also
+ return ( value <= 2 ) || ( (value & 0x3) == 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Initialization
+//-----------------------------------------------------------------------------
+bool CVTFTexture::Init( int nWidth, int nHeight, int nDepth, ImageFormat fmt, int iFlags, int iFrameCount, int nForceMipCount )
+{
+ if ( nDepth == 0 )
+ {
+ nDepth = 1;
+ }
+
+ if (iFlags & TEXTUREFLAGS_ENVMAP)
+ {
+ if (nWidth != nHeight)
+ {
+ Warning( "Height and width must be equal for cubemaps!\n" );
+ return false;
+ }
+ if (nDepth != 1)
+ {
+ Warning( "Depth must be 1 for cubemaps!\n" );
+ return false;
+ }
+ }
+
+ if ( !IsMultipleOf4( nWidth ) || !IsMultipleOf4( nHeight ) || !IsMultipleOf4( nDepth ) )
+ {
+ Warning( "Image dimensions must be multiple of 4!\n" );
+ return false;
+ }
+
+ if ( fmt == IMAGE_FORMAT_DEFAULT )
+ {
+ fmt = IMAGE_FORMAT_RGBA8888;
+ }
+
+ m_nWidth = nWidth;
+ m_nHeight = nHeight;
+ m_nDepth = nDepth;
+ m_Format = fmt;
+ m_nFlags = iFlags;
+
+ // THIS CAUSED A BUG!!! We want all of the mip levels in the vtf file even with nomip in case we have lod.
+ // NOTE: But we don't want more than 1 mip level for procedural textures
+ if ( (iFlags & (TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL)) == (TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL) )
+ {
+ nForceMipCount = 1;
+ }
+
+ if ( nForceMipCount == -1 )
+ {
+ m_nMipCount = ComputeMipCount();
+ }
+ else
+ {
+ m_nMipCount = nForceMipCount;
+ }
+
+ m_nFrameCount = iFrameCount;
+
+ m_nFaceCount = (iFlags & TEXTUREFLAGS_ENVMAP) ? CUBEMAP_FACE_COUNT : 1;
+ if ( IsX360() && ( iFlags & TEXTUREFLAGS_ENVMAP ) )
+ {
+ // 360 has no reason to support sphere map
+ m_nFaceCount = CUBEMAP_FACE_COUNT-1;
+ }
+
+#if defined( _X360 )
+ m_nMipSkipCount = 0;
+#endif
+
+ // Need to do this because Shutdown deallocates the low-res image
+ m_nLowResImageWidth = m_nLowResImageHeight = 0;
+
+ // Allocate me some bits!
+ int iMemorySize = ComputeTotalSize();
+ if ( !AllocateImageData( iMemorySize ) )
+ return false;
+
+ // As soon as we have image indicate so in the resources
+ if ( iMemorySize )
+ FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+ else
+ RemoveResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Methods to initialize the low-res image
+//-----------------------------------------------------------------------------
+void CVTFTexture::InitLowResImage( int nWidth, int nHeight, ImageFormat fmt )
+{
+ m_nLowResImageWidth = nWidth;
+ m_nLowResImageHeight = nHeight;
+ m_LowResImageFormat = fmt;
+
+ // Allocate low-res bits
+ int iLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth,
+ m_nLowResImageHeight, 1, m_LowResImageFormat, false );
+
+ if ( !AllocateLowResImageData( iLowResImageSize ) )
+ return;
+
+ // As soon as we have low-res image indicate so in the resources
+ if ( iLowResImageSize )
+ FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE );
+ else
+ RemoveResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE );
+}
+
+
+//-----------------------------------------------------------------------------
+// Methods to set other texture fields
+//-----------------------------------------------------------------------------
+void CVTFTexture::SetBumpScale( float flScale )
+{
+ m_flBumpScale = flScale;
+}
+
+void CVTFTexture::SetReflectivity( const Vector &vecReflectivity )
+{
+ VectorCopy( vecReflectivity, m_vecReflectivity );
+}
+
+// Sets threshhold values for alphatest mipmapping
+void CVTFTexture::SetAlphaTestThreshholds( float flBase, float flHighFreq )
+{
+ m_flAlphaThreshhold = flBase;
+ m_flAlphaHiFreqThreshhold = flHighFreq;
+}
+
+//-----------------------------------------------------------------------------
+// Release and reset the resources.
+//-----------------------------------------------------------------------------
+void CVTFTexture::ReleaseResources()
+{
+ m_arrResourcesInfo.RemoveAll();
+
+ for ( ResourceMemorySection *pRms = m_arrResourcesData.Base(),
+ *pRmsEnd = pRms + m_arrResourcesData.Count(); pRms < pRmsEnd; ++pRms )
+ {
+ delete [] pRms->m_pData;
+ }
+ m_arrResourcesData.RemoveAll();
+
+ for ( ResourceMemorySection *pRms = m_arrResourcesData_ForReuse.Base(),
+ *pRmsEnd = pRms + m_arrResourcesData_ForReuse.Count(); pRms < pRmsEnd; ++pRms )
+ {
+ delete [] pRms->m_pData;
+ }
+ m_arrResourcesData_ForReuse.RemoveAll();
+}
+
+//-----------------------------------------------------------------------------
+// Shutdown
+//-----------------------------------------------------------------------------
+void CVTFTexture::Shutdown()
+{
+#if defined( _X360 )
+ // must be first to ensure X360 aliased pointers are unhooked, otherwise memory corruption
+ ReleaseImageMemory();
+#endif
+
+ delete[] m_pImageData;
+ m_pImageData = NULL;
+ m_nImageAllocSize = 0;
+
+ delete[] m_pLowResImageData;
+ m_pLowResImageData = NULL;
+ m_nLowResImageAllocSize = 0;
+
+ ReleaseResources();
+}
+
+//-----------------------------------------------------------------------------
+// These are methods to help with optimization of file access
+//-----------------------------------------------------------------------------
+void CVTFTexture::LowResFileInfo( int *pStartLocation, int *pSizeInBytes ) const
+{
+ // Once the header is read in, they indicate where to start reading
+ // other data, and how many bytes to read....
+
+ if ( ResourceEntryInfo const *pLowResData = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) )
+ {
+ *pStartLocation = pLowResData->resData;
+ *pSizeInBytes = ImageLoader::GetMemRequired( m_nLowResImageWidth,
+ m_nLowResImageHeight, 1, m_LowResImageFormat, false );
+ }
+ else
+ {
+ *pStartLocation = 0;
+ *pSizeInBytes = 0;
+ }
+}
+
+void CVTFTexture::ImageFileInfo( int nFrame, int nFace, int nMipLevel, int *pStartLocation, int *pSizeInBytes) const
+{
+ int i;
+ int iMipWidth;
+ int iMipHeight;
+ int iMipDepth;
+
+ ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+
+ if ( pImageDataInfo == NULL )
+ {
+ // This should never happen for real, but can happen if someone intentionally fed us a bad VTF.
+ Assert( pImageDataInfo );
+ ( *pStartLocation ) = 0;
+ ( *pSizeInBytes ) = 0;
+ return;
+ }
+
+ // The image data start offset
+ int nOffset = pImageDataInfo->resData;
+
+ // get to the right miplevel
+ for( i = m_nMipCount - 1; i > nMipLevel; --i )
+ {
+ ComputeMipLevelDimensions( i, &iMipWidth, &iMipHeight, &iMipDepth );
+ int iMipLevelSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, m_Format, false );
+ nOffset += iMipLevelSize * m_nFrameCount * m_nFaceCount;
+ }
+
+ // get to the right frame
+ ComputeMipLevelDimensions( nMipLevel, &iMipWidth, &iMipHeight, &iMipDepth );
+ int nFaceSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, m_Format, false );
+
+ // For backwards compatibility, we don't read in the spheremap fallback on
+ // older format .VTF files...
+ int nFacesToRead = m_nFaceCount;
+ if ( IsCubeMap() )
+ {
+ if ((m_nVersion[0] == 7) && (m_nVersion[1] < 1))
+ {
+ nFacesToRead = 6;
+ if (nFace == CUBEMAP_FACE_SPHEREMAP)
+ {
+ --nFace;
+ }
+ }
+ }
+
+ int nFrameSize = nFacesToRead * nFaceSize;
+ nOffset += nFrameSize * nFrame;
+
+ // get to the right face
+ nOffset += nFace * nFaceSize;
+
+ *pStartLocation = nOffset;
+ *pSizeInBytes = nFaceSize;
+}
+
+int CVTFTexture::FileSize( int nMipSkipCount ) const
+{
+ ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+
+ // Can be null when someone gives us an intentionally malformed VTF.
+ if ( pImageDataInfo == NULL )
+ {
+ // Still do the assert so we can catch this in debug--we don't expect this for well formed files.
+ Assert( pImageDataInfo != NULL );
+ return 0;
+ }
+
+ int nOffset = pImageDataInfo->resData;
+
+ int nFaceSize = ComputeFaceSize( nMipSkipCount );
+ int nImageSize = nFaceSize * m_nFaceCount * m_nFrameCount;
+ return nOffset + nImageSize;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization of low-res data
+//-----------------------------------------------------------------------------
+bool CVTFTexture::LoadLowResData( CUtlBuffer &buf )
+{
+ // Allocate low-res bits
+ InitLowResImage( m_nLowResImageWidth, m_nLowResImageHeight, m_LowResImageFormat );
+ int nLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth,
+ m_nLowResImageHeight, 1, m_LowResImageFormat, false );
+ buf.Get( m_pLowResImageData, nLowResImageSize );
+
+ bool bValid = buf.IsValid();
+
+ return bValid;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization of image data
+//-----------------------------------------------------------------------------
+bool CVTFTexture::LoadImageData( CUtlBuffer &buf, const VTFFileHeader_t &header, int nSkipMipLevels )
+{
+ // Fix up the mip count + size based on how many mip levels we skip...
+ if (nSkipMipLevels > 0)
+ {
+ Assert( m_nMipCount > nSkipMipLevels );
+ if (header.numMipLevels < nSkipMipLevels)
+ {
+ // NOTE: This can only happen with older format .vtf files
+ Warning("Warning! Encountered old format VTF file; please rebuild it!\n");
+ return false;
+ }
+
+ ComputeMipLevelDimensions( nSkipMipLevels, &m_nWidth, &m_nHeight, &m_nDepth );
+ m_nMipCount -= nSkipMipLevels;
+ }
+
+ // read the texture image (including mipmaps if they are there and needed.)
+ int iImageSize = ComputeFaceSize();
+ iImageSize *= m_nFaceCount * m_nFrameCount;
+
+ // For backwards compatibility, we don't read in the spheremap fallback on
+ // older format .VTF files...
+ int nFacesToRead = m_nFaceCount;
+ if ( IsCubeMap() )
+ {
+ if ((header.version[0] == 7) && (header.version[1] < 1))
+ nFacesToRead = 6;
+ }
+
+ // Even if we are preloading partial data, always do the full allocation here. We'll use LOD clamping to ensure we only
+ // reference data that is available.
+ if ( !AllocateImageData( iImageSize ) )
+ return false;
+
+ // We may only have part of the data available--if so we will stream in the rest later.
+ // If we have the data available but we're ignoring it (for example during development), then we
+ // need to skip over the data we're ignoring below, otherwise we'll be sad pandas.
+ bool bMipDataPresent = true;
+ int nFirstAvailableMip = 0;
+ int nLastAvailableMip = m_nMipCount - 1;
+ TextureStreamSettings_t const *pStreamSettings = ( TextureStreamSettings_t const * ) GetResourceData( VTF_RSRC_TEXTURE_STREAM_SETTINGS, NULL );
+ if ( pStreamSettings )
+ {
+ nFirstAvailableMip = Max( 0, pStreamSettings->m_firstAvailableMip - nSkipMipLevels );
+ nLastAvailableMip = Max( 0, pStreamSettings->m_lastAvailableMip - nSkipMipLevels );
+ bMipDataPresent = false;
+ }
+
+ // If we have coarse mips but not the fine mips (yet)
+ if ( ( header.flags & TEXTUREFLAGS_STREAMABLE ) == TEXTUREFLAGS_STREAMABLE_COARSE )
+ {
+ nFirstAvailableMip = Max( 0, Max( nFirstAvailableMip, STREAMING_START_MIPMAP ) - nSkipMipLevels );
+ }
+
+ if ( header.flags & TEXTUREFLAGS_STREAMABLE_FINE )
+ {
+ // Don't need to subtract nSkipMipLevels: m_nMipCount has subtracted that above--assuming this assert doesn't fire.
+ Assert( m_nMipCount == header.numMipLevels - nSkipMipLevels );
+ nLastAvailableMip = Min( nLastAvailableMip, STREAMING_START_MIPMAP - 1 );
+ }
+
+ // Valid settings?
+ Assert( nFirstAvailableMip >= 0 && nFirstAvailableMip <= nLastAvailableMip && nLastAvailableMip < m_nMipCount );
+
+ // Store the clamp settings
+ m_nFinestMipmapLevel = nFirstAvailableMip;
+ m_nCoarsestMipmapLevel = nLastAvailableMip;
+
+ // NOTE: The mip levels are stored ascending from smallest (1x1) to largest (NxN)
+ // in order to allow for truncated reads of the minimal required data
+ for (int iMip = m_nMipCount; --iMip >= 0; )
+ {
+ // NOTE: This is for older versions...
+ if (header.numMipLevels - nSkipMipLevels <= iMip)
+ continue;
+
+ int iMipSize = ComputeMipSize( iMip );
+
+ // Skip over any levels we don't have data for--we'll get them later.
+ if ( iMip > nLastAvailableMip || iMip < nFirstAvailableMip )
+ {
+ // If the data is there but we're ignoring it, need to update the get pointer.
+ if ( bMipDataPresent )
+ {
+ for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame )
+ for ( int iFace = 0; iFace < nFacesToRead; ++iFace )
+ buf.SeekGet( CUtlBuffer::SEEK_CURRENT, iMipSize );
+ }
+ continue;
+ }
+
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < nFacesToRead; ++iFace)
+ {
+ // printf("\n tex %p mip %i frame %i face %i size %i buf offset %i", this, iMip, iFrame, iFace, iMipSize, buf.TellGet() );
+ unsigned char *pMipBits = ImageData( iFrame, iFace, iMip );
+ buf.Get( pMipBits, iMipSize );
+ }
+ }
+ }
+
+ return buf.IsValid();
+}
+
+void *CVTFTexture::SetResourceData( uint32 eType, void const *pData, size_t nNumBytes )
+{
+ Assert( ( eType & RSRCF_MASK ) == 0 );
+ eType &= ~RSRCF_MASK;
+
+ // Very inefficient to set less than 4 bytes of data
+ Assert( !nNumBytes || ( nNumBytes >= sizeof( uint32 ) ) );
+
+ if ( nNumBytes )
+ {
+ ResourceEntryInfo *pInfo = FindOrCreateResourceEntryInfo( eType );
+ int idx = pInfo - m_arrResourcesInfo.Base();
+ ResourceMemorySection &rms = m_arrResourcesData[ idx ];
+
+ if ( nNumBytes == sizeof( pInfo->resData ) )
+ {
+ // store 4 bytes directly
+ pInfo->eType |= RSRCF_HAS_NO_DATA_CHUNK;
+ if ( pData )
+ pInfo->resData = reinterpret_cast< const int * >( pData )[0];
+ return &pInfo->resData;
+ }
+ else
+ {
+ if ( !rms.AllocateData( nNumBytes ) )
+ {
+ RemoveResourceEntryInfo( eType );
+ return NULL;
+ }
+
+ if ( pData )
+ memcpy( rms.m_pData, pData, nNumBytes );
+ return rms.m_pData;
+ }
+ }
+ else
+ {
+ RemoveResourceEntryInfo( eType );
+ return NULL;
+ }
+}
+
+void *CVTFTexture::GetResourceData( uint32 eType, size_t *pDataSize ) const
+{
+ Assert( ( eType & RSRCF_MASK ) == 0 );
+ eType &= ~RSRCF_MASK;
+
+ ResourceEntryInfo const *pInfo = FindResourceEntryInfo( eType );
+ if ( pInfo )
+ {
+ if ( ( pInfo->eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 )
+ {
+ int idx = pInfo - m_arrResourcesInfo.Base();
+ ResourceMemorySection const &rms = m_arrResourcesData[ idx ];
+ if ( pDataSize )
+ {
+ *pDataSize = rms.m_nDataLength;
+ }
+ return rms.m_pData;
+ }
+ else
+ {
+ if ( pDataSize )
+ {
+ *pDataSize = sizeof( pInfo->resData );
+ }
+ return (void *)&pInfo->resData;
+ }
+ }
+ else
+ {
+ if ( pDataSize )
+ *pDataSize = 0;
+ }
+
+ return NULL;
+}
+
+bool CVTFTexture::HasResourceEntry( uint32 eType ) const
+{
+ return ( FindResourceEntryInfo( eType ) != NULL );
+}
+
+unsigned int CVTFTexture::GetResourceTypes( unsigned int *arrTypesBuffer, int numTypesBufferElems ) const
+{
+ for ( ResourceEntryInfo const *pInfo = m_arrResourcesInfo.Base(),
+ *pInfoEnd = pInfo + m_arrResourcesInfo.Count();
+ numTypesBufferElems-- > 0 && pInfo < pInfoEnd; )
+ {
+ *( arrTypesBuffer++ ) = ( ( pInfo++ )->eType & ~RSRCF_MASK );
+ }
+
+ return m_arrResourcesInfo.Count();
+}
+
+
+//-----------------------------------------------------------------------------
+// Serialization/Unserialization of resource data
+//-----------------------------------------------------------------------------
+bool CVTFTexture::ResourceMemorySection::LoadData( CUtlBuffer &buf, CByteswap &byteSwap )
+{
+ // Read the size
+ int iDataSize = 0;
+ buf.Get( &iDataSize, sizeof( iDataSize ) );
+ byteSwap.SwapBufferToTargetEndian( &iDataSize );
+
+ // Read the actual data
+ if ( !AllocateData( iDataSize ) )
+ return false;
+
+ buf.Get( m_pData, iDataSize );
+
+ // Test valid
+ bool bValid = buf.IsValid();
+
+ return bValid;
+}
+
+bool CVTFTexture::ResourceMemorySection::WriteData( CUtlBuffer &buf ) const
+{
+ Assert( m_nDataLength && m_pData );
+ int iBufSize = m_nDataLength;
+
+ buf.Put( &iBufSize, sizeof( iBufSize ) );
+ buf.Put( m_pData, m_nDataLength );
+
+ return buf.IsValid();
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks if the file data needs to be swapped
+//-----------------------------------------------------------------------------
+bool CVTFTexture::SetupByteSwap( CUtlBuffer &buf )
+{
+ VTFFileBaseHeader_t *header = (VTFFileBaseHeader_t*)buf.PeekGet();
+
+ if ( header->version[0] == SwapLong( VTF_MAJOR_VERSION ) )
+ {
+ m_Swap.ActivateByteSwapping( true );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization
+//-----------------------------------------------------------------------------
+static bool ReadHeaderFromBufferPastBaseHeader( CUtlBuffer &buf, VTFFileHeader_t &header )
+{
+ unsigned char *pBuf = (unsigned char*)(&header) + sizeof(VTFFileBaseHeader_t);
+ if ( header.version[1] == VTF_MINOR_VERSION )
+ {
+ buf.Get( pBuf, sizeof(VTFFileHeader_t) - sizeof(VTFFileBaseHeader_t) );
+ }
+ else if ( header.version[1] == 3 )
+ {
+ buf.Get( pBuf, sizeof(VTFFileHeaderV7_3_t) - sizeof(VTFFileBaseHeader_t) );
+ }
+ else if ( header.version[1] == 2 )
+ {
+ buf.Get( pBuf, sizeof(VTFFileHeaderV7_2_t) - sizeof(VTFFileBaseHeader_t) );
+
+ #if defined( _X360 ) || defined (POSIX)
+ // read 15 dummy bytes to be properly positioned with 7.2 PC data
+ byte dummy[15];
+ buf.Get( dummy, 15 );
+ #endif
+ }
+ else if ( header.version[1] == 1 || header.version[1] == 0 )
+ {
+ // previous version 7.0 or 7.1
+ buf.Get( pBuf, sizeof(VTFFileHeaderV7_1_t) - sizeof(VTFFileBaseHeader_t) );
+
+ #if defined( _X360 ) || defined (POSIX)
+ // read a dummy byte to be properly positioned with 7.0/1 PC data
+ byte dummy;
+ buf.Get( &dummy, 1 );
+ #endif
+ }
+ else
+ {
+ Warning( "*** Encountered VTF file with an invalid minor version!\n" );
+ return false;
+ }
+
+ return buf.IsValid();
+}
+
+bool CVTFTexture::ReadHeader( CUtlBuffer &buf, VTFFileHeader_t &header )
+{
+ if ( IsX360() && SetupByteSwap( buf ) )
+ {
+ VTFFileBaseHeader_t baseHeader;
+ m_Swap.SwapFieldsToTargetEndian( &baseHeader, (VTFFileBaseHeader_t*)buf.PeekGet() );
+
+ // Swap the header inside the UtlBuffer
+ if ( baseHeader.version[0] == VTF_MAJOR_VERSION )
+ {
+ if ( baseHeader.version[1] == 0 || baseHeader.version[1] == 1 )
+ {
+ // version 7.0 or 7.1
+ m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_1_t*)buf.PeekGet() );
+ }
+ else if ( baseHeader.version[1] == 2 )
+ {
+ // version 7.2
+ m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_2_t*)buf.PeekGet() );
+ }
+ else if ( baseHeader.version[1] == 3 )
+ {
+ m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_3_t*)buf.PeekGet() );
+ }
+ else if ( baseHeader.version[1] == VTF_MINOR_VERSION )
+ {
+ m_Swap.SwapFieldsToTargetEndian( (VTFFileHeader_t*)buf.PeekGet() );
+ }
+ }
+ }
+
+ memset( &header, 0, sizeof(VTFFileHeader_t) );
+ buf.Get( &header, sizeof(VTFFileBaseHeader_t) );
+ if ( !buf.IsValid() )
+ {
+ Warning( "*** Error unserializing VTF file... is the file empty?\n" );
+ return false;
+ }
+
+ // Validity check
+ if ( Q_strncmp( header.fileTypeString, "VTF", 4 ) )
+ {
+ Warning( "*** Tried to load a non-VTF file as a VTF file!\n" );
+ return false;
+ }
+
+ if ( header.version[0] != VTF_MAJOR_VERSION )
+ {
+ Warning( "*** Encountered VTF file with an invalid version!\n" );
+ return false;
+ }
+
+ if ( !ReadHeaderFromBufferPastBaseHeader( buf, header ) )
+ {
+ Warning( "*** Encountered VTF file with an invalid full header!\n" );
+ return false;
+ }
+
+ // version fixups
+ switch ( header.version[1] )
+ {
+ case 0:
+ case 1:
+ header.depth = 1;
+ // fall-through
+ case 2:
+ header.numResources = 0;
+ // fall-through
+ case 3:
+ header.flags &= VERSIONED_VTF_FLAGS_MASK_7_3;
+ // fall-through
+ case VTF_MINOR_VERSION:
+ break;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization
+//-----------------------------------------------------------------------------
+bool CVTFTexture::Unserialize( CUtlBuffer &buf, bool bHeaderOnly, int nSkipMipLevels )
+{
+ return UnserializeEx( buf, bHeaderOnly, 0, nSkipMipLevels );
+}
+
+bool CVTFTexture::UnserializeEx( CUtlBuffer &buf, bool bHeaderOnly, int nForceFlags, int nSkipMipLevels )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (header only: %d, nForceFlags: %d, skipMips: %d)", __FUNCTION__, bHeaderOnly ? 1 : 0, nForceFlags, nSkipMipLevels );
+
+ // When unserializing, we can skip a certain number of mip levels,
+ // and we also can just load everything but the image data
+ VTFFileHeader_t header;
+
+ if ( !ReadHeader( buf, header ) )
+ return false;
+
+ // Pretend these flags are also set.
+ header.flags |= nForceFlags;
+
+ if ( (header.flags & TEXTUREFLAGS_ENVMAP) && (header.width != header.height) )
+ {
+ Warning( "*** Encountered VTF non-square cubemap!\n" );
+ return false;
+ }
+ if ( (header.flags & TEXTUREFLAGS_ENVMAP) && (header.depth != 1) )
+ {
+ Warning( "*** Encountered VTF volume texture cubemap!\n" );
+ return false;
+ }
+ if ( header.width <= 0 || header.height <= 0 || header.depth <= 0 )
+ {
+ Warning( "*** Encountered VTF invalid texture size!\n" );
+ return false;
+ }
+ if ( ( header.imageFormat < IMAGE_FORMAT_UNKNOWN ) || ( header.imageFormat >= NUM_IMAGE_FORMATS ) )
+ {
+ Warning( "*** Encountered VTF invalid image format!\n" );
+ return false;
+ }
+
+ // If the header says we should be doing a texture allocation of more than 32M, just tell the caller we failed.
+ const int cMaxImageSizeLog2 = Q_log2( 32 * 1024 * 1024 );
+ if ( ( Q_log2( header.width ) + Q_log2( header.height ) + Q_log2( header.depth ) + Q_log2( header.numFrames ) > cMaxImageSizeLog2 ) || ( header.numResources > MAX_RSRC_DICTIONARY_ENTRIES ) )
+ {
+ STAGING_ONLY_EXEC( DevWarning( "Asked for a large texture to be created (%d h x %d w x %d d x %d f). Nope.\n", header.width, header.height, header.depth, header.numFrames ) );
+ return false;
+ }
+
+ m_nWidth = header.width;
+ m_nHeight = header.height;
+ m_nDepth = header.depth;
+ m_Format = header.imageFormat;
+ m_nFlags = header.flags;
+ m_nFrameCount = header.numFrames;
+
+
+ m_nFaceCount = (m_nFlags & TEXTUREFLAGS_ENVMAP) ? CUBEMAP_FACE_COUNT : 1;
+
+ // NOTE: We're going to store space for all mip levels, even if we don't
+ // have data on disk for them. This is for backward compatibility
+ m_nMipCount = ComputeMipCount();
+
+ m_nFinestMipmapLevel = 0;
+ m_nCoarsestMipmapLevel = m_nMipCount - 1;
+
+ m_vecReflectivity = header.reflectivity;
+ m_flBumpScale = header.bumpScale;
+
+ // FIXME: Why is this needed?
+ m_iStartFrame = header.startFrame;
+
+ // This is to make sure old-format .vtf files are read properly
+ m_nVersion[0] = header.version[0];
+ m_nVersion[1] = header.version[1];
+
+ if ( header.lowResImageWidth == 0 || header.lowResImageHeight == 0 )
+ {
+ m_nLowResImageWidth = 0;
+ m_nLowResImageHeight = 0;
+ }
+ else
+ {
+ m_nLowResImageWidth = header.lowResImageWidth;
+ m_nLowResImageHeight = header.lowResImageHeight;
+ }
+ m_LowResImageFormat = header.lowResImageFormat;
+
+ // invalid image format
+ if ( ( m_LowResImageFormat < IMAGE_FORMAT_UNKNOWN ) || ( m_LowResImageFormat >= NUM_IMAGE_FORMATS ) )
+ return false;
+
+ // Keep the allocated memory chunks of data
+ if ( int( header.numResources ) < m_arrResourcesData.Count() )
+ {
+ m_arrResourcesData_ForReuse.EnsureCapacity( m_arrResourcesData_ForReuse.Count() + m_arrResourcesData.Count() - header.numResources );
+ for ( ResourceMemorySection const *pRms = &m_arrResourcesData[ header.numResources ],
+ *pRmsEnd = m_arrResourcesData.Base() + m_arrResourcesData.Count(); pRms < pRmsEnd; ++ pRms )
+ {
+ if ( pRms->m_pData )
+ {
+ int idxReuse = m_arrResourcesData_ForReuse.AddToTail( *pRms );
+ m_arrResourcesData_ForReuse[ idxReuse ].m_nDataLength = 0; // Data for reuse shouldn't have length set
+ }
+ }
+ }
+ m_arrResourcesData.SetCount( header.numResources );
+
+ // Read the dictionary of resources info
+ if ( header.numResources > 0 )
+ {
+ m_arrResourcesInfo.RemoveAll();
+ m_arrResourcesInfo.SetCount( header.numResources );
+
+ buf.Get( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) );
+ if ( !buf.IsValid() )
+ return false;
+
+ if ( IsX360() )
+ {
+ // Byte-swap the dictionary data offsets
+ for ( int k = 0; k < m_arrResourcesInfo.Count(); ++ k )
+ {
+ ResourceEntryInfo &rei = m_arrResourcesInfo[k];
+ if ( ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 )
+ {
+ m_Swap.SwapBufferToTargetEndian( &rei.resData );
+ }
+ }
+ }
+ }
+ else
+ {
+ // Older version (7.0 - 7.2):
+ // - low-res image data first (optional)
+ // - then image data
+ m_arrResourcesInfo.RemoveAll();
+
+ // Low-res image data
+ int nLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth,
+ m_nLowResImageHeight, 1, m_LowResImageFormat, false );
+ if ( nLowResImageSize )
+ {
+ ResourceEntryInfo &rei = *FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE );
+ rei.resData = buf.TellGet();
+ }
+
+ // Image data
+ ResourceEntryInfo &rei = *FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+ rei.resData = buf.TellGet() + nLowResImageSize;
+ }
+
+ // Caller wants the header component only, avoids reading large image data sets
+ if ( bHeaderOnly )
+ return true;
+
+ // Load the low res image
+ if ( ResourceEntryInfo const *pLowResDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) )
+ {
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, pLowResDataInfo->resData );
+ if ( !LoadLowResData( buf ) )
+ return false;
+ }
+
+ // Load any new resources
+ if ( !LoadNewResources( buf ) )
+ {
+ return false;
+ }
+
+ // Load the image data
+ if ( ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ) )
+ {
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, pImageDataInfo->resData );
+ if ( !LoadImageData( buf, header, nSkipMipLevels ) )
+ return false;
+ }
+ else
+ {
+ // No image data
+ return false;
+ }
+
+ return true;
+}
+
+void CVTFTexture::GetMipmapRange( int* pOutFinest, int* pOutCoarsest )
+{
+ if ( pOutFinest )
+ *pOutFinest = m_nFinestMipmapLevel;
+
+ if ( pOutCoarsest )
+ *pOutCoarsest = m_nCoarsestMipmapLevel;
+}
+
+bool CVTFTexture::LoadNewResources( CUtlBuffer &buf )
+{
+ // Load the new resources
+ for ( int idxRsrc = 0; idxRsrc < m_arrResourcesInfo.Count(); ++idxRsrc )
+ {
+ ResourceEntryInfo &rei = m_arrResourcesInfo[ idxRsrc ];
+ ResourceMemorySection &rms = m_arrResourcesData[ idxRsrc ];
+
+ if ( ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 )
+ {
+ switch( rei.eType )
+ {
+ case VTF_LEGACY_RSRC_LOW_RES_IMAGE:
+ case VTF_LEGACY_RSRC_IMAGE:
+ // these legacy resources are loaded differently
+ continue;
+
+ default:
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, rei.resData );
+ if ( !rms.LoadData( buf, m_Swap ) )
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+ResourceEntryInfo const *CVTFTexture::FindResourceEntryInfo( uint32 eType ) const
+{
+ Assert( ( eType & RSRCF_MASK ) == 0 );
+
+ ResourceEntryInfo const *pRange[2];
+ pRange[0] = m_arrResourcesInfo.Base();
+ pRange[1] = pRange[0] + m_arrResourcesInfo.Count();
+
+ if ( IsPC() )
+ {
+ // Quick-search in a sorted array
+ ResourceEntryInfo const *pMid;
+find_routine:
+ if ( pRange[0] != pRange[1] )
+ {
+ pMid = pRange[0] + ( pRange[1] - pRange[0] ) / 2;
+ if ( int diff = int( pMid->eType & ~RSRCF_MASK ) - int( eType ) )
+ {
+ int off = !( diff > 0 );
+ pRange[ !off ] = pMid + off;
+ goto find_routine;
+ }
+ else
+ return pMid;
+ }
+ else
+ return NULL;
+ }
+ else
+ {
+ // 360 eschews a sorted format due to endian issues
+ // use a linear search for compatibility with reading pc formats
+ for ( ; pRange[0] < pRange[1]; ++pRange[0] )
+ {
+ if ( ( pRange[0]->eType & ~RSRCF_MASK ) == eType )
+ return pRange[0];
+ }
+ }
+
+ return NULL;
+}
+
+ResourceEntryInfo * CVTFTexture::FindResourceEntryInfo( uint32 eType )
+{
+ return const_cast< ResourceEntryInfo * >(
+ ( ( CVTFTexture const * ) this )->FindResourceEntryInfo( eType ) );
+}
+
+ResourceEntryInfo * CVTFTexture::FindOrCreateResourceEntryInfo( uint32 eType )
+{
+ Assert( ( eType & RSRCF_MASK ) == 0 );
+
+ int k = 0;
+ for ( ; k < m_arrResourcesInfo.Count(); ++ k )
+ {
+ uint32 rsrcType = ( m_arrResourcesInfo[ k ].eType & ~RSRCF_MASK );
+ if ( rsrcType == eType )
+ {
+ // found
+ return &m_arrResourcesInfo[ k ];
+ }
+
+ // sort for PC only, 360 uses linear sort for compatibility with PC endian
+ if ( IsPC() )
+ {
+ if ( rsrcType > eType )
+ break;
+ }
+ }
+
+ ResourceEntryInfo rei;
+ memset( &rei, 0, sizeof( rei ) );
+ rei.eType = eType;
+
+ // Inserting before "k"
+ if ( m_arrResourcesData_ForReuse.Count() )
+ {
+ m_arrResourcesData.InsertBefore( k, m_arrResourcesData_ForReuse[ m_arrResourcesData_ForReuse.Count() - 1 ] );
+ m_arrResourcesData_ForReuse.FastRemove( m_arrResourcesData_ForReuse.Count() - 1 );
+ }
+ else
+ {
+ m_arrResourcesData.InsertBefore( k );
+ }
+
+ m_arrResourcesInfo.InsertBefore( k, rei );
+ return &m_arrResourcesInfo[k];
+}
+
+bool CVTFTexture::RemoveResourceEntryInfo( uint32 eType )
+{
+ Assert( ( eType & RSRCF_MASK ) == 0 );
+
+ for ( int k = 0; k < m_arrResourcesInfo.Count(); ++ k )
+ {
+ if ( ( m_arrResourcesInfo[ k ].eType & ~RSRCF_MASK ) == eType )
+ {
+ m_arrResourcesInfo.Remove( k );
+
+ if ( m_arrResourcesData[k].m_pData )
+ {
+ int idxReuse = m_arrResourcesData_ForReuse.AddToTail( m_arrResourcesData[k] );
+ m_arrResourcesData_ForReuse[ idxReuse ].m_nDataLength = 0; // Data for reuse shouldn't have length set
+ }
+
+ m_arrResourcesData.Remove( k );
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Serialization of image data
+//-----------------------------------------------------------------------------
+bool CVTFTexture::WriteImageData( CUtlBuffer &buf )
+{
+ // NOTE: We load the bits this way because we store the bits in memory
+ // differently that the way they are stored on disk; we store on disk
+ // differently so we can only load up
+ // NOTE: The smallest mip levels are stored first!!
+ for (int iMip = m_nMipCount; --iMip >= 0; )
+ {
+ int iMipSize = ComputeMipSize( iMip );
+
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < m_nFaceCount; ++iFace)
+ {
+ unsigned char *pMipBits = ImageData( iFrame, iFace, iMip );
+ buf.Put( pMipBits, iMipSize );
+ }
+ }
+ }
+
+ return buf.IsValid();
+}
+
+// Inserts padding to have a multiple of "iAlignment" bytes in the buffer
+// Returns number of pad bytes written
+static int PadBuffer( CUtlBuffer &buf, int iAlignment )
+{
+ unsigned int uiCurrentBytes = buf.TellPut();
+ int iPadBytes = AlignValue( uiCurrentBytes, iAlignment ) - uiCurrentBytes;
+
+ // Fill data
+ for ( int i=0; i<iPadBytes; i++ )
+ {
+ buf.PutChar( '\0' );
+ }
+
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, uiCurrentBytes + iPadBytes );
+
+ return iPadBytes;
+}
+
+//-----------------------------------------------------------------------------
+// Serialization
+//-----------------------------------------------------------------------------
+bool CVTFTexture::Serialize( CUtlBuffer &buf )
+{
+ if ( IsX360() )
+ {
+ // Unsupported path, 360 has no reason and cannot serialize
+ Assert( 0 );
+ return false;
+ }
+
+ if ( !m_pImageData )
+ {
+ Warning("*** Unable to serialize... have no image data!\n");
+ return false;
+ }
+
+ VTFFileHeader_t header;
+ memset( &header, 0, sizeof( header ) );
+ Q_strncpy( header.fileTypeString, "VTF", 4 );
+ header.version[0] = VTF_MAJOR_VERSION;
+ header.version[1] = VTF_MINOR_VERSION;
+ header.headerSize = sizeof(VTFFileHeader_t) + m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo );
+
+ header.width = m_nWidth;
+ header.height = m_nHeight;
+ header.depth = m_nDepth;
+ header.flags = m_nFlags;
+ header.numFrames = m_nFrameCount;
+ header.numMipLevels = m_nMipCount;
+ header.imageFormat = m_Format;
+ VectorCopy( m_vecReflectivity, header.reflectivity );
+ header.bumpScale = m_flBumpScale;
+
+ // FIXME: Why is this needed?
+ header.startFrame = m_iStartFrame;
+
+ header.lowResImageWidth = m_nLowResImageWidth;
+ header.lowResImageHeight = m_nLowResImageHeight;
+ header.lowResImageFormat = m_LowResImageFormat;
+
+ header.numResources = m_arrResourcesInfo.Count();
+
+ buf.Put( &header, sizeof(VTFFileHeader_t) );
+ if ( !buf.IsValid() )
+ return false;
+
+ // Write the dictionary of resource entry infos
+ int iSeekOffsetResInfoFixup = buf.TellPut();
+ buf.Put( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) );
+ if ( !buf.IsValid() )
+ return false;
+
+ // Write the low res image first
+ if ( ResourceEntryInfo *pRei = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) )
+ {
+ pRei->resData = buf.TellPut();
+
+ Assert( m_pLowResImageData );
+ int iLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth,
+ m_nLowResImageHeight, 1, m_LowResImageFormat, false );
+ buf.Put( m_pLowResImageData, iLowResImageSize );
+ if ( !buf.IsValid() )
+ return false;
+ }
+
+ // Serialize the new resources
+ for ( int iRsrc = 0; iRsrc < m_arrResourcesInfo.Count(); ++ iRsrc )
+ {
+ ResourceEntryInfo &rei = m_arrResourcesInfo[ iRsrc ];
+
+ switch ( rei.eType )
+ {
+ case VTF_LEGACY_RSRC_LOW_RES_IMAGE:
+ case VTF_LEGACY_RSRC_IMAGE:
+ // written differently
+ continue;
+
+ default:
+ {
+ if ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK )
+ continue;
+ rei.resData = buf.TellPut();
+ ResourceMemorySection &rms = m_arrResourcesData[ iRsrc ];
+ if ( !rms.WriteData( buf ) )
+ return false;
+ }
+ }
+ }
+
+ // Write image data last
+ if ( ResourceEntryInfo *pRei = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ) )
+ {
+ pRei->resData = buf.TellPut();
+ WriteImageData( buf );
+ }
+ else
+ return false;
+
+ // Now fixup the resources dictionary
+ int iTotalBytesPut = buf.TellPut();
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, iSeekOffsetResInfoFixup );
+ buf.Put( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, iTotalBytesPut );
+
+ // Return if the buffer is valid
+ return buf.IsValid();
+}
+
+//-----------------------------------------------------------------------------
+// Attributes...
+//-----------------------------------------------------------------------------
+int CVTFTexture::Width() const
+{
+ return m_nWidth;
+}
+
+int CVTFTexture::Height() const
+{
+ return m_nHeight;
+}
+
+int CVTFTexture::Depth() const
+{
+ return m_nDepth;
+}
+
+int CVTFTexture::MipCount() const
+{
+ return m_nMipCount;
+}
+
+ImageFormat CVTFTexture::Format() const
+{
+ return m_Format;
+}
+
+int CVTFTexture::FaceCount() const
+{
+ return m_nFaceCount;
+}
+
+int CVTFTexture::FrameCount() const
+{
+ return m_nFrameCount;
+}
+
+int CVTFTexture::Flags() const
+{
+ return m_nFlags;
+}
+
+bool CVTFTexture::IsCubeMap() const
+{
+ return (m_nFlags & TEXTUREFLAGS_ENVMAP) != 0;
+}
+
+bool CVTFTexture::IsNormalMap() const
+{
+ return (m_nFlags & TEXTUREFLAGS_NORMAL) != 0;
+}
+
+bool CVTFTexture::IsVolumeTexture() const
+{
+ return (m_nDepth > 1);
+}
+
+float CVTFTexture::BumpScale() const
+{
+ return m_flBumpScale;
+}
+
+const Vector &CVTFTexture::Reflectivity() const
+{
+ return m_vecReflectivity;
+}
+
+unsigned char *CVTFTexture::ImageData()
+{
+ return m_pImageData;
+}
+
+int CVTFTexture::LowResWidth() const
+{
+ return m_nLowResImageWidth;
+}
+
+int CVTFTexture::LowResHeight() const
+{
+ return m_nLowResImageHeight;
+}
+
+ImageFormat CVTFTexture::LowResFormat() const
+{
+ return m_LowResImageFormat;
+}
+
+unsigned char *CVTFTexture::LowResImageData()
+{
+ return m_pLowResImageData;
+}
+
+int CVTFTexture::RowSizeInBytes( int nMipLevel ) const
+{
+ int nWidth = (m_nWidth >> nMipLevel);
+ if (nWidth < 1)
+ {
+ nWidth = 1;
+ }
+ return ImageLoader::SizeInBytes( m_Format ) * nWidth;
+}
+
+
+//-----------------------------------------------------------------------------
+// returns the size of one face of a particular mip level
+//-----------------------------------------------------------------------------
+int CVTFTexture::FaceSizeInBytes( int nMipLevel ) const
+{
+ int nWidth = (m_nWidth >> nMipLevel);
+ if (nWidth < 1)
+ {
+ nWidth = 1;
+ }
+ int nHeight = (m_nHeight >> nMipLevel);
+ if (nHeight < 1)
+ {
+ nHeight = 1;
+ }
+ return ImageLoader::SizeInBytes( m_Format ) * nWidth * nHeight;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns a pointer to the data associated with a particular frame, face, and mip level
+//-----------------------------------------------------------------------------
+unsigned char *CVTFTexture::ImageData( int iFrame, int iFace, int iMipLevel )
+{
+ Assert( m_pImageData );
+ int iOffset = GetImageOffset( iFrame, iFace, iMipLevel, m_Format );
+ return &m_pImageData[iOffset];
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns a pointer to the data associated with a particular frame, face, mip level, and offset
+//-----------------------------------------------------------------------------
+unsigned char *CVTFTexture::ImageData( int iFrame, int iFace, int iMipLevel, int x, int y, int z )
+{
+#ifdef _DEBUG
+ int nWidth, nHeight, nDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nWidth, &nHeight, &nDepth );
+ Assert( (x >= 0) && (x <= nWidth) && (y >= 0) && (y <= nHeight) && (z >= 0) && (z <= nDepth) );
+#endif
+
+ int nFaceBytes = FaceSizeInBytes( iMipLevel );
+ int nRowBytes = RowSizeInBytes( iMipLevel );
+ int nTexelBytes = ImageLoader::SizeInBytes( m_Format );
+
+ unsigned char *pMipBits = ImageData( iFrame, iFace, iMipLevel );
+ pMipBits += z * nFaceBytes + y * nRowBytes + x * nTexelBytes;
+ return pMipBits;
+}
+
+//-----------------------------------------------------------------------------
+// Computes the size (in bytes) of a single mipmap of a single face of a single frame
+//-----------------------------------------------------------------------------
+inline int CVTFTexture::ComputeMipSize( int iMipLevel, ImageFormat fmt ) const
+{
+ Assert( iMipLevel < m_nMipCount );
+ int w, h, d;
+ ComputeMipLevelDimensions( iMipLevel, &w, &h, &d );
+ return ImageLoader::GetMemRequired( w, h, d, fmt, false );
+}
+
+int CVTFTexture::ComputeMipSize( int iMipLevel ) const
+{
+ // Version for the public interface; don't want to expose the fmt parameter
+ return ComputeMipSize( iMipLevel, m_Format );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the size of a single face of a single frame
+// All mip levels starting at the specified mip level are included
+//-----------------------------------------------------------------------------
+inline int CVTFTexture::ComputeFaceSize( int iStartingMipLevel, ImageFormat fmt ) const
+{
+ int iSize = 0;
+ int w = m_nWidth;
+ int h = m_nHeight;
+ int d = m_nDepth;
+
+ for( int i = 0; i < m_nMipCount; ++i )
+ {
+ if (i >= iStartingMipLevel)
+ {
+ iSize += ImageLoader::GetMemRequired( w, h, d, fmt, false );
+ }
+ w >>= 1;
+ h >>= 1;
+ d >>= 1;
+ if ( w < 1 )
+ {
+ w = 1;
+ }
+ if ( h < 1 )
+ {
+ h = 1;
+ }
+ if ( d < 1 )
+ {
+ d = 1;
+ }
+ }
+ return iSize;
+}
+
+int CVTFTexture::ComputeFaceSize( int iStartingMipLevel ) const
+{
+ // Version for the public interface; don't want to expose the fmt parameter
+ return ComputeFaceSize( iStartingMipLevel, m_Format );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the total size of all faces, all frames
+//-----------------------------------------------------------------------------
+inline int CVTFTexture::ComputeTotalSize( ImageFormat fmt ) const
+{
+ // Compute the number of bytes required to store a single face/frame
+ int iMemRequired = ComputeFaceSize( 0, fmt );
+
+ // Now compute the total image size
+ return m_nFaceCount * m_nFrameCount * iMemRequired;
+}
+
+int CVTFTexture::ComputeTotalSize( ) const
+{
+ // Version for the public interface; don't want to expose the fmt parameter
+ return ComputeTotalSize( m_Format );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the location of a particular frame, face, and mip level
+//-----------------------------------------------------------------------------
+int CVTFTexture::GetImageOffset( int iFrame, int iFace, int iMipLevel, ImageFormat fmt ) const
+{
+ Assert( iFrame < m_nFrameCount );
+ Assert( iFace < m_nFaceCount );
+ Assert( iMipLevel < m_nMipCount );
+
+ int i;
+ int iOffset = 0;
+
+ if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) )
+ {
+ // 360 data is stored same as disk, 1x1 up to NxN
+ // get to the right miplevel
+ int iMipWidth, iMipHeight, iMipDepth;
+ for ( i = m_nMipCount - 1; i > iMipLevel; --i )
+ {
+ ComputeMipLevelDimensions( i, &iMipWidth, &iMipHeight, &iMipDepth );
+ int iMipLevelSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, fmt, false );
+ iOffset += m_nFrameCount * m_nFaceCount * iMipLevelSize;
+ }
+
+ // get to the right frame
+ ComputeMipLevelDimensions( iMipLevel, &iMipWidth, &iMipHeight, &iMipDepth );
+ int nFaceSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, fmt, false );
+ iOffset += iFrame * m_nFaceCount * nFaceSize;
+
+ // get to the right face
+ iOffset += iFace * nFaceSize;
+
+ return iOffset;
+ }
+
+ // get to the right frame
+ int iFaceSize = ComputeFaceSize( 0, fmt );
+ iOffset = iFrame * m_nFaceCount * iFaceSize;
+
+ // Get to the right face
+ iOffset += iFace * iFaceSize;
+
+ // Get to the right mip level
+ for (i = 0; i < iMipLevel; ++i)
+ {
+ iOffset += ComputeMipSize( i, fmt );
+ }
+
+ return iOffset;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the dimensions of a particular mip level
+//-----------------------------------------------------------------------------
+void CVTFTexture::ComputeMipLevelDimensions( int iMipLevel, int *pMipWidth, int *pMipHeight, int *pMipDepth ) const
+{
+ Assert( iMipLevel < m_nMipCount );
+
+ *pMipWidth = m_nWidth >> iMipLevel;
+ *pMipHeight = m_nHeight >> iMipLevel;
+ *pMipDepth = m_nDepth >> iMipLevel;
+ if ( *pMipWidth < 1 )
+ {
+ *pMipWidth = 1;
+ }
+ if ( *pMipHeight < 1 )
+ {
+ *pMipHeight = 1;
+ }
+ if ( *pMipDepth < 1 )
+ {
+ *pMipDepth = 1;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the size of a subrect at a particular mip level
+//-----------------------------------------------------------------------------
+void CVTFTexture::ComputeMipLevelSubRect( Rect_t *pSrcRect, int nMipLevel, Rect_t *pSubRect ) const
+{
+ Assert( pSrcRect->x >= 0 && pSrcRect->y >= 0 &&
+ (pSrcRect->x + pSrcRect->width <= m_nWidth) &&
+ (pSrcRect->y + pSrcRect->height <= m_nHeight) );
+
+ if (nMipLevel == 0)
+ {
+ *pSubRect = *pSrcRect;
+ return;
+ }
+
+ float flInvShrink = 1.0f / (float)(1 << nMipLevel);
+ pSubRect->x = pSrcRect->x * flInvShrink;
+ pSubRect->y = pSrcRect->y * flInvShrink;
+ pSubRect->width = (int)ceil( (pSrcRect->x + pSrcRect->width) * flInvShrink ) - pSubRect->x;
+ pSubRect->height = (int)ceil( (pSrcRect->y + pSrcRect->height) * flInvShrink ) - pSubRect->y;
+}
+
+
+//-----------------------------------------------------------------------------
+// Converts the texture's image format. Use IMAGE_FORMAT_DEFAULT
+// if you want to be able to use various tool functions below
+//-----------------------------------------------------------------------------
+void CVTFTexture::ConvertImageFormat( ImageFormat fmt, bool bNormalToDUDV )
+{
+ if ( !m_pImageData )
+ {
+ return;
+ }
+
+ if ( fmt == IMAGE_FORMAT_DEFAULT )
+ {
+ fmt = IMAGE_FORMAT_RGBA8888;
+ }
+
+ if ( bNormalToDUDV && !( fmt == IMAGE_FORMAT_UV88 || fmt == IMAGE_FORMAT_UVWQ8888 || fmt == IMAGE_FORMAT_UVLX8888 ) )
+ {
+ Assert( 0 );
+ return;
+ }
+
+ if ( m_Format == fmt )
+ {
+ return;
+ }
+
+ if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) )
+ {
+ // 360 textures should be baked in final format
+ Assert( 0 );
+ return;
+ }
+
+ // FIXME: Should this be re-written to not do an allocation?
+ int iConvertedSize = ComputeTotalSize( fmt );
+
+ unsigned char *pConvertedImage = new unsigned char[ iConvertedSize ];
+
+ // This can happen for large, bogus textures.
+ if ( !pConvertedImage )
+ return;
+
+ for (int iMip = 0; iMip < m_nMipCount; ++iMip)
+ {
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMip, &nMipWidth, &nMipHeight, &nMipDepth );
+
+ int nSrcFaceStride = ImageLoader::GetMemRequired( nMipWidth, nMipHeight, 1, m_Format, false );
+ int nDstFaceStride = ImageLoader::GetMemRequired( nMipWidth, nMipHeight, 1, fmt, false );
+
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < m_nFaceCount; ++iFace)
+ {
+ unsigned char *pSrcData = ImageData( iFrame, iFace, iMip );
+ unsigned char *pDstData = pConvertedImage +
+ GetImageOffset( iFrame, iFace, iMip, fmt );
+
+ for ( int z = 0; z < nMipDepth; ++z, pSrcData += nSrcFaceStride, pDstData += nDstFaceStride )
+ {
+ if( bNormalToDUDV )
+ {
+ if( fmt == IMAGE_FORMAT_UV88 )
+ {
+ ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUV88( pSrcData,
+ nMipWidth, nMipHeight, pDstData );
+ }
+ else if( fmt == IMAGE_FORMAT_UVWQ8888 )
+ {
+ ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUVWQ8888( pSrcData,
+ nMipWidth, nMipHeight, pDstData );
+ }
+ else if ( fmt == IMAGE_FORMAT_UVLX8888 )
+ {
+ ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUVLX8888( pSrcData,
+ nMipWidth, nMipHeight, pDstData );
+ }
+ else
+ {
+ Assert( 0 );
+ return;
+ }
+ }
+ else
+ {
+ ImageLoader::ConvertImageFormat( pSrcData, m_Format,
+ pDstData, fmt, nMipWidth, nMipHeight );
+ }
+ }
+ }
+ }
+ }
+
+ if ( !AllocateImageData(iConvertedSize) )
+ return;
+
+ memcpy( m_pImageData, pConvertedImage, iConvertedSize );
+ m_Format = fmt;
+
+ if ( !ImageLoader::IsCompressed( fmt ) )
+ {
+ int nAlphaBits = ImageLoader::ImageFormatInfo( fmt ).m_NumAlphaBits;
+ if ( nAlphaBits > 1 )
+ {
+ m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA;
+ m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA;
+ }
+ if ( nAlphaBits <= 1 )
+ {
+ m_nFlags &= ~TEXTUREFLAGS_EIGHTBITALPHA;
+ if ( nAlphaBits == 0 )
+ {
+ m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA;
+ }
+ }
+ }
+ else
+ {
+ // Only DXT5 has alpha bits
+ if ( ( fmt == IMAGE_FORMAT_DXT1 ) || ( fmt == IMAGE_FORMAT_ATI2N ) || ( fmt == IMAGE_FORMAT_ATI1N ) )
+ {
+ m_nFlags &= ~(TEXTUREFLAGS_ONEBITALPHA|TEXTUREFLAGS_EIGHTBITALPHA);
+ }
+ }
+
+ delete [] pConvertedImage;
+}
+
+
+//-----------------------------------------------------------------------------
+// Enums + structures related to conversion from cube to spheremap
+//-----------------------------------------------------------------------------
+struct SphereCalc_t
+{
+ Vector dir;
+ float m_flRadius;
+ float m_flOORadius;
+ float m_flRadiusSq;
+ LookDir_t m_LookDir;
+ Vector m_vecLookDir;
+ unsigned char m_pColor[4];
+ unsigned char **m_ppCubeFaces;
+ int m_iSize;
+};
+
+
+//-----------------------------------------------------------------------------
+//
+// Methods associated with computing a spheremap from a cubemap
+//
+//-----------------------------------------------------------------------------
+static void CalcInit( SphereCalc_t *pCalc, int iSize, unsigned char **ppCubeFaces, LookDir_t lookDir = LOOK_DOWN_Z )
+{
+ // NOTE: Width + height should be the same
+ pCalc->m_flRadius = iSize * 0.5f;
+ pCalc->m_flRadiusSq = pCalc->m_flRadius * pCalc->m_flRadius;
+ pCalc->m_flOORadius = 1.0f / pCalc->m_flRadius;
+ pCalc->m_LookDir = lookDir;
+ pCalc->m_ppCubeFaces = ppCubeFaces;
+ pCalc->m_iSize = iSize;
+
+ switch( lookDir)
+ {
+ case LOOK_DOWN_X:
+ pCalc->m_vecLookDir.Init( 1, 0, 0 );
+ break;
+
+ case LOOK_DOWN_NEGX:
+ pCalc->m_vecLookDir.Init( -1, 0, 0 );
+ break;
+
+ case LOOK_DOWN_Y:
+ pCalc->m_vecLookDir.Init( 0, 1, 0 );
+ break;
+
+ case LOOK_DOWN_NEGY:
+ pCalc->m_vecLookDir.Init( 0, -1, 0 );
+ break;
+
+ case LOOK_DOWN_Z:
+ pCalc->m_vecLookDir.Init( 0, 0, 1 );
+ break;
+
+ case LOOK_DOWN_NEGZ:
+ pCalc->m_vecLookDir.Init( 0, 0, -1 );
+ break;
+ }
+}
+
+static void TransformNormal( SphereCalc_t *pCalc, Vector& normal )
+{
+ Vector vecTemp = normal;
+
+ switch( pCalc->m_LookDir)
+ {
+ // Look down +x
+ case LOOK_DOWN_X:
+ normal[0] = vecTemp[2];
+ normal[2] = -vecTemp[0];
+ break;
+
+ // Look down -x
+ case LOOK_DOWN_NEGX:
+ normal[0] = -vecTemp[2];
+ normal[2] = vecTemp[0];
+ break;
+
+ // Look down +y
+ case LOOK_DOWN_Y:
+ normal[0] = -vecTemp[0];
+ normal[1] = vecTemp[2];
+ normal[2] = vecTemp[1];
+ break;
+
+ // Look down -y
+ case LOOK_DOWN_NEGY:
+ normal[0] = vecTemp[0];
+ normal[1] = -vecTemp[2];
+ normal[2] = vecTemp[1];
+ break;
+
+ // Look down +z
+ case LOOK_DOWN_Z:
+ return;
+
+ // Look down -z
+ case LOOK_DOWN_NEGZ:
+ normal[0] = -vecTemp[0];
+ normal[2] = -vecTemp[2];
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Given a iFace normal, determine which cube iFace to sample
+//-----------------------------------------------------------------------------
+static int CalcFaceIndex( const Vector& normal )
+{
+ float absx, absy, absz;
+
+ absx = normal[0] >= 0 ? normal[0] : -normal[0];
+ absy = normal[1] >= 0 ? normal[1] : -normal[1];
+ absz = normal[2] >= 0 ? normal[2] : -normal[2];
+
+ if ( absx > absy )
+ {
+ if ( absx > absz )
+ {
+ // left/right
+ if ( normal[0] >= 0 )
+ return CUBEMAP_FACE_RIGHT;
+ return CUBEMAP_FACE_LEFT;
+ }
+ }
+ else
+ {
+ if ( absy > absz )
+ {
+ // front / back
+ if ( normal[1] >= 0 )
+ return CUBEMAP_FACE_BACK;
+ return CUBEMAP_FACE_FRONT;
+ }
+ }
+
+ // top / bottom
+ if ( normal[2] >= 0 )
+ return CUBEMAP_FACE_UP;
+ return CUBEMAP_FACE_DOWN;
+}
+
+static void CalcColor( SphereCalc_t *pCalc, int iFace, const Vector &normal, unsigned char *color )
+{
+ float x, y, w;
+
+ int size = pCalc->m_iSize;
+ float hw = 0.5 * size;
+
+ if ( (iFace == CUBEMAP_FACE_LEFT) || (iFace == CUBEMAP_FACE_RIGHT) )
+ {
+ w = hw / normal[0];
+ x = -normal[2];
+ y = -normal[1];
+ if ( iFace == CUBEMAP_FACE_LEFT )
+ y = -y;
+ }
+ else if ( (iFace == CUBEMAP_FACE_FRONT) || (iFace == CUBEMAP_FACE_BACK) )
+ {
+ w = hw / normal[1];
+ x = normal[0];
+ y = normal[2];
+ if ( iFace == CUBEMAP_FACE_FRONT )
+ x = -x;
+ }
+ else
+ {
+ w = hw / normal[2];
+ x = -normal[0];
+ y = -normal[1];
+ if ( iFace == CUBEMAP_FACE_UP )
+ x = -x;
+ }
+
+ x = (x * w) + hw - 0.5;
+ y = (y * w) + hw - 0.5;
+
+ int u = (int)(x+0.5);
+ int v = (int)(y+0.5);
+
+ if ( u < 0 ) u = 0;
+ else if ( u > (size-1) ) u = (size-1);
+
+ if ( v < 0 ) v = 0;
+ else if ( v > (size-1) ) v = (size-1);
+
+ int offset = (v * size + u) * 4;
+
+ unsigned char *pPix = pCalc->m_ppCubeFaces[iFace] + offset;
+ color[0] = pPix[0];
+ color[1] = pPix[1];
+ color[2] = pPix[2];
+ color[3] = pPix[3];
+}
+
+//-----------------------------------------------------------------------------
+// Computes the spheremap color at a particular (x,y) texcoord
+//-----------------------------------------------------------------------------
+static void CalcSphereColor( SphereCalc_t *pCalc, float x, float y )
+{
+ Vector normal;
+ float flRadiusSq = x*x + y*y;
+ if (flRadiusSq > pCalc->m_flRadiusSq)
+ {
+ // Force a glancing reflection
+ normal.Init( 0, 1, 0 );
+ }
+ else
+ {
+ // Compute the z distance based on x*x + y*y + z*z = r*r
+ float z = sqrt( pCalc->m_flRadiusSq - flRadiusSq );
+
+ // Here's the untransformed surface normal
+ normal.Init( x, y, z );
+ normal *= pCalc->m_flOORadius;
+ }
+
+ // Transform the normal based on the actual view direction
+ TransformNormal( pCalc, normal );
+
+ // Compute the reflection vector (full spheremap solution)
+ // R = 2 * (N dot L)N - L
+ Vector vecReflect;
+ float nDotL = DotProduct( normal, pCalc->m_vecLookDir );
+ VectorMA( pCalc->m_vecLookDir, -2.0f * nDotL, normal, vecReflect );
+ vecReflect *= -1.0f;
+
+ int iFace = CalcFaceIndex( vecReflect );
+ CalcColor( pCalc, iFace, vecReflect, pCalc->m_pColor );
+}
+
+//-----------------------------------------------------------------------------
+// Computes the spheremap color at a particular (x,y) texcoord
+//-----------------------------------------------------------------------------
+static void CalcHemisphereColor( SphereCalc_t *pCalc, float x, float y )
+{
+ Vector normal;
+ float flRadiusSq = x*x + y*y;
+ if (flRadiusSq > pCalc->m_flRadiusSq)
+ {
+ normal.Init( x, y, 0.0f );
+ VectorNormalize( normal );
+ normal *= pCalc->m_flRadiusSq;
+ flRadiusSq = pCalc->m_flRadiusSq;
+ }
+
+ // Compute the z distance based on x*x + y*y + z*z = r*r
+ float z = sqrt( pCalc->m_flRadiusSq - flRadiusSq );
+
+ // Here's the untransformed surface normal
+ normal.Init( x, y, z );
+ normal *= pCalc->m_flOORadius;
+
+ // Transform the normal based on the actual view direction
+ TransformNormal( pCalc, normal );
+
+// printf( "x: %f y: %f normal: %f %f %f\n", x, y, normal.x, normal.y, normal.z );
+
+ /*
+ // Compute the reflection vector (full spheremap solution)
+ // R = 2 * (N dot L)N - L
+ Vector vecReflect;
+ float nDotL = DotProduct( normal, pCalc->m_vecLookDir );
+ VectorMA( pCalc->m_vecLookDir, -2.0f * nDotL, normal, vecReflect );
+ vecReflect *= -1.0f;
+*/
+
+ int iFace = CalcFaceIndex( normal );
+ CalcColor( pCalc, iFace, normal, pCalc->m_pColor );
+#if 0
+ pCalc->m_pColor[0] = normal[0] * 127 + 127;
+ pCalc->m_pColor[1] = normal[1] * 127 + 127;
+ pCalc->m_pColor[2] = normal[2] * 127 + 127;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Makes a single frame of spheremap
+//-----------------------------------------------------------------------------
+void CVTFTexture::ComputeSpheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir )
+{
+ SphereCalc_t sphere;
+ CalcInit( &sphere, m_nWidth, ppCubeFaces, lookDir );
+ int offset = 0;
+ for ( int y = 0; y < m_nHeight; y++ )
+ {
+ for ( int x = 0; x < m_nWidth; x++ )
+ {
+ int r = 0, g = 0, b = 0, a = 0;
+ float u = (float)x - m_nWidth * 0.5f;
+ float v = m_nHeight * 0.5f - (float)y;
+
+ CalcSphereColor( &sphere, u, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ CalcSphereColor( &sphere, u + 0.25, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ v += 0.25;
+ CalcSphereColor( &sphere, u + 0.25, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ CalcSphereColor( &sphere, u, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ pSpheremap[ offset + 0 ] = r >> 2;
+ pSpheremap[ offset + 1 ] = g >> 2;
+ pSpheremap[ offset + 2 ] = b >> 2;
+ pSpheremap[ offset + 3 ] = a >> 2;
+ offset += 4;
+ }
+ }
+}
+
+void CVTFTexture::ComputeHemispheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir )
+{
+ SphereCalc_t sphere;
+ CalcInit( &sphere, m_nWidth, ppCubeFaces, lookDir );
+ int offset = 0;
+ for ( int y = 0; y < m_nHeight; y++ )
+ {
+ for ( int x = 0; x < m_nWidth; x++ )
+ {
+ int r = 0, g = 0, b = 0, a = 0;
+ float u = (float)x - m_nWidth * 0.5f;
+ float v = m_nHeight * 0.5f - (float)y;
+
+ CalcHemisphereColor( &sphere, u, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ CalcHemisphereColor( &sphere, u + 0.25, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ v += 0.25;
+ CalcHemisphereColor( &sphere, u + 0.25, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ CalcHemisphereColor( &sphere, u, v );
+ r += sphere.m_pColor[0];
+ g += sphere.m_pColor[1];
+ b += sphere.m_pColor[2];
+ a += sphere.m_pColor[3];
+
+ pSpheremap[ offset + 0 ] = r >> 2;
+ pSpheremap[ offset + 1 ] = g >> 2;
+ pSpheremap[ offset + 2 ] = b >> 2;
+ pSpheremap[ offset + 3 ] = a >> 2;
+ offset += 4;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Generate spheremap based on the current images (only works for cubemaps)
+// The look dir indicates the direction of the center of the sphere
+//-----------------------------------------------------------------------------
+void CVTFTexture::GenerateSpheremap( LookDir_t lookDir )
+{
+ if (!IsCubeMap())
+ return;
+
+ // HDRFIXME: Need to re-enable this.
+// Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ // We'll be doing our work in IMAGE_FORMAT_RGBA8888 mode 'cause it's easier
+ unsigned char *pCubeMaps[6];
+
+ // Allocate the bits for the spheremap
+ Assert( m_nDepth == 1 );
+ int iMemRequired = ComputeFaceSize( 0, IMAGE_FORMAT_RGBA8888 );
+ unsigned char *pSphereMapBits = new unsigned char [ iMemRequired ];
+
+ // Generate a spheremap for each frame of the cubemap
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ // Point to our own textures (highest mip level)
+ for (int iFace = 0; iFace < 6; ++iFace)
+ {
+ pCubeMaps[iFace] = ImageData( iFrame, iFace, 0 );
+ }
+
+ // Compute the spheremap of the top LOD
+ // HDRFIXME: Make this work?
+ if( m_Format == IMAGE_FORMAT_RGBA8888 )
+ {
+ ComputeSpheremapFrame( pCubeMaps, pSphereMapBits, lookDir );
+ }
+
+ // Compute the mip levels of the spheremap, converting from RGBA8888 to our format
+ unsigned char *pFinalSphereMapBits = ImageData( iFrame, CUBEMAP_FACE_SPHEREMAP, 0 );
+ ImageLoader::GenerateMipmapLevels( pSphereMapBits, pFinalSphereMapBits,
+ m_nWidth, m_nHeight, m_nDepth, m_Format, 2.2, 2.2, m_nMipCount );
+ }
+
+ // Free memory
+ delete [] pSphereMapBits;
+}
+
+void CVTFTexture::GenerateHemisphereMap( unsigned char *pSphereMapBitsRGBA, int targetWidth,
+ int targetHeight, LookDir_t lookDir, int iFrame )
+{
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ unsigned char *pCubeMaps[6];
+
+ // Point to our own textures (highest mip level)
+ for (int iFace = 0; iFace < 6; ++iFace)
+ {
+ pCubeMaps[iFace] = ImageData( iFrame, iFace, 0 );
+ }
+
+ // Compute the spheremap of the top LOD
+ ComputeHemispheremapFrame( pCubeMaps, pSphereMapBitsRGBA, lookDir );
+}
+
+//-----------------------------------------------------------------------------
+// Rotate the image depending on what iFace we've got...
+// We need to do this because we define the cube textures in a different
+// format from DX8.
+//-----------------------------------------------------------------------------
+static void FixCubeMapFacing( unsigned char* pImage, int cubeFaceID, int size, ImageFormat fmt )
+{
+ int retVal;
+ switch( cubeFaceID )
+ {
+ case CUBEMAP_FACE_RIGHT: // +x
+ retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt );
+ Assert( retVal );
+ retVal = ImageLoader::FlipImageVertically( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ break;
+
+ case CUBEMAP_FACE_LEFT: // -x
+ retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt );
+ Assert( retVal );
+ retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ break;
+
+ case CUBEMAP_FACE_BACK: // +y
+ retVal = ImageLoader::RotateImage180( pImage, pImage, size, fmt );
+ Assert( retVal );
+ retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ break;
+
+ case CUBEMAP_FACE_FRONT: // -y
+ retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ break;
+
+ case CUBEMAP_FACE_UP: // +z
+ retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt );
+ Assert( retVal );
+ retVal = ImageLoader::FlipImageVertically( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ break;
+
+ case CUBEMAP_FACE_DOWN: // -z
+ retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt );
+ Assert( retVal );
+ retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt );
+ Assert( retVal );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Fixes the cubemap faces orientation from our standard to what the material system needs
+//-----------------------------------------------------------------------------
+void CVTFTexture::FixCubemapFaceOrientation( )
+{
+ if (!IsCubeMap())
+ return;
+
+ Assert( !ImageLoader::IsCompressed( m_Format ) );
+ for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel)
+ {
+ int iMipSize, iTemp, nDepth;
+ ComputeMipLevelDimensions( iMipLevel, &iMipSize, &iTemp, &nDepth );
+ Assert( (iMipSize == iTemp) && (nDepth == 1) );
+
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < 6; ++iFace)
+ {
+ FixCubeMapFacing( ImageData( iFrame, iFace, iMipLevel ), iFace, iMipSize, m_Format );
+ }
+ }
+ }
+}
+
+void CVTFTexture::NormalizeTopMipLevel()
+{
+ if( !( m_nFlags & TEXTUREFLAGS_NORMAL ) )
+ return;
+
+ int nSrcWidth, nSrcHeight, nSrcDepth;
+ int srcMipLevel = 0;
+ ComputeMipLevelDimensions( srcMipLevel, &nSrcWidth, &nSrcHeight, &nSrcDepth );
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < m_nFaceCount; ++iFace)
+ {
+ unsigned char *pSrcLevel = ImageData( iFrame, iFace, srcMipLevel );
+ ImageLoader::NormalizeNormalMapRGBA8888( pSrcLevel, nSrcWidth * nSrcHeight * nSrcDepth );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Generates mipmaps from the base mip levels
+//-----------------------------------------------------------------------------
+void CVTFTexture::GenerateMipmaps()
+{
+ // Go ahead and generate mipmaps even if we don't want 'em in the vtf.
+ // if( ( Flags() & ( TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD ) ) == ( TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD ) )
+// {
+// return;
+// }
+
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 || m_Format == IMAGE_FORMAT_RGB323232F );
+
+ // FIXME: Should we be doing anything special for normalmaps other than a final normalization pass?
+ ImageLoader::ResampleInfo_t info;
+ info.m_nSrcWidth = m_nWidth;
+ info.m_nSrcHeight = m_nHeight;
+ info.m_nSrcDepth = m_nDepth;
+ info.m_flSrcGamma = 2.2f;
+ info.m_flDestGamma = 2.2f;
+ info.m_nFlags = 0;
+ bool bNormalMap = ( Flags() & TEXTUREFLAGS_NORMAL ) || ( m_Options.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV );
+ bool bAlphaTest = ( ( m_Options.flags0 & VtfProcessingOptions::OPT_MIP_ALPHATEST ) != 0 );
+
+ if ( bAlphaTest )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_ALPHATEST;
+ if ( m_flAlphaThreshhold >= 0 )
+ {
+ info.m_flAlphaThreshhold = m_flAlphaThreshhold;
+ }
+ if ( m_flAlphaHiFreqThreshhold >= 0 )
+ {
+ info.m_flAlphaHiFreqThreshhold = m_flAlphaHiFreqThreshhold;
+ }
+ }
+
+ if ( m_Options.flags0 & VtfProcessingOptions::OPT_FILTER_NICE )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER;
+ }
+
+ if ( Flags() & TEXTUREFLAGS_CLAMPS )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPS;
+ }
+
+ if ( Flags() & TEXTUREFLAGS_CLAMPT )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPT;
+ }
+
+ if ( Flags() & TEXTUREFLAGS_CLAMPU )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPU;
+ }
+
+ // Compute how many mips are above "visible mip0"
+ int numMipsClampedLod = 0;
+ if ( TextureLODControlSettings_t const *pLodSettings = ( TextureLODControlSettings_t const * ) GetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, NULL ) )
+ {
+ int iClampX = 1 << min( pLodSettings->m_ResolutionClampX, pLodSettings->m_ResolutionClampX_360 );
+ int iClampY = 1 << min( pLodSettings->m_ResolutionClampX, pLodSettings->m_ResolutionClampX_360 );
+
+ while ( iClampX < m_nWidth || iClampY < m_nHeight )
+ {
+ ++ numMipsClampedLod;
+ iClampX <<= 1;
+ iClampY <<= 1;
+ }
+ }
+
+ for ( int iMipLevel = 1; iMipLevel < m_nMipCount; ++iMipLevel )
+ {
+ ComputeMipLevelDimensions( iMipLevel, &info.m_nDestWidth, &info.m_nDestHeight, &info.m_nDestDepth );
+
+ if ( m_Options.flags0 & VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP )
+ {
+ for ( int ch = 0; ch < 3; ++ ch )
+ info.m_flColorScale[ch] = 1.0f / ( float )( 1 << iMipLevel );
+ }
+
+ // don't use the 0th mip level since NICE filtering blows up!
+ int nSrcMipLevel = iMipLevel - 4;
+ if ( nSrcMipLevel < 0 )
+ nSrcMipLevel = 0;
+
+ // Decay options
+ bool bMipBlendActive = false;
+ char chChannels[4] = { 'R', 'G', 'B', 'A' };
+ for ( int ch = 0; ch < 4; ++ ch )
+ {
+ int iLastNonDecayMip = numMipsClampedLod + int( m_Options.numNotDecayMips[ch] );
+ if ( iLastNonDecayMip > m_nMipCount )
+ iLastNonDecayMip = m_nMipCount - 1;
+ int numDecayMips = m_nMipCount - iLastNonDecayMip - 1;
+ if ( numDecayMips < 1 )
+ numDecayMips = 1;
+
+ // Decay is only active starting from numDecayMips
+ if ( !( ( ( iMipLevel == m_nMipCount - 1 ) || ( iMipLevel > iLastNonDecayMip ) ) && // last 1x1 mip or past clamped and skipped
+ ( m_Options.flags0 & ( VtfProcessingOptions::OPT_DECAY_R << ch ) ) ) ) // the channel has decay
+ continue;
+
+ // Color goal
+ info.m_flColorGoal[ch] = m_Options.clrDecayGoal[ch];
+
+ // Color scale
+ if ( iMipLevel == m_nMipCount - 1 )
+ {
+ info.m_flColorScale[ch] = 0.0f;
+ }
+ else if ( m_Options.flags0 & ( VtfProcessingOptions::OPT_DECAY_EXP_R << ch ) )
+ {
+ info.m_flColorScale[ch] = pow( m_Options.fDecayExponentBase[ch], iMipLevel - iLastNonDecayMip );
+ }
+ else
+ {
+ info.m_flColorScale[ch] = 1.0f - float( iMipLevel - iLastNonDecayMip ) / float( numDecayMips );
+ }
+
+ if ( !bMipBlendActive )
+ {
+ bMipBlendActive = true;
+ printf( "Blending mip%d %dx%d to", iMipLevel, info.m_nDestWidth, info.m_nDestHeight );
+ }
+
+ printf( " %c=%d ~%d%%", chChannels[ch], m_Options.clrDecayGoal[ch], int( (1.f - info.m_flColorScale[ch]) * 100.0f + 0.5f ) );
+ }
+ if ( bMipBlendActive )
+ printf( "\n" );
+
+ if ( bNormalMap )
+ {
+ info.m_nFlags |= ImageLoader::RESAMPLE_NORMALMAP;
+ // Normal maps xyz decays to 127.f
+ for ( int ch = 0; ch < 3; ++ ch )
+ info.m_flColorGoal[ch] = 127.0f;
+ }
+
+ for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame )
+ {
+ for ( int iFace = 0; iFace < m_nFaceCount; ++iFace )
+ {
+ unsigned char *pSrcLevel = ImageData( iFrame, iFace, nSrcMipLevel );
+ unsigned char *pDstLevel = ImageData( iFrame, iFace, iMipLevel );
+
+ info.m_pSrc = pSrcLevel;
+ info.m_pDest = pDstLevel;
+ ComputeMipLevelDimensions( nSrcMipLevel, &info.m_nSrcWidth, &info.m_nSrcHeight, &info.m_nSrcDepth );
+ if( m_Format == IMAGE_FORMAT_RGB323232F )
+ {
+ ImageLoader::ResampleRGB323232F( info );
+ }
+ else
+ {
+ ImageLoader::ResampleRGBA8888( info );
+ }
+ if ( Flags() & TEXTUREFLAGS_NORMAL )
+ {
+ ImageLoader::NormalizeNormalMapRGBA8888( pDstLevel, info.m_nDestWidth * info.m_nDestHeight * info.m_nDestDepth );
+ }
+ }
+ }
+ }
+}
+
+void CVTFTexture::PutOneOverMipLevelInAlpha()
+{
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel)
+ {
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ int size = nMipWidth * nMipHeight * nMipDepth;
+ unsigned char ooMipLevel = ( unsigned char )( 255.0f * ( 1.0f / ( float )( 1 << iMipLevel ) ) );
+
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for (int iFace = 0; iFace < m_nFaceCount; ++iFace)
+ {
+ unsigned char *pDstLevel = ImageData( iFrame, iFace, iMipLevel );
+ unsigned char *pDst;
+ for( pDst = pDstLevel; pDst < pDstLevel + size * 4; pDst += 4 )
+ {
+ pDst[3] = ooMipLevel;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Computes the reflectivity
+//-----------------------------------------------------------------------------
+void CVTFTexture::ComputeReflectivity( )
+{
+ // HDRFIXME: fix this when we ahve a new intermediate format
+ if( m_Format != IMAGE_FORMAT_RGBA8888 )
+ {
+ m_vecReflectivity.Init( 0.2f, 0.2f, 0.2f );
+ return;
+ }
+
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ int divisor = 0;
+ m_vecReflectivity.Init( 0.0f, 0.0f, 0.0f );
+ for( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame )
+ {
+ for( int iFace = 0; iFace < m_nFaceCount; ++iFace )
+ {
+ Vector vecFaceReflect;
+ unsigned char* pSrc = ImageData( iFrame, iFace, 0 );
+ int nNumPixels = m_nWidth * m_nHeight * m_nDepth;
+
+ VectorClear( vecFaceReflect );
+ for (int i = 0; i < nNumPixels; ++i, pSrc += 4 )
+ {
+ vecFaceReflect[0] += TextureToLinear( pSrc[0] );
+ vecFaceReflect[1] += TextureToLinear( pSrc[1] );
+ vecFaceReflect[2] += TextureToLinear( pSrc[2] );
+ }
+
+ vecFaceReflect /= nNumPixels;
+
+ m_vecReflectivity += vecFaceReflect;
+ ++divisor;
+ }
+ }
+ m_vecReflectivity /= divisor;
+}
+
+//-----------------------------------------------------------------------------
+// Computes the alpha flags
+//-----------------------------------------------------------------------------
+void CVTFTexture::ComputeAlphaFlags()
+{
+ // HDRFIXME: hack hack hack
+ if( m_Format != IMAGE_FORMAT_RGBA8888 )
+ {
+ m_nFlags &= ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA );
+ m_Options.flags0 &= ~( VtfProcessingOptions::OPT_MIP_ALPHATEST );
+ return;
+ }
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ m_nFlags &= ~(TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA);
+
+ if( m_Options.flags0 & VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP )
+ {
+ m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA;
+ return;
+ }
+
+ for( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame )
+ {
+ for( int iFace = 0; iFace < m_nFaceCount; ++iFace )
+ {
+ for( int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel )
+ {
+ // If we're all 0 or all 255, assume it's opaque
+ bool bHasZero = false;
+ bool bHas255 = false;
+
+ unsigned char* pSrcBits = ImageData( iFrame, iFace, iMipLevel );
+
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ int nNumPixels = nMipWidth * nMipHeight * nMipDepth;
+
+ while ( --nNumPixels >= 0 )
+ {
+ if ( pSrcBits[3] == 0 )
+ {
+ bHasZero = true;
+ }
+ else if ( pSrcBits[3] == 255 )
+ {
+ bHas255 = true;
+ }
+ else
+ {
+ // Have grey at all? 8 bit alpha baby
+ m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA;
+ m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA;
+ return;
+ }
+
+ pSrcBits += 4;
+ }
+
+ // If we have both 0 at 255, we're at least one-bit alpha
+ if ( bHasZero && bHas255 )
+ {
+ m_nFlags |= TEXTUREFLAGS_ONEBITALPHA;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Gets the texture all internally consistent assuming you've loaded
+// mip 0 of all faces of all frames
+//-----------------------------------------------------------------------------
+void CVTFTexture::PostProcess( bool bGenerateSpheremap, LookDir_t lookDir, bool bAllowFixCubemapOrientation )
+{
+ // HDRFIXME: Make sure that all of the below functions check for the proper formats if we get rid of this assert.
+// Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+
+ // Set up the cube map faces
+ if (IsCubeMap())
+ {
+ // Rotate the cubemaps so they're appropriate for the material system
+ if ( bAllowFixCubemapOrientation )
+ FixCubemapFaceOrientation();
+
+ // FIXME: We could theoretically not compute spheremap mip levels
+ // in generate spheremaps; should we? The trick is when external
+ // clients can be expected to call it
+
+ // Compute the spheremap fallback for cubemaps if we weren't able to load up one...
+ if (bGenerateSpheremap)
+ GenerateSpheremap(lookDir);
+ }
+
+ // Normalize the top mip level if necessary.
+ NormalizeTopMipLevel();
+
+ // Generate mipmap levels
+ GenerateMipmaps();
+
+ if( m_Options.flags0 & VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP )
+ {
+ PutOneOverMipLevelInAlpha();
+ }
+
+ // Compute reflectivity
+ ComputeReflectivity();
+
+ // Are we 8-bit or 1-bit alpha?
+ // NOTE: We have to do this *after* computing the spheremap fallback for
+ // cubemaps or it'll throw the flags off
+ ComputeAlphaFlags();
+}
+
+void CVTFTexture::SetPostProcessingSettings( VtfProcessingOptions const *pOptions )
+{
+ memset( &m_Options, 0, sizeof( m_Options ) );
+ memcpy( &m_Options, pOptions, min( (uint32)sizeof( m_Options ), pOptions->cbSize ) );
+ m_Options.cbSize = sizeof( m_Options );
+
+ // Optionally perform the fixups
+}
+
+//-----------------------------------------------------------------------------
+// Generate the low-res image bits
+//-----------------------------------------------------------------------------
+bool CVTFTexture::ConstructLowResImage()
+{
+ // HDRFIXME: hack hack hack
+ if( m_Format != IMAGE_FORMAT_RGBA8888 )
+ {
+ return true;
+ }
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+ Assert( m_pLowResImageData );
+
+ CUtlMemory<unsigned char> lowResSizeImage;
+ lowResSizeImage.EnsureCapacity( m_nLowResImageWidth * m_nLowResImageHeight * 4 );
+
+ ImageLoader::ResampleInfo_t info;
+ info.m_pSrc = ImageData(0, 0, 0);
+ info.m_pDest = lowResSizeImage.Base();
+ info.m_nSrcWidth = m_nWidth;
+ info.m_nSrcHeight = m_nHeight;
+ info.m_nDestWidth = m_nLowResImageWidth;
+ info.m_nDestHeight = m_nLowResImageHeight;
+ info.m_flSrcGamma = 2.2f;
+ info.m_flDestGamma = 2.2f;
+ info.m_nFlags = ImageLoader::RESAMPLE_NICE_FILTER;
+
+ if( !ImageLoader::ResampleRGBA8888( info ) )
+ return false;
+
+ // convert to the low-res size version with the correct image format
+ unsigned char *tmpImage = lowResSizeImage.Base();
+ return ImageLoader::ConvertImageFormat( tmpImage, IMAGE_FORMAT_RGBA8888,
+ m_pLowResImageData, m_LowResImageFormat, m_nLowResImageWidth, m_nLowResImageHeight );
+}
+
+// -----------------------------------------------------------------------------
+// Cubemap edge-filtering functions.
+// -----------------------------------------------------------------------------
+void CVTFTexture::SetupFaceVert( int iMipLevel, int iVert, CEdgePos &out )
+{
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+
+ out.x = out.y = 0;
+ if ( iVert == 0 || iVert == 3 )
+ {
+ out.y = nMipHeight - 1;
+ }
+
+ if ( iVert == 2 || iVert == 3 )
+ {
+ out.x = nMipWidth - 1;
+ }
+}
+
+void CVTFTexture::SetupEdgeIncrement( CEdgePos &start, CEdgePos &end, CEdgePos &inc )
+{
+ inc.x = inc.y = 0;
+ if ( start.x != end.x )
+ {
+ Assert( start.y == end.y );
+ inc.x = (start.x < end.x) ? 1 : -1;
+ }
+ else if ( start.y != end.y )
+ {
+ Assert( start.x == end.x );
+ inc.y = (start.y < end.y) ? 1 : -1;
+ }
+ else
+ {
+ Assert( false );
+ }
+}
+
+void CVTFTexture::SetupTextureEdgeIncrements(
+ int iMipLevel,
+ int iFace1Edge,
+ int iFace2Edge,
+ bool bFlipFace2Edge,
+ CEdgeIncrements *incs )
+{
+ // Figure out the coordinates of the verts we're blending.
+ SetupFaceVert( iMipLevel, iFace1Edge, incs->iFace1Start );
+ SetupFaceVert( iMipLevel, (iFace1Edge+1)%4, incs->iFace1End );
+
+ if ( bFlipFace2Edge )
+ {
+ SetupFaceVert( iMipLevel, (iFace2Edge+1)%4, incs->iFace2Start );
+ SetupFaceVert( iMipLevel, iFace2Edge, incs->iFace2End );
+ }
+ else
+ {
+ SetupFaceVert( iMipLevel, iFace2Edge, incs->iFace2Start );
+ SetupFaceVert( iMipLevel, (iFace2Edge+1)%4, incs->iFace2End );
+ }
+
+ // Figure out the increments from start to end.
+ SetupEdgeIncrement( incs->iFace1Start, incs->iFace1End, incs->iFace1Inc );
+ SetupEdgeIncrement( incs->iFace2Start, incs->iFace2End, incs->iFace2Inc );
+}
+
+void BlendTexels( unsigned char **texels, int nTexels )
+{
+ int sum[4] = { 0, 0, 0, 0 };
+ int i;
+ for ( i=0; i < nTexels; i++ )
+ {
+ sum[0] += texels[i][0];
+ sum[1] += texels[i][1];
+ sum[2] += texels[i][2];
+ sum[3] += texels[i][3];
+ }
+ for ( i=0; i < nTexels; i++ )
+ {
+ texels[i][0] = (unsigned char)( sum[0] / nTexels );
+ texels[i][1] = (unsigned char)( sum[1] / nTexels );
+ texels[i][2] = (unsigned char)( sum[2] / nTexels );
+ texels[i][3] = (unsigned char)( sum[3] / nTexels );
+ }
+}
+
+void CVTFTexture::BlendCubeMapFaceEdges(
+ int iFrame,
+ int iMipLevel,
+ const CEdgeMatch *pMatch )
+{
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ Assert( nMipDepth == 1 );
+ if ( nMipWidth <= 1 || nMipHeight <= 1 )
+ return;
+
+ unsigned char *pFace1Data = ImageData( iFrame, pMatch->m_iFaces[0], iMipLevel );
+ unsigned char *pFace2Data = ImageData( iFrame, pMatch->m_iFaces[1], iMipLevel );
+
+ CEdgeIncrements incs;
+ SetupTextureEdgeIncrements( iMipLevel, pMatch->m_iEdges[0], pMatch->m_iEdges[1], pMatch->m_bFlipFace2Edge, &incs );
+
+ // Do all pixels but the first and the last one (those will be handled when blending corners).
+ CEdgePos iFace1Cur = incs.iFace1Start + incs.iFace1Inc;
+ CEdgePos iFace2Cur = incs.iFace2Start + incs.iFace2Inc;
+
+ if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 )
+ {
+ if ( iFace1Cur != incs.iFace1End )
+ {
+ while ( iFace1Cur != incs.iFace1End )
+ {
+ // Copy the palette index from image 1 to image 2.
+ S3PaletteIndex paletteIndex = S3TC_GetPaletteIndex( pFace1Data, m_Format, nMipWidth, iFace1Cur.x, iFace1Cur.y );
+ S3TC_SetPaletteIndex( pFace2Data, m_Format, nMipWidth, iFace2Cur.x, iFace2Cur.y, paletteIndex );
+
+ iFace1Cur += incs.iFace1Inc;
+ iFace2Cur += incs.iFace2Inc;
+ }
+ }
+ }
+ else if ( m_Format == IMAGE_FORMAT_RGBA8888 )
+ {
+ if ( iFace1Cur != incs.iFace1End )
+ {
+ while ( iFace1Cur != incs.iFace1End )
+ {
+ // Now we know the 2 pixels. Average them and copy the averaged value to both pixels.
+ unsigned char *texels[2] =
+ {
+ pFace1Data + ((iFace1Cur.y * nMipWidth) + iFace1Cur.x) * 4,
+ pFace2Data + ((iFace2Cur.y * nMipWidth) + iFace2Cur.x) * 4
+ };
+
+ BlendTexels( texels, 2 );
+
+ iFace1Cur += incs.iFace1Inc;
+ iFace2Cur += incs.iFace2Inc;
+ }
+ }
+ }
+ else
+ {
+ Error( "BlendCubeMapFaceEdges: unsupported image format (%d)", (int)m_Format );
+ }
+}
+
+void CVTFTexture::BlendCubeMapFaceCorners(
+ int iFrame,
+ int iMipLevel,
+ const CCornerMatch *pMatch )
+{
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ Assert( nMipDepth == 1 );
+
+ // Setup the coordinates of each texel.
+ CEdgePos texelPos[3];
+ unsigned char *pImageData[3];
+ int iEdge;
+ for ( iEdge=0; iEdge < 3; iEdge++ )
+ {
+ SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], texelPos[iEdge] );
+ pImageData[iEdge] = ImageData( iFrame, pMatch->m_iFaces[iEdge], iMipLevel );
+ }
+
+ if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 )
+ {
+ if ( nMipWidth < 4 || nMipHeight < 4 )
+ return;
+
+ // Copy the first palette index to the other blocks.
+ S3PaletteIndex paletteIndex = S3TC_GetPaletteIndex( pImageData[0], m_Format, nMipWidth, texelPos[0].x, texelPos[0].y );
+ S3TC_SetPaletteIndex( pImageData[1], m_Format, nMipWidth, texelPos[1].x, texelPos[1].y, paletteIndex );
+ S3TC_SetPaletteIndex( pImageData[2], m_Format, nMipWidth, texelPos[2].x, texelPos[2].y, paletteIndex );
+ }
+ else if ( m_Format == IMAGE_FORMAT_RGBA8888 )
+ {
+ // Setup pointers to the 3 corner texels.
+ unsigned char *texels[3];
+ for ( iEdge=0; iEdge < 3; iEdge++ )
+ {
+ CEdgePos facePos;
+ SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], facePos );
+
+ texels[iEdge] = pImageData[iEdge];
+ texels[iEdge] += (facePos.y * nMipWidth + facePos.x) * 4;
+ }
+
+ // Now blend the texels.
+ BlendTexels( texels, 3 );
+ }
+ else
+ {
+ Assert( false );
+ }
+}
+
+void CVTFTexture::BuildCubeMapMatchLists(
+ CEdgeMatch edgeMatches[NUM_EDGE_MATCHES],
+ CCornerMatch cornerMatches[NUM_CORNER_MATCHES],
+ bool bSkybox )
+{
+
+ int **faceVertsList = bSkybox ? g_skybox_FaceVerts : g_FaceVerts;
+
+ // For each face, look for matching edges on other faces.
+ int nTotalEdgesMatched = 0;
+ for ( int iFace = 0; iFace < 6; iFace++ )
+ {
+ for ( int iEdge=0; iEdge < 4; iEdge++ )
+ {
+ int i1 = faceVertsList[iFace][iEdge];
+ int i2 = faceVertsList[iFace][(iEdge+1)%4];
+
+ // Only look for faces with indices < what we have so we don't do each edge twice.
+ for ( int iOtherFace=0; iOtherFace < iFace; iOtherFace++ )
+ {
+ for ( int iOtherEdge=0; iOtherEdge < 4; iOtherEdge++ )
+ {
+ int o1 = faceVertsList[iOtherFace][iOtherEdge];
+ int o2 = faceVertsList[iOtherFace][(iOtherEdge+1)%4];
+
+ if ( (i1 == o1 && i2 == o2) || (i2 == o1 && i1 == o2) )
+ {
+ CEdgeMatch *pMatch = &edgeMatches[nTotalEdgesMatched];
+
+ pMatch->m_iFaces[0] = iFace;
+ pMatch->m_iEdges[0] = iEdge;
+
+ pMatch->m_iFaces[1] = iOtherFace;
+ pMatch->m_iEdges[1] = iOtherEdge;
+
+ pMatch->m_iCubeVerts[0] = o1;
+ pMatch->m_iCubeVerts[1] = o2;
+
+ pMatch->m_bFlipFace2Edge = i1 != o1;
+
+ ++nTotalEdgesMatched;
+ }
+ }
+ }
+ }
+ }
+
+ Assert( nTotalEdgesMatched == 12 );
+
+ // For each corner vert, find the 3 edges touching it.
+ for ( int iVert=0; iVert < NUM_CORNER_MATCHES; iVert++ )
+ {
+ int iTouchingFace = 0;
+
+ for ( int iFace=0; iFace < 6; iFace++ )
+ {
+ for ( int iFaceVert=0; iFaceVert < 4; iFaceVert++ )
+ {
+ if ( faceVertsList[iFace][iFaceVert] == iVert )
+ {
+ cornerMatches[iVert].m_iFaces[iTouchingFace] = iFace;
+ cornerMatches[iVert].m_iFaceEdges[iTouchingFace] = iFaceVert;
+ ++iTouchingFace;
+ }
+ }
+ }
+ Assert( iTouchingFace == 3 );
+ }
+}
+
+void CVTFTexture::BlendCubeMapEdgePalettes(
+ int iFrame,
+ int iMipLevel,
+ const CEdgeMatch *pMatch )
+{
+ Assert( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 );
+
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ Assert( nMipDepth == 1 );
+ if ( nMipWidth <= 8 || nMipHeight <= 8 )
+ return;
+
+ unsigned char *pFace1Data = ImageData( iFrame, pMatch->m_iFaces[0], iMipLevel );
+ unsigned char *pFace2Data = ImageData( iFrame, pMatch->m_iFaces[1], iMipLevel );
+ S3RGBA *pFace1Original = &m_OriginalData[ GetImageOffset( iFrame, pMatch->m_iFaces[0], iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ];
+ S3RGBA *pFace2Original = &m_OriginalData[ GetImageOffset( iFrame, pMatch->m_iFaces[1], iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ];
+
+ CEdgeIncrements incs;
+ SetupTextureEdgeIncrements( iMipLevel, pMatch->m_iEdges[0], pMatch->m_iEdges[1], pMatch->m_bFlipFace2Edge, &incs );
+
+ // Divide the coordinates by 4 since we're dealing with S3 blocks here.
+ incs.iFace1Start /= 4; incs.iFace1End /= 4; incs.iFace2Start /= 4; incs.iFace2End /= 4;
+
+ // Now walk along the edges, blending the edge pixels.
+ CEdgePos iFace1Cur = incs.iFace1Start + incs.iFace1Inc;
+ CEdgePos iFace2Cur = incs.iFace2Start + incs.iFace2Inc;
+ while ( iFace1Cur != incs.iFace1End ) // We intentionally want to not process the last block here..
+ {
+ // Merge the palette of these two blocks.
+ char *blocks[2] =
+ {
+ S3TC_GetBlock( pFace1Data, m_Format, nMipWidth>>2, iFace1Cur.x, iFace1Cur.y ),
+ S3TC_GetBlock( pFace2Data, m_Format, nMipWidth>>2, iFace2Cur.x, iFace2Cur.y )
+ };
+
+ S3RGBA *originals[2] =
+ {
+ &pFace1Original[(iFace1Cur.y * 4 * nMipWidth) + iFace1Cur.x * 4],
+ &pFace2Original[(iFace2Cur.y * 4 * nMipWidth) + iFace2Cur.x * 4]
+ };
+
+ S3TC_MergeBlocks(
+ blocks,
+ originals,
+ 2,
+ nMipWidth*4,
+ m_Format );
+
+ iFace1Cur += incs.iFace1Inc;
+ iFace2Cur += incs.iFace2Inc;
+ }
+}
+
+void CVTFTexture::BlendCubeMapCornerPalettes(
+ int iFrame,
+ int iMipLevel,
+ const CCornerMatch *pMatch )
+{
+ int nMipWidth, nMipHeight, nMipDepth;
+ ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth );
+ Assert( nMipDepth == 1 );
+ if ( nMipWidth < 4 || nMipHeight < 4 )
+ return;
+
+ // Now setup an S3TC block pointer for each of the corner blocks on each face.
+ char *blocks[3];
+ S3RGBA *originals[3];
+
+ for ( int iEdge=0; iEdge < 3; iEdge++ )
+ {
+ CEdgePos facePos;
+ SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], facePos );
+ facePos /= 4; // To get the S3 block index.
+
+ int iFaceIndex = pMatch->m_iFaces[iEdge];
+ unsigned char *pFaceData = ImageData( iFrame, iFaceIndex, iMipLevel );
+ S3RGBA *pFaceOriginal = &m_OriginalData[ GetImageOffset( iFrame, iFaceIndex, iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ];
+
+ blocks[iEdge] = S3TC_GetBlock( pFaceData, m_Format, nMipWidth>>2, facePos.x, facePos.y );
+ originals[iEdge] = &pFaceOriginal[ (facePos.y * 4 * nMipWidth) + facePos.x * 4 ];
+ }
+
+ S3TC_MergeBlocks(
+ blocks,
+ originals,
+ 3,
+ nMipWidth*4,
+ m_Format );
+}
+
+void CVTFTexture::MatchCubeMapS3TCPalettes(
+ CEdgeMatch edgeMatches[NUM_EDGE_MATCHES],
+ CCornerMatch cornerMatches[NUM_CORNER_MATCHES]
+ )
+{
+ for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel)
+ {
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ // First, match all the edge palettes (this part skips the first and last 4 texels
+ // along the edge since those S3 blocks are handled in the corner case).
+ for ( int iEdgeMatch=0; iEdgeMatch < NUM_EDGE_MATCHES; iEdgeMatch++ )
+ {
+ BlendCubeMapEdgePalettes(
+ iFrame,
+ iMipLevel,
+ &edgeMatches[iEdgeMatch] );
+ }
+
+ for ( int iCornerMatch=0; iCornerMatch < NUM_CORNER_MATCHES; iCornerMatch++ )
+ {
+ BlendCubeMapCornerPalettes(
+ iFrame,
+ iMipLevel,
+ &cornerMatches[iCornerMatch] );
+ }
+ }
+ }
+}
+
+void CVTFTexture::MatchCubeMapBorders( int iStage, ImageFormat finalFormat, bool bSkybox )
+{
+ // HDRFIXME: hack hack hack
+ if( m_Format != IMAGE_FORMAT_RGBA8888 )
+ {
+ return;
+ }
+ if ( !IsCubeMap() )
+ return;
+
+ Assert( IsCubeMap() );
+ Assert( m_nFaceCount >= 6 );
+
+ if ( iStage == 1 )
+ {
+ // Stage 1 is while the image is still RGBA8888. If we're not going to S3 compress the image,
+ // then it is easiest to match the borders now.
+ Assert( m_Format == IMAGE_FORMAT_RGBA8888 );
+ if ( finalFormat == IMAGE_FORMAT_DXT1 || finalFormat == IMAGE_FORMAT_DXT5 )
+ {
+ // If we're going to S3 compress the image eventually, then store off the original version
+ // because we can use that while matching the S3 compressed edges (we have to do some tricky
+ // repalettizing).
+ int nTotalBytes = ComputeTotalSize();
+ m_OriginalData.SetSize( nTotalBytes / 4 );
+ memcpy( m_OriginalData.Base(), ImageData(), nTotalBytes );
+
+ // Swap R and B in these because IMAGE_FORMAT_RGBA8888 is swapped from the way S3RGBAs are.
+ for ( int i=0; i < nTotalBytes/4; i++ )
+ V_swap( m_OriginalData[i].r, m_OriginalData[i].b );
+
+ return;
+ }
+ else
+ {
+ // Drop down below and do the edge matching.
+ }
+ }
+ else
+ {
+ if ( finalFormat == IMAGE_FORMAT_DXT1 || finalFormat == IMAGE_FORMAT_DXT5 )
+ {
+ Assert( m_Format == finalFormat );
+ }
+ else
+ {
+ // If we're not winding up S3 compressed, then we already fixed the cubemap borders.
+ return;
+ }
+ }
+
+ // Figure out
+ CEdgeMatch edgeMatches[NUM_EDGE_MATCHES];
+ CCornerMatch cornerMatches[NUM_CORNER_MATCHES];
+
+ BuildCubeMapMatchLists( edgeMatches, cornerMatches, bSkybox );
+
+ // If we're S3 compressed, then during the first pass, we need to match the palettes of all
+ // bordering S3 blocks.
+ if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 )
+ {
+ MatchCubeMapS3TCPalettes( edgeMatches, cornerMatches );
+ }
+
+ for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel)
+ {
+ for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame)
+ {
+ for ( int iEdgeMatch=0; iEdgeMatch < NUM_EDGE_MATCHES; iEdgeMatch++ )
+ {
+ BlendCubeMapFaceEdges(
+ iFrame,
+ iMipLevel,
+ &edgeMatches[iEdgeMatch] );
+ }
+
+ for ( int iCornerMatch=0; iCornerMatch < NUM_CORNER_MATCHES; iCornerMatch++ )
+ {
+ BlendCubeMapFaceCorners(
+ iFrame,
+ iMipLevel,
+ &cornerMatches[iCornerMatch] );
+ }
+ }
+ }
+}
+
+
+/*
+
+Test code used to draw the cubemap into a scratchpad file. Useful for debugging, or at least
+it was once.
+
+ IScratchPad3D *pPad = ScratchPad3D_Create();
+
+ int nMipWidth, nMipHeight;
+ ComputeMipLevelDimensions( 0, &nMipWidth, &nMipHeight );
+
+ CUtlVector<unsigned char> data;
+ data.SetSize( nMipWidth*nMipHeight );
+
+ float cubeSize = 200;
+ Vector vertPositions[8] =
+ {
+ Vector( 0, cubeSize, 0 ),
+ Vector( 0, cubeSize, cubeSize ),
+ Vector( cubeSize, 0, 0 ),
+ Vector( cubeSize, 0, cubeSize ),
+
+ Vector( 0, 0, 0 ),
+ Vector( 0, 0, cubeSize ),
+ Vector( cubeSize, cubeSize, 0 ),
+ Vector( cubeSize, cubeSize, cubeSize )
+ };
+ char *faceNames[6] = { "right","left","back","front","up","down" };
+
+ for ( int iVert=0; iVert < 8; iVert++ )
+ {
+ char str[512];
+ Q_snprintf( str, sizeof( str ), "%d", iVert );
+ CTextParams params;
+ params.m_flLetterWidth = 20;
+ params.m_vPos = vertPositions[iVert];
+ pPad->DrawText( str, params );
+ }
+
+ for ( int iFace=0; iFace < 6; iFace++ )
+ {
+ unsigned char *pFace1Data = ImageData( 0, iFace, 0 );
+ for ( int y=0; y < nMipHeight; y++ )
+ {
+ for( int x=0; x < nMipWidth; x++ )
+ {
+ S3PaletteIndex index = S3TC_GetPaletteIndex(
+ pFace1Data,
+ m_Format,
+ nMipWidth,
+ x, y );
+
+ const char *pBlock = S3TC_GetBlock( pFace1Data, m_Format, nMipWidth/4, x/4, y/4 );
+ unsigned char a0 = pBlock[0];
+ unsigned char a1 = pBlock[1];
+
+ if ( index.m_AlphaIndex == 0 )
+ {
+ data[y*nMipWidth+x] = a0;
+ }
+ else if ( index.m_AlphaIndex == 1 )
+ {
+ data[y*nMipWidth+x] = a1;
+ }
+ else if ( a0 > a1 )
+ {
+ data[y*nMipWidth+x] = ((8-(int)index.m_AlphaIndex)*a0 + ((int)index.m_AlphaIndex-1)*a1) / 7;
+ }
+ else
+ {
+ if ( index.m_AlphaIndex == 6 )
+ data[y*nMipWidth+x] = 0;
+ else if ( index.m_AlphaIndex == 7 )
+ data[y*nMipWidth+x] = 255;
+ else
+ data[y*nMipWidth+x] = ((6-(int)index.m_AlphaIndex)*a0 + ((int)index.m_AlphaIndex-1)*a1) / 5;
+ }
+ }
+ }
+
+ Vector vCorners[4];
+ for ( int iCorner=0; iCorner < 4; iCorner++ )
+ vCorners[iCorner] = vertPositions[g_FaceVerts[iFace][iCorner]];
+
+ pPad->DrawImageBW( data.Base(), nMipWidth, nMipHeight, nMipWidth, false, true, vCorners );
+
+ CTextParams params;
+ params.m_vPos = (vCorners[0] + vCorners[1] + vCorners[2] + vCorners[3]) / 4;
+ params.m_bCentered = true;
+ params.m_vColor.Init( 1, 0, 0 );
+ params.m_bTwoSided = true;
+ params.m_flLetterWidth = 10;
+
+ Vector vNormal = (vCorners[1] - vCorners[0]).Cross( vCorners[2] - vCorners[1] );
+ VectorNormalize( vNormal );
+ params.m_vPos += vNormal*5;
+ VectorAngles( vNormal, params.m_vAngles );
+
+ pPad->DrawText( faceNames[iFace], params );
+
+ pPad->Flush();
+ }
+*/
+
diff --git a/vtf/vtf.vpc b/vtf/vtf.vpc
new file mode 100644
index 0000000..a9ac82e
--- /dev/null
+++ b/vtf/vtf.vpc
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// VTF.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$macro SRCDIR ".."
+$include "$SRCDIR\vpc_scripts\source_lib_base.vpc"
+
+$Configuration
+{
+ $Compiler
+ {
+ $AdditionalIncludeDirectories "$BASE;$SRCDIR\dx9sdk\include" [$WINDOWS]
+ $PreprocessorDefinitions "$BASE;fopen=dont_use_fopen" [$WINDOWS]
+ }
+}
+
+$Project "vtf"
+{
+ $Folder "Source Files"
+ {
+ $File "convert_x360.cpp"
+ $File "s3tc_decode.cpp" [$WINDOWS]
+ $File "vtf.cpp"
+ $File "vtf_x360.cpp" [$X360]
+ }
+
+ $Folder "Public Header Files"
+ {
+ $File "s3tc_decode.h" [$WINDOWS]
+ $File "$SRCDIR\public\vtf\vtf.h"
+ }
+
+ $Folder "Header Files"
+ {
+ $File "cvtf.h"
+ }
+
+ $Folder "Link Libraries" [$X360]
+ {
+ $Lib bitmap
+ $Lib mathlib
+ $Lib tier2
+ }
+}
diff --git a/vtf/vtf_x360.cpp b/vtf/vtf_x360.cpp
new file mode 100644
index 0000000..d4a8e56
--- /dev/null
+++ b/vtf/vtf_x360.cpp
@@ -0,0 +1,347 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The 360 VTF file format I/O class to help simplify access to 360 VTF files.
+// 360 Formatted VTF's are stored ascending 1x1 up to NxN. Disk format and unserialized
+// formats are expected to be the same.
+//
+//=====================================================================================//
+
+#include "bitmap/imageformat.h"
+#include "cvtf.h"
+#include "utlbuffer.h"
+#include "tier0/dbg.h"
+#include "tier0/mem.h"
+#include "tier2/fileutils.h"
+#include "byteswap.h"
+#include "filesystem.h"
+#include "mathlib/mathlib.h"
+#include "tier1/lzmaDecoder.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// Callback for UpdateOrCreate utility function - swaps a vtf file.
+//-----------------------------------------------------------------------------
+static bool VTFCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData )
+{
+ // Generate the file
+ CUtlBuffer sourceBuf;
+ CUtlBuffer targetBuf;
+ bool bOk = g_pFullFileSystem->ReadFile( pSourceName, pPathID, sourceBuf );
+ if ( bOk )
+ {
+ bOk = ConvertVTFTo360Format( pSourceName, sourceBuf, targetBuf, NULL );
+ if ( bOk )
+ {
+ bOk = g_pFullFileSystem->WriteFile( pTargetName, pPathID, targetBuf );
+ }
+ }
+
+ if ( !bOk )
+ {
+ Warning( "Failed to create %s\n", pTargetName );
+ }
+ return bOk;
+}
+
+//-----------------------------------------------------------------------------
+// Calls utility function to create .360 version of a vtf file.
+//-----------------------------------------------------------------------------
+int CVTFTexture::UpdateOrCreate( const char *pFilename, const char *pPathID, bool bForce )
+{
+ return ::UpdateOrCreate( pFilename, NULL, 0, pPathID, VTFCreateCallback, bForce, NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Determine size of file, possibly smaller if skipping top mip levels.
+//-----------------------------------------------------------------------------
+int CVTFTexture::FileSize( bool bPreloadOnly, int nMipSkipCount ) const
+{
+ if ( bPreloadOnly )
+ {
+ // caller wants size of preload
+ return m_iPreloadDataSize;
+ }
+
+ const ResourceEntryInfo *pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+ if ( !pEntryInfo )
+ {
+ // has to exist
+ Assert( 0 );
+ return 0;
+ }
+ int iImageDataOffset = pEntryInfo->resData;
+
+ if ( m_iCompressedSize )
+ {
+ // file is compressed, mip skipping is non-applicable at this stage
+ return iImageDataOffset + m_iCompressedSize;
+ }
+
+ // caller gets file size, possibly truncated due to mip skipping
+ int nFaceSize = ComputeFaceSize( nMipSkipCount );
+ return iImageDataOffset + m_nFrameCount * m_nFaceCount * nFaceSize;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization of image data from buffer
+//-----------------------------------------------------------------------------
+bool CVTFTexture::LoadImageData( CUtlBuffer &buf, bool bBufferIsVolatile, int nMipSkipCount )
+{
+ ResourceEntryInfo *pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+ if ( !pEntryInfo )
+ {
+ // has to exist
+ Assert( 0 );
+ return false;
+ }
+ int iImageDataOffset = pEntryInfo->resData;
+
+ // Fix up the mip count + size based on how many mip levels we skip...
+ if ( nMipSkipCount > 0 )
+ {
+ if ( nMipSkipCount >= m_nMipCount )
+ {
+ nMipSkipCount = 0;
+ }
+ ComputeMipLevelDimensions( nMipSkipCount, &m_nWidth, &m_nHeight, &m_nDepth );
+ m_nMipCount -= nMipSkipCount;
+ m_nMipSkipCount += nMipSkipCount;
+ }
+
+ int iImageSize = ComputeFaceSize();
+ iImageSize = m_nFrameCount * m_nFaceCount * iImageSize;
+
+ // seek to start of image data
+ // The mip levels are stored on disk ascending from smallest (1x1) to largest (NxN) to allow for picmip truncated reads
+ buf.SeekGet( CUtlBuffer::SEEK_HEAD, iImageDataOffset );
+
+ CLZMA lzma;
+ if ( m_iCompressedSize )
+ {
+ unsigned char *pCompressedData = (unsigned char *)buf.PeekGet();
+ if ( !lzma.IsCompressed( pCompressedData ) )
+ {
+ // huh? header says it was compressed
+ Assert( 0 );
+ return false;
+ }
+
+ // have to decode entire image
+ unsigned int originalSize = lzma.GetActualSize( pCompressedData );
+ AllocateImageData( originalSize );
+ unsigned int outputLength = lzma.Uncompress( pCompressedData, m_pImageData );
+ return ( outputLength == originalSize );
+ }
+
+ bool bOK;
+ if ( bBufferIsVolatile )
+ {
+ AllocateImageData( iImageSize );
+ buf.Get( m_pImageData, iImageSize );
+ bOK = buf.IsValid();
+ }
+ else
+ {
+ // safe to alias
+ m_pImageData = (unsigned char *)buf.PeekGet( iImageSize, 0 );
+ bOK = ( m_pImageData != NULL );
+ }
+
+ return bOK;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization
+//-----------------------------------------------------------------------------
+bool CVTFTexture::ReadHeader( CUtlBuffer &buf, VTFFileHeaderX360_t &header )
+{
+ memset( &header, 0, sizeof( VTFFileHeaderX360_t ) );
+ buf.GetObjects( &header );
+ if ( !buf.IsValid() )
+ {
+ Warning( "*** Error getting header from a X360 VTF file.\n" );
+ return false;
+ }
+
+ // Validity check
+ if ( Q_strncmp( header.fileTypeString, "VTFX", 4 ) )
+ {
+ Warning( "*** Tried to load a PC VTF file as a X360 VTF file!\n" );
+ return false;
+ }
+
+ if ( header.version[0] != VTF_X360_MAJOR_VERSION && header.version[1] != VTF_X360_MINOR_VERSION )
+ {
+ Warning( "*** Encountered X360 VTF file with an invalid version!\n" );
+ return false;
+ }
+
+ if ( ( header.flags & TEXTUREFLAGS_ENVMAP ) && ( header.width != header.height ) )
+ {
+ Warning( "*** Encountered X360 VTF non-square cubemap!\n" );
+ return false;
+ }
+
+ if ( ( header.flags & TEXTUREFLAGS_ENVMAP ) && ( header.depth != 1 ) )
+ {
+ Warning( "*** Encountered X360 VTF volume texture cubemap!\n" );
+ return false;
+ }
+
+ if ( header.width <= 0 || header.height <= 0 || header.depth <= 0 )
+ {
+ Warning( "*** Encountered X360 VTF invalid texture size!\n" );
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Unserialization. Can optionally alias image components to a non-volatile buffer,
+// which prevents unecessary copies. Disk format and memory format of the image
+// components are explicitly the same.
+//-----------------------------------------------------------------------------
+bool CVTFTexture::UnserializeFromBuffer( CUtlBuffer &buf, bool bBufferIsVolatile, bool bHeaderOnly, bool bPreloadOnly, int nMipSkipCount )
+{
+ VTFFileHeaderX360_t header;
+ ResourceEntryInfo *pEntryInfo;
+
+ if ( !ReadHeader( buf, header ) )
+ {
+ return false;
+ }
+
+ // must first release any prior owned memory or reset aliases, otherwise corruption if types intermingled
+ ReleaseImageMemory();
+ ReleaseResources();
+
+ m_nVersion[0] = header.version[0];
+ m_nVersion[1] = header.version[1];
+
+ m_nWidth = header.width;
+ m_nHeight = header.height;
+ m_nDepth = header.depth;
+ m_Format = header.imageFormat;
+ m_nFlags = header.flags;
+ m_nFrameCount = header.numFrames;
+ m_nFaceCount = ( m_nFlags & TEXTUREFLAGS_ENVMAP ) ? CUBEMAP_FACE_COUNT-1 : 1;
+ m_nMipCount = ComputeMipCount();
+ m_nMipSkipCount = header.mipSkipCount;
+ m_vecReflectivity = header.reflectivity;
+ m_flBumpScale = header.bumpScale;
+ m_iPreloadDataSize = header.preloadDataSize;
+ m_iCompressedSize = header.compressedSize;
+
+ m_LowResImageFormat = IMAGE_FORMAT_RGB888;
+ if ( header.lowResImageSample[3] )
+ {
+ // nonzero denotes validity of color value
+ m_nLowResImageWidth = 1;
+ m_nLowResImageHeight = 1;
+ *(unsigned int *)m_LowResImageSample = *(unsigned int *)header.lowResImageSample;
+ }
+ else
+ {
+ m_nLowResImageWidth = 0;
+ m_nLowResImageHeight = 0;
+ *(unsigned int *)m_LowResImageSample = 0;
+ }
+
+ // 360 always has the image resource
+ Assert( header.numResources >= 1 );
+ m_arrResourcesInfo.SetCount( header.numResources );
+ m_arrResourcesData.SetCount( header.numResources );
+
+ // Read the dictionary of resources info
+ buf.Get( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) );
+ if ( !buf.IsValid() )
+ {
+ return false;
+ }
+
+ pEntryInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE );
+ if ( !pEntryInfo )
+ {
+ // not optional, has to be present
+ Assert( 0 );
+ return false;
+ }
+
+ if ( bHeaderOnly )
+ {
+ // caller wants header components only
+ // resource data chunks are NOT unserialized!
+ return true;
+ }
+
+ if ( !LoadNewResources( buf ) )
+ {
+ return false;
+ }
+
+ if ( bPreloadOnly )
+ {
+ // caller wants preload portion only, everything up to the image
+ return true;
+ }
+
+ if ( !LoadImageData( buf, bBufferIsVolatile, nMipSkipCount ) )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Discard image data to free up memory.
+//-----------------------------------------------------------------------------
+void CVTFTexture::ReleaseImageMemory()
+{
+ // valid sizes identify locally owned memory
+ if ( m_nImageAllocSize )
+ {
+ delete [] m_pImageData;
+ m_nImageAllocSize = 0;
+ }
+
+ // block pointers could be owned or aliased, always clear
+ // ensures other caller's don't free an aliased pointer
+ m_pImageData = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Attributes...
+//-----------------------------------------------------------------------------
+bool CVTFTexture::IsPreTiled() const
+{
+ return false;
+}
+
+int CVTFTexture::MappingWidth() const
+{
+ return m_nWidth << m_nMipSkipCount;
+}
+
+int CVTFTexture::MappingHeight() const
+{
+ return m_nHeight << m_nMipSkipCount;
+}
+
+int CVTFTexture::MappingDepth() const
+{
+ return m_nDepth << m_nMipSkipCount;
+}
+
+int CVTFTexture::MipSkipCount() const
+{
+ return m_nMipSkipCount;
+}
+
+unsigned char *CVTFTexture::LowResImageSample()
+{
+ return &m_LowResImageSample[0];
+}