• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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