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.
347 lines
9.7 KiB
347 lines
9.7 KiB
//========= 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]; |
|
}
|
|
|