/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "../Common/Textures/TextureStreamPool.h" #include "DriverD3D.h" #if !defined(CHK_RENDTH) # define CHK_RENDTH assert(gRenDev->m_pRT->IsRenderThread()) #endif #if !defined(CHK_MAINTH) # define CHK_MAINTH assert(gRenDev->m_pRT->IsMainThread()) #endif #if !defined(CHK_MAINORRENDTH) # define CHK_MAINORRENDTH assert(gRenDev->m_pRT->IsMainThread() || gRenDev->m_pRT->IsRenderThread()) #endif CryCriticalSection STexPoolItemHdr::s_sSyncLock; bool STexPoolItem::IsStillUsedByGPU(uint32 nTick) { CDeviceTexture* pDeviceTexture = m_pDevTexture; if (pDeviceTexture) { CHK_MAINORRENDTH; D3DBaseTexture* pD3DTex = pDeviceTexture->GetBaseTexture(); } return (nTick - m_nFreeTick) < 4; } STexPoolItem::STexPoolItem (STexPool* pOwner, CDeviceTexture* pDevTexture, size_t devSize) : m_pOwner(pOwner) , m_pTex(NULL) , m_pDevTexture(pDevTexture) , m_nDeviceTexSize(devSize) , m_nFreeTick(0) , m_nActiveLod(0) { assert (pDevTexture); ++pOwner->m_nItems; } STexPoolItem::~STexPoolItem() { assert (m_pDevTexture); if (m_pOwner) { --m_pOwner->m_nItems; if (IsFree()) { --m_pOwner->m_nItemsFree; } } Unlink(); UnlinkFree(); m_pDevTexture->Unbind(); m_pDevTexture->Release(); } CTextureStreamPoolMgr::CTextureStreamPoolMgr() { m_FreeTexPoolItems.m_NextFree = &m_FreeTexPoolItems; m_FreeTexPoolItems.m_PrevFree = &m_FreeTexPoolItems; m_nDeviceMemReserved = 0; m_nDeviceMemInUse = 0; #ifdef TEXSTRM_USE_FREEPOOL m_nFreePoolBegin = 0; m_nFreePoolEnd = 0; #endif m_nTick = 0; #if !defined(_RELEASE) m_bComputeStats = false; #endif } CTextureStreamPoolMgr::~CTextureStreamPoolMgr() { Flush(); } void CTextureStreamPoolMgr::Flush() { AUTO_LOCK(STexPoolItem::s_sSyncLock); FlushFree(); // Free pools first, as that will attempt to remove streamed textures in release, which will end up // in the free list. for (TexturePoolMap::iterator it = m_TexturesPools.begin(), itEnd = m_TexturesPools.end(); it != itEnd; ++it) { SAFE_DELETE(it->second); } stl::free_container(m_TexturesPools); // Now nuke the free items. FlushFree(); #ifdef TEXSTRM_USE_FREEPOOL while (m_nFreePoolBegin != m_nFreePoolEnd) { assert (m_nFreePoolEnd < sizeof(m_FreePool) / sizeof(m_FreePool[0])); void* p = m_FreePool[m_nFreePoolBegin]; m_nFreePoolBegin = (m_nFreePoolBegin + 1) % MaxFreePool; ::operator delete(p); } #endif } STexPool* CTextureStreamPoolMgr::GetPool(int nWidth, int nHeight, int nMips, int nArraySize, ETEX_Format eTF, bool bIsSRGB, ETEX_Type eTT) { D3DFormat d3dFmt = CTexture::DeviceFormatFromTexFormat(eTF); if (bIsSRGB) { d3dFmt = CTexture::ConvertToSRGBFmt(d3dFmt); } TexturePoolKey poolKey((uint16)nWidth, (uint16)nHeight, d3dFmt, (uint8)eTT, (uint8)nMips, (uint16)nArraySize); TexturePoolMap::iterator it = m_TexturesPools.find(poolKey); if (it != m_TexturesPools.end()) { return it->second; } return NULL; } STexPoolItem* CTextureStreamPoolMgr::GetPoolItem(int nWidth, int nHeight, int nMips, int nArraySize, ETEX_Format eTF, bool bIsSRGB, ETEX_Type eTT, bool bShouldBeCreated, const char* sName, STextureInfo* pTI, bool bCanCreate, bool bWaitForIdle) { D3DFormat d3dFmt = CTexture::DeviceFormatFromTexFormat(eTF); if (bIsSRGB) { d3dFmt = CTexture::ConvertToSRGBFmt(d3dFmt); } STexPool* pPool = CreatePool(nWidth, nHeight, nMips, nArraySize, d3dFmt, eTT); if (!pPool) { return NULL; } AUTO_LOCK(STexPoolItem::s_sSyncLock); STexPoolItemHdr* pITH = &m_FreeTexPoolItems; STexPoolItem* pIT = static_cast<STexPoolItem*>(pITH); bool bFoundACoolingMatch = false; #ifndef TSP_GC_ALL_ITEMS if (!pTI) { pITH = m_FreeTexPoolItems.m_PrevFree; pIT = static_cast<STexPoolItem*>(pITH); // Try to find empty item in the pool while (pITH != &m_FreeTexPoolItems) { if (pIT->m_pOwner == pPool) { if (!bWaitForIdle || !pIT->IsStillUsedByGPU(m_nTick)) { break; } bFoundACoolingMatch = true; } pITH = pITH->m_PrevFree; pIT = static_cast<STexPoolItem*>(pITH); } } #endif if (pITH != &m_FreeTexPoolItems) { pIT->UnlinkFree(); #if defined(DO_RENDERLOG) if (CRenderer::CV_r_logTexStreaming == 2) { gRenDev->LogStrv(SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID], "Remove from FreePool '%s', [%d x %d], Size: %d\n", sName, pIT->m_pOwner->m_Width, pIT->m_pOwner->m_Height, pIT->m_pOwner->m_Size); } #endif #if !defined(_RELEASE) ++m_frameStats.nSoftCreates; #endif --pIT->m_pOwner->m_nItemsFree; } else { if (!bCanCreate) { return NULL; } #if defined(TEXSTRM_TEXTURECENTRIC_MEMORY) if (bFoundACoolingMatch) { // Really, really, don't want to create a texture if one will be available soon if (!bShouldBeCreated) { return NULL; } } #endif // Create API texture for the item in DEFAULT pool const char* poolTextureName = "StreamingTexturePool"; HRESULT h = S_OK; CDeviceTexture* pDevTex = NULL; if (eTT != eTT_Cube) { h = gcpRendD3D->m_DevMan.Create2DTexture(poolTextureName, nWidth, nHeight, nMips, nArraySize, STREAMED_TEXTURE_USAGE, Clr_Transparent, d3dFmt, D3DPOOL_DEFAULT, &pDevTex, pTI, bShouldBeCreated); } else { h = gcpRendD3D->m_DevMan.CreateCubeTexture(poolTextureName, nWidth, nMips, 1, STREAMED_TEXTURE_USAGE, Clr_Transparent, d3dFmt, D3DPOOL_DEFAULT, &pDevTex, pTI, bShouldBeCreated); } if (FAILED(h) || !pDevTex) { return NULL; } #ifdef TEXSTRM_USE_FREEPOOL if (m_nFreePoolBegin != m_nFreePoolEnd) { assert (m_nFreePoolBegin < (sizeof(m_FreePool) / sizeof(m_FreePool[0]))); void* p = m_FreePool[m_nFreePoolBegin]; m_nFreePoolBegin = (m_nFreePoolBegin + 1) % MaxFreePool; pIT = new (p) STexPoolItem(pPool, pDevTex, pPool->m_Size); } else #endif { pIT = new STexPoolItem(pPool, pDevTex, pPool->m_Size); } pIT->Link(&pPool->m_ItemsList); CryInterlockedAddSize(&m_nDeviceMemReserved, (ptrdiff_t)pPool->m_Size); #if !defined(_RELEASE) ++m_frameStats.nHardCreates; #endif } CryInterlockedAddSize(&m_nDeviceMemInUse, (ptrdiff_t)pIT->m_nDeviceTexSize); return pIT; } void CTextureStreamPoolMgr::ReleaseItem(STexPoolItem* pItem) { assert(!pItem->m_NextFree); #ifdef DO_RENDERLOG if (CRenderer::CV_r_logTexStreaming == 2) { const char* name = pItem->m_pTex ? pItem->m_pTex->GetSourceName() : ""; gRenDev->LogStrv(SRendItem::m_RecurseLevel[gRenDev->m_RP.m_nProcessThreadID], "Add to FreePool '%s', [%d x %d], Size: %d\n", name, pItem->m_pOwner->m_Width, pItem->m_pOwner->m_Height, pItem->m_pOwner->m_Size); } #endif CryInterlockedAddSize(&m_nDeviceMemInUse, -(ptrdiff_t)pItem->m_nDeviceTexSize); pItem->m_pTex = NULL; pItem->m_nFreeTick = m_nTick; pItem->LinkFree(&m_FreeTexPoolItems); ++pItem->m_pOwner->m_nItemsFree; #if !defined(_RELEASE) ++m_frameStats.nSoftFrees; #endif } STexPool* CTextureStreamPoolMgr::CreatePool(int nWidth, int nHeight, int nMips, int nArraySize, D3DFormat eTF, ETEX_Type eTT) { STexPool* pPool = NULL; TexturePoolKey poolKey((uint16)nWidth, (uint16)nHeight, eTF, (uint8)eTT, (uint8)nMips, (uint16)nArraySize); TexturePoolMap::iterator it = m_TexturesPools.find(poolKey); if (it == m_TexturesPools.end()) { // Create new pool pPool = new STexPool; pPool->m_eTT = eTT; pPool->m_eFormat = eTF; pPool->m_Width = nWidth; pPool->m_Height = nHeight; pPool->m_nArraySize = nArraySize; pPool->m_nMips = nMips; pPool->m_Size = CDeviceTexture::TextureDataSize(nWidth, nHeight, 1, nMips, nArraySize * (eTT == eTT_Cube ? 6 : 1), CTexture::TexFormatFromDeviceFormat(eTF)); pPool->m_nItems = 0; pPool->m_nItemsFree = 0; pPool->m_ItemsList.m_Next = &pPool->m_ItemsList; pPool->m_ItemsList.m_Prev = &pPool->m_ItemsList; it = m_TexturesPools.insert(std::make_pair(poolKey, pPool)).first; } return it->second; } void CTextureStreamPoolMgr::FlushFree() { STexPoolItemHdr* pITH = m_FreeTexPoolItems.m_PrevFree; while (pITH != &m_FreeTexPoolItems) { STexPoolItemHdr* pITHNext = pITH->m_PrevFree; STexPoolItem* pIT = static_cast<STexPoolItem*>(pITH); assert (!pIT->m_pTex); CryInterlockedAddSize(&m_nDeviceMemReserved, -(ptrdiff_t)pIT->m_nDeviceTexSize); #ifdef TEXSTRM_USE_FREEPOOL unsigned int nFreePoolSize = (m_nFreePoolEnd - m_nFreePoolBegin) % MaxFreePool; if (nFreePoolSize < MaxFreePool - 1) // -1 to avoid needing a separate count { pIT->~STexPoolItem(); m_FreePool[m_nFreePoolEnd] = pIT; m_nFreePoolEnd = (m_nFreePoolEnd + 1) % MaxFreePool; } else #endif { delete pIT; } pITH = pITHNext; } } void CTextureStreamPoolMgr::GarbageCollect(size_t* nCurTexPoolSize, size_t nLowerPoolLimit, int nMaxItemsToFree) { size_t nSize = nCurTexPoolSize ? *nCurTexPoolSize : 1024 * 1024 * 1024; ptrdiff_t nFreed = 0; AUTO_LOCK(STexPoolItem::s_sSyncLock); STexPoolItemHdr* pITH = m_FreeTexPoolItems.m_PrevFree; while (pITH != &m_FreeTexPoolItems) { STexPoolItemHdr* pITHNext = pITH->m_PrevFree; STexPoolItem* pIT = static_cast<STexPoolItem*>(pITH); if (!pIT->m_pTex) { pITH = pITHNext; continue; } #if defined(TEXSTRM_TEXTURECENTRIC_MEMORY) if (pIT->m_pOwner->m_nItemsFree > 20 || pIT->m_pOwner->m_Width > 64 || pIT->m_pOwner->m_Height > 64) #endif { if (!pIT->IsStillUsedByGPU(m_nTick)) { nSize -= pIT->m_nDeviceTexSize; nFreed += (ptrdiff_t)pIT->m_nDeviceTexSize; #ifdef TEXSTRM_USE_FREEPOOL unsigned int nFreePoolSize = (m_nFreePoolEnd - m_nFreePoolBegin) % MaxFreePool; if (nFreePoolSize < MaxFreePool - 1) // -1 to avoid needing a separate count { pIT->~STexPoolItem(); m_FreePool[m_nFreePoolEnd] = pIT; m_nFreePoolEnd = (m_nFreePoolEnd + 1) % MaxFreePool; } else #endif { delete pIT; } --nMaxItemsToFree; #if !defined(_RELEASE) ++m_frameStats.nHardFrees; #endif } } pITH = pITHNext; // we release all textures immediately on consoles #ifndef TSP_GC_ALL_ITEMS if (nMaxItemsToFree <= 0 || nSize < nLowerPoolLimit) { break; } #endif } CryInterlockedAddSize(&m_nDeviceMemReserved, -nFreed); CTexture::StreamValidateTexSize(); if (nCurTexPoolSize) { *nCurTexPoolSize = nSize; } #if !defined(_RELEASE) if (m_bComputeStats) { CryAutoLock<CryCriticalSection> lock(m_statsLock); m_poolStats.clear(); for (TexturePoolMap::iterator it = m_TexturesPools.begin(), itEnd = m_TexturesPools.end(); it != itEnd; ++it) { STexPool* pPool = it->second; SPoolStats ps; ps.nWidth = pPool->m_Width; ps.nHeight = pPool->m_Height; ps.nMips = pPool->m_nMips; ps.nFormat = pPool->m_eFormat; ps.eTT = pPool->m_eTT; ps.nInUse = pPool->m_nItems - pPool->m_nItemsFree; ps.nFree = pPool->m_nItemsFree; ps.nHardCreatesPerFrame = 0; ps.nSoftCreatesPerFrame = 0; m_poolStats.push_back(ps); } } #endif ++m_nTick; } void CTextureStreamPoolMgr::GetMemoryUsage(ICrySizer* pSizer) { SIZER_COMPONENT_NAME(pSizer, "Texture Pools"); pSizer->AddObject(m_TexturesPools); }