// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #if COMPILE_WITH_SHADER_CACHE_MANAGER #include "ShaderCacheManager.h" #include "Engine/Platform/File.h" #include "Engine/Core/Log.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Globals.h" #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/Materials/MaterialShader.h" #include "Engine/Particles/Graph/GPU/ParticleEmitterGraph.GPU.h" const Char* ShaderProfileCacheDirNames[8] = { // @formatter:off TEXT("EARTH_IS_NOT_FLAT_XD"), // Unknown TEXT("DX_SM4"), // DirectX_SM4 TEXT("DX_SM5"), // DirectX_SM5 TEXT("GLSL_410"), // GLSL_410 TEXT("GLSL_440"), // GLSL_440 TEXT("VK_SM5"), // Vulkan_SM5 TEXT("PS4"), // PS4 TEXT("DX_SM6"), // DirectX_SM6 // @formatter:on }; static_assert(ARRAY_COUNT(ShaderProfileCacheDirNames) == (int32)ShaderProfile::MAX, "Invalid shaders cache dirs"); class ShaderProfileDatabase { private: ShaderProfile _profile; String _folder; public: /// /// Gets the folder path. /// const String& GetFolder() const { return _folder; } public: /// /// Internalizes the database. /// /// Shader profile for that cache database /// Parent root folder for cache databases void Init(ShaderProfile profile, const String& cacheRoot) { // Init _profile = profile; _folder = cacheRoot / ShaderProfileCacheDirNames[static_cast(profile)]; // Ensure that directory exists if (!FileSystem::DirectoryExists(_folder)) { if (FileSystem::CreateDirectory(_folder)) { LOG(Warning, "Cannot create cache directory for ShaderProfileDatabase: {0} (path: \'{1}\')", ::ToString(_profile), _folder); } } } /// /// Tries to get cached shader entry for a given shader. /// /// Shader ID /// Result entry if success /// False if cannot get it, otherwise true bool TryGetEntry(const Guid& id, ShaderCacheManager::CachedEntryHandle& cachedEntry) const { ASSERT(id.IsValid()); cachedEntry.ID = id; cachedEntry.Path = _folder / id.ToString(Guid::FormatType::D); return cachedEntry.Exists(); } /// /// Gets shader cache. /// /// Cached entry handle /// Output data /// True if cannot set cache data, otherwise false bool GetCache(const ShaderCacheManager::CachedEntryHandle& cachedEntry, BytesContainer& outputShaderCache) const { ASSERT(cachedEntry.IsValid()); // Read raw bytes return File::ReadAllBytes(cachedEntry.Path, outputShaderCache); } /// /// Sets shader cache. /// /// Cached entry handle /// Input data /// True if cannot set cache data, otherwise false static bool SetCache(const ShaderCacheManager::CachedEntryHandle& cachedEntry, MemoryWriteStream& inputShaderCache) { ASSERT(cachedEntry.IsValid() && inputShaderCache.GetHandle() != nullptr); // Write raw bytes return inputShaderCache.SaveToFile(cachedEntry.Path); } /// /// Removes shader cache. /// /// Shader ID void RemoveCache(const Guid& id) const { ASSERT(id.IsValid()); ShaderCacheManager::CachedEntryHandle cachedEntry; cachedEntry.ID = id; cachedEntry.Path = _folder / id.ToString(Guid::FormatType::D); // Delete file cachedEntry.Delete(); } }; class ShaderCacheManagerService : public EngineService { public: ShaderCacheManagerService() : EngineService(TEXT("Shader Cache Manager"), -200) { } bool Init() override; }; ShaderCacheManagerService ShaderCacheManagerServiceInstance; ShaderProfileDatabase Databases[(int32)ShaderProfile::MAX - 1]; static int32 shaderProfile2Index(const ShaderProfile profile) { return static_cast(profile) - 1; } static ShaderProfile index2ShaderProfile(const int32 index) { return static_cast(index + 1); } bool ShaderCacheManager::TryGetEntry(const ShaderProfile profile, const Guid& id, CachedEntryHandle& cachedEntry) { ASSERT(profile != ShaderProfile::Unknown); return Databases[shaderProfile2Index(profile)].TryGetEntry(id, cachedEntry); } bool ShaderCacheManager::GetCache(const ShaderProfile profile, const CachedEntryHandle& cachedEntry, BytesContainer& outputShaderCache) { ASSERT(profile != ShaderProfile::Unknown); return Databases[shaderProfile2Index(profile)].GetCache(cachedEntry, outputShaderCache); } bool ShaderCacheManager::SetCache(const ShaderProfile profile, const CachedEntryHandle& cachedEntry, MemoryWriteStream& inputShaderCache) { ASSERT(profile != ShaderProfile::Unknown); return Databases[shaderProfile2Index(profile)].SetCache(cachedEntry, inputShaderCache); } void ShaderCacheManager::RemoveCache(const ShaderProfile profile, const Guid& id) { ASSERT(profile != ShaderProfile::Unknown); Databases[shaderProfile2Index(profile)].RemoveCache(id); } void ShaderCacheManager::RemoveCache(const Guid& id) { for (int32 i = 0; i < ARRAY_COUNT(Databases); i++) Databases[i].RemoveCache(id); } void ShaderCacheManager::CopyCache(const Guid& dstId, const Guid& srcId) { ASSERT(dstId.IsValid() && srcId.IsValid()); String dstFilename = dstId.ToString(Guid::FormatType::D); String srcFilename = srcId.ToString(Guid::FormatType::D); for (int32 i = 0; i < ARRAY_COUNT(Databases); i++) { const String& folder = Databases[i].GetFolder(); String dstPath = folder / dstFilename; String srcPath = folder / srcFilename; if (FileSystem::FileExists(srcPath)) FileSystem::CopyFile(dstPath, srcPath); else if (FileSystem::FileExists(dstPath)) FileSystem::DeleteFile(dstPath); } } bool ShaderCacheManagerService::Init() { #if USE_EDITOR const String rootDir = Globals::ProjectCacheFolder / TEXT("Shaders/Cache"); #else const String rootDir = Globals::ProductLocalFolder / TEXT("Shaders/Cache"); #endif // Validate the database cache version (need to recompile all shaders on shader cache format change) struct CacheVersion { int32 ShaderCacheVersion = -1; int32 MaterialGraphVersion = -1; int32 ParticleGraphVersion = -1; }; CacheVersion cacheVersion; const String cacheVerFile = rootDir / TEXT("CacheVersion"); if (FileSystem::FileExists(cacheVerFile)) { if (File::ReadAllBytes(cacheVerFile, (byte*)&cacheVersion, sizeof(cacheVersion))) { cacheVersion = CacheVersion(); LOG(Warning, "Failed to read the shaders cache database version file."); } } if (cacheVersion.ShaderCacheVersion != GPU_SHADER_CACHE_VERSION || cacheVersion.MaterialGraphVersion != MATERIAL_GRAPH_VERSION || cacheVersion.ParticleGraphVersion != PARTICLE_GPU_GRAPH_VERSION ) { LOG(Warning, "Shaders cache database is invalid. Performing reset."); if (FileSystem::DeleteDirectory(rootDir)) { LOG(Warning, "Failed to reset the shaders cache database."); } if (FileSystem::CreateDirectory(rootDir)) { LOG(Error, "Failed to createe the shaders cache database directory."); } cacheVersion.ShaderCacheVersion = GPU_SHADER_CACHE_VERSION; cacheVersion.MaterialGraphVersion = MATERIAL_GRAPH_VERSION; cacheVersion.ParticleGraphVersion = PARTICLE_GPU_GRAPH_VERSION; if (File::WriteAllBytes(cacheVerFile, (byte*)&cacheVersion, sizeof(cacheVersion))) { LOG(Error, "Failed to create the shaders cache database version file."); } } // Init shader formats databases for (int32 i = 0; i < ARRAY_COUNT(Databases); i++) { Databases[i].Init(index2ShaderProfile(i), rootDir); } return false; } #endif