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