//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "gameuidynamictextures.h" #include "gamelayer.h" #include "gamerect.h" #include "tier1/utlstring.h" #include "tier1/utlstringmap.h" #include "tier1/utlbuffer.h" #include "gameuisystemmgr.h" #include "tier1/fmtstr.h" #include "materialsystem/imaterial.h" #include "materialsystem/imaterialvar.h" #include "materialsystem/imaterialsystem.h" #include "materialsystem/imesh.h" #include "rendersystem/irenderdevice.h" #include "rendersystem/irendercontext.h" #include "tier1/keyvalues.h" #include "materialsystem/IMaterialProxy.h" #include "materialsystem/imaterialproxyfactory.h" #include "gameuisystemmgr.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define GAMEUI_DYNAMIC_TEXTURE_SHEET_WIDTH 2048 #define GAMEUI_DYNAMIC_TEXTURE_SHEET_HEIGHT 2048 //----------------------------------------------------------------------------- // A material proxy that resets the base texture to use the dynamic texture //----------------------------------------------------------------------------- class CGameControlsProxy : public IMaterialProxy { public: CGameControlsProxy(); virtual ~CGameControlsProxy(){}; virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); virtual void OnBind( void *pProxyData ); virtual void Release( void ) { delete this; } virtual IMaterial *GetMaterial(); private: IMaterialVar* m_BaseTextureVar; }; CGameControlsProxy::CGameControlsProxy(): m_BaseTextureVar( NULL ) { } bool CGameControlsProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) { bool bFoundVar; m_BaseTextureVar = pMaterial->FindVar( "$basetexture", &bFoundVar, false ); return bFoundVar; } void CGameControlsProxy::OnBind( void *pProxyData ) { const char *pBaseTextureName = ( const char * )pProxyData; ITexture *pTexture = g_pMaterialSystem->FindTexture( pBaseTextureName, TEXTURE_GROUP_OTHER, true ); m_BaseTextureVar->SetTextureValue( pTexture ); } IMaterial *CGameControlsProxy::GetMaterial() { return m_BaseTextureVar->GetOwningMaterial(); } //----------------------------------------------------------------------------- // Factory to create dynamic material. ( Return in this case. ) //----------------------------------------------------------------------------- class CMaterialProxyFactory : public IMaterialProxyFactory { public: IMaterialProxy *CreateProxy( const char *proxyName ); void DeleteProxy( IMaterialProxy *pProxy ); CreateInterfaceFn GetFactory(); }; static CMaterialProxyFactory s_DynamicMaterialProxyFactory; IMaterialProxy *CMaterialProxyFactory::CreateProxy( const char *proxyName ) { if ( Q_strcmp( proxyName, "GameControlsProxy" ) == NULL ) { return static_cast< IMaterialProxy * >( new CGameControlsProxy ); } return NULL; } void CMaterialProxyFactory::DeleteProxy( IMaterialProxy *pProxy ) { } CreateInterfaceFn CMaterialProxyFactory::GetFactory() { return Sys_GetFactoryThis(); } //----------------------------------------------------------------------------- // Constructor / Destructor. //----------------------------------------------------------------------------- CGameUIDynamicTextures::CGameUIDynamicTextures() { m_pDynamicTexturePacker = NULL; m_RenderMaterial = NULL; m_bRegenerate = false; } CGameUIDynamicTextures::~CGameUIDynamicTextures() { Shutdown(); } //----------------------------------------------------------------------------- // Init any render targets needed by the UI. //----------------------------------------------------------------------------- void CGameUIDynamicTextures::InitRenderTargets() { if ( !m_TexturePage.IsValid() ) { m_TexturePage.InitRenderTarget( GAMEUI_DYNAMIC_TEXTURE_SHEET_WIDTH, GAMEUI_DYNAMIC_TEXTURE_SHEET_HEIGHT, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_ARGB8888, MATERIAL_RT_DEPTH_NONE, false, "_rt_DynamicUI" ); } if ( m_TexturePage.IsValid() ) { int nSheetWidth = m_TexturePage->GetActualWidth(); int nSheetHeight = m_TexturePage->GetActualHeight(); m_pDynamicTexturePacker = new CTexturePacker( nSheetWidth, nSheetHeight, IsGameConsole() ? 0 : 1 ); KeyValues *pVMTKeyValues = new KeyValues( "GameControls" ); pVMTKeyValues->SetString( "$basetexture", "_rt_DynamicUI" ); CMaterialReference material; // Material names must be different CFmtStr materialName; materialName.sprintf( "dynamictx_%s", "_rt_DynamicUI" ); material.Init( materialName, TEXTURE_GROUP_OTHER, pVMTKeyValues ); material->Refresh(); { ImageAliasData_t imageData; imageData.m_Width = nSheetWidth; imageData.m_Height = nSheetHeight; imageData.m_Material = material; m_ImageAliasMap[ "_rt_DynamicUI" ] = imageData; } { CTextureReference pErrorTexture; pErrorTexture.Init( "error", TEXTURE_GROUP_OTHER, true ); ImageAliasData_t imageData; imageData.m_Width = pErrorTexture->GetActualWidth(); imageData.m_Height = pErrorTexture->GetActualHeight(); imageData.m_szBaseTextureName = "error"; imageData.m_Material = NULL; m_ImageAliasMap[ "errorImageAlias" ] = imageData; } pVMTKeyValues = new KeyValues( "GameControls" ); pVMTKeyValues->SetInt( "$ignorez", 1 ); KeyValues *pProxies = new KeyValues( "Proxies" ); pProxies->AddSubKey( new KeyValues( "GameControlsProxy" ) ); pVMTKeyValues->AddSubKey( pProxies ); m_RenderMaterial.Init( "dynamic_render_texture", TEXTURE_GROUP_OTHER, pVMTKeyValues ); m_RenderMaterial->Refresh(); g_pGameUISystemMgrImpl->InitImageAlias( "defaultImageAlias" ); g_pGameUISystemMgrImpl->LoadImageAliasTexture( "defaultImageAlias", "vguiedit/pixel" ); } } IMaterialProxy *CGameUIDynamicTextures::CreateProxy( const char *proxyName ) { return s_DynamicMaterialProxyFactory.CreateProxy( proxyName ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGameUIDynamicTextures::Shutdown() { m_RenderMaterial.Shutdown(); m_TexturePage.Shutdown(); if ( m_pDynamicTexturePacker ) { delete m_pDynamicTexturePacker; } m_pDynamicTexturePacker = NULL; m_RenderMaterial = NULL; m_bRegenerate = false; } void CGameUIDynamicTextures::SetImageEntry( const char *pEntryName, ImageAliasData_t &imageData ) { m_ImageAliasMap[ pEntryName ] = imageData; } //----------------------------------------------------------------------------- // Associate this image alias name with a .vtf texture. //----------------------------------------------------------------------------- void CGameUIDynamicTextures::LoadImageAlias( const char *pAlias, const char *pBaseTextureName ) { if ( !pAlias || !*pAlias ) return; if ( !pBaseTextureName || !*pBaseTextureName ) return; CFmtStr texturePath; texturePath.sprintf( "materials\\%s.vtf", pBaseTextureName ); if ( !g_pFullFileSystem->FileExists( texturePath.Access(), IsGameConsole() ? "MOD" : "GAME" ) ) { Warning( "Unable to find game ui dynamic texture \"%s\"\n", texturePath.Access() ); } if ( Q_strcmp( pBaseTextureName, "" ) == 0 ) { ImageAliasData_t *pImageData = GetImageAliasData( "errorImageAlias" ); ImageAliasData_t imageData; imageData.Init(); imageData.m_XPos = pImageData->m_XPos; imageData.m_YPos = pImageData->m_YPos; imageData.m_Width = pImageData->m_Width; imageData.m_Height = pImageData->m_Height; imageData.m_szBaseTextureName = pImageData->m_szBaseTextureName; imageData.m_Material = pImageData->m_Material; imageData.m_bIsInSheet = pImageData->m_bIsInSheet; imageData.m_nNodeIndex = pImageData->m_nNodeIndex; SetImageEntry( pAlias, imageData ); return; } Msg( "Loading Alias %s Texture %s\n", pAlias, pBaseTextureName ); CTextureReference pTexture; pTexture.Init( pBaseTextureName, TEXTURE_GROUP_OTHER, true ); ImageAliasData_t *pImageData = GetImageAliasData( pAlias ); int nodeIndex = -1; if ( m_pDynamicTexturePacker ) { if ( !IsErrorImageAliasData( pImageData ) ) { if ( pImageData->m_nNodeIndex != -1 ) { // We already had something packed in there for this alias. // Remove the old alias texture m_pDynamicTexturePacker->RemoveRect( pImageData->m_nNodeIndex ); } // Set up new imagedata values pImageData->m_Width = pTexture->GetActualWidth(); pImageData->m_Height = pTexture->GetActualHeight(); pImageData->m_szBaseTextureName = pBaseTextureName; } else { ImageAliasData_t imageData; imageData.m_Width = pTexture->GetActualWidth(); imageData.m_Height = pTexture->GetActualHeight(); imageData.m_szBaseTextureName = pBaseTextureName; imageData.m_nRefCount = pImageData->m_nRefCount; SetImageEntry( pAlias, imageData ); pImageData = GetImageAliasData( pAlias ); Assert( !IsErrorImageAliasData( pImageData ) ); } int nSheetWidth = 0; int nSheetHeight = 0; GetDynamicSheetSize( nSheetWidth, nSheetHeight ); Assert( nSheetWidth != 0 ); Assert( nSheetHeight != 0 ); // If our texture is huge in some way, don't bother putting it in the packed texture. if ( ( pTexture->GetActualWidth() << 1 ) < nSheetWidth || ( pTexture->GetActualHeight() << 1 ) < nSheetHeight ) { // Put this texture into the packed render target texture. Rect_t texRect; texRect.x = 0; texRect.y = 0; texRect.width = pTexture->GetActualWidth(); texRect.height = pTexture->GetActualHeight(); nodeIndex = m_pDynamicTexturePacker->InsertRect( texRect ); } } // For debugging this will make no packing happen. Each texture is one draw call //nodeIndex = -1; pImageData->m_nNodeIndex = nodeIndex; LoadImageAliasTexture( pAlias, pBaseTextureName ); } //----------------------------------------------------------------------------- // Release the entry for this alias in the packer. //----------------------------------------------------------------------------- void CGameUIDynamicTextures::ReleaseImageAlias( const char *pAlias ) { if ( Q_strcmp( pAlias, "_rt_DynamicUI" ) == 0 ) return; if ( Q_strcmp( pAlias, "errorImageAlias" ) == 0 ) return; if ( Q_strcmp( pAlias, "defaultImageAlias" ) == 0 ) return; ImageAliasData_t *pImageData = GetImageAliasData( pAlias ); if ( pImageData ) { Assert( pImageData->m_nRefCount > 0 ); pImageData->m_nRefCount--; if ( pImageData->m_nRefCount == 0 ) { if ( pImageData->m_bIsInSheet ) { Msg( "Releasing Alias %s\n", pAlias ); m_pDynamicTexturePacker->RemoveRect( pImageData->m_nNodeIndex ); } pImageData->Init(); } } } //----------------------------------------------------------------------------- // Associate this image alias name with a .vtf texture. // This fxn loads the texture into the GameControls shader and makes a material // for you //----------------------------------------------------------------------------- void CGameUIDynamicTextures::LoadImageAliasTexture( const char *pAlias, const char *pBaseTextureName ) { if ( !pBaseTextureName || !*pBaseTextureName ) return; CTextureReference pTexture; pTexture.Init( pBaseTextureName, TEXTURE_GROUP_OTHER, true ); ImageAliasData_t *pImageData = GetImageAliasData( pAlias ); // Now set up the material for the new imagedata. if ( pImageData->m_nNodeIndex == -1 )// it won't fit in the map, give it its own material, it is its own draw call. { // Material names must be different CUtlVector words; V_SplitString( pImageData->m_szBaseTextureName, "/", words ); CFmtStr materialName; materialName.sprintf( "dynamictx_%s", words[words.Count()-1] ); KeyValues *pVMTKeyValues = new KeyValues( "GameControls" ); pVMTKeyValues->SetString( "$basetexture", pImageData->m_szBaseTextureName ); CMaterialReference material; material.Init( materialName, TEXTURE_GROUP_OTHER, pVMTKeyValues ); material->Refresh(); pImageData->m_Material = material; pImageData->m_bIsInSheet = false; } else // Do this if it fit in the packed texture. { if ( !m_RenderMaterial ) return; pImageData->m_Material.Init( GetImageAliasMaterial( "_rt_DynamicUI" ) ); Assert( pImageData->m_Material ); pImageData->m_bIsInSheet = true; const CTexturePacker::TreeEntry_t &newEntry = m_pDynamicTexturePacker->GetEntry( pImageData->m_nNodeIndex ); Assert( newEntry.rc.width == pImageData->m_Width ); Assert( newEntry.rc.height == pImageData->m_Height ); pImageData->m_XPos = newEntry.rc.x; pImageData->m_YPos = newEntry.rc.y; CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->PushRenderTargetAndViewport( m_TexturePage, NULL, pImageData->m_XPos, pImageData->m_YPos, pImageData->m_Width, pImageData->m_Height ); pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->Scale( 1, -1, 1 ); float flPixelOffsetX = 0.5f; float flPixelOffsetY = 0.5f; pRenderContext->Ortho( flPixelOffsetX, flPixelOffsetY, pImageData->m_Width + flPixelOffsetX, pImageData->m_Height + flPixelOffsetY, -1.0f, 1.0f ); //pRenderContext->Ortho( 0, 0, pImageData->m_Width, pImageData->m_Height, -1.0f, 1.0f ); // Clear to random color. Useful for testing. //pRenderContext->ClearColor3ub( rand()%256, rand()%256, rand()%256 ); pRenderContext->ClearColor4ub( 0, 0, 0, 255 ); pRenderContext->ClearBuffers( true, false ); // make sure there is no translation and rotation laying around pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); pRenderContext->LoadIdentity(); pRenderContext->Bind( m_RenderMaterial, (void *)pImageData->m_szBaseTextureName.Get() ); int x = 0; int y = 0; int wide = pImageData->m_Width; int tall = pImageData->m_Height; unsigned char drawcolor[4]; drawcolor[0] = 255; drawcolor[1] = 255; drawcolor[2] = 255; drawcolor[3] = 255; IMesh *pMesh = pRenderContext->GetDynamicMesh( true ); if ( pMesh ) { CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); meshBuilder.Position3f( x, y, 0 ); meshBuilder.Color4ubv( drawcolor ); meshBuilder.TexCoord2f( 0, 0, 0 ); meshBuilder.AdvanceVertexF(); meshBuilder.Position3f( wide, y, 0 ); meshBuilder.Color4ubv( drawcolor ); meshBuilder.TexCoord2f( 0, 1, 0 ); meshBuilder.AdvanceVertexF(); meshBuilder.Position3f( wide, tall, 0 ); meshBuilder.Color4ubv( drawcolor ); meshBuilder.TexCoord2f( 0, 1, 1 ); meshBuilder.AdvanceVertexF(); meshBuilder.Position3f( x, tall, 0 ); meshBuilder.Color4ubv( drawcolor ); meshBuilder.TexCoord2f( 0, 0, 1 ); meshBuilder.AdvanceVertexF(); meshBuilder.End(); pMesh->Draw(); } // Restore the matrices pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_MODEL ); pRenderContext->PopMatrix(); pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PopMatrix(); pRenderContext->PopRenderTargetAndViewport(); } } //----------------------------------------------------------------------------- // Return the material bound to this image alias. // If the texture was not found you will see the purple and black checkerboard // error texture. //----------------------------------------------------------------------------- IMaterial *CGameUIDynamicTextures::GetImageAliasMaterial( const char *pAlias ) { if ( m_ImageAliasMap.Defined( pAlias ) ) { return m_ImageAliasMap[pAlias].m_Material; } return m_ImageAliasMap[ "errorImageAlias" ].m_Material; } //----------------------------------------------------------------------------- // Return the data bound to this image alias. // If the alias was not found you will see the purple and black checkerboard // error texture. //----------------------------------------------------------------------------- ImageAliasData_t *CGameUIDynamicTextures::GetImageAliasData( const char *pAlias ) { if ( m_ImageAliasMap.Defined( pAlias ) ) { return &m_ImageAliasMap[pAlias]; } return &m_ImageAliasMap[ "errorImageAlias" ]; } //----------------------------------------------------------------------------- // Return true if this data is the error alias //----------------------------------------------------------------------------- bool CGameUIDynamicTextures::IsErrorImageAliasData( ImageAliasData_t *pData ) { return ( &m_ImageAliasMap[ "errorImageAlias" ] ) == pData; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CGameUIDynamicTextures::GetDynamicSheetSize( int &nWidth, int &nHeight ) { nWidth = m_TexturePage->GetActualWidth(); nHeight = m_TexturePage->GetActualHeight(); } //----------------------------------------------------------------------------- // Draw the dynamic texture associated with this alias at x, y // It is drawn full size. This fxn is modeled off of CGameUISystemSurface::DrawFontTexture() //----------------------------------------------------------------------------- void CGameUIDynamicTextures::DrawDynamicTexture( const char *pAlias, int x, int y ) { if ( !pAlias || !*pAlias ) return; ImageAliasData_t *pImageData = GetImageAliasData( pAlias ); int wide = 0; int tall = 0; if ( !pImageData->m_bIsInSheet ) { wide = pImageData->m_Width; tall = pImageData->m_Height; } else { GetDynamicSheetSize( wide, tall ); } color32 drawcolor; drawcolor.r = 255; drawcolor.g = 255; drawcolor.b = 255; drawcolor.a = 255; CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pImageData->m_Material ); if ( !pMesh ) return; CMeshBuilder meshBuilder; meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); meshBuilder.Position3f( x, y, 0 ); meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a ); meshBuilder.TexCoord3f( 0, 0, 0, 0 ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( x + wide, y, 0 ); meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a ); meshBuilder.TexCoord3f( 0, 1, 0, 0 ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( x + wide, y + tall, 0 ); meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a ); meshBuilder.TexCoord3f( 0, 1, 1, 0 ); meshBuilder.AdvanceVertex(); meshBuilder.Position3f( x, y + tall, 0 ); meshBuilder.Color4ub( drawcolor.r, drawcolor.g, drawcolor.b, drawcolor.a ); meshBuilder.TexCoord3f( 0, 0, 1, 0 ); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); } //----------------------------------------------------------------------------- // Reload all textures into the rendertarget. //----------------------------------------------------------------------------- void CGameUIDynamicTextures::OnRestore( int nChangeFlags ) { m_bRegenerate = true; } //----------------------------------------------------------------------------- // Reload all textures into the rendertarget. //----------------------------------------------------------------------------- void CGameUIDynamicTextures::RegenerateTexture( int nChangeFlags ) { if ( !m_bRegenerate ) return; int numEntries = m_ImageAliasMap.GetNumStrings(); for ( int i = 0; i < numEntries; ++i ) { const char *pAlias = m_ImageAliasMap.String( i ); ImageAliasData_t *pImageData = GetImageAliasData( pAlias ); if ( IsErrorImageAliasData( pImageData ) ) continue; if ( Q_strcmp( pAlias, "_rt_DynamicUI") == 0 ) continue; if ( !pImageData->m_szBaseTextureName || !*pImageData->m_szBaseTextureName ) continue; ITexture *pTexture = g_pMaterialSystem->FindTexture( pImageData->m_szBaseTextureName.Get(), TEXTURE_GROUP_OTHER, true ); Assert( pTexture ); pTexture->Download(); LoadImageAliasTexture( pAlias, pImageData->m_szBaseTextureName ); } }