Coverage Report

Created: 2024-05-20 07:14

/src/skia/fuzz/FuzzDDLThreading.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2021 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
#include "fuzz/Fuzz.h"
9
#include "fuzz/FuzzCommon.h"
10
11
#include "include/core/SkCanvas.h"
12
#include "include/core/SkExecutor.h"
13
#include "include/core/SkSize.h"
14
#include "include/core/SkSurface.h"
15
#include "include/gpu/GrDirectContext.h"
16
#include "include/gpu/ganesh/SkImageGanesh.h"
17
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
18
#include "include/private/base/SkDeque.h"
19
#include "include/private/base/SkMutex.h"
20
#include "include/private/base/SkTemplates.h"
21
#include "include/private/base/SkThreadID.h"
22
#include "include/private/chromium/GrDeferredDisplayList.h"
23
#include "include/private/chromium/GrDeferredDisplayListRecorder.h"
24
#include "include/private/chromium/GrPromiseImageTexture.h"
25
#include "include/private/chromium/SkImageChromium.h"
26
#include "src/core/SkTaskGroup.h"
27
#include "src/gpu/ganesh/image/SkImage_Ganesh.h"
28
#include "tools/gpu/GrContextFactory.h"
29
30
#include <atomic>
31
#include <memory>
32
#include <queue>
33
34
using namespace skia_private;
35
using ContextType = sk_gpu_test::GrContextFactory::ContextType;
36
37
// be careful: `foo(make_fuzz_t<T>(f), make_fuzz_t<U>(f))` is undefined.
38
// In fact, all make_fuzz_foo() functions have this potential problem.
39
// Use sequence points!
40
template <typename T>
41
235k
inline T make_fuzz_t(Fuzz* fuzz) {
42
235k
    T t;
43
235k
    fuzz->next(&t);
44
235k
    return t;
45
235k
}
46
47
class DDLFuzzer;
48
49
// This class stores the state of a given promise image owned by the fuzzer. It acts as the
50
// context for the callback procs of the promise image.
51
class PromiseImageInfo : public SkNVRefCnt<PromiseImageInfo> {
52
public:
53
    enum class State : int {
54
        kInitial,
55
        kTriedToFulfill,
56
        kDone
57
    };
58
59
224
    PromiseImageInfo() = default;
60
224
    ~PromiseImageInfo() {
61
        // If we hit this, then the image or the texture will outlive this object which is bad.
62
224
        SkASSERT_RELEASE(!fImage || fImage->unique());
63
224
        SkASSERT_RELEASE(!fTexture || fTexture->unique());
64
224
        fImage.reset();
65
224
        fTexture.reset();
66
224
        State s = fState;
67
224
        SkASSERT_RELEASE(!fDrawn || s == State::kDone);
68
224
    }
69
70
    // Make noncopyable
71
    PromiseImageInfo(PromiseImageInfo&) = delete;
72
    PromiseImageInfo& operator=(PromiseImageInfo&) = delete;
73
74
    DDLFuzzer* fFuzzer = nullptr;
75
    sk_sp<SkImage> fImage;
76
    // At the moment, the atomicity of this isn't used because all our promise image callbacks
77
    // happen on the same thread. See the TODO below about them unreffing them off the GPU thread.
78
    std::atomic<State> fState{State::kInitial};
79
    std::atomic<bool> fDrawn{false};
80
81
    sk_sp<GrPromiseImageTexture> fTexture;
82
};
83
84
static constexpr int kPromiseImageCount = 8;
85
static constexpr SkISize kPromiseImageSize{16, 16};
86
static constexpr int kPromiseImagesPerDDL = 4;
87
static constexpr int kRecordingThreadCount = 4;
88
static constexpr int kIterationCount = 10000;
89
90
// A one-shot runner object for fuzzing our DDL threading. It creates an array of promise images,
91
// and concurrently records DDLs that reference them, playing each DDL back on the GPU thread.
92
// The backing textures for promise images may be recycled into a pool, or not, for each case
93
// as determined by the fuzzing data.
94
class DDLFuzzer {
95
public:
96
    DDLFuzzer(Fuzz*, ContextType);
97
    DDLFuzzer() = delete;
98
    // Make noncopyable
99
    DDLFuzzer(DDLFuzzer&) = delete;
100
    DDLFuzzer& operator=(DDLFuzzer&) = delete;
101
102
    void run();
103
104
    sk_sp<GrPromiseImageTexture> fulfillPromiseImage(PromiseImageInfo&);
105
    void releasePromiseImage(PromiseImageInfo&);
106
private:
107
    void initPromiseImage(int index);
108
    void recordAndPlayDDL();
109
0
    bool isOnGPUThread() const { return SkGetThreadID() == fGpuThread; }
110
0
    bool isOnMainThread() const { return SkGetThreadID() == fMainThread; }
111
112
    Fuzz* fFuzz = nullptr;
113
    GrDirectContext* fContext = nullptr;
114
    AutoTArray<PromiseImageInfo> fPromiseImages{kPromiseImageCount};
115
    sk_sp<SkSurface> fSurface;
116
    GrSurfaceCharacterization fSurfaceCharacterization;
117
    std::unique_ptr<SkExecutor> fGpuExecutor = SkExecutor::MakeFIFOThreadPool(1, false);
118
    std::unique_ptr<SkExecutor> fRecordingExecutor =
119
        SkExecutor::MakeFIFOThreadPool(kRecordingThreadCount, false);
120
    SkTaskGroup fGpuTaskGroup{*fGpuExecutor};
121
    SkTaskGroup fRecordingTaskGroup{*fRecordingExecutor};
122
    SkThreadID fGpuThread = kIllegalThreadID;
123
    SkThreadID fMainThread = SkGetThreadID();
124
    std::queue<sk_sp<GrPromiseImageTexture>> fReusableTextures;
125
    sk_gpu_test::GrContextFactory fContextFactory;
126
};
127
128
28
DDLFuzzer::DDLFuzzer(Fuzz* fuzz, ContextType contextType) : fFuzz(fuzz) {
129
28
    sk_gpu_test::ContextInfo ctxInfo = fContextFactory.getContextInfo(contextType);
130
28
    sk_gpu_test::TestContext* testCtx = ctxInfo.testContext();
131
28
    fContext = ctxInfo.directContext();
132
28
    if (!fContext) {
133
28
        return;
134
28
    }
135
0
    SkISize canvasSize = kPromiseImageSize;
136
0
    canvasSize.fWidth *= kPromiseImagesPerDDL;
137
0
    SkImageInfo ii = SkImageInfo::Make(canvasSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
138
0
    fSurface = SkSurfaces::RenderTarget(fContext, skgpu::Budgeted::kNo, ii);
139
0
    if (!fSurface || !fSurface->characterize(&fSurfaceCharacterization)) {
140
0
        return;
141
0
    }
142
143
0
    testCtx->makeNotCurrent();
144
0
    fGpuTaskGroup.add([&]{
145
0
        testCtx->makeCurrent();
146
0
        fGpuThread = SkGetThreadID();
147
0
    });
148
0
    fGpuTaskGroup.wait();
149
0
    for (int i = 0; i < kPromiseImageCount; ++i) {
150
0
        this->initPromiseImage(i);
151
0
    }
152
0
}
153
154
0
sk_sp<GrPromiseImageTexture> DDLFuzzer::fulfillPromiseImage(PromiseImageInfo& promiseImage) {
155
0
    using State = PromiseImageInfo::State;
156
0
    if (!this->isOnGPUThread()) {
157
0
        fFuzz->signalBug();
158
0
    }
159
0
    bool success = make_fuzz_t<bool>(fFuzz);
160
0
    State prior = promiseImage.fState.exchange(State::kTriedToFulfill, std::memory_order_relaxed);
161
0
    if (prior != State::kInitial || promiseImage.fTexture != nullptr) {
162
0
        fFuzz->signalBug();
163
0
    }
164
0
    if (!success) {
165
0
        return nullptr;
166
0
    }
167
168
    // Try reusing an existing texture if we can and if the fuzzer wills it.
169
0
    if (!fReusableTextures.empty() && make_fuzz_t<bool>(fFuzz)) {
170
0
        promiseImage.fTexture = std::move(fReusableTextures.front());
171
0
        fReusableTextures.pop();
172
0
        return promiseImage.fTexture;
173
0
    }
174
175
0
    bool finishedBECreate = false;
176
0
    auto markFinished = [](void* context) {
177
0
        *(bool*)context = true;
178
0
    };
179
180
0
    GrBackendTexture backendTex =
181
0
            fContext->createBackendTexture(kPromiseImageSize.width(),
182
0
                                           kPromiseImageSize.height(),
183
0
                                           kRGBA_8888_SkColorType,
184
0
                                           SkColors::kRed,
185
0
                                           skgpu::Mipmapped::kNo,
186
0
                                           GrRenderable::kYes,
187
0
                                           GrProtected::kNo,
188
0
                                           markFinished,
189
0
                                           &finishedBECreate,
190
0
                                           /*label=*/"DDLFuzzer_FulFillPromiseImage");
191
0
    SkASSERT_RELEASE(backendTex.isValid());
192
0
    while (!finishedBECreate) {
193
0
        fContext->checkAsyncWorkCompletion();
194
0
    }
195
196
0
    promiseImage.fTexture = GrPromiseImageTexture::Make(backendTex);
197
198
0
    return promiseImage.fTexture;
199
0
}
200
201
0
void DDLFuzzer::releasePromiseImage(PromiseImageInfo& promiseImage) {
202
0
    using State = PromiseImageInfo::State;
203
    // TODO: This requirement will go away when we unref promise images off the GPU thread.
204
0
    if (!this->isOnGPUThread()) {
205
0
        fFuzz->signalBug();
206
0
    }
207
208
0
    State old = promiseImage.fState.exchange(State::kDone, std::memory_order_relaxed);
209
0
    if (promiseImage.fDrawn && old != State::kTriedToFulfill) {
210
0
        fFuzz->signalBug();
211
0
    }
212
213
    // If we failed to fulfill, then nothing to be done.
214
0
    if (!promiseImage.fTexture) {
215
0
        return;
216
0
    }
217
218
0
    bool reuse = make_fuzz_t<bool>(fFuzz);
219
0
    if (reuse) {
220
0
        fReusableTextures.push(std::move(promiseImage.fTexture));
221
0
    } else {
222
0
        fContext->deleteBackendTexture(promiseImage.fTexture->backendTexture());
223
0
    }
224
0
    promiseImage.fTexture = nullptr;
225
0
}
226
227
0
static sk_sp<GrPromiseImageTexture> fuzz_promise_image_fulfill(void* ctxIn) {
228
0
    PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
229
0
    return fuzzPromiseImage.fFuzzer->fulfillPromiseImage(fuzzPromiseImage);
230
0
}
231
232
0
static void fuzz_promise_image_release(void* ctxIn) {
233
0
    PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
234
0
    fuzzPromiseImage.fFuzzer->releasePromiseImage(fuzzPromiseImage);
235
0
}
236
237
0
void DDLFuzzer::initPromiseImage(int index) {
238
0
    PromiseImageInfo& promiseImage = fPromiseImages[index];
239
0
    promiseImage.fFuzzer = this;
240
0
    GrBackendFormat backendFmt = fContext->defaultBackendFormat(kRGBA_8888_SkColorType,
241
0
                                                                GrRenderable::kYes);
242
0
    promiseImage.fImage = SkImages::PromiseTextureFrom(fContext->threadSafeProxy(),
243
0
                                                       backendFmt,
244
0
                                                       kPromiseImageSize,
245
0
                                                       skgpu::Mipmapped::kNo,
246
0
                                                       kTopLeft_GrSurfaceOrigin,
247
0
                                                       kRGBA_8888_SkColorType,
248
0
                                                       kUnpremul_SkAlphaType,
249
0
                                                       SkColorSpace::MakeSRGB(),
250
0
                                                       &fuzz_promise_image_fulfill,
251
0
                                                       &fuzz_promise_image_release,
252
0
                                                       &promiseImage);
253
0
}
254
255
0
void DDLFuzzer::recordAndPlayDDL() {
256
0
    SkASSERT(!this->isOnGPUThread() && !this->isOnMainThread());
257
0
    GrDeferredDisplayListRecorder recorder(fSurfaceCharacterization);
258
0
    SkCanvas* canvas = recorder.getCanvas();
259
    // Draw promise images in a strip
260
0
    for (int i = 0; i < kPromiseImagesPerDDL; i++) {
261
0
        int xOffset = i * kPromiseImageSize.width();
262
0
        int j;
263
        // Pick random promise images to draw.
264
0
        fFuzz->nextRange(&j, 0, kPromiseImageCount - 1);
265
0
        fPromiseImages[j].fDrawn = true;
266
0
        canvas->drawImage(fPromiseImages[j].fImage, xOffset, 0);
267
0
    }
268
0
    sk_sp<GrDeferredDisplayList> ddl = recorder.detach();
269
0
    fGpuTaskGroup.add([ddl{std::move(ddl)}, this] {
270
0
        bool success = skgpu::ganesh::DrawDDL(fSurface, std::move(ddl));
271
0
        if (!success) {
272
0
            fFuzz->signalBug();
273
0
        }
274
0
    });
275
0
}
276
277
28
void DDLFuzzer::run() {
278
28
    if (!fSurface) {
279
28
        return;
280
28
    }
281
0
    fRecordingTaskGroup.batch(kIterationCount, [this](int i) { this->recordAndPlayDDL(); });
282
0
    fRecordingTaskGroup.wait();
283
284
0
    fGpuTaskGroup.add([this] { fContext->flushAndSubmit(fSurface.get(), GrSyncCpu::kYes); });
285
286
0
    fGpuTaskGroup.wait();
287
288
0
    fGpuTaskGroup.add([this] {
289
0
        while (!fReusableTextures.empty()) {
290
0
            sk_sp<GrPromiseImageTexture> gpuTexture = std::move(fReusableTextures.front());
291
0
            fContext->deleteBackendTexture(gpuTexture->backendTexture());
292
0
            fReusableTextures.pop();
293
0
        }
294
0
        fContextFactory.destroyContexts();
295
        // TODO: Release promise images not on the GPU thread.
296
0
        fPromiseImages.reset(0);
297
0
    });
298
0
    fGpuTaskGroup.wait();
299
0
}
300
301
28
DEF_FUZZ(DDLThreadingGL, fuzz) {
302
28
    DDLFuzzer(fuzz, skgpu::ContextType::kGL).run();
303
28
}