• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> &macros,
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> &macros,
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(&macro_count, sizeof(size_t));
90    for (const auto &macro : 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> &macros,
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> &macrosIn,
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 &macro : 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