1 /* 2 * Copyright 2020 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #ifndef GrThreadSafeCache_DEFINED 9 #define GrThreadSafeCache_DEFINED 10 11 #include "include/core/SkAlphaType.h" 12 #include "include/core/SkRefCnt.h" 13 #include "include/private/base/SkAssert.h" 14 #include "include/private/base/SkDebug.h" 15 #include "include/private/base/SkMalloc.h" 16 #include "include/private/base/SkThreadAnnotations.h" 17 #include "src/base/SkArenaAlloc.h" 18 #include "src/base/SkSpinlock.h" 19 #include "src/base/SkTInternalLList.h" 20 #include "src/core/SkTDynamicHash.h" 21 #include "src/gpu/GpuTypesPriv.h" 22 #include "src/gpu/ResourceKey.h" 23 #include "src/gpu/ganesh/GrGpuBuffer.h" 24 #include "src/gpu/ganesh/GrSurfaceProxy.h" 25 #include "src/gpu/ganesh/GrSurfaceProxyView.h" 26 #include "src/gpu/ganesh/GrTextureProxy.h" 27 28 #include <cstddef> 29 #include <cstdint> 30 #include <tuple> 31 #include <utility> 32 33 class GrDirectContext; 34 class GrResourceCache; 35 class SkData; 36 enum GrSurfaceOrigin : int; 37 enum class GrColorType; 38 enum class SkBackingFit; 39 struct SkISize; 40 41 // Ganesh creates a lot of utility textures (e.g., blurred-rrect masks) that need to be shared 42 // between the direct context and all the DDL recording contexts. This thread-safe cache 43 // allows this sharing. 44 // 45 // In operation, each thread will first check if the threaded cache possesses the required texture. 46 // 47 // If a DDL thread doesn't find a needed texture it will go off and create it on the cpu and then 48 // attempt to add it to the cache. If another thread had added it in the interim, the losing thread 49 // will discard its work and use the texture the winning thread had created. 50 // 51 // If the thread in possession of the direct context doesn't find the needed texture it should 52 // add a place holder view and then queue up the draw calls to complete it. In this way the 53 // gpu-thread has precedence over the recording threads. 54 // 55 // The invariants for this cache differ a bit from those of the proxy and resource caches. 56 // For this cache: 57 // 58 // only this cache knows the unique key - neither the proxy nor backing resource should 59 // be discoverable in any other cache by the unique key 60 // if a backing resource resides in the resource cache then there should be an entry in this 61 // cache 62 // an entry in this cache, however, doesn't guarantee that there is a corresponding entry in 63 // the resource cache - although the entry here should be able to generate that entry 64 // (i.e., be a lazy proxy) 65 // 66 // Wrt interactions w/ GrContext/GrResourceCache purging, we have: 67 // 68 // Both GrContext::abandonContext and GrContext::releaseResourcesAndAbandonContext will cause 69 // all the refs held in this cache to be dropped prior to clearing out the resource cache. 70 // 71 // For the size_t-variant of GrContext::purgeUnlockedResources, after an initial attempt 72 // to purge the requested amount of resources fails, uniquely held resources in this cache 73 // will be dropped in LRU to MRU order until the cache is under budget. Note that this 74 // prioritizes the survival of resources in this cache over those just in the resource cache. 75 // 76 // For the 'scratchResourcesOnly' variant of GrContext::purgeUnlockedResources, this cache 77 // won't be modified in the scratch-only case unless the resource cache is over budget (in 78 // which case it will purge uniquely-held resources in LRU to MRU order to get 79 // back under budget). In the non-scratch-only case, all uniquely held resources in this cache 80 // will be released prior to the resource cache being cleared out. 81 // 82 // For GrContext::setResourceCacheLimit, if an initial pass through the resource cache doesn't 83 // reach the budget, uniquely held resources in this cache will be released in LRU to MRU order. 84 // 85 // For GrContext::performDeferredCleanup, any uniquely held resources that haven't been accessed 86 // w/in 'msNotUsed' will be released from this cache prior to the resource cache being cleaned. 87 class GrThreadSafeCache { 88 public: 89 GrThreadSafeCache(); 90 ~GrThreadSafeCache(); 91 92 #if defined(GPU_TEST_UTILS) 93 int numEntries() const SK_EXCLUDES(fSpinLock); 94 95 size_t approxBytesUsedForHash() const SK_EXCLUDES(fSpinLock); 96 #endif 97 98 void dropAllRefs() SK_EXCLUDES(fSpinLock); 99 100 // Drop uniquely held refs until under the resource cache's budget. 101 // A null parameter means drop all uniquely held refs. 102 void dropUniqueRefs(GrResourceCache* resourceCache) SK_EXCLUDES(fSpinLock); 103 104 // Drop uniquely held refs that were last accessed before 'purgeTime' 105 void dropUniqueRefsOlderThan( 106 skgpu::StdSteadyClock::time_point purgeTime) SK_EXCLUDES(fSpinLock); 107 108 SkDEBUGCODE(bool has(const skgpu::UniqueKey&) SK_EXCLUDES(fSpinLock);) 109 110 GrSurfaceProxyView find(const skgpu::UniqueKey&) SK_EXCLUDES(fSpinLock); 111 std::tuple<GrSurfaceProxyView, sk_sp<SkData>> findWithData( 112 const skgpu::UniqueKey&) SK_EXCLUDES(fSpinLock); 113 114 GrSurfaceProxyView add( 115 const skgpu::UniqueKey&, const GrSurfaceProxyView&) SK_EXCLUDES(fSpinLock); 116 std::tuple<GrSurfaceProxyView, sk_sp<SkData>> addWithData( 117 const skgpu::UniqueKey&, const GrSurfaceProxyView&) SK_EXCLUDES(fSpinLock); 118 119 GrSurfaceProxyView findOrAdd(const skgpu::UniqueKey&, 120 const GrSurfaceProxyView&) SK_EXCLUDES(fSpinLock); 121 std::tuple<GrSurfaceProxyView, sk_sp<SkData>> findOrAddWithData( 122 const skgpu::UniqueKey&, const GrSurfaceProxyView&) SK_EXCLUDES(fSpinLock); 123 124 // To hold vertex data in the cache and have it transparently transition from cpu-side to 125 // gpu-side while being shared between all the threads we need a ref counted object that 126 // keeps hold of the cpu-side data but allows deferred filling in of the mirroring gpu buffer. 127 class VertexData : public SkNVRefCnt<VertexData> { 128 public: 129 ~VertexData(); 130 vertices()131 const void* vertices() const { return fVertices; } size()132 size_t size() const { return fNumVertices * fVertexSize; } 133 numVertices()134 int numVertices() const { return fNumVertices; } vertexSize()135 size_t vertexSize() const { return fVertexSize; } 136 137 // TODO: make these return const GrGpuBuffers? gpuBuffer()138 GrGpuBuffer* gpuBuffer() { return fGpuBuffer.get(); } refGpuBuffer()139 sk_sp<GrGpuBuffer> refGpuBuffer() { return fGpuBuffer; } 140 setGpuBuffer(sk_sp<GrGpuBuffer> gpuBuffer)141 void setGpuBuffer(sk_sp<GrGpuBuffer> gpuBuffer) { 142 // TODO: once we add the gpuBuffer we could free 'fVertices'. Deinstantiable 143 // DDLs could throw a monkey wrench into that plan though. 144 SkASSERT(!fGpuBuffer); 145 fGpuBuffer = std::move(gpuBuffer); 146 } 147 reset()148 void reset() { 149 sk_free(const_cast<void*>(fVertices)); 150 fVertices = nullptr; 151 fNumVertices = 0; 152 fVertexSize = 0; 153 fGpuBuffer.reset(); 154 } 155 156 private: 157 friend class GrThreadSafeCache; // for access to ctor 158 VertexData(const void * vertices,int numVertices,size_t vertexSize)159 VertexData(const void* vertices, int numVertices, size_t vertexSize) 160 : fVertices(vertices) 161 , fNumVertices(numVertices) 162 , fVertexSize(vertexSize) { 163 } 164 VertexData(sk_sp<GrGpuBuffer> gpuBuffer,int numVertices,size_t vertexSize)165 VertexData(sk_sp<GrGpuBuffer> gpuBuffer, int numVertices, size_t vertexSize) 166 : fVertices(nullptr) 167 , fNumVertices(numVertices) 168 , fVertexSize(vertexSize) 169 , fGpuBuffer(std::move(gpuBuffer)) { 170 } 171 172 const void* fVertices; 173 int fNumVertices; 174 size_t fVertexSize; 175 176 sk_sp<GrGpuBuffer> fGpuBuffer; 177 }; 178 179 // The returned VertexData object takes ownership of 'vertices' which had better have been 180 // allocated with malloc! 181 static sk_sp<VertexData> MakeVertexData(const void* vertices, 182 int vertexCount, 183 size_t vertexSize); 184 static sk_sp<VertexData> MakeVertexData(sk_sp<GrGpuBuffer> buffer, 185 int vertexCount, 186 size_t vertexSize); 187 188 std::tuple<sk_sp<VertexData>, sk_sp<SkData>> findVertsWithData( 189 const skgpu::UniqueKey&) SK_EXCLUDES(fSpinLock); 190 191 typedef bool (*IsNewerBetter)(SkData* incumbent, SkData* challenger); 192 193 std::tuple<sk_sp<VertexData>, sk_sp<SkData>> addVertsWithData( 194 const skgpu::UniqueKey&, 195 sk_sp<VertexData>, 196 IsNewerBetter) SK_EXCLUDES(fSpinLock); 197 198 void remove(const skgpu::UniqueKey&) SK_EXCLUDES(fSpinLock); 199 200 // To allow gpu-created resources to have priority, we pre-emptively place a lazy proxy 201 // in the thread-safe cache (with findOrAdd). The Trampoline object allows that lazy proxy to 202 // be instantiated with some later generated rendering result. 203 class Trampoline : public SkRefCnt { 204 public: 205 sk_sp<GrTextureProxy> fProxy; 206 }; 207 208 static std::tuple<GrSurfaceProxyView, sk_sp<Trampoline>> CreateLazyView(GrDirectContext*, 209 GrColorType, 210 SkISize dimensions, 211 GrSurfaceOrigin, 212 SkBackingFit); 213 private: 214 struct Entry { EntryEntry215 Entry(const skgpu::UniqueKey& key, const GrSurfaceProxyView& view) 216 : fKey(key), fView(view), fTag(Entry::Tag::kView) {} 217 EntryEntry218 Entry(const skgpu::UniqueKey& key, sk_sp<VertexData> vertData) 219 : fKey(key), fVertData(std::move(vertData)), fTag(Entry::Tag::kVertData) {} 220 ~EntryEntry221 ~Entry() { 222 this->makeEmpty(); 223 } 224 uniquelyHeldEntry225 bool uniquelyHeld() const { 226 SkASSERT(fTag != Tag::kEmpty); 227 228 if (fTag == Tag::kView && fView.proxy()->unique()) { 229 return true; 230 } else if (fTag == Tag::kVertData && fVertData->unique()) { 231 return true; 232 } 233 234 return false; 235 } 236 keyEntry237 const skgpu::UniqueKey& key() const { 238 SkASSERT(fTag != Tag::kEmpty); 239 return fKey; 240 } 241 getCustomDataEntry242 SkData* getCustomData() const { 243 SkASSERT(fTag != Tag::kEmpty); 244 return fKey.getCustomData(); 245 } 246 refCustomDataEntry247 sk_sp<SkData> refCustomData() const { 248 SkASSERT(fTag != Tag::kEmpty); 249 return fKey.refCustomData(); 250 } 251 viewEntry252 GrSurfaceProxyView view() { 253 SkASSERT(fTag == Tag::kView); 254 return fView; 255 } 256 vertexDataEntry257 sk_sp<VertexData> vertexData() { 258 SkASSERT(fTag == Tag::kVertData); 259 return fVertData; 260 } 261 setEntry262 void set(const skgpu::UniqueKey& key, const GrSurfaceProxyView& view) { 263 SkASSERT(fTag == Tag::kEmpty); 264 fKey = key; 265 fView = view; 266 fTag = Tag::kView; 267 } 268 makeEmptyEntry269 void makeEmpty() { 270 fKey.reset(); 271 if (fTag == Tag::kView) { 272 fView.reset(); 273 } else if (fTag == Tag::kVertData) { 274 fVertData.reset(); 275 } 276 fTag = Tag::kEmpty; 277 } 278 setEntry279 void set(const skgpu::UniqueKey& key, sk_sp<VertexData> vertData) { 280 SkASSERT(fTag == Tag::kEmpty || fTag == Tag::kVertData); 281 fKey = key; 282 fVertData = std::move(vertData); 283 fTag = Tag::kVertData; 284 } 285 286 // The thread-safe cache gets to directly manipulate the llist and last-access members 287 skgpu::StdSteadyClock::time_point fLastAccess; 288 SK_DECLARE_INTERNAL_LLIST_INTERFACE(Entry); 289 290 // for SkTDynamicHash GetKeyEntry291 static const skgpu::UniqueKey& GetKey(const Entry& e) { 292 SkASSERT(e.fTag != Tag::kEmpty); 293 return e.fKey; 294 } HashEntry295 static uint32_t Hash(const skgpu::UniqueKey& key) { return key.hash(); } 296 297 private: 298 // Note: the unique key is stored here bc it is never attached to a proxy or a GrTexture 299 skgpu::UniqueKey fKey; 300 union { 301 GrSurfaceProxyView fView; 302 sk_sp<VertexData> fVertData; 303 }; 304 305 enum class Tag { 306 kEmpty, 307 kView, 308 kVertData, 309 }; 310 Tag fTag{Tag::kEmpty}; 311 }; 312 313 void makeExistingEntryMRU(Entry*) SK_REQUIRES(fSpinLock); 314 Entry* makeNewEntryMRU(Entry*) SK_REQUIRES(fSpinLock); 315 316 Entry* getEntry(const skgpu::UniqueKey&, const GrSurfaceProxyView&) SK_REQUIRES(fSpinLock); 317 Entry* getEntry(const skgpu::UniqueKey&, sk_sp<VertexData>) SK_REQUIRES(fSpinLock); 318 319 void recycleEntry(Entry*) SK_REQUIRES(fSpinLock); 320 321 std::tuple<GrSurfaceProxyView, sk_sp<SkData>> internalFind( 322 const skgpu::UniqueKey&) SK_REQUIRES(fSpinLock); 323 std::tuple<GrSurfaceProxyView, sk_sp<SkData>> internalAdd( 324 const skgpu::UniqueKey&, const GrSurfaceProxyView&) SK_REQUIRES(fSpinLock); 325 326 std::tuple<sk_sp<VertexData>, sk_sp<SkData>> internalFindVerts( 327 const skgpu::UniqueKey&) SK_REQUIRES(fSpinLock); 328 std::tuple<sk_sp<VertexData>, sk_sp<SkData>> internalAddVerts( 329 const skgpu::UniqueKey&, sk_sp<VertexData>, IsNewerBetter) SK_REQUIRES(fSpinLock); 330 331 mutable SkSpinlock fSpinLock; 332 333 SkTDynamicHash<Entry, skgpu::UniqueKey> fUniquelyKeyedEntryMap SK_GUARDED_BY(fSpinLock); 334 // The head of this list is the MRU 335 SkTInternalLList<Entry> fUniquelyKeyedEntryList SK_GUARDED_BY(fSpinLock); 336 337 // TODO: empirically determine this from the skps 338 static const int kInitialArenaSize = 64 * sizeof(Entry); 339 340 char fStorage[kInitialArenaSize]; 341 SkArenaAlloc fEntryAllocator{fStorage, kInitialArenaSize, kInitialArenaSize}; 342 Entry* fFreeEntryList SK_GUARDED_BY(fSpinLock); 343 }; 344 345 #endif // GrThreadSafeCache_DEFINED 346