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