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.
3478 lines
95 KiB
3478 lines
95 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: The VTF file format I/O class to help simplify access to VTF files |
|
// |
|
//=====================================================================================// |
|
|
|
#undef fopen |
|
#include "bitmap/imageformat.h" |
|
#include "cvtf.h" |
|
#include "utlbuffer.h" |
|
#include "tier0/dbg.h" |
|
#include "mathlib/vector.h" |
|
#include "mathlib/mathlib.h" |
|
#include "tier1/strtools.h" |
|
#include "tier0/mem.h" |
|
#include "s3tc_decode.h" |
|
#include "utlvector.h" |
|
#include "vprof_telemetry.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// byteswap data descriptions |
|
BEGIN_BYTESWAP_DATADESC( VTFFileBaseHeader_t ) |
|
DEFINE_ARRAY( fileTypeString, FIELD_CHARACTER, 4 ), |
|
DEFINE_ARRAY( version, FIELD_INTEGER, 2 ), |
|
DEFINE_FIELD( headerSize, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_1_t, VTFFileBaseHeader_t ) |
|
DEFINE_FIELD( width, FIELD_SHORT ), |
|
DEFINE_FIELD( height, FIELD_SHORT ), |
|
DEFINE_FIELD( flags, FIELD_INTEGER ), |
|
DEFINE_FIELD( numFrames, FIELD_SHORT ), |
|
DEFINE_FIELD( startFrame, FIELD_SHORT ), |
|
DEFINE_FIELD( reflectivity, FIELD_VECTOR ), |
|
DEFINE_FIELD( bumpScale, FIELD_FLOAT ), |
|
DEFINE_FIELD( imageFormat, FIELD_INTEGER ), |
|
DEFINE_FIELD( numMipLevels, FIELD_CHARACTER ), |
|
DEFINE_FIELD( lowResImageFormat, FIELD_INTEGER ), |
|
DEFINE_FIELD( lowResImageWidth, FIELD_CHARACTER ), |
|
DEFINE_FIELD( lowResImageHeight, FIELD_CHARACTER ), |
|
END_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_2_t, VTFFileHeaderV7_1_t ) |
|
DEFINE_FIELD( depth, FIELD_SHORT ), |
|
END_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderV7_3_t, VTFFileHeaderV7_2_t ) |
|
DEFINE_FIELD( numResources, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC_( VTFFileHeader_t, VTFFileHeaderV7_2_t ) |
|
END_DATADESC() |
|
|
|
BEGIN_BYTESWAP_DATADESC_( VTFFileHeaderX360_t, VTFFileBaseHeader_t ) |
|
DEFINE_FIELD( flags, FIELD_INTEGER ), |
|
DEFINE_FIELD( width, FIELD_SHORT ), |
|
DEFINE_FIELD( height, FIELD_SHORT ), |
|
DEFINE_FIELD( depth, FIELD_SHORT ), |
|
DEFINE_FIELD( numFrames, FIELD_SHORT ), |
|
DEFINE_FIELD( preloadDataSize, FIELD_SHORT ), |
|
DEFINE_FIELD( mipSkipCount, FIELD_CHARACTER ), |
|
DEFINE_FIELD( numResources, FIELD_CHARACTER ), |
|
DEFINE_FIELD( reflectivity, FIELD_VECTOR ), |
|
DEFINE_FIELD( bumpScale, FIELD_FLOAT ), |
|
DEFINE_FIELD( imageFormat, FIELD_INTEGER ), |
|
DEFINE_ARRAY( lowResImageSample, FIELD_CHARACTER, 4 ), |
|
DEFINE_FIELD( compressedSize, FIELD_INTEGER ), |
|
END_DATADESC() |
|
|
|
#if defined( POSIX ) || defined( _X360 ) |
|
// stub functions |
|
const char* S3TC_GetBlock( |
|
const void *pCompressed, |
|
ImageFormat format, |
|
int nBlocksWide, // How many blocks wide is the image (pixels wide / 4). |
|
int xBlock, |
|
int yBlock ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
char* S3TC_GetBlock( |
|
void *pCompressed, |
|
ImageFormat format, |
|
int nBlocksWide, // How many blocks wide is the image (pixels wide / 4). |
|
int xBlock, |
|
int yBlock ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
S3PaletteIndex S3TC_GetPaletteIndex( |
|
unsigned char *pFaceData, |
|
ImageFormat format, |
|
int imageWidth, |
|
int x, |
|
int y ) |
|
{ |
|
S3PaletteIndex nullPalette; |
|
memset(&nullPalette, 0x0, sizeof(nullPalette)); |
|
return nullPalette; |
|
} |
|
|
|
// Merge the two palettes and copy the colors |
|
void S3TC_MergeBlocks( |
|
char **blocks, |
|
S3RGBA **pOriginals, |
|
int nBlocks, |
|
int lPitch, // (in BYTES) |
|
ImageFormat format |
|
) |
|
{ |
|
} |
|
|
|
// Note: width, x, and y are in texels, not S3 blocks. |
|
void S3TC_SetPaletteIndex( |
|
unsigned char *pFaceData, |
|
ImageFormat format, |
|
int imageWidth, |
|
int x, |
|
int y, |
|
S3PaletteIndex paletteIndex ) |
|
{ |
|
} |
|
#endif |
|
|
|
// This gives a vertex number to each of the 4 verts on each face. |
|
// We use this to match the verts and determine which edges need to be blended together. |
|
// The vert ordering is lower-left, top-left, top-right, bottom-right. |
|
int g_leftFaceVerts[4] = { 2, 6, 7, 3 }; |
|
int g_frontFaceVerts[4] = { 2, 3, 5, 4 }; |
|
int g_downFaceVerts[4] = { 4, 0, 6, 2 }; |
|
int g_rightFaceVerts[4] = { 5, 1, 0, 4 }; |
|
int g_backFaceVerts[4] = { 7, 6, 0, 1 }; |
|
int g_upFaceVerts[4] = { 3, 7, 1, 5 }; |
|
|
|
int *g_FaceVerts[6] = |
|
{ |
|
g_rightFaceVerts, |
|
g_leftFaceVerts, |
|
g_backFaceVerts, |
|
g_frontFaceVerts, |
|
g_upFaceVerts, |
|
g_downFaceVerts |
|
}; |
|
|
|
// For skyboxes.. |
|
// These were constructed for the engine skybox, which looks like this |
|
// (assuming X goes forward, Y goes left, and Z goes up). |
|
// |
|
// 6 ------------- 5 |
|
// / / |
|
// / | / | |
|
// / | / | |
|
// 2 ------------- 1 | |
|
// | | |
|
// | | |
|
// | 7 ------|------ 4 |
|
// | / | / |
|
// | / | / |
|
// / / |
|
// 3 ------------- 0 |
|
// |
|
int g_skybox_rightFaceVerts[4] = { 7, 6, 5, 4 }; |
|
int g_skybox_leftFaceVerts[4] = { 0, 1, 2, 3 }; |
|
int g_skybox_backFaceVerts[4] = { 3, 2, 6, 7 }; |
|
int g_skybox_frontFaceVerts[4] = { 4, 5, 1, 0 }; |
|
int g_skybox_upFaceVerts[4] = { 6, 2, 1, 5 }; |
|
int g_skybox_downFaceVerts[4] = { 3, 7, 4, 0 }; |
|
|
|
int *g_skybox_FaceVerts[6] = |
|
{ |
|
g_skybox_rightFaceVerts, |
|
g_skybox_leftFaceVerts, |
|
g_skybox_backFaceVerts, |
|
g_skybox_frontFaceVerts, |
|
g_skybox_upFaceVerts, |
|
g_skybox_downFaceVerts |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Class factory |
|
//----------------------------------------------------------------------------- |
|
IVTFTexture *CreateVTFTexture() |
|
{ |
|
return new CVTFTexture; |
|
} |
|
|
|
void DestroyVTFTexture( IVTFTexture *pTexture ) |
|
{ |
|
CVTFTexture *pTex = static_cast<CVTFTexture*>(pTexture); |
|
if ( pTex ) |
|
{ |
|
delete pTex; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Allows us to only load in the first little bit of the VTF file to get info |
|
//----------------------------------------------------------------------------- |
|
int VTFFileHeaderSize( int nMajorVersion, int nMinorVersion ) |
|
{ |
|
if ( nMajorVersion == -1 ) |
|
{ |
|
nMajorVersion = VTF_MAJOR_VERSION; |
|
} |
|
|
|
if ( nMinorVersion == -1 ) |
|
{ |
|
nMinorVersion = VTF_MINOR_VERSION; |
|
} |
|
|
|
switch ( nMajorVersion ) |
|
{ |
|
case VTF_MAJOR_VERSION: |
|
switch ( nMinorVersion ) |
|
{ |
|
case 0: // fall through |
|
case 1: |
|
return sizeof( VTFFileHeaderV7_1_t ); |
|
case 2: |
|
return sizeof( VTFFileHeaderV7_2_t ); |
|
case 3: |
|
return sizeof( VTFFileHeaderV7_3_t ) + sizeof( ResourceEntryInfo ) * MAX_RSRC_DICTIONARY_ENTRIES; |
|
case 4: |
|
case VTF_MINOR_VERSION: |
|
int size1 = sizeof( VTFFileHeader_t ); |
|
int size2 = sizeof( ResourceEntryInfo ) * MAX_RSRC_DICTIONARY_ENTRIES; |
|
int result = size1 + size2; |
|
//printf("\n VTFFileHeaderSize (%i %i) is %i + %i -> %i",nMajorVersion,nMinorVersion, size1, size2, result ); |
|
return result; |
|
} |
|
break; |
|
|
|
case VTF_X360_MAJOR_VERSION: |
|
return sizeof( VTFFileHeaderX360_t ) + sizeof( ResourceEntryInfo ) * MAX_X360_RSRC_DICTIONARY_ENTRIES; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Constructor, destructor |
|
//----------------------------------------------------------------------------- |
|
CVTFTexture::CVTFTexture() |
|
{ |
|
m_nVersion[0] = 0; |
|
m_nVersion[1] = 0; |
|
|
|
m_nWidth = 0; |
|
m_nHeight = 0; |
|
m_nDepth = 1; |
|
m_Format = IMAGE_FORMAT_UNKNOWN; |
|
|
|
m_nMipCount = 0; |
|
m_nFaceCount = 0; |
|
m_nFrameCount = 0; |
|
|
|
// FIXME: Is the start frame needed? |
|
m_iStartFrame = 0; |
|
|
|
m_flAlphaThreshhold = -1.0f; |
|
m_flAlphaHiFreqThreshhold = -1.0f; |
|
|
|
m_flBumpScale = 1.0f; |
|
m_vecReflectivity.Init( 1.0, 1.0, 1.0f ); |
|
|
|
m_nFlags = 0; |
|
m_pImageData = NULL; |
|
m_nImageAllocSize = 0; |
|
|
|
// LowRes data |
|
m_LowResImageFormat = IMAGE_FORMAT_UNKNOWN; |
|
m_nLowResImageWidth = 0; |
|
m_nLowResImageHeight = 0; |
|
m_pLowResImageData = NULL; |
|
m_nLowResImageAllocSize = 0; |
|
|
|
#if defined( _X360 ) |
|
m_nMipSkipCount = 0; |
|
*(unsigned int *)m_LowResImageSample = 0; |
|
#endif |
|
|
|
Assert( m_arrResourcesInfo.Count() == 0 ); |
|
Assert( m_arrResourcesData.Count() == 0 ); |
|
Assert( m_arrResourcesData_ForReuse.Count() == 0 ); |
|
|
|
memset( &m_Options, 0, sizeof( m_Options ) ); |
|
m_Options.cbSize = sizeof( m_Options ); |
|
|
|
m_nFinestMipmapLevel = 0; |
|
m_nCoarsestMipmapLevel = 0; |
|
} |
|
|
|
CVTFTexture::~CVTFTexture() |
|
{ |
|
Shutdown(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Compute the mip count based on the size + flags |
|
//----------------------------------------------------------------------------- |
|
int CVTFTexture::ComputeMipCount() const |
|
{ |
|
if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) && ( m_nFlags & TEXTUREFLAGS_NOMIP ) ) |
|
{ |
|
// 360 vtf format culled unused mips at conversion time |
|
return 1; |
|
} |
|
|
|
// NOTE: No matter what, all mip levels should be created because |
|
// we have to worry about various fallbacks |
|
return ImageLoader::GetNumMipMapLevels( m_nWidth, m_nHeight, m_nDepth ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocate data blocks with an eye toward re-using memory |
|
//----------------------------------------------------------------------------- |
|
|
|
static bool GenericAllocateReusableData( unsigned char **ppData, int *pNumAllocated, int numRequested ) |
|
{ |
|
// If we're asking for memory and we have way more than we expect, free some. |
|
if ( *pNumAllocated < numRequested || ( numRequested > 0 && *pNumAllocated > 16 * numRequested ) ) |
|
{ |
|
delete [] *ppData; |
|
*ppData = new unsigned char[ numRequested ]; |
|
if ( *ppData ) |
|
{ |
|
*pNumAllocated = numRequested; |
|
return true; |
|
} |
|
|
|
*pNumAllocated = 0; |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CVTFTexture::AllocateImageData( int nMemorySize ) |
|
{ |
|
return GenericAllocateReusableData( &m_pImageData, &m_nImageAllocSize, nMemorySize ); |
|
} |
|
|
|
bool CVTFTexture::ResourceMemorySection::AllocateData( int nMemorySize ) |
|
{ |
|
if ( GenericAllocateReusableData( &m_pData, &m_nDataAllocSize, nMemorySize ) ) |
|
{ |
|
m_nDataLength = nMemorySize; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CVTFTexture::AllocateLowResImageData( int nMemorySize ) |
|
{ |
|
return GenericAllocateReusableData( &m_pLowResImageData, &m_nLowResImageAllocSize, nMemorySize ); |
|
} |
|
|
|
inline bool IsMultipleOf4( int value ) |
|
{ |
|
// NOTE: This catches powers of 2 less than 4 also |
|
return ( value <= 2 ) || ( (value & 0x3) == 0 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Initialization |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::Init( int nWidth, int nHeight, int nDepth, ImageFormat fmt, int iFlags, int iFrameCount, int nForceMipCount ) |
|
{ |
|
if ( nDepth == 0 ) |
|
{ |
|
nDepth = 1; |
|
} |
|
|
|
if (iFlags & TEXTUREFLAGS_ENVMAP) |
|
{ |
|
if (nWidth != nHeight) |
|
{ |
|
Warning( "Height and width must be equal for cubemaps!\n" ); |
|
return false; |
|
} |
|
if (nDepth != 1) |
|
{ |
|
Warning( "Depth must be 1 for cubemaps!\n" ); |
|
return false; |
|
} |
|
} |
|
|
|
if ( ( fmt == IMAGE_FORMAT_DXT1 ) || ( fmt == IMAGE_FORMAT_DXT3 ) || ( fmt == IMAGE_FORMAT_DXT5 ) || |
|
( fmt == IMAGE_FORMAT_DXT1_RUNTIME ) || ( fmt == IMAGE_FORMAT_DXT5_RUNTIME ) ) |
|
{ |
|
if ( !IsMultipleOf4( nWidth ) || !IsMultipleOf4( nHeight ) || !IsMultipleOf4( nDepth ) ) |
|
{ |
|
Warning( "Image dimensions must be multiple of 4!\n" ); |
|
return false; |
|
} |
|
} |
|
|
|
if ( fmt == IMAGE_FORMAT_DEFAULT ) |
|
{ |
|
fmt = IMAGE_FORMAT_RGBA8888; |
|
} |
|
|
|
m_nWidth = nWidth; |
|
m_nHeight = nHeight; |
|
m_nDepth = nDepth; |
|
m_Format = fmt; |
|
m_nFlags = iFlags; |
|
|
|
// THIS CAUSED A BUG!!! We want all of the mip levels in the vtf file even with nomip in case we have lod. |
|
// NOTE: But we don't want more than 1 mip level for procedural textures |
|
if ( (iFlags & (TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL)) == (TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_PROCEDURAL) ) |
|
{ |
|
nForceMipCount = 1; |
|
} |
|
|
|
if ( nForceMipCount == -1 ) |
|
{ |
|
m_nMipCount = ComputeMipCount(); |
|
} |
|
else |
|
{ |
|
m_nMipCount = nForceMipCount; |
|
} |
|
|
|
m_nFrameCount = iFrameCount; |
|
|
|
m_nFaceCount = (iFlags & TEXTUREFLAGS_ENVMAP) ? (CUBEMAP_FACE_COUNT-1) : 1; |
|
|
|
#if defined( _X360 ) |
|
m_nMipSkipCount = 0; |
|
#endif |
|
|
|
// Need to do this because Shutdown deallocates the low-res image |
|
m_nLowResImageWidth = m_nLowResImageHeight = 0; |
|
|
|
// Allocate me some bits! |
|
int iMemorySize = ComputeTotalSize(); |
|
if ( !AllocateImageData( iMemorySize ) ) |
|
return false; |
|
|
|
// As soon as we have image indicate so in the resources |
|
if ( iMemorySize ) |
|
FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ); |
|
else |
|
RemoveResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods to initialize the low-res image |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::InitLowResImage( int nWidth, int nHeight, ImageFormat fmt ) |
|
{ |
|
m_nLowResImageWidth = nWidth; |
|
m_nLowResImageHeight = nHeight; |
|
m_LowResImageFormat = fmt; |
|
|
|
// Allocate low-res bits |
|
int iLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth, |
|
m_nLowResImageHeight, 1, m_LowResImageFormat, false ); |
|
|
|
if ( !AllocateLowResImageData( iLowResImageSize ) ) |
|
return; |
|
|
|
// As soon as we have low-res image indicate so in the resources |
|
if ( iLowResImageSize ) |
|
FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ); |
|
else |
|
RemoveResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods to set other texture fields |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::SetBumpScale( float flScale ) |
|
{ |
|
m_flBumpScale = flScale; |
|
} |
|
|
|
void CVTFTexture::SetReflectivity( const Vector &vecReflectivity ) |
|
{ |
|
VectorCopy( vecReflectivity, m_vecReflectivity ); |
|
} |
|
|
|
// Sets threshhold values for alphatest mipmapping |
|
void CVTFTexture::SetAlphaTestThreshholds( float flBase, float flHighFreq ) |
|
{ |
|
m_flAlphaThreshhold = flBase; |
|
m_flAlphaHiFreqThreshhold = flHighFreq; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Release and reset the resources. |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ReleaseResources() |
|
{ |
|
m_arrResourcesInfo.RemoveAll(); |
|
|
|
for ( ResourceMemorySection *pRms = m_arrResourcesData.Base(), |
|
*pRmsEnd = pRms + m_arrResourcesData.Count(); pRms < pRmsEnd; ++pRms ) |
|
{ |
|
delete [] pRms->m_pData; |
|
} |
|
m_arrResourcesData.RemoveAll(); |
|
|
|
for ( ResourceMemorySection *pRms = m_arrResourcesData_ForReuse.Base(), |
|
*pRmsEnd = pRms + m_arrResourcesData_ForReuse.Count(); pRms < pRmsEnd; ++pRms ) |
|
{ |
|
delete [] pRms->m_pData; |
|
} |
|
m_arrResourcesData_ForReuse.RemoveAll(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Shutdown |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::Shutdown() |
|
{ |
|
#if defined( _X360 ) |
|
// must be first to ensure X360 aliased pointers are unhooked, otherwise memory corruption |
|
ReleaseImageMemory(); |
|
#endif |
|
|
|
delete[] m_pImageData; |
|
m_pImageData = NULL; |
|
m_nImageAllocSize = 0; |
|
|
|
delete[] m_pLowResImageData; |
|
m_pLowResImageData = NULL; |
|
m_nLowResImageAllocSize = 0; |
|
|
|
ReleaseResources(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// These are methods to help with optimization of file access |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::LowResFileInfo( int *pStartLocation, int *pSizeInBytes ) const |
|
{ |
|
// Once the header is read in, they indicate where to start reading |
|
// other data, and how many bytes to read.... |
|
|
|
if ( ResourceEntryInfo const *pLowResData = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) ) |
|
{ |
|
*pStartLocation = pLowResData->resData; |
|
*pSizeInBytes = ImageLoader::GetMemRequired( m_nLowResImageWidth, |
|
m_nLowResImageHeight, 1, m_LowResImageFormat, false ); |
|
} |
|
else |
|
{ |
|
*pStartLocation = 0; |
|
*pSizeInBytes = 0; |
|
} |
|
} |
|
|
|
void CVTFTexture::ImageFileInfo( int nFrame, int nFace, int nMipLevel, int *pStartLocation, int *pSizeInBytes) const |
|
{ |
|
int i; |
|
int iMipWidth; |
|
int iMipHeight; |
|
int iMipDepth; |
|
|
|
ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ); |
|
|
|
if ( pImageDataInfo == NULL ) |
|
{ |
|
// This should never happen for real, but can happen if someone intentionally fed us a bad VTF. |
|
Assert( pImageDataInfo ); |
|
( *pStartLocation ) = 0; |
|
( *pSizeInBytes ) = 0; |
|
return; |
|
} |
|
|
|
// The image data start offset |
|
int nOffset = pImageDataInfo->resData; |
|
|
|
// get to the right miplevel |
|
for( i = m_nMipCount - 1; i > nMipLevel; --i ) |
|
{ |
|
ComputeMipLevelDimensions( i, &iMipWidth, &iMipHeight, &iMipDepth ); |
|
int iMipLevelSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, m_Format, false ); |
|
nOffset += iMipLevelSize * m_nFrameCount * m_nFaceCount; |
|
} |
|
|
|
// get to the right frame |
|
ComputeMipLevelDimensions( nMipLevel, &iMipWidth, &iMipHeight, &iMipDepth ); |
|
int nFaceSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, m_Format, false ); |
|
|
|
// For backwards compatibility, we don't read in the spheremap fallback on |
|
// older format .VTF files... |
|
int nFacesToRead = m_nFaceCount; |
|
if ( IsCubeMap() ) |
|
{ |
|
if ((m_nVersion[0] == 7) && (m_nVersion[1] < 1)) |
|
{ |
|
nFacesToRead = 6; |
|
if (nFace == CUBEMAP_FACE_SPHEREMAP) |
|
{ |
|
--nFace; |
|
} |
|
} |
|
} |
|
|
|
int nFrameSize = nFacesToRead * nFaceSize; |
|
nOffset += nFrameSize * nFrame; |
|
|
|
// get to the right face |
|
nOffset += nFace * nFaceSize; |
|
|
|
*pStartLocation = nOffset; |
|
*pSizeInBytes = nFaceSize; |
|
} |
|
|
|
int CVTFTexture::FileSize( int nMipSkipCount ) const |
|
{ |
|
ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ); |
|
|
|
// Can be null when someone gives us an intentionally malformed VTF. |
|
if ( pImageDataInfo == NULL ) |
|
{ |
|
// Still do the assert so we can catch this in debug--we don't expect this for well formed files. |
|
Assert( pImageDataInfo != NULL ); |
|
return 0; |
|
} |
|
|
|
int nOffset = pImageDataInfo->resData; |
|
|
|
int nFaceSize = ComputeFaceSize( nMipSkipCount ); |
|
int nImageSize = nFaceSize * m_nFaceCount * m_nFrameCount; |
|
return nOffset + nImageSize; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization of low-res data |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::LoadLowResData( CUtlBuffer &buf ) |
|
{ |
|
// Allocate low-res bits |
|
InitLowResImage( m_nLowResImageWidth, m_nLowResImageHeight, m_LowResImageFormat ); |
|
int nLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth, |
|
m_nLowResImageHeight, 1, m_LowResImageFormat, false ); |
|
buf.Get( m_pLowResImageData, nLowResImageSize ); |
|
|
|
bool bValid = buf.IsValid(); |
|
|
|
return bValid; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization of image data |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::LoadImageData( CUtlBuffer &buf, const VTFFileHeader_t &header, int nSkipMipLevels ) |
|
{ |
|
// Fix up the mip count + size based on how many mip levels we skip... |
|
if (nSkipMipLevels > 0) |
|
{ |
|
Assert( m_nMipCount > nSkipMipLevels ); |
|
if (header.numMipLevels < nSkipMipLevels) |
|
{ |
|
// NOTE: This can only happen with older format .vtf files |
|
Warning("Warning! Encountered old format VTF file; please rebuild it!\n"); |
|
return false; |
|
} |
|
|
|
ComputeMipLevelDimensions( nSkipMipLevels, &m_nWidth, &m_nHeight, &m_nDepth ); |
|
m_nMipCount -= nSkipMipLevels; |
|
} |
|
|
|
// read the texture image (including mipmaps if they are there and needed.) |
|
int iImageSize = ComputeFaceSize(); |
|
iImageSize *= m_nFaceCount * m_nFrameCount; |
|
|
|
if ( !AllocateImageData( iImageSize ) ) |
|
return false; |
|
|
|
// NOTE: The mip levels are stored ascending from smallest (1x1) to largest (NxN) |
|
// in order to allow for truncated reads of the minimal required data |
|
|
|
// NOTE: I checked in a bad version 4 where it stripped out the spheremap. |
|
// To make it all work, need to check for that bad case. |
|
bool bNoSkip = false; |
|
if ( IsCubeMap() && ( header.version[0] == 7 ) && ( header.version[1] == 4 ) ) |
|
{ |
|
int nBytesRemaining = buf.TellMaxPut() - buf.TellGet(); |
|
int nFileSize = ComputeFaceSize( nSkipMipLevels ) * m_nFaceCount * m_nFrameCount; |
|
if ( nBytesRemaining == nFileSize ) |
|
{ |
|
bNoSkip = true; |
|
} |
|
} |
|
|
|
int nGet = buf.TellGet(); |
|
|
|
retryCubemapLoad: |
|
for (int iMip = m_nMipCount; --iMip >= 0; ) |
|
{ |
|
// NOTE: This is for older versions... |
|
if ( header.numMipLevels - nSkipMipLevels <= iMip ) |
|
continue; |
|
|
|
int iMipSize = ComputeMipSize( iMip ); |
|
|
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < m_nFaceCount; ++iFace) |
|
{ |
|
// printf("\n tex %p mip %i frame %i face %i size %i buf offset %i", this, iMip, iFrame, iFace, iMipSize, buf.TellGet() ); |
|
unsigned char *pMipBits = ImageData( iFrame, iFace, iMip ); |
|
buf.Get( pMipBits, iMipSize ); |
|
} |
|
|
|
// Strip out the spheremap in older versions |
|
if ( IsCubeMap() && !bNoSkip && ( header.version[0] == 7 ) && ( header.version[1] >= 1 ) && ( header.version[1] < 5 ) ) |
|
{ |
|
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, iMipSize ); |
|
} |
|
} |
|
} |
|
|
|
bool bOk = buf.IsValid(); |
|
if ( !bOk && IsCubeMap() && ( header.version[0] == 7 ) && ( header.version[1] <= 4 ) ) |
|
{ |
|
if ( !bNoSkip ) |
|
{ |
|
bNoSkip = true; |
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, nGet ); |
|
goto retryCubemapLoad; |
|
} |
|
Warning( "** Encountered stale cubemap! Please rebuild the following vtf:\n" ); |
|
} |
|
return bOk; |
|
} |
|
|
|
void *CVTFTexture::SetResourceData( uint32 eType, void const *pData, size_t nNumBytes ) |
|
{ |
|
Assert( ( eType & RSRCF_MASK ) == 0 ); |
|
eType &= ~RSRCF_MASK; |
|
|
|
// Very inefficient to set less than 4 bytes of data |
|
Assert( !nNumBytes || ( nNumBytes >= sizeof( uint32 ) ) ); |
|
|
|
if ( nNumBytes ) |
|
{ |
|
ResourceEntryInfo *pInfo = FindOrCreateResourceEntryInfo( eType ); |
|
int idx = pInfo - m_arrResourcesInfo.Base(); |
|
ResourceMemorySection &rms = m_arrResourcesData[ idx ]; |
|
|
|
if ( nNumBytes == sizeof( pInfo->resData ) ) |
|
{ |
|
// store 4 bytes directly |
|
pInfo->eType |= RSRCF_HAS_NO_DATA_CHUNK; |
|
if ( pData ) |
|
pInfo->resData = reinterpret_cast< const int * >( pData )[0]; |
|
return &pInfo->resData; |
|
} |
|
else |
|
{ |
|
if ( !rms.AllocateData( nNumBytes ) ) |
|
{ |
|
RemoveResourceEntryInfo( eType ); |
|
return NULL; |
|
} |
|
|
|
if ( pData ) |
|
memcpy( rms.m_pData, pData, nNumBytes ); |
|
return rms.m_pData; |
|
} |
|
} |
|
else |
|
{ |
|
RemoveResourceEntryInfo( eType ); |
|
return NULL; |
|
} |
|
} |
|
|
|
void *CVTFTexture::GetResourceData( uint32 eType, size_t *pDataSize ) const |
|
{ |
|
Assert( ( eType & RSRCF_MASK ) == 0 ); |
|
eType &= ~RSRCF_MASK; |
|
|
|
ResourceEntryInfo const *pInfo = FindResourceEntryInfo( eType ); |
|
if ( pInfo ) |
|
{ |
|
if ( ( pInfo->eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 ) |
|
{ |
|
int idx = pInfo - m_arrResourcesInfo.Base(); |
|
ResourceMemorySection const &rms = m_arrResourcesData[ idx ]; |
|
if ( pDataSize ) |
|
{ |
|
*pDataSize = rms.m_nDataLength; |
|
} |
|
return rms.m_pData; |
|
} |
|
else |
|
{ |
|
if ( pDataSize ) |
|
{ |
|
*pDataSize = sizeof( pInfo->resData ); |
|
} |
|
return (void *)&pInfo->resData; |
|
} |
|
} |
|
else |
|
{ |
|
if ( pDataSize ) |
|
*pDataSize = 0; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
bool CVTFTexture::HasResourceEntry( uint32 eType ) const |
|
{ |
|
return ( FindResourceEntryInfo( eType ) != NULL ); |
|
} |
|
|
|
unsigned int CVTFTexture::GetResourceTypes( unsigned int *arrTypesBuffer, int numTypesBufferElems ) const |
|
{ |
|
for ( ResourceEntryInfo const *pInfo = m_arrResourcesInfo.Base(), |
|
*pInfoEnd = pInfo + m_arrResourcesInfo.Count(); |
|
numTypesBufferElems-- > 0 && pInfo < pInfoEnd; ) |
|
{ |
|
*( arrTypesBuffer++ ) = ( ( pInfo++ )->eType & ~RSRCF_MASK ); |
|
} |
|
|
|
return m_arrResourcesInfo.Count(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Serialization/Unserialization of resource data |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::ResourceMemorySection::LoadData( CUtlBuffer &buf, CByteswap &byteSwap ) |
|
{ |
|
// Read the size |
|
int iDataSize = 0; |
|
buf.Get( &iDataSize, sizeof( iDataSize ) ); |
|
byteSwap.SwapBufferToTargetEndian( &iDataSize ); |
|
|
|
// Read the actual data |
|
if ( !AllocateData( iDataSize ) ) |
|
return false; |
|
|
|
buf.Get( m_pData, iDataSize ); |
|
|
|
// Test valid |
|
bool bValid = buf.IsValid(); |
|
|
|
return bValid; |
|
} |
|
|
|
bool CVTFTexture::ResourceMemorySection::WriteData( CUtlBuffer &buf ) const |
|
{ |
|
Assert( m_nDataLength && m_pData ); |
|
int iBufSize = m_nDataLength; |
|
|
|
buf.Put( &iBufSize, sizeof( iBufSize ) ); |
|
buf.Put( m_pData, m_nDataLength ); |
|
|
|
return buf.IsValid(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Checks if the file data needs to be swapped |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::SetupByteSwap( CUtlBuffer &buf ) |
|
{ |
|
VTFFileBaseHeader_t *header = (VTFFileBaseHeader_t*)buf.PeekGet(); |
|
|
|
if ( header->version[0] == SwapLong( VTF_MAJOR_VERSION ) ) |
|
{ |
|
m_Swap.ActivateByteSwapping( true ); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization |
|
//----------------------------------------------------------------------------- |
|
static bool ReadHeaderFromBufferPastBaseHeader( CUtlBuffer &buf, VTFFileHeader_t &header ) |
|
{ |
|
unsigned char *pBuf = (unsigned char*)(&header) + sizeof(VTFFileBaseHeader_t); |
|
if ( header.version[1] <= VTF_MINOR_VERSION && header.version[1] >= 4 ) |
|
{ |
|
buf.Get( pBuf, sizeof(VTFFileHeader_t) - sizeof(VTFFileBaseHeader_t) ); |
|
} |
|
else if ( header.version[1] == 3 ) |
|
{ |
|
buf.Get( pBuf, sizeof(VTFFileHeaderV7_3_t) - sizeof(VTFFileBaseHeader_t) ); |
|
} |
|
else if ( header.version[1] == 2 ) |
|
{ |
|
buf.Get( pBuf, sizeof(VTFFileHeaderV7_2_t) - sizeof(VTFFileBaseHeader_t) ); |
|
|
|
#if defined( _X360 ) || defined (POSIX) |
|
// read 15 dummy bytes to be properly positioned with 7.2 PC data |
|
byte dummy[15]; |
|
buf.Get( dummy, 15 ); |
|
#endif |
|
} |
|
else if ( header.version[1] == 1 || header.version[1] == 0 ) |
|
{ |
|
// previous version 7.0 or 7.1 |
|
buf.Get( pBuf, sizeof(VTFFileHeaderV7_1_t) - sizeof(VTFFileBaseHeader_t) ); |
|
|
|
#if defined( _X360 ) || defined (POSIX) |
|
// read a dummy byte to be properly positioned with 7.0/1 PC data |
|
byte dummy; |
|
buf.Get( &dummy, 1 ); |
|
#endif |
|
} |
|
else |
|
{ |
|
Warning( "*** Encountered VTF file with an invalid minor version!\n" ); |
|
return false; |
|
} |
|
|
|
return buf.IsValid(); |
|
} |
|
|
|
bool CVTFTexture::ReadHeader( CUtlBuffer &buf, VTFFileHeader_t &header ) |
|
{ |
|
if ( IsX360() && SetupByteSwap( buf ) ) |
|
{ |
|
VTFFileBaseHeader_t baseHeader; |
|
m_Swap.SwapFieldsToTargetEndian( &baseHeader, (VTFFileBaseHeader_t*)buf.PeekGet() ); |
|
|
|
// Swap the header inside the UtlBuffer |
|
if ( baseHeader.version[0] == VTF_MAJOR_VERSION ) |
|
{ |
|
if ( baseHeader.version[1] == 0 || baseHeader.version[1] == 1 ) |
|
{ |
|
// version 7.0 or 7.1 |
|
m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_1_t*)buf.PeekGet() ); |
|
} |
|
else if ( baseHeader.version[1] == 2 ) |
|
{ |
|
// version 7.2 |
|
m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_2_t*)buf.PeekGet() ); |
|
} |
|
else if ( baseHeader.version[1] == 3 ) |
|
{ |
|
m_Swap.SwapFieldsToTargetEndian( (VTFFileHeaderV7_3_t*)buf.PeekGet() ); |
|
} |
|
else if ( baseHeader.version[1] >= 4 && baseHeader.version[1] <= VTF_MINOR_VERSION ) |
|
{ |
|
m_Swap.SwapFieldsToTargetEndian( (VTFFileHeader_t*)buf.PeekGet() ); |
|
} |
|
} |
|
} |
|
|
|
memset( &header, 0, sizeof(VTFFileHeader_t) ); |
|
buf.Get( &header, sizeof(VTFFileBaseHeader_t) ); |
|
if ( !buf.IsValid() ) |
|
{ |
|
Warning( "*** Error unserializing VTF file... is the file empty?\n" ); |
|
return false; |
|
} |
|
|
|
// Validity check |
|
if ( Q_strncmp( header.fileTypeString, "VTF", 4 ) ) |
|
{ |
|
Warning( "*** Tried to load a non-VTF file as a VTF file!\n" ); |
|
return false; |
|
} |
|
|
|
if ( header.version[0] != VTF_MAJOR_VERSION ) |
|
{ |
|
Warning( "*** Encountered VTF file with an invalid version!\n" ); |
|
return false; |
|
} |
|
|
|
if ( !ReadHeaderFromBufferPastBaseHeader( buf, header ) ) |
|
{ |
|
Warning( "*** Encountered VTF file with an invalid full header!\n" ); |
|
return false; |
|
} |
|
|
|
// version fixups |
|
switch ( header.version[1] ) |
|
{ |
|
case 0: |
|
case 1: |
|
header.depth = 1; |
|
// fall-through |
|
case 2: |
|
header.numResources = 0; |
|
// fall-through |
|
case 3: |
|
header.flags &= VERSIONED_VTF_FLAGS_MASK_7_3; |
|
// fall-through |
|
case 4: |
|
case VTF_MINOR_VERSION: |
|
break; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Unserialization |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::Unserialize( CUtlBuffer &buf, bool bHeaderOnly, int nSkipMipLevels ) |
|
{ |
|
return UnserializeEx( buf, bHeaderOnly, 0, nSkipMipLevels ); |
|
} |
|
|
|
bool CVTFTexture::UnserializeEx( CUtlBuffer &buf, bool bHeaderOnly, int nForceFlags, int nSkipMipLevels ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (header only: %d, nForceFlags: %d, skipMips: %d)", __FUNCTION__, bHeaderOnly ? 1 : 0, nForceFlags, nSkipMipLevels ); |
|
|
|
// When unserializing, we can skip a certain number of mip levels, |
|
// and we also can just load everything but the image data |
|
VTFFileHeader_t header; |
|
|
|
if ( !ReadHeader( buf, header ) ) |
|
return false; |
|
|
|
// Pretend these flags are also set. |
|
header.flags |= nForceFlags; |
|
|
|
if ( (header.flags & TEXTUREFLAGS_ENVMAP) && (header.width != header.height) ) |
|
{ |
|
Warning( "*** Encountered VTF non-square cubemap!\n" ); |
|
return false; |
|
} |
|
if ( (header.flags & TEXTUREFLAGS_ENVMAP) && (header.depth != 1) ) |
|
{ |
|
Warning( "*** Encountered VTF volume texture cubemap!\n" ); |
|
return false; |
|
} |
|
if ( header.width <= 0 || header.height <= 0 || header.depth <= 0 ) |
|
{ |
|
Warning( "*** Encountered VTF invalid texture size!\n" ); |
|
return false; |
|
} |
|
if ( ( header.imageFormat < IMAGE_FORMAT_UNKNOWN ) || ( header.imageFormat >= NUM_IMAGE_FORMATS ) ) |
|
{ |
|
Warning( "*** Encountered VTF invalid image format!\n" ); |
|
return false; |
|
} |
|
|
|
// If the header says we should be doing a texture allocation of more than 32M, just tell the caller we failed. |
|
const int cMaxImageSizeLog2 = Q_log2( 32 * 1024 * 1024 ); |
|
if ( ( Q_log2( header.width ) + Q_log2( header.height ) + Q_log2( header.depth ) + Q_log2( header.numFrames ) > cMaxImageSizeLog2 ) || ( header.numResources > MAX_RSRC_DICTIONARY_ENTRIES ) ) |
|
{ |
|
STAGING_ONLY_EXEC( DevWarning( "Asked for a large texture to be created (%d h x %d w x %d d x %d f). Nope.\n", header.width, header.height, header.depth, header.numFrames ) ); |
|
return false; |
|
} |
|
|
|
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; |
|
|
|
// NOTE: We're going to store space for all mip levels, even if we don't |
|
// have data on disk for them. This is for backward compatibility |
|
m_nMipCount = ComputeMipCount(); |
|
|
|
m_nFinestMipmapLevel = 0; |
|
m_nCoarsestMipmapLevel = m_nMipCount - 1; |
|
|
|
m_vecReflectivity = header.reflectivity; |
|
m_flBumpScale = header.bumpScale; |
|
|
|
// FIXME: Why is this needed? |
|
m_iStartFrame = header.startFrame; |
|
|
|
// This is to make sure old-format .vtf files are read properly |
|
m_nVersion[0] = header.version[0]; |
|
m_nVersion[1] = header.version[1]; |
|
|
|
if ( header.lowResImageWidth == 0 || header.lowResImageHeight == 0 ) |
|
{ |
|
m_nLowResImageWidth = 0; |
|
m_nLowResImageHeight = 0; |
|
} |
|
else |
|
{ |
|
m_nLowResImageWidth = header.lowResImageWidth; |
|
m_nLowResImageHeight = header.lowResImageHeight; |
|
} |
|
m_LowResImageFormat = header.lowResImageFormat; |
|
|
|
// invalid image format |
|
if ( ( m_LowResImageFormat < IMAGE_FORMAT_UNKNOWN ) || ( m_LowResImageFormat >= NUM_IMAGE_FORMATS ) ) |
|
return false; |
|
|
|
// Keep the allocated memory chunks of data |
|
if ( int( header.numResources ) < m_arrResourcesData.Count() ) |
|
{ |
|
m_arrResourcesData_ForReuse.EnsureCapacity( m_arrResourcesData_ForReuse.Count() + m_arrResourcesData.Count() - header.numResources ); |
|
for ( ResourceMemorySection const *pRms = &m_arrResourcesData[ header.numResources ], |
|
*pRmsEnd = m_arrResourcesData.Base() + m_arrResourcesData.Count(); pRms < pRmsEnd; ++ pRms ) |
|
{ |
|
if ( pRms->m_pData ) |
|
{ |
|
int idxReuse = m_arrResourcesData_ForReuse.AddToTail( *pRms ); |
|
m_arrResourcesData_ForReuse[ idxReuse ].m_nDataLength = 0; // Data for reuse shouldn't have length set |
|
} |
|
} |
|
} |
|
m_arrResourcesData.SetCount( header.numResources ); |
|
|
|
// Read the dictionary of resources info |
|
if ( header.numResources > 0 ) |
|
{ |
|
m_arrResourcesInfo.RemoveAll(); |
|
m_arrResourcesInfo.SetCount( header.numResources ); |
|
|
|
buf.Get( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) ); |
|
if ( !buf.IsValid() ) |
|
return false; |
|
|
|
if ( IsX360() ) |
|
{ |
|
// Byte-swap the dictionary data offsets |
|
for ( int k = 0; k < m_arrResourcesInfo.Count(); ++ k ) |
|
{ |
|
ResourceEntryInfo &rei = m_arrResourcesInfo[k]; |
|
if ( ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 ) |
|
{ |
|
m_Swap.SwapBufferToTargetEndian( &rei.resData ); |
|
} |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Older version (7.0 - 7.2): |
|
// - low-res image data first (optional) |
|
// - then image data |
|
m_arrResourcesInfo.RemoveAll(); |
|
|
|
// Low-res image data |
|
int nLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth, |
|
m_nLowResImageHeight, 1, m_LowResImageFormat, false ); |
|
if ( nLowResImageSize ) |
|
{ |
|
ResourceEntryInfo &rei = *FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ); |
|
rei.resData = buf.TellGet(); |
|
} |
|
|
|
// Image data |
|
ResourceEntryInfo &rei = *FindOrCreateResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ); |
|
rei.resData = buf.TellGet() + nLowResImageSize; |
|
} |
|
|
|
// Caller wants the header component only, avoids reading large image data sets |
|
if ( bHeaderOnly ) |
|
return true; |
|
|
|
// Load the low res image |
|
if ( ResourceEntryInfo const *pLowResDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) ) |
|
{ |
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, pLowResDataInfo->resData ); |
|
if ( !LoadLowResData( buf ) ) |
|
return false; |
|
} |
|
|
|
// Load any new resources |
|
if ( !LoadNewResources( buf ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
// Load the image data |
|
if ( ResourceEntryInfo const *pImageDataInfo = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ) ) |
|
{ |
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, pImageDataInfo->resData ); |
|
if ( !LoadImageData( buf, header, nSkipMipLevels ) ) |
|
return false; |
|
} |
|
else |
|
{ |
|
// No image data |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CVTFTexture::GetMipmapRange( int* pOutFinest, int* pOutCoarsest ) |
|
{ |
|
if ( pOutFinest ) |
|
*pOutFinest = m_nFinestMipmapLevel; |
|
|
|
if ( pOutCoarsest ) |
|
*pOutCoarsest = m_nCoarsestMipmapLevel; |
|
} |
|
|
|
bool CVTFTexture::LoadNewResources( CUtlBuffer &buf ) |
|
{ |
|
// Load the new resources |
|
for ( int idxRsrc = 0; idxRsrc < m_arrResourcesInfo.Count(); ++idxRsrc ) |
|
{ |
|
ResourceEntryInfo &rei = m_arrResourcesInfo[ idxRsrc ]; |
|
ResourceMemorySection &rms = m_arrResourcesData[ idxRsrc ]; |
|
|
|
if ( ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 ) |
|
{ |
|
switch( rei.eType ) |
|
{ |
|
case VTF_LEGACY_RSRC_LOW_RES_IMAGE: |
|
case VTF_LEGACY_RSRC_IMAGE: |
|
// these legacy resources are loaded differently |
|
continue; |
|
|
|
default: |
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, rei.resData ); |
|
if ( !rms.LoadData( buf, m_Swap ) ) |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
ResourceEntryInfo const *CVTFTexture::FindResourceEntryInfo( uint32 eType ) const |
|
{ |
|
Assert( ( eType & RSRCF_MASK ) == 0 ); |
|
|
|
ResourceEntryInfo const *pRange[2]; |
|
pRange[0] = m_arrResourcesInfo.Base(); |
|
pRange[1] = pRange[0] + m_arrResourcesInfo.Count(); |
|
|
|
if ( IsPC() ) |
|
{ |
|
// Quick-search in a sorted array |
|
ResourceEntryInfo const *pMid; |
|
find_routine: |
|
if ( pRange[0] != pRange[1] ) |
|
{ |
|
pMid = pRange[0] + ( pRange[1] - pRange[0] ) / 2; |
|
if ( int diff = int( pMid->eType & ~RSRCF_MASK ) - int( eType ) ) |
|
{ |
|
int off = !( diff > 0 ); |
|
pRange[ !off ] = pMid + off; |
|
goto find_routine; |
|
} |
|
else |
|
return pMid; |
|
} |
|
else |
|
return NULL; |
|
} |
|
else |
|
{ |
|
// 360 eschews a sorted format due to endian issues |
|
// use a linear search for compatibility with reading pc formats |
|
for ( ; pRange[0] < pRange[1]; ++pRange[0] ) |
|
{ |
|
if ( ( pRange[0]->eType & ~RSRCF_MASK ) == eType ) |
|
return pRange[0]; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
ResourceEntryInfo * CVTFTexture::FindResourceEntryInfo( uint32 eType ) |
|
{ |
|
return const_cast< ResourceEntryInfo * >( |
|
( ( CVTFTexture const * ) this )->FindResourceEntryInfo( eType ) ); |
|
} |
|
|
|
ResourceEntryInfo * CVTFTexture::FindOrCreateResourceEntryInfo( uint32 eType ) |
|
{ |
|
Assert( ( eType & RSRCF_MASK ) == 0 ); |
|
|
|
int k = 0; |
|
for ( ; k < m_arrResourcesInfo.Count(); ++ k ) |
|
{ |
|
uint32 rsrcType = ( m_arrResourcesInfo[ k ].eType & ~RSRCF_MASK ); |
|
if ( rsrcType == eType ) |
|
{ |
|
// found |
|
return &m_arrResourcesInfo[ k ]; |
|
} |
|
|
|
// sort for PC only, 360 uses linear sort for compatibility with PC endian |
|
if ( IsPC() ) |
|
{ |
|
if ( rsrcType > eType ) |
|
break; |
|
} |
|
} |
|
|
|
ResourceEntryInfo rei; |
|
memset( &rei, 0, sizeof( rei ) ); |
|
rei.eType = eType; |
|
|
|
// Inserting before "k" |
|
if ( m_arrResourcesData_ForReuse.Count() ) |
|
{ |
|
m_arrResourcesData.InsertBefore( k, m_arrResourcesData_ForReuse[ m_arrResourcesData_ForReuse.Count() - 1 ] ); |
|
m_arrResourcesData_ForReuse.FastRemove( m_arrResourcesData_ForReuse.Count() - 1 ); |
|
} |
|
else |
|
{ |
|
m_arrResourcesData.InsertBefore( k ); |
|
} |
|
|
|
m_arrResourcesInfo.InsertBefore( k, rei ); |
|
return &m_arrResourcesInfo[k]; |
|
} |
|
|
|
bool CVTFTexture::RemoveResourceEntryInfo( uint32 eType ) |
|
{ |
|
Assert( ( eType & RSRCF_MASK ) == 0 ); |
|
|
|
for ( int k = 0; k < m_arrResourcesInfo.Count(); ++ k ) |
|
{ |
|
if ( ( m_arrResourcesInfo[ k ].eType & ~RSRCF_MASK ) == eType ) |
|
{ |
|
m_arrResourcesInfo.Remove( k ); |
|
|
|
if ( m_arrResourcesData[k].m_pData ) |
|
{ |
|
int idxReuse = m_arrResourcesData_ForReuse.AddToTail( m_arrResourcesData[k] ); |
|
m_arrResourcesData_ForReuse[ idxReuse ].m_nDataLength = 0; // Data for reuse shouldn't have length set |
|
} |
|
|
|
m_arrResourcesData.Remove( k ); |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Serialization of image data |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::WriteImageData( CUtlBuffer &buf ) |
|
{ |
|
// NOTE: We load the bits this way because we store the bits in memory |
|
// differently that the way they are stored on disk; we store on disk |
|
// differently so we can only load up |
|
// NOTE: The smallest mip levels are stored first!! |
|
for (int iMip = m_nMipCount; --iMip >= 0; ) |
|
{ |
|
int iMipSize = ComputeMipSize( iMip ); |
|
|
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < m_nFaceCount; ++iFace) |
|
{ |
|
unsigned char *pMipBits = ImageData( iFrame, iFace, iMip ); |
|
buf.Put( pMipBits, iMipSize ); |
|
} |
|
} |
|
} |
|
|
|
return buf.IsValid(); |
|
} |
|
|
|
// Inserts padding to have a multiple of "iAlignment" bytes in the buffer |
|
// Returns number of pad bytes written |
|
static int PadBuffer( CUtlBuffer &buf, int iAlignment ) |
|
{ |
|
unsigned int uiCurrentBytes = buf.TellPut(); |
|
int iPadBytes = AlignValue( uiCurrentBytes, iAlignment ) - uiCurrentBytes; |
|
|
|
// Fill data |
|
for ( int i=0; i<iPadBytes; i++ ) |
|
{ |
|
buf.PutChar( '\0' ); |
|
} |
|
|
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, uiCurrentBytes + iPadBytes ); |
|
|
|
return iPadBytes; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Serialization |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::Serialize( CUtlBuffer &buf ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
// Unsupported path, 360 has no reason and cannot serialize |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
if ( !m_pImageData ) |
|
{ |
|
Warning("*** Unable to serialize... have no image data!\n"); |
|
return false; |
|
} |
|
|
|
VTFFileHeader_t header; |
|
memset( &header, 0, sizeof( header ) ); |
|
Q_strncpy( header.fileTypeString, "VTF", 4 ); |
|
header.version[0] = VTF_MAJOR_VERSION; |
|
header.version[1] = VTF_MINOR_VERSION; |
|
header.headerSize = sizeof(VTFFileHeader_t) + m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ); |
|
|
|
header.width = m_nWidth; |
|
header.height = m_nHeight; |
|
header.depth = m_nDepth; |
|
header.flags = m_nFlags; |
|
header.numFrames = m_nFrameCount; |
|
header.numMipLevels = m_nMipCount; |
|
header.imageFormat = m_Format; |
|
VectorCopy( m_vecReflectivity, header.reflectivity ); |
|
header.bumpScale = m_flBumpScale; |
|
|
|
// FIXME: Why is this needed? |
|
header.startFrame = m_iStartFrame; |
|
|
|
header.lowResImageWidth = m_nLowResImageWidth; |
|
header.lowResImageHeight = m_nLowResImageHeight; |
|
header.lowResImageFormat = m_LowResImageFormat; |
|
|
|
header.numResources = m_arrResourcesInfo.Count(); |
|
|
|
buf.Put( &header, sizeof(VTFFileHeader_t) ); |
|
if ( !buf.IsValid() ) |
|
return false; |
|
|
|
// Write the dictionary of resource entry infos |
|
int iSeekOffsetResInfoFixup = buf.TellPut(); |
|
buf.Put( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) ); |
|
if ( !buf.IsValid() ) |
|
return false; |
|
|
|
// Write the low res image first |
|
if ( ResourceEntryInfo *pRei = FindResourceEntryInfo( VTF_LEGACY_RSRC_LOW_RES_IMAGE ) ) |
|
{ |
|
pRei->resData = buf.TellPut(); |
|
|
|
Assert( m_pLowResImageData ); |
|
int iLowResImageSize = ImageLoader::GetMemRequired( m_nLowResImageWidth, |
|
m_nLowResImageHeight, 1, m_LowResImageFormat, false ); |
|
buf.Put( m_pLowResImageData, iLowResImageSize ); |
|
if ( !buf.IsValid() ) |
|
return false; |
|
} |
|
|
|
// Serialize the new resources |
|
for ( int iRsrc = 0; iRsrc < m_arrResourcesInfo.Count(); ++ iRsrc ) |
|
{ |
|
ResourceEntryInfo &rei = m_arrResourcesInfo[ iRsrc ]; |
|
|
|
switch ( rei.eType ) |
|
{ |
|
case VTF_LEGACY_RSRC_LOW_RES_IMAGE: |
|
case VTF_LEGACY_RSRC_IMAGE: |
|
// written differently |
|
continue; |
|
|
|
default: |
|
{ |
|
if ( rei.eType & RSRCF_HAS_NO_DATA_CHUNK ) |
|
continue; |
|
rei.resData = buf.TellPut(); |
|
ResourceMemorySection &rms = m_arrResourcesData[ iRsrc ]; |
|
if ( !rms.WriteData( buf ) ) |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
// Write image data last |
|
if ( ResourceEntryInfo *pRei = FindResourceEntryInfo( VTF_LEGACY_RSRC_IMAGE ) ) |
|
{ |
|
pRei->resData = buf.TellPut(); |
|
WriteImageData( buf ); |
|
} |
|
else |
|
return false; |
|
|
|
// Now fixup the resources dictionary |
|
int iTotalBytesPut = buf.TellPut(); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, iSeekOffsetResInfoFixup ); |
|
buf.Put( m_arrResourcesInfo.Base(), m_arrResourcesInfo.Count() * sizeof( ResourceEntryInfo ) ); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, iTotalBytesPut ); |
|
|
|
// Return if the buffer is valid |
|
return buf.IsValid(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Attributes... |
|
//----------------------------------------------------------------------------- |
|
int CVTFTexture::Width() const |
|
{ |
|
return m_nWidth; |
|
} |
|
|
|
int CVTFTexture::Height() const |
|
{ |
|
return m_nHeight; |
|
} |
|
|
|
int CVTFTexture::Depth() const |
|
{ |
|
return m_nDepth; |
|
} |
|
|
|
int CVTFTexture::MipCount() const |
|
{ |
|
return m_nMipCount; |
|
} |
|
|
|
ImageFormat CVTFTexture::Format() const |
|
{ |
|
return m_Format; |
|
} |
|
|
|
int CVTFTexture::FaceCount() const |
|
{ |
|
return m_nFaceCount; |
|
} |
|
|
|
int CVTFTexture::FrameCount() const |
|
{ |
|
return m_nFrameCount; |
|
} |
|
|
|
int CVTFTexture::Flags() const |
|
{ |
|
return m_nFlags; |
|
} |
|
|
|
bool CVTFTexture::IsCubeMap() const |
|
{ |
|
return (m_nFlags & TEXTUREFLAGS_ENVMAP) != 0; |
|
} |
|
|
|
bool CVTFTexture::IsNormalMap() const |
|
{ |
|
return (m_nFlags & TEXTUREFLAGS_NORMAL) != 0; |
|
} |
|
|
|
bool CVTFTexture::IsVolumeTexture() const |
|
{ |
|
return (m_nDepth > 1); |
|
} |
|
|
|
float CVTFTexture::BumpScale() const |
|
{ |
|
return m_flBumpScale; |
|
} |
|
|
|
const Vector &CVTFTexture::Reflectivity() const |
|
{ |
|
return m_vecReflectivity; |
|
} |
|
|
|
unsigned char *CVTFTexture::ImageData() |
|
{ |
|
return m_pImageData; |
|
} |
|
|
|
int CVTFTexture::LowResWidth() const |
|
{ |
|
return m_nLowResImageWidth; |
|
} |
|
|
|
int CVTFTexture::LowResHeight() const |
|
{ |
|
return m_nLowResImageHeight; |
|
} |
|
|
|
ImageFormat CVTFTexture::LowResFormat() const |
|
{ |
|
return m_LowResImageFormat; |
|
} |
|
|
|
unsigned char *CVTFTexture::LowResImageData() |
|
{ |
|
return m_pLowResImageData; |
|
} |
|
|
|
int CVTFTexture::RowSizeInBytes( int nMipLevel ) const |
|
{ |
|
int nWidth = (m_nWidth >> nMipLevel); |
|
if (nWidth < 1) |
|
{ |
|
nWidth = 1; |
|
} |
|
return ImageLoader::SizeInBytes( m_Format ) * nWidth; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// returns the size of one face of a particular mip level |
|
//----------------------------------------------------------------------------- |
|
int CVTFTexture::FaceSizeInBytes( int nMipLevel ) const |
|
{ |
|
int nWidth = (m_nWidth >> nMipLevel); |
|
if (nWidth < 1) |
|
{ |
|
nWidth = 1; |
|
} |
|
int nHeight = (m_nHeight >> nMipLevel); |
|
if (nHeight < 1) |
|
{ |
|
nHeight = 1; |
|
} |
|
return ImageLoader::SizeInBytes( m_Format ) * nWidth * nHeight; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a pointer to the data associated with a particular frame, face, and mip level |
|
//----------------------------------------------------------------------------- |
|
unsigned char *CVTFTexture::ImageData( int iFrame, int iFace, int iMipLevel ) |
|
{ |
|
Assert( m_pImageData ); |
|
int iOffset = GetImageOffset( iFrame, iFace, iMipLevel, m_Format ); |
|
return &m_pImageData[iOffset]; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns a pointer to the data associated with a particular frame, face, mip level, and offset |
|
//----------------------------------------------------------------------------- |
|
unsigned char *CVTFTexture::ImageData( int iFrame, int iFace, int iMipLevel, int x, int y, int z ) |
|
{ |
|
#ifdef _DEBUG |
|
int nWidth, nHeight, nDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nWidth, &nHeight, &nDepth ); |
|
Assert( (x >= 0) && (x <= nWidth) && (y >= 0) && (y <= nHeight) && (z >= 0) && (z <= nDepth) ); |
|
#endif |
|
|
|
int nFaceBytes = FaceSizeInBytes( iMipLevel ); |
|
int nRowBytes = RowSizeInBytes( iMipLevel ); |
|
int nTexelBytes = ImageLoader::SizeInBytes( m_Format ); |
|
|
|
unsigned char *pMipBits = ImageData( iFrame, iFace, iMipLevel ); |
|
pMipBits += z * nFaceBytes + y * nRowBytes + x * nTexelBytes; |
|
return pMipBits; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the size (in bytes) of a single mipmap of a single face of a single frame |
|
//----------------------------------------------------------------------------- |
|
inline int CVTFTexture::ComputeMipSize( int iMipLevel, ImageFormat fmt ) const |
|
{ |
|
Assert( iMipLevel < m_nMipCount ); |
|
int w, h, d; |
|
ComputeMipLevelDimensions( iMipLevel, &w, &h, &d ); |
|
return ImageLoader::GetMemRequired( w, h, d, fmt, false ); |
|
} |
|
|
|
int CVTFTexture::ComputeMipSize( int iMipLevel ) const |
|
{ |
|
// Version for the public interface; don't want to expose the fmt parameter |
|
return ComputeMipSize( iMipLevel, m_Format ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the size of a single face of a single frame |
|
// All mip levels starting at the specified mip level are included |
|
//----------------------------------------------------------------------------- |
|
inline int CVTFTexture::ComputeFaceSize( int iStartingMipLevel, ImageFormat fmt ) const |
|
{ |
|
int iSize = 0; |
|
int w = m_nWidth; |
|
int h = m_nHeight; |
|
int d = m_nDepth; |
|
|
|
for( int i = 0; i < m_nMipCount; ++i ) |
|
{ |
|
if (i >= iStartingMipLevel) |
|
{ |
|
iSize += ImageLoader::GetMemRequired( w, h, d, fmt, false ); |
|
} |
|
w >>= 1; |
|
h >>= 1; |
|
d >>= 1; |
|
if ( w < 1 ) |
|
{ |
|
w = 1; |
|
} |
|
if ( h < 1 ) |
|
{ |
|
h = 1; |
|
} |
|
if ( d < 1 ) |
|
{ |
|
d = 1; |
|
} |
|
} |
|
return iSize; |
|
} |
|
|
|
int CVTFTexture::ComputeFaceSize( int iStartingMipLevel ) const |
|
{ |
|
// Version for the public interface; don't want to expose the fmt parameter |
|
return ComputeFaceSize( iStartingMipLevel, m_Format ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the total size of all faces, all frames |
|
//----------------------------------------------------------------------------- |
|
inline int CVTFTexture::ComputeTotalSize( ImageFormat fmt ) const |
|
{ |
|
// Compute the number of bytes required to store a single face/frame |
|
int iMemRequired = ComputeFaceSize( 0, fmt ); |
|
|
|
// Now compute the total image size |
|
return m_nFaceCount * m_nFrameCount * iMemRequired; |
|
} |
|
|
|
int CVTFTexture::ComputeTotalSize( ) const |
|
{ |
|
// Version for the public interface; don't want to expose the fmt parameter |
|
return ComputeTotalSize( m_Format ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the location of a particular frame, face, and mip level |
|
//----------------------------------------------------------------------------- |
|
int CVTFTexture::GetImageOffset( int iFrame, int iFace, int iMipLevel, ImageFormat fmt ) const |
|
{ |
|
Assert( iFrame < m_nFrameCount ); |
|
Assert( iFace < m_nFaceCount ); |
|
Assert( iMipLevel < m_nMipCount ); |
|
|
|
int i; |
|
int iOffset = 0; |
|
|
|
if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) ) |
|
{ |
|
// 360 data is stored same as disk, 1x1 up to NxN |
|
// get to the right miplevel |
|
int iMipWidth, iMipHeight, iMipDepth; |
|
for ( i = m_nMipCount - 1; i > iMipLevel; --i ) |
|
{ |
|
ComputeMipLevelDimensions( i, &iMipWidth, &iMipHeight, &iMipDepth ); |
|
int iMipLevelSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, fmt, false ); |
|
iOffset += m_nFrameCount * m_nFaceCount * iMipLevelSize; |
|
} |
|
|
|
// get to the right frame |
|
ComputeMipLevelDimensions( iMipLevel, &iMipWidth, &iMipHeight, &iMipDepth ); |
|
int nFaceSize = ImageLoader::GetMemRequired( iMipWidth, iMipHeight, iMipDepth, fmt, false ); |
|
iOffset += iFrame * m_nFaceCount * nFaceSize; |
|
|
|
// get to the right face |
|
iOffset += iFace * nFaceSize; |
|
|
|
return iOffset; |
|
} |
|
|
|
// get to the right frame |
|
int iFaceSize = ComputeFaceSize( 0, fmt ); |
|
iOffset = iFrame * m_nFaceCount * iFaceSize; |
|
|
|
// Get to the right face |
|
iOffset += iFace * iFaceSize; |
|
|
|
// Get to the right mip level |
|
for (i = 0; i < iMipLevel; ++i) |
|
{ |
|
iOffset += ComputeMipSize( i, fmt ); |
|
} |
|
|
|
return iOffset; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the dimensions of a particular mip level |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ComputeMipLevelDimensions( int iMipLevel, int *pMipWidth, int *pMipHeight, int *pMipDepth ) const |
|
{ |
|
Assert( iMipLevel < m_nMipCount ); |
|
|
|
*pMipWidth = m_nWidth >> iMipLevel; |
|
*pMipHeight = m_nHeight >> iMipLevel; |
|
*pMipDepth = m_nDepth >> iMipLevel; |
|
if ( *pMipWidth < 1 ) |
|
{ |
|
*pMipWidth = 1; |
|
} |
|
if ( *pMipHeight < 1 ) |
|
{ |
|
*pMipHeight = 1; |
|
} |
|
if ( *pMipDepth < 1 ) |
|
{ |
|
*pMipDepth = 1; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the size of a subrect at a particular mip level |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ComputeMipLevelSubRect( Rect_t *pSrcRect, int nMipLevel, Rect_t *pSubRect ) const |
|
{ |
|
Assert( pSrcRect->x >= 0 && pSrcRect->y >= 0 && |
|
(pSrcRect->x + pSrcRect->width <= m_nWidth) && |
|
(pSrcRect->y + pSrcRect->height <= m_nHeight) ); |
|
|
|
if (nMipLevel == 0) |
|
{ |
|
*pSubRect = *pSrcRect; |
|
return; |
|
} |
|
|
|
float flInvShrink = 1.0f / (float)(1 << nMipLevel); |
|
pSubRect->x = pSrcRect->x * flInvShrink; |
|
pSubRect->y = pSrcRect->y * flInvShrink; |
|
pSubRect->width = (int)ceil( (pSrcRect->x + pSrcRect->width) * flInvShrink ) - pSubRect->x; |
|
pSubRect->height = (int)ceil( (pSrcRect->y + pSrcRect->height) * flInvShrink ) - pSubRect->y; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Converts the texture's image format. Use IMAGE_FORMAT_DEFAULT |
|
// if you want to be able to use various tool functions below |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ConvertImageFormat( ImageFormat fmt, bool bNormalToDUDV ) |
|
{ |
|
if ( !m_pImageData ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( fmt == IMAGE_FORMAT_DEFAULT ) |
|
{ |
|
fmt = IMAGE_FORMAT_RGBA8888; |
|
} |
|
|
|
if ( bNormalToDUDV && !( fmt == IMAGE_FORMAT_UV88 || fmt == IMAGE_FORMAT_UVWQ8888 || fmt == IMAGE_FORMAT_UVLX8888 ) ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
if ( m_Format == fmt ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( IsX360() && ( m_nVersion[0] == VTF_X360_MAJOR_VERSION ) ) |
|
{ |
|
// 360 textures should be baked in final format |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
// FIXME: Should this be re-written to not do an allocation? |
|
int iConvertedSize = ComputeTotalSize( fmt ); |
|
|
|
unsigned char *pConvertedImage = new unsigned char[ iConvertedSize ]; |
|
|
|
// This can happen for large, bogus textures. |
|
if ( !pConvertedImage ) |
|
return; |
|
|
|
for (int iMip = 0; iMip < m_nMipCount; ++iMip) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMip, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
|
|
int nSrcFaceStride = ImageLoader::GetMemRequired( nMipWidth, nMipHeight, 1, m_Format, false ); |
|
int nDstFaceStride = ImageLoader::GetMemRequired( nMipWidth, nMipHeight, 1, fmt, false ); |
|
|
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < m_nFaceCount; ++iFace) |
|
{ |
|
unsigned char *pSrcData = ImageData( iFrame, iFace, iMip ); |
|
unsigned char *pDstData = pConvertedImage + |
|
GetImageOffset( iFrame, iFace, iMip, fmt ); |
|
|
|
for ( int z = 0; z < nMipDepth; ++z, pSrcData += nSrcFaceStride, pDstData += nDstFaceStride ) |
|
{ |
|
if( bNormalToDUDV ) |
|
{ |
|
if( fmt == IMAGE_FORMAT_UV88 ) |
|
{ |
|
ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUV88( pSrcData, |
|
nMipWidth, nMipHeight, pDstData ); |
|
} |
|
else if( fmt == IMAGE_FORMAT_UVWQ8888 ) |
|
{ |
|
ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUVWQ8888( pSrcData, |
|
nMipWidth, nMipHeight, pDstData ); |
|
} |
|
else if ( fmt == IMAGE_FORMAT_UVLX8888 ) |
|
{ |
|
ImageLoader::ConvertNormalMapRGBA8888ToDUDVMapUVLX8888( pSrcData, |
|
nMipWidth, nMipHeight, pDstData ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
} |
|
else |
|
{ |
|
ImageLoader::ConvertImageFormat( pSrcData, m_Format, |
|
pDstData, fmt, nMipWidth, nMipHeight ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( !AllocateImageData(iConvertedSize) ) |
|
return; |
|
|
|
memcpy( m_pImageData, pConvertedImage, iConvertedSize ); |
|
m_Format = fmt; |
|
|
|
if ( !ImageLoader::IsCompressed( fmt ) ) |
|
{ |
|
int nAlphaBits = ImageLoader::ImageFormatInfo( fmt ).m_NumAlphaBits; |
|
if ( nAlphaBits > 1 ) |
|
{ |
|
m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA; |
|
m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA; |
|
} |
|
if ( nAlphaBits <= 1 ) |
|
{ |
|
m_nFlags &= ~TEXTUREFLAGS_EIGHTBITALPHA; |
|
if ( nAlphaBits == 0 ) |
|
{ |
|
m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Only DXT5 has alpha bits |
|
if ( ( fmt == IMAGE_FORMAT_DXT1 ) || ( fmt == IMAGE_FORMAT_ATI2N ) || ( fmt == IMAGE_FORMAT_ATI1N ) ) |
|
{ |
|
m_nFlags &= ~(TEXTUREFLAGS_ONEBITALPHA|TEXTUREFLAGS_EIGHTBITALPHA); |
|
} |
|
} |
|
|
|
delete [] pConvertedImage; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Enums + structures related to conversion from cube to spheremap |
|
//----------------------------------------------------------------------------- |
|
struct SphereCalc_t |
|
{ |
|
Vector dir; |
|
float m_flRadius; |
|
float m_flOORadius; |
|
float m_flRadiusSq; |
|
LookDir_t m_LookDir; |
|
Vector m_vecLookDir; |
|
unsigned char m_pColor[4]; |
|
unsigned char **m_ppCubeFaces; |
|
int m_iSize; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Methods associated with computing a spheremap from a cubemap |
|
// |
|
//----------------------------------------------------------------------------- |
|
static void CalcInit( SphereCalc_t *pCalc, int iSize, unsigned char **ppCubeFaces, LookDir_t lookDir = LOOK_DOWN_Z ) |
|
{ |
|
// NOTE: Width + height should be the same |
|
pCalc->m_flRadius = iSize * 0.5f; |
|
pCalc->m_flRadiusSq = pCalc->m_flRadius * pCalc->m_flRadius; |
|
pCalc->m_flOORadius = 1.0f / pCalc->m_flRadius; |
|
pCalc->m_LookDir = lookDir; |
|
pCalc->m_ppCubeFaces = ppCubeFaces; |
|
pCalc->m_iSize = iSize; |
|
|
|
switch( lookDir) |
|
{ |
|
case LOOK_DOWN_X: |
|
pCalc->m_vecLookDir.Init( 1, 0, 0 ); |
|
break; |
|
|
|
case LOOK_DOWN_NEGX: |
|
pCalc->m_vecLookDir.Init( -1, 0, 0 ); |
|
break; |
|
|
|
case LOOK_DOWN_Y: |
|
pCalc->m_vecLookDir.Init( 0, 1, 0 ); |
|
break; |
|
|
|
case LOOK_DOWN_NEGY: |
|
pCalc->m_vecLookDir.Init( 0, -1, 0 ); |
|
break; |
|
|
|
case LOOK_DOWN_Z: |
|
pCalc->m_vecLookDir.Init( 0, 0, 1 ); |
|
break; |
|
|
|
case LOOK_DOWN_NEGZ: |
|
pCalc->m_vecLookDir.Init( 0, 0, -1 ); |
|
break; |
|
} |
|
} |
|
|
|
static void TransformNormal( SphereCalc_t *pCalc, Vector& normal ) |
|
{ |
|
Vector vecTemp = normal; |
|
|
|
switch( pCalc->m_LookDir) |
|
{ |
|
// Look down +x |
|
case LOOK_DOWN_X: |
|
normal[0] = vecTemp[2]; |
|
normal[2] = -vecTemp[0]; |
|
break; |
|
|
|
// Look down -x |
|
case LOOK_DOWN_NEGX: |
|
normal[0] = -vecTemp[2]; |
|
normal[2] = vecTemp[0]; |
|
break; |
|
|
|
// Look down +y |
|
case LOOK_DOWN_Y: |
|
normal[0] = -vecTemp[0]; |
|
normal[1] = vecTemp[2]; |
|
normal[2] = vecTemp[1]; |
|
break; |
|
|
|
// Look down -y |
|
case LOOK_DOWN_NEGY: |
|
normal[0] = vecTemp[0]; |
|
normal[1] = -vecTemp[2]; |
|
normal[2] = vecTemp[1]; |
|
break; |
|
|
|
// Look down +z |
|
case LOOK_DOWN_Z: |
|
return; |
|
|
|
// Look down -z |
|
case LOOK_DOWN_NEGZ: |
|
normal[0] = -vecTemp[0]; |
|
normal[2] = -vecTemp[2]; |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Given a iFace normal, determine which cube iFace to sample |
|
//----------------------------------------------------------------------------- |
|
static int CalcFaceIndex( const Vector& normal ) |
|
{ |
|
float absx, absy, absz; |
|
|
|
absx = normal[0] >= 0 ? normal[0] : -normal[0]; |
|
absy = normal[1] >= 0 ? normal[1] : -normal[1]; |
|
absz = normal[2] >= 0 ? normal[2] : -normal[2]; |
|
|
|
if ( absx > absy ) |
|
{ |
|
if ( absx > absz ) |
|
{ |
|
// left/right |
|
if ( normal[0] >= 0 ) |
|
return CUBEMAP_FACE_RIGHT; |
|
return CUBEMAP_FACE_LEFT; |
|
} |
|
} |
|
else |
|
{ |
|
if ( absy > absz ) |
|
{ |
|
// front / back |
|
if ( normal[1] >= 0 ) |
|
return CUBEMAP_FACE_BACK; |
|
return CUBEMAP_FACE_FRONT; |
|
} |
|
} |
|
|
|
// top / bottom |
|
if ( normal[2] >= 0 ) |
|
return CUBEMAP_FACE_UP; |
|
return CUBEMAP_FACE_DOWN; |
|
} |
|
|
|
static void CalcColor( SphereCalc_t *pCalc, int iFace, const Vector &normal, unsigned char *color ) |
|
{ |
|
float x, y, w; |
|
|
|
int size = pCalc->m_iSize; |
|
float hw = 0.5 * size; |
|
|
|
if ( (iFace == CUBEMAP_FACE_LEFT) || (iFace == CUBEMAP_FACE_RIGHT) ) |
|
{ |
|
w = hw / normal[0]; |
|
x = -normal[2]; |
|
y = -normal[1]; |
|
if ( iFace == CUBEMAP_FACE_LEFT ) |
|
y = -y; |
|
} |
|
else if ( (iFace == CUBEMAP_FACE_FRONT) || (iFace == CUBEMAP_FACE_BACK) ) |
|
{ |
|
w = hw / normal[1]; |
|
x = normal[0]; |
|
y = normal[2]; |
|
if ( iFace == CUBEMAP_FACE_FRONT ) |
|
x = -x; |
|
} |
|
else |
|
{ |
|
w = hw / normal[2]; |
|
x = -normal[0]; |
|
y = -normal[1]; |
|
if ( iFace == CUBEMAP_FACE_UP ) |
|
x = -x; |
|
} |
|
|
|
x = (x * w) + hw - 0.5; |
|
y = (y * w) + hw - 0.5; |
|
|
|
int u = (int)(x+0.5); |
|
int v = (int)(y+0.5); |
|
|
|
if ( u < 0 ) u = 0; |
|
else if ( u > (size-1) ) u = (size-1); |
|
|
|
if ( v < 0 ) v = 0; |
|
else if ( v > (size-1) ) v = (size-1); |
|
|
|
int offset = (v * size + u) * 4; |
|
|
|
unsigned char *pPix = pCalc->m_ppCubeFaces[iFace] + offset; |
|
color[0] = pPix[0]; |
|
color[1] = pPix[1]; |
|
color[2] = pPix[2]; |
|
color[3] = pPix[3]; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the spheremap color at a particular (x,y) texcoord |
|
//----------------------------------------------------------------------------- |
|
static void CalcSphereColor( SphereCalc_t *pCalc, float x, float y ) |
|
{ |
|
Vector normal; |
|
float flRadiusSq = x*x + y*y; |
|
if (flRadiusSq > pCalc->m_flRadiusSq) |
|
{ |
|
// Force a glancing reflection |
|
normal.Init( 0, 1, 0 ); |
|
} |
|
else |
|
{ |
|
// Compute the z distance based on x*x + y*y + z*z = r*r |
|
float z = sqrt( pCalc->m_flRadiusSq - flRadiusSq ); |
|
|
|
// Here's the untransformed surface normal |
|
normal.Init( x, y, z ); |
|
normal *= pCalc->m_flOORadius; |
|
} |
|
|
|
// Transform the normal based on the actual view direction |
|
TransformNormal( pCalc, normal ); |
|
|
|
// Compute the reflection vector (full spheremap solution) |
|
// R = 2 * (N dot L)N - L |
|
Vector vecReflect; |
|
float nDotL = DotProduct( normal, pCalc->m_vecLookDir ); |
|
VectorMA( pCalc->m_vecLookDir, -2.0f * nDotL, normal, vecReflect ); |
|
vecReflect *= -1.0f; |
|
|
|
int iFace = CalcFaceIndex( vecReflect ); |
|
CalcColor( pCalc, iFace, vecReflect, pCalc->m_pColor ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the spheremap color at a particular (x,y) texcoord |
|
//----------------------------------------------------------------------------- |
|
static void CalcHemisphereColor( SphereCalc_t *pCalc, float x, float y ) |
|
{ |
|
Vector normal; |
|
float flRadiusSq = x*x + y*y; |
|
if (flRadiusSq > pCalc->m_flRadiusSq) |
|
{ |
|
normal.Init( x, y, 0.0f ); |
|
VectorNormalize( normal ); |
|
normal *= pCalc->m_flRadiusSq; |
|
flRadiusSq = pCalc->m_flRadiusSq; |
|
} |
|
|
|
// Compute the z distance based on x*x + y*y + z*z = r*r |
|
float z = sqrt( pCalc->m_flRadiusSq - flRadiusSq ); |
|
|
|
// Here's the untransformed surface normal |
|
normal.Init( x, y, z ); |
|
normal *= pCalc->m_flOORadius; |
|
|
|
// Transform the normal based on the actual view direction |
|
TransformNormal( pCalc, normal ); |
|
|
|
// printf( "x: %f y: %f normal: %f %f %f\n", x, y, normal.x, normal.y, normal.z ); |
|
|
|
/* |
|
// Compute the reflection vector (full spheremap solution) |
|
// R = 2 * (N dot L)N - L |
|
Vector vecReflect; |
|
float nDotL = DotProduct( normal, pCalc->m_vecLookDir ); |
|
VectorMA( pCalc->m_vecLookDir, -2.0f * nDotL, normal, vecReflect ); |
|
vecReflect *= -1.0f; |
|
*/ |
|
|
|
int iFace = CalcFaceIndex( normal ); |
|
CalcColor( pCalc, iFace, normal, pCalc->m_pColor ); |
|
#if 0 |
|
pCalc->m_pColor[0] = normal[0] * 127 + 127; |
|
pCalc->m_pColor[1] = normal[1] * 127 + 127; |
|
pCalc->m_pColor[2] = normal[2] * 127 + 127; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Makes a single frame of spheremap |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ComputeSpheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir ) |
|
{ |
|
SphereCalc_t sphere; |
|
CalcInit( &sphere, m_nWidth, ppCubeFaces, lookDir ); |
|
int offset = 0; |
|
for ( int y = 0; y < m_nHeight; y++ ) |
|
{ |
|
for ( int x = 0; x < m_nWidth; x++ ) |
|
{ |
|
int r = 0, g = 0, b = 0, a = 0; |
|
float u = (float)x - m_nWidth * 0.5f; |
|
float v = m_nHeight * 0.5f - (float)y; |
|
|
|
CalcSphereColor( &sphere, u, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
CalcSphereColor( &sphere, u + 0.25, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
v += 0.25; |
|
CalcSphereColor( &sphere, u + 0.25, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
CalcSphereColor( &sphere, u, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
pSpheremap[ offset + 0 ] = r >> 2; |
|
pSpheremap[ offset + 1 ] = g >> 2; |
|
pSpheremap[ offset + 2 ] = b >> 2; |
|
pSpheremap[ offset + 3 ] = a >> 2; |
|
offset += 4; |
|
} |
|
} |
|
} |
|
|
|
void CVTFTexture::ComputeHemispheremapFrame( unsigned char **ppCubeFaces, unsigned char *pSpheremap, LookDir_t lookDir ) |
|
{ |
|
SphereCalc_t sphere; |
|
CalcInit( &sphere, m_nWidth, ppCubeFaces, lookDir ); |
|
int offset = 0; |
|
for ( int y = 0; y < m_nHeight; y++ ) |
|
{ |
|
for ( int x = 0; x < m_nWidth; x++ ) |
|
{ |
|
int r = 0, g = 0, b = 0, a = 0; |
|
float u = (float)x - m_nWidth * 0.5f; |
|
float v = m_nHeight * 0.5f - (float)y; |
|
|
|
CalcHemisphereColor( &sphere, u, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
CalcHemisphereColor( &sphere, u + 0.25, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
v += 0.25; |
|
CalcHemisphereColor( &sphere, u + 0.25, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
CalcHemisphereColor( &sphere, u, v ); |
|
r += sphere.m_pColor[0]; |
|
g += sphere.m_pColor[1]; |
|
b += sphere.m_pColor[2]; |
|
a += sphere.m_pColor[3]; |
|
|
|
pSpheremap[ offset + 0 ] = r >> 2; |
|
pSpheremap[ offset + 1 ] = g >> 2; |
|
pSpheremap[ offset + 2 ] = b >> 2; |
|
pSpheremap[ offset + 3 ] = a >> 2; |
|
offset += 4; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generate spheremap based on the current images (only works for cubemaps) |
|
// The look dir indicates the direction of the center of the sphere |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::GenerateSpheremap( LookDir_t lookDir ) |
|
{ |
|
if (!IsCubeMap()) |
|
return; |
|
|
|
// HDRFIXME: Need to re-enable this. |
|
// Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
// We'll be doing our work in IMAGE_FORMAT_RGBA8888 mode 'cause it's easier |
|
unsigned char *pCubeMaps[6]; |
|
|
|
// Allocate the bits for the spheremap |
|
Assert( m_nDepth == 1 ); |
|
int iMemRequired = ComputeFaceSize( 0, IMAGE_FORMAT_RGBA8888 ); |
|
unsigned char *pSphereMapBits = new unsigned char [ iMemRequired ]; |
|
|
|
// Generate a spheremap for each frame of the cubemap |
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
// Point to our own textures (highest mip level) |
|
for (int iFace = 0; iFace < 6; ++iFace) |
|
{ |
|
pCubeMaps[iFace] = ImageData( iFrame, iFace, 0 ); |
|
} |
|
|
|
// Compute the spheremap of the top LOD |
|
// HDRFIXME: Make this work? |
|
if( m_Format == IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
ComputeSpheremapFrame( pCubeMaps, pSphereMapBits, lookDir ); |
|
} |
|
|
|
// Compute the mip levels of the spheremap, converting from RGBA8888 to our format |
|
unsigned char *pFinalSphereMapBits = ImageData( iFrame, CUBEMAP_FACE_SPHEREMAP, 0 ); |
|
ImageLoader::GenerateMipmapLevels( pSphereMapBits, pFinalSphereMapBits, |
|
m_nWidth, m_nHeight, m_nDepth, m_Format, 2.2, 2.2, m_nMipCount ); |
|
} |
|
|
|
// Free memory |
|
delete [] pSphereMapBits; |
|
} |
|
|
|
void CVTFTexture::GenerateHemisphereMap( unsigned char *pSphereMapBitsRGBA, int targetWidth, |
|
int targetHeight, LookDir_t lookDir, int iFrame ) |
|
{ |
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
unsigned char *pCubeMaps[6]; |
|
|
|
// Point to our own textures (highest mip level) |
|
for (int iFace = 0; iFace < 6; ++iFace) |
|
{ |
|
pCubeMaps[iFace] = ImageData( iFrame, iFace, 0 ); |
|
} |
|
|
|
// Compute the spheremap of the top LOD |
|
ComputeHemispheremapFrame( pCubeMaps, pSphereMapBitsRGBA, lookDir ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Rotate the image depending on what iFace we've got... |
|
// We need to do this because we define the cube textures in a different |
|
// format from DX8. |
|
//----------------------------------------------------------------------------- |
|
static void FixCubeMapFacing( unsigned char* pImage, int cubeFaceID, int size, ImageFormat fmt ) |
|
{ |
|
int retVal; |
|
switch( cubeFaceID ) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: // +x |
|
retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt ); |
|
Assert( retVal ); |
|
retVal = ImageLoader::FlipImageVertically( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
|
|
case CUBEMAP_FACE_LEFT: // -x |
|
retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt ); |
|
Assert( retVal ); |
|
retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
|
|
case CUBEMAP_FACE_BACK: // +y |
|
retVal = ImageLoader::RotateImage180( pImage, pImage, size, fmt ); |
|
Assert( retVal ); |
|
retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
|
|
case CUBEMAP_FACE_FRONT: // -y |
|
retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
|
|
case CUBEMAP_FACE_UP: // +z |
|
retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt ); |
|
Assert( retVal ); |
|
retVal = ImageLoader::FlipImageVertically( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
|
|
case CUBEMAP_FACE_DOWN: // -z |
|
retVal = ImageLoader::FlipImageHorizontally( pImage, pImage, size, size, fmt ); |
|
Assert( retVal ); |
|
retVal = ImageLoader::RotateImageLeft( pImage, pImage, size, fmt ); |
|
Assert( retVal ); |
|
break; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Fixes the cubemap faces orientation from our standard to what the material system needs |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::FixCubemapFaceOrientation( ) |
|
{ |
|
if (!IsCubeMap()) |
|
return; |
|
|
|
Assert( !ImageLoader::IsCompressed( m_Format ) ); |
|
for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel) |
|
{ |
|
int iMipSize, iTemp, nDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &iMipSize, &iTemp, &nDepth ); |
|
Assert( (iMipSize == iTemp) && (nDepth == 1) ); |
|
|
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < 6; ++iFace) |
|
{ |
|
FixCubeMapFacing( ImageData( iFrame, iFace, iMipLevel ), iFace, iMipSize, m_Format ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CVTFTexture::NormalizeTopMipLevel() |
|
{ |
|
if( !( m_nFlags & TEXTUREFLAGS_NORMAL ) ) |
|
return; |
|
|
|
int nSrcWidth, nSrcHeight, nSrcDepth; |
|
int srcMipLevel = 0; |
|
ComputeMipLevelDimensions( srcMipLevel, &nSrcWidth, &nSrcHeight, &nSrcDepth ); |
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < m_nFaceCount; ++iFace) |
|
{ |
|
unsigned char *pSrcLevel = ImageData( iFrame, iFace, srcMipLevel ); |
|
ImageLoader::NormalizeNormalMapRGBA8888( pSrcLevel, nSrcWidth * nSrcHeight * nSrcDepth ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates mipmaps from the base mip levels |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::GenerateMipmaps() |
|
{ |
|
// Go ahead and generate mipmaps even if we don't want 'em in the vtf. |
|
// if( ( Flags() & ( TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD ) ) == ( TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD ) ) |
|
// { |
|
// return; |
|
// } |
|
|
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 || m_Format == IMAGE_FORMAT_RGB323232F || m_Format == IMAGE_FORMAT_RGBA32323232F ); |
|
|
|
// FIXME: Should we be doing anything special for normalmaps other than a final normalization pass? |
|
ImageLoader::ResampleInfo_t info; |
|
info.m_nSrcWidth = m_nWidth; |
|
info.m_nSrcHeight = m_nHeight; |
|
info.m_nSrcDepth = m_nDepth; |
|
info.m_flSrcGamma = 2.2f; |
|
info.m_flDestGamma = 2.2f; |
|
info.m_nFlags = 0; |
|
bool bNormalMap = ( Flags() & TEXTUREFLAGS_NORMAL ) || ( m_Options.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV ); |
|
bool bAlphaTest = ( ( m_Options.flags0 & VtfProcessingOptions::OPT_MIP_ALPHATEST ) != 0 ); |
|
|
|
if ( bAlphaTest ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_ALPHATEST; |
|
if ( m_flAlphaThreshhold >= 0 ) |
|
{ |
|
info.m_flAlphaThreshhold = m_flAlphaThreshhold; |
|
} |
|
if ( m_flAlphaHiFreqThreshhold >= 0 ) |
|
{ |
|
info.m_flAlphaHiFreqThreshhold = m_flAlphaHiFreqThreshhold; |
|
} |
|
} |
|
|
|
if ( m_Options.flags0 & VtfProcessingOptions::OPT_FILTER_NICE ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER; |
|
} |
|
|
|
if ( Flags() & TEXTUREFLAGS_CLAMPS ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPS; |
|
} |
|
|
|
if ( Flags() & TEXTUREFLAGS_CLAMPT ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPT; |
|
} |
|
|
|
if ( Flags() & TEXTUREFLAGS_CLAMPU ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_CLAMPU; |
|
} |
|
|
|
// Compute how many mips are above "visible mip0" |
|
int numMipsClampedLod = 0; |
|
if ( TextureLODControlSettings_t const *pLodSettings = ( TextureLODControlSettings_t const * ) GetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, NULL ) ) |
|
{ |
|
int iClampX = 1 << min( pLodSettings->m_ResolutionClampX, pLodSettings->m_ResolutionClampX_360 ); |
|
int iClampY = 1 << min( pLodSettings->m_ResolutionClampX, pLodSettings->m_ResolutionClampX_360 ); |
|
|
|
while ( iClampX < m_nWidth || iClampY < m_nHeight ) |
|
{ |
|
++ numMipsClampedLod; |
|
iClampX <<= 1; |
|
iClampY <<= 1; |
|
} |
|
} |
|
|
|
for ( int iMipLevel = 1; iMipLevel < m_nMipCount; ++iMipLevel ) |
|
{ |
|
ComputeMipLevelDimensions( iMipLevel, &info.m_nDestWidth, &info.m_nDestHeight, &info.m_nDestDepth ); |
|
|
|
if ( m_Options.flags0 & VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP ) |
|
{ |
|
for ( int ch = 0; ch < 3; ++ ch ) |
|
info.m_flColorScale[ch] = 1.0f / ( float )( 1 << iMipLevel ); |
|
} |
|
|
|
// don't use the 0th mip level since NICE filtering blows up! |
|
int nSrcMipLevel = iMipLevel - 4; |
|
if ( nSrcMipLevel < 0 ) |
|
nSrcMipLevel = 0; |
|
|
|
// Decay options |
|
bool bMipBlendActive = false; |
|
char chChannels[4] = { 'R', 'G', 'B', 'A' }; |
|
for ( int ch = 0; ch < 4; ++ ch ) |
|
{ |
|
int iLastNonDecayMip = numMipsClampedLod + int( m_Options.numNotDecayMips[ch] ); |
|
if ( iLastNonDecayMip > m_nMipCount ) |
|
iLastNonDecayMip = m_nMipCount - 1; |
|
int numDecayMips = m_nMipCount - iLastNonDecayMip - 1; |
|
if ( numDecayMips < 1 ) |
|
numDecayMips = 1; |
|
|
|
// Decay is only active starting from numDecayMips |
|
if ( !( ( ( iMipLevel == m_nMipCount - 1 ) || ( iMipLevel > iLastNonDecayMip ) ) && // last 1x1 mip or past clamped and skipped |
|
( m_Options.flags0 & ( VtfProcessingOptions::OPT_DECAY_R << ch ) ) ) ) // the channel has decay |
|
continue; |
|
|
|
// Color goal |
|
info.m_flColorGoal[ch] = m_Options.clrDecayGoal[ch]; |
|
|
|
// Color scale |
|
if ( iMipLevel == m_nMipCount - 1 ) |
|
{ |
|
info.m_flColorScale[ch] = 0.0f; |
|
} |
|
else if ( m_Options.flags0 & ( VtfProcessingOptions::OPT_DECAY_EXP_R << ch ) ) |
|
{ |
|
info.m_flColorScale[ch] = pow( m_Options.fDecayExponentBase[ch], iMipLevel - iLastNonDecayMip ); |
|
} |
|
else |
|
{ |
|
info.m_flColorScale[ch] = 1.0f - float( iMipLevel - iLastNonDecayMip ) / float( numDecayMips ); |
|
} |
|
|
|
if ( !bMipBlendActive ) |
|
{ |
|
bMipBlendActive = true; |
|
printf( "Blending mip%d %dx%d to", iMipLevel, info.m_nDestWidth, info.m_nDestHeight ); |
|
} |
|
|
|
printf( " %c=%d ~%d%%", chChannels[ch], m_Options.clrDecayGoal[ch], int( (1.f - info.m_flColorScale[ch]) * 100.0f + 0.5f ) ); |
|
} |
|
if ( bMipBlendActive ) |
|
printf( "\n" ); |
|
|
|
if ( bNormalMap ) |
|
{ |
|
info.m_nFlags |= ImageLoader::RESAMPLE_NORMALMAP; |
|
// Normal maps xyz decays to 127.f |
|
for ( int ch = 0; ch < 3; ++ ch ) |
|
info.m_flColorGoal[ch] = 127.0f; |
|
} |
|
|
|
for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) |
|
{ |
|
for ( int iFace = 0; iFace < m_nFaceCount; ++iFace ) |
|
{ |
|
unsigned char *pSrcLevel = ImageData( iFrame, iFace, nSrcMipLevel ); |
|
unsigned char *pDstLevel = ImageData( iFrame, iFace, iMipLevel ); |
|
|
|
info.m_pSrc = pSrcLevel; |
|
info.m_pDest = pDstLevel; |
|
ComputeMipLevelDimensions( nSrcMipLevel, &info.m_nSrcWidth, &info.m_nSrcHeight, &info.m_nSrcDepth ); |
|
if( m_Format == IMAGE_FORMAT_RGBA32323232F ) |
|
{ |
|
ImageLoader::ResampleRGBA32323232F( info ); |
|
} |
|
else if( m_Format == IMAGE_FORMAT_RGB323232F ) |
|
{ |
|
ImageLoader::ResampleRGB323232F( info ); |
|
} |
|
else |
|
{ |
|
ImageLoader::ResampleRGBA8888( info ); |
|
} |
|
if ( Flags() & TEXTUREFLAGS_NORMAL ) |
|
{ |
|
ImageLoader::NormalizeNormalMapRGBA8888( pDstLevel, info.m_nDestWidth * info.m_nDestHeight * info.m_nDestDepth ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CVTFTexture::PutOneOverMipLevelInAlpha() |
|
{ |
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
int size = nMipWidth * nMipHeight * nMipDepth; |
|
unsigned char ooMipLevel = ( unsigned char )( 255.0f * ( 1.0f / ( float )( 1 << iMipLevel ) ) ); |
|
|
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for (int iFace = 0; iFace < m_nFaceCount; ++iFace) |
|
{ |
|
unsigned char *pDstLevel = ImageData( iFrame, iFace, iMipLevel ); |
|
unsigned char *pDst; |
|
for( pDst = pDstLevel; pDst < pDstLevel + size * 4; pDst += 4 ) |
|
{ |
|
pDst[3] = ooMipLevel; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the reflectivity |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ComputeReflectivity( ) |
|
{ |
|
// HDRFIXME: fix this when we ahve a new intermediate format |
|
if( m_Format != IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
m_vecReflectivity.Init( 0.2f, 0.2f, 0.2f ); |
|
return; |
|
} |
|
|
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
int divisor = 0; |
|
m_vecReflectivity.Init( 0.0f, 0.0f, 0.0f ); |
|
for( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) |
|
{ |
|
for( int iFace = 0; iFace < m_nFaceCount; ++iFace ) |
|
{ |
|
Vector vecFaceReflect; |
|
unsigned char* pSrc = ImageData( iFrame, iFace, 0 ); |
|
int nNumPixels = m_nWidth * m_nHeight * m_nDepth; |
|
|
|
VectorClear( vecFaceReflect ); |
|
for (int i = 0; i < nNumPixels; ++i, pSrc += 4 ) |
|
{ |
|
vecFaceReflect[0] += TextureToLinear( pSrc[0] ); |
|
vecFaceReflect[1] += TextureToLinear( pSrc[1] ); |
|
vecFaceReflect[2] += TextureToLinear( pSrc[2] ); |
|
} |
|
|
|
vecFaceReflect /= nNumPixels; |
|
|
|
m_vecReflectivity += vecFaceReflect; |
|
++divisor; |
|
} |
|
} |
|
m_vecReflectivity /= divisor; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the alpha flags |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::ComputeAlphaFlags() |
|
{ |
|
// HDRFIXME: hack hack hack |
|
if( m_Format != IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
m_nFlags &= ~( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ); |
|
m_Options.flags0 &= ~( VtfProcessingOptions::OPT_MIP_ALPHATEST ); |
|
return; |
|
} |
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
m_nFlags &= ~(TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA); |
|
|
|
if( m_Options.flags0 & VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP ) |
|
{ |
|
m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA; |
|
return; |
|
} |
|
|
|
for( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) |
|
{ |
|
for( int iFace = 0; iFace < m_nFaceCount; ++iFace ) |
|
{ |
|
for( int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel ) |
|
{ |
|
// If we're all 0 or all 255, assume it's opaque |
|
bool bHasZero = false; |
|
bool bHas255 = false; |
|
|
|
unsigned char* pSrcBits = ImageData( iFrame, iFace, iMipLevel ); |
|
|
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
int nNumPixels = nMipWidth * nMipHeight * nMipDepth; |
|
|
|
while ( --nNumPixels >= 0 ) |
|
{ |
|
if ( pSrcBits[3] == 0 ) |
|
{ |
|
bHasZero = true; |
|
} |
|
else if ( pSrcBits[3] == 255 ) |
|
{ |
|
bHas255 = true; |
|
} |
|
else |
|
{ |
|
// Have grey at all? 8 bit alpha baby |
|
m_nFlags &= ~TEXTUREFLAGS_ONEBITALPHA; |
|
m_nFlags |= TEXTUREFLAGS_EIGHTBITALPHA; |
|
return; |
|
} |
|
|
|
pSrcBits += 4; |
|
} |
|
|
|
// If we have both 0 at 255, we're at least one-bit alpha |
|
if ( bHasZero && bHas255 ) |
|
{ |
|
m_nFlags |= TEXTUREFLAGS_ONEBITALPHA; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Gets the texture all internally consistent assuming you've loaded |
|
// mip 0 of all faces of all frames |
|
//----------------------------------------------------------------------------- |
|
void CVTFTexture::PostProcess( bool bGenerateSpheremap, LookDir_t lookDir, bool bAllowFixCubemapOrientation ) |
|
{ |
|
// HDRFIXME: Make sure that all of the below functions check for the proper formats if we get rid of this assert. |
|
// Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
|
|
// Set up the cube map faces |
|
if (IsCubeMap()) |
|
{ |
|
// Rotate the cubemaps so they're appropriate for the material system |
|
if ( bAllowFixCubemapOrientation ) |
|
FixCubemapFaceOrientation(); |
|
|
|
// FIXME: We could theoretically not compute spheremap mip levels |
|
// in generate spheremaps; should we? The trick is when external |
|
// clients can be expected to call it |
|
|
|
// Compute the spheremap fallback for cubemaps if we weren't able to load up one... |
|
if (bGenerateSpheremap) |
|
GenerateSpheremap(lookDir); |
|
} |
|
|
|
// Normalize the top mip level if necessary. |
|
NormalizeTopMipLevel(); |
|
|
|
// Generate mipmap levels |
|
GenerateMipmaps(); |
|
|
|
if( m_Options.flags0 & VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP ) |
|
{ |
|
PutOneOverMipLevelInAlpha(); |
|
} |
|
|
|
// Compute reflectivity |
|
ComputeReflectivity(); |
|
|
|
// Are we 8-bit or 1-bit alpha? |
|
// NOTE: We have to do this *after* computing the spheremap fallback for |
|
// cubemaps or it'll throw the flags off |
|
ComputeAlphaFlags(); |
|
} |
|
|
|
void CVTFTexture::SetPostProcessingSettings( VtfProcessingOptions const *pOptions ) |
|
{ |
|
memset( &m_Options, 0, sizeof( m_Options ) ); |
|
memcpy( &m_Options, pOptions, min( (uint32)sizeof( m_Options ), pOptions->cbSize ) ); |
|
m_Options.cbSize = sizeof( m_Options ); |
|
|
|
// Optionally perform the fixups |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Generate the low-res image bits |
|
//----------------------------------------------------------------------------- |
|
bool CVTFTexture::ConstructLowResImage() |
|
{ |
|
// HDRFIXME: hack hack hack |
|
if( m_Format != IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
return true; |
|
} |
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
Assert( m_pLowResImageData ); |
|
|
|
CUtlMemory<unsigned char> lowResSizeImage; |
|
lowResSizeImage.EnsureCapacity( m_nLowResImageWidth * m_nLowResImageHeight * 4 ); |
|
|
|
ImageLoader::ResampleInfo_t info; |
|
info.m_pSrc = ImageData(0, 0, 0); |
|
info.m_pDest = lowResSizeImage.Base(); |
|
info.m_nSrcWidth = m_nWidth; |
|
info.m_nSrcHeight = m_nHeight; |
|
info.m_nDestWidth = m_nLowResImageWidth; |
|
info.m_nDestHeight = m_nLowResImageHeight; |
|
info.m_flSrcGamma = 2.2f; |
|
info.m_flDestGamma = 2.2f; |
|
info.m_nFlags = ImageLoader::RESAMPLE_NICE_FILTER; |
|
|
|
if( !ImageLoader::ResampleRGBA8888( info ) ) |
|
return false; |
|
|
|
// convert to the low-res size version with the correct image format |
|
unsigned char *tmpImage = lowResSizeImage.Base(); |
|
return ImageLoader::ConvertImageFormat( tmpImage, IMAGE_FORMAT_RGBA8888, |
|
m_pLowResImageData, m_LowResImageFormat, m_nLowResImageWidth, m_nLowResImageHeight ); |
|
} |
|
|
|
// ----------------------------------------------------------------------------- |
|
// Cubemap edge-filtering functions. |
|
// ----------------------------------------------------------------------------- |
|
void CVTFTexture::SetupFaceVert( int iMipLevel, int iVert, CEdgePos &out ) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
|
|
out.x = out.y = 0; |
|
if ( iVert == 0 || iVert == 3 ) |
|
{ |
|
out.y = nMipHeight - 1; |
|
} |
|
|
|
if ( iVert == 2 || iVert == 3 ) |
|
{ |
|
out.x = nMipWidth - 1; |
|
} |
|
} |
|
|
|
void CVTFTexture::SetupEdgeIncrement( CEdgePos &start, CEdgePos &end, CEdgePos &inc ) |
|
{ |
|
inc.x = inc.y = 0; |
|
if ( start.x != end.x ) |
|
{ |
|
Assert( start.y == end.y ); |
|
inc.x = (start.x < end.x) ? 1 : -1; |
|
} |
|
else if ( start.y != end.y ) |
|
{ |
|
Assert( start.x == end.x ); |
|
inc.y = (start.y < end.y) ? 1 : -1; |
|
} |
|
else |
|
{ |
|
Assert( false ); |
|
} |
|
} |
|
|
|
void CVTFTexture::SetupTextureEdgeIncrements( |
|
int iMipLevel, |
|
int iFace1Edge, |
|
int iFace2Edge, |
|
bool bFlipFace2Edge, |
|
CEdgeIncrements *incs ) |
|
{ |
|
// Figure out the coordinates of the verts we're blending. |
|
SetupFaceVert( iMipLevel, iFace1Edge, incs->iFace1Start ); |
|
SetupFaceVert( iMipLevel, (iFace1Edge+1)%4, incs->iFace1End ); |
|
|
|
if ( bFlipFace2Edge ) |
|
{ |
|
SetupFaceVert( iMipLevel, (iFace2Edge+1)%4, incs->iFace2Start ); |
|
SetupFaceVert( iMipLevel, iFace2Edge, incs->iFace2End ); |
|
} |
|
else |
|
{ |
|
SetupFaceVert( iMipLevel, iFace2Edge, incs->iFace2Start ); |
|
SetupFaceVert( iMipLevel, (iFace2Edge+1)%4, incs->iFace2End ); |
|
} |
|
|
|
// Figure out the increments from start to end. |
|
SetupEdgeIncrement( incs->iFace1Start, incs->iFace1End, incs->iFace1Inc ); |
|
SetupEdgeIncrement( incs->iFace2Start, incs->iFace2End, incs->iFace2Inc ); |
|
} |
|
|
|
void BlendTexels( unsigned char **texels, int nTexels ) |
|
{ |
|
int sum[4] = { 0, 0, 0, 0 }; |
|
int i; |
|
for ( i=0; i < nTexels; i++ ) |
|
{ |
|
sum[0] += texels[i][0]; |
|
sum[1] += texels[i][1]; |
|
sum[2] += texels[i][2]; |
|
sum[3] += texels[i][3]; |
|
} |
|
for ( i=0; i < nTexels; i++ ) |
|
{ |
|
texels[i][0] = (unsigned char)( sum[0] / nTexels ); |
|
texels[i][1] = (unsigned char)( sum[1] / nTexels ); |
|
texels[i][2] = (unsigned char)( sum[2] / nTexels ); |
|
texels[i][3] = (unsigned char)( sum[3] / nTexels ); |
|
} |
|
} |
|
|
|
void CVTFTexture::BlendCubeMapFaceEdges( |
|
int iFrame, |
|
int iMipLevel, |
|
const CEdgeMatch *pMatch ) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
Assert( nMipDepth == 1 ); |
|
if ( nMipWidth <= 1 || nMipHeight <= 1 ) |
|
return; |
|
|
|
unsigned char *pFace1Data = ImageData( iFrame, pMatch->m_iFaces[0], iMipLevel ); |
|
unsigned char *pFace2Data = ImageData( iFrame, pMatch->m_iFaces[1], iMipLevel ); |
|
|
|
CEdgeIncrements incs; |
|
SetupTextureEdgeIncrements( iMipLevel, pMatch->m_iEdges[0], pMatch->m_iEdges[1], pMatch->m_bFlipFace2Edge, &incs ); |
|
|
|
// Do all pixels but the first and the last one (those will be handled when blending corners). |
|
CEdgePos iFace1Cur = incs.iFace1Start + incs.iFace1Inc; |
|
CEdgePos iFace2Cur = incs.iFace2Start + incs.iFace2Inc; |
|
|
|
if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 ) |
|
{ |
|
if ( iFace1Cur != incs.iFace1End ) |
|
{ |
|
while ( iFace1Cur != incs.iFace1End ) |
|
{ |
|
// Copy the palette index from image 1 to image 2. |
|
S3PaletteIndex paletteIndex = S3TC_GetPaletteIndex( pFace1Data, m_Format, nMipWidth, iFace1Cur.x, iFace1Cur.y ); |
|
S3TC_SetPaletteIndex( pFace2Data, m_Format, nMipWidth, iFace2Cur.x, iFace2Cur.y, paletteIndex ); |
|
|
|
iFace1Cur += incs.iFace1Inc; |
|
iFace2Cur += incs.iFace2Inc; |
|
} |
|
} |
|
} |
|
else if ( m_Format == IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
if ( iFace1Cur != incs.iFace1End ) |
|
{ |
|
while ( iFace1Cur != incs.iFace1End ) |
|
{ |
|
// Now we know the 2 pixels. Average them and copy the averaged value to both pixels. |
|
unsigned char *texels[2] = |
|
{ |
|
pFace1Data + ((iFace1Cur.y * nMipWidth) + iFace1Cur.x) * 4, |
|
pFace2Data + ((iFace2Cur.y * nMipWidth) + iFace2Cur.x) * 4 |
|
}; |
|
|
|
BlendTexels( texels, 2 ); |
|
|
|
iFace1Cur += incs.iFace1Inc; |
|
iFace2Cur += incs.iFace2Inc; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
Error( "BlendCubeMapFaceEdges: unsupported image format (%d)", (int)m_Format ); |
|
} |
|
} |
|
|
|
void CVTFTexture::BlendCubeMapFaceCorners( |
|
int iFrame, |
|
int iMipLevel, |
|
const CCornerMatch *pMatch ) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
Assert( nMipDepth == 1 ); |
|
|
|
// Setup the coordinates of each texel. |
|
CEdgePos texelPos[3]; |
|
unsigned char *pImageData[3]; |
|
int iEdge; |
|
for ( iEdge=0; iEdge < 3; iEdge++ ) |
|
{ |
|
SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], texelPos[iEdge] ); |
|
pImageData[iEdge] = ImageData( iFrame, pMatch->m_iFaces[iEdge], iMipLevel ); |
|
} |
|
|
|
if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 ) |
|
{ |
|
if ( nMipWidth < 4 || nMipHeight < 4 ) |
|
return; |
|
|
|
// Copy the first palette index to the other blocks. |
|
S3PaletteIndex paletteIndex = S3TC_GetPaletteIndex( pImageData[0], m_Format, nMipWidth, texelPos[0].x, texelPos[0].y ); |
|
S3TC_SetPaletteIndex( pImageData[1], m_Format, nMipWidth, texelPos[1].x, texelPos[1].y, paletteIndex ); |
|
S3TC_SetPaletteIndex( pImageData[2], m_Format, nMipWidth, texelPos[2].x, texelPos[2].y, paletteIndex ); |
|
} |
|
else if ( m_Format == IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
// Setup pointers to the 3 corner texels. |
|
unsigned char *texels[3]; |
|
for ( iEdge=0; iEdge < 3; iEdge++ ) |
|
{ |
|
CEdgePos facePos; |
|
SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], facePos ); |
|
|
|
texels[iEdge] = pImageData[iEdge]; |
|
texels[iEdge] += (facePos.y * nMipWidth + facePos.x) * 4; |
|
} |
|
|
|
// Now blend the texels. |
|
BlendTexels( texels, 3 ); |
|
} |
|
else |
|
{ |
|
Assert( false ); |
|
} |
|
} |
|
|
|
void CVTFTexture::BuildCubeMapMatchLists( |
|
CEdgeMatch edgeMatches[NUM_EDGE_MATCHES], |
|
CCornerMatch cornerMatches[NUM_CORNER_MATCHES], |
|
bool bSkybox ) |
|
{ |
|
|
|
int **faceVertsList = bSkybox ? g_skybox_FaceVerts : g_FaceVerts; |
|
|
|
// For each face, look for matching edges on other faces. |
|
int nTotalEdgesMatched = 0; |
|
for ( int iFace = 0; iFace < 6; iFace++ ) |
|
{ |
|
for ( int iEdge=0; iEdge < 4; iEdge++ ) |
|
{ |
|
int i1 = faceVertsList[iFace][iEdge]; |
|
int i2 = faceVertsList[iFace][(iEdge+1)%4]; |
|
|
|
// Only look for faces with indices < what we have so we don't do each edge twice. |
|
for ( int iOtherFace=0; iOtherFace < iFace; iOtherFace++ ) |
|
{ |
|
for ( int iOtherEdge=0; iOtherEdge < 4; iOtherEdge++ ) |
|
{ |
|
int o1 = faceVertsList[iOtherFace][iOtherEdge]; |
|
int o2 = faceVertsList[iOtherFace][(iOtherEdge+1)%4]; |
|
|
|
if ( (i1 == o1 && i2 == o2) || (i2 == o1 && i1 == o2) ) |
|
{ |
|
CEdgeMatch *pMatch = &edgeMatches[nTotalEdgesMatched]; |
|
|
|
pMatch->m_iFaces[0] = iFace; |
|
pMatch->m_iEdges[0] = iEdge; |
|
|
|
pMatch->m_iFaces[1] = iOtherFace; |
|
pMatch->m_iEdges[1] = iOtherEdge; |
|
|
|
pMatch->m_iCubeVerts[0] = o1; |
|
pMatch->m_iCubeVerts[1] = o2; |
|
|
|
pMatch->m_bFlipFace2Edge = i1 != o1; |
|
|
|
++nTotalEdgesMatched; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
Assert( nTotalEdgesMatched == 12 ); |
|
|
|
// For each corner vert, find the 3 edges touching it. |
|
for ( int iVert=0; iVert < NUM_CORNER_MATCHES; iVert++ ) |
|
{ |
|
int iTouchingFace = 0; |
|
|
|
for ( int iFace=0; iFace < 6; iFace++ ) |
|
{ |
|
for ( int iFaceVert=0; iFaceVert < 4; iFaceVert++ ) |
|
{ |
|
if ( faceVertsList[iFace][iFaceVert] == iVert ) |
|
{ |
|
cornerMatches[iVert].m_iFaces[iTouchingFace] = iFace; |
|
cornerMatches[iVert].m_iFaceEdges[iTouchingFace] = iFaceVert; |
|
++iTouchingFace; |
|
} |
|
} |
|
} |
|
Assert( iTouchingFace == 3 ); |
|
} |
|
} |
|
|
|
void CVTFTexture::BlendCubeMapEdgePalettes( |
|
int iFrame, |
|
int iMipLevel, |
|
const CEdgeMatch *pMatch ) |
|
{ |
|
Assert( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 ); |
|
|
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
Assert( nMipDepth == 1 ); |
|
if ( nMipWidth <= 8 || nMipHeight <= 8 ) |
|
return; |
|
|
|
unsigned char *pFace1Data = ImageData( iFrame, pMatch->m_iFaces[0], iMipLevel ); |
|
unsigned char *pFace2Data = ImageData( iFrame, pMatch->m_iFaces[1], iMipLevel ); |
|
S3RGBA *pFace1Original = &m_OriginalData[ GetImageOffset( iFrame, pMatch->m_iFaces[0], iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ]; |
|
S3RGBA *pFace2Original = &m_OriginalData[ GetImageOffset( iFrame, pMatch->m_iFaces[1], iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ]; |
|
|
|
CEdgeIncrements incs; |
|
SetupTextureEdgeIncrements( iMipLevel, pMatch->m_iEdges[0], pMatch->m_iEdges[1], pMatch->m_bFlipFace2Edge, &incs ); |
|
|
|
// Divide the coordinates by 4 since we're dealing with S3 blocks here. |
|
incs.iFace1Start /= 4; incs.iFace1End /= 4; incs.iFace2Start /= 4; incs.iFace2End /= 4; |
|
|
|
// Now walk along the edges, blending the edge pixels. |
|
CEdgePos iFace1Cur = incs.iFace1Start + incs.iFace1Inc; |
|
CEdgePos iFace2Cur = incs.iFace2Start + incs.iFace2Inc; |
|
while ( iFace1Cur != incs.iFace1End ) // We intentionally want to not process the last block here.. |
|
{ |
|
// Merge the palette of these two blocks. |
|
char *blocks[2] = |
|
{ |
|
S3TC_GetBlock( pFace1Data, m_Format, nMipWidth>>2, iFace1Cur.x, iFace1Cur.y ), |
|
S3TC_GetBlock( pFace2Data, m_Format, nMipWidth>>2, iFace2Cur.x, iFace2Cur.y ) |
|
}; |
|
|
|
S3RGBA *originals[2] = |
|
{ |
|
&pFace1Original[(iFace1Cur.y * 4 * nMipWidth) + iFace1Cur.x * 4], |
|
&pFace2Original[(iFace2Cur.y * 4 * nMipWidth) + iFace2Cur.x * 4] |
|
}; |
|
|
|
S3TC_MergeBlocks( |
|
blocks, |
|
originals, |
|
2, |
|
nMipWidth*4, |
|
m_Format ); |
|
|
|
iFace1Cur += incs.iFace1Inc; |
|
iFace2Cur += incs.iFace2Inc; |
|
} |
|
} |
|
|
|
void CVTFTexture::BlendCubeMapCornerPalettes( |
|
int iFrame, |
|
int iMipLevel, |
|
const CCornerMatch *pMatch ) |
|
{ |
|
int nMipWidth, nMipHeight, nMipDepth; |
|
ComputeMipLevelDimensions( iMipLevel, &nMipWidth, &nMipHeight, &nMipDepth ); |
|
Assert( nMipDepth == 1 ); |
|
if ( nMipWidth < 4 || nMipHeight < 4 ) |
|
return; |
|
|
|
// Now setup an S3TC block pointer for each of the corner blocks on each face. |
|
char *blocks[3]; |
|
S3RGBA *originals[3]; |
|
|
|
for ( int iEdge=0; iEdge < 3; iEdge++ ) |
|
{ |
|
CEdgePos facePos; |
|
SetupFaceVert( iMipLevel, pMatch->m_iFaceEdges[iEdge], facePos ); |
|
facePos /= 4; // To get the S3 block index. |
|
|
|
int iFaceIndex = pMatch->m_iFaces[iEdge]; |
|
unsigned char *pFaceData = ImageData( iFrame, iFaceIndex, iMipLevel ); |
|
S3RGBA *pFaceOriginal = &m_OriginalData[ GetImageOffset( iFrame, iFaceIndex, iMipLevel, IMAGE_FORMAT_RGBA8888 ) / 4 ]; |
|
|
|
blocks[iEdge] = S3TC_GetBlock( pFaceData, m_Format, nMipWidth>>2, facePos.x, facePos.y ); |
|
originals[iEdge] = &pFaceOriginal[ (facePos.y * 4 * nMipWidth) + facePos.x * 4 ]; |
|
} |
|
|
|
S3TC_MergeBlocks( |
|
blocks, |
|
originals, |
|
3, |
|
nMipWidth*4, |
|
m_Format ); |
|
} |
|
|
|
void CVTFTexture::MatchCubeMapS3TCPalettes( |
|
CEdgeMatch edgeMatches[NUM_EDGE_MATCHES], |
|
CCornerMatch cornerMatches[NUM_CORNER_MATCHES] |
|
) |
|
{ |
|
for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel) |
|
{ |
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
// First, match all the edge palettes (this part skips the first and last 4 texels |
|
// along the edge since those S3 blocks are handled in the corner case). |
|
for ( int iEdgeMatch=0; iEdgeMatch < NUM_EDGE_MATCHES; iEdgeMatch++ ) |
|
{ |
|
BlendCubeMapEdgePalettes( |
|
iFrame, |
|
iMipLevel, |
|
&edgeMatches[iEdgeMatch] ); |
|
} |
|
|
|
for ( int iCornerMatch=0; iCornerMatch < NUM_CORNER_MATCHES; iCornerMatch++ ) |
|
{ |
|
BlendCubeMapCornerPalettes( |
|
iFrame, |
|
iMipLevel, |
|
&cornerMatches[iCornerMatch] ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CVTFTexture::MatchCubeMapBorders( int iStage, ImageFormat finalFormat, bool bSkybox ) |
|
{ |
|
// HDRFIXME: hack hack hack |
|
if( m_Format != IMAGE_FORMAT_RGBA8888 ) |
|
{ |
|
return; |
|
} |
|
if ( !IsCubeMap() ) |
|
return; |
|
|
|
Assert( IsCubeMap() ); |
|
Assert( m_nFaceCount >= 6 ); |
|
|
|
if ( iStage == 1 ) |
|
{ |
|
// Stage 1 is while the image is still RGBA8888. If we're not going to S3 compress the image, |
|
// then it is easiest to match the borders now. |
|
Assert( m_Format == IMAGE_FORMAT_RGBA8888 ); |
|
if ( finalFormat == IMAGE_FORMAT_DXT1 || finalFormat == IMAGE_FORMAT_DXT5 ) |
|
{ |
|
// If we're going to S3 compress the image eventually, then store off the original version |
|
// because we can use that while matching the S3 compressed edges (we have to do some tricky |
|
// repalettizing). |
|
int nTotalBytes = ComputeTotalSize(); |
|
m_OriginalData.SetSize( nTotalBytes / 4 ); |
|
memcpy( m_OriginalData.Base(), ImageData(), nTotalBytes ); |
|
|
|
// Swap R and B in these because IMAGE_FORMAT_RGBA8888 is swapped from the way S3RGBAs are. |
|
for ( int i=0; i < nTotalBytes/4; i++ ) |
|
V_swap( m_OriginalData[i].r, m_OriginalData[i].b ); |
|
|
|
return; |
|
} |
|
else |
|
{ |
|
// Drop down below and do the edge matching. |
|
} |
|
} |
|
else |
|
{ |
|
if ( finalFormat == IMAGE_FORMAT_DXT1 || finalFormat == IMAGE_FORMAT_DXT5 ) |
|
{ |
|
Assert( m_Format == finalFormat ); |
|
} |
|
else |
|
{ |
|
// If we're not winding up S3 compressed, then we already fixed the cubemap borders. |
|
return; |
|
} |
|
} |
|
|
|
// Figure out |
|
CEdgeMatch edgeMatches[NUM_EDGE_MATCHES]; |
|
CCornerMatch cornerMatches[NUM_CORNER_MATCHES]; |
|
|
|
BuildCubeMapMatchLists( edgeMatches, cornerMatches, bSkybox ); |
|
|
|
// If we're S3 compressed, then during the first pass, we need to match the palettes of all |
|
// bordering S3 blocks. |
|
if ( m_Format == IMAGE_FORMAT_DXT1 || m_Format == IMAGE_FORMAT_DXT5 ) |
|
{ |
|
MatchCubeMapS3TCPalettes( edgeMatches, cornerMatches ); |
|
} |
|
|
|
for (int iMipLevel = 0; iMipLevel < m_nMipCount; ++iMipLevel) |
|
{ |
|
for (int iFrame = 0; iFrame < m_nFrameCount; ++iFrame) |
|
{ |
|
for ( int iEdgeMatch=0; iEdgeMatch < NUM_EDGE_MATCHES; iEdgeMatch++ ) |
|
{ |
|
BlendCubeMapFaceEdges( |
|
iFrame, |
|
iMipLevel, |
|
&edgeMatches[iEdgeMatch] ); |
|
} |
|
|
|
for ( int iCornerMatch=0; iCornerMatch < NUM_CORNER_MATCHES; iCornerMatch++ ) |
|
{ |
|
BlendCubeMapFaceCorners( |
|
iFrame, |
|
iMipLevel, |
|
&cornerMatches[iCornerMatch] ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
/* |
|
|
|
Test code used to draw the cubemap into a scratchpad file. Useful for debugging, or at least |
|
it was once. |
|
|
|
IScratchPad3D *pPad = ScratchPad3D_Create(); |
|
|
|
int nMipWidth, nMipHeight; |
|
ComputeMipLevelDimensions( 0, &nMipWidth, &nMipHeight ); |
|
|
|
CUtlVector<unsigned char> data; |
|
data.SetSize( nMipWidth*nMipHeight ); |
|
|
|
float cubeSize = 200; |
|
Vector vertPositions[8] = |
|
{ |
|
Vector( 0, cubeSize, 0 ), |
|
Vector( 0, cubeSize, cubeSize ), |
|
Vector( cubeSize, 0, 0 ), |
|
Vector( cubeSize, 0, cubeSize ), |
|
|
|
Vector( 0, 0, 0 ), |
|
Vector( 0, 0, cubeSize ), |
|
Vector( cubeSize, cubeSize, 0 ), |
|
Vector( cubeSize, cubeSize, cubeSize ) |
|
}; |
|
char *faceNames[6] = { "right","left","back","front","up","down" }; |
|
|
|
for ( int iVert=0; iVert < 8; iVert++ ) |
|
{ |
|
char str[512]; |
|
Q_snprintf( str, sizeof( str ), "%d", iVert ); |
|
CTextParams params; |
|
params.m_flLetterWidth = 20; |
|
params.m_vPos = vertPositions[iVert]; |
|
pPad->DrawText( str, params ); |
|
} |
|
|
|
for ( int iFace=0; iFace < 6; iFace++ ) |
|
{ |
|
unsigned char *pFace1Data = ImageData( 0, iFace, 0 ); |
|
for ( int y=0; y < nMipHeight; y++ ) |
|
{ |
|
for( int x=0; x < nMipWidth; x++ ) |
|
{ |
|
S3PaletteIndex index = S3TC_GetPaletteIndex( |
|
pFace1Data, |
|
m_Format, |
|
nMipWidth, |
|
x, y ); |
|
|
|
const char *pBlock = S3TC_GetBlock( pFace1Data, m_Format, nMipWidth/4, x/4, y/4 ); |
|
unsigned char a0 = pBlock[0]; |
|
unsigned char a1 = pBlock[1]; |
|
|
|
if ( index.m_AlphaIndex == 0 ) |
|
{ |
|
data[y*nMipWidth+x] = a0; |
|
} |
|
else if ( index.m_AlphaIndex == 1 ) |
|
{ |
|
data[y*nMipWidth+x] = a1; |
|
} |
|
else if ( a0 > a1 ) |
|
{ |
|
data[y*nMipWidth+x] = ((8-(int)index.m_AlphaIndex)*a0 + ((int)index.m_AlphaIndex-1)*a1) / 7; |
|
} |
|
else |
|
{ |
|
if ( index.m_AlphaIndex == 6 ) |
|
data[y*nMipWidth+x] = 0; |
|
else if ( index.m_AlphaIndex == 7 ) |
|
data[y*nMipWidth+x] = 255; |
|
else |
|
data[y*nMipWidth+x] = ((6-(int)index.m_AlphaIndex)*a0 + ((int)index.m_AlphaIndex-1)*a1) / 5; |
|
} |
|
} |
|
} |
|
|
|
Vector vCorners[4]; |
|
for ( int iCorner=0; iCorner < 4; iCorner++ ) |
|
vCorners[iCorner] = vertPositions[g_FaceVerts[iFace][iCorner]]; |
|
|
|
pPad->DrawImageBW( data.Base(), nMipWidth, nMipHeight, nMipWidth, false, true, vCorners ); |
|
|
|
CTextParams params; |
|
params.m_vPos = (vCorners[0] + vCorners[1] + vCorners[2] + vCorners[3]) / 4; |
|
params.m_bCentered = true; |
|
params.m_vColor.Init( 1, 0, 0 ); |
|
params.m_bTwoSided = true; |
|
params.m_flLetterWidth = 10; |
|
|
|
Vector vNormal = (vCorners[1] - vCorners[0]).Cross( vCorners[2] - vCorners[1] ); |
|
VectorNormalize( vNormal ); |
|
params.m_vPos += vNormal*5; |
|
VectorAngles( vNormal, params.m_vAngles ); |
|
|
|
pPad->DrawText( faceNames[iFace], params ); |
|
|
|
pPad->Flush(); |
|
} |
|
*/ |
|
|
|
|