Coverage Report

Created: 2021-08-22 09:07

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