diff options
Diffstat (limited to 'vtf')
| -rw-r--r-- | vtf/convert_x360.cpp | 891 | ||||
| -rw-r--r-- | vtf/cvtf.h | 441 | ||||
| -rw-r--r-- | vtf/s3tc_decode.cpp | 395 | ||||
| -rw-r--r-- | vtf/s3tc_decode.h | 109 | ||||
| -rw-r--r-- | vtf/vtf.cpp | 3493 | ||||
| -rw-r--r-- | vtf/vtf.vpc | 46 | ||||
| -rw-r--r-- | vtf/vtf_x360.cpp | 347 |
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]; +} |