1 /* 2 * Copyright 2022 Google LLC 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 skgpu_graphite_GlobalCache_DEFINED 9 #define skgpu_graphite_GlobalCache_DEFINED 10 11 #include "include/core/SkRefCnt.h" 12 #include "include/private/base/SkTArray.h" 13 #include "src/base/SkSpinlock.h" 14 #include "src/core/SkLRUCache.h" 15 #include "src/gpu/ResourceKey.h" 16 #include "src/gpu/graphite/GraphicsPipeline.h" 17 18 19 #include <functional> 20 21 namespace skgpu::graphite { 22 23 class ComputePipeline; 24 class GraphicsPipeline; 25 class Resource; 26 class ShaderCodeDictionary; 27 28 /** 29 * GlobalCache holds GPU resources that should be shared by every Recorder. The common requirement 30 * of these resources are they are static/read-only, have long lifetimes, and are likely to be used 31 * by multiple Recorders. The canonical example of this are pipelines. 32 * 33 * GlobalCache is thread safe, but intentionally splits queries and storing operations so that they 34 * are not atomic. The pattern is to query for a resource, which has a high likelihood of a cache 35 * hit. If it's not found, the Recorder creates the resource on its own, without locking the 36 * GlobalCache. After the resource is created, it is added to the GlobalCache, atomically returning 37 * the winning Resource in the event of a race between Recorders for the same UniqueKey. 38 */ 39 class GlobalCache { 40 public: 41 GlobalCache(); 42 ~GlobalCache(); 43 44 void deleteResources(); 45 46 // Find a cached GraphicsPipeline that matches the associated key. 47 sk_sp<GraphicsPipeline> findGraphicsPipeline( 48 const UniqueKey&, 49 SkEnumBitMask<PipelineCreationFlags> = PipelineCreationFlags::kNone, 50 uint32_t* compilationID = nullptr) SK_EXCLUDES(fSpinLock); 51 52 // Associate the given pipeline with the key. If the key has already had a separate pipeline 53 // associated with the key, that pipeline is returned and the passed-in pipeline is discarded. 54 // Otherwise, the passed-in pipeline is held by the GlobalCache and also returned back. 55 sk_sp<GraphicsPipeline> addGraphicsPipeline(const UniqueKey&, 56 sk_sp<GraphicsPipeline>) SK_EXCLUDES(fSpinLock); 57 58 void purgePipelinesNotUsedSince( 59 StdSteadyClock::time_point purgeTime) SK_EXCLUDES(fSpinLock); 60 61 #if defined(GPU_TEST_UTILS) 62 int numGraphicsPipelines() const SK_EXCLUDES(fSpinLock); 63 void resetGraphicsPipelines() SK_EXCLUDES(fSpinLock); 64 void forEachGraphicsPipeline( 65 const std::function<void(const UniqueKey&, const GraphicsPipeline*)>& fn) 66 SK_EXCLUDES(fSpinLock); 67 68 struct PipelineStats { 69 int fGraphicsCacheHits = 0; 70 int fGraphicsCacheMisses = 0; 71 int fGraphicsCacheAdditions = 0; 72 int fGraphicsRaces = 0; 73 int fGraphicsPurges = 0; 74 }; 75 76 PipelineStats getStats() const SK_EXCLUDES(fSpinLock); 77 #endif 78 79 // Find and add operations for ComputePipelines, with the same pattern as GraphicsPipelines. 80 sk_sp<ComputePipeline> findComputePipeline(const UniqueKey&) SK_EXCLUDES(fSpinLock); 81 sk_sp<ComputePipeline> addComputePipeline(const UniqueKey&, 82 sk_sp<ComputePipeline>) SK_EXCLUDES(fSpinLock); 83 84 // The GlobalCache holds a ref on the given Resource until the cache is destroyed, keeping it 85 // alive for the lifetime of the SharedContext. This should be used only for Resources that are 86 // immutable after initialization so that anyone can use the resource without synchronization 87 // or reference tracking. 88 void addStaticResource(sk_sp<Resource>) SK_EXCLUDES(fSpinLock); 89 90 using PipelineCallbackContext = void*; 91 using PipelineCallback = void (*)(PipelineCallbackContext context, sk_sp<SkData> pipelineData); 92 void setPipelineCallback(PipelineCallback, PipelineCallbackContext) SK_EXCLUDES(fSpinLock); 93 94 void invokePipelineCallback(SharedContext*, 95 const GraphicsPipelineDesc&, 96 const RenderPassDesc&); 97 private: 98 struct KeyHash { operatorKeyHash99 uint32_t operator()(const UniqueKey& key) const { return key.hash(); } 100 }; 101 102 static void LogPurge(const UniqueKey& key, sk_sp<GraphicsPipeline>* p); 103 struct PurgeCB { operatorPurgeCB104 void operator()(const UniqueKey& k, sk_sp<GraphicsPipeline>* p) const { LogPurge(k, p); } 105 }; 106 107 using GraphicsPipelineCache = SkLRUCache<UniqueKey, sk_sp<GraphicsPipeline>, KeyHash, PurgeCB>; 108 using ComputePipelineCache = SkLRUCache<UniqueKey, sk_sp<ComputePipeline>, KeyHash>; 109 110 // TODO: can we do something better given this should have write-seldom/read-often behavior? 111 mutable SkSpinlock fSpinLock; 112 113 // GraphicsPipelines and ComputePipelines are expensive to create, likely to be used by multiple 114 // Recorders, and are ideally pre-compiled on process startup so thread write-contention is 115 // expected to be low. For these reasons we store pipelines globally instead of per-Recorder. 116 GraphicsPipelineCache fGraphicsPipelineCache SK_GUARDED_BY(fSpinLock); 117 ComputePipelineCache fComputePipelineCache SK_GUARDED_BY(fSpinLock); 118 119 skia_private::TArray<sk_sp<Resource>> fStaticResource SK_GUARDED_BY(fSpinLock); 120 121 PipelineCallback fPipelineCallback SK_GUARDED_BY(fSpinLock) = nullptr; 122 PipelineCallbackContext fPipelineCallbackContext SK_GUARDED_BY(fSpinLock) = nullptr; 123 124 #if defined(GPU_TEST_UTILS) 125 PipelineStats fStats SK_GUARDED_BY(fSpinLock); 126 #endif 127 }; 128 129 } // namespace skgpu::graphite 130 131 #endif // skgpu_graphite_GlobalCache_DEFINED 132