Coverage Report

Created: 2025-06-24 08:20

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