source-engine/vtf/vtf_x360.cpp

348 lines
9.7 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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];
}