You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/RenderDll/Common/Textures/TextureStreaming.cpp

2082 lines
63 KiB
C++

/*
* 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.
// Description : Common Texture Streaming manager implementation.
#include "RenderDll_precompiled.h"
#include "../CommonRender.h"
#include <CryPath.h>
#include "Image/DDSImage.h"
#include "StringUtils.h" // stristr()
#include "ILocalMemoryUsage.h"
#include "TextureManager.h"
#include "TextureStreamPool.h"
#include "TextureHelpers.h"
#include "PlanningTextureStreamer.h"
// checks for MT-safety of called functions
#define CHK_RENDTH assert(gRenDev->m_pRT->IsRenderThread())
#define CHK_MAINTH assert(gRenDev->m_pRT->IsMainThread())
#define CHK_MAINORRENDTH assert(gRenDev->m_pRT->IsMainThread() || gRenDev->m_pRT->IsRenderThread())
bool CTexture::s_bStreamingFromHDD = true;
CTextureArrayAlloc<STexStreamInState, CTexture::MaxStreamTasks> CTexture::s_StreamInTasks;
CTextureArrayAlloc<STexStreamPrepState*, CTexture::MaxStreamPrepTasks> CTexture::s_StreamPrepTasks;
#if defined(AZ_RESTRICTED_PLATFORM)
#undef AZ_RESTRICTED_SECTION
#define TEXTURESTREAMING_CPP_SECTION_1 1
#define TEXTURESTREAMING_CPP_SECTION_2 2
#define TEXTURESTREAMING_CPP_SECTION_3 3
#define TEXTURESTREAMING_CPP_SECTION_4 4
#define TEXTURESTREAMING_CPP_SECTION_5 5
#define TEXTURESTREAMING_CPP_SECTION_6 6
#define TEXTURESTREAMING_CPP_SECTION_7 7
#endif
#ifdef TEXSTRM_ASYNC_TEXCOPY
CTextureArrayAlloc<STexStreamOutState, CTexture::MaxStreamTasks> CTexture::s_StreamOutTasks;
#endif
volatile TIntAtomic CTexture::s_nBytesSubmittedToStreaming = {0};
volatile TIntAtomic CTexture::s_nMipsSubmittedToStreaming = {0};
int CTexture::s_nBytesRequiredNotSubmitted = 0;
#if !defined (_RELEASE)
int CTexture::s_TextureUpdates = 0;
float CTexture::s_TextureUpdatesTime = 0.0f;
int CTexture::s_TexturesUpdatedRendered = 0;
float CTexture::s_TextureUpdatedRenderedTime = 0.0f;
int CTexture::s_StreamingRequestsCount = 0;
float CTexture::s_StreamingRequestsTime = 0.0f;
#endif
#ifdef ENABLE_TEXTURE_STREAM_LISTENER
ITextureStreamListener* CTexture::s_pStreamListener;
#endif
bool CTexture::s_bStreamDontKeepSystem = false;
int CTexture::s_nTexturesDataBytesLoaded = 0;
volatile int CTexture::s_nTexturesDataBytesUploaded = 0;
int CTexture::s_nStatsAllocFails;
bool CTexture::s_bOutOfMemoryTotally;
volatile size_t CTexture::s_nStatsStreamPoolInUseMem;
volatile size_t CTexture::s_nStatsStreamPoolBoundMem;
volatile size_t CTexture::s_nStatsStreamPoolBoundPersMem;
AZStd::atomic_uint CTexture::s_nStatsCurManagedNonStreamedTexMem = {0};
AZStd::atomic_uint CTexture::s_nStatsCurDynamicTexMem = {0};
volatile size_t CTexture::s_nStatsStreamPoolWanted = {0};
bool CTexture::s_bStatsComputeStreamPoolWanted = false;
std::vector<CTexture::WantedStat>* CTexture::s_pStatsTexWantedLists = NULL;
ITextureStreamer* CTexture::s_pTextureStreamer;
CryCriticalSection CTexture::s_streamFormatLock;
SStreamFormatCode CTexture::s_formatCodes[256];
uint32 CTexture::s_nFormatCodes = 1;
StaticInstance<CTexture::TStreamFormatCodeKeyMap> CTexture::s_formatCodeMap;
const int CTexture::LOW_SPEC_PC = 5;
const int CTexture::MEDIUM_SPEC_PC = 6;
const int CTexture::HIGH_SPEC_PC = 7;
const int CTexture::VERYHIGH_SPEC_PC = 8;
#ifdef TEXSTRM_ASYNC_TEXCOPY
void STexStreamInState::CopyMips()
{
FUNCTION_PROFILER_RENDERER;
CTexture* tp = m_pTexture;
if (!m_bAborted)
{
if (tp->m_pFileTexMips->m_pPoolItem)
{
const int nNewMipOffset = tp->m_nMinMipVidUploaded - m_nHigherUploadedMip;
const int nNumMips = tp->GetNumMipsNonVirtual() - tp->m_nMinMipVidUploaded;
if (0)
{
}
#if TEXTURESTREAMING_CPP_TRAIT_COPYMIPS_MOVEENGINE && !defined(NULL_RENDERER)
else if (!gRenDev->m_pRT->IsRenderThread())
{
m_copyMipsFence = CTexture::StreamCopyMipsTexToTex_MoveEngine(tp->m_pFileTexMips->m_pPoolItem, 0, m_pNewPoolItem, 0 + nNewMipOffset, nNumMips);
}
#endif
else
{
CTexture::StreamCopyMipsTexToTex(tp->m_pFileTexMips->m_pPoolItem, 0, m_pNewPoolItem, 0 + nNewMipOffset, nNumMips);
}
m_bValidLowMips = true;
}
}
else
{
m_bValidLowMips = true;
}
}
void STexStreamOutState::Reset()
{
// if we have streaming error, release new pool item
if (m_pNewPoolItem)
{
CTexture::s_pPoolMgr->ReleaseItem(m_pNewPoolItem);
}
m_pNewPoolItem = NULL;
m_pTexture = 0;
m_bDone = false;
m_bAborted = false;
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_1
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
}
bool STexStreamOutState::TryCommit()
{
if (m_bDone)
{
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_2
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
if (!m_bAborted)
{
if (m_nStartMip < MAX_MIP_LEVELS)
{
m_pTexture->StreamAssignPoolItem(m_pNewPoolItem, m_nStartMip);
m_pNewPoolItem = NULL;
m_pTexture->SetWasUnload(false);
}
else
{
// Stream unload
m_pTexture->ReleaseDeviceTexture(true, true);
m_pTexture->SetWasUnload(true);
}
}
m_pTexture->SetStreamingInProgress(CTexture::InvalidStreamSlot);
m_pTexture->Release();
m_pTexture = NULL;
return true;
}
return false;
}
#endif
void CTexture::StreamReleaseMipsData(int nStartMip, int nEndMip)
{
assert(m_pFileTexMips);
assert(nStartMip <= nEndMip);
nEndMip = min(nEndMip, m_nMips - 1);
nStartMip = min(nStartMip, nEndMip);
const int nSides = StreamGetNumSlices();
for (int i = 0; i < nSides; i++)
{
for (int j = nStartMip; j <= nEndMip; j++)
{
m_pFileTexMips->m_pMipHeader[j].m_Mips[i].Free();
}
}
}
STexStreamInState::STexStreamInState()
{
m_pNewPoolItem = NULL;
#if defined(TEXSTRM_DEFERRED_UPLOAD)
m_pCmdList = NULL;
#endif
Reset();
}
void STexStreamInState::Reset()
{
if (m_pNewPoolItem)
{
CTexture::s_pPoolMgr->ReleaseItem(m_pNewPoolItem);
m_pNewPoolItem = NULL;
}
#if defined(TEXSTRM_DEFERRED_UPLOAD)
SAFE_RELEASE(m_pCmdList);
#endif
for (size_t i = 0, c = m_nLowerUploadedMip - m_nHigherUploadedMip + 1; i != c; ++i)
{
m_pStreams[i] = NULL;
}
memset(&m_pTexture, 0, (char*)(this + 1) - (char*)&m_pTexture);
}
// streaming thread
void STexStreamInState::StreamAsyncOnComplete(IReadStream* pStream, unsigned nError)
{
PROFILE_FRAME(Texture_StreamAsyncOnComplete);
CTexture* tp = m_pTexture;
int nMip = (int)pStream->GetUserData();
STexStreamInMipState& mipState = m_mips[nMip];
if (!nError && tp->m_pFileTexMips)
{
#if defined(TEXSTRM_ASYNC_UPLOAD)
tp->StreamUploadMip(pStream, nMip, m_nHigherUploadedMip, m_pNewPoolItem, mipState);
mipState.m_bUploaded = true;
#define AZ_RESTRICTED_SECTION_IMPLEMENTED
#elif defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_3
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
if (!mipState.m_bStreamInPlace)
{
tp->StreamExpandMip(pStream->GetBuffer(), nMip, m_nHigherUploadedMip, mipState.m_nSideDelta);
mipState.m_bExpanded = true;
}
else
{
mipState.m_bUploaded = true;
}
#endif
// Update the cached media type to optimise future requests
EStreamSourceMediaType eMT = pStream->GetMediaType();
int nAbsMip = m_nHigherUploadedMip + nMip;
tp->m_pFileTexMips->m_pMipHeader[nAbsMip].m_eMediaType = eMT;
}
else
{
m_bAborted = true;
}
pStream->FreeTemporaryMemory(); // We don't need internal stream loaded buffer anymore.
int nChunkSize = tp->m_pFileTexMips->m_pMipHeader[nMip + m_nHigherUploadedMip].m_SideSize * tp->GetNumSides();
CryInterlockedAdd(CTexture::s_nBytesSubmittedToStreaming.Addr(), -nChunkSize);
CryInterlockedDecrement(CTexture::s_nMipsSubmittedToStreaming.Addr());
assert(CTexture::s_nBytesSubmittedToStreaming >= 0);
const int nRef = CryInterlockedDecrement(&m_nAsyncRefCount);
// Check to see if this is the last mip (and thus owns the job)
if (nRef == 0)
{
if (!m_bAborted)
{
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_4
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
#if defined(TEXSTRM_DEFERRED_UPLOAD)
if (tp->m_pFileTexMips->m_pPoolItem) ///< Don't upload if the source is nullptr it'll just cause an exception
{
ID3D11CommandList* pCmdList = tp->StreamCreateDeferred(m_nHigherUploadedMip, m_nLowerUploadedMip, m_pNewPoolItem, tp->m_pFileTexMips->m_pPoolItem);
if (pCmdList)
{
m_pCmdList = pCmdList;
m_bValidLowMips = true;
for (int i = 0, c = m_nLowerUploadedMip - m_nHigherUploadedMip + 1; i != c; ++i)
{
m_mips[i].m_bExpanded = false;
}
if (CTexture::s_bStreamDontKeepSystem)
{
tp->StreamReleaseMipsData(m_nHigherUploadedMip, m_nLowerUploadedMip);
}
}
}
#endif
#if defined(TEXSTRM_ASYNC_TEXCOPY)
if (!m_bValidLowMips && tp->CanAsyncCopy())
{
CopyMips();
}
#endif
}
#ifndef _RELEASE
// collect statistics
if (pStream->GetParams().nSize > 1024)
{
CTexture::s_nStreamingThroughput += pStream->GetParams().nSize;
}
const CTimeValue currentTime = iTimer->GetAsyncTime();
if (currentTime - m_fStartTime > .01f) // avoid measurement errors for small textures
{
CTexture::s_nStreamingTotalTime += currentTime.GetSeconds() - m_fStartTime;
}
#endif
m_bAllStreamsComplete = true;
}
}
bool STexStreamInState::TryCommit()
{
PROFILE_FRAME(Texture_StreamOnComplete_Render);
CHK_RENDTH;
CTexture* tp = m_pTexture;
if (!m_bAborted)
{
STexPoolItem*& pNewPoolItem = m_pNewPoolItem;
#if !defined(TEXSTRM_ASYNC_UPLOAD) && TEXTURESTREAMING_CPP_TRAIT_TRYCOMMIT_COPYMIPS
for (size_t i = 0, c = m_nLowerUploadedMip - m_nHigherUploadedMip + 1; i != c; ++i)
{
STexStreamInMipState& mipState = m_mips[i];
if (mipState.m_bExpanded)
{
mipState.m_bUploaded = true;
mipState.m_bExpanded = false;
tp->StreamCopyMipsTexToMem(m_nHigherUploadedMip + i, m_nHigherUploadedMip + i, true, pNewPoolItem);
if (CTexture::s_bStreamDontKeepSystem)
{
tp->StreamReleaseMipsData(m_nHigherUploadedMip + i, m_nHigherUploadedMip + i);
}
}
}
#endif
#ifdef TEXSTRM_DEFERRED_UPLOAD
if (m_pCmdList)
{
tp->StreamApplyDeferred(m_pCmdList);
m_pCmdList->Release();
m_pCmdList = NULL;
}
#endif
#if defined(TEXSTRM_COMMIT_COOLDOWN)
if ((m_nStallFrames++) < 4)
{
return false;
}
#endif
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_5
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
if (!m_bValidLowMips)
{
STexPoolItem* pCurItem = tp->m_pFileTexMips->m_pPoolItem;
if (pCurItem)
{
// it is a sync operation anyway, so we do it in the render thread
// restore already loaded mips
const int nNumMips = pCurItem->m_pOwner->m_nMips;
const int nNewMipOffset = pNewPoolItem->m_pOwner->m_nMips - nNumMips;
CTexture::StreamCopyMipsTexToTex(pCurItem, 0, pNewPoolItem, nNewMipOffset, nNumMips);
}
else
{
m_pTexture->StreamCopyMipsTexToMem(m_pTexture->GetNumMipsNonVirtual() - m_pTexture->GetNumPersistentMips(), m_pTexture->GetNumMipsNonVirtual() - 1, true, pNewPoolItem);
if (CTexture::s_bStreamDontKeepSystem)
{
m_pTexture->StreamReleaseMipsData(m_pTexture->GetNumMipsNonVirtual() - m_pTexture->GetNumPersistentMips(), m_pTexture->GetNumMipsNonVirtual() - 1);
}
}
m_bValidLowMips = true;
}
if (pNewPoolItem)
{
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_texturesstreamingmipfading)
{
tp->m_fCurrentMipBias = min(2.f, tp->m_fCurrentMipBias + float(m_nLowerUploadedMip - m_nHigherUploadedMip + 1));
}
// bind new texture
const int nNewNumMips = m_nHigherUploadedMip;
tp->StreamAssignPoolItem(pNewPoolItem, m_nActivateMip);
pNewPoolItem = NULL;
tp->SetWasUnload(false);
}
}
else
{
if (CTexture::s_bStreamDontKeepSystem)
{
tp->StreamReleaseMipsData(m_nHigherUploadedMip, m_nLowerUploadedMip);
}
}
tp->SetStreamingInProgress(CTexture::InvalidStreamSlot);
m_pTexture->Release();
m_pTexture = NULL;
CTexture::StreamValidateTexSize();
return true;
}
bool STexStreamPrepState::Commit()
{
_smart_ptr<CImageFile> pNextImage;
if (!m_bFailed)
{
if (m_pImage)
{
if (m_pTexture->IsStreamed())
{
if (m_pTexture->StreamPrepare(&*m_pImage))
{
m_bNeedsFinalise = true;
}
else
{
m_bCompleted = false;
// StreamPrepare failed, so presumably the image can't be streamed. Since we only have an image assuming it was streamed,
// load it again, with all mips.
// StreamPrepare failure will mark the texture as non-streamable.
pNextImage = CImageFile::mfStream_File(m_pImage->mfGet_filename(), m_pImage->mfGet_Flags() & ~FIM_STREAM_PREPARE, this);
}
}
else
{
m_pTexture->Load(&*m_pImage);
}
}
if (m_bNeedsFinalise)
{
if (m_pTexture->IsStreamed())
{
m_bNeedsFinalise = !m_pTexture->StreamPrepare_Finalise(true);
}
}
}
else
{
m_pTexture->SetNoTexture( CTextureManager::Instance()->GetNoTexture() );
}
m_pImage = pNextImage;
if (!m_pImage && !m_bNeedsFinalise && m_pTexture)
{
m_pTexture->PostCreate();
}
return !m_pImage && !m_bNeedsFinalise;
}
void STexStreamPrepState::OnImageFileStreamComplete(CImageFile* pImFile)
{
if (!pImFile)
{
m_pImage = NULL;
m_bFailed = true;
}
m_bCompleted = true;
}
int CTexture::StreamCalculateMipsSigned(float fMipFactor) const
{
return StreamCalculateMipsSignedFP(fMipFactor) >> 8;
}
int CTexture::GetStreamableMipNumber() const
{
assert(IsStreamed());
return max(0, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
}
bool CTexture::IsStreamedIn(const int nMinPrecacheRoundIds[MAX_STREAM_PREDICTION_ZONES]) const
{
if (IsStreamed())
{
for (int nZoneIdx = 0; nZoneIdx < MAX_STREAM_PREDICTION_ZONES; ++nZoneIdx)
{
if (m_streamRounds[nZoneIdx].nRoundUpdateId < nMinPrecacheRoundIds[nZoneIdx])
{
return false;
}
}
int nMinMip = s_pTextureStreamer->GetMinStreamableMipWithSkip();
return max(GetRequiredMipNonVirtual(), nMinMip) >= CTexture::GetMinLoadedMip();
}
return true;
}
int CTexture::GetStreamableMemoryUsage(int nStartMip) const
{
assert(IsStreamed());
if (m_pFileTexMips == NULL)
{
assert(0);
return 0;
}
return m_pFileTexMips->m_pMipHeader[nStartMip].m_SideSizeWithMips;
}
void CTexture::SetMinLoadedMip(int nMinMip)
{
#ifdef ENABLE_TEXTURE_STREAM_LISTENER
if (m_nMinMipVidUploaded != nMinMip)
{
ITextureStreamListener* pListener = s_pStreamListener;
if (pListener)
{
pListener->OnTextureHasMip(this, min(nMinMip, (int)m_nMips));
}
}
#endif
m_nMinMipVidUploaded = nMinMip;
}
const bool CTexture::IsParticularMipStreamed(float fMipFactor) const
{
if (!IsStreamed())
{
return true;
}
bool bHighPriority = false;
if (m_pFileTexMips)
{
bHighPriority = m_bStreamHighPriority != 0;
}
int nMipClamp = (s_bStreamingFromHDD || bHighPriority) ? 0 : CRenderer::CV_r_TexturesStreamingMipClampDVD;
const int nMip = max(nMipClamp, CTexture::StreamCalculateMipsSigned(fMipFactor));
return m_nMinMipVidUploaded <= nMip;
}
void CTexture::PrecacheAsynchronously(float fMipFactor, int nFlags, int nUpdateId, int nCounter)
{
if (!IsStreamed())
{
return; // already done
}
s_pTextureStreamer->UpdateMip(this, fMipFactor, nFlags, nUpdateId, nCounter);
// for distance streaming it's just the same as update rendering distance
StreamLoadFromCache(nFlags);
}
void CTexture::StreamLoadFromCache([[maybe_unused]] const int Flags)
{
if (IsUnloaded())
{
if (!StreamPrepare(false))
{
// ignore error for optional attached alpha channel
if (!m_bNoTexture && !(m_nFlags & FT_ALPHA) && (m_nFlags & FT_FROMIMAGE))
{
#ifndef NDEBUG
bool bRes =
#endif
Reload();
assert(bRes);
}
}
}
}
bool CTexture::StreamPrepare(bool bFromLoad)
{
CHK_MAINORRENDTH;
if (!CRenderer::CV_r_texturesstreaming || m_nFlags & FT_DONT_STREAM)
{
return false;
}
LOADING_TIME_PROFILE_SECTION(iSystem);
PROFILE_FRAME(Texture_StreamPrepare);
CRY_DEFINE_ASSET_SCOPE("Texture", m_sAssetScopeName);
// release the old texture
if (m_pDevTexture)
{
ReleaseDeviceTexture(false);
}
if (!m_pFileTexMips)
{
if (IResourceCompilerHelper::IsSourceImageFormatSupported(m_SrcName.c_str()) &&
!gEnv->pCryPak->IsFileExist(m_SrcName.c_str()))
{
m_SrcName = PathUtil::ReplaceExtension(m_SrcName, "dds");
if (!gEnv->pCryPak->IsFileExist(m_SrcName.c_str()))
{
return false;
}
}
#if !defined(_RELEASE)
if ((m_nFlags & FT_TEX_NORMAL_MAP) && !TextureHelpers::VerifyTexSuffix(EFTT_NORMALS, m_SrcName))
{
FileWarning(m_SrcName.c_str(), "Normal map should have '%s' suffix in filename", TextureHelpers::LookupTexSuffix(EFTT_NORMALS));
}
#endif
if (m_bPostponed)
{
if (CTexture::s_pTextureStreamer->BeginPrepare(this, m_SrcName.c_str(), ((m_nFlags & FT_ALPHA) ? FIM_ALPHA : 0) | FIM_STREAM_PREPARE))
{
return true;
}
}
uint32 nImageFlags = FIM_STREAM_PREPARE | ((m_nFlags & FT_ALPHA) ? FIM_ALPHA : 0);
_smart_ptr<CImageFile> pIM(CImageFile::mfLoad_file(m_SrcName, nImageFlags));
m_bisTextureMissing = !pIM || pIM->mfGet_IsImageMissing();
if (!pIM || m_bisTextureMissing || !StreamPrepare(&*pIM))
{
return false;
}
}
return StreamPrepare_Finalise(bFromLoad);
}
bool CTexture::StreamPrepareComposition()
{
CHK_MAINORRENDTH;
if (!CRenderer::CV_r_texturesstreaming || m_nFlags & FT_DONT_STREAM)
{
return false;
}
LOADING_TIME_PROFILE_SECTION(iSystem);
PROFILE_FRAME(Texture_StreamPrepare);
CRY_DEFINE_ASSET_SCOPE("Texture", m_sAssetScopeName);
int nWidth = m_nWidth;
int nHeight = m_nHeight;
int nMips = m_nMips;
ETEX_Format eTF = m_eTFDst;
const STexComposition* pCompositions = &m_composition[0];
size_t nCompositions = m_composition.size();
// release the old texture
if (m_pDevTexture)
{
ReleaseDeviceTexture(false);
}
int nSides = 1;
for (size_t i = 0; i < nCompositions; ++i)
{
nSides = max(nSides, pCompositions[i].nDstSlice + 1);
}
ETEX_Type eTT = nSides > 1 ? eTT_2DArray : eTT_2D;
ETEX_Format eTFSrc = eTF;
ETEX_Format eTFDst = eTF;
float fAvgBrightness = 1.0f;
ETEX_TileMode eTileMode = eTM_None;
int nMipsPersistent = DDSSplitted::GetNumLastMips(nWidth, nHeight, nMips, nSides, eTFSrc, 0);
m_nFlags &= ~(FT_SPLITTED | FT_TEX_WAS_NOT_PRE_TILED | FT_HAS_ATTACHED_ALPHA | FT_DONT_STREAM | FT_FROMIMAGE);
const SPixFormat* pPF = NULL;
#ifndef NULL_RENDERER
ClosestFormatSupported(eTFDst, pPF);
#endif
if (!pPF)
{
__debugbreak();
return false;
}
// Can't fail from this point on - commit everything.
STexCacheFileHeader& fh = m_CacheFileHeader;
// copy image properties
m_nWidth = nWidth;
m_nHeight = nHeight;
m_nDepth = 1;
m_nArraySize = nSides;
m_nMips = nMips;
m_eTT = eTT;
fh.m_nSides = nSides;
m_eTFSrc = eTFSrc;
m_eTFDst = eTFDst;
m_bIsSRGB = pPF->bCanReadSRGB; // OF FIXME: Probably ought to be supplied, and checked against the PF, rather than taken from the PF
assert (!m_pFileTexMips);
m_pFileTexMips = StreamState_AllocateInfo(m_nMips);
m_nStreamingPriority = 0;
SetMinLoadedMip(MAX_MIP_LEVELS);
m_nMinMipVidActive = MAX_MIP_LEVELS;
m_fCurrentMipBias = 0.0f;
m_fAvgBrightness = fAvgBrightness;
m_eSrcTileMode = eTileMode;
m_bStreamed = true;
// allocate and fill up streaming auxiliary structures
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_Mips = new SMipData[m_CacheFileHeader.m_nSides];
}
for (int iSide = 0; iSide < fh.m_nSides; ++iSide)
{
int nTopMipWidth = m_nWidth;
int nTopMipHeight = m_nHeight;
const Vec2i vMipAlign = CTexture::GetBlockDim(m_eTFDst);
nTopMipWidth = Align(nTopMipWidth, vMipAlign.x);
nTopMipHeight = Align(nTopMipHeight, vMipAlign.y);
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSize = CTexture::TextureDataSize(
max(1, nTopMipWidth >> iMip),
max(1, nTopMipHeight >> iMip),
1, 1, 1, m_eTFDst, eTileMode);
}
}
#if defined(TEXSTRM_STORE_DEVSIZES)
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_DevSideSizeWithMips = CDeviceTexture::TextureDataSize(
max(1, m_nWidth >> iMip),
max(1, m_nHeight >> iMip),
max(1, m_nDepth >> iMip),
m_nMips - iMip,
StreamGetNumSlices(),
m_eTFDst);
}
#endif
for (int iMip = 0; iMip < m_nMips; iMip++)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips = 0;
for (int j = iMip; j < m_nMips; j++)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips += m_pFileTexMips->m_pMipHeader[j].m_SideSize;
}
}
m_pPixelFormat = pPF;
fh.m_nMipsPersistent = nMipsPersistent;
m_pFileTexMips->m_fMinMipFactor = StreamCalculateMipFactor((m_nMips - m_CacheFileHeader.m_nMipsPersistent) << 8);
m_nStreamFormatCode = StreamComputeFormatCode(m_nWidth, m_nHeight, m_nMips, m_eTFDst);
assert (m_eTFDst != eTF_Unknown);
StreamPrepare_Platform();
SetTexStates();
PostCreate();
Relink();
m_bStreamPrepared = true;
#if !defined(NULL_RENDERER)
if (gRenDev->m_pRT->IsRenderThread() && !gRenDev->m_pRT->IsRenderLoadingThread())
{
// create texture
assert(!m_pFileTexMips->m_pPoolItem);
assert(!IsStreaming());
STexPoolItem* pNewPoolItem = StreamGetPoolItem(m_nMips - m_CacheFileHeader.m_nMipsPersistent, m_CacheFileHeader.m_nMipsPersistent, true);
if (pNewPoolItem)
{
int nTexWantedMip = m_nMips - m_CacheFileHeader.m_nMipsPersistent;
// bake persistent mips
for (int i = 0, c = m_composition.size(); i != c; ++i)
{
const STexComposition& tc = m_composition[i];
CTexture* p = (CTexture*)&*tc.pTexture;
CDeviceTexture* pSrcDevTex = p->m_pDevTexture;
uint32 nSrcDevMips = p->GetNumMipsNonVirtual() - p->StreamGetLoadedMip();
CTexture::CopySliceChain(
pNewPoolItem->m_pDevTexture, pNewPoolItem->m_pOwner->m_nMips, tc.nDstSlice, 0,
pSrcDevTex, tc.nSrcSlice, nTexWantedMip - (m_nMips - nSrcDevMips), nSrcDevMips,
m_nMips - nTexWantedMip);
}
// upload mips to texture
StreamAssignPoolItem(pNewPoolItem, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
StreamReleaseMipsData(0, m_nMips - 1);
SetWasUnload(false);
}
}
#endif
return true;
}
bool CTexture::StreamPrepare(CImageFile* pIM)
{
if (!pIM)
{
return false;
}
int nWidth = pIM->mfGet_width();
int nHeight = pIM->mfGet_height();
int nDepth = pIM->mfGet_depth();
int nMips = pIM->mfGet_numMips();
ETEX_Type eTT = pIM->mfGet_NumSides() == 1 ? eTT_2D : eTT_Cube;
int nSides = eTT != eTT_Cube ? 1 : 6;
ETEX_Format eTFSrc = pIM->mfGetFormat();
ETEX_Format eTFDst = FormatFixup(eTFSrc);
const ColorF& cMinColor = pIM->mfGet_minColor();
const ColorF& cMaxColor = pIM->mfGet_maxColor();
ETEX_TileMode eTileMode = pIM->mfGetTileMode();
#ifndef _RELEASE
if (eTileMode != eTM_None)
{
if (eTFSrc != eTFDst)
{
__debugbreak();
}
}
#endif
int nMipsPersistent = max(pIM->mfGet_numPersistentMips(), DDSSplitted::GetNumLastMips(nWidth, nHeight, nMips, nSides, eTFSrc, (m_nFlags & FT_ALPHA) ? FIM_ALPHA : 0));
bool bStreamable = true;
// Can't stream volume textures and textures without mips
if (eTFDst == eTF_Unknown || nDepth > 1 || nMips < 2)
{
bStreamable = false;
}
if ((nWidth <= DDSSplitted::etexLowerMipMaxSize || nHeight <= DDSSplitted::etexLowerMipMaxSize) || nMips <= nMipsPersistent || nMipsPersistent == 0)
{
bStreamable = false;
}
const SPixFormat* pPF = NULL;
#ifndef NULL_RENDERER
ClosestFormatSupported(eTFDst, pPF);
#endif
if (!pPF && !DDSFormats::IsNormalMap(eTFDst)) // special case for 3DC and CTX1
{
assert(0);
gEnv->pLog->LogError("Failed to load texture '%s': format '%s' is not supported", m_SrcName.c_str(), NameForTextureFormat(m_eTFDst));
bStreamable = false;
}
if (!bStreamable)
{
if (m_pFileTexMips)
{
Unlink();
StreamState_ReleaseInfo(this, m_pFileTexMips);
m_pFileTexMips = NULL;
}
m_nFlags |= FT_DONT_STREAM;
m_bStreamed = false;
m_bStreamPrepared = false;
SetWasUnload(false);
SetStreamingInProgress(InvalidStreamSlot);
m_bStreamRequested = false;
m_bNoTexture = false;
return false;
}
// Can't fail from this point on - commit everything.
m_nFlags &= ~(FT_SPLITTED | FT_TEX_WAS_NOT_PRE_TILED | FT_HAS_ATTACHED_ALPHA | FT_DONT_STREAM | FT_FROMIMAGE);
STexCacheFileHeader& fh = m_CacheFileHeader;
if (pIM->mfGet_Flags() & FIM_SPLITTED)
{
m_nFlags |= FT_SPLITTED;
}
if (pIM->mfGet_Flags() & FIM_X360_NOT_PRETILED)
{
m_nFlags |= FT_TEX_WAS_NOT_PRE_TILED;
}
if (pIM->mfGet_Flags() & FIM_HAS_ATTACHED_ALPHA)
{
m_nFlags |= FT_HAS_ATTACHED_ALPHA; // if the image has alpha attached we store this in the CTexture
}
// copy image properties
m_nWidth = nWidth;
m_nHeight = nHeight;
m_nDepth = nDepth;
m_nMips = nMips;
m_eTT = eTT;
fh.m_nSides = nSides;
m_eTFSrc = eTFSrc;
m_eTFDst = eTFDst;
m_nFlags |= FT_FROMIMAGE;
m_bUseDecalBorderCol = (pIM->mfGet_Flags() & FIM_DECAL) != 0;
m_bIsSRGB = (pIM->mfGet_Flags() & FIM_SRGB_READ) != 0;
m_SrcName = pIM->mfGet_filename();
assert (!m_pFileTexMips);
m_pFileTexMips = StreamState_AllocateInfo(m_nMips);
m_nStreamingPriority = 0;
SetMinLoadedMip(MAX_MIP_LEVELS);
m_nMinMipVidActive = MAX_MIP_LEVELS;
m_fCurrentMipBias = 0.0f;
m_cMinColor = cMinColor;
m_cMaxColor = cMaxColor;
m_cClearColor = ColorF(0.0f, 0.0f, 0.0f, 1.0f);
m_eSrcTileMode = eTileMode;
m_bStreamed = true;
// base range after normalization, fe. [0,1] for 8bit images, or [0,2^15] for RGBE/HDR data
if ((m_eTFSrc == eTF_R9G9B9E5) || (m_eTFSrc == eTF_BC6UH) || (m_eTFSrc == eTF_BC6SH))
{
m_cMinColor /= m_cMaxColor.a;
m_cMaxColor /= m_cMaxColor.a;
}
// allocate and fill up streaming auxiliary structures
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_Mips = new SMipData[m_CacheFileHeader.m_nSides];
}
m_pFileTexMips->m_desc = pIM->mfGet_DDSDesc();
for (int iSide = 0; iSide < fh.m_nSides; ++iSide)
{
int nTopMipWidth = m_nWidth;
int nTopMipHeight = m_nHeight;
const Vec2i vMipAlign = CTexture::GetBlockDim(m_eTFDst);
nTopMipWidth = Align(nTopMipWidth, vMipAlign.x);
nTopMipHeight = Align(nTopMipHeight, vMipAlign.y);
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSize = CTexture::TextureDataSize(
max(1, nTopMipWidth >> iMip),
max(1, nTopMipHeight >> iMip),
1, 1, 1, m_eTFDst, eTileMode);
}
}
#if defined(TEXSTRM_STORE_DEVSIZES)
for (int iMip = 0; iMip < m_nMips; ++iMip)
{
m_pFileTexMips->m_pMipHeader[iMip].m_DevSideSizeWithMips = CDeviceTexture::TextureDataSize(
max(1, m_nWidth >> iMip),
max(1, m_nHeight >> iMip),
max(1, m_nDepth >> iMip),
m_nMips - iMip,
StreamGetNumSlices(),
m_eTFDst);
}
#endif
m_pFileTexMips->m_nSrcStart = pIM->mfGet_StartSeek();
for (int iMip = 0; iMip < m_nMips; iMip++)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips = 0;
for (int j = iMip; j < m_nMips; j++)
{
m_pFileTexMips->m_pMipHeader[iMip].m_SideSizeWithMips += m_pFileTexMips->m_pMipHeader[j].m_SideSize;
}
}
// set up pixel format and check if it's supported for
m_pPixelFormat = pPF;
if (m_pPixelFormat)
{
m_bIsSRGB &= m_pPixelFormat->bCanReadSRGB;
}
else
{
m_bIsSRGB = false;
}
fh.m_nMipsPersistent = nMipsPersistent;
assert (m_eTFDst != eTF_Unknown);
StreamPrepare_Platform();
SetTexStates();
PostCreate();
// Always load lowest nMipsPersistent mips synchronously
byte* buf = NULL;
if (m_nMips > 1)
{
int nSyncStartMip = -1;
int nSyncEndMip = -1;
int nStartLowestM = max(0, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
if (nStartLowestM < m_nMinMipVidUploaded)
{
nSyncStartMip = nStartLowestM;
nSyncEndMip = m_nMips - 1;
}
int nOffs = 0;
assert(nSyncStartMip <= nSyncEndMip);
const Vec2i vMipAlign = CTexture::GetBlockDim(m_eTFDst);
for (int iSide = 0; iSide < m_CacheFileHeader.m_nSides; iSide++)
{
nOffs = 0;
for (int iMip = nSyncStartMip, nMipW = max(1, m_nWidth >> iMip), nMipH = max(1, m_nHeight >> iMip);
iMip <= nSyncEndMip;
++iMip, nMipW = max(1, nMipW >> 1), nMipH = max(1, nMipH >> 1))
{
STexMipHeader& mh = m_pFileTexMips->m_pMipHeader[iMip];
SMipData* mp = &mh.m_Mips[iSide];
if (!mp->DataArray)
{
mp->Init(mh.m_SideSize, Align(nMipW, vMipAlign.x), Align(nMipH, vMipAlign.y));
}
if (eTileMode != eTM_None)
{
mp->m_bNative = 1;
}
int nSrcSideSize = CTexture::TextureDataSize(
max(1, m_nWidth >> iMip),
max(1, m_nHeight >> iMip),
max(1, m_nDepth >> iMip),
1, 1, m_eTFSrc, eTileMode);
CTexture::s_nTexturesDataBytesLoaded += nSrcSideSize;
if (iSide == 0 || (m_nFlags & FT_REPLICATE_TO_ALL_SIDES) == 0)
{
assert(pIM->mfIs_image(iSide));
buf = pIM->mfGet_image(iSide);
cryMemcpy(&mp->DataArray[0], &buf[nOffs], nSrcSideSize);
nOffs += nSrcSideSize;
}
else if (iSide > 0)
{
if (m_nFlags & FT_REPLICATE_TO_ALL_SIDES)
{
memcpy(&mp->DataArray[0], &mh.m_Mips[0].DataArray[0], nSrcSideSize);
}
else
{
assert(0);
}
}
else
{
assert(0);
}
}
}
}
// store file position on DVD
{
if (m_nFlags & FT_SPLITTED)
{
for (int i = 0; i < m_nMips - m_CacheFileHeader.m_nMipsPersistent; ++i)
{
const int chunkNumber = i >= (m_nMips - m_CacheFileHeader.m_nMipsPersistent) ? 0 : m_nMips - i - m_CacheFileHeader.m_nMipsPersistent;
DDSSplitted::TPath sLastChunkName;
DDSSplitted::MakeName(sLastChunkName, m_SrcName.c_str(), chunkNumber, FIM_SPLITTED | ((GetFlags() & FT_ALPHA) ? FIM_ALPHA : 0));
m_pFileTexMips->m_pMipHeader[i].m_eMediaType = gEnv->pCryPak->GetFileMediaType(sLastChunkName.c_str());
}
}
}
m_pFileTexMips->m_fMinMipFactor = StreamCalculateMipFactor((m_nMips - m_CacheFileHeader.m_nMipsPersistent) << 8);
m_nStreamFormatCode = StreamComputeFormatCode(m_nWidth, m_nHeight, m_nMips, m_eTFDst);
Relink();
#if defined(TEXTURE_GET_SYSTEM_COPY_SUPPORT) && !defined(NULL_RENDERER)
if (m_nFlags & FT_KEEP_LOWRES_SYSCOPY)
{
PrepareLowResSystemCopy(pIM->mfGet_image(0), false);
}
#endif
return true;
}
bool CTexture::StreamPrepare_Finalise([[maybe_unused]] bool bFromLoad)
{
LOADING_TIME_PROFILE_SECTION;
assert(m_pFileTexMips);
for (int iSide = 0; iSide < m_CacheFileHeader.m_nSides; ++iSide)
{
for (int iMip = m_nMips - m_CacheFileHeader.m_nMipsPersistent; iMip < m_nMips; iMip++)
{
STexMipHeader& mh = m_pFileTexMips->m_pMipHeader[iMip];
SMipData* mp = &mh.m_Mips[iSide];
assert(mp->DataArray);
// If native, assume we're tiled and prepped for the device - we shouldn't need to expand.
if (!mp->m_bNative)
{
int nSrcSideSize = TextureDataSize(max(1, m_nWidth >> iMip), max(1, m_nHeight >> iMip), max(1, m_nDepth >> iMip), 1, 1, m_eTFSrc);
ExpandMipFromFile(mp->DataArray, mh.m_SideSize, mp->DataArray, nSrcSideSize, m_eTFSrc);
}
}
}
{
// create texture
assert(!m_pFileTexMips->m_pPoolItem);
assert(!IsStreaming());
STexPoolItem* pNewPoolItem = StreamGetPoolItem(m_nMips - m_CacheFileHeader.m_nMipsPersistent, m_CacheFileHeader.m_nMipsPersistent, true, true);
if (!pNewPoolItem)
{
gEnv->pLog->LogError("CTexture::StreamPrepare: Failed to allocate memory for persistent mip chain! Texture: '%s'", m_SrcName.c_str());
CRY_ASSERT(false);
CTexture::s_bOutOfMemoryTotally = true;
return false;
}
// upload mips to texture
StreamAssignPoolItem(pNewPoolItem, m_nMips - m_CacheFileHeader.m_nMipsPersistent);
StreamReleaseMipsData(0, m_nMips - 1);
SetWasUnload(false);
}
for (int z = 0; z < MAX_PREDICTION_ZONES; z++)
{
m_streamRounds[z].nRoundUpdateId = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_arrZonesRoundId[z];
}
m_bPostponed = false;
m_fCurrentMipBias = 0.f;
#ifdef ENABLE_TEXTURE_STREAM_LISTENER
if (!m_bStatTracked)
{
ITextureStreamListener* pListener = s_pStreamListener;
m_bStatTracked = 1;
if (pListener)
{
pListener->OnCreatedStreamedTexture(this, m_SrcName.c_str(), m_nMips, m_nMips - GetNumPersistentMips());
}
}
#endif
m_bStreamPrepared = true;
return true;
}
//=========================================================================
void CTexture::StreamValidateTexSize()
{
return;
}
uint8 CTexture::StreamComputeFormatCode(uint32 nWidth, uint32 nHeight, uint32 nMips, ETEX_Format fmt)
{
// Must have a dimension
if (nWidth == 0)
{
return 0;
}
if (nHeight == 0)
{
return 0;
}
// Must be PoT
if (nWidth & (nWidth - 1))
{
return 0;
}
if (nHeight & (nHeight - 1))
{
return 0;
}
uint32 nMaxDim = 1 << (SStreamFormatCode::MaxMips - 1);
// Determine how many missing tail mips there are
uint32 nFullMips = IntegerLog2(max(nWidth, nHeight)) + 1;
uint32 nTailMips = nFullMips - nMips;
// Shift upto find aspect
while ((nWidth != nMaxDim) && (nHeight != nMaxDim))
{
nWidth <<= 1;
nHeight <<= 1;
}
SStreamFormatCodeKey key(nWidth, nHeight, fmt, (uint8)nTailMips);
CryAutoLock<CryCriticalSection> lock(s_streamFormatLock);
TStreamFormatCodeKeyMap::iterator it = s_formatCodeMap.find(key);
if (it == s_formatCodeMap.end())
{
if (s_nFormatCodes == 256)
{
__debugbreak();
}
SStreamFormatCode code;
memset(&code, 0, sizeof(code));
for (uint32 nMip = nTailMips, nMipWidth = nWidth, nMipHeight = nHeight; nMip < SStreamFormatCode::MaxMips; ++nMip, nMipWidth = max(1u, nMipWidth >> 1), nMipHeight = max(1u, nMipHeight >> 1))
{
uint32 nMip1Size = CDeviceTexture::TextureDataSize(nMipWidth, nMipHeight, 1, SStreamFormatCode::MaxMips - nMip, 1, fmt);
bool bAppearsLinear = true;
bool bAppearsPoT = true;
// Determine how the size function varies with slices. Currently only supports linear, or aligning slices to next pot
for (uint32 nSlices = 1; nSlices <= 32; ++nSlices)
{
uint32 nMipSize = CDeviceTexture::TextureDataSize(nMipWidth, nMipHeight, 1, SStreamFormatCode::MaxMips - nMip, nSlices, fmt);
uint32 nExpectedLinearSize = nMip1Size * nSlices;
uint32 nAlignedSlices = 1u << (32 - (nSlices > 1 ? countLeadingZeros32(nSlices - 1) : 32));
uint32 nExpectedPoTSize = nMip1Size * nAlignedSlices;
if (nExpectedLinearSize != nMipSize)
{
bAppearsLinear = false;
}
if (nExpectedPoTSize != nMipSize)
{
bAppearsPoT = false;
}
}
// If this fires, we can't encode the size(slices) function
if (!bAppearsLinear && !bAppearsPoT)
{
__debugbreak();
}
code.sizes[nMip].size = nMip1Size;
code.sizes[nMip].alignSlices = !bAppearsLinear && bAppearsPoT;
}
it = s_formatCodeMap.insert(std::make_pair(key, s_nFormatCodes)).first;
s_formatCodes[s_nFormatCodes++] = code;
}
return it->second;
}
#ifdef ENABLE_TEXTURE_STREAM_LISTENER
void CTexture::StreamUpdateStats()
{
ITextureStreamListener* pListener = s_pStreamListener;
if (pListener)
{
void* pBegunUsing[512];
void* pStoppedUsing[512];
int nBegun = 0, nStopped = 0;
int curFrame = gRenDev->m_RP.m_TI[gRenDev->m_RP.m_nProcessThreadID].m_nFrameUpdateID;
std::vector<CTexture*> texs;
s_pTextureStreamer->StatsFetchTextures(texs);
for (std::vector<CTexture*>::const_iterator it = texs.begin(), itEnd = texs.end(); it != itEnd; ++it)
{
CTexture* pTex = *it;
if (curFrame - pTex->m_nAccessFrameID <= 2)
{
// In use
if (!pTex->m_bUsedRecently)
{
pTex->m_bUsedRecently = 1;
pBegunUsing[nBegun++] = pTex;
IF_UNLIKELY (nBegun == sizeof(pBegunUsing) / sizeof(pBegunUsing[0]))
{
pListener->OnBegunUsingTextures(pBegunUsing, nBegun);
nBegun = 0;
}
}
}
else
{
if (pTex->m_bUsedRecently)
{
pTex->m_bUsedRecently = 0;
pStoppedUsing[nStopped++] = pTex;
IF_UNLIKELY (nStopped == sizeof(pStoppedUsing) / sizeof(pStoppedUsing[0]))
{
pListener->OnEndedUsingTextures(pStoppedUsing, nStopped);
nStopped = 0;
}
}
}
}
if (nBegun)
{
pListener->OnBegunUsingTextures(pBegunUsing, nBegun);
}
if (nStopped)
{
pListener->OnEndedUsingTextures(pStoppedUsing, nStopped);
}
}
}
#endif
//the format on disk must match exactly the format in memory
bool CTexture::CanStreamInPlace([[maybe_unused]] int nMip, [[maybe_unused]] STexPoolItem* pNewPoolItem)
{
#if defined(SUPPORTS_INPLACE_TEXTURE_STREAMING)
if (CRenderer::CV_r_texturesstreamingInPlace)
{
#if TEXTURESTREAMING_CPP_TRAIT_CANSTREAMINPLACE_ETT_2D_EARLY_OUT
if (m_eTT != eTT_2D)
{
return false;
}
#endif
#if TEXTURESTREAMING_CPP_TRAIT_CANSTREAMINPLACE_FORMATCOMPATIBLE
bool bFormatCompatible = false;
switch (m_eTFSrc)
{
case eTF_DXT1:
case eTF_DXT3:
case eTF_DXT5:
case eTF_A8:
case eTF_R32F:
case eTF_R16G16F:
case eTF_R16G16S:
case eTF_B4G4R4A4:
case eTF_R16G16B16A16F:
case eTF_3DC:
case eTF_3DCP:
case eTF_CTX1:
case eTF_BC6UH:
case eTF_BC7:
case eTF_R9G9B9E5:
case eTF_EAC_R11:
case eTF_EAC_RG11:
case eTF_ETC2:
case eTF_ETC2A:
#ifdef CRY_USE_METAL
case eTF_PVRTC2:
case eTF_PVRTC4:
#endif
#if defined(ANDROID) || defined(CRY_USE_METAL)
case eTF_ASTC_4x4:
case eTF_ASTC_5x4:
case eTF_ASTC_5x5:
case eTF_ASTC_6x5:
case eTF_ASTC_6x6:
case eTF_ASTC_8x5:
case eTF_ASTC_8x6:
case eTF_ASTC_8x8:
case eTF_ASTC_10x5:
case eTF_ASTC_10x6:
case eTF_ASTC_10x8:
case eTF_ASTC_10x10:
case eTF_ASTC_12x10:
case eTF_ASTC_12x12:
#endif
bFormatCompatible = true;
break;
default:
bFormatCompatible = false;
break;
}
if (!bFormatCompatible)
{
return false;
}
if (m_nFlags & FT_TEX_WAS_NOT_PRE_TILED)
{
return false;
}
#endif
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_6
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
#if TEXTURESTREAMING_CPP_TRAIT_CANSTREAMINPLACE_SRCTILEMODE_CHECK && !defined(NULL_RENDERER)
CDeviceTexture* pDevTex = pNewPoolItem->m_pDevTexture;
if (m_eSrcTileMode != eTM_LinearPadded || !pDevTex->IsInPool() || !m_pFileTexMips->m_pMipHeader[nMip].m_InPlaceStreamable)
{
return false;
}
#endif
return true;
}
#endif
return false;
}
#if defined(TEXSTRM_ASYNC_TEXCOPY)
bool CTexture::CanAsyncCopy()
{
#if defined(TEXSTRM_CUBE_DMA_BROKEN)
return m_eTT == eTT_2D;
#else
return true;
#endif
}
#endif
bool CTexture::StartStreaming(CTexture* pTex, STexPoolItem* pNewPoolItem, const int nStartMip, const int nEndMip, const int nActivateMip, EStreamTaskPriority estp)
{
FUNCTION_PROFILER_RENDERER;
CHK_RENDTH;
if (pTex->TryAddRef() > 0)
{
if (pTex->IsStreaming())
{
__debugbreak();
}
if (pTex->m_eTT != eTT_2D && pTex->m_eTT != eTT_Cube)
{
__debugbreak();
}
STexStreamInState* pStreamState = StreamState_AllocateIn();
if (pStreamState)
{
DDSSplitted::ChunkInfo chunks[16];
DDSSplitted::DDSDesc desc = pTex->m_pFileTexMips->m_desc;
desc.pName = pTex->m_SrcName.c_str();
int nDeltaMips = desc.nMips - pTex->m_nMips;
size_t nNumChunks = DDSSplitted::GetFilesToRead(chunks, 16, desc, nStartMip + nDeltaMips, nEndMip + nDeltaMips);
StreamReadBatchParams streamRequests[STexStreamInState::MaxStreams];
size_t nStreamRequests = 0;
pStreamState->m_pTexture = pTex;
pStreamState->m_pNewPoolItem = pNewPoolItem;
#ifndef _RELEASE
pStreamState->m_fStartTime = iTimer->GetAsyncTime().GetSeconds();
#endif
pStreamState->m_nAsyncRefCount = 0;
pStreamState->m_nHigherUploadedMip = nStartMip;
pStreamState->m_nLowerUploadedMip = nStartMip + nNumChunks - 1;
pStreamState->m_nActivateMip = nActivateMip;
// update streaming frame ID
int nSizeToSubmit = 0;
int nSides = pTex->GetNumSides();
for (DDSSplitted::ChunkInfo* it = chunks, * itEnd = chunks + nNumChunks; it != itEnd; ++it)
{
const DDSSplitted::ChunkInfo& chunk = *it;
assert(chunk.nMipLevel >= nStartMip);
uint32 nChunkMip = chunk.nMipLevel - nDeltaMips;
uint16 nMipIdx = nChunkMip - nStartMip;
STexStreamInMipState& mipState = pStreamState->m_mips[nMipIdx];
mipState.m_nSideDelta = chunk.nSideDelta;
StreamReadParams baseParams;
baseParams.nFlags |= IStreamEngine::FLAGS_NO_SYNC_CALLBACK;
baseParams.dwUserData = nMipIdx;
baseParams.nLoadTime = 1;
baseParams.nMaxLoadTime = 4;
baseParams.ePriority = estp;
baseParams.nOffset = chunk.nOffsetInFile;
baseParams.nSize = chunk.nSizeInFile;
baseParams.nPerceptualImportance = nEndMip - nStartMip;
baseParams.eMediaType = (EStreamSourceMediaType)pTex->m_pFileTexMips->m_pMipHeader[nChunkMip].m_eMediaType;
if (pTex->CanStreamInPlace(nChunkMip, pNewPoolItem))
{
streamRequests[nStreamRequests].params = baseParams;
byte* pBaseAddress = NULL;
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION TEXTURESTREAMING_CPP_SECTION_7
#include AZ_RESTRICTED_FILE(TextureStreaming_cpp)
#endif
if (pBaseAddress)
{
streamRequests[nStreamRequests].params.pBuffer = pBaseAddress;
mipState.m_bStreamInPlace = true;
}
streamRequests[nStreamRequests].pCallback = pStreamState;
streamRequests[nStreamRequests].szFile = chunk.fileName.c_str();
streamRequests[nStreamRequests].tSource = eStreamTaskTypeTexture;
++nStreamRequests;
++pStreamState->m_nAsyncRefCount;
}
else
{
streamRequests[nStreamRequests].params = baseParams;
streamRequests[nStreamRequests].pCallback = pStreamState;
streamRequests[nStreamRequests].szFile = chunk.fileName.c_str();
streamRequests[nStreamRequests].tSource = eStreamTaskTypeTexture;
++nStreamRequests;
++pStreamState->m_nAsyncRefCount;
}
nSizeToSubmit += pTex->m_pFileTexMips->m_pMipHeader[nChunkMip].m_SideSize * nSides;
}
AZStd::function<void()> updateTextureByteCounterAtomic = [&nSizeToSubmit]()
{
CryInterlockedAdd(s_nBytesSubmittedToStreaming.Addr(), nSizeToSubmit);
};
size_t nStreams = gEnv->pSystem->GetStreamEngine()->StartBatchRead(pStreamState->m_pStreams, streamRequests, nStreamRequests, &updateTextureByteCounterAtomic);
if (nStreams)
{
pTex->SetStreamingInProgress(s_StreamInTasks.GetIdxFromPtr(pStreamState));
CryInterlockedAdd(s_nMipsSubmittedToStreaming.Addr(), (int)nStreams);
return true;
}
else
{
pStreamState->m_pTexture->Release();
pStreamState->m_pTexture = NULL;
StreamState_ReleaseIn(pStreamState);
}
}
else
{
s_pPoolMgr->ReleaseItem(pNewPoolItem);
pTex->Release();
}
}
else
{
s_pPoolMgr->ReleaseItem(pNewPoolItem);
}
return false;
}
void CTexture::StreamUploadMips(int nStartMip, int nEndMip, STexPoolItem* pNewPoolItem)
{
assert (pNewPoolItem);
CTimeValue time0 = iTimer->GetAsyncTime();
StreamCopyMipsTexToMem(nStartMip, nEndMip, true, pNewPoolItem);
// Restore mips data from the device texture
if (s_bStreamDontKeepSystem && m_pFileTexMips && m_pFileTexMips->m_pPoolItem)
{
STexPoolItem* pSrcItem = m_pFileTexMips->m_pPoolItem;
int nSrcOffset = m_nMips - pSrcItem->m_pOwner->m_nMips;
int nDstOffset = m_nMips - pNewPoolItem->m_pOwner->m_nMips;
StreamCopyMipsTexToTex(pSrcItem, (nEndMip + 1) - nSrcOffset, pNewPoolItem, (nEndMip + 1) - nDstOffset, m_nMips - (nEndMip + 1));
}
if (s_bStreamDontKeepSystem)
{
StreamReleaseMipsData(nStartMip, nEndMip);
}
gRenDev->m_RP.m_PS[gRenDev->m_RP.m_nProcessThreadID].m_fTexUploadTime += (iTimer->GetAsyncTime() - time0).GetSeconds();
}
void CTexture::InitStreaming()
{
CHK_MAINORRENDTH;
iLog->Log("Init textures management (%" PRISIZE_T " Mb of video memory is available)...", gRenDev->m_MaxTextureMemory / 1024 / 1024);
// reset all statistics
s_nStreamingThroughput = 0;
s_nStreamingTotalTime = 0;
InitStreamingDev();
if (!s_pTextureStreamer || s_nStreamingUpdateMode != CRenderer::CV_r_texturesstreamingUpdateType)
{
delete s_pTextureStreamer;
switch (CRenderer::CV_r_texturesstreamingUpdateType)
{
default:
case 1:
s_pTextureStreamer = new CPlanningTextureStreamer();
break;
}
s_nStreamingUpdateMode = CRenderer::CV_r_texturesstreamingUpdateType;
}
if (!s_pPoolMgr)
{
s_pPoolMgr = new CTextureStreamPoolMgr();
}
#if !defined(CONSOLE)
if (CRenderer::CV_r_texturesstreaming)
{
int nMinTexStreamPool = 192;
#ifdef NULL_RENDERER
// Dedicated server uses CNULLRenderer, which doesn't report any available
// memory via gRenDev->m_MaxTextureMemory, so use a magic number. It's OK
// to use it here, as a dedicated server will not load textures anyway.
int nMaxTexStreamPool = 8192;
#else
int nMaxTexStreamPool = (gRenDev->m_MaxTextureMemory / 1024 / 1024);
#endif
ICVar* pICVarTexRes = iConsole->GetCVar("sys_spec_TextureResolution");
if (pICVarTexRes)
{
const int valTexRes = pICVarTexRes->GetIVal();
if (valTexRes == 0)
{
// Some cards report slightly lower byte numbers than their spec in MB suggests, so we have to be conservative
// Note: On some MGPU systems the memory reported is the overall amount and not the memory available per GPU
if (gRenDev->m_MaxTextureMemory >= (size_t)2800 * 1024 * 1024)
{
pICVarTexRes->Set(VERYHIGH_SPEC_PC);
}
else if (gRenDev->m_MaxTextureMemory >= (size_t)1900 * 1024 * 1024)
{
pICVarTexRes->Set(HIGH_SPEC_PC);
}
else if (gRenDev->m_MaxTextureMemory >= (size_t)1450 * 1024 * 1024)
{
pICVarTexRes->Set(MEDIUM_SPEC_PC);
}
else
{
pICVarTexRes->Set(LOW_SPEC_PC);
}
}
else
{
pICVarTexRes->Set(valTexRes);
}
}
AZ_WarningOnce("TextureStreaming", CRenderer::CV_r_TexturesStreamPoolSize <= (nMaxTexStreamPool * 0.75f), "Warning! You are assigning more than 75 percent of total available GPU memory to texture streaming!");
CRenderer::CV_r_TexturesStreamPoolSize = clamp_tpl(CRenderer::CV_r_TexturesStreamPoolSize, nMinTexStreamPool, nMaxTexStreamPool);
// Don't skip mips in the editor so that assets can be viewed in full resolution
if (gEnv->IsEditor())
{
CRenderer::CV_r_texturesstreamingSkipMips = 0;
}
}
#endif
s_nStreamingMode = CRenderer::CV_r_texturesstreaming;
s_bStreamDontKeepSystem = CRenderer::CV_r_texturesstreamingonlyvideo == 0;
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_texturesstreaming)
{
iLog->Log(" Enabling of textures streaming...");
iLog->Log(" Using %d Mb of textures pool for streaming...", CRenderer::GetTexturesStreamPoolSize());
}
else
{
iLog->Log(" Disabling of textures streaming...");
}
if (gRenDev->m_MaxTextureMemory <= 256 * 1024 * 1024)
{
SDynTexture::s_CurDynTexAtlasCloudsMaxsize = min(24u, SDynTexture::s_CurDynTexAtlasCloudsMaxsize);
SDynTexture::s_CurTexAtlasSize = min(128u, SDynTexture::s_CurTexAtlasSize);
SDynTexture::s_CurDynTexMaxSize = min(128u, SDynTexture::s_CurDynTexMaxSize);
}
iLog->Log(" Video textures: Atlas clouds max size: %d Mb", SDynTexture::s_CurDynTexAtlasCloudsMaxsize);
iLog->Log(" Video textures: Dynamic managed max size: %d Mb", SDynTexture::s_CurDynTexMaxSize);
// re-init all textures
iLog->Log(" Reloading all textures...");
{
AUTO_LOCK(CBaseResource::s_cResLock);
SResourceContainer* pRL = CBaseResource::GetResourcesForClass(CTexture::mfGetClassName());
if (pRL)
{
for (ResourcesMapItor itor = pRL->m_RMap.begin(); itor != pRL->m_RMap.end(); ++itor)
{
CTexture* tp = (CTexture*)itor->second;
if (!tp)
{
continue;
}
tp->ToggleStreaming(CRenderer::CV_r_texturesstreaming != 0);
}
}
}
iLog->Log(" Finished reloading textures...");
iLog->Log(" Finished initializing textures streaming...");
if (gEnv->pLocalMemoryUsage)
{
gEnv->pLocalMemoryUsage->DeleteGlobalData();
}
}
void CTexture::RT_FlushStreaming(bool bAbort)
{
RT_FlushAllStreamingTasks(bAbort);
// flush all pool items
s_pPoolMgr->GarbageCollect(NULL, 0, 10000000);
}
void CTexture::RT_FlushAllStreamingTasks(const bool bAbort /* = false*/)
{
// flush all tasks globally
AZ_TRACE_METHOD();
CHK_RENDTH;
iLog->Log("Flushing pended textures...");
#ifdef TEXSTRM_ASYNC_TEXCOPY
{
for (int i = 0; i < MaxStreamTasks; ++i)
{
STexStreamOutState& os = *s_StreamOutTasks.GetPtrFromIdx(i);
if (os.m_pTexture)
{
if (bAbort)
{
os.m_bAborted = true;
}
os.m_jobExecutor.WaitForCompletion();
}
}
}
#endif
if (bAbort)
{
for (int i = 0; i < MaxStreamPrepTasks; ++i)
{
STexStreamPrepState*& ps = *s_StreamPrepTasks.GetPtrFromIdx(i);
if (ps)
{
_smart_ptr<CImageFile> pFile = ps->m_pImage;
if (pFile)
{
pFile->mfAbortStreaming();
}
delete ps;
ps = NULL;
s_StreamPrepTasks.Release(&ps);
}
}
}
{
for (int i = 0; i < MaxStreamTasks; ++i)
{
STexStreamInState& ins = *s_StreamInTasks.GetPtrFromIdx(i);
if (ins.m_pTexture)
{
if (bAbort)
{
ins.m_bAborted = true;
}
for (int m = 0; m < (ins.m_nLowerUploadedMip - ins.m_nHigherUploadedMip) + 1; ++m)
{
IReadStreamPtr pStream = ins.m_pStreams[m];
if (pStream)
{
if (bAbort)
{
pStream->Abort();
}
else
{
pStream->Wait();
}
}
}
}
}
}
StreamState_Update();
StreamState_UpdatePrep();
s_pTextureStreamer->Flush();
assert(s_nBytesSubmittedToStreaming == 0);
iLog->Log("Finished flushing pended textures...");
}
void CTexture::AbortStreamingTasks(CTexture* pTex)
{
if (pTex->m_nStreamSlot != InvalidStreamSlot)
{
CHK_RENDTH;
if (pTex->m_nStreamSlot & StreamOutMask)
{
#ifdef TEXSTRM_ASYNC_TEXCOPY
STexStreamOutState& streamState = *s_StreamOutTasks.GetPtrFromIdx(pTex->m_nStreamSlot & StreamIdxMask);
#ifndef _RELEASE
if (streamState.m_pTexture != pTex)
{
__debugbreak();
}
#endif
streamState.m_bAborted = true;
if (!streamState.m_bDone)
{
streamState.m_jobExecutor.WaitForCompletion();
}
while (!streamState.TryCommit())
{
CrySleep(0);
}
StreamState_ReleaseOut(&streamState);
#endif
}
else if (pTex->m_nStreamSlot & StreamPrepMask)
{
STexStreamPrepState*& pStreamState = *s_StreamPrepTasks.GetPtrFromIdx(pTex->m_nStreamSlot & StreamIdxMask);
#ifndef _RELEASE
if (pStreamState->m_pTexture != pTex)
{
__debugbreak();
}
#endif
delete pStreamState;
pStreamState = NULL;
s_StreamPrepTasks.Release(&pStreamState);
}
else
{
STexStreamInState& streamState = *s_StreamInTasks.GetPtrFromIdx(pTex->m_nStreamSlot & StreamIdxMask);
#ifndef _RELEASE
if (streamState.m_pTexture != pTex)
{
__debugbreak();
}
#endif
streamState.m_bAborted = true;
for (size_t i = 0, c = streamState.m_nLowerUploadedMip - streamState.m_nHigherUploadedMip + 1; i < c; ++i)
{
IReadStream* pStream = streamState.m_pStreams[i];
if (pStream)
{
pStream->Abort();
assert (streamState.m_pStreams[i] == NULL);
}
}
#ifndef NDEBUG
bool bCommitted =
#endif
streamState.TryCommit();
assert (bCommitted);
StreamState_ReleaseIn(&streamState);
}
}
}
void CTexture::StreamState_Update()
{
CHK_RENDTH;
FUNCTION_PROFILER_RENDERER;
// Finalise and garbage collect the stream out tasks
#ifdef TEXSTRM_ASYNC_TEXCOPY
for (size_t i = 0, c = s_StreamOutTasks.GetNumLive(); i < MaxStreamTasks && c > 0; ++i)
{
STexStreamOutState& state = *s_StreamOutTasks.GetPtrFromIdx(i);
if (state.m_pTexture)
{
if (state.TryCommit())
{
StreamState_ReleaseOut(&state);
}
--c;
}
}
#endif
// Garbage collect the stream in slots
for (size_t i = 0, c = s_StreamInTasks.GetNumLive(); i < MaxStreamTasks && c > 0; ++i)
{
STexStreamInState& state = *s_StreamInTasks.GetPtrFromIdx(i);
if (state.m_pTexture)
{
if (state.m_bAllStreamsComplete)
{
if (state.TryCommit())
{
StreamState_ReleaseIn(&state);
}
}
else if (state.m_bAborted)
{
// An error occurred. Try and cancel all the stream tasks.
for (int mi = 0, mc = state.m_nLowerUploadedMip - state.m_nHigherUploadedMip + 1; mi != mc; ++mi)
{
IReadStream* pStream = &*state.m_pStreams[mi];
if (pStream != NULL)
{
pStream->TryAbort();
}
}
}
--c;
}
}
}
void CTexture::StreamState_UpdatePrep()
{
FUNCTION_PROFILER_RENDERER;
LOADING_TIME_PROFILE_SECTION;
// Garbage collect the stream prep slots
for (size_t i = 0, c = s_StreamPrepTasks.GetNumLive(); i < MaxStreamPrepTasks && c > 0; ++i)
{
STexStreamPrepState*& pState = *s_StreamPrepTasks.GetPtrFromIdx(i);
if (pState && pState->m_bCompleted && pState->Commit())
{
s_pTextureStreamer->EndPrepare(pState);
}
}
}
STexStreamInState* CTexture::StreamState_AllocateIn()
{
CHK_RENDTH;
return s_StreamInTasks.Allocate();
}
void CTexture::StreamState_ReleaseIn(STexStreamInState* pState)
{
CHK_RENDTH;
pState->Reset();
s_StreamInTasks.Release(pState);
}
#ifdef TEXSTRM_ASYNC_TEXCOPY
STexStreamOutState* CTexture::StreamState_AllocateOut()
{
CHK_RENDTH;
return s_StreamOutTasks.Allocate();
}
void CTexture::StreamState_ReleaseOut(STexStreamOutState* pState)
{
CHK_RENDTH;
pState->Reset();
s_StreamOutTasks.Release(pState);
}
#endif
STexStreamingInfo* CTexture::StreamState_AllocateInfo(int nMips)
{
// Temporary - will be replaced by custom allocator later.
STexStreamingInfo* pInfo = (STexStreamingInfo*)CryModuleMalloc(sizeof(STexStreamingInfo));
new (pInfo) STexStreamingInfo(nMips);
return pInfo;
}
void CTexture::StreamState_ReleaseInfo(CTexture* pOwnerTex, STexStreamingInfo* pInfo)
{
// Make sure we the streamer is notified, so jobs can be synced with
s_pTextureStreamer->OnTextureDestroy(pOwnerTex);
pInfo->~STexStreamingInfo();
CryModuleFree(pInfo);
}