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.
1799 lines
68 KiB
C++
1799 lines
68 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 : Manages geometry cache instances and streaming
|
|
|
|
|
|
#include "Cry3DEngine_precompiled.h"
|
|
|
|
#if defined(USE_GEOM_CACHES)
|
|
|
|
#include "GeomCacheManager.h"
|
|
#include "GeomCache.h"
|
|
#include "GeomCacheRenderNode.h"
|
|
#include "Cry_Color.h"
|
|
|
|
namespace
|
|
{
|
|
const uint kMinBufferSizeInMiB = 8;
|
|
const uint kMaxBufferSizeInMiB = 2048;
|
|
}
|
|
|
|
CGeomCacheManager::CGeomCacheManager()
|
|
: m_pPoolBaseAddress(NULL)
|
|
, m_pPool(NULL)
|
|
, m_poolSize(0)
|
|
, m_lastRequestStream(0)
|
|
, m_numMissedFrames(0)
|
|
, m_numStreamAborts(0)
|
|
, m_numErrorAborts(0)
|
|
, m_numDecompressStreamAborts(0)
|
|
, m_numReadStreamAborts(0)
|
|
, m_numFailedAllocs(0)
|
|
{
|
|
ChangeBufferSize(GetCVars()->e_GeomCacheBufferSize);
|
|
|
|
ICVar* pGeomCacheBufferSizeCVar = gEnv->pConsole->GetCVar("e_GeomCacheBufferSize");
|
|
if (pGeomCacheBufferSizeCVar)
|
|
{
|
|
pGeomCacheBufferSizeCVar->SetOnChangeCallback(&CGeomCacheManager::OnChangeBufferSize);
|
|
}
|
|
|
|
// Connect for LegacyAssetEventBus::Handler
|
|
BusConnect(AZ_CRC("cax", 0x97e80f83));
|
|
}
|
|
|
|
CGeomCacheManager::~CGeomCacheManager()
|
|
{
|
|
Reset();
|
|
UnloadGeomCaches();
|
|
|
|
if (!gEnv->IsDedicated())
|
|
{
|
|
m_pPool->Release();
|
|
m_pPool = NULL;
|
|
|
|
CryMemory::FreePages(m_pPoolBaseAddress, m_poolSize);
|
|
}
|
|
// Disconnect for LegacyAssetEventBus::Handler
|
|
BusDisconnect();
|
|
}
|
|
|
|
void CGeomCacheManager::Reset()
|
|
{
|
|
const uint numStreams = m_streamInfos.size();
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = m_streamInfos[i];
|
|
AbortStreamAndWait(*pStreamInfo);
|
|
delete pStreamInfo;
|
|
}
|
|
|
|
stl::free_container(m_streamInfos);
|
|
|
|
GetMeshManager().Reset();
|
|
}
|
|
|
|
void CGeomCacheManager::StopCacheStreamsAndWait(CGeomCache* pGeomCache)
|
|
{
|
|
const uint numStreams = m_streamInfos.size();
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = m_streamInfos[i];
|
|
if (pStreamInfo->m_pGeomCache == pGeomCache)
|
|
{
|
|
AbortStreamAndWait(*pStreamInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
CGeomCache* CGeomCacheManager::FindGeomCacheByFilename(const char* filename)
|
|
{
|
|
return stl::find_in_map(m_nameToGeomCacheMap, CONST_TEMP_STRING(filename), NULL);
|
|
}
|
|
|
|
CGeomCache* CGeomCacheManager::LoadGeomCache(const char* szFileName)
|
|
{
|
|
LOADING_TIME_PROFILE_SECTION;
|
|
|
|
// Normalize file name
|
|
char sFilename[_MAX_PATH];
|
|
|
|
// Remap %level% alias if needed an unify filename
|
|
int nAliasNameLen = sizeof("%level%") - 1;
|
|
if (strncmp(szFileName, "%level%", nAliasNameLen) == 0)
|
|
{
|
|
cry_strcpy(sFilename, Get3DEngine()->GetLevelFilePath(szFileName + nAliasNameLen));
|
|
}
|
|
else
|
|
{
|
|
cry_strcpy(sFilename, szFileName);
|
|
}
|
|
std::replace(sFilename, sFilename + strlen(sFilename), '\\', '/'); // To Unix Path
|
|
|
|
// Try to find existing object for that file
|
|
CGeomCache* pGeomCache = stl::find_in_map(m_nameToGeomCacheMap, CONST_TEMP_STRING(sFilename), NULL);
|
|
if (pGeomCache)
|
|
{
|
|
return pGeomCache;
|
|
}
|
|
|
|
// Load geom cache
|
|
pGeomCache = new CGeomCache(sFilename);
|
|
m_nameToGeomCacheMap[sFilename] = pGeomCache;
|
|
return pGeomCache;
|
|
}
|
|
|
|
void CGeomCacheManager::UnloadGeomCaches()
|
|
{
|
|
for (TGeomCacheMap::iterator iter = m_nameToGeomCacheMap.begin(); iter != m_nameToGeomCacheMap.end(); ++iter)
|
|
{
|
|
delete iter->second;
|
|
}
|
|
|
|
stl::free_container(m_nameToGeomCacheMap);
|
|
}
|
|
|
|
void CGeomCacheManager::DeleteGeomCache(CGeomCache* pGeomCache)
|
|
{
|
|
const char* pFilename = pGeomCache->GetFilePath();
|
|
m_nameToGeomCacheMap.erase(pFilename);
|
|
delete pGeomCache;
|
|
}
|
|
|
|
void CGeomCacheManager::RegisterForStreaming(CGeomCacheRenderNode* pRenderNode)
|
|
{
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
if (!pRenderNode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (uint i = 0; i < m_streamInfos.size(); ++i)
|
|
{
|
|
const SGeomCacheStreamInfo* pStreamInfo = m_streamInfos[i];
|
|
if (pStreamInfo->m_pRenderNode == pRenderNode)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
CGeomCache* pGeomCache = static_cast<CGeomCache*>(pRenderNode->GetGeomCache());
|
|
pGeomCache->IncreaseNumStreams();
|
|
|
|
const uint numFrames = pGeomCache->GetNumFrames();
|
|
|
|
SGeomCacheStreamInfo* pStreamInfo = new SGeomCacheStreamInfo(pRenderNode, pGeomCache, numFrames);
|
|
m_streamInfos.push_back(pStreamInfo);
|
|
|
|
// If cache is too short we need to allocate double the amount of frame data. Otherwise we can't loop without aborts,
|
|
// because IssueDiskReadRequest prevents the same frame info from being used twice.
|
|
const uint preferredDiskRequestSize = (size_t)std::max(0, GetCVars()->e_GeomCachePreferredDiskRequestSize * 1024);
|
|
const uint64 compressedAnimationDataSize = pGeomCache->GetCompressedAnimationDataSize();
|
|
|
|
const float maxBufferAheadTime = std::max(1.0f, GetCVars()->e_GeomCacheMaxBufferAheadTime);
|
|
const float duration = pGeomCache->GetDuration();
|
|
|
|
const bool bNeedDoubleFrameData = (compressedAnimationDataSize < (preferredDiskRequestSize * 2)) || (duration < (maxBufferAheadTime * 2));
|
|
|
|
const uint numFrameData = bNeedDoubleFrameData ? (numFrames * 2) : numFrames;
|
|
pStreamInfo->m_frameData.resize(numFrameData);
|
|
ReinitializeStreamFrameData(*pStreamInfo, 0, numFrameData - 1);
|
|
}
|
|
|
|
void CGeomCacheManager::OnFileChanged(AZStd::string assetPath)
|
|
{
|
|
CGeomCache* geomCache = FindGeomCacheByFilename(assetPath.c_str());
|
|
if (geomCache)
|
|
{
|
|
geomCache->Reload();
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::OnChangeBufferSize(ICVar* pCVar)
|
|
{
|
|
GetGeomCacheManager()->ChangeBufferSize(pCVar->GetIVal());
|
|
}
|
|
|
|
void CGeomCacheManager::ChangeBufferSize(const uint newSizeInMiB)
|
|
{
|
|
const uint numStreams = m_streamInfos.size();
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = m_streamInfos[i];
|
|
AbortStreamAndWait(*pStreamInfo);
|
|
}
|
|
|
|
if (!gEnv->IsDedicated())
|
|
{
|
|
SAFE_RELEASE(m_pPool);
|
|
if (m_pPoolBaseAddress)
|
|
{
|
|
CryMemory::FreePages(m_pPoolBaseAddress, m_poolSize);
|
|
}
|
|
|
|
const int geomCacheBufferSize = clamp_tpl(newSizeInMiB, kMinBufferSizeInMiB, kMaxBufferSizeInMiB);
|
|
GetCVars()->e_GeomCacheBufferSize = geomCacheBufferSize;
|
|
|
|
const uint kMiBtoBytesFactor = 1024 * 1024;
|
|
m_poolSize = geomCacheBufferSize * kMiBtoBytesFactor;
|
|
|
|
m_pPoolBaseAddress = CryMemory::AllocPages(m_poolSize);
|
|
m_pPool = gEnv->pSystem->GetIMemoryManager()->CreateGeneralMemoryHeap(m_pPoolBaseAddress, m_poolSize, "GEOMCACHE_POOL");
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::ReinitializeStreamFrameData(SGeomCacheStreamInfo& streamInfo, uint startFrame, uint endFrame)
|
|
{
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
|
|
const uint frameDataSize = streamInfo.m_frameData.size();
|
|
startFrame = std::min(frameDataSize - 1, startFrame);
|
|
endFrame = std::min(frameDataSize - 1, endFrame);
|
|
|
|
for (uint i = startFrame; i <= endFrame; ++i)
|
|
{
|
|
SGeomCacheStreamInfo::SFrameData& frameData = streamInfo.m_frameData[i];
|
|
|
|
frameData.m_bDecompressJobLaunched = false;
|
|
frameData.m_pDecompressHandle = NULL;
|
|
|
|
const GeomCacheFile::EFrameType frameType = pGeomCache->GetFrameType(i);
|
|
|
|
if (frameType == GeomCacheFile::eFrameType_IFrame)
|
|
{
|
|
frameData.m_decodeDependencyCounter = 1;
|
|
}
|
|
else if (frameType == GeomCacheFile::eFrameType_BFrame)
|
|
{
|
|
assert(i > 0);
|
|
|
|
if (pGeomCache->GetFrameType(i - 1) == GeomCacheFile::eFrameType_IFrame)
|
|
{
|
|
frameData.m_decodeDependencyCounter = 3;
|
|
}
|
|
else
|
|
{
|
|
frameData.m_decodeDependencyCounter = 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::UnRegisterForStreaming(CGeomCacheRenderNode* pRenderNode, bool bWaitForJobs)
|
|
{
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
TStreamInfosIter iter = m_streamInfos.begin();
|
|
while (iter != m_streamInfos.end())
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = *iter;
|
|
if (pStreamInfo->m_pRenderNode == pRenderNode)
|
|
{
|
|
if (pStreamInfo->m_pNewestReadRequestHandle || pStreamInfo->m_pOldestDecompressHandle)
|
|
{
|
|
gEnv->pLog->LogWarning("Unregistering stream %s while still active", pStreamInfo->m_pRenderNode->GetName());
|
|
}
|
|
|
|
if (!bWaitForJobs)
|
|
{
|
|
AbortStream(*pStreamInfo);
|
|
}
|
|
else
|
|
{
|
|
AbortStreamAndWait(*pStreamInfo);
|
|
}
|
|
|
|
m_streamInfosAbortList.push_back(pStreamInfo);
|
|
iter = m_streamInfos.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
RetireRemovedStreams();
|
|
}
|
|
|
|
void CGeomCacheManager::StreamingUpdate()
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
const bool bCachesActive = GetCVars()->e_GeomCaches != 0;
|
|
|
|
RetireRemovedStreams();
|
|
|
|
const uint numStreams = m_streamInfos.size();
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo& streamInfo = *m_streamInfos[i];
|
|
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::StreamingUpdate_WaitForLastFillJob", GetSystem(), PROFILE_3DENGINE);
|
|
streamInfo.m_fillRenderNodeJobExecutor.WaitForCompletion();
|
|
}
|
|
|
|
// Update wanted playback frame
|
|
CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
streamInfo.m_wantedPlaybackTime = pRenderNode->GetPlaybackTime();
|
|
streamInfo.m_wantedFloorFrame = pGeomCache->GetFloorFrameIndex(streamInfo.m_wantedPlaybackTime);
|
|
streamInfo.m_wantedCeilFrame = pGeomCache->GetCeilFrameIndex(streamInfo.m_wantedPlaybackTime);
|
|
streamInfo.m_bLooping = streamInfo.m_pRenderNode->IsLooping();
|
|
|
|
if (!streamInfo.m_bLooping)
|
|
{
|
|
const uint numFrames = streamInfo.m_numFrames;
|
|
streamInfo.m_wantedFloorFrame = std::min((uint)streamInfo.m_wantedFloorFrame, numFrames - 1);
|
|
streamInfo.m_wantedCeilFrame = std::min((uint)streamInfo.m_wantedCeilFrame, numFrames - 1);
|
|
}
|
|
|
|
assert(streamInfo.m_wantedFloorFrame + 1 == streamInfo.m_wantedCeilFrame
|
|
|| streamInfo.m_wantedFloorFrame == streamInfo.m_wantedCeilFrame);
|
|
|
|
// Update bbox for this frame
|
|
pRenderNode->UpdateBBox();
|
|
|
|
// Abort stream and trash it if it's not valid anymore
|
|
ValidateStream(streamInfo);
|
|
|
|
// Try to trash as many aborted handles as possible
|
|
RetireAbortedHandles(streamInfo);
|
|
|
|
// Retire handles that are not needed anymore
|
|
RetireHandles(streamInfo);
|
|
}
|
|
|
|
if (bCachesActive)
|
|
{
|
|
const CTimeValue currentFrameTime = GetTimer()->GetFrameStartTime();
|
|
LaunchStreamingJobs(numStreams, currentFrameTime);
|
|
}
|
|
|
|
// Start disk read requests alternating between geom cache render nodes until no more
|
|
// can be issued (buffers full, max read ahead time reached, no free request object)
|
|
bool bMoreRequests = true;
|
|
while (bMoreRequests && bCachesActive)
|
|
{
|
|
bMoreRequests = false;
|
|
uint nextRequestStream = m_lastRequestStream + 1;
|
|
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
const uint requestStream = (nextRequestStream + i) % numStreams;
|
|
|
|
SGeomCacheStreamInfo& streamInfo = *m_streamInfos[i];
|
|
const CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
const bool bIsStreaming = pRenderNode->IsStreaming();
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
const bool bPlaybackFromMemory = pGeomCache->PlaybackFromMemory();
|
|
const float playbackFrame = streamInfo.m_wantedPlaybackTime;
|
|
const float displayedFrame = streamInfo.m_displayedFrameTime;
|
|
|
|
if (!bPlaybackFromMemory && (bIsStreaming || (displayedFrame != playbackFrame)))
|
|
{
|
|
const bool bRequestIssued = IssueDiskReadRequest(streamInfo);
|
|
|
|
if (bRequestIssued)
|
|
{
|
|
m_lastRequestStream = requestStream;
|
|
}
|
|
|
|
bMoreRequests |= bRequestIssued;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef _RELEASE
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo& streamInfo = *m_streamInfos[i];
|
|
streamInfo.m_pRenderNode->DebugRender();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CGeomCacheManager::LaunchStreamingJobs(const uint numStreams, const CTimeValue currentFrameTime)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
// Launch streaming jobs
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = m_streamInfos[i];
|
|
CGeomCacheRenderNode* pRenderNode = pStreamInfo->m_pRenderNode;
|
|
const CGeomCache* pGeomCache = pStreamInfo->m_pGeomCache;
|
|
|
|
if (!pGeomCache)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const bool bIsStreaming = pRenderNode->IsStreaming();
|
|
const float playbackFrameTime = pStreamInfo->m_wantedPlaybackTime;
|
|
const float displayedFrameTime = pStreamInfo->m_displayedFrameTime;
|
|
|
|
LaunchDecompressJobs(pStreamInfo, currentFrameTime);
|
|
|
|
const bool bSameFrame = playbackFrameTime == displayedFrameTime;
|
|
if (!bSameFrame || pStreamInfo->m_sameFrameFillCount < 2)
|
|
{
|
|
pRenderNode->StartAsyncUpdate();
|
|
// Legacy priority - High
|
|
pStreamInfo->m_fillRenderNodeJobExecutor.StartJob([this, pStreamInfo]() { this->FillRenderNodeAsync_JobEntry(pStreamInfo); });
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::RetireRemovedStreams()
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
TStreamInfosIter iter = m_streamInfosAbortList.begin();
|
|
|
|
while (iter != m_streamInfosAbortList.end())
|
|
{
|
|
SGeomCacheStreamInfo* pStreamInfo = *iter;
|
|
|
|
pStreamInfo->m_fillRenderNodeJobExecutor.WaitForCompletion();
|
|
RetireAbortedHandles(*pStreamInfo);
|
|
|
|
if (pStreamInfo->m_fillRenderNodeJobExecutor.IsRunning()
|
|
|| pStreamInfo->m_pReadAbortListHead || pStreamInfo->m_pDecompressAbortListHead)
|
|
{
|
|
++iter;
|
|
}
|
|
else
|
|
{
|
|
// Nothing left running, we can finally delete the stream
|
|
CGeomCache* pGeomCache = pStreamInfo->m_pGeomCache;
|
|
pGeomCache->DecreaseNumStreams();
|
|
|
|
pStreamInfo->m_pRenderNode->ClearFillData();
|
|
|
|
delete pStreamInfo;
|
|
iter = m_streamInfosAbortList.erase(iter);
|
|
}
|
|
}
|
|
|
|
for (TGeomCacheMap::iterator it = m_nameToGeomCacheMap.begin(); it != m_nameToGeomCacheMap.end(); ++it)
|
|
{
|
|
CGeomCache* pGeomCache = it->second;
|
|
if (pGeomCache->GetNumStreams() == 0)
|
|
{
|
|
pGeomCache->UnloadData();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::ValidateStream(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
const CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
const bool bIsStreaming = pRenderNode->IsStreaming();
|
|
const float wantedPlaybackTime = streamInfo.m_wantedPlaybackTime;
|
|
const float displayedFrameTime = streamInfo.m_displayedFrameTime;
|
|
|
|
if (!pRenderNode->IsStreaming() && (displayedFrameTime == wantedPlaybackTime) && streamInfo.m_sameFrameFillCount >= 2)
|
|
{
|
|
AbortStream(streamInfo);
|
|
return;
|
|
}
|
|
|
|
// Abort if there was an error in the stream. Also set the render node to not play back in this case.
|
|
if (streamInfo.m_pOldestReadRequestHandle && streamInfo.m_pOldestReadRequestHandle->m_error != 0)
|
|
{
|
|
++m_numStreamAborts;
|
|
++m_numErrorAborts;
|
|
gEnv->pLog->LogError("Error in cache stream %s", streamInfo.m_pRenderNode->GetName());
|
|
AbortStream(streamInfo);
|
|
streamInfo.m_pRenderNode->StopStreaming();
|
|
return;
|
|
}
|
|
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
const float currentCacheStreamingTime = streamInfo.m_pRenderNode->GetStreamingTime();
|
|
const uint wantedFloorFrame = pGeomCache->GetFloorFrameIndex(currentCacheStreamingTime);
|
|
|
|
// Check if stream is invalid
|
|
bool bAbort = false;
|
|
|
|
if (streamInfo.m_pOldestDecompressHandle)
|
|
{
|
|
if ((streamInfo.m_pOldestDecompressHandle->m_startFrame > wantedFloorFrame)
|
|
|| (streamInfo.m_pNewestDecompressHandle->m_endFrame < wantedFloorFrame))
|
|
{
|
|
gEnv->pLog->LogWarning("Aborting cache stream %s (decompress stream: [%u, %u], wanted frame: %u)", streamInfo.m_pRenderNode->GetName(),
|
|
streamInfo.m_pOldestDecompressHandle->m_startFrame, streamInfo.m_pNewestDecompressHandle->m_endFrame, wantedFloorFrame);
|
|
++m_numDecompressStreamAborts;
|
|
bAbort = true;
|
|
}
|
|
}
|
|
else if (streamInfo.m_pOldestReadRequestHandle)
|
|
{
|
|
if (streamInfo.m_pOldestReadRequestHandle->m_startFrame > wantedFloorFrame)
|
|
{
|
|
gEnv->pLog->LogWarning("Aborting cache stream %s (read stream start: %u, wanted frame: %u)", streamInfo.m_pRenderNode->GetName(),
|
|
streamInfo.m_pOldestReadRequestHandle->m_startFrame, wantedFloorFrame);
|
|
++m_numReadStreamAborts;
|
|
bAbort = true;
|
|
}
|
|
}
|
|
|
|
if (bAbort)
|
|
{
|
|
++m_numStreamAborts;
|
|
AbortStream(streamInfo);
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::AbortStream(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
streamInfo.m_bAbort = true;
|
|
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::AbortStream_LockFillRenderNode", GetSystem(), PROFILE_3DENGINE);
|
|
streamInfo.m_abortCS.Lock();
|
|
}
|
|
|
|
if (streamInfo.m_pNewestReadRequestHandle)
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::AbortStream_AbortReads", GetSystem(), PROFILE_3DENGINE);
|
|
|
|
assert(streamInfo.m_pOldestReadRequestHandle != NULL);
|
|
|
|
// Abort read requests if possible
|
|
for (SGeomCacheReadRequestHandle* pCurrentReadRequestHandle = streamInfo.m_pOldestReadRequestHandle;
|
|
pCurrentReadRequestHandle; pCurrentReadRequestHandle = static_cast<SGeomCacheReadRequestHandle*>(pCurrentReadRequestHandle->m_pNext))
|
|
{
|
|
if (pCurrentReadRequestHandle->m_pReadStream)
|
|
{
|
|
pCurrentReadRequestHandle->m_pReadStream->TryAbort();
|
|
}
|
|
}
|
|
|
|
// Put all handles in the read request list on the abort list
|
|
assert(streamInfo.m_pNewestReadRequestHandle->m_pNext == NULL);
|
|
streamInfo.m_pNewestReadRequestHandle->m_pNext = streamInfo.m_pReadAbortListHead;
|
|
streamInfo.m_pReadAbortListHead = streamInfo.m_pOldestReadRequestHandle;
|
|
|
|
// Mark read request list as empty
|
|
streamInfo.m_pOldestReadRequestHandle = NULL;
|
|
streamInfo.m_pNewestReadRequestHandle = NULL;
|
|
}
|
|
|
|
if (streamInfo.m_pOldestDecompressHandle)
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::AbortStream_AbortDecompress", GetSystem(), PROFILE_3DENGINE);
|
|
|
|
assert(streamInfo.m_pOldestDecompressHandle != NULL);
|
|
|
|
// Put all handles in the decompress list on the abort list
|
|
assert(streamInfo.m_pNewestDecompressHandle->m_pNext == NULL);
|
|
streamInfo.m_pNewestDecompressHandle->m_pNext = streamInfo.m_pDecompressAbortListHead;
|
|
streamInfo.m_pDecompressAbortListHead = streamInfo.m_pOldestDecompressHandle;
|
|
|
|
// Mark read decompress list as empty
|
|
streamInfo.m_pNewestDecompressHandle = NULL;
|
|
streamInfo.m_pOldestDecompressHandle = NULL;
|
|
}
|
|
|
|
assert(streamInfo.m_pOldestDecompressHandle == NULL
|
|
&& streamInfo.m_pNewestDecompressHandle == NULL);
|
|
|
|
streamInfo.m_numFramesMissed = 0;
|
|
streamInfo.m_bAbort = false;
|
|
streamInfo.m_bLooping = false;
|
|
streamInfo.m_abortCS.Unlock();
|
|
}
|
|
|
|
void CGeomCacheManager::AbortStreamAndWait(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
AbortStream(streamInfo);
|
|
|
|
streamInfo.m_fillRenderNodeJobExecutor.WaitForCompletion();
|
|
|
|
CryMutex dummyCS;
|
|
|
|
// Wait for all read requests to finish
|
|
for (SGeomCacheReadRequestHandle* pCurrentAbortedHandle = streamInfo.m_pReadAbortListHead;
|
|
pCurrentAbortedHandle; pCurrentAbortedHandle = static_cast<SGeomCacheReadRequestHandle*>(pCurrentAbortedHandle->m_pNext))
|
|
{
|
|
CryAutoLock<CryMutex> lock(dummyCS);
|
|
while (pCurrentAbortedHandle->m_numJobReferences > 0)
|
|
{
|
|
pCurrentAbortedHandle->m_jobReferencesCV.Wait(dummyCS);
|
|
}
|
|
|
|
if (pCurrentAbortedHandle->m_pReadStream)
|
|
{
|
|
pCurrentAbortedHandle->m_pReadStream->Wait();
|
|
}
|
|
}
|
|
|
|
for (SGeomCacheBufferHandle* pCurrentAbortedHandle = streamInfo.m_pDecompressAbortListHead;
|
|
pCurrentAbortedHandle; pCurrentAbortedHandle = pCurrentAbortedHandle->m_pNext)
|
|
{
|
|
CryAutoLock<CryMutex> lock(dummyCS);
|
|
while (pCurrentAbortedHandle->m_numJobReferences > 0)
|
|
{
|
|
pCurrentAbortedHandle->m_jobReferencesCV.Wait(dummyCS);
|
|
}
|
|
}
|
|
|
|
// And finally retire their handles
|
|
RetireAbortedHandles(streamInfo);
|
|
|
|
assert(streamInfo.m_pReadAbortListHead == NULL);
|
|
}
|
|
|
|
void CGeomCacheManager::RetireAbortedHandles(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
SGeomCacheReadRequestHandle* pNextReadRequestAbortHandle = NULL;
|
|
for (SGeomCacheReadRequestHandle* pCurrentAbortedHandle = streamInfo.m_pReadAbortListHead;
|
|
pCurrentAbortedHandle; pCurrentAbortedHandle = pNextReadRequestAbortHandle)
|
|
{
|
|
// If jobs are still running, wait till next frame
|
|
if (pCurrentAbortedHandle->m_numJobReferences > 0 || (pCurrentAbortedHandle->m_pReadStream && !pCurrentAbortedHandle->m_pReadStream->IsFinished()))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Remove from abort list
|
|
pNextReadRequestAbortHandle = static_cast<SGeomCacheReadRequestHandle*>(pCurrentAbortedHandle->m_pNext);
|
|
streamInfo.m_pReadAbortListHead = pNextReadRequestAbortHandle;
|
|
|
|
// And finally retire handle
|
|
RetireBufferHandle(pCurrentAbortedHandle);
|
|
}
|
|
|
|
SGeomCacheBufferHandle* pNextDecompressAbortHandle = NULL;
|
|
for (SGeomCacheBufferHandle* pCurrentAbortedHandle = streamInfo.m_pDecompressAbortListHead;
|
|
pCurrentAbortedHandle; pCurrentAbortedHandle = pNextDecompressAbortHandle)
|
|
{
|
|
// If jobs are still running, wait till next frame
|
|
if (pCurrentAbortedHandle->m_numJobReferences > 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Remove from abort list
|
|
pNextDecompressAbortHandle = pCurrentAbortedHandle->m_pNext;
|
|
streamInfo.m_pDecompressAbortListHead = pNextDecompressAbortHandle;
|
|
|
|
// And finally retire handle
|
|
RetireDecompressHandle(streamInfo, pCurrentAbortedHandle);
|
|
}
|
|
}
|
|
|
|
bool CGeomCacheManager::IssueDiskReadRequest(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
// Constants
|
|
const CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
const bool bIsStreaming = pRenderNode->IsStreaming();
|
|
|
|
const float currentCacheStreamingTime = streamInfo.m_pRenderNode->GetStreamingTime();
|
|
const uint wantedFloorFrame = pGeomCache->GetFloorFrameIndex(currentCacheStreamingTime);
|
|
const uint wantedCeilFrame = pGeomCache->GetCeilFrameIndex(currentCacheStreamingTime);
|
|
const float minBufferAheadTime = std::max(0.1f, GetCVars()->e_GeomCacheMinBufferAheadTime);
|
|
const float maxBufferAheadTime = std::max(1.0f, GetCVars()->e_GeomCacheMaxBufferAheadTime);
|
|
const float cacheMinBufferAhead = currentCacheStreamingTime + minBufferAheadTime;
|
|
const float cacheMaxBufferAhead = currentCacheStreamingTime + maxBufferAheadTime;
|
|
|
|
const bool bLooping = streamInfo.m_bLooping;
|
|
const uint numFrames = streamInfo.m_numFrames;
|
|
|
|
const uint preferredDiskRequestSize = (size_t)std::max(0, GetCVars()->e_GeomCachePreferredDiskRequestSize * 1024);
|
|
|
|
// Compute frame range that we want to read from disk
|
|
uint frameRangeBegin = pGeomCache->GetPrevIFrame(wantedFloorFrame);
|
|
uint frameRangeEnd = pGeomCache->GetNextIFrame(bIsStreaming ? pGeomCache->GetCeilFrameIndex(cacheMaxBufferAhead) : wantedFloorFrame);
|
|
|
|
// Avoid reading an entire block if we are not streaming and time is precisely at an index frame.
|
|
// This primarily helps when streaming in the first frame after a render node is created.
|
|
if (!bIsStreaming && (wantedFloorFrame == wantedCeilFrame) &&
|
|
pGeomCache->GetFrameType(wantedFloorFrame) == GeomCacheFile::eFrameType_IFrame)
|
|
{
|
|
frameRangeBegin = wantedFloorFrame;
|
|
frameRangeEnd = frameRangeBegin;
|
|
}
|
|
|
|
// Make sure not to re-request frames that are already in the decode buffer
|
|
if (streamInfo.m_pNewestDecompressHandle)
|
|
{
|
|
uint decodedFramesEnd = streamInfo.m_pNewestDecompressHandle->m_endFrame;
|
|
frameRangeBegin = std::max(decodedFramesEnd + 1, frameRangeBegin);
|
|
}
|
|
|
|
// Read request params and handle to be filled
|
|
StreamReadParams params;
|
|
SGeomCacheReadRequestHandle* pRequestHandle;
|
|
|
|
{
|
|
assert(!streamInfo.m_pOldestReadRequestHandle || streamInfo.m_pNewestReadRequestHandle);
|
|
if (streamInfo.m_pNewestReadRequestHandle)
|
|
{
|
|
uint streamEndFrame = streamInfo.m_pNewestReadRequestHandle->m_endFrame;
|
|
|
|
// Check if we already requested up to frameRangeEnd
|
|
if (streamInfo.m_pNewestReadRequestHandle->m_endFrame >= frameRangeEnd)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
frameRangeBegin = streamEndFrame + 1;
|
|
}
|
|
|
|
const float frameRangeBeginTime = pGeomCache->GetFrameTime(frameRangeBegin);
|
|
if (frameRangeBeginTime > cacheMinBufferAhead)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!bLooping && frameRangeEnd >= (numFrames - 1))
|
|
{
|
|
frameRangeEnd = numFrames - 1;
|
|
}
|
|
|
|
if (frameRangeBegin > frameRangeEnd)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ((frameRangeEnd - frameRangeBegin + 1) > numFrames)
|
|
{
|
|
frameRangeEnd = frameRangeBegin + numFrames - 1;
|
|
}
|
|
|
|
pGeomCache->ValidateReadRange(frameRangeBegin, frameRangeEnd);
|
|
|
|
// Now that we have a final range of unread frames, make a read request
|
|
uint requestSize = 0;
|
|
for (uint currentFrame = frameRangeBegin; currentFrame <= frameRangeEnd; ++currentFrame)
|
|
{
|
|
requestSize += pGeomCache->GetFrameSize(currentFrame);
|
|
|
|
// Stop if size has reached preferred request size and current frame is an index frame
|
|
if (requestSize >= preferredDiskRequestSize && pGeomCache->GetFrameType(currentFrame) == GeomCacheFile::eFrameType_IFrame && frameRangeBegin != currentFrame)
|
|
{
|
|
frameRangeEnd = currentFrame;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(requestSize > 0);
|
|
|
|
// Allocate new request handle & buffer space
|
|
pRequestHandle = NewReadRequestHandle(requestSize, streamInfo);
|
|
if (!pRequestHandle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Init handle
|
|
pRequestHandle->m_startFrame = frameRangeBegin;
|
|
pRequestHandle->m_endFrame = frameRangeEnd;
|
|
|
|
// Add request handle to stream linked list
|
|
if (streamInfo.m_pNewestReadRequestHandle)
|
|
{
|
|
streamInfo.m_pNewestReadRequestHandle->m_pNext = pRequestHandle;
|
|
streamInfo.m_pNewestReadRequestHandle = pRequestHandle;
|
|
}
|
|
else
|
|
{
|
|
streamInfo.m_pOldestReadRequestHandle = pRequestHandle;
|
|
streamInfo.m_pNewestReadRequestHandle = pRequestHandle;
|
|
}
|
|
|
|
const float timeLeft = std::max(pGeomCache->GetFrameTime(frameRangeBegin) - currentCacheStreamingTime
|
|
- static_cast<float>(GetCVars()->e_GeomCacheDecodeAheadTime), 0.0f);
|
|
|
|
// Fill read request params
|
|
params.nOffset = static_cast<uint>(pGeomCache->GetFrameOffset(frameRangeBegin));
|
|
params.nSize = requestSize;
|
|
params.pBuffer = pRequestHandle->m_pBuffer;
|
|
params.ePriority = estpAboveNormal;
|
|
params.nLoadTime = static_cast<uint>(timeLeft * 1000);
|
|
params.nPerceptualImportance = 255;
|
|
params.nFlags = IStreamEngine::FLAGS_NO_SYNC_CALLBACK;
|
|
}
|
|
|
|
// Issue request
|
|
CryInterlockedIncrement(&pRequestHandle->m_numJobReferences);
|
|
pRequestHandle->m_pReadStream = GetSystem()->GetStreamEngine()->StartRead(
|
|
eStreamTaskTypeGeomCache, pGeomCache->GetFilePath(), pRequestHandle, ¶ms);
|
|
|
|
// This can happen if streaming system is already shutting down. There will be no callback in this case, so decrement m_numJobReferences.
|
|
if (pRequestHandle->m_pReadStream == NULL)
|
|
{
|
|
if (CryInterlockedDecrement(&pRequestHandle->m_numJobReferences) == 0)
|
|
{
|
|
pRequestHandle->m_jobReferencesCV.Notify();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CGeomCacheManager::LaunchDecompressJobs(SGeomCacheStreamInfo* pStreamInfo, const CTimeValue currentFrameTime)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
const CGeomCacheRenderNode* pRenderNode = pStreamInfo->m_pRenderNode;
|
|
const CGeomCache* pGeomCache = pStreamInfo->m_pGeomCache;
|
|
const GeomCacheFile::EBlockCompressionFormat blockCompressionFormat = pGeomCache->GetBlockCompressionFormat();
|
|
const float currentCacheStreamingTime = pStreamInfo->m_pRenderNode->GetStreamingTime();
|
|
const uint wantedFloorFrame = pGeomCache->GetFloorFrameIndex(currentCacheStreamingTime);
|
|
const uint numStreamFrames = pStreamInfo->m_numFrames;
|
|
const uint frameDataSize = pStreamInfo->m_frameData.size();
|
|
|
|
// Need to check if there are still jobs running for the same render node on the stream abort list
|
|
for (TStreamInfosIter iter = m_streamInfosAbortList.begin(); iter != m_streamInfosAbortList.end(); ++iter)
|
|
{
|
|
SGeomCacheStreamInfo* pCurrentStreamInfo = *iter;
|
|
if (pStreamInfo->m_pRenderNode == pCurrentStreamInfo->m_pRenderNode)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Wait until abort list has been processed before spawning new jobs
|
|
if (pStreamInfo->m_pDecompressAbortListHead || pStreamInfo->m_pReadAbortListHead)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (SGeomCacheReadRequestHandle* pReadRequestHandle = pStreamInfo->m_pOldestReadRequestHandle; pReadRequestHandle;
|
|
pReadRequestHandle = static_cast<SGeomCacheReadRequestHandle*>(pReadRequestHandle->m_pNext))
|
|
{
|
|
if (pReadRequestHandle->m_frameTime == currentFrameTime)
|
|
{
|
|
// Don't decode frames that were read in the same render frame
|
|
return;
|
|
}
|
|
|
|
if (pStreamInfo->m_bAbort)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pReadRequestHandle->m_error)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pReadRequestHandle->m_state != SGeomCacheReadRequestHandle::eRRHS_FinishedRead)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Stop decoding after e_GeomCacheDecodeAheadTime
|
|
const float blockDeltaFromPlaybackTime = (pGeomCache->GetFrameTime(pReadRequestHandle->m_startFrame) - currentCacheStreamingTime);
|
|
const float decodeAheadTime = GetCVars()->e_GeomCacheDecodeAheadTime;
|
|
if (blockDeltaFromPlaybackTime > decodeAheadTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const uint startFrame = pReadRequestHandle->m_startFrame;
|
|
const uint endFrame = pReadRequestHandle->m_endFrame;
|
|
|
|
// Need to check if stream is already referencing same frames when looping
|
|
if (pStreamInfo->m_pOldestDecompressHandle && pStreamInfo->m_bLooping)
|
|
{
|
|
const uint checkRangeStart = pStreamInfo->m_pOldestDecompressHandle->m_startFrame;
|
|
const uint checkRangeEnd = pStreamInfo->m_pNewestDecompressHandle->m_endFrame;
|
|
|
|
const uint startRangeMod = checkRangeStart % frameDataSize;
|
|
const uint endRangeMod = checkRangeEnd % frameDataSize;
|
|
const uint startFrameMod = startFrame % frameDataSize;
|
|
// Check range must be extended to next index frame because retiring of stream begin could otherwise overwrite frame data
|
|
const uint endFrameMod = pGeomCache->GetNextIFrame(endFrame) % frameDataSize;
|
|
|
|
const bool bRangeWraps = endRangeMod < startRangeMod;
|
|
const bool bFramesWrap = endFrameMod < startFrameMod;
|
|
|
|
// check all four different cases for range overlapping
|
|
if ((bRangeWraps && bFramesWrap)
|
|
|| (bRangeWraps && !bFramesWrap && (startFrameMod <= endRangeMod || endFrameMod >= startRangeMod))
|
|
|| (!bRangeWraps && bFramesWrap && (startRangeMod <= endFrameMod || endRangeMod >= startFrameMod))
|
|
|| (!bRangeWraps && !bFramesWrap && (startFrameMod <= endRangeMod && startRangeMod <= endFrameMod)))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Determine size for decompression buffer
|
|
const uint numFrames = (endFrame - startFrame) + 1;
|
|
const uint32 decompressBlockSize = GeomCacheDecoder::GetDecompressBufferSize(pReadRequestHandle->m_pBuffer, numFrames);
|
|
|
|
SGeomCacheBufferHandle* pNewDecompressBufferHandle = NewBufferHandle<SGeomCacheBufferHandle>(decompressBlockSize, *pStreamInfo);
|
|
if (!pNewDecompressBufferHandle)
|
|
{
|
|
// Could not allocate space for decompression
|
|
return;
|
|
}
|
|
|
|
// Zero frame headers
|
|
memset(pNewDecompressBufferHandle->m_pBuffer, 0, sizeof(SGeomCacheFrameHeader) * numFrames);
|
|
|
|
pReadRequestHandle->m_state = SGeomCacheReadRequestHandle::eRRHS_Decompressing;
|
|
pNewDecompressBufferHandle->m_startFrame = startFrame;
|
|
pNewDecompressBufferHandle->m_endFrame = endFrame;
|
|
|
|
for (uint i = startFrame; i <= endFrame; ++i)
|
|
{
|
|
const uint frameIndex = i % frameDataSize;
|
|
assert(!pStreamInfo->m_frameData[frameIndex].m_pDecompressHandle);
|
|
pStreamInfo->m_frameData[frameIndex].m_pDecompressHandle = pNewDecompressBufferHandle;
|
|
}
|
|
|
|
// Add to decompress request handle linked list
|
|
{
|
|
assert(!pStreamInfo->m_pOldestDecompressHandle || pStreamInfo->m_pNewestDecompressHandle);
|
|
|
|
// Add request handle to stream linked list
|
|
if (pStreamInfo->m_pNewestDecompressHandle)
|
|
{
|
|
pStreamInfo->m_pNewestDecompressHandle->m_pNext = pNewDecompressBufferHandle;
|
|
pStreamInfo->m_pNewestDecompressHandle = pNewDecompressBufferHandle;
|
|
}
|
|
else
|
|
{
|
|
pStreamInfo->m_pOldestDecompressHandle = pNewDecompressBufferHandle;
|
|
pStreamInfo->m_pNewestDecompressHandle = pNewDecompressBufferHandle;
|
|
}
|
|
}
|
|
|
|
for (uint i = 0; i < numFrames; ++i)
|
|
{
|
|
const uint frameIndex = startFrame + i;
|
|
|
|
// For b frames we need to make sure that jobs for previous frames were launched. Otherwise m_numJobReferences will
|
|
// never reach zero, because the frame decode job has a dependency job that was never launched.
|
|
if (pGeomCache->GetFrameType(frameIndex) == GeomCacheFile::eFrameType_IFrame
|
|
|| pStreamInfo->m_frameData[(frameIndex - 1) % frameDataSize].m_bDecompressJobLaunched)
|
|
{
|
|
CryInterlockedIncrement(&pReadRequestHandle->m_numJobReferences);
|
|
CryInterlockedIncrement(&pNewDecompressBufferHandle->m_numJobReferences);
|
|
CryInterlockedIncrement(&pNewDecompressBufferHandle->m_numJobReferences);
|
|
|
|
pStreamInfo->m_frameData[frameIndex % frameDataSize].m_bDecompressJobLaunched = true;
|
|
// Legacy priority - stream
|
|
AZ::Job* job = AZ::CreateJobFunction([this, pStreamInfo, i, pNewDecompressBufferHandle, pReadRequestHandle]() { this->DecompressFrame_JobEntry(pStreamInfo, i, pNewDecompressBufferHandle, pReadRequestHandle); }, true, nullptr);
|
|
job->Start();
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::DecompressFrame_JobEntry(SGeomCacheStreamInfo* pStreamInfo, const uint blockIndex,
|
|
SGeomCacheBufferHandle* pDecompressHandle, SGeomCacheReadRequestHandle* pReadRequestHandle)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
const CGeomCacheRenderNode* pRenderNode = pStreamInfo->m_pRenderNode;
|
|
const CGeomCache* pGeomCache = pStreamInfo->m_pGeomCache;
|
|
const uint frameIndex = pDecompressHandle->m_startFrame + blockIndex;
|
|
|
|
if (!pStreamInfo->m_bAbort && !pStreamInfo->m_pDecompressAbortListHead)
|
|
{
|
|
const GeomCacheFile::EBlockCompressionFormat blockCompressionFormat = pGeomCache->GetBlockCompressionFormat();
|
|
const uint numFrames = pReadRequestHandle->m_endFrame - pReadRequestHandle->m_startFrame + 1;
|
|
|
|
SGeomCacheFrameHeader* pHeader = GetFrameDecompressHeader(pStreamInfo, frameIndex);
|
|
if (!pHeader || pHeader->m_state != SGeomCacheFrameHeader::eFHS_Uninitialized)
|
|
{
|
|
CryFatalError("Trying to access uninitialized data while decoding an index frame");
|
|
}
|
|
|
|
if (!GeomCacheDecoder::DecompressBlocks(blockCompressionFormat, pDecompressHandle->m_pBuffer,
|
|
pReadRequestHandle->m_pBuffer, blockIndex, 1, numFrames))
|
|
{
|
|
// Decompress block size failed: Flag error
|
|
pReadRequestHandle->m_error = 1;
|
|
}
|
|
}
|
|
|
|
if (CryInterlockedDecrement(&pReadRequestHandle->m_numJobReferences) == 0)
|
|
{
|
|
pReadRequestHandle->m_state = SGeomCacheReadRequestHandle::eRRHS_Done;
|
|
pReadRequestHandle->m_jobReferencesCV.Notify();
|
|
}
|
|
|
|
if (CryInterlockedDecrement(&pDecompressHandle->m_numJobReferences) == 0)
|
|
{
|
|
pDecompressHandle->m_jobReferencesCV.Notify();
|
|
}
|
|
|
|
const int newDependencyCounter = CryInterlockedDecrement(GetDependencyCounter(pStreamInfo, frameIndex));
|
|
|
|
if (newDependencyCounter < 0 || newDependencyCounter > 2)
|
|
{
|
|
CryFatalError("Invalid dependency counter");
|
|
}
|
|
else if (newDependencyCounter == 0)
|
|
{
|
|
SDecodeFrameJobData jobData;
|
|
jobData.m_frameIndex = frameIndex;
|
|
jobData.m_pGeomCache = pGeomCache;
|
|
jobData.m_pStreamInfo = pStreamInfo;
|
|
LaunchDecodeJob(jobData);
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::LaunchDecodeJob(SDecodeFrameJobData jobData)
|
|
{
|
|
const GeomCacheFile::EFrameType frameType = jobData.m_pGeomCache->GetFrameType(jobData.m_frameIndex);
|
|
|
|
switch (frameType)
|
|
{
|
|
case GeomCacheFile::eFrameType_IFrame:
|
|
{
|
|
// Legacy priority - stream
|
|
AZ::Job* job = AZ::CreateJobFunction([this, jobData]() { this->DecodeIFrame_JobEntry(jobData); }, true);
|
|
job->Start();
|
|
break;
|
|
}
|
|
case GeomCacheFile::eFrameType_BFrame:
|
|
{
|
|
// Legacy priority - stream
|
|
AZ::Job* job = AZ::CreateJobFunction([this, jobData]() { this->DecodeBFrame_JobEntry(jobData); }, true);
|
|
job->Start();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::DecodeIFrame_JobEntry(SDecodeFrameJobData jobData)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
if (!jobData.m_pStreamInfo->m_bAbort && !jobData.m_pStreamInfo->m_pDecompressAbortListHead)
|
|
{
|
|
char* pFrameData = GetFrameDecompressData(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
GeomCacheDecoder::DecodeIFrame(jobData.m_pGeomCache, pFrameData);
|
|
|
|
SGeomCacheFrameHeader* pHeader = GetFrameDecompressHeader(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
|
|
if (pHeader->m_state != SGeomCacheFrameHeader::eFHS_Undecoded)
|
|
{
|
|
CryFatalError("Trying to access uninitialized data while decoding an index frame");
|
|
}
|
|
|
|
pHeader->m_state = SGeomCacheFrameHeader::eFHS_Decoded;
|
|
}
|
|
|
|
SGeomCacheBufferHandle* pHandle = GetFrameDecompressHandle(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
if (CryInterlockedDecrement(&pHandle->m_numJobReferences) == 0)
|
|
{
|
|
pHandle->m_jobReferencesCV.Notify();
|
|
}
|
|
|
|
// Decrement dependency counter of first b frame after previous index frame and launch job if ready
|
|
const uint prevIFrame = jobData.m_pGeomCache->GetPrevIFrame(jobData.m_frameIndex);
|
|
if (prevIFrame < jobData.m_frameIndex)
|
|
{
|
|
const uint bFrameIndex = prevIFrame + 1;
|
|
if (jobData.m_pGeomCache->GetFrameType(bFrameIndex) == GeomCacheFile::eFrameType_BFrame)
|
|
{
|
|
const int newDependencyCounter = CryInterlockedDecrement(GetDependencyCounter(jobData.m_pStreamInfo, bFrameIndex));
|
|
|
|
if (newDependencyCounter < 0 || newDependencyCounter > 2)
|
|
{
|
|
CryFatalError("Invalid dependency counter");
|
|
}
|
|
else if (newDependencyCounter == 0)
|
|
{
|
|
SDecodeFrameJobData bFrameJobState = jobData;
|
|
bFrameJobState.m_frameIndex = bFrameIndex;
|
|
LaunchDecodeJob(bFrameJobState);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decrement dependency counter of b frame right after index frame and launch job if ready
|
|
const uint numFrames = jobData.m_pStreamInfo->m_numFrames;
|
|
if ((jobData.m_frameIndex % numFrames) + 1 < numFrames)
|
|
{
|
|
const uint bFrameIndex = jobData.m_frameIndex + 1;
|
|
if (jobData.m_pGeomCache->GetFrameType(bFrameIndex) == GeomCacheFile::eFrameType_BFrame)
|
|
{
|
|
const int newDependencyCounter = CryInterlockedDecrement(GetDependencyCounter(jobData.m_pStreamInfo, bFrameIndex));
|
|
|
|
if (newDependencyCounter < 0 || newDependencyCounter > 2)
|
|
{
|
|
CryFatalError("Invalid dependency counter");
|
|
}
|
|
else if (newDependencyCounter == 0)
|
|
{
|
|
SDecodeFrameJobData bFrameJobState = jobData;
|
|
bFrameJobState.m_frameIndex = bFrameIndex;
|
|
LaunchDecodeJob(bFrameJobState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::DecodeBFrame_JobEntry(SDecodeFrameJobData jobData)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
const uint prevIFrame = jobData.m_pGeomCache->GetPrevIFrame(jobData.m_frameIndex);
|
|
const uint nextIFrame = jobData.m_pGeomCache->GetNextIFrame(jobData.m_frameIndex);
|
|
|
|
if (!jobData.m_pStreamInfo->m_bAbort && !jobData.m_pStreamInfo->m_pDecompressAbortListHead)
|
|
{
|
|
char* pFrameData = GetFrameDecompressData(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
|
|
// For frames that have 0 influence for motion the predictor will still read data
|
|
// from the prev frame pointers, so just set it to the current frame's data.
|
|
char* pPrevFramesData[2] = { pFrameData, pFrameData };
|
|
if (jobData.m_pGeomCache->NeedsPrevFrames(jobData.m_frameIndex))
|
|
{
|
|
pPrevFramesData[0] = GetFrameDecompressData(jobData.m_pStreamInfo, jobData.m_frameIndex - 2);
|
|
pPrevFramesData[1] = GetFrameDecompressData(jobData.m_pStreamInfo, jobData.m_frameIndex - 1);
|
|
}
|
|
|
|
SGeomCacheFrameHeader* pPrevIFrameHeader = GetFrameDecompressHeader(jobData.m_pStreamInfo, prevIFrame);
|
|
SGeomCacheFrameHeader* pNextIFrameHeader = GetFrameDecompressHeader(jobData.m_pStreamInfo, nextIFrame);
|
|
|
|
if (pPrevIFrameHeader->m_state != SGeomCacheFrameHeader::eFHS_Decoded
|
|
|| pNextIFrameHeader->m_state != SGeomCacheFrameHeader::eFHS_Decoded)
|
|
{
|
|
CryFatalError("Trying to access invalid data while decoding a b frame");
|
|
}
|
|
|
|
char* pFloorIndexFrameData = GetFrameDecompressData(jobData.m_pStreamInfo, prevIFrame);
|
|
char* pCeilIndexFrameData = GetFrameDecompressData(jobData.m_pStreamInfo, nextIFrame);
|
|
|
|
GeomCacheDecoder::DecodeBFrame(jobData.m_pGeomCache, pFrameData, pPrevFramesData, pFloorIndexFrameData, pCeilIndexFrameData);
|
|
|
|
SGeomCacheFrameHeader* pHeader = GetFrameDecompressHeader(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
|
|
if (pHeader->m_state != SGeomCacheFrameHeader::eFHS_Undecoded)
|
|
{
|
|
CryFatalError("Trying to access invalid data while decoding a b frame");
|
|
}
|
|
|
|
pHeader->m_state = SGeomCacheFrameHeader::eFHS_Decoded;
|
|
}
|
|
|
|
SGeomCacheBufferHandle* pHandle = GetFrameDecompressHandle(jobData.m_pStreamInfo, jobData.m_frameIndex);
|
|
if (CryInterlockedDecrement(&pHandle->m_numJobReferences) == 0)
|
|
{
|
|
pHandle->m_jobReferencesCV.Notify();
|
|
}
|
|
|
|
// Decrement dependency counter of b frame right after frame and launch job if ready
|
|
const uint numFrames = jobData.m_pStreamInfo->m_numFrames;
|
|
if ((jobData.m_frameIndex % numFrames) + 1 < numFrames)
|
|
{
|
|
const uint bFrameIndex = jobData.m_frameIndex + 1;
|
|
if (jobData.m_pGeomCache->GetFrameType(bFrameIndex) == GeomCacheFile::eFrameType_BFrame)
|
|
{
|
|
const int newDependencyCounter = CryInterlockedDecrement(GetDependencyCounter(jobData.m_pStreamInfo, bFrameIndex));
|
|
|
|
if (newDependencyCounter < 0 || newDependencyCounter > 2)
|
|
{
|
|
CryFatalError("Invalid dependency counter");
|
|
}
|
|
else if (newDependencyCounter == 0)
|
|
{
|
|
SDecodeFrameJobData bFrameJobState = jobData;
|
|
bFrameJobState.m_frameIndex = bFrameIndex;
|
|
LaunchDecodeJob(bFrameJobState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::FillRenderNodeAsync_JobEntry(SGeomCacheStreamInfo* pStreamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
// Prevent the stream from aborting while filling the render buffer
|
|
CryAutoLock<CryCriticalSection> abortLock(pStreamInfo->m_abortCS);
|
|
|
|
CGeomCacheRenderNode* pRenderNode = pStreamInfo->m_pRenderNode;
|
|
const CGeomCache* pGeomCache = pStreamInfo->m_pGeomCache;
|
|
|
|
if (pStreamInfo->m_bAbort || pStreamInfo->m_pDecompressAbortListHead)
|
|
{
|
|
// End job if abort flag was raised
|
|
pRenderNode->SkipFrameFill();
|
|
return;
|
|
}
|
|
|
|
const uint floorPlaybackFrame = pStreamInfo->m_wantedFloorFrame;
|
|
const uint ceilPlaybackFrame = pStreamInfo->m_wantedCeilFrame;
|
|
bool bFrameFilled = false;
|
|
|
|
const char* pFloorFrameData = NULL;
|
|
const char* pCeilFrameData = NULL;
|
|
|
|
if (!pGeomCache->PlaybackFromMemory())
|
|
{
|
|
SGeomCacheFrameHeader* pFloorHeader = GetFrameDecompressHeader(pStreamInfo, floorPlaybackFrame);
|
|
SGeomCacheFrameHeader* pCeilHeader = GetFrameDecompressHeader(pStreamInfo, ceilPlaybackFrame);
|
|
|
|
if (pFloorHeader && pFloorHeader->m_state == SGeomCacheFrameHeader::eFHS_Decoded && pCeilHeader && pCeilHeader->m_state == SGeomCacheFrameHeader::eFHS_Decoded)
|
|
{
|
|
pFloorFrameData = GetFrameDecompressData(pStreamInfo, floorPlaybackFrame);
|
|
pCeilFrameData = GetFrameDecompressData(pStreamInfo, ceilPlaybackFrame);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pFloorFrameData = pGeomCache->GetFrameData(floorPlaybackFrame % pGeomCache->GetNumFrames());
|
|
pCeilFrameData = pGeomCache->GetFrameData(ceilPlaybackFrame % pGeomCache->GetNumFrames());
|
|
}
|
|
|
|
if (pFloorFrameData && pCeilFrameData)
|
|
{
|
|
const float floorFrameTime = pGeomCache->GetFrameTime(floorPlaybackFrame);
|
|
const float ceilFrameTime = pGeomCache->GetFrameTime(ceilPlaybackFrame);
|
|
const float wantedPlaybackTime = pStreamInfo->m_wantedPlaybackTime;
|
|
|
|
assert(wantedPlaybackTime >= floorFrameTime && wantedPlaybackTime <= ceilFrameTime);
|
|
|
|
float lerpFactor = 0.0f;
|
|
if (ceilFrameTime != floorFrameTime)
|
|
{
|
|
lerpFactor = (wantedPlaybackTime - floorFrameTime) / (ceilFrameTime - floorFrameTime);
|
|
}
|
|
|
|
assert(lerpFactor >= 0.0f && lerpFactor <= 1.0f);
|
|
|
|
// m_displayedFrameTime == -1.0f means uninitialized cache. Treat as same frame, because we only want to fill twice on load.
|
|
const bool bSameFrame = (pStreamInfo->m_displayedFrameTime == pStreamInfo->m_wantedPlaybackTime) || (pStreamInfo->m_displayedFrameTime == -1.0f);
|
|
|
|
if (bSameFrame)
|
|
{
|
|
CryInterlockedIncrement(&pStreamInfo->m_sameFrameFillCount);
|
|
}
|
|
else
|
|
{
|
|
pStreamInfo->m_sameFrameFillCount = 0;
|
|
}
|
|
|
|
if (!pRenderNode->FillFrameAsync(pFloorFrameData, pCeilFrameData, lerpFactor))
|
|
{
|
|
pRenderNode->SkipFrameFill();
|
|
}
|
|
|
|
pStreamInfo->m_displayedFrameTime = pStreamInfo->m_wantedPlaybackTime;
|
|
bFrameFilled = true;
|
|
}
|
|
else
|
|
{
|
|
pRenderNode->SkipFrameFill();
|
|
}
|
|
|
|
if (!bFrameFilled && pRenderNode->IsStreaming())
|
|
{
|
|
// If this happens, then we don't have the necessary data in the decompression
|
|
// buffer and the geom cache render node wasn't updated
|
|
++pStreamInfo->m_numFramesMissed;
|
|
++m_numMissedFrames;
|
|
}
|
|
}
|
|
|
|
template<class TBufferHandleType>
|
|
TBufferHandleType* CGeomCacheManager::NewBufferHandle(const uint32 size, SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
if (gEnv->mMainThreadId != CryGetCurrentThreadId())
|
|
{
|
|
CryFatalError("CGeomCacheManager::NewBufferHandle must be called from main thread");
|
|
}
|
|
|
|
char* pBlock = NULL;
|
|
|
|
size_t pointerSize = (sizeof(SGeomCacheBufferHandle*) + 15) & ~15;
|
|
|
|
if (!gEnv->IsDedicated())
|
|
{
|
|
// Try to allocate requested size in the buffer, otherwise return NULL
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::NewBufferHandle_Malloc", GetSystem(), PROFILE_3DENGINE);
|
|
pBlock = reinterpret_cast<char*>(m_pPool->Memalign(16, pointerSize + size, "geom cache block"));
|
|
}
|
|
}
|
|
|
|
if (!pBlock)
|
|
{
|
|
++m_numFailedAllocs;
|
|
return NULL;
|
|
}
|
|
|
|
// Remove from free list
|
|
TBufferHandleType* pNewRequest = new TBufferHandleType();
|
|
|
|
// Initialize and return request handle
|
|
*alias_cast<TBufferHandleType**>(pBlock) = pNewRequest;
|
|
pNewRequest->m_pBuffer = pBlock + pointerSize;
|
|
pNewRequest->m_bufferSize = size;
|
|
pNewRequest->m_frameTime = GetTimer()->GetFrameStartTime();
|
|
pNewRequest->m_pStream = &streamInfo;
|
|
|
|
return pNewRequest;
|
|
}
|
|
|
|
SGeomCacheReadRequestHandle* CGeomCacheManager::NewReadRequestHandle(const uint32 size, SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
SGeomCacheBufferHandle* pNewHandle = NewBufferHandle<SGeomCacheReadRequestHandle>(size, streamInfo);
|
|
SGeomCacheReadRequestHandle* pReadRequestHandle = static_cast<SGeomCacheReadRequestHandle*>(pNewHandle);
|
|
|
|
if (pReadRequestHandle)
|
|
{
|
|
pReadRequestHandle->m_state = SGeomCacheReadRequestHandle::eRRHS_Reading;
|
|
pReadRequestHandle->m_error = 0L;
|
|
pReadRequestHandle->m_pReadStream = NULL;
|
|
}
|
|
|
|
return pReadRequestHandle;
|
|
}
|
|
|
|
void CGeomCacheManager::RetireHandles(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
const CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
const float currentCacheStreamingTime = streamInfo.m_pRenderNode->GetStreamingTime();
|
|
const uint wantedFloorFrame = pGeomCache->GetFloorFrameIndex(currentCacheStreamingTime);
|
|
|
|
SGeomCacheBufferHandle* pNextReadRequestHandle = streamInfo.m_pOldestReadRequestHandle;
|
|
while (pNextReadRequestHandle)
|
|
{
|
|
SGeomCacheReadRequestHandle* pCurrentReadRequestHandle = static_cast<SGeomCacheReadRequestHandle*>(pNextReadRequestHandle);
|
|
pNextReadRequestHandle = pCurrentReadRequestHandle->m_pNext;
|
|
|
|
const uint handleEndFrame = pCurrentReadRequestHandle->m_endFrame;
|
|
|
|
if (pCurrentReadRequestHandle->m_numJobReferences == 0 &&
|
|
((handleEndFrame + 2) < wantedFloorFrame || pCurrentReadRequestHandle->m_state == SGeomCacheReadRequestHandle::eRRHS_Done))
|
|
{
|
|
RetireOldestReadRequestHandle(streamInfo);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
SGeomCacheBufferHandle* pNextDecompressHandle = streamInfo.m_pOldestDecompressHandle;
|
|
while (pNextDecompressHandle)
|
|
{
|
|
SGeomCacheBufferHandle* pCurrentDecompressHandle = pNextDecompressHandle;
|
|
pNextDecompressHandle = pCurrentDecompressHandle->m_pNext;
|
|
|
|
const uint handleEndFrame = pCurrentDecompressHandle->m_endFrame;
|
|
|
|
// We also wait for the jobs of the next frame because of b frame -> index frame back references
|
|
if (((handleEndFrame + 2) < wantedFloorFrame) && (pCurrentDecompressHandle->m_numJobReferences == 0)
|
|
&& !(pCurrentDecompressHandle->m_pNext && pCurrentDecompressHandle->m_pNext->m_numJobReferences > 0))
|
|
{
|
|
RetireOldestDecompressHandle(streamInfo);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::RetireOldestReadRequestHandle(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
SGeomCacheReadRequestHandle* pOldestHandle;
|
|
|
|
pOldestHandle = streamInfo.m_pOldestReadRequestHandle;
|
|
|
|
if (pOldestHandle == streamInfo.m_pNewestReadRequestHandle)
|
|
{
|
|
assert(pOldestHandle->m_pNext == NULL);
|
|
streamInfo.m_pOldestReadRequestHandle = NULL;
|
|
streamInfo.m_pNewestReadRequestHandle = NULL;
|
|
}
|
|
else
|
|
{
|
|
streamInfo.m_pOldestReadRequestHandle = static_cast<SGeomCacheReadRequestHandle*>(pOldestHandle->m_pNext);
|
|
}
|
|
|
|
assert(pOldestHandle);
|
|
if (pOldestHandle)
|
|
{
|
|
if (pOldestHandle->m_numJobReferences > 0)
|
|
{
|
|
CryFatalError("Trying to retire handle with non zero job count");
|
|
}
|
|
|
|
RetireBufferHandle(pOldestHandle);
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::RetireOldestDecompressHandle(SGeomCacheStreamInfo& streamInfo)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
SGeomCacheBufferHandle* pOldestHandle = NULL;
|
|
|
|
// Unlink from stream linked list
|
|
{
|
|
pOldestHandle = streamInfo.m_pOldestDecompressHandle;
|
|
|
|
assert(pOldestHandle);
|
|
if (!pOldestHandle)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pOldestHandle == streamInfo.m_pNewestDecompressHandle)
|
|
{
|
|
assert(pOldestHandle->m_pNext == NULL);
|
|
streamInfo.m_pOldestDecompressHandle = NULL;
|
|
streamInfo.m_pNewestDecompressHandle = NULL;
|
|
}
|
|
else
|
|
{
|
|
streamInfo.m_pOldestDecompressHandle = pOldestHandle->m_pNext;
|
|
}
|
|
}
|
|
|
|
if (pOldestHandle)
|
|
{
|
|
RetireDecompressHandle(streamInfo, pOldestHandle);
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::RetireDecompressHandle(SGeomCacheStreamInfo& streamInfo, SGeomCacheBufferHandle* pHandle)
|
|
{
|
|
CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
|
|
const uint frameDataSize = streamInfo.m_frameData.size();
|
|
uint reinitializeStart = pGeomCache->GetPrevIFrame(pHandle->m_startFrame) % frameDataSize;
|
|
uint reinitializeEnd = pHandle->m_endFrame % frameDataSize;
|
|
|
|
if (!pHandle->m_pNext && (reinitializeEnd != (frameDataSize - 1)))
|
|
{
|
|
// If this was the last handle in stream or the next handle start frame is not right after the current handles end frame
|
|
// we need to reinitialize until the next index frame because index frame jobs will decrement dependency
|
|
// counters from b frames ahead of time.
|
|
|
|
reinitializeEnd = pGeomCache->GetNextIFrame(reinitializeEnd) + 1;
|
|
}
|
|
|
|
ReinitializeStreamFrameData(streamInfo, reinitializeStart, reinitializeEnd);
|
|
RetireBufferHandle(pHandle);
|
|
}
|
|
|
|
template<class TBufferHandleType>
|
|
void CGeomCacheManager::RetireBufferHandle(TBufferHandleType* pHandle)
|
|
{
|
|
FUNCTION_PROFILER_3DENGINE;
|
|
|
|
if (gEnv->mMainThreadId != CryGetCurrentThreadId())
|
|
{
|
|
CryFatalError("CGeomCacheManager::RetireBufferHandle must be called from main thread");
|
|
}
|
|
|
|
if (pHandle->m_numJobReferences)
|
|
{
|
|
CryFatalError("Trying to retire handle with jobs still running");
|
|
}
|
|
|
|
{
|
|
FRAME_PROFILER("CGeomCacheManager::RetireBufferHandle_Free", GetSystem(), PROFILE_3DENGINE);
|
|
size_t pointerSize = (sizeof(SGeomCacheBufferHandle*) + 15) & ~15;
|
|
m_pPool->Free(pHandle->m_pBuffer - pointerSize);
|
|
}
|
|
|
|
delete pHandle;
|
|
}
|
|
|
|
float CGeomCacheManager::GetPrecachedTime(const IGeomCacheRenderNode* pRenderNode)
|
|
{
|
|
assert(gEnv->mMainThreadId == CryGetCurrentThreadId());
|
|
|
|
for (uint i = 0; i < m_streamInfos.size(); ++i)
|
|
{
|
|
SGeomCacheStreamInfo& streamInfo = *m_streamInfos[i];
|
|
if (streamInfo.m_pRenderNode == pRenderNode)
|
|
{
|
|
const float playbackTime = pRenderNode->GetPlaybackTime();
|
|
|
|
if (streamInfo.m_pNewestDecompressHandle)
|
|
{
|
|
CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
|
|
const uint frame = streamInfo.m_pNewestDecompressHandle->m_startFrame;
|
|
const float frameTime = pGeomCache->GetFrameTime(frame);
|
|
|
|
if (playbackTime <= frameTime)
|
|
{
|
|
return frameTime - playbackTime;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
SGeomCacheBufferHandle* CGeomCacheManager::GetFrameDecompressHandle(SGeomCacheStreamInfo* pStreamInfo, const uint frameIndex)
|
|
{
|
|
const uint frameDataSize = pStreamInfo->m_frameData.size();
|
|
return pStreamInfo->m_frameData[frameIndex % frameDataSize].m_pDecompressHandle;
|
|
}
|
|
|
|
SGeomCacheFrameHeader* CGeomCacheManager::GetFrameDecompressHeader(SGeomCacheStreamInfo* pStreamInfo, const uint frameIndex)
|
|
{
|
|
SGeomCacheBufferHandle* pHandle = GetFrameDecompressHandle(pStreamInfo, frameIndex);
|
|
|
|
if (!pHandle)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
//If the start frame is greater than the frame index
|
|
//we're going to crash because frameOffset will overflow and
|
|
//access pHandle->pBuffer beyond its limits
|
|
//This can happen if the user sets the start frame too high
|
|
if (pHandle->m_startFrame > frameIndex)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const uint frameOffset = frameIndex - pHandle->m_startFrame;
|
|
|
|
SGeomCacheFrameHeader* pHeader = reinterpret_cast<SGeomCacheFrameHeader*>(
|
|
pHandle->m_pBuffer + (frameOffset * sizeof(SGeomCacheFrameHeader)));
|
|
return pHeader;
|
|
}
|
|
|
|
char* CGeomCacheManager::GetFrameDecompressData(SGeomCacheStreamInfo* pStreamInfo, const uint frameIndex)
|
|
{
|
|
SGeomCacheFrameHeader* pHeader = GetFrameDecompressHeader(pStreamInfo, frameIndex);
|
|
SGeomCacheBufferHandle* pHandle = GetFrameDecompressHandle(pStreamInfo, frameIndex);
|
|
|
|
if (!pHandle || !pHeader)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return pHandle->m_pBuffer + pHeader->m_offset;
|
|
}
|
|
|
|
int* CGeomCacheManager::GetDependencyCounter(SGeomCacheStreamInfo* pStreamInfo, const uint frameIndex)
|
|
{
|
|
const uint frameDataSize = pStreamInfo->m_frameData.size();
|
|
return &pStreamInfo->m_frameData[frameIndex % frameDataSize].m_decodeDependencyCounter;
|
|
}
|
|
|
|
#ifndef _RELEASE
|
|
namespace
|
|
{
|
|
void Draw2DBox(float x, float y, float width, float height, const ColorB& color, float screenHeight, float screenWidth, IRenderAuxGeom* pAuxRenderer)
|
|
{
|
|
float position[4][2] = {
|
|
{ x - 1.0f, y - 1.0f},
|
|
{ x - 1.0f, y + height + 1.0f},
|
|
{ x + width + 1.0f, y + height + 1.0f},
|
|
{ x + width + 1.0f, y - 1.0f}
|
|
};
|
|
|
|
Vec3 positions[4] = {
|
|
Vec3(position[0][0] / screenWidth, position[0][1] / screenHeight, 0.0f),
|
|
Vec3(position[1][0] / screenWidth, position[1][1] / screenHeight, 0.0f),
|
|
Vec3(position[2][0] / screenWidth, position[2][1] / screenHeight, 0.0f),
|
|
Vec3(position[3][0] / screenWidth, position[3][1] / screenHeight, 0.0f)
|
|
};
|
|
|
|
vtx_idx const indices[6] = { 0, 1, 2, 0, 2, 3 };
|
|
|
|
pAuxRenderer->DrawTriangles(positions, 4, indices, 6, color);
|
|
}
|
|
|
|
void Draw2DBoxOutLine(float x, float y, float width, float height, const ColorB& color, float screenHeight, float screenWidth, IRenderAuxGeom* pAuxRenderer)
|
|
{
|
|
float position[4][2] = {
|
|
{ x - 1.0f, y - 1.0f},
|
|
{ x - 1.0f, y + height + 1.0f},
|
|
{ x + width + 1.0f, y + height + 1.0f},
|
|
{ x + width + 1.0f, y - 1.0f}
|
|
};
|
|
|
|
Vec3 positions[4] = {
|
|
Vec3(position[0][0] / screenWidth, position[0][1] / screenHeight, 0.0f),
|
|
Vec3(position[1][0] / screenWidth, position[1][1] / screenHeight, 0.0f),
|
|
Vec3(position[2][0] / screenWidth, position[2][1] / screenHeight, 0.0f),
|
|
Vec3(position[3][0] / screenWidth, position[3][1] / screenHeight, 0.0f)
|
|
};
|
|
|
|
|
|
pAuxRenderer->DrawLine(positions[0], color, positions[1], color);
|
|
pAuxRenderer->DrawLine(positions[1], color, positions[2], color);
|
|
pAuxRenderer->DrawLine(positions[2], color, positions[3], color);
|
|
pAuxRenderer->DrawLine(positions[3], color, positions[0], color);
|
|
}
|
|
|
|
void DrawStream(const char* pBase, const size_t poolSize, const SGeomCacheBufferHandle* pFirstHandle, const ColorF& color, const float boxLeft, const float boxTop,
|
|
const float boxWidth, const float boxHeight, const float screenWidth, const float screenHeight, IRenderAuxGeom* pRenderAuxGeom)
|
|
{
|
|
const float bufferSize = (float)poolSize;
|
|
for (const SGeomCacheBufferHandle* pCurrentHandle = pFirstHandle; pCurrentHandle; pCurrentHandle = pCurrentHandle->m_pNext)
|
|
{
|
|
ColorF blockColor = color;
|
|
|
|
const float offset = (float)(pCurrentHandle->m_pBuffer - pBase);
|
|
const float size = (float)pCurrentHandle->m_bufferSize;
|
|
|
|
const float left = boxWidth * (offset / bufferSize);
|
|
const float width = boxWidth * (size / bufferSize);
|
|
|
|
Draw2DBox(boxLeft + left + 1, boxTop + 1, width - 3, boxHeight - 2, blockColor, screenHeight, screenWidth, pRenderAuxGeom);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGeomCacheManager::DrawDebugInfo()
|
|
{
|
|
IRenderAuxGeom* const pRenderAuxGeom = gEnv->pRenderer->GetIRenderAuxGeom();
|
|
|
|
const SAuxGeomRenderFlags oldFlags = pRenderAuxGeom->GetRenderFlags();
|
|
|
|
SAuxGeomRenderFlags flags(e_Def2DPublicRenderflags);
|
|
flags.SetDepthTestFlag(e_DepthTestOff);
|
|
flags.SetDepthWriteFlag(e_DepthWriteOff);
|
|
flags.SetCullMode(e_CullModeNone);
|
|
flags.SetAlphaBlendMode(e_AlphaNone);
|
|
pRenderAuxGeom->SetRenderFlags(flags);
|
|
|
|
const float screenHeight = (float)gEnv->pRenderer->GetHeight();
|
|
const float screenWidth = (float)gEnv->pRenderer->GetWidth();
|
|
|
|
const float topOffset = screenHeight * 0.01f;
|
|
const float sideOffset = screenWidth * 0.01f;
|
|
|
|
const float bufferBoxTop = 2.0f + 8.0f * topOffset;
|
|
const float bufferBoxHeight = screenHeight * 0.05f;
|
|
const float bufferBoxLeft = sideOffset;
|
|
const float bufferBoxWidth = screenWidth * 0.5f;
|
|
|
|
float streamInfosTop = bufferBoxTop + bufferBoxHeight + 2.0f * topOffset;
|
|
const float streamInfoSpacing = 20.0f;
|
|
const float streamInfoBoxSize = 10.0f;
|
|
|
|
uint numActiveStreams = 0;
|
|
uint numFramesMissed = 0;
|
|
|
|
const uint kNumColors = 8;
|
|
ColorF colors[kNumColors] = { Col_Red, Col_Green, Col_Yellow, Col_Blue, Col_Aquamarine, Col_Thistle, Col_Tan, Col_Salmon };
|
|
|
|
uint colorIndex = 0;
|
|
const uint numStreams = m_streamInfos.size();
|
|
for (uint i = 0; i < numStreams; ++i)
|
|
{
|
|
SGeomCacheStreamInfo& streamInfo = *m_streamInfos[i];
|
|
CGeomCacheRenderNode* pRenderNode = streamInfo.m_pRenderNode;
|
|
CGeomCache* pGeomCache = streamInfo.m_pGeomCache;
|
|
const char* pName = streamInfo.m_pRenderNode->GetName();
|
|
const char* pFilter = GetCVars()->e_GeomCacheDebugFilter->GetString();
|
|
|
|
const bool bDisplay = ((GetCVars()->e_GeomCacheDebug != 2) || (pRenderNode->IsStreaming() || streamInfo.m_pOldestDecompressHandle || streamInfo.m_pNewestReadRequestHandle))
|
|
&& strstr(pName, GetCVars()->e_GeomCacheDebugFilter->GetString()) != NULL;
|
|
|
|
if (bDisplay)
|
|
{
|
|
ColorF& color = colors[colorIndex % kNumColors];
|
|
|
|
DrawStream(reinterpret_cast<char*>(m_pPoolBaseAddress), m_poolSize, streamInfo.m_pOldestDecompressHandle, color, bufferBoxLeft, bufferBoxTop,
|
|
bufferBoxWidth, bufferBoxHeight, screenWidth, screenHeight, pRenderAuxGeom);
|
|
DrawStream(reinterpret_cast<char*>(m_pPoolBaseAddress), m_poolSize, streamInfo.m_pDecompressAbortListHead, color, bufferBoxLeft, bufferBoxTop,
|
|
bufferBoxWidth, bufferBoxHeight, screenWidth, screenHeight, pRenderAuxGeom);
|
|
DrawStream(reinterpret_cast<char*>(m_pPoolBaseAddress), m_poolSize, streamInfo.m_pOldestReadRequestHandle, color, bufferBoxLeft, bufferBoxTop,
|
|
bufferBoxWidth, bufferBoxHeight, screenWidth, screenHeight, pRenderAuxGeom);
|
|
DrawStream(reinterpret_cast<char*>(m_pPoolBaseAddress), m_poolSize, streamInfo.m_pReadAbortListHead, color, bufferBoxLeft, bufferBoxTop,
|
|
bufferBoxWidth, bufferBoxHeight, screenWidth, screenHeight, pRenderAuxGeom);
|
|
|
|
const float currentTop = streamInfosTop + streamInfoSpacing * 2.5f * numActiveStreams;
|
|
Draw2DBox(sideOffset, currentTop, streamInfoBoxSize, streamInfoBoxSize, color, screenHeight, screenWidth, pRenderAuxGeom);
|
|
|
|
const float wantedPlaybackTime = streamInfo.m_wantedPlaybackTime;
|
|
const uint wantedFloorFrame = streamInfo.m_wantedFloorFrame;
|
|
const uint wantedCeilFrame = streamInfo.m_wantedCeilFrame;
|
|
const int oldestDiskFrame = streamInfo.m_pOldestReadRequestHandle ? streamInfo.m_pOldestReadRequestHandle->m_startFrame : -1;
|
|
const int newestDiskFrame = streamInfo.m_pNewestReadRequestHandle ? streamInfo.m_pNewestReadRequestHandle->m_endFrame : -1;
|
|
const int oldestDecompressFrame = streamInfo.m_pOldestDecompressHandle ? streamInfo.m_pOldestDecompressHandle->m_startFrame : -1;
|
|
const int newestDecompressFrame = streamInfo.m_pNewestDecompressHandle ? streamInfo.m_pNewestDecompressHandle->m_endFrame : -1;
|
|
|
|
IGeomCache::SStatistics stats = pGeomCache->GetStatistics();
|
|
|
|
const char* pCompressionMethod = "Store";
|
|
if (pGeomCache->GetBlockCompressionFormat() == GeomCacheFile::eBlockCompressionFormat_Deflate)
|
|
{
|
|
pCompressionMethod = "Deflate";
|
|
}
|
|
else if (pGeomCache->GetBlockCompressionFormat() == GeomCacheFile::eBlockCompressionFormat_LZ4HC)
|
|
{
|
|
pCompressionMethod = "LZ4 HC";
|
|
}
|
|
else if (pGeomCache->GetBlockCompressionFormat() == GeomCacheFile::eBlockCompressionFormat_ZSTD)
|
|
{
|
|
pCompressionMethod = "ZSTD";
|
|
}
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset + streamInfoSpacing, currentTop - 5.0f, 1.5f, Col_White, false, "%s - %.3gs %s- %.3g MiB/s - %s - %d frames missed",
|
|
streamInfo.m_pRenderNode->GetName(), pGeomCache->GetDuration(), streamInfo.m_bLooping ? "looping " : "", stats.m_averageAnimationDataRate, pCompressionMethod, streamInfo.m_numFramesMissed);
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset + streamInfoSpacing, streamInfoSpacing + currentTop - 5.0f, 1.5f, Col_White, false,
|
|
"Frame: [%04u, %04u], Disk Frames: [%04u, %04u], Decompress Frames: [%04u, %04u], Playback time: %g", wantedFloorFrame, wantedCeilFrame,
|
|
oldestDiskFrame, newestDiskFrame, oldestDecompressFrame, newestDecompressFrame, wantedPlaybackTime);
|
|
|
|
++numActiveStreams;
|
|
++colorIndex;
|
|
}
|
|
}
|
|
|
|
const uint numAbortedStreams = m_streamInfosAbortList.size();
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset, topOffset, 1.5f, Col_Yellow, false, "%u Geometry Cache(s) active", numActiveStreams);
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset, 3.5f * topOffset, 1.5f, (m_numMissedFrames > 0) ? Col_Red : Col_Green, false, "%u Frames missed", m_numMissedFrames);
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset + 160.0f, 3.5f * topOffset, 1.5f, (m_numStreamAborts > 0) ? Col_Red : Col_Green, false, "%u Stream aborts (err: %u, decomp: %u, read: %u)",
|
|
m_numStreamAborts, m_numErrorAborts, m_numDecompressStreamAborts, m_numReadStreamAborts);
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset + 520.0f, 3.5f * topOffset, 1.5f, (m_numFailedAllocs > 0) ? Col_Yellow : Col_Green, false, "%u Failed alloc(s)", m_numFailedAllocs);
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset + 670.0f, 3.5f * topOffset, 1.5f, (numAbortedStreams > 0) ? Col_Yellow : Col_Green, false, "%u Aborted stream(s)", numAbortedStreams);
|
|
|
|
gEnv->pRenderer->Draw2dLabel(sideOffset, 6.0f * topOffset, 1.25f, Col_White, false, "Geom Cache Buffer:");
|
|
Draw2DBoxOutLine(bufferBoxLeft, bufferBoxTop, bufferBoxWidth, bufferBoxHeight, Col_White, screenHeight, screenWidth, pRenderAuxGeom);
|
|
|
|
pRenderAuxGeom->SetRenderFlags(oldFlags);
|
|
}
|
|
#endif
|
|
|
|
#endif
|