diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vtf/convert_x360.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'vtf/convert_x360.cpp')
| -rw-r--r-- | vtf/convert_x360.cpp | 891 |
1 files changed, 891 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; +} |