xref: /aosp_15_r20/external/angle/src/libANGLE/renderer/metal/mtl_library_cache.mm (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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> &macros,
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> &macros,
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(&macro_count, sizeof(size_t));
89    for (const auto &macro : 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> &macros,
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> &macrosIn,
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 &macro : 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