//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Implementation of IEditorTexture interface for materials. // // Materials are kept in a directory tree containing pairs of VMT // and VTF files. Each pair of files represents a material. // //=============================================================================// #include "stdafx.h" #include #include #include #include #include #include "hammer.h" #include "MapDoc.h" #include "Material.h" #include "Options.h" #include "MainFrm.h" #include "GlobalFunctions.h" #include "WADTypes.h" #include "BSPFile.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/IMaterialSystemHardwareConfig.h" #include "materialsystem/MaterialSystem_Config.h" #include "materialsystem/MaterialSystemUtil.h" #include "materialsystem/itexture.h" #include "materialsystem/imaterial.h" #include "bitmap/imageformat.h" // hack : don't want to include this just for ImageFormat #include "filesystem.h" #include "StudioModel.h" #include "tier1/strtools.h" #include "tier0/dbg.h" #include "TextureSystem.h" #include "materialproxyfactory_wc.h" #include "vstdlib/cvar.h" #include "interface.h" // memdbgon must be the last include file in a .cpp file!!! #include #pragma warning(disable:4244) #define _GraphicCacheAllocate(n) malloc(n) MaterialSystem_Config_t g_materialSystemConfig; static MaterialHandle_t g_CurrMaterial; extern void ScaleBitmap(CSize sizeSrc, CSize sizeDest, char *src, char *dest); struct MaterialCacheEntry_t { char szName[MAX_PATH]; // CMaterial *pMaterial; // int nRefCount; // }; //----------------------------------------------------------------------------- // Purpose: // This class speeds up the call to IMaterial::GetPreviewImageProperties because // we call it thousands of times per level load when there are detail props. //----------------------------------------------------------------------------- class CPreviewImagePropertiesCache { public: //----------------------------------------------------------------------------- // Purpose: Anyone can call this instead of IMaterial::GetPreviewImageProperties // and it'll be a lot faster if there are redundant calls to it. //----------------------------------------------------------------------------- static PreviewImageRetVal_t GetPreviewImageProperties( IMaterial *pMaterial, int *width, int *height, ImageFormat *imageFormat, bool* isTranslucent ) { int i = s_PreviewImagePropertiesCache.Find( pMaterial ); if ( i == s_PreviewImagePropertiesCache.InvalidIndex() ) { // Add an entry to the cache. CPreviewImagePropertiesCache::CEntry entry; entry.m_RetVal = pMaterial->GetPreviewImageProperties( &entry.m_Width, &entry.m_Height, &entry.m_ImageFormat, &entry.m_bIsTranslucent ); i = s_PreviewImagePropertiesCache.Insert( pMaterial, entry ); } CPreviewImagePropertiesCache::CEntry &entry = s_PreviewImagePropertiesCache[i]; *width = entry.m_Width; *height = entry.m_Height; *imageFormat = entry.m_ImageFormat; *isTranslucent = entry.m_bIsTranslucent; return entry.m_RetVal; } private: class CEntry { public: int m_Width; int m_Height; ImageFormat m_ImageFormat; bool m_bIsTranslucent; PreviewImageRetVal_t m_RetVal; }; static bool PreviewImageLessFunc( IMaterial* const &a, IMaterial* const &b ) { return a < b; } static CUtlMap s_PreviewImagePropertiesCache; }; CUtlMap CPreviewImagePropertiesCache::s_PreviewImagePropertiesCache( 64, 64, &CPreviewImagePropertiesCache::PreviewImageLessFunc ); //----------------------------------------------------------------------------- // Purpose: stuff for caching textures in memory. //----------------------------------------------------------------------------- class CMaterialImageCache { public: CMaterialImageCache(int maxNumGraphicsLoaded); ~CMaterialImageCache(void); void EnCache( CMaterial *pMaterial ); protected: CMaterial **pool; int cacheSize; int currentID; // next one to get killed. }; //----------------------------------------------------------------------------- // Purpose: Constructor. Allocates a pool of material pointers. // Input : maxNumGraphicsLoaded - //----------------------------------------------------------------------------- CMaterialImageCache::CMaterialImageCache(int maxNumGraphicsLoaded) { cacheSize = maxNumGraphicsLoaded; pool = new CMaterialPtr[cacheSize]; if (pool != NULL) { memset(pool, 0, sizeof(CMaterialPtr) * cacheSize); } currentID = 0; } //----------------------------------------------------------------------------- // Purpose: Destructor. Frees the pool memory. //----------------------------------------------------------------------------- CMaterialImageCache::~CMaterialImageCache(void) { if (pool != NULL) { delete [] pool; } } //----------------------------------------------------------------------------- // Purpose: // Input : *pMaterial - //----------------------------------------------------------------------------- void CMaterialImageCache::EnCache( CMaterial *pMaterial ) { if (pMaterial->m_pData != NULL) { // Already cached. return; } // kill currentID if ((pool[currentID]) && (pool[currentID]->HasData())) { pool[currentID]->FreeData(); } pool[currentID] = pMaterial; pMaterial->LoadMaterialImage(); currentID = ( currentID + 1 ) % cacheSize; #if 0 OutputDebugString( "CMaterialCache::Encache: " ); OutputDebugString( pMaterial->m_szName ); OutputDebugString( "\n" ); #endif } static CMaterialImageCache *g_pMaterialImageCache = NULL; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMaterialCache::CMaterialCache(void) { m_pCache = NULL; m_nMaxEntries = 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMaterialCache::~CMaterialCache(void) { if (m_pCache != NULL) { delete m_pCache; } } //----------------------------------------------------------------------------- // Purpose: Allocates cache memory for a given number of materials. // Input : nMaxEntries - Maximum number of materials in the cache. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMaterialCache::Create(int nMaxEntries) { Assert((m_pCache == NULL) && (m_nMaxEntries == 0)); if (m_pCache != NULL) { delete m_pCache; m_pCache = NULL; m_nMaxEntries = 0; } if (nMaxEntries <= 0) { nMaxEntries = 500; } m_pCache = new MaterialCacheEntry_t[nMaxEntries]; if (m_pCache != NULL) { memset(m_pCache, 0, sizeof(m_pCache[0]) * nMaxEntries); m_nMaxEntries = nMaxEntries; } return(m_pCache != NULL); } //----------------------------------------------------------------------------- // Purpose: Factory. Creates a material by name, first looking in the cache. // Input : pszMaterialName - Name of material, ie "brick/brickfloor01". // Output : Returns a pointer to the new material object, NULL if the given // material did not exist. //----------------------------------------------------------------------------- CMaterial *CMaterialCache::CreateMaterial(const char *pszMaterialName) { CMaterial *pMaterial = NULL; if (pszMaterialName != NULL) { // // Find this material in the cache. If it is here, return it. // pMaterial = FindMaterial(pszMaterialName); if (pMaterial == NULL) { // // Not found in the cache, try to create it. // pMaterial = CMaterial::CreateMaterial(pszMaterialName, true); if (pMaterial != NULL) { // // Success. Add the newly created material to the cache. // AddMaterial(pMaterial); return(pMaterial); } } else { // // Found in the cache, bump the reference count. // AddRef(pMaterial); } } return(pMaterial); } //----------------------------------------------------------------------------- // Purpose: Finds a material in the cache. // Input : char *pszMaterialName - // Output : CMaterial //----------------------------------------------------------------------------- void CMaterialCache::AddMaterial(CMaterial *pMaterial) { if (pMaterial != NULL) { Assert(m_nEntries < m_nMaxEntries); if (m_nEntries < m_nMaxEntries) { m_pCache[m_nEntries].pMaterial = pMaterial; m_pCache[m_nEntries].nRefCount = 1; m_nEntries++; } } } //----------------------------------------------------------------------------- // Purpose: Increments the reference count on a material in the cache. Called by // client code when a pointer to the model is copied, making that // reference independent. // Input : pModel - Model for which to increment the reference count. //----------------------------------------------------------------------------- void CMaterialCache::AddRef(CMaterial *pMaterial) { for (int i = 0; i < m_nEntries; i++) { if (m_pCache[i].pMaterial == pMaterial) { m_pCache[i].nRefCount++; return; } } } //----------------------------------------------------------------------------- // Purpose: Finds a material in the cache by name. // Input : char *pszMaterialName - // Output : CMaterial //----------------------------------------------------------------------------- CMaterial *CMaterialCache::FindMaterial(const char *pszMaterialName) { if (pszMaterialName != NULL) { for (int i = 0; i < m_nEntries; i++) { if (!stricmp(m_pCache[i].pMaterial->GetName(), pszMaterialName)) { return(m_pCache[i].pMaterial); } } } return(NULL); } //----------------------------------------------------------------------------- // Purpose: Decrements the reference count of a material, deleting it and // removing it from the cache if its reference count becomes zero. // Input : pMaterial - Material to release. //----------------------------------------------------------------------------- void CMaterialCache::Release(CMaterial *pMaterial) { if (pMaterial != NULL) { for (int i = 0; i < m_nEntries; i++) { if (m_pCache[i].pMaterial == pMaterial) { m_pCache[i].nRefCount--; if (m_pCache[i].nRefCount == 0) { delete m_pCache[i].pMaterial; m_nEntries--; m_pCache[i] = m_pCache[m_nEntries]; memset(&m_pCache[m_nEntries], 0, sizeof(m_pCache[0])); } break; } } } } //----------------------------------------------------------------------------- // Purpose: Constructor. Initializes data members. //----------------------------------------------------------------------------- CMaterial::CMaterial(void) { memset(m_szName, 0, sizeof(m_szName)); memset(m_szFileName, 0, sizeof(m_szFileName)); memset(m_szKeywords, 0, sizeof(m_szKeywords)); m_nWidth = 0; m_nHeight = 0; m_nTextureID = 0; m_pData = NULL; m_bLoaded = false; m_pMaterial = NULL; m_TranslucentBaseTexture = false; } //----------------------------------------------------------------------------- // Purpose: Destructor. Frees texture image data and palette. //----------------------------------------------------------------------------- CMaterial::~CMaterial(void) { // // Free image data. // if (m_pData != NULL) { free(m_pData); m_pData = NULL; } /* FIXME: Texture manager shuts down after the material system if (m_pMaterial) { m_pMaterial->DecrementReferenceCount(); m_pMaterial = NULL; } */ } #define MATERIAL_PREFIX_LEN 10 //----------------------------------------------------------------------------- // Finds all .VMT files in a particular directory //----------------------------------------------------------------------------- bool CMaterial::LoadMaterialsInDirectory( char const* pDirectoryName, int nDirectoryNameLen, IMaterialEnumerator *pEnum, int nContext, int nFlags ) { //Assert( Q_strnicmp( pDirectoryName, "materials", 9 ) == 0 ); char *pWildCard; pWildCard = ( char * )stackalloc( nDirectoryNameLen + 7 ); Q_snprintf( pWildCard, nDirectoryNameLen + 7, "%s/*.vmt", pDirectoryName ); if ( !g_pFileSystem ) { return false; } FileFindHandle_t findHandle; const char *pFileName = g_pFullFileSystem->FindFirstEx( pWildCard, "GAME", &findHandle ); while( pFileName ) { if (IsIgnoredMaterial(pFileName)) { pFileName = g_pFullFileSystem->FindNext( findHandle ); continue; } if( !g_pFullFileSystem->FindIsDirectory( findHandle ) ) { // Strip off the 'materials/' part of the material name. char *pFileNameWithPath; int nAllocSize = nDirectoryNameLen + Q_strlen(pFileName) + 2; pFileNameWithPath = (char *)stackalloc( nAllocSize ); Q_snprintf( pFileNameWithPath, nAllocSize, "%s/%s", &pDirectoryName[MATERIAL_PREFIX_LEN], pFileName ); Q_strnlwr( pFileNameWithPath, nAllocSize ); // Strip off the extension... char *pExt = Q_strrchr( pFileNameWithPath, '.'); if (pExt) *pExt = 0; if (!pEnum->EnumMaterial( pFileNameWithPath, nContext )) { return false; } } pFileName = g_pFullFileSystem->FindNext( findHandle ); } g_pFullFileSystem->FindClose( findHandle ); return true; } //----------------------------------------------------------------------------- // Discovers all .VMT files lying under a particular directory // It only finds their names so we can generate shell materials for them // that we can load up at a later time //----------------------------------------------------------------------------- bool CMaterial::InitDirectoryRecursive( char const* pDirectoryName, IMaterialEnumerator *pEnum, int nContext, int nFlags ) { // Make sure this is an ok directory, otherwise don't bother if (ShouldSkipMaterial( pDirectoryName + MATERIAL_PREFIX_LEN, nFlags )) return true; // Compute directory name length int nDirectoryNameLen = Q_strlen( pDirectoryName ); if (!LoadMaterialsInDirectory( pDirectoryName, nDirectoryNameLen, pEnum, nContext, nFlags )) return false; char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 5 ); strcpy(pWildCard, pDirectoryName); strcat(pWildCard, "/*.*"); int nPathStrLen = nDirectoryNameLen + 1; FileFindHandle_t findHandle; const char *pFileName = g_pFullFileSystem->FindFirstEx( pWildCard, "GAME", &findHandle ); while( pFileName ) { if (!IsIgnoredMaterial(pFileName)) { if ((pFileName[0] != '.') || (pFileName[1] != '.' && pFileName[1] != 0)) { if( g_pFullFileSystem->FindIsDirectory( findHandle ) ) { int fileNameStrLen = Q_strlen( pFileName ); char *pFileNameWithPath = ( char * )stackalloc( nPathStrLen + fileNameStrLen + 1 ); memcpy( pFileNameWithPath, pWildCard, nPathStrLen ); pFileNameWithPath[nPathStrLen] = '\0'; Q_strncat( pFileNameWithPath, pFileName, nPathStrLen + fileNameStrLen + 1 ); if (!InitDirectoryRecursive( pFileNameWithPath, pEnum, nContext, nFlags )) return false; } } } pFileName = g_pFullFileSystem->FindNext( findHandle ); } return true; } //----------------------------------------------------------------------------- // Discovers all .VMT files lying under a particular directory // It only finds their names so we can generate shell materials for them // that we can load up at a later time //----------------------------------------------------------------------------- void CMaterial::EnumerateMaterials( IMaterialEnumerator *pEnum, const char *szRoot, int nContext, int nFlags ) { InitDirectoryRecursive( szRoot, pEnum, nContext, nFlags ); } //----------------------------------------------------------------------------- // Purpose: Called from GetFirst/GetNextMaterialName to skip unwanted materials. // Input : pszName - Name of material to evaluate. // nFlags - One or more of the following: // INCLUDE_ALL_MATERIALS // INCLUDE_WORLD_MATERIALS // INCLUDE_MODEL_MATERIALS // Output : Returns true to skip, false to not skip this material. //----------------------------------------------------------------------------- bool CMaterial::ShouldSkipMaterial(const char *pszName, int nFlags) { static char szStrippedName[MAX_PATH]; // if NULL skip it if( !pszName ) return true; // // check against the list of user-defined exclusion directories // for( int i = 0; i < g_pGameConfig->m_MaterialExcludeCount; i++ ) { // This will guarantee the match is at the start of the string const char *pMatchFound = Q_stristr( pszName, g_pGameConfig->m_MaterialExclusions[i].szDirectory ); if( pMatchFound == pszName ) return true; } // also check against any FGD-defined exclusions if (pGD != NULL) { for( int i = 0; i < pGD->m_FGDMaterialExclusions.Count(); i++ ) { const char *pMatchFound = Q_stristr( pszName, pGD->m_FGDMaterialExclusions[i].szDirectory ); if( pMatchFound == pszName ) return true; } } return false; #if 0 bool bSkip = false; if (pszName != NULL) { if (!(nFlags & INCLUDE_MODEL_MATERIALS)) { if (_strnicmp(pszName, "models/", 7) == 0) { bSkip = true; } } if (!(nFlags & INCLUDE_WORLD_MATERIALS)) { if (_strnicmp(pszName, "models/", 7) != 0) { bSkip = true; } } } else { bSkip = true; } return(bSkip); #endif } //----------------------------------------------------------------------------- // Purpose: Factory. Creates a material by name. // Input : pszMaterialName - Name of material, ie "brick/brickfloor01". // Output : Returns a pointer to the new material object, NULL if the given // material did not exist. //----------------------------------------------------------------------------- CMaterial *CMaterial::CreateMaterial(const char *pszMaterialName, bool bLoadImmediately, bool* pFound) { Assert (pszMaterialName); CMaterial *pMaterial = new CMaterial; Assert( pMaterial ); // Store off the material name so we can load it later if we need to Q_snprintf( pMaterial->m_szFileName, MAX_PATH, pszMaterialName ); Q_snprintf( pMaterial->m_szName, MAX_PATH, pszMaterialName ); // // Find the material by name and load it. // if (bLoadImmediately) { bool bFound = pMaterial->LoadMaterial(); // Returns if the material was found or not if (pFound) *pFound = bFound; } return pMaterial; } bool CMaterial::IsIgnoredMaterial( const char *pName ) { //TODO: make this a customizable user option? if ( !Q_strnicmp(pName, ".svn", 4) || strstr (pName, ".svn") || !Q_strnicmp(pName, "models", 6) || strstr (pName, "models") ) return true; return false; } //----------------------------------------------------------------------------- // Will actually load the material bits // We don't want to load them all at once because it takes way too long //----------------------------------------------------------------------------- bool CMaterial::LoadMaterial() { bool bFound = true; if (!m_bLoaded) { if (IsIgnoredMaterial(m_szFileName)) { return false; } m_bLoaded = true; IMaterial *pMat = materials->FindMaterial(m_szFileName, TEXTURE_GROUP_OTHER); if ( IsErrorMaterial( pMat ) ) bFound = false; Assert( pMat ); if (!pMat) { return false; } if (!LoadMaterialHeader(pMat)) { // dvs: yeaaaaaaaaah, we're gonna disable this until the spew can be reduced //Msg( mwError,"Load material header failed: %s", m_szFileName ); bFound = false; pMat = materials->FindMaterial("debug/debugempty", TEXTURE_GROUP_OTHER); if (pMat) { LoadMaterialHeader(pMat); } } } return bFound; } //----------------------------------------------------------------------------- // Reloads owing to a material change //----------------------------------------------------------------------------- void CMaterial::Reload( bool bFullReload ) { // Don't bother if we're not loaded yet if (!m_bLoaded) return; FreeData(); if ( m_pMaterial ) { m_pMaterial->DecrementReferenceCount(); } m_pMaterial = materials->FindMaterial(m_szFileName, TEXTURE_GROUP_OTHER); Assert( m_pMaterial ); if ( bFullReload ) m_pMaterial->Refresh(); PreviewImageRetVal_t retVal; bool translucentBaseTexture; ImageFormat eImageFormat; int width, height; retVal = m_pMaterial->GetPreviewImageProperties(&width, &height, &eImageFormat, &translucentBaseTexture); if (retVal == MATERIAL_PREVIEW_IMAGE_BAD) return; m_nWidth = width; m_nHeight = height; m_TranslucentBaseTexture = translucentBaseTexture; // Find the keywords for this material from the vmt file. bool bFound; IMaterialVar *pVar = m_pMaterial->FindVar("%keywords", &bFound, false); if (bFound) { V_strcpy_safe( m_szKeywords, pVar->GetStringValue() ); // Register the keywords g_Textures.RegisterTextureKeywords( this ); } // Make sure to bump the refcount again. Not sure why this wasn't always done (check for leaks). if (m_pMaterial) { m_pMaterial->IncrementReferenceCount(); } } //----------------------------------------------------------------------------- // Returns the material //----------------------------------------------------------------------------- IMaterial* CMaterial::GetMaterial( bool bForceLoad ) { if ( bForceLoad ) LoadMaterial(); return m_pMaterial; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMaterial::DrawIcon( CDC *pDC, CMaterial* pIcon, RECT& dstRect ) { if (!pIcon) return; g_pMaterialImageCache->EnCache(pIcon); RECT rect, dst; rect.left = 0; rect.right = pIcon->GetWidth(); // FIXME: Workaround the fact that materials must be power of 2, I want 12 bite rect.top = 2; rect.bottom = pIcon->GetHeight() - 2; dst = dstRect; float dstHeight = dstRect.bottom - dstRect.top; float srcAspect = (float)(rect.right - rect.left) / (float)(rect.bottom - rect.top); dst.right = dst.left + (dstHeight * srcAspect); pIcon->DrawBitmap( pDC, rect, dst ); dstRect.left += dst.right - dst.left; } //----------------------------------------------------------------------------- // Purpose: // Input : pDC - // dstRect - // detectErrors - //----------------------------------------------------------------------------- void CMaterial::DrawBrowserIcons( CDC *pDC, RECT& dstRect, bool detectErrors ) { static CMaterial* pTranslucentIcon = 0; static CMaterial* pOpaqueIcon = 0; static CMaterial* pSelfIllumIcon = 0; static CMaterial* pBaseAlphaEnvMapMaskIcon = 0; static CMaterial* pErrorIcon = 0; if (!pTranslucentIcon) { pTranslucentIcon = CreateMaterial("editor/translucenticon", true); pOpaqueIcon = CreateMaterial("editor/opaqueicon", true); pSelfIllumIcon = CreateMaterial("editor/selfillumicon", true); pBaseAlphaEnvMapMaskIcon = CreateMaterial("editor/basealphaenvmapmaskicon", true); pErrorIcon = CreateMaterial("editor/erroricon", true); Assert( pTranslucentIcon && pOpaqueIcon && pSelfIllumIcon && pBaseAlphaEnvMapMaskIcon && pErrorIcon ); } bool error = false; IMaterial* pMaterial = GetMaterial(); if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_TRANSLUCENT ) ) { DrawIcon( pDC, pTranslucentIcon, dstRect ); if (detectErrors) { error = error || !m_TranslucentBaseTexture; } } else { DrawIcon( pDC, pOpaqueIcon, dstRect ); } if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SELFILLUM )) { DrawIcon( pDC, pSelfIllumIcon, dstRect ); if (detectErrors) { error = error || !m_TranslucentBaseTexture; } } if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_BASEALPHAENVMAPMASK )) { DrawIcon( pDC, pBaseAlphaEnvMapMaskIcon, dstRect ); if (detectErrors) { error = error || !m_TranslucentBaseTexture; } } if (error) { DrawIcon( pDC, pErrorIcon, dstRect ); } } //----------------------------------------------------------------------------- // Purpose: // Input : pDC - // srcRect - // dstRect - //----------------------------------------------------------------------------- void CMaterial::DrawBitmap( CDC *pDC, RECT& srcRect, RECT& dstRect ) { static struct { BITMAPINFOHEADER bmih; unsigned short colorindex[256]; } bmi; int srcWidth = srcRect.right - srcRect.left; int srcHeight = srcRect.bottom - srcRect.top; BITMAPINFOHEADER &bmih = bmi.bmih; memset(&bmih, 0, sizeof(bmih)); bmih.biSize = sizeof(bmih); bmih.biWidth = srcWidth; bmih.biHeight = -srcHeight; bmih.biCompression = BI_RGB; bmih.biBitCount = 24; bmih.biPlanes = 1; static BOOL bInit = false; if (!bInit) { bInit = true; for (int i = 0; i < 256; i++) { bmi.colorindex[i] = i; } } int dest_width = dstRect.right - dstRect.left; int dest_height = dstRect.bottom - dstRect.top; // ** bits ** SetStretchBltMode(pDC->m_hDC, COLORONCOLOR); if (StretchDIBits(pDC->m_hDC, dstRect.left, dstRect.top, dest_width, dest_height, srcRect.left, -srcRect.top, srcWidth, srcHeight, m_pData, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, SRCCOPY) == GDI_ERROR) { Msg(mwError, "CMaterial::Draw(): StretchDIBits failed."); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pDC - // rect - // iFontHeight - // dwFlags - //----------------------------------------------------------------------------- void CMaterial::Draw(CDC *pDC, RECT& rect, int iFontHeight, int iIconHeight, DrawTexData_t &DrawTexData)//, BrowserData_t *pBrowserData) { g_pMaterialImageCache->EnCache(this); if (!this->HasData()) { return; } if (m_nWidth <= 0) { NoData: // draw "no data" CFont *pOldFont = (CFont*) pDC->SelectStockObject(ANSI_VAR_FONT); COLORREF cr = pDC->SetTextColor(RGB(0xff, 0xff, 0xff)); COLORREF cr2 = pDC->SetBkColor(RGB(0, 0, 0)); // draw black rect first pDC->FillRect(&rect, CBrush::FromHandle(HBRUSH(GetStockObject(BLACK_BRUSH)))); // then text pDC->TextOut(rect.left+2, rect.top+2, "No Image", 8); pDC->SelectObject(pOldFont); pDC->SetTextColor(cr); pDC->SetBkColor(cr2); return; } // no data - if (!m_pData) { // try to load - if (!Load()) { // can't load - goto NoData; } } // Draw the material image RECT srcRect, dstRect; srcRect.left = 0; srcRect.top = 0; srcRect.right = m_nWidth; srcRect.bottom = m_nHeight; dstRect = rect; if (DrawTexData.nFlags & drawCaption) { dstRect.bottom -= iFontHeight + 4; } if (DrawTexData.nFlags & drawIcons) { dstRect.bottom -= iIconHeight; } if (!(DrawTexData.nFlags & drawResizeAlways)) { if (m_nWidth < dstRect.right - dstRect.left ) { dstRect.right = dstRect.left + m_nWidth; } if (m_nHeight < dstRect.bottom - dstRect.top ) { dstRect.bottom = dstRect.top + m_nHeight; } } DrawBitmap( pDC, srcRect, dstRect ); // Draw the icons if (DrawTexData.nFlags & drawIcons) { dstRect = rect; if (DrawTexData.nFlags & drawCaption) { dstRect.bottom -= iFontHeight + 5; } dstRect.top = dstRect.bottom - iIconHeight; DrawBrowserIcons(pDC, dstRect, (DrawTexData.nFlags & drawErrors) != 0 ); } // ** caption ** if (DrawTexData.nFlags & drawCaption) { // draw background for name CBrush brCaption(RGB(0, 0, 255)); CRect rcCaption(rect); rcCaption.top = rcCaption.bottom - (iFontHeight + 5); pDC->FillRect(rcCaption, &brCaption); // draw name char szShortName[MAX_PATH]; int iLen = GetShortName(szShortName); pDC->TextOut(rect.left, rect.bottom - (iFontHeight + 4), szShortName, iLen); // draw usage count if (DrawTexData.nFlags & drawUsageCount) { CString str; str.Format("%d", DrawTexData.nUsageCount); CSize size = pDC->GetTextExtent(str); pDC->TextOut(rect.right - size.cx, rect.bottom - (iFontHeight + 4), str); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMaterial::FreeData( void ) { free( m_pData ); m_pData = NULL; } //----------------------------------------------------------------------------- // Purpose: Returns a string of comma delimited keywords associated with this // material. // Input : pszKeywords - Buffer to receive keywords, NULL to query string length. // Output : Returns the number of characters in the keyword string. //----------------------------------------------------------------------------- int CMaterial::GetKeywords(char *pszKeywords) const { // To access keywords, we have to have the header loaded const_cast(this)->Load(); if (pszKeywords != NULL) { strcpy(pszKeywords, m_szKeywords); } return(strlen(m_szKeywords)); } //----------------------------------------------------------------------------- // Purpose: // Input : *pszName - // Output : int //----------------------------------------------------------------------------- int CMaterial::GetShortName(char *pszName) const { if (pszName != NULL) { strcpy(pszName, m_szName); } return(strlen(m_szName)); } //----------------------------------------------------------------------------- // Purpose: // Input : material - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMaterial::LoadMaterialHeader( IMaterial *pMat ) { PreviewImageRetVal_t retVal; bool translucentBaseTexture; ImageFormat eImageFormat; int width, height; retVal = CPreviewImagePropertiesCache::GetPreviewImageProperties( pMat, &width, &height, &eImageFormat, &translucentBaseTexture); if (retVal == MATERIAL_PREVIEW_IMAGE_BAD) return false; m_pMaterial = pMat; m_pMaterial->IncrementReferenceCount(); m_nWidth = width; m_nHeight = height; m_TranslucentBaseTexture = translucentBaseTexture; // Find the keywords for this material from the vmt file. bool bFound; IMaterialVar *pVar = pMat->FindVar("%keywords", &bFound, false); if (bFound) { V_strcpy_safe( m_szKeywords, pVar->GetStringValue() ); // Register the keywords g_Textures.RegisterTextureKeywords( this ); } return(true); } //----------------------------------------------------------------------------- // Purpose: Returns the full path of the file from which this material was loaded. //----------------------------------------------------------------------------- const char *CMaterial::GetFileName( void ) const { return(m_szFileName); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMaterial::IsWater( void ) const { bool bFound; IMaterialVar *pVar = m_pMaterial->FindVar( "$surfaceprop", &bFound, false ); if ( bFound ) { if ( !strcmp( "water", pVar->GetStringValue() ) ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: If the buffer pointer passed in is not NULL, copies the image data // in RGB format to the buffer // Input : pImageRGB - Pointer to buffer that receives the image data. If the // pointer is NULL, no data is copied, only the data size is returned. // Output : Returns a the size of the RGB image in bytes. //----------------------------------------------------------------------------- int CMaterial::GetImageDataRGB( void *pImageRGB ) { Assert( m_nWidth > 0 ); if ( pImageRGB != NULL ) { Load(); g_pMaterialImageCache->EnCache( this ); if (!this->HasData()) { return(NULL); } unsigned char *src, *dst; src = ( unsigned char * )m_pData; dst = (unsigned char *)pImageRGB; for( ; src < ( unsigned char * )m_pData + m_nWidth * m_nHeight * 3; src += 3, dst += 3 ) { dst[0] = src[2]; dst[1] = src[1]; dst[2] = src[0]; } return( m_nWidth * m_nHeight * 3 ); } return( m_nWidth * m_nHeight * 3 ); } //----------------------------------------------------------------------------- // Purpose: If the buffer pointer passed in is not NULL, copies the image data // in RGBA format to the buffer // Input : pImageRGBA - Pointer to buffer that receives the image data. If the // pointer is NULL, no data is copied, only the data size is returned. // Output : Returns a the size of the RGBA image in bytes. //----------------------------------------------------------------------------- int CMaterial::GetImageDataRGBA(void *pImageRGBA) { Assert( m_nWidth > 0 ); if (pImageRGBA != NULL) { Load(); g_pMaterialImageCache->EnCache(this); if (!this->HasData()) { return(NULL); } unsigned char *src, *dst; src = (unsigned char *)m_pData; dst = (unsigned char *)pImageRGBA; while (src < (unsigned char *)m_pData + m_nWidth * m_nHeight * 4); { dst[0] = src[2]; dst[1] = src[1]; dst[2] = src[0]; dst[3] = 0; src += 4; dst += 4; } } return(m_nWidth * m_nHeight * 4); } //----------------------------------------------------------------------------- // Purpose: // Input : size - //----------------------------------------------------------------------------- void CMaterial::GetSize(SIZE &size) const { const_cast(this)->Load(); Assert( m_nWidth >= 0 ); size.cx = m_nWidth; size.cy = m_nHeight; } //----------------------------------------------------------------------------- // Purpose: Loads this material's image from disk if it is not already loaded. // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMaterial::Load( void ) { LoadMaterial(); return true; } //----------------------------------------------------------------------------- // cache in the image size only when we need to //----------------------------------------------------------------------------- int CMaterial::GetImageWidth(void) const { const_cast(this)->Load(); return(m_nWidth); } int CMaterial::GetImageHeight(void) const { const_cast(this)->Load(); return(m_nHeight); } int CMaterial::GetWidth(void) const { const_cast(this)->Load(); return(m_nWidth); } int CMaterial::GetHeight(void) const { const_cast(this)->Load(); return(m_nHeight); } float CMaterial::GetDecalScale(void) const { const_cast(this)->Load(); IMaterialVar *decalScaleVar; bool found; decalScaleVar = m_pMaterial->FindVar( "$decalScale", &found, false ); if( !found ) { return 1.0f; } else { return decalScaleVar->GetFloatValue(); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMaterial::LoadMaterialImage( void ) { Load(); if ((!m_nWidth) || (!m_nHeight)) return(false); m_pData = malloc(m_nWidth * m_nHeight * 3); Assert(m_pData); ImageFormat imageFormat; // if( _strnicmp( m_pMaterial->GetName(), "decals", 6 ) == 0 ) // { // imageFormat = IMAGE_FORMAT_BGR888_BLUESCREEN; // } // else { imageFormat = IMAGE_FORMAT_BGR888; } PreviewImageRetVal_t retVal; retVal = m_pMaterial->GetPreviewImage( (unsigned char *)m_pData, m_nWidth, m_nHeight, imageFormat ); return (retVal != MATERIAL_PREVIEW_IMAGE_BAD); } static void InitMaterialSystemConfig(MaterialSystem_Config_t *pConfig) { pConfig->bEditMode = true; pConfig->m_nAASamples = 0; pConfig->SetFlag( MATSYS_VIDCFG_FLAGS_DISABLE_BUMPMAP, true); // When I do this the model browser layout is horked... // pConfig->SetFlag( MATSYS_VIDCFG_FLAGS_USING_MULTIPLE_WINDOWS, true ); } static char const *s_rt_names[]={"_rt_albedo","_rt_normal","_rt_position","_rt_flags", "_rt_accbuf_0","_rt_accbuf_1"}; ImageFormat s_rt_formats[]={ IMAGE_FORMAT_RGBA32323232F, IMAGE_FORMAT_RGBA32323232F, IMAGE_FORMAT_RGBA32323232F, IMAGE_FORMAT_RGBA32323232F, IMAGE_FORMAT_RGBA16161616F, IMAGE_FORMAT_RGBA16161616F }; // ImageFormat s_rt_formats[]={ // IMAGE_FORMAT_RGBA16161616F, IMAGE_FORMAT_RGBA16161616F, // IMAGE_FORMAT_RGBA16161616F, IMAGE_FORMAT_RGBA16161616F, // IMAGE_FORMAT_RGBA16161616F, IMAGE_FORMAT_RGBA16161616F, // IMAGE_FORMAT_RGBA16161616F, IMAGE_FORMAT_RGBA16161616F }; static CTextureReference sg_ExtraFP16Targets[NELEMS(s_rt_names)]; void AllocateLightingPreviewtextures(void) { static bool bHaveAllocated=false; if (! bHaveAllocated ) { bHaveAllocated = true; MaterialSystemInterface()->BeginRenderTargetAllocation(); for(int idx=0;idxCreateNamedRenderTargetTextureEx2( s_rt_names[idx], 512, 512, RT_SIZE_DEFAULT, s_rt_formats[idx], MATERIAL_RT_DEPTH_SHARED, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT, CREATERENDERTARGETFLAGS_HDR ) ); // End block in which all render targets should be allocated (kicking off an Alt-Tab type // behavior) MaterialSystemInterface()->EndRenderTargetAllocation(); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMaterial::Initialize( HWND hwnd ) { // NOTE: This gets set to true later upon creating a 3d view. g_materialSystemConfig = materials->GetCurrentConfigForVideoCard(); InitMaterialSystemConfig( &g_materialSystemConfig ); // Create a cache for material images (for browsing and uploading to the driver). if (g_pMaterialImageCache == NULL) { g_pMaterialImageCache = new CMaterialImageCache(500); if (g_pMaterialImageCache == NULL) return false ; } materials->OverrideConfig( g_materialSystemConfig, false ); // Set the mode // When setting the mode, we need to grab the parent window // since that's going to enclose all our little render windows g_materialSystemConfig.m_VideoMode.m_Width = g_materialSystemConfig.m_VideoMode.m_Height = 0; g_materialSystemConfig.m_VideoMode.m_Format = IMAGE_FORMAT_BGRA8888; g_materialSystemConfig.m_VideoMode.m_RefreshRate = 0; g_materialSystemConfig.SetFlag( MATSYS_VIDCFG_FLAGS_WINDOWED, true ); g_materialSystemConfig.SetFlag( MATSYS_VIDCFG_FLAGS_RESIZING, true ); if (!MaterialSystemInterface()->SetMode( hwnd, g_materialSystemConfig ) ) return false; return true; } //----------------------------------------------------------------------------- // Purpose: Restores the material system to an uninitialized state. //----------------------------------------------------------------------------- void CMaterial::ShutDown(void) { for ( int i = 0; i < NELEMS(sg_ExtraFP16Targets); ++i ) { sg_ExtraFP16Targets[i].Shutdown(); } if (materials != NULL) { materials->UncacheAllMaterials(); } delete g_pMaterialImageCache; g_pMaterialImageCache = NULL; }