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.
1025 lines
31 KiB
1025 lines
31 KiB
//========= Copyright © 1996-2013, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Provide custom texture generation (compositing)
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "composite_texture.h"
|
|
#include "vstdlib/jobthread.h"
|
|
#include "materialsystem/base_visuals_data_processor.h"
|
|
#include "materialsystem_global.h"
|
|
#include "tier0/vprof.h"
|
|
#include "keyvalues.h"
|
|
#include "texturemanager.h"
|
|
|
|
//#define WRITE_OUT_VTF_PRE_COMPRESS
|
|
#ifdef WRITE_OUT_VTF_PRE_COMPRESS
|
|
#include "filesystem.h"
|
|
#endif
|
|
|
|
ConVar mat_verbose_texture_gen( "mat_verbose_texture_gen", "0" );
|
|
|
|
#define TEX_GEN_LOG( msg, ... ) \
|
|
if ( mat_verbose_texture_gen.GetBool() ) \
|
|
{ \
|
|
Msg( "TextureGeneration: " msg, ##__VA_ARGS__ ); \
|
|
} \
|
|
|
|
// NOTE: This has to be the last file included!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
int CCompositeTexture::m_nTextureCount = 0;
|
|
|
|
int s_nCompositeMaterialIndex = 0;
|
|
|
|
static ConVar *s_mat_picmip = NULL;
|
|
|
|
int GetMatPicMip()
|
|
{
|
|
if ( !s_mat_picmip )
|
|
{
|
|
s_mat_picmip = g_pCVar->FindVar( "mat_picmip" );
|
|
}
|
|
|
|
return ( s_mat_picmip && s_mat_picmip->GetInt() > 0 ) ? s_mat_picmip->GetInt() : 0;
|
|
}
|
|
|
|
// this should match the CompositeTextureRTSizes_t enum in composite_texture.h
|
|
static SCompositeTextureRTData_t s_compositeTextureRTData[COMPOSITE_TEXTURE_RT_COUNT] =
|
|
{
|
|
// these should be sorted in descending size order
|
|
#if !defined( PLATFORM_OSX )
|
|
{ "_rt_CustomMaterial2048", 2048, true, false, NULL },
|
|
#endif
|
|
{ "_rt_CustomMaterial1024", 1024, true, false, NULL },
|
|
{ "_rt_CustomMaterial512", 512, true, false, NULL },
|
|
{ "_rt_CustomMaterial256", 256, false, false, NULL },
|
|
{ "_rt_CustomMaterial128", 128, false, false, NULL }
|
|
};
|
|
|
|
int GetRTIndex( int nSize )
|
|
{
|
|
int nClosestBigger = 0;
|
|
for (int i = 0; i < COMPOSITE_TEXTURE_RT_COUNT; i++)
|
|
{
|
|
if ( s_compositeTextureRTData[i].nSize == nSize && s_compositeTextureRTData[i].bAvailable == true )
|
|
{
|
|
return i;
|
|
}
|
|
|
|
if ( s_compositeTextureRTData[i].nSize > nSize && s_compositeTextureRTData[i].bAvailable == true )
|
|
{
|
|
nClosestBigger = i;
|
|
}
|
|
}
|
|
|
|
return nClosestBigger;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Inherited from ITextureRegenerator
|
|
//-----------------------------------------------------------------------------
|
|
// If generation is complete then this will copy the results over into the texture
|
|
// pRect is ignored, this always does the whole texture
|
|
void CCompositeTextureResult::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
|
|
{
|
|
TM_ZONE_FILTERED( TELEMETRY_LEVEL1, 200, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
if ( m_pOwner && m_pOwner->GenerationComplete() )
|
|
{
|
|
TEX_GEN_LOG( "Finished generating texture %s... ", m_pOwner->GetName() );
|
|
IVTFTexture *pResultVTF = m_pOwner->GetResultVTF();
|
|
|
|
// we have already generated once, if we have results of an appropriate size, then copy them over, else signal regeneration
|
|
if ( pResultVTF != NULL && ( pVTFTexture->Width() == ( m_pOwner->ActualSize() ) ) && ( pVTFTexture->Width() <= pResultVTF->Width() ) )
|
|
{
|
|
// handle the case where the destination texture is smaller than our result (can happen with mat_picmip change to values lower than we have RTs for)
|
|
int nMipOffset = 0;
|
|
if ( pVTFTexture->Width() < pResultVTF->Width() )
|
|
{
|
|
int nSourceSize = pResultVTF->Width();
|
|
while ( nSourceSize > pVTFTexture->Width() )
|
|
{
|
|
nMipOffset++;
|
|
nSourceSize >>= 1;
|
|
}
|
|
}
|
|
|
|
// copy each mip over
|
|
for (int nMip = 0; nMip < pResultVTF->MipCount(); nMip++)
|
|
{
|
|
unsigned char *pBuffer = pResultVTF->ImageData( 0, 0, nMip + nMipOffset );
|
|
unsigned char *pImageData = pVTFTexture->ImageData( 0, 0, nMip );
|
|
// this amounts to a memcpy, but deals with properly calculating the size for compressed textures
|
|
ImageLoader::ConvertImageFormat( pBuffer, pResultVTF->Format(), pImageData, pVTFTexture->Format(), pResultVTF->Width() >> ( nMip + nMipOffset ), pResultVTF->Height() >> ( nMip + nMipOffset ) );
|
|
}
|
|
|
|
TEX_GEN_LOG( "SUCCESS.\n" );
|
|
}
|
|
else
|
|
{
|
|
TEX_GEN_LOG( "FAILURE: resulting vtf size missmatch, forcing regenerate.\n" );
|
|
m_pOwner->ForceRegenerate();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCompositeTextureResult::Release()
|
|
{
|
|
if ( m_pTexture != NULL )
|
|
{
|
|
m_pTexture->SetTextureRegenerator( NULL, false );
|
|
m_pTexture->DecrementReferenceCount();
|
|
m_pTexture->DeleteIfUnreferenced();
|
|
m_pTexture = NULL;
|
|
}
|
|
m_pOwner = NULL;
|
|
}
|
|
|
|
//
|
|
// Composite Texture - used to make a custom texture using a Shader that can be used with custom materials
|
|
//
|
|
|
|
CCompositeTexture::CCompositeTexture( const CUtlBuffer &compareBlob, KeyValues *pCompositingMaterialKeyValues, CompositeTextureSize_t size, CompositeTextureFormat_t format, int nMaterialParamNameId, bool bSRGB, bool bIgnorePicMip )
|
|
: m_size( size )
|
|
, m_format( format )
|
|
, m_nMaterialParamNameId( nMaterialParamNameId )
|
|
, m_bSRGB( bSRGB )
|
|
, m_nRegenerateStage( COMPOSITE_TEXTURE_STATE_NOT_STARTED )
|
|
, m_pResultVTF( NULL )
|
|
, m_ResultTexture( this )
|
|
, m_bNeedsRegenerate( false )
|
|
, m_bNeedsFinalize( true )
|
|
, m_pScratchVTF( NULL )
|
|
, m_pCustomMaterialRT( NULL )
|
|
, m_pCompositingMaterial( NULL )
|
|
, m_nLastFrameCount( 0 )
|
|
, m_pPixelsReadEvent( NULL )
|
|
, m_bIgnorePicMip( bIgnorePicMip )
|
|
{
|
|
for ( int i = 0; i < NUM_PRELOAD_TEXTURES; i++ )
|
|
{
|
|
m_pPreLoadTextures[ i ] = NULL;
|
|
}
|
|
|
|
m_compareBlob.CopyBuffer( compareBlob );
|
|
|
|
// we need to copy this, because the passed in one was allocated outside materialsystem.dll
|
|
m_pCompositingMaterialKeyValues = pCompositingMaterialKeyValues->MakeCopy();
|
|
|
|
m_nActualSize = ( 1 << Size() ) >> ( m_bIgnorePicMip ? 0 : GetMatPicMip() );
|
|
V_sprintf_safe( m_szTextureName, "composite_texture_%d_%d_%d_%d", m_nMaterialParamNameId, m_nActualSize, m_format, m_nTextureCount++ );
|
|
|
|
}
|
|
|
|
bool CCompositeTexture::Init()
|
|
{
|
|
int nRT = GetRTIndex( m_nActualSize );
|
|
|
|
m_pCustomMaterialRT = materials->FindTexture( s_compositeTextureRTData[nRT].pName, TEXTURE_GROUP_RENDER_TARGET );
|
|
|
|
if ( !m_pCustomMaterialRT->IsError() )
|
|
{
|
|
m_pCustomMaterialRT->AddRef();
|
|
m_pScratchVTF = s_compositeTextureRTData[nRT].pScratch;
|
|
}
|
|
else
|
|
{
|
|
// release the error texture
|
|
m_pCustomMaterialRT->Release();
|
|
m_pCustomMaterialRT = NULL;
|
|
|
|
Warning( "Error creating composite tex8ture! Could not find render target texture. \n" );
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_COMPLETE;
|
|
return false;
|
|
}
|
|
|
|
// it's possible for the result vtf to exist already in the case of regeneration, so we clean up the old one before making a new one
|
|
if ( m_pResultVTF != NULL )
|
|
{
|
|
DestroyVTFTexture( m_pResultVTF );
|
|
m_pResultVTF = NULL;
|
|
}
|
|
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "CreateResultVTF" );
|
|
// create the final result VTF that we'll keep around. It's compressed and used to copy over to the actual texture whenever the texture needs downloading
|
|
m_pResultVTF = CreateVTFTexture();
|
|
m_pResultVTF->Init( m_pScratchVTF->Width(), m_pScratchVTF->Height(), m_pScratchVTF->Depth(), ( Format() == COMPOSITE_TEXTURE_FORMAT_DXT1 ) ? IMAGE_FORMAT_DXT1_RUNTIME : IMAGE_FORMAT_DXT5_RUNTIME, TEXTUREFLAGS_ALL_MIPS, 1, m_pScratchVTF->MipCount() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
CCompositeTexture::~CCompositeTexture()
|
|
{
|
|
ReleasePreloadedTextures();
|
|
if ( m_pCompositingMaterial != NULL )
|
|
{
|
|
m_pCompositingMaterial->DecrementReferenceCount();
|
|
m_pCompositingMaterial->DeleteIfUnreferenced();
|
|
m_pCompositingMaterial = NULL;
|
|
}
|
|
|
|
if ( m_pCompositingMaterialKeyValues != NULL )
|
|
{
|
|
m_pCompositingMaterialKeyValues->deleteThis();
|
|
m_pCompositingMaterialKeyValues = NULL;
|
|
}
|
|
|
|
m_ResultTexture.Release();
|
|
|
|
if ( m_pResultVTF != NULL )
|
|
{
|
|
DestroyVTFTexture(m_pResultVTF);
|
|
m_pResultVTF = NULL;
|
|
}
|
|
|
|
m_pScratchVTF = NULL;
|
|
|
|
if ( m_pCustomMaterialRT != NULL )
|
|
{
|
|
m_pCustomMaterialRT->Release();
|
|
m_pCustomMaterialRT = NULL;
|
|
}
|
|
|
|
if ( m_pPixelsReadEvent != NULL )
|
|
{
|
|
delete m_pPixelsReadEvent;
|
|
m_pPixelsReadEvent = NULL;
|
|
}
|
|
}
|
|
|
|
void CCompositeTexture::ReleasePreloadedTextures()
|
|
{
|
|
for ( int i = 0; i < NUM_PRELOAD_TEXTURES; i++ )
|
|
{
|
|
if ( m_pPreLoadTextures[ i ] != NULL )
|
|
{
|
|
m_pPreLoadTextures[ i ]->DecrementReferenceCount();
|
|
m_pPreLoadTextures[ i ]->DeleteIfUnreferenced();
|
|
m_pPreLoadTextures[ i ] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is called from the GenerationStep which is called from the generate thread
|
|
void CCompositeTexture::GenerateComposite( void )
|
|
{
|
|
TM_ZONE_FILTERED( TELEMETRY_LEVEL1, 100, TMZF_NONE, "GenerateComposite" );
|
|
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_NOT_STARTED )
|
|
{
|
|
TEX_GEN_LOG( "Loading texture for %s\n", GetName() );
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_ASYNC_TEXTURE_LOAD;
|
|
}
|
|
else if ( m_pScratchVTF != NULL )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_COPY_TO_VTF_COMPLETE )
|
|
{
|
|
// no longer need the RT
|
|
if ( m_pCustomMaterialRT != NULL )
|
|
{
|
|
m_pCustomMaterialRT->Release();
|
|
m_pCustomMaterialRT = NULL;
|
|
}
|
|
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "MipGen" );
|
|
m_pScratchVTF->GenerateMipmaps();
|
|
}
|
|
|
|
#ifdef WRITE_OUT_VTF_PRE_COMPRESS
|
|
CUtlBuffer buf;
|
|
m_pScratchVTF->Serialize(buf);
|
|
|
|
char szTextureName[MAX_PATH];
|
|
V_snprintf(szTextureName, MAX_PATH, "d:/temp/%s.vtf", m_szTextureName );
|
|
|
|
FileHandle_t f = g_pFullFileSystem->Open(szTextureName, "wb", NULL);
|
|
|
|
if ( f != FILESYSTEM_INVALID_HANDLE )
|
|
{
|
|
g_pFullFileSystem->Write( buf.Base(), buf.TellMaxPut(), f );
|
|
g_pFullFileSystem->Close(f);
|
|
}
|
|
#endif
|
|
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "DXT Compress" );
|
|
// compress and copy the Scratch VTF mip maps to the Result VTF
|
|
for (int nMip = 0; nMip < m_pScratchVTF->MipCount(); nMip++)
|
|
{
|
|
unsigned char *pResultImageData = m_pResultVTF->ImageData( 0, 0, nMip );
|
|
unsigned char *pScratchImageData = m_pScratchVTF->ImageData( 0, 0, nMip );
|
|
ImageLoader::ConvertImageFormat( pScratchImageData, IMAGE_FORMAT_RGBA8888, pResultImageData, ( Format() == COMPOSITE_TEXTURE_FORMAT_DXT1 ) ? IMAGE_FORMAT_DXT1_RUNTIME : IMAGE_FORMAT_DXT5_RUNTIME, m_pResultVTF->Width() >> nMip, m_pResultVTF->Height() >> nMip );
|
|
}
|
|
}
|
|
|
|
// done with the scratch VTF
|
|
m_pScratchVTF = NULL;
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_WAITING_FOR_MATERIAL_CLEANUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// This does one generation step. It's called from the generate thread
|
|
void CCompositeTexture::GenerationStep( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_REQUESTED_READ )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "ReadPixelsEventWait" );
|
|
// wait on ReadPixelsAsync() to signal up that it's completed
|
|
if ( m_pPixelsReadEvent->Wait( 20 ) )
|
|
{
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_WAITING_FOR_GETRESULT;
|
|
}
|
|
}
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_REQUESTED_GETRESULT )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "ReadPixelsGetResultEventWait" );
|
|
// wait on ReadPixelsAsyncGetResult() to signal up that it's completed
|
|
if ( m_pPixelsReadEvent->Wait( 20 ) )
|
|
{
|
|
delete m_pPixelsReadEvent;
|
|
m_pPixelsReadEvent = NULL;
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_COPY_TO_VTF_COMPLETE;
|
|
}
|
|
}
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_WAITING_FOR_ASYNC_TEXTURE_LOAD_FINISH )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "WaitForAsyncTextureLoads" );
|
|
bool bDone = true;
|
|
for ( int i = 0; i < NUM_PRELOAD_TEXTURES; i++ )
|
|
{
|
|
if ( ( m_pPreLoadTextures[ i ] != NULL ) && !m_pPreLoadTextures[ i ]->IsAsyncDone() )
|
|
{
|
|
bDone = false;
|
|
break;
|
|
}
|
|
}
|
|
if ( bDone )
|
|
{
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_NEEDS_INIT;
|
|
}
|
|
}
|
|
|
|
GenerateComposite();
|
|
}
|
|
|
|
void CCompositeTexture::ForceRegenerate( void )
|
|
{
|
|
// we need to recalculate m_nActualSize
|
|
m_nActualSize = ( 1 << Size() ) >> ( m_bIgnorePicMip ? 0 : GetMatPicMip() );
|
|
|
|
m_bNeedsRegenerate = true;
|
|
}
|
|
|
|
struct materialTextureParams
|
|
{
|
|
const char *m_pParamName;
|
|
const char *m_pDefaultTexture;
|
|
};
|
|
|
|
void CCompositeTexture::DoAsyncTextureLoad( void )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "DoAsyncTextureLoad" );
|
|
|
|
materialTextureParams materialParams[ NUM_PRELOAD_TEXTURES ] =
|
|
{
|
|
// used by weapons
|
|
{ "$basetexture", nullptr },
|
|
{ "$exptexture", nullptr },
|
|
{ "$painttexture", nullptr },
|
|
{ "$maskstexture", nullptr },
|
|
{ "$aotexture", nullptr },
|
|
{ "$postexture", nullptr },
|
|
{ "$surfacetexture", nullptr },
|
|
|
|
// used by gloves (character / custom_character shaders)
|
|
{ "$bumpmap", nullptr },
|
|
{ "$masks1", nullptr },
|
|
{ "$phongwarptexture", nullptr },
|
|
{ "$fresnelrangestexture", nullptr },
|
|
{ "$masks2", nullptr },
|
|
{ "$envmap", nullptr },
|
|
{ "$materialmask", nullptr },
|
|
{ "$ao", nullptr },
|
|
{ "$grunge", nullptr },
|
|
{ "$detail", nullptr },
|
|
{ "$detailnormal", nullptr },
|
|
{ "$pattern", nullptr },
|
|
{ "$noise", "models/weapons/customization/materials/noise" } // default set in custom character shader
|
|
};
|
|
|
|
//KeyValuesDumpAsDevMsg( m_pCompositingMaterialKeyValues, 1, 1 );
|
|
|
|
// initiate async load of the textures needed for the compositing material
|
|
for ( int i = 0; i < NUM_PRELOAD_TEXTURES; i++ )
|
|
{
|
|
const char *pszTexture = m_pCompositingMaterialKeyValues->GetString( materialParams[ i ].m_pParamName );
|
|
if ( !( pszTexture && pszTexture[0] != 0 ) && materialParams[i].m_pDefaultTexture )
|
|
{
|
|
pszTexture = materialParams[i].m_pDefaultTexture;
|
|
}
|
|
|
|
if ( pszTexture && pszTexture[0] != 0 )
|
|
{
|
|
m_pPreLoadTextures[ i ] = TextureManager()->FindOrLoadTexture( pszTexture, TEXTURE_GROUP_COMPOSITE, TEXTUREFLAGS_ASYNC_DOWNLOAD );
|
|
if ( !m_pPreLoadTextures[ i ]->IsError() )
|
|
{
|
|
m_pPreLoadTextures[ i ]->AddRef();
|
|
}
|
|
else
|
|
{
|
|
m_pPreLoadTextures[ i ] = NULL;
|
|
DevMsg( "DoAsyncTextureLoad: Failed to preload texture: %s (%s)\n", pszTexture, materialParams[ i ].m_pParamName );
|
|
}
|
|
}
|
|
}
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_WAITING_FOR_ASYNC_TEXTURE_LOAD_FINISH;
|
|
}
|
|
|
|
void CCompositeTexture::CreateCompositingMaterial( void )
|
|
{
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "CreateCompositingMat" );
|
|
|
|
char szCompositeMaterialName[32];
|
|
V_sprintf_safe( szCompositeMaterialName, "compositing_material_%d", s_nCompositeMaterialIndex++ );
|
|
m_pCompositingMaterial = materials->CreateMaterial( szCompositeMaterialName, m_pCompositingMaterialKeyValues->MakeCopy() );
|
|
|
|
if ( m_pCompositingMaterial && m_pCompositingMaterial->IsErrorMaterial() )
|
|
{
|
|
// release the error material
|
|
m_pCompositingMaterial->Release();
|
|
|
|
// we do not call DeleteIfUnreferenced here because it's the error material
|
|
m_pCompositingMaterial = NULL;
|
|
|
|
Warning( "Error creating compositing material! Marking composite complete and not rendering to RT...\n" );
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_COMPLETE;
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "RefreshCompositingMat" );
|
|
// this causes a precache of the material
|
|
m_pCompositingMaterial->Refresh();
|
|
}
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_WAITING_FOR_RENDER_TO_RT;
|
|
}
|
|
|
|
void CCompositeTexture::RenderToRT( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_WAITING_FOR_RENDER_TO_RT )
|
|
{
|
|
if ( materials->CanDownloadTextures() )
|
|
{
|
|
int resolutionX = m_pCustomMaterialRT->GetActualWidth();
|
|
int resolutionY = m_pCustomMaterialRT->GetActualHeight();
|
|
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "RenderToRT %d", resolutionX );
|
|
|
|
//init render context and set the the custom weapon RT as the current target
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
pRenderContext->PushRenderTargetAndViewport( m_pCustomMaterialRT, 0, 0, resolutionX, resolutionY );
|
|
|
|
//render a quad using the composite material to the custom weapon RT
|
|
pRenderContext->DrawScreenSpaceRectangle( m_pCompositingMaterial, 0, 0, resolutionX, resolutionY, 0, 0, resolutionX - 1, resolutionY - 1, resolutionX, resolutionY );
|
|
|
|
pRenderContext->PopRenderTargetAndViewport();
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_RENDERED_TO_RT;
|
|
}
|
|
}
|
|
}
|
|
|
|
// this is just to have two frames between rendering and attempting to read back.
|
|
void CCompositeTexture::AdvanceToReadRT( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_RENDERED_TO_RT )
|
|
{
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_WAITING_FOR_READ_RT;
|
|
}
|
|
}
|
|
|
|
void CCompositeTexture::ReadRT( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_WAITING_FOR_READ_RT )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "ReadRT" );
|
|
if ( materials->CanDownloadTextures() )
|
|
{
|
|
int resolutionX = m_pCustomMaterialRT->GetActualWidth();
|
|
int resolutionY = m_pCustomMaterialRT->GetActualHeight();
|
|
unsigned char *pDestImage = m_pScratchVTF->ImageData( 0, 0, 0 );
|
|
|
|
// queue up read pixels
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
m_pPixelsReadEvent = new CThreadEvent();
|
|
pRenderContext->ReadPixelsAsync( 0, 0, resolutionX, resolutionY, pDestImage, IMAGE_FORMAT_RGBA8888, m_pCustomMaterialRT, m_pPixelsReadEvent );
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_REQUESTED_READ;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCompositeTexture::GetReadRTResult( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_WAITING_FOR_GETRESULT )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "GetReadRTResult" );
|
|
if ( materials->CanDownloadTextures() )
|
|
{
|
|
int resolutionX = m_pCustomMaterialRT->GetActualWidth();
|
|
int resolutionY = m_pCustomMaterialRT->GetActualHeight();
|
|
unsigned char *pDestImage = m_pScratchVTF->ImageData( 0, 0, 0 );
|
|
|
|
// queue up read pixels
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
m_pPixelsReadEvent->Reset();
|
|
pRenderContext->ReadPixelsAsyncGetResult( 0, 0, resolutionX, resolutionY, pDestImage, IMAGE_FORMAT_RGBA8888, m_pPixelsReadEvent );
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_REQUESTED_GETRESULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCompositeTexture::CleanupCompositingMaterial( void )
|
|
{
|
|
if ( m_nRegenerateStage == COMPOSITE_TEXTURE_STATE_WAITING_FOR_MATERIAL_CLEANUP )
|
|
{
|
|
if ( m_pCompositingMaterial != NULL )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "CleanupCompositingMat" );
|
|
m_pCompositingMaterial->DecrementReferenceCount();
|
|
m_pCompositingMaterial->DeleteIfUnreferenced();
|
|
m_pCompositingMaterial = NULL;
|
|
}
|
|
ReleasePreloadedTextures(); // no longer need these
|
|
|
|
m_nRegenerateStage = COMPOSITE_TEXTURE_STATE_COMPLETE;
|
|
}
|
|
}
|
|
|
|
void CCompositeTexture::Usage( int &nTextures, int &nBackingTextures )
|
|
{
|
|
nTextures += ( m_ResultTexture.m_pTexture != NULL ) ? m_ResultTexture.m_pTexture->GetApproximateVidMemBytes() : 0;
|
|
nBackingTextures += ( m_pResultVTF != NULL ) ? m_pResultVTF->ComputeTotalSize() : 0;
|
|
}
|
|
|
|
void CCompositeTexture::Finalize()
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "Finalize" );
|
|
|
|
if ( m_ResultTexture.m_pTexture == NULL )
|
|
{
|
|
m_ResultTexture.m_pTexture = materials->CreateProceduralTexture( m_szTextureName, TEXTURE_GROUP_COMPOSITE, ( 1 << Size() ), ( 1 << Size() ),
|
|
( Format() == COMPOSITE_TEXTURE_FORMAT_DXT5 ) ? IMAGE_FORMAT_DXT5_RUNTIME : IMAGE_FORMAT_DXT1_RUNTIME,
|
|
TEXTUREFLAGS_PROCEDURAL | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_ANISOTROPIC | ( ( m_bSRGB ) ? TEXTUREFLAGS_SRGB : 0 ) | TEXTUREFLAGS_SKIP_INITIAL_DOWNLOAD );
|
|
m_ResultTexture.m_pTexture->SetTextureRegenerator( &m_ResultTexture );
|
|
}
|
|
|
|
m_ResultTexture.m_pTexture->Download();
|
|
m_bNeedsFinalize = false;
|
|
|
|
#if defined( DX_TO_GL_ABSTRACTION )
|
|
// Free VTF, significant mem saving - after Downloand not used for anything other than CWeaponCSBase::SaveCustomMaterialsTextures()
|
|
// forceregenerate ensures this is safe on loss of focus - see CCompositeTextureResult::RegenerateTextureBits()
|
|
if ( m_pResultVTF != NULL )
|
|
{
|
|
DestroyVTFTexture( m_pResultVTF );
|
|
m_pResultVTF = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool CCompositeTexture::Compare( const SCompositeTextureInfo &textureInfo )
|
|
{
|
|
if (Size() == textureInfo.m_size &&
|
|
Format() == textureInfo.m_format &&
|
|
GetMaterialParamNameId() == textureInfo.m_nMaterialParamID &&
|
|
IsSRGB() == textureInfo.m_bSRGB &&
|
|
textureInfo.m_pVisualsDataProcessor->GetCompareObject()->Compare( GetVisualsDataCompareBlob() ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
// global weapon material generator
|
|
// the game uses this to make/get a custom material for a weapon
|
|
//
|
|
|
|
CCompositeTextureGenerator::CCompositeTextureGenerator( void )
|
|
: m_bGenerateThreadExit( false )
|
|
, m_hGenerateThread( NULL )
|
|
, m_pGunGrimeTexture( NULL )
|
|
, m_pPaintWearTexture( NULL )
|
|
{
|
|
#ifndef DEDICATED
|
|
m_pCompositeTextures.EnsureCapacity( 256 );
|
|
m_pPendingCompositeTextures.EnsureCapacity( 256 );
|
|
#endif
|
|
}
|
|
|
|
CCompositeTextureGenerator::~CCompositeTextureGenerator()
|
|
{
|
|
}
|
|
|
|
// this is called at the end of each frame
|
|
bool ProcessCompositeTextureGenerator( void )
|
|
{
|
|
return MaterialSystem()->GetCompositeTextureGenerator()->Process();
|
|
}
|
|
|
|
// this handles doing rendering and material refreshes for the regenerators, downloads textures and flags materials ready so they will draw,
|
|
// triggers regenerations as needed, and handles swapping materials that are pending swap and ready, and cleans up materials that are no longer used
|
|
bool CCompositeTextureGenerator::Process( void )
|
|
{
|
|
TM_ZONE( TELEMETRY_LEVEL1, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
bool bDidWork = false;
|
|
|
|
for ( int i = m_pPendingCompositeTextures.Count() - 1; i >= 0; i-- )
|
|
{
|
|
CCompositeTexture *pTexture = m_pPendingCompositeTextures[ i ];
|
|
if ( pTexture && !pTexture->IsReady() )
|
|
{
|
|
if ( pTexture->GenerationComplete() && pTexture->NeedsFinalize() )
|
|
{
|
|
pTexture->Finalize();
|
|
// move from pending to final list
|
|
m_pCompositeTextures.AddToTail( pTexture );
|
|
m_pPendingCompositeTextures.Remove( i );
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsAsyncTextureLoad() )
|
|
{
|
|
pTexture->DoAsyncTextureLoad();
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsCompositingMaterial() )
|
|
{
|
|
pTexture->CreateCompositingMaterial();
|
|
bDidWork = true;
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsMaterialCleanup() )
|
|
{
|
|
pTexture->CleanupCompositingMaterial();
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsRender() )
|
|
{
|
|
pTexture->RenderToRT();
|
|
bDidWork = true;
|
|
break;
|
|
}
|
|
else if ( pTexture->HasRendered() )
|
|
{
|
|
pTexture->AdvanceToReadRT();
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsReadRT() )
|
|
{
|
|
pTexture->ReadRT();
|
|
break;
|
|
}
|
|
else if ( pTexture->NeedsGetResult() )
|
|
{
|
|
pTexture->GetReadRTResult();
|
|
bDidWork = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( int i = m_pCompositeTextures.Count() - 1; i >= 0; i-- )
|
|
{
|
|
CCompositeTexture *pTexture = m_pCompositeTextures[ i ];
|
|
if ( pTexture )
|
|
{
|
|
// see if a material needs regeneration (it must have completed generation)
|
|
if ( pTexture->GenerationComplete() && pTexture->NeedsRegenerate() )
|
|
{
|
|
// signal the regeneration, and set the material to not draw
|
|
pTexture->Refresh();
|
|
pTexture->Init();
|
|
|
|
// move back to the pending list
|
|
m_pPendingCompositeTextures.AddToTail( pTexture );
|
|
m_pCompositeTextures.Remove( i );
|
|
|
|
// add material to generation queue
|
|
m_GenerateQueueMutex.Lock();
|
|
m_pGenerateQueue.AddToTail( pTexture );
|
|
m_GenerateQueueMutex.Unlock();
|
|
}
|
|
// clean up materials that are no longer used (we are the only reference)
|
|
else if ( pTexture->ShouldRelease() )
|
|
{
|
|
pTexture->Release();
|
|
m_pCompositeTextures.Remove( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
return bDidWork;
|
|
}
|
|
|
|
bool CCompositeTextureGenerator::Init( void )
|
|
{
|
|
#ifndef DEDICATED
|
|
for (int i = 0; i < COMPOSITE_TEXTURE_RT_COUNT; i++)
|
|
{
|
|
ITexture *pRT = MaterialSystem()->CreateNamedRenderTargetTextureEx2( s_compositeTextureRTData[i].pName, s_compositeTextureRTData[i].nSize, s_compositeTextureRTData[i].nSize, RT_SIZE_NO_CHANGE,
|
|
IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_SINGLECOPY | ( ( s_compositeTextureRTData[i].bSRGB ) ? TEXTUREFLAGS_SRGB : 0 ) );
|
|
if ( pRT->IsError() )
|
|
{
|
|
pRT->Release();
|
|
pRT = NULL;
|
|
}
|
|
|
|
if ( pRT == NULL )
|
|
{
|
|
Warning( "Failed to create %d x %d render target for composite texturing!\n", s_compositeTextureRTData[i].nSize, s_compositeTextureRTData[i].nSize );
|
|
}
|
|
else
|
|
{
|
|
m_CompositeTextureManagerRTs[i].Init( pRT );
|
|
s_compositeTextureRTData[i].bAvailable = true;
|
|
|
|
s_compositeTextureRTData[i].pScratch = CreateVTFTexture();
|
|
s_compositeTextureRTData[i].pScratch->Init( s_compositeTextureRTData[i].nSize, s_compositeTextureRTData[i].nSize, 1, IMAGE_FORMAT_RGBA8888, TEXTUREFLAGS_ALL_MIPS, 1 );
|
|
}
|
|
}
|
|
|
|
// precache scratch and grime textures
|
|
m_pGunGrimeTexture = TextureManager()->FindOrLoadTexture( "models/weapons/customization/shared/gun_grunge.vtf", TEXTURE_GROUP_COMPOSITE );
|
|
if ( !m_pGunGrimeTexture->IsError() )
|
|
{
|
|
m_pGunGrimeTexture->AddRef();
|
|
}
|
|
else
|
|
{
|
|
m_pGunGrimeTexture = NULL;
|
|
}
|
|
m_pPaintWearTexture = TextureManager()->FindOrLoadTexture( "models/weapons/customization/shared/paint_wear.vtf", TEXTURE_GROUP_COMPOSITE );
|
|
if ( !m_pPaintWearTexture->IsError() )
|
|
{
|
|
m_pPaintWearTexture->AddRef();
|
|
}
|
|
else
|
|
{
|
|
m_pPaintWearTexture = NULL;
|
|
}
|
|
|
|
MaterialSystem()->AddEndFramePriorToNextContextFunc( ::ProcessCompositeTextureGenerator );
|
|
CreateGenerateThread();
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void CCompositeTextureGenerator::Shutdown( void )
|
|
{
|
|
#ifndef DEDICATED
|
|
DestroyGenerateThread();
|
|
MaterialSystem()->RemoveEndFramePriorToNextContextFunc( ::ProcessCompositeTextureGenerator );
|
|
DestroyTextures();
|
|
for (int i = 0; i < COMPOSITE_TEXTURE_RT_COUNT; i++)
|
|
{
|
|
if ( s_compositeTextureRTData[i].bAvailable )
|
|
{
|
|
s_compositeTextureRTData[i].bAvailable = false;
|
|
if ( s_compositeTextureRTData[i].pScratch != NULL )
|
|
{
|
|
DestroyVTFTexture( s_compositeTextureRTData[i].pScratch );
|
|
s_compositeTextureRTData[i].pScratch = NULL;
|
|
}
|
|
m_CompositeTextureManagerRTs[i].Shutdown();
|
|
}
|
|
}
|
|
|
|
if ( m_pPaintWearTexture )
|
|
{
|
|
m_pPaintWearTexture->DecrementReferenceCount();
|
|
m_pPaintWearTexture->DeleteIfUnreferenced();
|
|
m_pPaintWearTexture = NULL;
|
|
}
|
|
if ( m_pGunGrimeTexture )
|
|
{
|
|
m_pGunGrimeTexture->DecrementReferenceCount();
|
|
m_pGunGrimeTexture->DeleteIfUnreferenced();
|
|
m_pGunGrimeTexture = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ICompositeTexture *CCompositeTextureGenerator::GetCompositeTexture( const SCompositeTextureInfo &textureInfo, bool bIgnorePicMip, bool bAllowCreate )
|
|
{
|
|
return GetCompositeTexture( textureInfo.m_pVisualsDataProcessor, textureInfo.m_nMaterialParamID, textureInfo.m_size, textureInfo.m_format, textureInfo.m_bSRGB, bIgnorePicMip, bAllowCreate );
|
|
}
|
|
|
|
ICompositeTexture *CCompositeTextureGenerator::GetCompositeTexture( IVisualsDataProcessor *pVisualsDataProcessor, int nMaterialParamNameId, CompositeTextureSize_t size, CompositeTextureFormat_t format, bool bSRGB, bool bIgnorePicMip, bool bAllowCreate )
|
|
{
|
|
#if defined( DEDICATED ) || defined( DISABLE_CUSTOM_MATERIAL_GENERATION )
|
|
return NULL;
|
|
#endif
|
|
|
|
if ( !pVisualsDataProcessor->HasCustomMaterial() )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
CCompositeTexture *pTexture = NULL;
|
|
|
|
// Look for an existing material match
|
|
for ( int i = 0; i < m_pCompositeTextures.Count(); ++i )
|
|
{
|
|
CCompositeTexture *pCompareTexture = m_pCompositeTextures[ i ];
|
|
if ( pCompareTexture->Format() == format &&
|
|
pCompareTexture->Size() == size &&
|
|
pCompareTexture->GetMaterialParamNameId() == nMaterialParamNameId &&
|
|
pCompareTexture->IsSRGB() == bSRGB &&
|
|
pVisualsDataProcessor->GetCompareObject()->Compare( pCompareTexture->GetVisualsDataCompareBlob() ) )
|
|
{
|
|
pTexture = m_pCompositeTextures[ i ];
|
|
break;
|
|
}
|
|
}
|
|
for ( int i = 0; i < m_pPendingCompositeTextures.Count(); ++i )
|
|
{
|
|
CCompositeTexture *pCompareTexture = m_pPendingCompositeTextures[ i ];
|
|
if ( pCompareTexture->Format() == format &&
|
|
pCompareTexture->Size() == size &&
|
|
pCompareTexture->GetMaterialParamNameId() == nMaterialParamNameId &&
|
|
pCompareTexture->IsSRGB() == bSRGB &&
|
|
pVisualsDataProcessor->GetCompareObject()->Compare( pCompareTexture->GetVisualsDataCompareBlob() ) )
|
|
{
|
|
pTexture = m_pPendingCompositeTextures[ i ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !pTexture && bAllowCreate )
|
|
{
|
|
KeyValues *pCompositeMaterialKeyValues = pVisualsDataProcessor->GenerateCompositeMaterialKeyValues( nMaterialParamNameId );
|
|
if ( !pCompositeMaterialKeyValues )
|
|
{
|
|
return NULL;
|
|
}
|
|
pTexture = new CCompositeTexture( pVisualsDataProcessor->GetCompareObject()->GetCompareBlob(), pCompositeMaterialKeyValues, size, format, nMaterialParamNameId, bSRGB, bIgnorePicMip );
|
|
pCompositeMaterialKeyValues->deleteThis(); // copied inside CCompositeTexture(), no longer needed
|
|
|
|
if ( pTexture->Init() )
|
|
{
|
|
m_pPendingCompositeTextures.AddToTail( pTexture );
|
|
|
|
// add texture to generation queue (handled in the generation thread)
|
|
m_GenerateQueueMutex.Lock();
|
|
m_pGenerateQueue.AddToHead( pTexture );
|
|
m_GenerateQueueMutex.Unlock();
|
|
}
|
|
else
|
|
{
|
|
pTexture->Release();
|
|
pTexture = NULL;
|
|
}
|
|
}
|
|
|
|
// The texture may not be complete yet, but it will be completed over the next few frames via Process()
|
|
return pTexture;
|
|
}
|
|
|
|
bool CCompositeTextureGenerator::ForceRegenerate( ICompositeTexture *pTextureInterface )
|
|
{
|
|
CCompositeTexture *pTexture = dynamic_cast< CCompositeTexture * >( pTextureInterface );
|
|
if ( !pTexture )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int nTexture = m_pCompositeTextures.Find( pTexture );
|
|
if ( nTexture != -1 )
|
|
{
|
|
// move back to the pending list
|
|
m_pPendingCompositeTextures.AddToTail( pTexture );
|
|
m_pCompositeTextures.Remove( nTexture );
|
|
}
|
|
else if ( m_pPendingCompositeTextures.Find( pTexture ) == -1 )
|
|
{
|
|
// texture isn't in either list, so we can't regenerate it
|
|
return false;
|
|
}
|
|
|
|
pTexture->Refresh();
|
|
|
|
// add texture to generation queue (handled in the generation thread)
|
|
m_GenerateQueueMutex.Lock();
|
|
m_pGenerateQueue.AddToTail( pTexture );
|
|
m_GenerateQueueMutex.Unlock();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CCompositeTextureGenerator::DestroyTextures( void )
|
|
{
|
|
for ( int i = 0; i < m_pCompositeTextures.Count(); ++i )
|
|
{
|
|
CCompositeTexture *pTexture = m_pCompositeTextures[ i ];
|
|
if ( pTexture )
|
|
{
|
|
pTexture->ReleaseResult();
|
|
pTexture->Release();
|
|
}
|
|
}
|
|
m_pCompositeTextures.RemoveAll();
|
|
|
|
for ( int i = 0; i < m_pPendingCompositeTextures.Count(); ++i )
|
|
{
|
|
CCompositeTexture *pTexture = m_pPendingCompositeTextures[ i ];
|
|
if ( pTexture )
|
|
{
|
|
pTexture->ReleaseResult();
|
|
pTexture->Release();
|
|
}
|
|
}
|
|
m_pPendingCompositeTextures.RemoveAll();
|
|
}
|
|
|
|
void CCompositeTextureGenerator::CreateGenerateThread()
|
|
{
|
|
// kick off a thread to do custom texture generation
|
|
m_bGenerateThreadExit = false;
|
|
m_hGenerateThread = ThreadExecuteSolo( "CompositeTextureGenerationThread", this, &CCompositeTextureGenerator::GenerateThread );
|
|
}
|
|
|
|
void CCompositeTextureGenerator::DestroyGenerateThread()
|
|
{
|
|
if ( m_hGenerateThread )
|
|
{
|
|
// remove all the queued materials from the generate queue
|
|
m_GenerateQueueMutex.Lock();
|
|
m_pGenerateQueue.RemoveAll();
|
|
m_GenerateQueueMutex.Unlock();
|
|
|
|
m_bGenerateThreadExit = true;
|
|
ThreadJoin( m_hGenerateThread );
|
|
ReleaseThreadHandle( m_hGenerateThread );
|
|
m_hGenerateThread = NULL;
|
|
}
|
|
}
|
|
|
|
void CCompositeTextureGenerator::GenerateThread()
|
|
{
|
|
while ( !m_bGenerateThreadExit )
|
|
{
|
|
CCompositeTexture *pTexture = NULL;
|
|
|
|
// pull a material that needs generation off the queue (if any exist)
|
|
m_GenerateQueueMutex.Lock();
|
|
if ( m_pGenerateQueue.Count() > 0 )
|
|
{
|
|
pTexture = m_pGenerateQueue.Element( m_pGenerateQueue.Head() );
|
|
m_pGenerateQueue.Remove( m_pGenerateQueue.Head() );
|
|
}
|
|
m_GenerateQueueMutex.Unlock();
|
|
|
|
if ( pTexture )
|
|
{
|
|
while ( !m_bGenerateThreadExit && !pTexture->GenerationComplete() )
|
|
{
|
|
pTexture->GenerationStep();
|
|
|
|
ThreadSleep( 1 );
|
|
}
|
|
|
|
// wait for the current texture to finalize (in the main thread) before going to the next one
|
|
while ( !m_bGenerateThreadExit && pTexture->NeedsFinalize() && pTexture->GenerationComplete() )
|
|
{
|
|
ThreadSleep( 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ThreadSleep( 1 );
|
|
}
|
|
}
|
|
|
|
m_GenerateQueueMutex.Lock();
|
|
m_pGenerateQueue.RemoveAll();
|
|
m_GenerateQueueMutex.Unlock();
|
|
}
|