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.
2834 lines
77 KiB
2834 lines
77 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <malloc.h> |
|
#include <string.h> |
|
#include "tier1/strtools.h" |
|
#include <sys/stat.h> |
|
#include "bitmap/bitmap.h" |
|
#include "bitmap/tgaloader.h" |
|
#include "bitmap/psd.h" |
|
#include "bitmap/float_bm.h" |
|
#include "bitmap/imageformat.h" |
|
#include "mathlib/mathlib.h" |
|
|
|
#ifdef POSIX |
|
#include <sys/stat.h> |
|
#define _stat stat |
|
#endif |
|
|
|
#ifdef WIN32 |
|
#include "conio.h" |
|
#include <direct.h> |
|
#include <io.h> |
|
#endif |
|
|
|
#include "vtf/vtf.h" |
|
#include "utlbuffer.h" |
|
#include "tier0/dbg.h" |
|
#include "cmdlib.h" |
|
#include "tier0/icommandline.h" |
|
#ifdef WIN32 |
|
#include "windows.h" |
|
#endif |
|
#include "ilaunchabledll.h" |
|
#include "ivtex.h" |
|
#include "appframework/IAppSystemGroup.h" |
|
|
|
#include "tier2/tier2.h" |
|
#include "tier1/checksum_crc.h" |
|
#include "imageutils.h" |
|
|
|
#define FF_TRYAGAIN 1 |
|
#define FF_DONTPROCESS 2 |
|
|
|
#define LOWRESIMAGE_DIM 16 |
|
|
|
#ifdef POSIX |
|
#define LOWRES_IMAGE_FORMAT IMAGE_FORMAT_RGBA8888 |
|
#else |
|
#define LOWRES_IMAGE_FORMAT IMAGE_FORMAT_DXT1 |
|
#endif |
|
|
|
//#define DEBUG_NO_COMPRESSION |
|
static bool g_Quiet = false; |
|
static const char *g_ShaderName = NULL; |
|
static bool g_CreateDir = true; |
|
static bool g_UseGameDir = true; |
|
|
|
static bool g_bWarningsAsErrors = false; |
|
static bool g_bUsedAsLaunchableDLL = false; |
|
|
|
static char g_ForcedOutputDir[MAX_PATH]; |
|
|
|
|
|
#define MAX_VMT_PARAMS 16 |
|
|
|
struct VTexVMTParam_t |
|
{ |
|
const char *m_szParam; |
|
const char *m_szValue; |
|
}; |
|
|
|
class SmartIVtfTexture |
|
{ |
|
public: |
|
explicit SmartIVtfTexture( IVTFTexture *pVtf ) : m_p( pVtf ) {} |
|
~SmartIVtfTexture() { if ( m_p ) DestroyVTFTexture( m_p ); } |
|
|
|
private: |
|
SmartIVtfTexture( SmartIVtfTexture const &x ); |
|
SmartIVtfTexture & operator = ( SmartIVtfTexture const &x ); |
|
|
|
private: |
|
SmartIVtfTexture & operator = ( IVTFTexture *pVtf ) { m_p = pVtf; return *this; } |
|
operator IVTFTexture * () const { return m_p; } |
|
|
|
public: |
|
IVTFTexture * Assign( IVTFTexture *pVtfNew ) { IVTFTexture *pOld = m_p; m_p = pVtfNew; return pOld; } |
|
IVTFTexture * Get() const { return m_p; } |
|
IVTFTexture * operator->() const { return m_p; } |
|
|
|
protected: |
|
IVTFTexture *m_p; |
|
}; |
|
|
|
static VTexVMTParam_t g_VMTParams[MAX_VMT_PARAMS]; |
|
|
|
static int g_NumVMTParams = 0; |
|
static enum Mode { eModePSD, eModeTGA, eModePFM, eModePNG } g_eMode = eModePSD; |
|
|
|
// NOTE: these must stay in the same order as CubeMapFaceIndex_t. |
|
static const char *g_CubemapFacingNames[7] = { "rt", "lf", "bk", "ft", "up", "dn", "sph" }; |
|
|
|
static bool VTexErrorAborts() |
|
{ |
|
if ( CommandLine()->FindParm( "-crcvalidate" ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
|
|
static void VTexError( const char *pFormat, ... ) |
|
{ |
|
char str[4096]; |
|
va_list marker; |
|
va_start( marker, pFormat ); |
|
Q_vsnprintf( str, sizeof( str ), pFormat, marker ); |
|
va_end( marker ); |
|
|
|
if ( !VTexErrorAborts() ) |
|
{ |
|
fprintf( stderr, "ERROR: %s", str ); |
|
return; |
|
} |
|
|
|
|
|
fprintf( stderr, "ERROR: %s", str ); |
|
exit( 1 ); |
|
} |
|
|
|
|
|
static void VTexWarning( const char *pFormat, ... ) |
|
{ |
|
char str[4096]; |
|
va_list marker; |
|
va_start( marker, pFormat ); |
|
Q_vsnprintf( str, sizeof( str ), pFormat, marker ); |
|
va_end( marker ); |
|
|
|
if ( g_bWarningsAsErrors ) |
|
{ |
|
VTexError( "%s", str ); |
|
} |
|
else |
|
{ |
|
fprintf( stderr, "WARN: %s", str ); |
|
} |
|
} |
|
|
|
|
|
|
|
struct VTexConfigInfo_t |
|
{ |
|
int m_nStartFrame; |
|
int m_nEndFrame; |
|
unsigned int m_nFlags; |
|
float m_flBumpScale; |
|
LookDir_t m_LookDir; |
|
bool m_bNormalToDuDv; |
|
bool m_bAlphaToLuminance; |
|
bool m_bDuDv; |
|
float m_flAlphaThreshhold; |
|
float m_flAlphaHiFreqThreshhold; |
|
bool m_bSkyBox; |
|
int m_nVolumeTextureDepth; |
|
float m_pfmscale; |
|
bool m_bStripAlphaChannel; |
|
bool m_bStripColorChannel; |
|
bool m_bIsCubeMap; |
|
|
|
|
|
// scaling parameters |
|
int m_nReduceX; |
|
int m_nReduceY; |
|
|
|
int m_nMaxDimensionX, m_nMaxDimensionX_360; |
|
int m_nMaxDimensionY, m_nMaxDimensionY_360; |
|
|
|
// may restrict the texture to reading only 3 channels |
|
int m_numChannelsMax; |
|
|
|
bool m_bAlphaToDistance; |
|
float m_flDistanceSpread; // how far to stretch out distance range in pixels |
|
|
|
CRC32_t m_uiInputHash; // Sources hash |
|
|
|
TextureSettingsEx_t m_exSettings0; |
|
VtfProcessingOptions m_vtfProcOptions; |
|
|
|
enum |
|
{ |
|
// CRC of input files: |
|
// txt + tga/pfm |
|
// or |
|
// psd |
|
VTF_INPUTSRC_CRC = MK_VTF_RSRC_ID( 'C','R','C' ) |
|
}; |
|
|
|
char m_SrcName[MAX_PATH]; |
|
|
|
VTexConfigInfo_t( void ) |
|
{ |
|
m_nStartFrame = -1; |
|
m_nEndFrame = -1; |
|
m_nFlags = 0; |
|
m_bNormalToDuDv = false; |
|
m_bAlphaToLuminance = false; |
|
m_flBumpScale = 1.0f; |
|
m_bDuDv = false; |
|
m_flAlphaThreshhold = -1.0f; |
|
m_flAlphaHiFreqThreshhold = -1.0f; |
|
m_bSkyBox = false; |
|
m_nVolumeTextureDepth = 1; |
|
m_pfmscale=1.0; |
|
m_bStripAlphaChannel = false; |
|
m_bStripColorChannel = false; |
|
m_bIsCubeMap = false; |
|
m_nReduceX = 1; |
|
m_nReduceY = 1; |
|
m_SrcName[0]=0; |
|
m_numChannelsMax = 4; |
|
m_bAlphaToDistance = 0; |
|
m_flDistanceSpread = 1.0; |
|
m_nMaxDimensionX = -1; |
|
m_nMaxDimensionX_360 = -1; |
|
m_nMaxDimensionY = -1; |
|
m_nMaxDimensionY_360 = -1; |
|
|
|
memset( &m_exSettings0, 0, sizeof( m_exSettings0 ) ); |
|
|
|
memset( &m_vtfProcOptions, 0, sizeof( m_vtfProcOptions ) ); |
|
m_vtfProcOptions.cbSize = sizeof( m_vtfProcOptions ); |
|
|
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_FILTER_NICE; |
|
|
|
CRC32_Init( &m_uiInputHash ); |
|
} |
|
|
|
bool IsSettings0Valid( void ) const |
|
{ |
|
TextureSettingsEx_t exSettingsEmpty; |
|
memset( &exSettingsEmpty, 0, sizeof( exSettingsEmpty ) ); |
|
Assert( sizeof( m_exSettings0 ) == sizeof( exSettingsEmpty ) ); |
|
return !!memcmp( &m_exSettings0, &exSettingsEmpty, sizeof( m_exSettings0 ) ); |
|
} |
|
|
|
// returns false if unrecognized option |
|
void ParseOptionKey( const char *pKeyName, const char *pKeyValue ); |
|
|
|
}; |
|
|
|
template < typename T > |
|
static inline T& SetFlagValueT( T &field, T const &flag, int bSetFlag ) |
|
{ |
|
if ( bSetFlag ) |
|
field |= flag; |
|
else |
|
field &=~flag; |
|
|
|
return field; |
|
} |
|
|
|
static inline uint32& SetFlagValue( uint32 &field, uint32 const &flag, int bSetFlag ) |
|
{ |
|
return SetFlagValueT<uint32>( field, flag, bSetFlag ); |
|
} |
|
|
|
void VTexConfigInfo_t::ParseOptionKey( const char *pKeyName, const char *pKeyValue ) |
|
{ |
|
int iValue = atoi( pKeyValue ); // To properly have "clamps 0" and not enable the clamping |
|
|
|
if ( !stricmp( pKeyName, "skybox" ) ) |
|
{ |
|
// We're going to treat it like a cubemap until the very end, so it'll load the other skybox faces and |
|
// match their edges with the texture compression and mipmapping. |
|
m_bSkyBox = iValue ? true : false; |
|
m_bIsCubeMap = iValue ? true : false; |
|
if ( !g_Quiet && iValue ) |
|
Msg( "'skybox' detected. Treating skybox like a cubemap for edge-matching purposes.\n" ); |
|
} |
|
else if( !stricmp( pKeyName, "startframe" ) ) |
|
{ |
|
m_nStartFrame = atoi( pKeyValue ); |
|
} |
|
else if( !stricmp( pKeyName, "endframe" ) ) |
|
{ |
|
m_nEndFrame = atoi( pKeyValue ); |
|
} |
|
else if( !stricmp( pKeyName, "volumetexture" ) ) |
|
{ |
|
m_nVolumeTextureDepth = atoi( pKeyValue ); |
|
|
|
// FIXME: Volume textures don't currently support DXT compression |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_NOCOMPRESS; |
|
|
|
// FIXME: Volume textures don't currently support NICE filtering |
|
m_vtfProcOptions.flags0 &= ~VtfProcessingOptions::OPT_FILTER_NICE; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_x" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_X; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_negx" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_NEGX; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_y" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_Y; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_negy" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_NEGY; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_z" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_Z; |
|
} |
|
else if( !stricmp( pKeyName, "spheremap_negz" ) ) |
|
{ |
|
if ( iValue ) |
|
m_LookDir = LOOK_DOWN_NEGZ; |
|
} |
|
else if( !stricmp( pKeyName, "bumpscale" ) ) |
|
{ |
|
m_flBumpScale = atof( pKeyValue ); |
|
} |
|
else if( !stricmp( pKeyName, "pointsample" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_POINTSAMPLE, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "trilinear" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_TRILINEAR, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "clamps" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPS, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "clampt" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPT, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "clampu" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_CLAMPU, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "border" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_BORDER, iValue ); |
|
// Gets applied to s, t and u We currently assume black border color |
|
} |
|
else if( !stricmp( pKeyName, "anisotropic" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_ANISOTROPIC, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "dxt5" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_HINT_DXT5, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "nocompress" ) ) |
|
{ |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_NOCOMPRESS, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "normal" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_NORMAL, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "ssbump" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_SSBUMP, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "nomip" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_NOMIP, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "allmips" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "nonice" ) ) |
|
{ |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_FILTER_NICE, !iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "nolod" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_NOLOD, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "procedural" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_PROCEDURAL, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "alphatest" ) ) |
|
{ |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_MIP_ALPHATEST, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "alphatest_threshhold" ) ) |
|
{ |
|
m_flAlphaThreshhold = atof( pKeyValue ); |
|
} |
|
else if( !stricmp( pKeyName, "alphatest_hifreq_threshhold" ) ) |
|
{ |
|
m_flAlphaHiFreqThreshhold = atof( pKeyValue ); |
|
} |
|
else if( !stricmp( pKeyName, "rendertarget" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_RENDERTARGET, iValue ); |
|
} |
|
else if ( !stricmp( pKeyName, "numchannels" ) ) |
|
{ |
|
m_numChannelsMax = atoi( pKeyValue ); |
|
} |
|
else if ( !stricmp( pKeyName, "nodebug" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_NODEBUGOVERRIDE, iValue ); |
|
} |
|
else if ( !stricmp( pKeyName, "singlecopy" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_SINGLECOPY, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "oneovermiplevelinalpha" ) ) |
|
{ |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_SET_ALPHA_ONEOVERMIP, iValue ); |
|
} |
|
else if( !stricmp( pKeyName, "premultcolorbyoneovermiplevel" ) ) |
|
{ |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP, iValue ); |
|
} |
|
else if ( !stricmp( pKeyName, "normaltodudv" ) ) |
|
{ |
|
m_bNormalToDuDv = iValue ? true : false; |
|
SetFlagValue( m_vtfProcOptions.flags0, VtfProcessingOptions::OPT_NORMAL_DUDV, iValue ); |
|
} |
|
else if ( !stricmp( pKeyName, "stripalphachannel" ) ) |
|
{ |
|
m_bStripAlphaChannel = iValue ? true : false; |
|
} |
|
else if ( !stricmp( pKeyName, "stripcolorchannel" ) ) |
|
{ |
|
m_bStripColorChannel = iValue ? true : false; |
|
} |
|
else if ( !stricmp( pKeyName, "normalalphatodudvluminance" ) ) |
|
{ |
|
m_bAlphaToLuminance = iValue ? true : false; |
|
} |
|
else if ( !stricmp( pKeyName, "dudv" ) ) |
|
{ |
|
m_bDuDv = iValue ? true : false; |
|
} |
|
else if( !stricmp( pKeyName, "reduce" ) ) |
|
{ |
|
m_nReduceX = atoi(pKeyValue); |
|
m_nReduceY = m_nReduceX; |
|
} |
|
else if( !stricmp( pKeyName, "reducex" ) ) |
|
{ |
|
m_nReduceX = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "reducey" ) ) |
|
{ |
|
m_nReduceY = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "maxwidth" ) ) |
|
{ |
|
m_nMaxDimensionX = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "maxwidth_360" ) ) |
|
{ |
|
m_nMaxDimensionX_360 = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "maxheight" ) ) |
|
{ |
|
m_nMaxDimensionY = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "maxheight_360" ) ) |
|
{ |
|
m_nMaxDimensionY_360 = atoi(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "alphatodistance" ) ) |
|
{ |
|
m_bAlphaToDistance = iValue ? true : false; |
|
} |
|
else if( !stricmp( pKeyName, "distancespread" ) ) |
|
{ |
|
m_flDistanceSpread = atof(pKeyValue); |
|
} |
|
else if( !stricmp( pKeyName, "pfmscale" ) ) |
|
{ |
|
m_pfmscale=atof(pKeyValue); |
|
printf("******pfm scale=%f\n",m_pfmscale); |
|
} |
|
else if ( !stricmp( pKeyName, "pfm" ) ) |
|
{ |
|
if ( iValue ) |
|
g_eMode = eModePFM; |
|
} |
|
else if ( !stricmp( pKeyName, "specvar" ) ) |
|
{ |
|
int iDecayChannel = -1; |
|
|
|
if ( !stricmp( pKeyValue, "red" ) || !stricmp( pKeyValue, "r" ) ) |
|
iDecayChannel = 0; |
|
if ( !stricmp( pKeyValue, "green" ) || !stricmp( pKeyValue, "g" ) ) |
|
iDecayChannel = 1; |
|
if ( !stricmp( pKeyValue, "blue" ) || !stricmp( pKeyValue, "b" ) ) |
|
iDecayChannel = 2; |
|
if ( !stricmp( pKeyValue, "alpha" ) || !stricmp( pKeyValue, "a" ) ) |
|
iDecayChannel = 3; |
|
|
|
if ( iDecayChannel >= 0 && iDecayChannel < 4 ) |
|
{ |
|
m_vtfProcOptions.flags0 |= ( VtfProcessingOptions::OPT_DECAY_R | VtfProcessingOptions::OPT_DECAY_EXP_R ) << iDecayChannel; |
|
m_vtfProcOptions.numNotDecayMips[iDecayChannel] = 0; |
|
m_vtfProcOptions.clrDecayGoal[iDecayChannel] = 0; |
|
m_vtfProcOptions.fDecayExponentBase[iDecayChannel] = 0.75; |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, 1 ); |
|
} |
|
} |
|
else if ( !stricmp( pKeyName, "mipblend" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_ALL_MIPS, 1 ); |
|
|
|
// Possible values |
|
if ( !stricmp( pKeyValue, "detail" ) ) // Skip 2 mips and fade to gray -> (128, 128, 128, -) |
|
{ |
|
for( int ch = 0; ch < 3; ++ ch ) |
|
{ |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch; |
|
// m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch); |
|
m_vtfProcOptions.numNotDecayMips[ch] = 2; |
|
m_vtfProcOptions.clrDecayGoal[ch] = 128; |
|
} |
|
} |
|
/* |
|
else if ( !stricmp( pKeyValue, "additive" ) ) // Skip 2 mips and fade to black -> (0, 0, 0, -) |
|
{ |
|
for( int ch = 0; ch < 3; ++ ch ) |
|
{ |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch; |
|
m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch); |
|
m_vtfProcOptions.numDecayMips[ch] = 2; |
|
m_vtfProcOptions.clrDecayGoal[ch] = 0; |
|
} |
|
} |
|
else if ( !stricmp( pKeyValue, "alphablended" ) ) // Skip 2 mips and fade out alpha to 0 |
|
{ |
|
for( int ch = 3; ch < 4; ++ ch ) |
|
{ |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch; |
|
m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch); |
|
m_vtfProcOptions.numDecayMips[ch] = 2; |
|
m_vtfProcOptions.clrDecayGoal[ch] = 0; |
|
} |
|
} |
|
*/ |
|
else |
|
{ |
|
// Parse the given value: |
|
// skip=3:r=255:g=255:b=255:a=255 - linear decay |
|
// r=0e.75 - exponential decay targeting 0 with exponent base 0.75 |
|
|
|
int nSteps = 0; // default |
|
|
|
for ( char const *szParse = pKeyValue; szParse; szParse = strchr( szParse, ':' ), szParse ? ++ szParse : 0 ) |
|
{ |
|
if ( char const *sz = StringAfterPrefix( szParse, "skip=" ) ) |
|
{ |
|
szParse = sz; |
|
nSteps = atoi(sz); |
|
} |
|
else if ( StringHasPrefix( szParse, "r=" ) || |
|
StringHasPrefix( szParse, "g=" ) || |
|
StringHasPrefix( szParse, "b=" ) || |
|
StringHasPrefix( szParse, "a=" ) ) |
|
{ |
|
int ch = 0; |
|
switch ( *szParse ) |
|
{ |
|
case 'g': case 'G': ch = 1; break; |
|
case 'b': case 'B': ch = 2; break; |
|
case 'a': case 'A': ch = 3; break; |
|
} |
|
|
|
szParse += 2; |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_R << ch; |
|
m_vtfProcOptions.flags0 &= ~(VtfProcessingOptions::OPT_DECAY_EXP_R << ch); |
|
m_vtfProcOptions.numNotDecayMips[ch] = nSteps; |
|
m_vtfProcOptions.clrDecayGoal[ch] = atoi( szParse ); |
|
|
|
while ( isdigit( *szParse ) ) |
|
++ szParse; |
|
|
|
// Exponential decay |
|
if ( ( *szParse == 'e' || *szParse == 'E' ) && ( szParse[1] == '.' ) ) |
|
{ |
|
m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_DECAY_EXP_R << ch; |
|
m_vtfProcOptions.fDecayExponentBase[ch] = ( float ) atof( szParse + 1 ); |
|
} |
|
} |
|
else |
|
{ |
|
printf( "Warning: invalid mipblend setting \"%s\"\n", pKeyValue ); |
|
} |
|
} |
|
} |
|
} |
|
else if( !stricmp( pKeyName, "srgb" ) ) |
|
{ |
|
SetFlagValue( m_nFlags, TEXTUREFLAGS_SRGB, iValue ); |
|
} |
|
else |
|
{ |
|
VTexError("unrecognized option in text file - %s\n", pKeyName ); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
static const char *GetSourceExtension( void ) |
|
{ |
|
switch ( g_eMode ) |
|
{ |
|
case eModePSD: |
|
return ".psd"; |
|
case eModeTGA: |
|
return ".tga"; |
|
case eModePFM: |
|
return ".pfm"; |
|
case eModePNG: |
|
return ".png"; |
|
default: |
|
return ".tga"; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the desired texture format based on flags |
|
//----------------------------------------------------------------------------- |
|
static ImageFormat ComputeDesiredImageFormat( IVTFTexture *pTexture, VTexConfigInfo_t &info ) |
|
{ |
|
bool bDUDVTarget = info.m_bNormalToDuDv || info.m_bDuDv; |
|
bool bCopyAlphaToLuminance = info.m_bNormalToDuDv && info.m_bAlphaToLuminance; |
|
|
|
ImageFormat targetFormat; |
|
|
|
int nFlags = pTexture->Flags(); |
|
if ( info.m_bStripAlphaChannel ) |
|
{ |
|
nFlags &= ~( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ); |
|
} |
|
|
|
// HDRFIXME: Need to figure out what format to use here. |
|
if ( pTexture->Format() == IMAGE_FORMAT_RGB323232F ) |
|
{ |
|
#ifndef DEBUG_NO_COMPRESSION |
|
if ( g_bUsedAsLaunchableDLL && !( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS ) ) |
|
{ |
|
return IMAGE_FORMAT_BGRA8888; |
|
} |
|
else |
|
#endif // #ifndef DEBUG_NO_COMPRESSION |
|
{ |
|
return IMAGE_FORMAT_RGBA16161616F; |
|
} |
|
} |
|
|
|
if ( bDUDVTarget ) |
|
{ |
|
if ( bCopyAlphaToLuminance && ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) ) ) |
|
return IMAGE_FORMAT_UVLX8888; |
|
return IMAGE_FORMAT_UV88; |
|
} |
|
|
|
if ( info.m_bStripColorChannel ) |
|
{ |
|
return IMAGE_FORMAT_A8; |
|
} |
|
|
|
// can't compress textures that are smaller than 4x4 |
|
if( (nFlags & TEXTUREFLAGS_PROCEDURAL) || |
|
(info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS) || |
|
(pTexture->Width() < 4) || (pTexture->Height() < 4) ) |
|
{ |
|
if ( nFlags & ( TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA ) ) |
|
{ |
|
targetFormat = IMAGE_FORMAT_BGRA8888; |
|
} |
|
else |
|
{ |
|
targetFormat = IMAGE_FORMAT_BGR888; |
|
} |
|
} |
|
else if( nFlags & TEXTUREFLAGS_HINT_DXT5 ) |
|
{ |
|
#ifdef DEBUG_NO_COMPRESSION |
|
targetFormat = IMAGE_FORMAT_BGRA8888; |
|
#else |
|
targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix |
|
#endif |
|
} |
|
else if( nFlags & TEXTUREFLAGS_EIGHTBITALPHA ) |
|
{ |
|
// compressed with alpha blending |
|
#ifdef DEBUG_NO_COMPRESSION |
|
targetFormat = IMAGE_FORMAT_BGRA8888; |
|
#else |
|
targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix |
|
#endif |
|
} |
|
else if ( nFlags & TEXTUREFLAGS_ONEBITALPHA ) |
|
{ |
|
// garymcthack - fixme IMAGE_FORMAT_DXT1_ONEBITALPHA doesn't work yet. |
|
#ifdef DEBUG_NO_COMPRESSION |
|
targetFormat = IMAGE_FORMAT_BGRA8888; |
|
#else |
|
// targetFormat = IMAGE_FORMAT_DXT1_ONEBITALPHA; |
|
targetFormat = IsPosix() ? IMAGE_FORMAT_BGRA8888 : IMAGE_FORMAT_DXT5; // No DXT compressor on Posix |
|
#endif |
|
} |
|
else |
|
{ |
|
#ifdef DEBUG_NO_COMPRESSION |
|
targetFormat = IMAGE_FORMAT_BGR888; |
|
#else |
|
targetFormat = IsPosix() ? IMAGE_FORMAT_BGR888 : IMAGE_FORMAT_DXT1; // No DXT compressor on Posix |
|
#endif |
|
} |
|
return targetFormat; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the low res image size |
|
//----------------------------------------------------------------------------- |
|
void VTFGetLowResImageInfo( int cacheWidth, int cacheHeight, int *lowResImageWidth, int *lowResImageHeight, |
|
ImageFormat *imageFormat ) |
|
{ |
|
if (cacheWidth > cacheHeight) |
|
{ |
|
int factor = cacheWidth / LOWRESIMAGE_DIM; |
|
if (factor > 0) |
|
{ |
|
*lowResImageWidth = LOWRESIMAGE_DIM; |
|
*lowResImageHeight = cacheHeight / factor; |
|
} |
|
else |
|
{ |
|
*lowResImageWidth = cacheWidth; |
|
*lowResImageHeight = cacheHeight; |
|
} |
|
} |
|
else |
|
{ |
|
int factor = cacheHeight / LOWRESIMAGE_DIM; |
|
if (factor > 0) |
|
{ |
|
*lowResImageHeight = LOWRESIMAGE_DIM; |
|
*lowResImageWidth = cacheWidth / factor; |
|
} |
|
else |
|
{ |
|
*lowResImageWidth = cacheWidth; |
|
*lowResImageHeight = cacheHeight; |
|
} |
|
} |
|
|
|
// Can end up with a dimension of zero for high aspect ration images. |
|
if( *lowResImageWidth < 1 ) |
|
{ |
|
*lowResImageWidth = 1; |
|
} |
|
if( *lowResImageHeight < 1 ) |
|
{ |
|
*lowResImageHeight = 1; |
|
} |
|
*imageFormat = LOWRES_IMAGE_FORMAT; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This method creates the low-res image and hooks it into the VTF Texture |
|
//----------------------------------------------------------------------------- |
|
static void CreateLowResImage( IVTFTexture *pVTFTexture ) |
|
{ |
|
int iWidth, iHeight; |
|
ImageFormat imageFormat; |
|
VTFGetLowResImageInfo( pVTFTexture->Width(), pVTFTexture->Height(), &iWidth, &iHeight, &imageFormat ); |
|
|
|
// Allocate the low-res image data |
|
pVTFTexture->InitLowResImage( iWidth, iHeight, imageFormat ); |
|
|
|
// Generate the low-res image bits |
|
if (!pVTFTexture->ConstructLowResImage()) |
|
{ |
|
VTexError( "Can't convert image from %s to %s in CalcLowResImage\n", |
|
ImageLoader::GetName(IMAGE_FORMAT_RGBA8888), ImageLoader::GetName(imageFormat) ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the source file name |
|
//----------------------------------------------------------------------------- |
|
void MakeSrcFileName( char *pSrcName, unsigned int flags, const char *pFullNameWithoutExtension, int frameID, |
|
int faceID, int z, bool isCubeMap, int startFrame, int endFrame, bool bNormalToDUDV ) |
|
{ |
|
bool bAnimated = !( startFrame == -1 || endFrame == -1 ); |
|
|
|
char tempBuf[512]; |
|
if( bNormalToDUDV ) |
|
{ |
|
char *pNormalString = Q_stristr( ( char * )pFullNameWithoutExtension, "_dudv" ); |
|
if( pNormalString ) |
|
{ |
|
Q_strncpy( tempBuf, pFullNameWithoutExtension, sizeof(tempBuf) ); |
|
char *pNormalString = Q_stristr( tempBuf, "_dudv" ); |
|
Q_strcpy( pNormalString, "_normal" ); |
|
pFullNameWithoutExtension = tempBuf; |
|
} |
|
else |
|
{ |
|
Assert( Q_stristr( ( char * )pFullNameWithoutExtension, "_dudv" ) ); |
|
} |
|
} |
|
|
|
if( bAnimated ) |
|
{ |
|
if( isCubeMap ) |
|
{ |
|
Assert( z == -1 ); |
|
sprintf( pSrcName, "%s%s%03d%s", pFullNameWithoutExtension, g_CubemapFacingNames[faceID], frameID + startFrame, GetSourceExtension() ); |
|
} |
|
else |
|
{ |
|
if ( z == -1 ) |
|
{ |
|
sprintf( pSrcName, "%s%03d%s", pFullNameWithoutExtension, frameID + startFrame, GetSourceExtension() ); |
|
} |
|
else |
|
{ |
|
sprintf( pSrcName, "%s%03d_z%03d%s", pFullNameWithoutExtension, z, frameID + startFrame, GetSourceExtension() ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if( isCubeMap ) |
|
{ |
|
Assert( z == -1 ); |
|
sprintf( pSrcName, "%s%s%s", pFullNameWithoutExtension, g_CubemapFacingNames[faceID], GetSourceExtension() ); |
|
} |
|
else |
|
{ |
|
if ( z == -1 ) |
|
{ |
|
sprintf( pSrcName, "%s%s", pFullNameWithoutExtension, GetSourceExtension() ); |
|
} |
|
else |
|
{ |
|
sprintf( pSrcName, "%s_z%03d%s", pFullNameWithoutExtension, z, GetSourceExtension() ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void ComputeBufferHash( void const *pvBuffer, size_t numBytes, CRC32_t &uiHashUpdate ) |
|
{ |
|
CRC32_ProcessBuffer( &uiHashUpdate, pvBuffer, numBytes ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads a file into a UTLBuffer, |
|
// also computes the hash of the buffer. |
|
//----------------------------------------------------------------------------- |
|
static bool LoadFile( const char *pFileName, CUtlBuffer &buf, bool bFailOnError, CRC32_t *puiHash ) |
|
{ |
|
FILE *fp = fopen( pFileName, "rb" ); |
|
if (!fp) |
|
{ |
|
if ( bFailOnError ) |
|
VTexError( "Can't open: \"%s\"\n", pFileName ); |
|
|
|
return false; |
|
} |
|
|
|
fseek( fp, 0, SEEK_END ); |
|
int nFileLength = ftell( fp ); |
|
fseek( fp, 0, SEEK_SET ); |
|
|
|
buf.EnsureCapacity( nFileLength ); |
|
int nBytesRead = fread( buf.Base(), 1, nFileLength, fp ); |
|
fclose( fp ); |
|
|
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); |
|
|
|
// Auto-compute buffer hash if necessary |
|
if ( puiHash ) |
|
ComputeBufferHash( buf.Base(), nBytesRead, *puiHash ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a texture the size of the PSD image stored in the buffer |
|
//----------------------------------------------------------------------------- |
|
static void InitializeSrcTexture_PSD( IVTFTexture *pTexture, const char *pInputFileName, |
|
CUtlBuffer &psdBuffer, int nDepth, int nFrameCount, |
|
const VTexConfigInfo_t &info ) |
|
{ |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
bool ok = PSDGetInfo( psdBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma ); |
|
if (!ok) |
|
{ |
|
Error( "PSD %s is bogus!\n", pInputFileName ); |
|
} |
|
nWidth /= info.m_nReduceX; |
|
nHeight /= info.m_nReduceY; |
|
|
|
|
|
if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount )) |
|
{ |
|
Error( "Error initializing texture %s\n", pInputFileName ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a texture the size of the TGA image stored in the buffer |
|
//----------------------------------------------------------------------------- |
|
static void InitializeSrcTexture_TGA( IVTFTexture *pTexture, const char *pInputFileName, |
|
CUtlBuffer &tgaBuffer, int nDepth, int nFrameCount, |
|
const VTexConfigInfo_t &info ) |
|
{ |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
bool ok = TGALoader::GetInfo( tgaBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma ); |
|
if (!ok) |
|
{ |
|
Error( "TGA %s is bogus!\n", pInputFileName ); |
|
} |
|
nWidth /= info.m_nReduceX; |
|
nHeight /= info.m_nReduceY; |
|
|
|
if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount )) |
|
{ |
|
Error( "Error initializing texture %s\n", pInputFileName ); |
|
} |
|
} |
|
|
|
static void InitializeSrcTexture_PNG( IVTFTexture *pTexture, const char *pInputFileName, |
|
CUtlBuffer &pngBuffer, int nDepth, int nFrameCount, |
|
const VTexConfigInfo_t &info ) |
|
{ |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
|
|
ConversionErrorType error = CE_SUCCESS; |
|
unsigned char *data = ImgUtl_ReadPNGAsRGBAFromBuffer( pngBuffer, nWidth, nHeight, error ); |
|
if (error != CE_SUCCESS) |
|
Error( "PNG %s is bogus!\n", pInputFileName ); |
|
|
|
pngBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); |
|
pngBuffer.Put( data, nWidth*nHeight*4 ); |
|
|
|
nWidth /= info.m_nReduceX; |
|
nHeight /= info.m_nReduceY; |
|
|
|
FILE *f = fopen("shit", "wb"); |
|
fwrite(pngBuffer.Base(), 1, 256*256*4, f); |
|
fclose(f); |
|
|
|
if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_DEFAULT, info.m_nFlags, nFrameCount )) |
|
{ |
|
Error( "Error initializing texture %s\n", pInputFileName ); |
|
} |
|
} |
|
|
|
// HDRFIXME: Put this somewhere better than this. |
|
// This reads an integer from a binary CUtlBuffer. |
|
static int ReadIntFromUtlBuffer( CUtlBuffer &buf ) |
|
{ |
|
int val = 0; |
|
int c; |
|
while( buf.IsValid() ) |
|
{ |
|
c = buf.GetChar(); |
|
if( c >= '0' && c <= '9' ) |
|
{ |
|
val = val * 10 + ( c - '0' ); |
|
} |
|
else |
|
{ |
|
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, -1 ); |
|
break; |
|
} |
|
} |
|
return val; |
|
} |
|
|
|
static inline bool IsWhitespace( char c ) |
|
{ |
|
return c == ' ' || c == '\t' || c == 10; |
|
} |
|
|
|
static void EatWhiteSpace( CUtlBuffer &buf ) |
|
{ |
|
while( buf.IsValid() ) |
|
{ |
|
int c = buf.GetChar(); |
|
if( !IsWhitespace( c ) ) |
|
{ |
|
buf.SeekGet( CUtlBuffer::SEEK_CURRENT, -1 ); |
|
return; |
|
} |
|
} |
|
return; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a texture the size of the PFM image stored in the buffer |
|
//----------------------------------------------------------------------------- |
|
static void InitializeSrcTexture_PFM( IVTFTexture *pTexture, const char *pInputFileName, |
|
CUtlBuffer &fileBuffer, int nDepth, int nFrameCount, |
|
const VTexConfigInfo_t &info ) |
|
{ |
|
fileBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); |
|
if( fileBuffer.GetChar() != 'P' ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
if( fileBuffer.GetChar() != 'F' ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
if( fileBuffer.GetChar() != 0xa ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
int nWidth, nHeight; |
|
nWidth = ReadIntFromUtlBuffer( fileBuffer ); |
|
EatWhiteSpace( fileBuffer ); |
|
nHeight = ReadIntFromUtlBuffer( fileBuffer ); |
|
|
|
// // eat crap until the next newline |
|
// while( fileBuffer.GetChar() != 0xa ) |
|
// { |
|
// } |
|
|
|
nWidth /= info.m_nReduceX; |
|
|
|
nHeight /= info.m_nReduceY; |
|
|
|
|
|
if (!pTexture->Init( nWidth, nHeight, nDepth, IMAGE_FORMAT_RGB323232F, info.m_nFlags, nFrameCount )) |
|
{ |
|
Error( "Error initializing texture %s\n", pInputFileName ); |
|
} |
|
} |
|
|
|
static void InitializeSrcTexture( IVTFTexture *pTexture, const char *pInputFileName, |
|
CUtlBuffer &tgaBuffer, int nDepth, int nFrameCount, |
|
const VTexConfigInfo_t &info ) |
|
{ |
|
switch ( g_eMode ) |
|
{ |
|
case eModePSD: |
|
InitializeSrcTexture_PSD( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info ); |
|
break; |
|
case eModeTGA: |
|
InitializeSrcTexture_TGA( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info ); |
|
break; |
|
case eModePNG: |
|
InitializeSrcTexture_PNG( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info ); |
|
break; |
|
case eModePFM: |
|
InitializeSrcTexture_PFM( pTexture, pInputFileName, tgaBuffer, nDepth, nFrameCount, info ); |
|
break; |
|
} |
|
} |
|
|
|
#define DISTANCE_CODE_ALPHA_INOUT_THRESHOLD 10 |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads a face from a PSD image |
|
//----------------------------------------------------------------------------- |
|
static bool LoadFaceFromPSD( IVTFTexture *pTexture, CUtlBuffer &psdBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
// NOTE: This only works because all mip levels are stored sequentially |
|
// in memory, starting with the highest mip level. It also only works |
|
// because the VTF Texture store *all* mip levels down to 1x1 |
|
|
|
// Get the information from the file... |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
bool ok = PSDGetInfo( psdBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma ); |
|
if (!ok) |
|
return false; |
|
|
|
// Seek back so PSDLoader can see the psd header... |
|
psdBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); |
|
|
|
// Load the psd and create all mipmap levels |
|
unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z ); |
|
|
|
if ( ( info.m_bAlphaToDistance ) || |
|
( nWidth != pTexture->Width() ) || |
|
( nHeight != pTexture->Height() ) ) |
|
{ |
|
// Load into temp |
|
Bitmap_t bmPsdData; |
|
ok = PSDReadFileRGBA8888( psdBuffer, bmPsdData ); |
|
if ( !ok ) |
|
return false; |
|
|
|
CUtlMemory<uint8> tmpDest( 0, pTexture->Width() * pTexture->Height() * 4 ); |
|
|
|
ImageLoader::ResampleInfo_t resInfo; |
|
resInfo.m_pSrc = bmPsdData.GetBits(); |
|
resInfo.m_pDest = tmpDest.Base(); |
|
resInfo.m_nSrcWidth = nWidth; |
|
resInfo.m_nSrcHeight = nHeight; |
|
resInfo.m_nDestWidth = pTexture->Width(); |
|
resInfo.m_nDestHeight = pTexture->Height(); |
|
resInfo.m_flSrcGamma = flGamma; |
|
resInfo.m_flDestGamma = flGamma; |
|
if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE ) |
|
{ |
|
resInfo.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER; |
|
} |
|
|
|
ResampleRGBA8888( resInfo ); |
|
|
|
if ( info.m_bAlphaToDistance ) |
|
{ |
|
float flMaxRad=info.m_flDistanceSpread*2.0*max(info.m_nReduceX,info.m_nReduceY); |
|
int nSearchRad=ceil(flMaxRad); |
|
bool bWarnEdges = false; |
|
// now, do alpha to distance coded stuff |
|
ImageFormatInfo_t fmtInfo=ImageLoader::ImageFormatInfo( pTexture->Format() ); |
|
if ( fmtInfo.m_NumAlphaBits == 0 ) |
|
{ |
|
VTexWarning( "%s: alpha to distance asked for but no alpha channel.\n", info.m_SrcName ); |
|
} |
|
else |
|
{ |
|
for(int x=0; x < pTexture->Width(); x++ ) |
|
{ |
|
for(int y=0; y < pTexture->Height(); y++ ) |
|
{ |
|
// map to original image coords |
|
int nOrig_x=FLerp(0,nWidth-1,0,pTexture->Width()-1,x); |
|
int nOrig_y=FLerp(0,nHeight-1,0,pTexture->Height()-1,y); |
|
|
|
uint8 nOrigAlpha = bmPsdData.GetColor(nOrig_x, nOrig_y).a(); |
|
bool bInOrOut=nOrigAlpha > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD; |
|
|
|
float flClosest_Dist=1.0e23; |
|
for(int iy=-nSearchRad; iy <= nSearchRad; iy++ ) |
|
{ |
|
for(int ix=-nSearchRad; ix <= nSearchRad; ix++ ) |
|
{ |
|
int cx=max( 0, min( nWidth-1, ix + nOrig_x ) ); |
|
int cy=max( 0, min( nHeight-1, iy + nOrig_y ) ); |
|
|
|
uint8 alphaValue = bmPsdData.GetColor(cx, cy).a(); |
|
bool bIn =( alphaValue > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD ); |
|
if ( bInOrOut != bIn ) // transition? |
|
{ |
|
float flTryDist = sqrt( (float) (ix*ix+iy*iy) ); |
|
flClosest_Dist = min( flClosest_Dist, flTryDist ); |
|
} |
|
} |
|
} |
|
|
|
// now, map signed distance to alpha value |
|
float flOutDist = min( 0.5f, FLerp( 0, .5, 0, flMaxRad, flClosest_Dist ) ); |
|
if ( ! bInOrOut ) |
|
{ |
|
// negative distance |
|
flOutDist = -flOutDist; |
|
} |
|
uint8 &nOutAlpha= tmpDest[3+4*(x+pTexture->Width()*y )]; |
|
nOutAlpha = min( 255.0, 255.0*( 0.5+flOutDist ) ); |
|
if ( ( nOutAlpha != 0 ) && |
|
( |
|
( x == 0 ) || |
|
( y == 0 ) || |
|
( x == pTexture->Width()-1 ) || |
|
( y == pTexture->Height()-1 ) ) ) |
|
{ |
|
bWarnEdges = true; |
|
nOutAlpha = 0; // force it. |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( bWarnEdges ) |
|
{ |
|
VTexWarning( "%s: There are non-zero distance pixels along the image edge. You may need" |
|
" to reduce your distance spread or reduce the image less" |
|
" or add a border to the image.\n", |
|
info.m_SrcName ); |
|
} |
|
} |
|
|
|
|
|
// now, store in dest |
|
ImageLoader::ConvertImageFormat( tmpDest.Base(), IMAGE_FORMAT_RGBA8888, pDestBits, |
|
pTexture->Format(), pTexture->Width(), pTexture->Height(), |
|
0, 0 ); |
|
return true; |
|
|
|
} |
|
else |
|
{ |
|
// Read the PSD file into a bitmap |
|
Bitmap_t bmPsdData; |
|
ok = PSDReadFileRGBA8888( psdBuffer, bmPsdData ); |
|
if ( ok ) |
|
{ |
|
memcpy( pDestBits, bmPsdData.GetBits(), bmPsdData.Height() * bmPsdData.Stride() ); |
|
} |
|
return ok; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads a face from a TGA image |
|
//----------------------------------------------------------------------------- |
|
static bool LoadFaceFromTGA( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
// NOTE: This only works because all mip levels are stored sequentially |
|
// in memory, starting with the highest mip level. It also only works |
|
// because the VTF Texture store *all* mip levels down to 1x1 |
|
|
|
// Get the information from the file... |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
bool ok = TGALoader::GetInfo( tgaBuffer, &nWidth, &nHeight, &imageFormat, &flSrcGamma ); |
|
if (!ok) |
|
return false; |
|
|
|
// Seek back so TGALoader::Load can see the tga header... |
|
tgaBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); |
|
|
|
// Load the tga and create all mipmap levels |
|
unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z ); |
|
|
|
if ( ( info.m_bAlphaToDistance ) || |
|
( nWidth != pTexture->Width() ) || |
|
( nHeight != pTexture->Height() ) ) |
|
{ |
|
// load into temp and resample |
|
CUtlMemory<uint8> tmpImage( 0, nWidth*nHeight*4 ); |
|
if ( ! TGALoader::Load( tmpImage.Base(), tgaBuffer, nWidth, |
|
nHeight, IMAGE_FORMAT_RGBA8888, flGamma, false ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
CUtlMemory<uint8> tmpDest( 0, pTexture->Width() * pTexture->Height() *4 ); |
|
|
|
ImageLoader::ResampleInfo_t resInfo; |
|
resInfo.m_pSrc = tmpImage.Base(); |
|
resInfo.m_pDest = tmpDest.Base(); |
|
resInfo.m_nSrcWidth = nWidth; |
|
resInfo.m_nSrcHeight = nHeight; |
|
resInfo.m_nDestWidth = pTexture->Width(); |
|
resInfo.m_nDestHeight = pTexture->Height(); |
|
resInfo.m_flSrcGamma = flGamma; |
|
resInfo.m_flDestGamma = flGamma; |
|
if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE ) |
|
{ |
|
resInfo.m_nFlags |= ImageLoader::RESAMPLE_NICE_FILTER; |
|
} |
|
|
|
ResampleRGBA8888( resInfo ); |
|
|
|
if ( info.m_bAlphaToDistance ) |
|
{ |
|
float flMaxRad=info.m_flDistanceSpread*2.0*max(info.m_nReduceX,info.m_nReduceY); |
|
int nSearchRad=ceil(flMaxRad); |
|
bool bWarnEdges = false; |
|
// now, do alpha to distance coded stuff |
|
ImageFormatInfo_t fmtInfo=ImageLoader::ImageFormatInfo( pTexture->Format() ); |
|
if ( fmtInfo.m_NumAlphaBits == 0 ) |
|
{ |
|
VTexWarning( "%s: alpha to distance asked for but no alpha channel.\n", info.m_SrcName ); |
|
} |
|
else |
|
{ |
|
for(int x=0; x < pTexture->Width(); x++ ) |
|
{ |
|
for(int y=0; y < pTexture->Height(); y++ ) |
|
{ |
|
// map to original image coords |
|
int nOrig_x=FLerp(0,nWidth-1,0,pTexture->Width()-1,x); |
|
int nOrig_y=FLerp(0,nHeight-1,0,pTexture->Height()-1,y); |
|
|
|
uint8 nOrigAlpha=tmpImage[3+4*(nOrig_x+nWidth*nOrig_y)]; |
|
bool bInOrOut=nOrigAlpha > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD; |
|
|
|
float flClosest_Dist=1.0e23; |
|
for(int iy=-nSearchRad; iy <= nSearchRad; iy++ ) |
|
{ |
|
for(int ix=-nSearchRad; ix <= nSearchRad; ix++ ) |
|
{ |
|
int cx=max( 0, min( nWidth-1, ix + nOrig_x ) ); |
|
int cy=max( 0, min( nHeight-1, iy + nOrig_y ) ); |
|
|
|
int nOffset = 3+ 4 * ( cx + cy * nWidth ); |
|
uint8 alphaValue = tmpImage[nOffset]; |
|
bool bIn =( alphaValue > DISTANCE_CODE_ALPHA_INOUT_THRESHOLD ); |
|
if ( bInOrOut != bIn ) // transition? |
|
{ |
|
float flTryDist = sqrt( (float) (ix*ix+iy*iy) ); |
|
flClosest_Dist = min( flClosest_Dist, flTryDist ); |
|
} |
|
} |
|
} |
|
|
|
// now, map signed distance to alpha value |
|
float flOutDist = min( 0.5f, FLerp( 0, .5, 0, flMaxRad, flClosest_Dist ) ); |
|
if ( ! bInOrOut ) |
|
{ |
|
// negative distance |
|
flOutDist = -flOutDist; |
|
} |
|
uint8 &nOutAlpha= tmpDest[3+4*(x+pTexture->Width()*y )]; |
|
nOutAlpha = min( 255.0, 255.0*( 0.5+flOutDist ) ); |
|
if ( ( nOutAlpha != 0 ) && |
|
( |
|
( x == 0 ) || |
|
( y == 0 ) || |
|
( x == pTexture->Width()-1 ) || |
|
( y == pTexture->Height()-1 ) ) ) |
|
{ |
|
bWarnEdges = true; |
|
nOutAlpha = 0; // force it. |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( bWarnEdges ) |
|
{ |
|
VTexWarning( "%s: There are non-zero distance pixels along the image edge. You may need" |
|
" to reduce your distance spread or reduce the image less" |
|
" or add a border to the image.\n", |
|
info.m_SrcName ); |
|
} |
|
} |
|
|
|
|
|
// now, store in dest |
|
ImageLoader::ConvertImageFormat( tmpDest.Base(), IMAGE_FORMAT_RGBA8888, pDestBits, |
|
pTexture->Format(), pTexture->Width(), pTexture->Height(), |
|
0, 0 ); |
|
return true; |
|
|
|
} |
|
else |
|
{ |
|
return TGALoader::Load( pDestBits, tgaBuffer, pTexture->Width(), |
|
pTexture->Height(), pTexture->Format(), flGamma, false ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads a face from a PNG image |
|
//----------------------------------------------------------------------------- |
|
static bool LoadFaceFromPNG( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
// NOTE: This only works because all mip levels are stored sequentially |
|
// in memory, starting with the highest mip level. It also only works |
|
// because the VTF Texture store *all* mip levels down to 1x1 |
|
|
|
// Get the information from the file... |
|
int nWidth, nHeight; |
|
ImageFormat imageFormat; |
|
float flSrcGamma; |
|
|
|
ConversionErrorType error = CE_SUCCESS; |
|
|
|
// Load the tga and create all mipmap levels |
|
unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z ); |
|
|
|
memcpy( pDestBits, tgaBuffer.Base(), tgaBuffer.TellPut() ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads a face from a PFM image |
|
//----------------------------------------------------------------------------- |
|
// HDRFIXME: How is this different from InitializeSrcTexture_PFM? |
|
static bool LoadFaceFromPFM( IVTFTexture *pTexture, CUtlBuffer &fileBuffer, int z, int nFrame, |
|
int nFace, float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
fileBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); |
|
if( fileBuffer.GetChar() != 'P' ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
if( fileBuffer.GetChar() != 'F' ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
if( fileBuffer.GetChar() != 0xa ) |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
int nWidth, nHeight; |
|
nWidth = ReadIntFromUtlBuffer( fileBuffer ); |
|
EatWhiteSpace( fileBuffer ); |
|
nHeight = ReadIntFromUtlBuffer( fileBuffer ); |
|
|
|
// eat crap until the next newline |
|
while( fileBuffer.IsValid() && fileBuffer.GetChar() != 0xa ) |
|
{ |
|
} |
|
// eat crap until the next newline |
|
while( fileBuffer.IsValid() && fileBuffer.GetChar() != 0xa ) |
|
{ |
|
} |
|
Assert( ImageLoader::SizeInBytes( pTexture->Format() ) == 3 * sizeof( float ) ); |
|
|
|
// Load the pfm and create all mipmap levels |
|
float *pDestBits = ( float * )pTexture->ImageData( nFrame, nFace, 0, 0, 0, z ); |
|
|
|
int y; |
|
for( y = nHeight-1; y >= 0; y-- ) |
|
{ |
|
Assert( fileBuffer.IsValid() ); |
|
fileBuffer.Get( pDestBits + y * nWidth * 3, nWidth * 3 * sizeof( float ) ); |
|
for(int x=0;x<nWidth*3;x++) |
|
pDestBits[x+y*nWidth*3]*=info.m_pfmscale; |
|
} |
|
return true; |
|
} |
|
|
|
static bool LoadFaceFromX( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, |
|
float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
switch ( g_eMode ) |
|
{ |
|
case eModePSD: |
|
return LoadFaceFromPSD( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ); |
|
break; |
|
case eModeTGA: |
|
return LoadFaceFromTGA( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ); |
|
break; |
|
case eModePNG: |
|
return LoadFaceFromPNG( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ); |
|
break; |
|
case eModePFM: |
|
return LoadFaceFromPFM( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ); |
|
break; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
static bool LoadFace( IVTFTexture *pTexture, CUtlBuffer &tgaBuffer, int z, int nFrame, int nFace, |
|
float flGamma, const VTexConfigInfo_t &info ) |
|
{ |
|
if ( !LoadFaceFromX( pTexture, tgaBuffer, z, nFrame, nFace, flGamma, info ) ) |
|
return false; |
|
|
|
// Restricting number of channels by painting white into the rest |
|
if ( info.m_numChannelsMax < 1 || info.m_numChannelsMax > 4 ) |
|
{ |
|
VTexWarning( "%s: Invalid setting restricting number of channels to %d, discarded!\n", info.m_SrcName, info.m_numChannelsMax ); |
|
} |
|
else if ( info.m_numChannelsMax < 4 ) |
|
{ |
|
if ( 4 != ImageLoader::SizeInBytes( pTexture->Format() ) ) |
|
{ |
|
VTexWarning( "%s: Channels restricted to %d, but cannot fill white" |
|
" because pixel format is %d (size in bytes %d)!" |
|
" Proceeding with unmodified channels.\n", |
|
info.m_SrcName, |
|
info.m_numChannelsMax, pTexture->Format(), ImageLoader::SizeInBytes( pTexture->Format() ) ); |
|
Assert( 0 ); |
|
} |
|
else |
|
{ |
|
// Fill other channels with white |
|
|
|
unsigned char *pDestBits = pTexture->ImageData( nFrame, nFace, 0, 0, 0, z ); |
|
int nWidth = pTexture->Width(); |
|
int nHeight = pTexture->Height(); |
|
|
|
int nPaintOff = info.m_numChannelsMax; |
|
int nPaintBytes = 4 - nPaintOff; |
|
|
|
pDestBits += nPaintOff; |
|
|
|
for( int j = 0; j < nHeight; ++ j ) |
|
{ |
|
for ( int k = 0; k < nWidth; ++ k, pDestBits += 4 ) |
|
{ |
|
memset( pDestBits, 0xFF, nPaintBytes ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads source image data |
|
//----------------------------------------------------------------------------- |
|
static bool LoadSourceImages( IVTFTexture *pTexture, const char *pFullNameWithoutExtension, |
|
bool *pbGenerateSphereMaps, |
|
VTexConfigInfo_t &info ) |
|
{ |
|
static char pSrcName[1024]; |
|
|
|
bool bGenerateSpheremaps = false; |
|
|
|
// The input file name here is simply for error reporting |
|
char *pInputFileName = ( char * )stackalloc( strlen( pFullNameWithoutExtension ) + strlen( GetSourceExtension() ) + 1 ); |
|
strcpy( pInputFileName, pFullNameWithoutExtension ); |
|
strcat( pInputFileName, GetSourceExtension() ); |
|
|
|
int nFrameCount; |
|
bool bAnimated = !( info.m_nStartFrame == -1 || info.m_nEndFrame == -1 ); |
|
if( !bAnimated ) |
|
{ |
|
nFrameCount = 1; |
|
} |
|
else |
|
{ |
|
nFrameCount = info.m_nEndFrame - info.m_nStartFrame + 1; |
|
} |
|
|
|
bool bIsCubeMap = (info.m_nFlags & TEXTUREFLAGS_ENVMAP) != 0; |
|
bool bIsVolumeTexture = ( info.m_nVolumeTextureDepth > 1 ); |
|
|
|
// Iterate over all faces of all frames |
|
int nFaceCount = bIsCubeMap ? CUBEMAP_FACE_COUNT : 1; |
|
for( int iFrame = 0; iFrame < nFrameCount; ++iFrame ) |
|
{ |
|
for( int iFace = 0; iFace < nFaceCount; ++iFace ) |
|
{ |
|
for ( int z = 0; z < info.m_nVolumeTextureDepth; ++z ) |
|
{ |
|
// Generate the filename to load.... |
|
MakeSrcFileName( pSrcName, info.m_nFlags, pFullNameWithoutExtension, |
|
iFrame, iFace, bIsVolumeTexture ? z : -1, bIsCubeMap, info.m_nStartFrame, info.m_nEndFrame, info.m_bNormalToDuDv ); |
|
|
|
// Don't fail if the 7th iFace of a cubemap isn't loaded... |
|
// that just means that we're gonna have to build the spheremap ourself. |
|
bool bFailOnError = !bIsCubeMap || (iFace != CUBEMAP_FACE_SPHEREMAP); |
|
|
|
// Load the TGA from disk... |
|
CUtlBuffer tgaBuffer; |
|
if ( !LoadFile( pSrcName, tgaBuffer, bFailOnError, |
|
( g_eMode != eModePSD ) ? &info.m_uiInputHash : NULL ) ) |
|
{ |
|
// If we want to fail on error and VTexError didn't abort then |
|
// simply notify the caller that we failed |
|
if ( bFailOnError ) |
|
return false; |
|
|
|
// The only other way we can get here is if LoadFile tried to load a spheremap and failed |
|
bGenerateSpheremaps = true; |
|
continue; |
|
} |
|
|
|
// Initialize the VTF Texture here if we haven't already.... |
|
// Note that we have to do it here because we have to get the width + height from the file |
|
if (!pTexture->ImageData()) |
|
{ |
|
InitializeSrcTexture( pTexture, pSrcName, tgaBuffer, info.m_nVolumeTextureDepth, nFrameCount, info ); |
|
|
|
// Re-seek back to the front of the buffer so LoadFaceFromTGA works |
|
tgaBuffer.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); |
|
} |
|
|
|
strcpy( info.m_SrcName, pSrcName ); |
|
// NOTE: This here will generate all mip levels of the source image |
|
if (!LoadFace( pTexture, tgaBuffer, z, iFrame, iFace, 2.2, info )) |
|
{ |
|
Error( "Error loading texture %s\n", pInputFileName ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( pbGenerateSphereMaps ) |
|
{ |
|
*pbGenerateSphereMaps = bGenerateSpheremaps; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
void PreprocessSkyBox( char *pFullNameWithoutExtension, int *iSkyboxFace ) |
|
{ |
|
// When we get here, it means that we're processing one face of a skybox, but we're going to |
|
// load all the faces and treat it as a cubemap so we can do the edge matching. |
|
|
|
// Since they passed in only one face of the skybox, there's a 2 letter extension we want to get rid of. |
|
int len = strlen( pFullNameWithoutExtension ); |
|
if ( len >= 3 ) |
|
{ |
|
// Make sure there really is a 2 letter extension. |
|
char *pEnd = &pFullNameWithoutExtension[ len - 2 ]; |
|
*iSkyboxFace = -1; |
|
for ( int i=0; i < ARRAYSIZE( g_CubemapFacingNames ); i++ ) |
|
{ |
|
if ( stricmp( pEnd, g_CubemapFacingNames[i] ) == 0 ) |
|
{ |
|
*iSkyboxFace = i; |
|
break; |
|
} |
|
} |
|
|
|
// Cut off the 2 letter extension. |
|
if ( *iSkyboxFace != -1 ) |
|
{ |
|
pEnd[0] = 0; |
|
return; |
|
} |
|
} |
|
|
|
Error( "PreprocessSkyBox: filename %s doesn't have a proper extension (bk, dn, rt, etc..)\n", pFullNameWithoutExtension ); |
|
} |
|
|
|
|
|
// Right now, we've got a full cubemap, and we want to return the one face of the |
|
// skybox that we're supposed to be processing. |
|
IVTFTexture* PostProcessSkyBox( IVTFTexture *pTexture, int iSkyboxFace ) |
|
{ |
|
int nFlags = pTexture->Flags(); |
|
Assert( nFlags & TEXTUREFLAGS_ENVMAP ); // Should have been treated as an envmap till now. |
|
nFlags &= ~TEXTUREFLAGS_ENVMAP; // But it ends now! |
|
|
|
IVTFTexture *pRet = CreateVTFTexture(); |
|
if ( !pRet->Init( pTexture->Width(), pTexture->Height(), 1, pTexture->Format(), nFlags, pTexture->FrameCount() ) ) |
|
Error( "PostProcessSkyBox: IVTFTexture::Init() failed.\n" ); |
|
|
|
// Now just dump the data for the face we want to keep. |
|
int nMips = min( pTexture->MipCount(), pRet->MipCount() ); |
|
for ( int iMip=0; iMip < nMips; iMip++ ) |
|
{ |
|
int mipSize = pTexture->ComputeMipSize( iMip ); |
|
if ( pRet->ComputeMipSize( iMip ) != mipSize ) |
|
{ |
|
Error( "PostProcessSkyBox: ComputeMipSize differs (src=%d, dest=%d)\n", mipSize, pRet->ComputeMipSize( iMip ) ); |
|
} |
|
|
|
for ( int iFrame=0; iFrame < pTexture->FrameCount(); iFrame++ ) |
|
{ |
|
unsigned char *pDest = pRet->ImageData( iFrame, 0, iMip ); |
|
const unsigned char *pSrc = pTexture->ImageData( iFrame, iSkyboxFace, iMip ); |
|
memcpy( pDest, pSrc, mipSize ); |
|
} |
|
} |
|
|
|
// Note: there are a few things that don't get copied here, like alpha test threshold |
|
// and bumpscale, but we shouldn't need those for skyboxes anyway. |
|
|
|
// Get rid of the full cubemap one and return the single-face one. |
|
DestroyVTFTexture( pTexture ); |
|
return pRet; |
|
} |
|
|
|
|
|
void MakeDirHier( const char *pPath ) |
|
{ |
|
#ifdef POSIX |
|
#define mkdir(s) mkdir(s, S_IRWXU | S_IRWXG | S_IRWXO ) |
|
#endif |
|
char temp[1024]; |
|
Q_strncpy( temp, pPath, 1024 ); |
|
int i; |
|
for( i = 0; i < strlen( temp ); i++ ) |
|
{ |
|
if( temp[i] == '/' || temp[i] == '\\' ) |
|
{ |
|
temp[i] = '\0'; |
|
// DebugOut( "mkdir( %s )\n", temp ); |
|
mkdir( temp ); |
|
temp[i] = CORRECT_PATH_SEPARATOR; |
|
} |
|
} |
|
// DebugOut( "mkdir( %s )\n", temp ); |
|
mkdir( temp ); |
|
} |
|
|
|
|
|
static uint8 GetClampingValue( int nClampSize ) |
|
{ |
|
if ( nClampSize <= 0 ) |
|
return 30; // ~1 billion |
|
int nRet = 0; |
|
while ( nClampSize > 1 ) |
|
{ |
|
nClampSize >>= 1; |
|
nRet++; |
|
} |
|
return nRet; |
|
} |
|
|
|
static void SetTextureLodData( IVTFTexture *pTexture, VTexConfigInfo_t const &info ) |
|
{ |
|
if ( |
|
( info.m_nMaxDimensionX > 0 && info.m_nMaxDimensionX < pTexture->Width() ) || |
|
( info.m_nMaxDimensionY > 0 && info.m_nMaxDimensionY < pTexture->Height() ) || |
|
( info.m_nMaxDimensionX_360 > 0 && info.m_nMaxDimensionX_360 < pTexture->Width() ) || |
|
( info.m_nMaxDimensionY_360 > 0 && info.m_nMaxDimensionY_360 < pTexture->Height() ) |
|
) |
|
{ |
|
TextureLODControlSettings_t lodChunk; |
|
memset( &lodChunk, 0, sizeof( lodChunk ) ); |
|
lodChunk.m_ResolutionClampX = GetClampingValue( info.m_nMaxDimensionX ); |
|
lodChunk.m_ResolutionClampY = GetClampingValue( info.m_nMaxDimensionY ); |
|
lodChunk.m_ResolutionClampX_360 = GetClampingValue( info.m_nMaxDimensionX_360 ); |
|
lodChunk.m_ResolutionClampY_360 = GetClampingValue( info.m_nMaxDimensionY_360 ); |
|
pTexture->SetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, &lodChunk, sizeof( lodChunk ) ); |
|
} |
|
} |
|
|
|
|
|
static void AttachShtFile( const char *pFullNameWithoutExtension, IVTFTexture *pTexture, CRC32_t *puiHash ) |
|
{ |
|
char shtName[MAX_PATH]; |
|
Q_strncpy( shtName, pFullNameWithoutExtension, sizeof(shtName) ); |
|
Q_SetExtension( shtName, ".sht", sizeof(shtName) ); |
|
|
|
struct _stat statBuf; |
|
if( _stat( shtName, &statBuf ) == -1 ) |
|
return; |
|
|
|
printf( "Attaching .sht file %s.\n", shtName ); |
|
|
|
// Ok, the file exists. Read it. |
|
CUtlBuffer buf; |
|
if ( !LoadFile( shtName, buf, false, puiHash ) ) |
|
return; |
|
|
|
pTexture->SetResourceData( VTF_RSRC_SHEET, buf.Base(), buf.TellPut() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Does the dirty deed and generates a VTF file |
|
//----------------------------------------------------------------------------- |
|
bool ProcessFiles( const char *pFullNameWithoutExtension, |
|
const char *pOutputDir, const char *pBaseName, |
|
bool isCubeMap, VTexConfigInfo_t &info ) |
|
{ |
|
// force clamps/clampt for cube maps |
|
if( isCubeMap ) |
|
{ |
|
info.m_nFlags |= TEXTUREFLAGS_ENVMAP; |
|
info.m_nFlags |= TEXTUREFLAGS_CLAMPS; |
|
info.m_nFlags |= TEXTUREFLAGS_CLAMPT; |
|
} |
|
|
|
// Create the texture we're gonna store out |
|
SmartIVtfTexture pVTFTexture( CreateVTFTexture() ); |
|
|
|
int iSkyboxFace = 0; |
|
char fullNameTemp[512]; |
|
if ( info.m_bSkyBox ) |
|
{ |
|
Q_strncpy( fullNameTemp, pFullNameWithoutExtension, sizeof( fullNameTemp ) ); |
|
pFullNameWithoutExtension = fullNameTemp; |
|
PreprocessSkyBox( fullNameTemp, &iSkyboxFace ); |
|
} |
|
|
|
// Load the source images into the texture |
|
bool bGenerateSpheremaps = false; |
|
bool bLoadedSourceImages = LoadSourceImages( pVTFTexture.Get(), |
|
pFullNameWithoutExtension, &bGenerateSpheremaps, info ); |
|
if ( !bLoadedSourceImages ) |
|
{ |
|
VTexError( "Can't load source images for \"%s\"\n", pFullNameWithoutExtension ); |
|
return false; |
|
} |
|
|
|
// Attach a sheet file if present |
|
AttachShtFile( pFullNameWithoutExtension, pVTFTexture.Get(), &info.m_uiInputHash ); |
|
|
|
// No more file loads, finalize the sources hash |
|
CRC32_Final( &info.m_uiInputHash ); |
|
pVTFTexture->SetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &info.m_uiInputHash, sizeof( info.m_uiInputHash ) ); |
|
CRC32_t crcWritten = info.m_uiInputHash; |
|
|
|
// Name of the destination file |
|
char dstFileName[1024]; |
|
sprintf( dstFileName, "%s/%s%s.vtf", pOutputDir, pBaseName, ( ( eModePFM == g_eMode ) && isCubeMap ) ? ".hdr" : "" ); |
|
|
|
// Now if we are only validating the CRC |
|
if( CommandLine()->FindParm( "-crcvalidate" ) ) |
|
{ |
|
CUtlBuffer bufFile; |
|
bool bLoad = LoadFile( dstFileName, bufFile, false, NULL ); |
|
if ( !bLoad ) |
|
{ |
|
fprintf( stderr, "LOAD ERROR: %s\n", dstFileName ); |
|
return false; |
|
} |
|
|
|
SmartIVtfTexture spExistingVtf( CreateVTFTexture() ); |
|
bLoad = spExistingVtf->Unserialize( bufFile ); |
|
if ( !bLoad ) |
|
{ |
|
fprintf( stderr, "UNSERIALIZE ERROR: %s\n", dstFileName ); |
|
return false; |
|
} |
|
|
|
size_t numDataBytes; |
|
void *pCrcData = spExistingVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &numDataBytes ); |
|
if ( !pCrcData || numDataBytes != sizeof( CRC32_t ) ) |
|
{ |
|
fprintf( stderr, "OLD TEXTURE FORMAT: %s\n", dstFileName ); |
|
return false; |
|
} |
|
|
|
CRC32_t crcFile = * reinterpret_cast< CRC32_t const * >( pCrcData ); |
|
if ( crcFile != crcWritten ) |
|
{ |
|
fprintf( stderr, "CRC MISMATCH: %s\n", dstFileName ); |
|
return false; |
|
} |
|
|
|
fprintf( stderr, "OK: %s\n", dstFileName ); |
|
return true; |
|
} |
|
|
|
// Now if we are not forcing the CRC |
|
if( !CommandLine()->FindParm( "-crcforce" ) ) |
|
{ |
|
CUtlBuffer bufFile; |
|
if ( LoadFile( dstFileName, bufFile, false, NULL ) ) |
|
{ |
|
SmartIVtfTexture spExistingVtf( CreateVTFTexture() ); |
|
if ( spExistingVtf->Unserialize( bufFile ) ) |
|
{ |
|
size_t numDataBytes; |
|
void *pCrcData = spExistingVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, &numDataBytes ); |
|
if ( pCrcData && numDataBytes == sizeof( CRC32_t ) ) |
|
{ |
|
CRC32_t crcFile = * reinterpret_cast< CRC32_t const * >( pCrcData ); |
|
if ( crcFile == crcWritten ) |
|
{ |
|
if( !g_Quiet ) |
|
printf( "SUCCESS: %s is up-to-date\n", dstFileName ); |
|
|
|
if( !CommandLine()->FindParm( "-crcforce" ) ) |
|
return true; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Bumpmap scale.. |
|
pVTFTexture->SetBumpScale( info.m_flBumpScale ); |
|
|
|
// Alphatest threshhold |
|
pVTFTexture->SetAlphaTestThreshholds( info.m_flAlphaThreshhold, info.m_flAlphaHiFreqThreshhold ); |
|
|
|
// Set texture lod data |
|
SetTextureLodData( pVTFTexture.Get(), info ); |
|
|
|
// Get the texture all internally consistent and happy |
|
bool bAllowFixCubemapOrientation = !info.m_bSkyBox; // Don't let it rotate our pseudo-cubemap faces around if it's a skybox. |
|
pVTFTexture->SetPostProcessingSettings( &info.m_vtfProcOptions ); |
|
pVTFTexture->PostProcess( bGenerateSpheremaps, info.m_LookDir, bAllowFixCubemapOrientation ); |
|
|
|
// Compute the preferred image format |
|
ImageFormat vtfImageFormat = ComputeDesiredImageFormat( pVTFTexture.Get(), info ); |
|
|
|
// Set up the low-res image |
|
if (pVTFTexture->IsCubeMap()) |
|
{ |
|
// "Stage 1" of matching cubemap borders. Sometimes, it has to store off the original image. |
|
pVTFTexture->MatchCubeMapBorders( 1, vtfImageFormat, info.m_bSkyBox ); |
|
} |
|
else |
|
{ |
|
CreateLowResImage( pVTFTexture.Get() ); |
|
} |
|
|
|
// Convert to the final format |
|
pVTFTexture->ConvertImageFormat( vtfImageFormat, info.m_bNormalToDuDv ); |
|
|
|
// Stage 2 of matching cubemap borders. |
|
pVTFTexture->MatchCubeMapBorders( 2, vtfImageFormat, info.m_bSkyBox ); |
|
|
|
if ( info.m_bSkyBox ) |
|
{ |
|
pVTFTexture.Assign( PostProcessSkyBox( pVTFTexture.Get(), iSkyboxFace ) ); |
|
} |
|
|
|
if ( info.IsSettings0Valid() ) |
|
{ |
|
pVTFTexture->SetResourceData( VTF_RSRC_TEXTURE_SETTINGS_EX, &info.m_exSettings0, sizeof( info.m_exSettings0 ) ); |
|
} |
|
|
|
// Write it! |
|
if ( g_CreateDir ) |
|
MakeDirHier( pOutputDir ); //It'll create it if it doesn't exist. |
|
|
|
// Make sure the CRC hasn't been modified since finalized |
|
Assert( crcWritten == info.m_uiInputHash ); |
|
|
|
CUtlBuffer outputBuf; |
|
if (!pVTFTexture->Serialize( outputBuf )) |
|
{ |
|
VTexError( "ERROR: \"%s\": Unable to serialize the VTF file!\n", dstFileName ); |
|
} |
|
|
|
{ |
|
FILE *fp = fopen( dstFileName, "wb" ); |
|
if( !fp ) |
|
{ |
|
VTexError( "Can't open: %s\n", dstFileName ); |
|
} |
|
fwrite( outputBuf.Base(), 1, outputBuf.TellPut(), fp ); |
|
fclose( fp ); |
|
} |
|
|
|
printf("SUCCESS: Vtf file created\n"); |
|
return true; |
|
} |
|
|
|
const char *GetPossiblyQuotedWord( const char *pInBuf, char *pOutbuf ) |
|
{ |
|
pInBuf += strspn( pInBuf, " \t" ); // skip whitespace |
|
|
|
const char *pWordEnd; |
|
bool bQuote = false; |
|
if (pInBuf[0]=='"') |
|
{ |
|
pInBuf++; |
|
pWordEnd=strchr(pInBuf,'"'); |
|
bQuote = true; |
|
} |
|
else |
|
{ |
|
pWordEnd=strchr(pInBuf,' '); |
|
if (! pWordEnd ) |
|
pWordEnd = strchr(pInBuf,'\t' ); |
|
if (! pWordEnd ) |
|
pWordEnd = pInBuf+strlen(pInBuf); |
|
} |
|
if ((! pWordEnd ) || (pWordEnd == pInBuf ) ) |
|
return NULL; // no word found |
|
memcpy( pOutbuf, pInBuf, pWordEnd-pInBuf ); |
|
pOutbuf[pWordEnd-pInBuf]=0; |
|
|
|
pInBuf = pWordEnd; |
|
if ( bQuote ) |
|
pInBuf++; |
|
return pInBuf; |
|
} |
|
|
|
// GetKeyValueFromBuffer: |
|
// fills in "key" and "val" respectively and returns "true" if succeeds. |
|
// returns false if: |
|
// a) end-of-buffer is reached (then "val" is empty) |
|
// b) error occurs (then "val" is the error message) |
|
// |
|
static bool GetKeyValueFromBuffer( CUtlBuffer &buffer, char *key, char *val ) |
|
{ |
|
char buf[2048]; |
|
|
|
while( buffer.GetBytesRemaining() ) |
|
{ |
|
buffer.GetLine( buf, sizeof( buf ) ); |
|
|
|
// Scanning algorithm |
|
char *pComment = strpbrk( buf, "#\n\r" ); |
|
if ( pComment ) |
|
*pComment = 0; |
|
|
|
pComment = strstr( buf, "//" ); |
|
if ( pComment) |
|
*pComment = 0; |
|
|
|
const char *scan = buf; |
|
scan=GetPossiblyQuotedWord( scan, key ); |
|
if ( scan ) |
|
{ |
|
scan=GetPossiblyQuotedWord( scan, val ); |
|
if ( scan ) |
|
return true; |
|
else |
|
{ |
|
sprintf( val, "parameter %s has no value", key ); |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
val[0] = 0; |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Loads the .psd file or .txt file associated with the .tga and gets out various data |
|
//----------------------------------------------------------------------------- |
|
static bool LoadConfigFile( const char *pFileBaseName, VTexConfigInfo_t &info, bool *isCubeMap ) |
|
{ |
|
// Tries to load .txt, then .psd |
|
|
|
int lenBaseName = strlen( pFileBaseName ); |
|
char *pFileName = ( char * )stackalloc( lenBaseName + strlen( ".tga" ) + 1 ); |
|
strcpy( pFileName, pFileBaseName ); |
|
strcat( pFileName, ".tga" ); |
|
|
|
bool bOK = false; |
|
|
|
info.m_LookDir = LOOK_DOWN_Z; |
|
|
|
// Try TGA file with config |
|
memcpy( pFileName + lenBaseName, ".tga", 4 ); |
|
if ( !bOK && ( 00 == access( pFileName, 00 ) ) ) // TGA file exists |
|
{ |
|
g_eMode = eModeTGA; |
|
|
|
memcpy( pFileName + lenBaseName, ".txt", 4 ); |
|
CUtlBuffer bufFile( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash ); |
|
if ( bOK ) |
|
{ |
|
printf("config file %s\n",pFileName); |
|
|
|
{ |
|
char key[2048]; |
|
char val[2048]; |
|
while( GetKeyValueFromBuffer( bufFile, key, val ) ) |
|
{ |
|
info.ParseOptionKey( key, val ); |
|
} |
|
|
|
if ( val[0] ) |
|
{ |
|
VTexError( "%s: %s\n", pFileName, val ); |
|
return false; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
memcpy( pFileName + lenBaseName, ".tga", 4 ); |
|
printf("no config file for %s\n",pFileName); |
|
bOK = true; |
|
} |
|
} |
|
memcpy( pFileName + lenBaseName, ".tga", 4 ); |
|
|
|
// PSD file attempt |
|
memcpy( pFileName + lenBaseName, ".psd", 4 ); |
|
if ( !bOK && ( 00 == access( pFileName, 00 ) ) ) // If PSD mode was not disabled |
|
{ |
|
g_eMode = eModePSD; |
|
|
|
CUtlBuffer bufFile; |
|
bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash ); |
|
if ( bOK ) |
|
{ |
|
printf("config file %s\n", pFileName); |
|
bOK = IsPSDFile( bufFile ); |
|
if ( !bOK ) |
|
{ |
|
VTexError( "%s is not a valid PSD file!\n", pFileName ); |
|
return false; |
|
} |
|
|
|
PSDImageResources imgres = PSDGetImageResources( bufFile ); |
|
PSDResFileInfo resFileInfo( imgres.FindElement( PSDImageResources::eResFileInfo ) ); |
|
PSDResFileInfo::ResFileInfoElement descr = resFileInfo.FindElement( PSDResFileInfo::eDescription ); |
|
if ( descr.m_pvData ) |
|
{ |
|
CUtlBuffer bufDescr( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
bufDescr.EnsureCapacity( descr.m_numBytes ); |
|
bufDescr.Put( descr.m_pvData, descr.m_numBytes ); |
|
|
|
{ |
|
char key[2048]; |
|
char val[2048]; |
|
while( GetKeyValueFromBuffer( bufDescr, key, val ) ) |
|
{ |
|
info.ParseOptionKey( key, val ); |
|
} |
|
|
|
if ( val[0] ) |
|
{ |
|
VTexError( "%s: %s\n", pFileName, val ); |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// PNG file attempt |
|
memcpy( pFileName + lenBaseName, ".png", 4 ); |
|
if ( !bOK ) // If PNG mode was not disabled |
|
{ |
|
g_eMode = eModePNG; |
|
info.m_nFlags |= TEXTUREFLAGS_NOMIP; |
|
bOK = true; |
|
} |
|
|
|
// Try TXT file as config again for TGA cubemap / PFM |
|
memcpy( pFileName + lenBaseName, ".txt", 4 ); |
|
if ( !bOK ) |
|
{ |
|
g_eMode = eModeTGA; |
|
|
|
CUtlBuffer bufFile( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
bOK = LoadFile( pFileName, bufFile, false, &info.m_uiInputHash ); |
|
if ( bOK ) |
|
{ |
|
printf("config file %s\n",pFileName); |
|
|
|
{ |
|
char key[2048]; |
|
char val[2048]; |
|
while( GetKeyValueFromBuffer( bufFile, key, val ) ) |
|
{ |
|
info.ParseOptionKey( key, val ); |
|
} |
|
|
|
if ( val[0] ) |
|
{ |
|
VTexError( "%s: %s\n", pFileName, val ); |
|
return false; |
|
} |
|
} |
|
|
|
if ( g_eMode == eModePFM ) |
|
{ |
|
if ( g_bUsedAsLaunchableDLL && !( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NOCOMPRESS ) ) |
|
{ |
|
info.m_nFlags |= TEXTUREFLAGS_NOMIP; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if ( !bOK ) |
|
{ |
|
VTexError( "\"%s\" does not specify valid PSD or TGA or PFM+TXT files!\n"); |
|
return false; |
|
} |
|
|
|
if ( info.m_bIsCubeMap ) |
|
*isCubeMap = true; |
|
|
|
if( ( info.m_bNormalToDuDv || ( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV ) ) && |
|
!( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP ) ) |
|
{ |
|
printf( "Implicitly setting premultcolorbyoneovermiplevel since you are generating a dudv map\n" ); |
|
info.m_vtfProcOptions.flags0 |= VtfProcessingOptions::OPT_PREMULT_COLOR_ONEOVERMIP; |
|
} |
|
|
|
if( ( info.m_bNormalToDuDv || ( info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_NORMAL_DUDV ) ) ) |
|
{ |
|
printf( "Implicitly setting trilinear since you are generating a dudv map\n" ); |
|
info.m_nFlags |= TEXTUREFLAGS_TRILINEAR; |
|
} |
|
|
|
if( Q_stristr( pFileBaseName, "_normal" ) ) |
|
{ |
|
if( !( info.m_nFlags & TEXTUREFLAGS_NORMAL ) ) |
|
{ |
|
if( !g_Quiet ) |
|
{ |
|
fprintf( stderr, "implicitly setting:\n" ); |
|
fprintf( stderr, "\t\"normal\" \"1\"\n" ); |
|
fprintf( stderr, "since filename ends in \"_normal\"\n" ); |
|
} |
|
info.m_nFlags |= TEXTUREFLAGS_NORMAL; |
|
} |
|
} |
|
|
|
if( Q_stristr( pFileBaseName, "ssbump" ) ) |
|
{ |
|
if( !( info.m_nFlags & TEXTUREFLAGS_SSBUMP ) ) |
|
{ |
|
if( !g_Quiet ) |
|
{ |
|
fprintf( stderr, "implicitly setting:\n" ); |
|
fprintf( stderr, "\t\"ssbump\" \"1\"\n" ); |
|
fprintf( stderr, "since filename includes \"ssbump\"\n" ); |
|
} |
|
info.m_nFlags |= TEXTUREFLAGS_SSBUMP; |
|
} |
|
} |
|
|
|
if( Q_stristr( pFileBaseName, "_dudv" ) ) |
|
{ |
|
if( !info.m_bNormalToDuDv && !info.m_bDuDv ) |
|
{ |
|
if( !g_Quiet ) |
|
{ |
|
fprintf( stderr, "Implicitly setting:\n" ); |
|
fprintf( stderr, "\t\"dudv\" \"1\"\n" ); |
|
fprintf( stderr, "since filename ends in \"_dudv\"\n" ); |
|
fprintf( stderr, "If you are trying to convert from a normal map to a dudv map, put \"normaltodudv\" \"1\" in description.\n" ); |
|
} |
|
info.m_bDuDv = true; |
|
} |
|
} |
|
|
|
// turn off nice filtering if we are a cube map (takes too long with buildcubemaps) or |
|
// if we are a normal map (looks like terd.) |
|
if( ( info.m_nFlags & TEXTUREFLAGS_NORMAL ) || *isCubeMap ) |
|
{ |
|
if (info.m_vtfProcOptions.flags0 & VtfProcessingOptions::OPT_FILTER_NICE ) |
|
{ |
|
if ( !g_Quiet ) |
|
{ |
|
fprintf( stderr, "implicity disabling nice filtering\n" ); |
|
} |
|
} |
|
info.m_vtfProcOptions.flags0 &= ~VtfProcessingOptions::OPT_FILTER_NICE; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void Usage( void ) |
|
{ |
|
VTexError( |
|
"Usage: vtex [-quiet] [-mkdir] [-shader ShaderName] [-vmtparam Param Value] tex1.txt tex2.txt . . .\n" |
|
"-quiet : don't print anything out, don't pause for input\n" |
|
"-warningsaserrors : treat warnings as errors\n" |
|
"-nomkdir : don't create destination folder if it doesn't exist\n" |
|
"-vmtparam : adds parameter and value to the .vmt file\n" |
|
"-deducepath : deduce path of sources by target file names\n" |
|
"-quickconvert : use with \"-dontusegamedir -quickconvert\" to upgrade old .vmt files\n" |
|
"-crcvalidate : validate .vmt against the sources\n" |
|
"-crcforce : generate a new .vmt even if sources crc matches\n" |
|
"\teg: -vmtparam $ignorez 1 -vmtparam $translucent 1\n" |
|
"Note that you can use wildcards and that you can also chain them\n" |
|
"e.g. materialsrc/monster1/*.tga materialsrc/monster2/*.tga\n" ); |
|
} |
|
|
|
bool GetOutputDir( const char *inputName, char *outputDir ) |
|
{ |
|
if ( g_ForcedOutputDir[0] ) |
|
{ |
|
strcpy( outputDir, g_ForcedOutputDir ); |
|
} |
|
else |
|
{ |
|
// Is inputName a relative path? |
|
char buf[MAX_PATH]; |
|
Q_MakeAbsolutePath( buf, sizeof( buf ), inputName, NULL ); |
|
Q_FixSlashes( buf ); |
|
|
|
char szSearch[MAX_PATH] = { 0 }; |
|
V_snprintf( szSearch, sizeof( szSearch ), "materialsrc%c", CORRECT_PATH_SEPARATOR ); |
|
const char *pTmp = Q_stristr( buf, szSearch ); |
|
if( !pTmp ) |
|
{ |
|
return false; |
|
} |
|
pTmp += strlen( "materialsrc/" ); |
|
strcpy( outputDir, gamedir ); |
|
strcat( outputDir, "materials/" ); |
|
strcat( outputDir, pTmp ); |
|
Q_StripFilename( outputDir ); |
|
} |
|
if( !g_Quiet ) |
|
{ |
|
printf( "output directory: %s\n", outputDir ); |
|
} |
|
return true; |
|
} |
|
|
|
bool IsCube( const char *inputName ) |
|
{ |
|
char tgaName[MAX_PATH]; |
|
// Do Strcmp for ".hdr" to make sure we aren't ripping too much stuff off. |
|
Q_StripExtension( inputName, tgaName, MAX_PATH ); |
|
const char *pInputExtension = inputName + Q_strlen( tgaName ); |
|
Q_strncat( tgaName, "rt", MAX_PATH, COPY_ALL_CHARACTERS ); |
|
Q_strncat( tgaName, pInputExtension, MAX_PATH, COPY_ALL_CHARACTERS ); |
|
Q_strncat( tgaName, GetSourceExtension(), MAX_PATH, COPY_ALL_CHARACTERS ); |
|
struct _stat buf; |
|
if( _stat( tgaName, &buf ) != -1 ) |
|
{ |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
#ifdef WIN32 |
|
int Find_Files( WIN32_FIND_DATA &wfd, HANDLE &hResult, const char *basedir, const char *extension ) |
|
{ |
|
char filename[MAX_PATH] = {0}; |
|
|
|
BOOL bMoreFiles = FindNextFile( hResult, &wfd); |
|
|
|
if ( bMoreFiles ) |
|
{ |
|
// Skip . and .. |
|
if ( wfd.cFileName[0] == '.' ) |
|
{ |
|
return FF_TRYAGAIN; |
|
} |
|
|
|
// If it's a subdirectory, just recurse down it |
|
if ( (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) ) |
|
{ |
|
char subdir[MAX_PATH]; |
|
sprintf( subdir, "%s\\%s", basedir, wfd.cFileName ); |
|
|
|
// Recurse |
|
Find_Files( wfd, hResult, basedir, extension ); |
|
return FF_TRYAGAIN; |
|
} |
|
|
|
// Check that it's a tga |
|
// |
|
|
|
char fname[_MAX_FNAME] = {0}; |
|
char ext[_MAX_EXT] = {0}; |
|
|
|
_splitpath( wfd.cFileName, NULL, NULL, fname, ext ); |
|
|
|
// Not the type we want. |
|
if ( stricmp( ext, extension ) ) |
|
return FF_DONTPROCESS; |
|
|
|
// Check for .vmt |
|
sprintf( filename, "%s\\%s.vmt", basedir, fname ); |
|
// Exists, so don't overwrite it |
|
if ( access( filename, 0 ) != -1 ) |
|
return FF_TRYAGAIN; |
|
|
|
char texturename[ _MAX_PATH ] = {0}; |
|
char *p = ( char * )basedir; |
|
|
|
// Skip over the base path to get a material system relative path |
|
// p += strlen( wfd.cFileName ) + 1; |
|
|
|
// Construct texture name |
|
sprintf( texturename, "%s\\%s", p, fname ); |
|
|
|
// Convert all to lower case |
|
strlwr( texturename ); |
|
strlwr( filename ); |
|
} |
|
|
|
return bMoreFiles; |
|
} |
|
#endif |
|
|
|
bool Process_File( char *pInputBaseName, int maxlen ) |
|
{ |
|
char outputDir[1024]; |
|
Q_FixSlashes( pInputBaseName, '/' ); |
|
Q_StripExtension( pInputBaseName, pInputBaseName, maxlen ); |
|
|
|
if ( CommandLine()->FindParm( "-deducepath" ) ) |
|
{ |
|
strcpy( outputDir, pInputBaseName ); |
|
|
|
// If it is not a full path, try making it a full path |
|
if ( pInputBaseName[0] != '/' && |
|
pInputBaseName[1] != ':' ) |
|
{ |
|
// Convert to full path |
|
getcwd( outputDir, sizeof( outputDir ) ); |
|
Q_FixSlashes( outputDir, '/' ); |
|
Q_strncat( outputDir, "/", sizeof( outputDir ) ); |
|
Q_strncat( outputDir, pInputBaseName, sizeof( outputDir ) ); |
|
} |
|
|
|
// If it is pointing inside "/materials/" make it go for "/materialsrc/" |
|
char *pGame = strstr( outputDir, "/game/" ); |
|
char *pMaterials = strstr( outputDir, "/materials/" ); |
|
if ( pGame && pMaterials && ( pGame < pMaterials ) ) |
|
{ |
|
// "u:/data/game/tf/materials/" -> "u:/data/content/tf/materialsrc/" |
|
int numExtraBytes = strlen( "/content/.../materialsrc/" ) - strlen( "/game/.../materials/" ); |
|
int numConvertBytes = pMaterials + strlen( "/materials/" ) - outputDir; |
|
memmove( outputDir + numConvertBytes + numExtraBytes, outputDir + numConvertBytes, strlen( outputDir ) - numConvertBytes + 1 ); |
|
|
|
int numMidBytes = pMaterials - pGame - strlen( "/game" ); |
|
memmove( pGame + strlen( "/content" ), pGame + strlen( "/game" ), numMidBytes ); |
|
|
|
memmove( pGame, "/content", strlen( "/content" ) ); |
|
memmove( pGame + strlen( "/content" ) + numMidBytes, "/materialsrc/", strlen( "/materialsrc/" ) ); |
|
} |
|
|
|
Q_strncpy( pInputBaseName, outputDir, maxlen ); |
|
} |
|
|
|
if( !g_Quiet ) |
|
{ |
|
printf( "input file: %s\n", pInputBaseName ); |
|
} |
|
|
|
if(g_UseGameDir && !GetOutputDir( pInputBaseName, outputDir ) ) |
|
{ |
|
VTexError( "Problem figuring out outputdir for %s\n", pInputBaseName ); |
|
return FALSE; |
|
} |
|
else if (!g_UseGameDir) |
|
{ |
|
strcpy(outputDir, pInputBaseName); |
|
Q_StripFilename(outputDir); |
|
} |
|
|
|
// Usage: |
|
// vtex -dontusegamedir -quickconvert u:\data\game\tf\texture.vtf |
|
// Will read the old texture format and write the new texture format |
|
// |
|
if ( CommandLine()->FindParm( "-quickconvert" ) ) |
|
{ |
|
printf( "Quick convert of '%s'...\n", pInputBaseName ); |
|
|
|
char chFileNameConvert[ 512 ]; |
|
sprintf( chFileNameConvert, "%s.vtf", pInputBaseName ); |
|
|
|
IVTFTexture *pVtf = CreateVTFTexture(); |
|
CUtlBuffer bufFile; |
|
LoadFile( chFileNameConvert, bufFile, true, NULL ); |
|
bool bRes = pVtf->Unserialize( bufFile ); |
|
if ( !bRes ) |
|
VTexError( "Failed to read '%s'!\n", chFileNameConvert ); |
|
|
|
// Determine the CRC if it was there |
|
// CRC32_t uiDataHash = 0; |
|
// CRC32_t *puiDataHash = &uiDataHash; |
|
// Assert( sizeof( uiDataHash ) == sizeof( int ) ); |
|
// if ( !pVtf->GetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, ... ) ) |
|
|
|
AttachShtFile( pInputBaseName, pVtf, NULL ); |
|
|
|
// Update the CRC |
|
// if ( puiDataHash ) |
|
// { |
|
// pVtf->InitResourceDataSection( VTexConfigInfo_t::VTF_INPUTSRC_CRC, *puiDataHash ); |
|
// } |
|
// Remove the CRC when quick-converting |
|
pVtf->SetResourceData( VTexConfigInfo_t::VTF_INPUTSRC_CRC, NULL, 0 ); |
|
|
|
bufFile.Clear(); |
|
bRes = pVtf->Serialize( bufFile ); |
|
if ( !bRes ) |
|
VTexError( "Failed to write '%s'!\n", chFileNameConvert ); |
|
|
|
DestroyVTFTexture( pVtf ); |
|
|
|
if ( FILE *fw = fopen( chFileNameConvert, "wb" ) ) |
|
{ |
|
fwrite( bufFile.Base(), 1, bufFile.TellPut(), fw ); |
|
fclose( fw ); |
|
} |
|
else |
|
VTexError( "Failed to open '%s' for writing!\n", chFileNameConvert ); |
|
|
|
printf( "... succeeded.\n" ); |
|
|
|
return TRUE; |
|
} |
|
|
|
VTexConfigInfo_t info; |
|
bool isCubeMap = false; |
|
if ( !LoadConfigFile( pInputBaseName, info, &isCubeMap ) ) |
|
return FALSE; |
|
|
|
if( !isCubeMap ) |
|
{ |
|
isCubeMap = IsCube( pInputBaseName ); |
|
} |
|
|
|
if( ( info.m_nStartFrame == -1 && info.m_nEndFrame != -1 ) || |
|
( info.m_nStartFrame != -1 && info.m_nEndFrame == -1 ) ) |
|
{ |
|
VTexError( "%s: If you use startframe, you must use endframe, and vice versa.\n", pInputBaseName ); |
|
return FALSE; |
|
} |
|
|
|
const char *pBaseName = &pInputBaseName[strlen( pInputBaseName ) - 1]; |
|
while( (pBaseName >= pInputBaseName) && *pBaseName != '\\' && *pBaseName != '/' ) |
|
{ |
|
pBaseName--; |
|
} |
|
pBaseName++; |
|
|
|
bool bProcessedFilesOK = ProcessFiles( pInputBaseName, outputDir, pBaseName, isCubeMap, info ); |
|
if ( !bProcessedFilesOK ) |
|
return FALSE; |
|
|
|
// create vmts if necessary |
|
if( g_ShaderName ) |
|
{ |
|
char buf[1024]; |
|
sprintf( buf, "%s/%s.vmt", outputDir, pBaseName ); |
|
const char *tmp = Q_stristr( outputDir, "materials" ); |
|
FILE *fp; |
|
if( tmp ) |
|
{ |
|
// check if the file already exists. |
|
fp = fopen( buf, "r" ); |
|
if( fp ) |
|
{ |
|
if ( !g_Quiet ) |
|
fprintf( stderr, "vmt file \"%s\" already exists\n", buf ); |
|
|
|
fclose( fp ); |
|
} |
|
else |
|
{ |
|
fp = fopen( buf, "w" ); |
|
if( fp ) |
|
{ |
|
if ( !g_Quiet ) |
|
fprintf( stderr, "Creating vmt file: %s/%s\n", tmp, pBaseName ); |
|
tmp += strlen( "materials/" ); |
|
fprintf( fp, "\"%s\"\n", g_ShaderName ); |
|
fprintf( fp, "{\n" ); |
|
fprintf( fp, "\t\"$baseTexture\" \"%s/%s\"\n", tmp, pBaseName ); |
|
|
|
int i; |
|
for( i=0;i<g_NumVMTParams;i++ ) |
|
{ |
|
fprintf( fp, "\t\"%s\" \"%s\"\n", g_VMTParams[i].m_szParam, g_VMTParams[i].m_szValue ); |
|
} |
|
|
|
fprintf( fp, "}\n" ); |
|
fclose( fp ); |
|
} |
|
else |
|
{ |
|
VTexWarning( "Couldn't open \"%s\" for writing\n", buf ); |
|
} |
|
} |
|
|
|
} |
|
else |
|
{ |
|
VTexWarning( "Couldn't find \"materials/\" in output path\n", buf ); |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
static SpewRetval_t VTexOutputFunc( SpewType_t spewType, char const *pMsg ) |
|
{ |
|
printf( "%s", pMsg ); |
|
if (spewType == SPEW_ERROR) |
|
{ |
|
return SPEW_ABORT; |
|
} |
|
return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE; |
|
} |
|
|
|
|
|
class CVTex : public CTier2AppSystem< IVTex >, public ILaunchableDLL |
|
{ |
|
public: |
|
int VTex( int argc, char **argv ); |
|
|
|
// ILaunchableDLL, used by vtex.exe. |
|
virtual int main( int argc, char **argv ) |
|
{ |
|
g_bUsedAsLaunchableDLL = true; |
|
|
|
// Being used as a launchable DLL, we don't want to blow away the host app's command line |
|
CUtlString strOrigCmdLine( CommandLine()->GetCmdLine() ); |
|
|
|
// Run the vtex logic |
|
int iResult = VTex( argc, argv ); |
|
|
|
// Restore command line |
|
CommandLine()->CreateCmdLine( strOrigCmdLine.Get() ); |
|
|
|
return iResult; |
|
} |
|
|
|
virtual int VTex( CreateInterfaceFn fsFactory, const char *pGameDir, int argc, char **argv ) |
|
{ |
|
g_pFileSystem = g_pFullFileSystem = (IFileSystem*)fsFactory( FILESYSTEM_INTERFACE_VERSION, NULL ); |
|
if ( !g_pFileSystem ) |
|
{ |
|
Error( "IVTex3::VTex - fsFactory can't get '%s' interface.", FILESYSTEM_INTERFACE_VERSION ); |
|
return 0; |
|
} |
|
|
|
Q_strncpy( gamedir, pGameDir, sizeof( gamedir ) ); |
|
Q_AppendSlash( gamedir, sizeof( gamedir ) ); |
|
|
|
// When being used embedded in a host app, we don't want to blow away the host app's command line |
|
CUtlString strOrigCmdLine( CommandLine()->GetCmdLine() ); |
|
|
|
int iResult = VTex( argc, argv ); |
|
|
|
// Restore command line |
|
CommandLine()->CreateCmdLine( strOrigCmdLine.Get() ); |
|
|
|
return iResult; |
|
} |
|
}; |
|
|
|
static class CSuggestGameDirHelper |
|
{ |
|
public: |
|
static bool SuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ); |
|
bool MySuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ); |
|
|
|
public: |
|
CSuggestGameDirHelper() : m_pszInputFiles( NULL ), m_numInputFiles( 0 ) {} |
|
|
|
public: |
|
char const * const *m_pszInputFiles; |
|
size_t m_numInputFiles; |
|
} g_suggestGameDirHelper; |
|
|
|
bool CSuggestGameDirHelper::SuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ) |
|
{ |
|
return g_suggestGameDirHelper.MySuggestFn( pFsSteamSetupInfo, pchPathBuffer, nBufferLength, pbBubbleDirectories ); |
|
} |
|
|
|
bool CSuggestGameDirHelper::MySuggestFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ) |
|
{ |
|
if ( !m_numInputFiles || !m_pszInputFiles ) |
|
return false; |
|
|
|
if ( pbBubbleDirectories ) |
|
*pbBubbleDirectories = true; |
|
|
|
for ( int k = 0; k < m_numInputFiles; ++ k ) |
|
{ |
|
Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, m_pszInputFiles[ k ] ); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
int CVTex::VTex( int argc, char **argv ) |
|
{ |
|
// CommandLine()->CreateCmdLine( argc, argv ); |
|
|
|
if ( g_bUsedAsLaunchableDLL ) |
|
{ |
|
SpewOutputFunc( VTexOutputFunc ); |
|
} |
|
|
|
MathLib_Init( 2.2f, 2.2f, 0.0f, 1.0f, false, false, false, false ); |
|
if( argc < 2 ) |
|
{ |
|
Usage(); |
|
return -1; |
|
} |
|
|
|
g_UseGameDir = false; // make sure this is initialized to true. |
|
bool bCreatedFilesystem = false; |
|
|
|
int i; |
|
i = 1; |
|
while( i < argc ) |
|
{ |
|
if( stricmp( argv[i], "-quiet" ) == 0 ) |
|
{ |
|
i++; |
|
g_Quiet = true; |
|
} |
|
else if ( stricmp( argv[i], "-warningsaserrors" ) == 0 ) |
|
{ |
|
i++; |
|
g_bWarningsAsErrors = true; |
|
} |
|
else if ( stricmp( argv[i], "-nomkdir" ) == 0 ) |
|
{ |
|
i++; |
|
g_CreateDir = false; |
|
} |
|
else if ( stricmp( argv[i], "-outdir" ) == 0 ) |
|
{ |
|
V_strcpy_safe( g_ForcedOutputDir, argv[i+1] ); |
|
i += 2; |
|
} |
|
else if( stricmp( argv[i], "-shader" ) == 0 ) |
|
{ |
|
i++; |
|
if( i < argc ) |
|
{ |
|
g_ShaderName = argv[i]; |
|
i++; |
|
} |
|
} |
|
else if( stricmp(argv[i], "-crcvalidate") == 0 ) |
|
{ |
|
i++; |
|
} |
|
else if( stricmp(argv[i], "-crcforce") == 0 ) |
|
{ |
|
i++; |
|
} |
|
else if( stricmp( argv[i], "-vmtparam" ) == 0 ) |
|
{ |
|
if( g_NumVMTParams < MAX_VMT_PARAMS ) |
|
{ |
|
i++; |
|
if( i < argc - 1 ) |
|
{ |
|
g_VMTParams[g_NumVMTParams].m_szParam = argv[i]; |
|
i++; |
|
|
|
if( i < argc - 1 ) |
|
{ |
|
g_VMTParams[g_NumVMTParams].m_szValue = argv[i]; |
|
i++; |
|
} |
|
else |
|
{ |
|
g_VMTParams[g_NumVMTParams].m_szValue = ""; |
|
} |
|
|
|
if( !g_Quiet ) |
|
{ |
|
fprintf( stderr, "Adding .vmt parameter: \"%s\"\t\"%s\"\n", |
|
g_VMTParams[g_NumVMTParams].m_szParam, |
|
g_VMTParams[g_NumVMTParams].m_szValue ); |
|
} |
|
|
|
g_NumVMTParams++; |
|
} |
|
} |
|
else |
|
{ |
|
fprintf( stderr, "Exceeded max number of vmt parameters, extra ignored ( max %d )\n", MAX_VMT_PARAMS ); |
|
} |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
} |
|
|
|
// Set the suggest game info directory helper |
|
g_suggestGameDirHelper.m_pszInputFiles = argv + i; |
|
g_suggestGameDirHelper.m_numInputFiles = argc - i; |
|
SetSuggestGameInfoDirFn( CSuggestGameDirHelper::SuggestFn ); |
|
|
|
// g_pFileSystem may have been inherited with -inherit_filesystem. |
|
|
|
if (g_UseGameDir && !g_pFileSystem) |
|
{ |
|
FileSystem_Init( argv[i] ); |
|
bCreatedFilesystem = true; |
|
|
|
Q_FixSlashes( gamedir, '/' ); |
|
} |
|
|
|
// Parse args |
|
for( ; i < argc; i++ ) |
|
{ |
|
if ( argv[i][0] == '-' ) |
|
continue; // Assuming flags |
|
|
|
char pInputBaseName[MAX_PATH]; |
|
Q_strncpy( pInputBaseName, argv[i], sizeof(pInputBaseName) ); |
|
// int maxlen = Q_strlen( pInputBaseName ) + 1; |
|
|
|
if ( !Q_strstr( pInputBaseName, "*." ) ) |
|
{ |
|
Process_File( pInputBaseName, sizeof(pInputBaseName) ); |
|
continue; |
|
} |
|
} |
|
|
|
if ( bCreatedFilesystem ) |
|
{ |
|
FileSystem_Term(); |
|
} |
|
|
|
if ( g_bUsedAsLaunchableDLL ) |
|
{ |
|
// Make sure any further spew doesn't call the function in this module (which will be unloaded shortly) |
|
SpewOutputFunc( NULL ); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
CVTex g_VTex; |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVTex, IVTex, IVTEX_VERSION_STRING, g_VTex ); |
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVTex, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VTex );
|
|
|