1// 2// Copyright 2023 The ANGLE Project Authors. All rights reserved. 3// Use of this source code is governed by a BSD-style license that can be 4// found in the LICENSE file. 5// 6// mtl_library_cache.mm: 7// Defines classes for caching of mtl libraries 8// 9 10#include "libANGLE/renderer/metal/mtl_library_cache.h" 11 12#include <stdio.h> 13 14#include <limits> 15 16#include "common/MemoryBuffer.h" 17#include "common/angleutils.h" 18#include "common/hash_utils.h" 19#include "common/mathutil.h" 20#include "common/string_utils.h" 21#include "common/system_utils.h" 22#include "libANGLE/histogram_macros.h" 23#include "libANGLE/renderer/metal/DisplayMtl.h" 24#include "libANGLE/renderer/metal/process.h" 25#include "platform/PlatformMethods.h" 26 27namespace rx 28{ 29namespace mtl 30{ 31 32LibraryCache::LibraryCache() : mCache(kMaxCachedLibraries) {} 33 34AutoObjCPtr<id<MTLLibrary>> LibraryCache::get(const std::shared_ptr<const std::string> &source, 35 const std::map<std::string, std::string> ¯os, 36 bool disableFastMath, 37 bool usesInvariance) 38{ 39 ASSERT(source != nullptr); 40 LibraryCache::LibraryCacheEntry &entry = 41 getCacheEntry(LibraryKey(source, macros, disableFastMath, usesInvariance)); 42 43 // Try to lock the entry and return the library if it exists. If we can't lock then it means 44 // another thread is currently compiling. 45 std::unique_lock<std::mutex> entryLockGuard(entry.lock, std::try_to_lock); 46 if (entryLockGuard) 47 { 48 return entry.library; 49 } 50 else 51 { 52 return nil; 53 } 54} 55 56namespace 57{ 58 59// Reads a metallib file at the specified path. 60angle::MemoryBuffer ReadMetallibFromFile(const std::string &path) 61{ 62 // TODO: optimize this to avoid the unnecessary strings. 63 std::string metallib; 64 if (!angle::ReadFileToString(path, &metallib)) 65 { 66 FATAL() << "Failed reading back metallib"; 67 } 68 69 angle::MemoryBuffer buffer; 70 if (!buffer.resize(metallib.size())) 71 { 72 FATAL() << "Failed to resize metallib buffer"; 73 } 74 memcpy(buffer.data(), metallib.data(), metallib.size()); 75 return buffer; 76} 77 78// Generates a key for the BlobCache based on the specified params. 79egl::BlobCache::Key GenerateBlobCacheKeyForShaderLibrary( 80 const std::shared_ptr<const std::string> &source, 81 const std::map<std::string, std::string> ¯os, 82 bool disableFastMath, 83 bool usesInvariance) 84{ 85 angle::base::SecureHashAlgorithm sha1; 86 sha1.Update(source->c_str(), source->size()); 87 const size_t macro_count = macros.size(); 88 sha1.Update(¯o_count, sizeof(size_t)); 89 for (const auto ¯o : macros) 90 { 91 sha1.Update(macro.first.c_str(), macro.first.size()); 92 sha1.Update(macro.second.c_str(), macro.second.size()); 93 } 94 sha1.Update(&disableFastMath, sizeof(bool)); 95 sha1.Update(&usesInvariance, sizeof(bool)); 96 sha1.Final(); 97 return sha1.DigestAsArray(); 98} 99 100 101} // namespace 102 103AutoObjCPtr<id<MTLLibrary>> LibraryCache::getOrCompileShaderLibrary( 104 DisplayMtl *displayMtl, 105 const std::shared_ptr<const std::string> &source, 106 const std::map<std::string, std::string> ¯os, 107 bool disableFastMath, 108 bool usesInvariance, 109 AutoObjCPtr<NSError *> *errorOut) 110{ 111 id<MTLDevice> metalDevice = displayMtl->getMetalDevice(); 112 const angle::FeaturesMtl &features = displayMtl->getFeatures(); 113 if (!features.enableInMemoryMtlLibraryCache.enabled) 114 { 115 return CreateShaderLibrary(metalDevice, *source, macros, disableFastMath, usesInvariance, 116 errorOut); 117 } 118 119 ASSERT(source != nullptr); 120 LibraryCache::LibraryCacheEntry &entry = 121 getCacheEntry(LibraryKey(source, macros, disableFastMath, usesInvariance)); 122 123 // Lock this cache entry while compiling the shader. This causes other threads calling this 124 // function to wait and not duplicate the compilation. 125 std::lock_guard<std::mutex> entryLockGuard(entry.lock); 126 if (entry.library) 127 { 128 return entry.library; 129 } 130 131 if (features.printMetalShaders.enabled) 132 { 133 auto cache_key = 134 GenerateBlobCacheKeyForShaderLibrary(source, macros, disableFastMath, usesInvariance); 135 NSLog(@"Loading metal shader, key=%@ source=%s", 136 [NSData dataWithBytes:cache_key.data() length:cache_key.size()], source -> c_str()); 137 } 138 139 if (features.compileMetalShaders.enabled) 140 { 141 if (features.enableParallelMtlLibraryCompilation.enabled) 142 { 143 // When enableParallelMtlLibraryCompilation is enabled, compilation happens in the 144 // background. Chrome's ProgramCache only saves to disk when called at certain points, 145 // which are not present when compiling in the background. 146 FATAL() << "EnableParallelMtlLibraryCompilation is not compatible with " 147 "compileMetalShdaders"; 148 } 149 // Note: there does not seem to be a 150 std::string metallib_filename = 151 CompileShaderLibraryToFile(*source, macros, disableFastMath, usesInvariance); 152 angle::MemoryBuffer memory_buffer = ReadMetallibFromFile(metallib_filename); 153 AutoObjCPtr<NSError *> error; 154 entry.library = CreateShaderLibraryFromBinary(metalDevice, memory_buffer.data(), 155 memory_buffer.size(), &error); 156 auto cache_key = 157 GenerateBlobCacheKeyForShaderLibrary(source, macros, disableFastMath, usesInvariance); 158 displayMtl->getBlobCache()->put(nullptr, cache_key, std::move(memory_buffer)); 159 return entry.library; 160 } 161 162 if (features.loadMetalShadersFromBlobCache.enabled) 163 { 164 auto cache_key = 165 GenerateBlobCacheKeyForShaderLibrary(source, macros, disableFastMath, usesInvariance); 166 egl::BlobCache::Value value; 167 angle::ScratchBuffer scratch_buffer; 168 if (displayMtl->getBlobCache()->get(nullptr, &scratch_buffer, cache_key, &value)) 169 { 170 AutoObjCPtr<NSError *> error; 171 entry.library = 172 CreateShaderLibraryFromBinary(metalDevice, value.data(), value.size(), &error); 173 } 174 ANGLE_HISTOGRAM_BOOLEAN("GPU.ANGLE.MetalShaderInBlobCache", entry.library); 175 ANGLEPlatformCurrent()->recordShaderCacheUse(entry.library); 176 if (entry.library) 177 { 178 return entry.library; 179 } 180 } 181 182 entry.library = CreateShaderLibrary(metalDevice, *source, macros, disableFastMath, 183 usesInvariance, errorOut); 184 return entry.library; 185} 186 187LibraryCache::LibraryCacheEntry &LibraryCache::getCacheEntry(LibraryKey &&key) 188{ 189 // Lock while searching or adding new items to the cache. 190 std::lock_guard<std::mutex> cacheLockGuard(mCacheLock); 191 192 auto iter = mCache.Get(key); 193 if (iter != mCache.end()) 194 { 195 return iter->second; 196 } 197 198 angle::TrimCache(kMaxCachedLibraries, kGCLimit, "metal library", &mCache); 199 200 iter = mCache.Put(std::move(key), LibraryCacheEntry()); 201 return iter->second; 202} 203 204LibraryCache::LibraryKey::LibraryKey(const std::shared_ptr<const std::string> &sourceIn, 205 const std::map<std::string, std::string> ¯osIn, 206 bool disableFastMathIn, 207 bool usesInvarianceIn) 208 : source(sourceIn), 209 macros(macrosIn), 210 disableFastMath(disableFastMathIn), 211 usesInvariance(usesInvarianceIn) 212{} 213 214bool LibraryCache::LibraryKey::operator==(const LibraryKey &other) const 215{ 216 return std::tie(*source, macros, disableFastMath, usesInvariance) == 217 std::tie(*other.source, other.macros, other.disableFastMath, other.usesInvariance); 218} 219 220size_t LibraryCache::LibraryKeyHasher::operator()(const LibraryKey &k) const 221{ 222 size_t hash = 0; 223 angle::HashCombine(hash, *k.source); 224 for (const auto ¯o : k.macros) 225 { 226 angle::HashCombine(hash, macro.first); 227 angle::HashCombine(hash, macro.second); 228 } 229 angle::HashCombine(hash, k.disableFastMath); 230 angle::HashCombine(hash, k.usesInvariance); 231 return hash; 232} 233 234LibraryCache::LibraryCacheEntry::~LibraryCacheEntry() 235{ 236 // Lock the cache entry before deletion to ensure there is no other thread compiling and 237 // preparing to write to the library. LibraryCacheEntry objects can only be deleted while the 238 // mCacheLock is held so only one thread modifies mCache at a time. 239 std::lock_guard<std::mutex> entryLockGuard(lock); 240} 241 242LibraryCache::LibraryCacheEntry::LibraryCacheEntry(LibraryCacheEntry &&moveFrom) 243{ 244 // Lock the cache entry being moved from to make sure the library can be safely accessed. 245 // Mutexes cannot be moved so a new one will be created in this entry 246 std::lock_guard<std::mutex> entryLockGuard(moveFrom.lock); 247 248 library = std::move(moveFrom.library); 249 moveFrom.library = nullptr; 250} 251 252} // namespace mtl 253} // namespace rx 254