Files
UnrealEngine/Engine/Source/Runtime/OpenGLDrv/Private/OpenGLProgramBinaryFileCache.cpp
2025-05-18 13:04:45 +08:00

1086 lines
41 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
OpenGLProgramBinaryFileCache.cpp: OpenGL program binary file cache stores/loads a set of binary ogl programs.
=============================================================================*/
#include "OpenGLProgramBinaryFileCache.h"
#include "OpenGLShaders.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/FileManager.h"
#include "Misc/Paths.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/MemoryReader.h"
#include "OpenGLDrvPrivate.h"
#include "Shader.h"
#include "GlobalShader.h"
#include "SceneUtils.h"
#include "PsoLruCache.h"
#include "OpenGLBinaryProgramUtils.h"
#include <Serialization/StaticMemoryReader.h>
#include "ProfilingDebugging/ScopedTimers.h"
#include <Async/MappedFileHandle.h>
#if PLATFORM_ANDROID
#include "Android/AndroidPlatformMisc.h"
#endif
static bool GMemoryMapGLProgramCache = true;
static FAutoConsoleVariableRef CVarMemoryMapGLProgramCache(
TEXT("r.OpenGL.MemoryMapGLProgramCache"),
GMemoryMapGLProgramCache,
TEXT("If true enabled memory mapping of the GL program binary cache. (default)\n")
TEXT("If false then upon opening the binary cache all programs are loaded into memory.\n")
TEXT("When enabled this can reduce RSS pressure when combined with program LRU. (see r.OpenGL.EnableProgramLRUCache).")
,
ECVF_ReadOnly | ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> FOpenGLProgramBinaryCache::CVarPBCEnable(
TEXT("r.ProgramBinaryCache.Enable"),
#if PLATFORM_ANDROID
1, // Enabled by default on Android.
#else
0,
#endif
TEXT("If true, enables binary program cache. Enabled by default only on Android"),
ECVF_ReadOnly | ECVF_RenderThreadSafe
);
TAutoConsoleVariable<int32> FOpenGLProgramBinaryCache::CVarRestartAndroidAfterPrecompile(
TEXT("r.ProgramBinaryCache.RestartAndroidAfterPrecompile"),
0,
TEXT("If true, Android apps will restart after precompiling the binary program cache."),
ECVF_ReadOnly | ECVF_RenderThreadSafe
);
static int32 GMaxBinaryProgramLoadTimeMS = 3;
static FAutoConsoleVariableRef CVarMaxBinaryProgramLoadTime(
TEXT("r.OpenGL.MaxBinaryProgramLoadTime"),
GMaxBinaryProgramLoadTimeMS,
TEXT("The maximum time per frame to transfer programs from the binary program cache to the GL RHI. in milliseconds.\n")
TEXT("default 3ms. Note: Driver compile time for programs may exceed this limit if you're not using the LRU."),
ECVF_RenderThreadSafe
);
static int32 GBinaryCachePeriodicFlushProgramCount = 20;
static FAutoConsoleVariableRef CVarBinaryCachePeriodicFlushProgramCount(
TEXT("r.OpenGL.BinaryCachePeriodicFlushProgramCount"),
GBinaryCachePeriodicFlushProgramCount,
TEXT("When r.PSOPrecaching is active this value\n")
TEXT("is the number of appended programs to accumulate before the cache is flushed to storage."),
ECVF_RenderThreadSafe
);
static int32 GBinaryCacheMMapAfterEveryMB = 50;
static FAutoConsoleVariableRef CVarBinaryCacheMMapAfterEveryMB(
TEXT("r.OpenGL.BinaryCacheMMapAfterEveryMB"),
GBinaryCacheMMapAfterEveryMB,
TEXT("When r.PSOPrecaching is active this value\n")
TEXT("specifies the size program binary cache can grow before it is memory mapped, the mmapped programs replace the allocated programs and potentially frees up memory for unused precached programs."),
ECVF_RenderThreadSafe
);
static int32 GBinaryCacheMaxPermittedSizeMB = 350;
static FAutoConsoleVariableRef CVarBinaryCacheMaxPermittedSizeMB(
TEXT("r.OpenGL.BinaryCacheMaxPermittedSize"),
GBinaryCacheMaxPermittedSizeMB,
TEXT("When r.PSOPrecaching is active and the binary cache's size is greater\n")
TEXT("than this value the cache will be deleted at startup. The precaching cache is rebuilt from empty."),
ECVF_RenderThreadSafe
);
namespace UE
{
namespace OpenGL
{
bool CanMemoryMapGLProgramCache()
{
return FPlatformProperties::SupportsMemoryMappedFiles() && GMemoryMapGLProgramCache;
}
extern void OnGLProgramLoadedFromBinaryCache(const FOpenGLProgramKey& ProgramKey, TUniqueObj<FOpenGLProgramBinary>&& ProgramBinaryData);
bool AreBinaryProgramsCompressed()
{
static const auto StoreCompressedBinariesCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.OpenGL.StoreCompressedProgramBinaries"));
return StoreCompressedBinariesCVar->GetInt() != 0;
}
static const uint32 GBinaryProgramFileVersion = 6;
struct FBinaryCacheFileHeader
{
uint32 Version = 0xFFFFFFFF;
FGuid BinaryCacheGuid;
bool bCacheUsesCompressedBinaries;
uint32 ProgramCount = 0;
uint32 ValidSize = 0;
static FBinaryCacheFileHeader CreateHeader(const FGuid& BinaryCacheGuidIn, uint32 NumPrograms, uint32 ValidSize)
{
FBinaryCacheFileHeader NewHeader;
NewHeader.Version = GBinaryProgramFileVersion;
NewHeader.BinaryCacheGuid = BinaryCacheGuidIn;
NewHeader.bCacheUsesCompressedBinaries = UE::OpenGL::AreBinaryProgramsCompressed();
NewHeader.ProgramCount = NumPrograms;
NewHeader.ValidSize = ValidSize;
return NewHeader;
}
friend FArchive& operator<<(FArchive& Ar, FBinaryCacheFileHeader& Header)
{
Ar << Header.Version;
check(Ar.IsLoading() || Header.IsValidVersion()); // This should always be correct when saving.
if (Header.IsValidVersion())
{
Ar << Header.BinaryCacheGuid;
Ar << Header.bCacheUsesCompressedBinaries;
Ar << Header.ProgramCount;
Ar << Header.ValidSize;
}
return Ar;
}
bool IsValidVersion() const
{
return Version == GBinaryProgramFileVersion;
}
bool IsValid(const FGuid* OptionalWantedGuid) const
{
return IsValidVersion()
&& (UE::OpenGL::AreBinaryProgramsCompressed() == bCacheUsesCompressedBinaries)
&& (OptionalWantedGuid == nullptr || (*OptionalWantedGuid == BinaryCacheGuid))
&& (ProgramCount > 0);
}
FString ToString() const
{
return FString::Format(TEXT("{0}, {1}, {2}, {3}"), { Version, bCacheUsesCompressedBinaries, *BinaryCacheGuid.ToString(), ProgramCount });
}
};
}
}
// This contains the mapping for a binary program cache file.
// It also contains a list of programs that the cache contains.
class FOpenGLProgramBinaryMapping : public FThreadSafeRefCountedObject
{
public:
FOpenGLProgramBinaryMapping(TUniquePtr<IMappedFileHandle> MappedCacheFileIn, uint32 ProgramCountIfKnown) : MappedCacheFile(MoveTemp(MappedCacheFileIn)) { Content.Reserve(ProgramCountIfKnown); }
TArrayView<const uint8> GetView(uint32 FileOffset, uint32 NumBytes) const
{
check(FileOffset >= CurrentMappingRegionOffset);
uint32 OffsetWithinMapping = FileOffset - CurrentMappingRegionOffset;
check(OffsetWithinMapping + NumBytes <= GetCurrentMappedRegion()->GetMappedSize());
return TArrayView<const uint8>(GetCurrentMappedRegion()->GetMappedPtr() + OffsetWithinMapping, NumBytes);
}
void AddMapping(uint32 MappingRegionOffset, TUniquePtr<IMappedFileRegion> NewMappedRegionIn)
{
check(MappingRegionOffset != 0 || MappedRegions.IsEmpty());
CurrentMappingRegionOffset = MappingRegionOffset;
MappedRegions.Add(MoveTemp(NewMappedRegionIn));
};
void AddProgramKey(const class FOpenGLProgramKey& KeyIn) { check(!Content.Contains(KeyIn)); Content.Add(KeyIn); }
bool HasValidMapping() const { return MappedCacheFile.IsValid() && !MappedRegions.IsEmpty() && MappedRegions.Last().IsValid(); }
int32 NumPrograms() const { return Content.Num(); }
TUniquePtr<IMappedFileHandle>& GetMappedCacheFile() { return MappedCacheFile; }
TUniquePtr<IMappedFileRegion>& GetCurrentMappedRegion()
{
check(!MappedRegions.IsEmpty());
return MappedRegions.Last();
};
const TUniquePtr<IMappedFileRegion>& GetCurrentMappedRegion() const
{
check(!MappedRegions.IsEmpty());
return MappedRegions.Last();
};
private:
TUniquePtr<IMappedFileHandle> MappedCacheFile;
TArray<TUniquePtr<IMappedFileRegion>> MappedRegions;
uint32 CurrentMappingRegionOffset = 0;
TSet<FOpenGLProgramKey> Content;
};
static FCriticalSection GProgramBinaryFileCacheCS;
// guards the container that collects scanned programs and send to RHIT
static FCriticalSection GPendingGLProgramCreateRequestsCS;
FOpenGLProgramBinaryCache* FOpenGLProgramBinaryCache::CachePtr = nullptr;
FOpenGLProgramBinaryCache::FOpenGLProgramBinaryCache(const FString& InCachePathRoot)
: CachePathRoot(InCachePathRoot)
, BinaryCacheWriteFileHandle(nullptr)
, CurrentBinaryFileState(EBinaryFileState::Uninitialized)
{
ANSICHAR* GLVersion = (ANSICHAR*)glGetString(GL_VERSION);
ANSICHAR* GLRenderer = (ANSICHAR*)glGetString(GL_RENDERER);
FString HashString;
HashString.Append(GLVersion);
HashString.Append(GLRenderer);
#if PLATFORM_ANDROID
// FORT-512259:
// Apparently we can't rely on GL_VERSION alone to assume binary compatibility.
// Some devices report binary compatibility errors after minor OS updates even though the GL driver version has not changed.
const FString BuildNumber = FAndroidMisc::GetDeviceBuildNumber();
HashString.Append(BuildNumber);
HashString.Append(AndroidEGL::GetInstance()->IsUsingRobustContext() ? TEXT("ROBUST") : TEXT("NRB"));
// Optional configrule variable for triggering a rebuild of the cache.
const FString* ConfigRulesGLProgramKey = FAndroidMisc::GetConfigRulesVariable(TEXT("OpenGLProgramCacheKey"));
if (ConfigRulesGLProgramKey && !ConfigRulesGLProgramKey->IsEmpty())
{
HashString.Append(*ConfigRulesGLProgramKey);
}
#endif
FSHAHash VersionHash;
FSHA1::HashBuffer(TCHAR_TO_ANSI(*HashString), HashString.Len(), VersionHash.Hash);
CacheSubDir = LegacyShaderPlatformToShaderFormat(GMaxRHIShaderPlatform).ToString() + TEXT("_") + VersionHash.ToString();
// delete anything from the binary program root that does not match the device string.
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
TArray<FString> FoundFiles;
IFileManager::Get().FindFiles(FoundFiles, *(CachePathRoot/TEXT("*")), true, true);
for(FString& FoundFile : FoundFiles)
{
const FString FullPath = (CachePathRoot / FoundFile);
const bool bIsDir = PlatformFile.DirectoryExists(*FullPath);
if (FoundFile != CacheSubDir || !bIsDir)
{
bool bSuccess;
if(bIsDir)
{
bSuccess = PlatformFile.DeleteDirectoryRecursively(*FullPath);
}
else
{
bSuccess = PlatformFile.DeleteFile(*FullPath);
}
UE_LOG(LogRHI, Verbose, TEXT("FOpenGLProgramBinaryCache Deleting %s %s"), bIsDir ? TEXT("dir") : TEXT("file"), *FullPath );
UE_CLOG(!bSuccess, LogRHI, Warning, TEXT("FOpenGLProgramBinaryCache Failed to delete %s"), *FullPath);
}
}
}
FOpenGLProgramBinaryCache::~FOpenGLProgramBinaryCache()
{
#if PLATFORM_ANDROID
if (FAndroidOpenGL::AreRemoteCompileServicesActive())
{
FAndroidOpenGL::StopRemoteCompileServices();
}
#endif
if (BinaryCacheWriteFileHandle)
{
delete BinaryCacheWriteFileHandle;
}
if (OnShaderPipelineCacheOpenedDelegate.IsValid())
{
FShaderPipelineCache::GetCacheOpenedDelegate().Remove(OnShaderPipelineCacheOpenedDelegate);
}
if (OnShaderPipelineCachePrecompilationCompleteDelegate.IsValid())
{
FShaderPipelineCache::GetPrecompilationCompleteDelegate().Remove(OnShaderPipelineCachePrecompilationCompleteDelegate);
}
};
bool FOpenGLProgramBinaryCache::IsEnabled()
{
return CachePtr != nullptr;
}
bool FOpenGLProgramBinaryCache::IsBuildingCache()
{
if (CachePtr != nullptr)
{
return CachePtr->IsBuildingCache_internal();
}
return false;
}
extern bool IsPrecachingEnabled();
void FOpenGLProgramBinaryCache::Initialize()
{
check(CachePtr == nullptr);
if (CVarPBCEnable.GetValueOnAnyThread() == 0)
{
UE_LOG(LogRHI, Log, TEXT("FOpenGLProgramBinaryCache disabled by r.ProgramBinaryCache.Enable=0"));
return;
}
if (!FOpenGL::SupportsProgramBinary())
{
UE_LOG(LogRHI, Warning, TEXT("FOpenGLProgramBinaryCache disabled as devices does not support program binaries"));
return;
}
#if PLATFORM_ANDROID
if (FOpenGL::HasBinaryProgramRetrievalFailed())
{
if (FOpenGL::SupportsProgramBinary())
{
UE_LOG(LogRHI, Warning, TEXT("FOpenGLProgramBinaryCache: Device has failed to emit program binary despite SupportsProgramBinary == true. Disabling binary cache."));
return;
}
}
#endif
FString CacheFolderPathRoot;
FString OldCacheFolderPathRoot;
#if PLATFORM_ANDROID && USE_ANDROID_FILE
// @todo Lumin: Use that GetPathForExternalWrite or something?
extern FString GExternalFilePath;
OldCacheFolderPathRoot = GExternalFilePath / TEXT("ProgramBinaryCache");
CacheFolderPathRoot = GExternalFilePath / TEXT("RHICache") / TEXT("ProgramBinaryCache");
#else
OldCacheFolderPathRoot = FPaths::ProjectSavedDir() / TEXT("ProgramBinaryCache");
CacheFolderPathRoot = FPaths::ProjectSavedDir() / TEXT("RHICache") / TEXT("ProgramBinaryCache");
#endif
// Remove entire ProgramBinaryCache folder if -ClearOpenGLBinaryProgramCache is specified on command line
if (FParse::Param(FCommandLine::Get(), TEXT("ClearOpenGLBinaryProgramCache")))
{
UE_LOG(LogRHI, Log, TEXT("ClearOpenGLBinaryProgramCache specified, deleting binary program cache folder: %s"), *CacheFolderPathRoot);
FPlatformFileManager::Get().GetPlatformFile().DeleteDirectoryRecursively(*CacheFolderPathRoot);
}
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*OldCacheFolderPathRoot))
{
UE_LOG(LogRHI, Log, TEXT("Moving program binary cache: %s -> %s"), *OldCacheFolderPathRoot, *CacheFolderPathRoot);
// Note: have to copy and delete as TManagedStoragePlatformFile prevents moving of directories.
// FPlatformFileManager::Get().GetPlatformFile().MoveFile(*CacheFolderPathRoot, *OldCacheFolderPathRoot);
FPlatformFileManager::Get().GetPlatformFile().CopyDirectoryTree(*CacheFolderPathRoot, *OldCacheFolderPathRoot, false);
FPlatformFileManager::Get().GetPlatformFile().DeleteDirectoryRecursively(*OldCacheFolderPathRoot);
}
CachePtr = new FOpenGLProgramBinaryCache(CacheFolderPathRoot);
UE_LOG(LogRHI, Log, TEXT("Enabling program binary cache dir at %s"), *CachePtr->GetProgramBinaryCacheDir());
if (IsPrecachingEnabled())
{
CachePtr->InitPrecaching();
}
else
{
// Add delegates for the ShaderPipelineCache precompile.
UE_LOG(LogRHI, Log, TEXT("FOpenGLProgramBinaryCache will be initialized when ShaderPipelineCache opens its file"));
CachePtr->OnShaderPipelineCacheOpenedDelegate = FShaderPipelineCache::GetCacheOpenedDelegate().AddRaw(CachePtr, &FOpenGLProgramBinaryCache::OnShaderPipelineCacheOpened);
CachePtr->OnShaderPipelineCachePrecompilationCompleteDelegate = FShaderPipelineCache::GetPrecompilationCompleteDelegate().AddRaw(CachePtr, &FOpenGLProgramBinaryCache::OnShaderPipelineCachePrecompilationComplete);
}
}
#if PLATFORM_ANDROID
static int32 GNumRemoteProgramCompileServices = 4;
static FAutoConsoleVariableRef CVarNumRemoteProgramCompileServices(
TEXT("Android.OpenGL.NumRemoteProgramCompileServices"),
GNumRemoteProgramCompileServices,
TEXT("The number of separate processes to make available to compile opengl programs.\n")
TEXT("0 to disable use of separate processes to precompile Programs\n")
TEXT("valid range is 1-8 (4 default).")
,
ECVF_RenderThreadSafe | ECVF_ReadOnly
);
#endif
namespace UE
{
namespace OpenGL
{
static bool IsBinaryCacheValid(const FString& CachePath, const FGuid* OptionalGuidCheck, bool bLogWhenInvalid, UE::OpenGL::FBinaryCacheFileHeader* HeaderOUT)
{
TUniquePtr<FArchive> BinaryProgramReader = nullptr;
BinaryProgramReader = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*CachePath));
if (BinaryProgramReader.IsValid())
{
FArchive& Ar = *BinaryProgramReader;
UE::OpenGL::FBinaryCacheFileHeader BinaryCacheHeader;
if (Ar.TotalSize() > 0)
{
Ar << BinaryCacheHeader;
}
BinaryProgramReader->Close();
if (BinaryCacheHeader.IsValid(OptionalGuidCheck))
{
if (HeaderOUT)
{
*HeaderOUT = BinaryCacheHeader;
}
// we could validate file content.
return true;
}
UE_CLOG(bLogWhenInvalid, LogRHI, Warning, TEXT("FOpenGLProgramBinaryCache: %s is an invalid binary cache (%s)"), *CachePath, *BinaryCacheHeader.ToString());
}
else
{
UE_CLOG(bLogWhenInvalid, LogRHI, Warning, TEXT("FOpenGLProgramBinaryCache: %s not found."), *CachePath);
}
return false;
}
}
}
FShaderPipelineCache::FShaderCachePrecompileContext GShaderCachePrecompileContext;
void FOpenGLProgramBinaryCache::InitPrecaching()
{
UE_LOG(LogRHI, Log, TEXT("FOpenGLProgramBinaryCache: using precache system."));
FScopeLock Lock(&GProgramBinaryFileCacheCS);
const FString PreCacheName(TEXT("PrecacheBinaries"));
const FString RootDir = CachePathRoot / CacheSubDir;
TArray<FString> CacheFileNames;
IFileManager::Get().FindFiles(CacheFileNames, *RootDir, TEXT("*"));
for (FString& Filename : CacheFileNames)
{
Filename = RootDir / Filename;
}
// remove old caches (such as those used by the PSO file cache) or precache caches that are oversized.
UE::OpenGL::FBinaryCacheFileHeader PrecacheBinaryHeader;
for (const FString& CacheFileName : CacheFileNames)
{
FString CacheName = FPaths::GetBaseFilename(CacheFileName);
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FFileStatData CacheStatData = PlatformFile.GetStatData(*CacheFileName);
bool bDeleteMe;
if (CacheName.Equals(PreCacheName) && CacheStatData.FileSize <= GBinaryCacheMaxPermittedSizeMB * 1024 * 1024)
{
bDeleteMe = !UE::OpenGL::IsBinaryCacheValid(CacheFileName, nullptr, true, &PrecacheBinaryHeader);
}
else
{
bDeleteMe = true;
}
if (bDeleteMe)
{
UE_LOG(LogRHI, Log, TEXT("Deleting binary cache file %s (%" INT64_FMT " bytes)"), *CacheFileName, CacheStatData.FileSize);
PlatformFile.DeleteFile(*CacheFileName);
}
}
// Use the existing cache's guid to load it.
FGuid CacheGuid = PrecacheBinaryHeader.IsValid(nullptr) ? PrecacheBinaryHeader.BinaryCacheGuid : FGuid::NewGuid();
GShaderCachePrecompileContext = FShaderPipelineCache::FShaderCachePrecompileContext(PreCacheName);
UE_LOG(LogRHI, Log, TEXT("InitPrecaching : Beginning new cache = %s"), *GShaderCachePrecompileContext.GetCacheName());
OnShaderPipelineCacheOpened(
PreCacheName,
GetFeatureLevelShaderPlatform(FOpenGL::GetFeatureLevel()),
1, // count must be > 0. 0 sized caches are ignored.
CacheGuid,
GShaderCachePrecompileContext);
}
// if the file has sufficiently grown flush the program cache and mmap the extra programs.
void FOpenGLProgramBinaryCache::UpdatePrecacheMapping()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLUpdatePrecacheMapping);
// TODO: avoid this lock.
FScopeLock Lock(&GProgramBinaryFileCacheCS);
FArchive& DestAr = *BinaryCacheWriteFileHandle;
int64 DestPos = DestAr.Tell();
if (DestPos >= CurrentShaderPipelineProperties.LastMappedPosition + (GBinaryCacheMMapAfterEveryMB * 1024 * 1024))
{
int32 ProgramCount = ProgramsInCurrentCache.Num();
MarkValidContent(ProgramCount);
UE_LOG(LogRHI, Log, TEXT("UpdatePrecacheMapping : Mapping program cache from %" INT64_FMT " to %" INT64_FMT " "), CurrentShaderPipelineProperties.LastMappedPosition, DestPos);
TUniquePtr<FArchive> BinaryProgramReader = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*GetProgramBinaryCacheFilePath()));
BinaryProgramReader->Seek(CurrentShaderPipelineProperties.LastMappedPosition);
int32 ProgramsToRead = ProgramCount - CurrentShaderPipelineProperties.MappedPrograms;
bool bSuccess = ReadProgramFile_Internal(ProgramsToRead, DestPos, GetProgramBinaryCacheFilePath(), *BinaryProgramReader);
CurrentShaderPipelineProperties.MappedPrograms = ProgramCount;
check(bSuccess);
}
}
void FOpenGLProgramBinaryCache::OnShaderPipelineCacheOpened(FString const& Name, EShaderPlatform Platform, uint32 Count, const FGuid& VersionGuid, FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext)
{
FScopeLock Lock(&GProgramBinaryFileCacheCS);
checkf(CurrentShaderPipelineProperties.CacheVersionGuid == FGuid(), TEXT("OGL: OnShaderPipelineCacheOpened, previous PSO cache %s (%s) has not completed!"), *CurrentShaderPipelineProperties.PipelineCacheName, *CurrentShaderPipelineProperties.CacheVersionGuid.ToString());
CurrentShaderPipelineProperties.CacheVersionGuid = VersionGuid;
CurrentShaderPipelineProperties.PipelineCacheName = ShaderCachePrecompileContext.GetCacheName();
if (Count == 0)
{
check(CurrentBinaryFileState == EBinaryFileState::Uninitialized);
UE_LOG(LogRHI, Verbose, TEXT("OnShaderPipelineCacheOpened, Ignoring empty PSO cache. %s (%s)"), *CurrentShaderPipelineProperties.PipelineCacheName, *CurrentShaderPipelineProperties.CacheVersionGuid.ToString());
return;
}
UE_LOG(LogRHI, Log, TEXT("Scanning Binary program cache, using Shader Pipeline Cache %s (%s)"), *CurrentShaderPipelineProperties.PipelineCacheName, *CurrentShaderPipelineProperties.CacheVersionGuid.ToString());
ScanProgramCacheFile();
if (IsBuildingCache_internal())
{
#if PLATFORM_ANDROID
if (GNumRemoteProgramCompileServices)
{
check(!FAndroidOpenGL::AreRemoteCompileServicesActive() || IsPrecachingEnabled());
if(FAndroidOpenGL::AreRemoteCompileServicesActive() == false)
{
FAndroidOpenGL::StartRemoteCompileServices(GNumRemoteProgramCompileServices);
}
}
#endif
ShaderCachePrecompileContext.SetPrecompilationIsSlowTask();
}
}
void FOpenGLProgramBinaryCache::Reset()
{
check(!BinaryCacheWriteFileHandle);
CurrentBinaryFileState = EBinaryFileState::Uninitialized;
ProgramsInCurrentCache.Empty();
}
void FOpenGLProgramBinaryCache::OnShaderPipelineCachePrecompilationComplete(uint32 Count, double Seconds, const FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLOnShaderPipelineCachePrecompilationComplete);
FScopeLock Lock(&GProgramBinaryFileCacheCS);
// We discard the cache if 0 entries were recorded:
// if 0 programs were cached when Count >0, then we suffer a performance penalty for invoking the services for no reason.
const bool bIsBuildingCache = IsBuildingCache_internal();
TRefCountPtr<FOpenGLProgramBinaryMapping>*FoundCache = MappedCacheFiles.Find(CurrentShaderPipelineProperties.CacheVersionGuid);
const int32 ProgramsInLoadedCache = FoundCache ? (*FoundCache)->NumPrograms() : 0;
const int32 ProgramsCached = bIsBuildingCache ? ProgramsInCurrentCache.Num() : ProgramsInLoadedCache;
check(!bIsBuildingCache || Count); // we always start cache building if the count>0
check(bIsBuildingCache || Count == 0 || CurrentBinaryFileState == EBinaryFileState::ValidCacheFile);
const TCHAR* CacheStatusText = bIsBuildingCache ?
(ProgramsCached == 0 ? TEXT("empty cache discarded") : TEXT("cache built"))
:
(Count == 0 ? TEXT("ignored empty cache") : TEXT("cache loaded"));
UE_LOG(LogRHI, Log, TEXT("OnShaderPipelineCachePrecompilationComplete: %s(%s) - %s %d program binaries (%d requested)"), *CurrentShaderPipelineProperties.PipelineCacheName, *CurrentShaderPipelineProperties.CacheVersionGuid.ToString(), CacheStatusText, ProgramsCached, Count );
if (bIsBuildingCache)
{
#if PLATFORM_ANDROID
if ( !IsPrecachingEnabled() && FAndroidOpenGL::AreRemoteCompileServicesActive())
{
FAndroidOpenGL::StopRemoteCompileServices();
}
#endif
const bool bSuccess = CloseCacheWriteHandle(ProgramsInCurrentCache.Num());
if(bSuccess && CVarRestartAndroidAfterPrecompile.GetValueOnAnyThread() == 1)
{
#if PLATFORM_ANDROID
FAndroidMisc::bNeedsRestartAfterPSOPrecompile = true;
#if USE_ANDROID_JNI
extern void AndroidThunkCpp_RestartApplication(const FString & IntentString);
AndroidThunkCpp_RestartApplication(TEXT(""));
#endif
#endif
}
Reset();
if(bSuccess)
{
ScanProgramCacheFile();
check(!IsBuildingCache_internal());
if (IsBuildingCache_internal())
{
UE_LOG( LogRHI, Error, TEXT("Failed to load just completed cache! : %s(%s)"), *CurrentShaderPipelineProperties.PipelineCacheName, *CurrentShaderPipelineProperties.CacheVersionGuid.ToString());
// The cache we've just written is invalid. This is extremely unlikely.
CloseCacheWriteHandle(ProgramsInCurrentCache.Num());
}
}
}
// unset the completed cache.
Reset();
CurrentShaderPipelineProperties.CacheVersionGuid = FGuid();
CurrentShaderPipelineProperties.PipelineCacheName.Reset();
CurrentShaderPipelineProperties.LastMappedPosition = 0;
CurrentShaderPipelineProperties.MappedPrograms = 0;
CurrentShaderPipelineProperties.NumProgramsFlushed = 0;
}
TRefCountPtr<FOpenGLProgramBinaryMapping> FOpenGLProgramBinaryCache::GetOrAddFileMapping(const FString& ProgramCacheFilename, int32 ProgramCount, int64 Offset, int64 Size)
{
TRefCountPtr<FOpenGLProgramBinaryMapping> CurrentMapping;
if (!UE::OpenGL::CanMemoryMapGLProgramCache())
{
return CurrentMapping;
}
TRefCountPtr<FOpenGLProgramBinaryMapping>* FoundMapping = MappedCacheFiles.Find(CurrentShaderPipelineProperties.CacheVersionGuid);
if( FoundMapping == nullptr )
{
TUniquePtr<IMappedFileHandle> MappedCacheFile;
if(UE::OpenGL::CanMemoryMapGLProgramCache())
{
FOpenMappedResult Result = FPlatformFileManager::Get().GetPlatformFile().OpenMappedEx(*ProgramCacheFilename);
MappedCacheFile = Result.HasError() ? nullptr : Result.StealValue();
}
CurrentMapping = new FOpenGLProgramBinaryMapping(MoveTemp(MappedCacheFile), ProgramCount);
MappedCacheFiles.Add(CurrentShaderPipelineProperties.CacheVersionGuid, CurrentMapping);
}
else
{
CurrentMapping = *FoundMapping;
}
if(UE::OpenGL::CanMemoryMapGLProgramCache())
{
TUniquePtr<IMappedFileHandle>& MappedCacheFile = CurrentMapping->GetMappedCacheFile();
// add the new mapping
if (ensure(MappedCacheFile.IsValid()))
{
check(Size);
TUniquePtr<IMappedFileRegion> MappedRegion = TUniquePtr<IMappedFileRegion>(MappedCacheFile->MapRegion(Offset, Size));
check(MappedRegion.IsValid());
CurrentMapping->AddMapping(Offset, MoveTemp(MappedRegion));
}
}
return CurrentMapping;
}
// Scan the binary cache file and build a record of all programs.
void FOpenGLProgramBinaryCache::ScanProgramCacheFile()
{
//FScopedDurationTimeLogger Timer(TEXT("ScanProgramCacheFile"));
UE_LOG(LogRHI, Log, TEXT("OnShaderScanProgramCacheFile"));
FString ProgramCacheFilename = GetProgramBinaryCacheFilePath();
FString ProgramCacheFilenameTemp = ProgramCacheFilename + TEXT(".scan");
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
check(CurrentBinaryFileState == EBinaryFileState::Uninitialized);
check(ProgramsInCurrentCache.IsEmpty());
bool bBinaryFileIsValid = false;
// Try to move the file to a temporary filename before the scan, so we won't try to read it again if it's corrupted
PlatformFile.DeleteFile(*ProgramCacheFilenameTemp);
PlatformFile.MoveFile(*ProgramCacheFilenameTemp, *ProgramCacheFilename);
TUniquePtr<FArchive> BinaryProgramReader = nullptr;
BinaryProgramReader = TUniquePtr<FArchive>(IFileManager::Get().CreateFileReader(*ProgramCacheFilenameTemp));
if (!BinaryProgramReader)
{
UE_LOG(LogRHI, Log, TEXT("OnShaderScanProgramCacheFile : %s was not found, recreating."), *ProgramCacheFilename);
}
else
{
UE::OpenGL::FBinaryCacheFileHeader BinaryCacheHeader;
FArchive& Ar = *BinaryProgramReader;
if (Ar.TotalSize() > 0)
{
Ar << BinaryCacheHeader;
}
bBinaryFileIsValid = BinaryCacheHeader.IsValid(&CurrentShaderPipelineProperties.CacheVersionGuid);
if (!bBinaryFileIsValid)
{
UE_LOG(LogRHI, Warning, TEXT("ScanProgramCacheFile - invalid binary cache file encountered (%s, %d). Rebuilding binary program cache."), *BinaryCacheHeader.ToString(), Ar.TotalSize());
BinaryProgramReader->Close();
PlatformFile.DeleteFile(*ProgramCacheFilenameTemp);
}
else
{
UE_LOG(LogRHI, Warning, TEXT("ScanProgramCacheFile - reading binary cache, Programs %d, ValidSize: %d, TotalSize %" INT64_FMT), BinaryCacheHeader.ProgramCount, BinaryCacheHeader.ValidSize, Ar.TotalSize());
ProgramsInCurrentCache.Reserve(BinaryCacheHeader.ProgramCount);
bool bSuccess = ReadProgramFile_Internal(BinaryCacheHeader.ProgramCount, BinaryCacheHeader.ValidSize, ProgramCacheFilenameTemp, Ar);
check(bSuccess);
}
}
if (!bBinaryFileIsValid)
{
if (OpenCacheWriteHandle(GetProgramBinaryCacheFilePath(), false))
{
CurrentBinaryFileState = EBinaryFileState::BuildingCacheFile;
// save header, 0 program count indicates an unfinished file.
// The header is overwritten at the end of the process.
UE::OpenGL::FBinaryCacheFileHeader OutHeader = UE::OpenGL::FBinaryCacheFileHeader::CreateHeader(CurrentShaderPipelineProperties.CacheVersionGuid, 0, 0);
FArchive& Ar = *BinaryCacheWriteFileHandle;
Ar << OutHeader;
CurrentShaderPipelineProperties.LastMappedPosition = Ar.Tell();
}
else
{
// Binary cache file cannot be used, failed to open output file.
CurrentBinaryFileState = EBinaryFileState::Uninitialized;
RHIGetPanicDelegate().ExecuteIfBound(FName("FailedBinaryProgramArchiveOpen"));
UE_LOG(LogRHI, Fatal, TEXT("ScanProgramCacheFile - Failed to open binary cache."));
}
}
else
{
uint32 LastReadPos = BinaryProgramReader->Tell();
BinaryProgramReader->Close();
// Rename the file back after a successful scan.
PlatformFile.MoveFile(*ProgramCacheFilename, *ProgramCacheFilenameTemp);
CurrentBinaryFileState = EBinaryFileState::ValidCacheFile;
if (IsPrecachingEnabled())
{
if (OpenCacheWriteHandle(GetProgramBinaryCacheFilePath(), true))
{
check(LastReadPos == CurrentShaderPipelineProperties.LastMappedPosition);
CurrentBinaryFileState = EBinaryFileState::BuildingCacheFile;
BinaryCacheWriteFileHandle->Seek(CurrentShaderPipelineProperties.LastMappedPosition);
}
}
}
}
bool FOpenGLProgramBinaryCache::ReadProgramFile_Internal(uint32 ProgramsToRead, int64 EndOffset, const FString& ProgramCacheFilename, FArchive& Ar)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLReadBinaryProgramCache);
int32 ProgramIndex = 0;
FScopeLock Lock(&GPendingGLProgramCreateRequestsCS);
PendingGLContainerPrograms.Reserve(ProgramsToRead);
int64 MappingSize = EndOffset - CurrentShaderPipelineProperties.LastMappedPosition;
TRefCountPtr<FOpenGLProgramBinaryMapping> CurrentMapping = GetOrAddFileMapping(ProgramCacheFilename, ProgramsToRead, CurrentShaderPipelineProperties.LastMappedPosition, MappingSize);
UE_LOG(LogRHI, Log, TEXT("OnShaderScanProgramCacheFile : %s %s"), CurrentMapping->HasValidMapping() ? TEXT("mapped") : TEXT("opened"), *ProgramCacheFilename);
while (Ar.Tell() < EndOffset)
{
FOpenGLProgramKey ProgramKey;
uint32 ProgramBinarySize = 0;
Ar << ProgramKey;
Ar << ProgramBinarySize;
check(ProgramKey != FOpenGLProgramKey());
if (ensure(ProgramBinarySize > 0))
{
ProgramIndex++;
uint32 ProgramBinaryOffset = Ar.Tell();
CurrentMapping->AddProgramKey(ProgramKey);
UE_LOG(LogRHI, VeryVerbose, TEXT(" scan found PSO %s - %d"), *ProgramKey.ToString(), ProgramBinarySize);
ProgramsInCurrentCache.Add(ProgramKey);
if (CurrentMapping->HasValidMapping())
{
PendingGLContainerPrograms.Emplace(ProgramKey, TUniqueObj<FOpenGLProgramBinary>(CurrentMapping->GetView(ProgramBinaryOffset, ProgramBinarySize)));
Ar.Seek(ProgramBinaryOffset + ProgramBinarySize);
}
else
{
check(!UE::OpenGL::CanMemoryMapGLProgramCache());
TArray<uint8> ProgramBytes;
ProgramBytes.SetNumUninitialized(ProgramBinarySize);
Ar.Serialize(ProgramBytes.GetData(), ProgramBinarySize);
PendingGLContainerPrograms.Emplace(ProgramKey, TUniqueObj<FOpenGLProgramBinary>(MoveTemp(ProgramBytes)));
}
}
else
{
return false;
}
}
CurrentShaderPipelineProperties.LastMappedPosition = Ar.Tell();
UE_LOG(LogRHI, VeryVerbose, TEXT("Program Binary cache: Found %d cached programs"), ProgramIndex);
UE_CLOG(ProgramIndex != ProgramsToRead, LogRHI, Error, TEXT("Program Binary cache: Mismatched program count! expected: %d"), ProgramsToRead);
return true;
}
bool FOpenGLProgramBinaryCache::OpenCacheWriteHandle(const FString& ProgramCacheFilenameToWrite, bool bAppendToExisting)
{
check(BinaryCacheWriteFileHandle == nullptr);
BinaryCacheWriteFileHandle = IFileManager::Get().CreateFileWriter(*ProgramCacheFilenameToWrite, EFileWrite::FILEWRITE_AllowRead | (bAppendToExisting ? EFileWrite::FILEWRITE_Append : EFileWrite::FILEWRITE_None));
UE_CLOG(BinaryCacheWriteFileHandle, LogRHI, Log, TEXT("Opened binary cache for write (%s)"), *ProgramCacheFilenameToWrite);
UE_CLOG(BinaryCacheWriteFileHandle == nullptr, LogRHI, Warning, TEXT("Failed to open OGL binary cache output file. (%s)"), *ProgramCacheFilenameToWrite);
UE_CLOG(BinaryCacheWriteFileHandle && (BinaryCacheWriteFileHandle->IsError() || BinaryCacheWriteFileHandle->IsCriticalError()), LogRHI, Error, TEXT("OGL binary cache output archive error (%s, %d,%d)"), *ProgramCacheFilenameToWrite, BinaryCacheWriteFileHandle->IsError(), BinaryCacheWriteFileHandle->IsCriticalError());
return BinaryCacheWriteFileHandle != nullptr;
}
// Update the file's header to indicate the last successful write location. This allows the app to ignore a truncated file such as after an abnormal exit.
bool FOpenGLProgramBinaryCache::MarkValidContent(int32 NumPrograms)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLMarkValidBinaryProgramCache);
check(BinaryCacheWriteFileHandle != nullptr);
const bool bCacheFileIsEmpty = NumPrograms == 0;
uint32 FailCode = BinaryCacheWriteFileHandle->IsError() || BinaryCacheWriteFileHandle->IsCriticalError() ? 1 : 0;
// Overwrite the header with the final program count. This indicates a successful write.
if (FailCode == 0)
{
FArchive& Ar = *BinaryCacheWriteFileHandle;
int64 CurrPos = Ar.Tell();
Ar.Seek(0);
UE::OpenGL::FBinaryCacheFileHeader OutHeader = UE::OpenGL::FBinaryCacheFileHeader::CreateHeader(CurrentShaderPipelineProperties.CacheVersionGuid, NumPrograms, (int32)CurrPos);
UE_LOG(LogRHI, Verbose, TEXT("MarkValidContent, file valid at %d Programs, %d bytes."), NumPrograms, CurrPos);
Ar << OutHeader;
Ar.Seek(CurrPos);
Ar.Flush();
FailCode = BinaryCacheWriteFileHandle->IsError() || BinaryCacheWriteFileHandle->IsCriticalError() ? 2 : 0;
}
if (FailCode != 0)
{
RHIGetPanicDelegate().ExecuteIfBound(FName("FailedBinaryProgramArchiveWrite"));
UE_LOG(LogRHI, Fatal, TEXT("MarkValidContent - FArchive error bit set, failed to write binary cache. %d, %d"), NumPrograms, FailCode);
}
CurrentShaderPipelineProperties.NumProgramsFlushed = NumPrograms;
return !bCacheFileIsEmpty;
}
bool FOpenGLProgramBinaryCache::CloseCacheWriteHandle(int32 NumProgramsAdded)
{
check(BinaryCacheWriteFileHandle != nullptr);
const bool bCacheFileIsEmpty = NumProgramsAdded == 0;
uint32 FailCode = BinaryCacheWriteFileHandle->IsError() || BinaryCacheWriteFileHandle->IsCriticalError() ? 1 : 0;
// Overwrite the header with the final program count. This indicates a successful write.
if(FailCode == 0)
{
FArchive& Ar = *BinaryCacheWriteFileHandle;
Ar.Seek(0);
UE::OpenGL::FBinaryCacheFileHeader OutHeader = UE::OpenGL::FBinaryCacheFileHeader::CreateHeader(CurrentShaderPipelineProperties.CacheVersionGuid, NumProgramsAdded, (int32)Ar.TotalSize());
Ar << OutHeader;
FailCode = BinaryCacheWriteFileHandle->IsError() || BinaryCacheWriteFileHandle->IsCriticalError() ? 2 : 0;
}
BinaryCacheWriteFileHandle->Close();
delete BinaryCacheWriteFileHandle;
BinaryCacheWriteFileHandle = nullptr;
const FString ProgramCacheFilename = GetProgramBinaryCacheFilePath();
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (FailCode != 0)
{
RHIGetPanicDelegate().ExecuteIfBound(FName("FailedBinaryProgramArchiveWrite"));
UE_LOG(LogRHI, Fatal, TEXT("CloseCacheWriteHandle - FArchive error bit set, failed to write binary cache. %d"), FailCode);
}
if (bCacheFileIsEmpty)
{
// we dont want empty files left on disk.
PlatformFile.DeleteFile(*ProgramCacheFilename);
}
return !bCacheFileIsEmpty;
}
void FOpenGLProgramBinaryCache::CacheProgramBinary(const FOpenGLProgramKey& ProgramKey, TUniqueObj<FOpenGLProgramBinary>&& ProgramBinary)
{
if (CachePtr)
{
FScopeLock Lock(&GProgramBinaryFileCacheCS);
if (!CachePtr->ProgramsInCurrentCache.Contains(ProgramKey))
{
CachePtr->AddProgramBinaryDataToBinaryCache(ProgramKey, ProgramBinary.Get());
if (IsPrecachingEnabled())
{
// If we're precaching then we need to send it to RHIT immediately. without this we'll wait for this portion of the in progress file to be mmapped.
EnqueueBinaryForGLProgramContainer(ProgramKey, MoveTemp(ProgramBinary));
}
}
}
}
// Serialize out the program binary data and add to runtime structures.
void FOpenGLProgramBinaryCache::AddProgramBinaryDataToBinaryCache(const FOpenGLProgramKey& ProgramKey, const FOpenGLProgramBinary& BinaryProgramData)
{
check(IsBuildingCache_internal());
check(BinaryProgramData.IsValid());
FArchive& Ar = *BinaryCacheWriteFileHandle;
bool bInitiallyValid = !(Ar.IsError() || Ar.IsCriticalError());
// Serialize to output file:
FOpenGLProgramKey SerializedProgramKey = ProgramKey;
const TArrayView<const uint8> BinaryProgramDataView = BinaryProgramData.GetDataView();
uint32 ProgramBinarySize = (uint32)BinaryProgramDataView.Num();
const uint8* ProgramBinaryBytes = BinaryProgramDataView.GetData();
uint32 Start = Ar.Tell();
check(SerializedProgramKey != FOpenGLProgramKey());
Ar << SerializedProgramKey;
uint32 ProgramBinaryOffset = Ar.Tell();
check(ProgramBinarySize > 0);
Ar << ProgramBinarySize;
Ar.Serialize(const_cast<uint8*>(ProgramBinaryBytes), ProgramBinarySize);
uint32 End = Ar.Tell();
UE_CLOG(Ar.IsError() || Ar.IsCriticalError(), LogRHI, Error, TEXT("AddProgramBinaryDataToBinaryCache : archive failed (%d, %d, %d, %d, %d, %d)"), Ar.IsError(), Ar.IsCriticalError(), Start, End, ProgramBinarySize, bInitiallyValid);
UE_LOG(LogRHI, VeryVerbose, TEXT("AddProgramBinaryDataToBinaryCache : added %d bytes to cache"), End-Start);
if (UE::OpenGL::AreBinaryProgramsCompressed())
{
static uint32 TotalUncompressed = 0;
static uint32 TotalCompressed = 0;
const UE::OpenGL::FCompressedProgramBinaryHeader* Header = (UE::OpenGL::FCompressedProgramBinaryHeader*)ProgramBinaryBytes;
TotalUncompressed += Header->UncompressedSize;
TotalCompressed += ProgramBinarySize;
UE_LOG(LogRHI, Verbose, TEXT("AddProgramBinaryDataToBinaryCache: total Uncompressed: %d, total Compressed %d, Total saved so far: %d"), TotalUncompressed, TotalCompressed, TotalUncompressed - TotalCompressed);
}
UE_LOG(LogRHI, VeryVerbose, TEXT("AddProgramBinaryDataToBinaryCache: written Program %s to cache (%d bytes)"), *ProgramKey.ToString(), BinaryProgramDataView.Num());
ProgramsInCurrentCache.Add(ProgramKey);
if (IsPrecachingEnabled())
{
int32 NumProgramsStored = ProgramsInCurrentCache.Num();
if (NumProgramsStored >= CurrentShaderPipelineProperties.NumProgramsFlushed + GBinaryCachePeriodicFlushProgramCount)
{
MarkValidContent(NumProgramsStored);
}
}
}
void FOpenGLProgramBinaryCache::EnqueueBinaryForGLProgramContainer(const FOpenGLProgramKey& ProgramKey, TUniqueObj<FOpenGLProgramBinary>&& ProgramBinary)
{
if (CachePtr)
{
FScopeLock Lock(&GPendingGLProgramCreateRequestsCS);
CachePtr->PendingGLContainerPrograms.Emplace(ProgramKey, MoveTemp(ProgramBinary));
}
}
void FOpenGLProgramBinaryCache::Shutdown()
{
if (CachePtr)
{
delete CachePtr;
CachePtr = nullptr;
}
}
bool FOpenGLProgramBinaryCache::RequiresCaching(const FOpenGLProgramKey& ProgramKey)
{
if (CachePtr)
{
FScopeLock Lock(&GProgramBinaryFileCacheCS);
return CachePtr->RequiresCaching_Internal(ProgramKey);
}
return false;
}
bool FOpenGLProgramBinaryCache::RequiresCaching_Internal(const FOpenGLProgramKey& ProgramKey)
{
return !ProgramsInCurrentCache.Contains(ProgramKey);
}
FString FOpenGLProgramBinaryCache::GetProgramBinaryCacheFilePath() const
{
check(CurrentShaderPipelineProperties.CacheVersionGuid != FGuid());
FString ProgramFilename = CachePathRoot / CacheSubDir / CurrentShaderPipelineProperties.PipelineCacheName;
return ProgramFilename;
}
void FOpenGLProgramBinaryCache::TickBinaryCache()
{
if (CachePtr)
{
if(IsPrecachingEnabled())
{
CachePtr->UpdatePrecacheMapping();
}
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLCheckPendingGLProgramCreateRequests);
check(IsInRenderingThread() || IsInRHIThread());
FScopeLock Lock(&GPendingGLProgramCreateRequestsCS);
CachePtr->CheckPendingGLProgramCreateRequests_internal();
}
}
}
// Move programs encountered during the scan to the GL RHI program container.
// GMaxBinaryProgramLoadTimeMS attempts to reduce hitching, if we're not using the LRU then we still create GL programs and require more time.
void FOpenGLProgramBinaryCache::CheckPendingGLProgramCreateRequests_internal()
{
if (PendingGLContainerPrograms.Num() > 0)
{
//FScopedDurationTimeLogger Timer(TEXT("CheckPendingGLProgramCreateRequests"));
float TimeRemainingS = (float)GMaxBinaryProgramLoadTimeMS / 1000.0f;
double StartTime = FPlatformTime::Seconds();
int32 Count = 0;
for (auto It = PendingGLContainerPrograms.CreateIterator(); It && TimeRemainingS > 0.0f; ++It)
{
UE::OpenGL::OnGLProgramLoadedFromBinaryCache(It->Key, MoveTemp(It->Value));
TimeRemainingS -= (float)(FPlatformTime::Seconds() - StartTime);
StartTime = FPlatformTime::Seconds();
Count++;
It.RemoveCurrent();
}
float TimeTaken = (float)GMaxBinaryProgramLoadTimeMS - (TimeRemainingS * 1000.0f);
UE_LOG(LogRHI, Verbose, TEXT("CheckPendingGLProgramCreateRequests : iter count = %d, time taken = %f ms (remaining %d)"), Count, TimeTaken, PendingGLContainerPrograms.Num());
}
}
bool FOpenGLProgramBinaryCache::CheckSinglePendingGLProgramCreateRequest(const FOpenGLProgramKey& ProgramKey)
{
if (CachePtr)
{
check(IsInRenderingThread() || IsInRHIThread());
FScopeLock Lock(&GPendingGLProgramCreateRequestsCS);
return CachePtr->CheckSinglePendingGLProgramCreateRequest_internal(ProgramKey);
}
return false;
}
// Any pending program must complete in this case.
bool FOpenGLProgramBinaryCache::CheckSinglePendingGLProgramCreateRequest_internal(const FOpenGLProgramKey& ProgramKey)
{
TUniqueObj<FOpenGLProgramBinary> ProgFound;
if (PendingGLContainerPrograms.RemoveAndCopyValue(ProgramKey, ProgFound))
{
UE::OpenGL::OnGLProgramLoadedFromBinaryCache(ProgramKey, MoveTemp(ProgFound));
return true;
}
return false;
}