Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

891 lines
28 KiB

//========= 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;
}