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
32using 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!
37template <typename T>
38inline T make_fuzz_t(Fuzz* fuzz) {
39    T t;
40    fuzz->next(&t);
41    return t;
42}
43
44class 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.
48class PromiseImageInfo : public SkNVRefCnt<PromiseImageInfo>, SkNoncopyable {
49public:
50    enum class State : int {
51        kInitial,
52        kTriedToFulfill,
53        kDone
54    };
55    ~PromiseImageInfo() {
56        // If we hit this, then the image or the texture will outlive this object which is bad.
57        SkASSERT_RELEASE(fImage->unique());
58        SkASSERT_RELEASE(!fTexture || fTexture->unique());
59        fImage.reset();
60        fTexture.reset();
61        State s = fState;
62        SkASSERT_RELEASE(s == State::kDone);
63    }
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
72static constexpr int kPromiseImageCount = 8;
73static constexpr SkISize kPromiseImageSize{16, 16};
74static constexpr int kPromiseImagesPerDDL = 4;
75static constexpr int kRecordingThreadCount = 4;
76static 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.
82class DDLFuzzer : SkNoncopyable {
83public:
84    DDLFuzzer(Fuzz*, ContextType);
85    void run();
86
87    sk_sp<SkPromiseImageTexture> fulfillPromiseImage(PromiseImageInfo&);
88    void releasePromiseImage(PromiseImageInfo&);
89private:
90    void initPromiseImage(int index);
91    void recordAndPlayDDL();
92    bool isOnGPUThread() const { return SkGetThreadID() == fGpuThread; }
93    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
111DDLFuzzer::DDLFuzzer(Fuzz* fuzz, ContextType contextType) : fFuzz(fuzz) {
112    sk_gpu_test::ContextInfo ctxInfo = fContextFactory.getContextInfo(contextType);
113    sk_gpu_test::TestContext* testCtx = ctxInfo.testContext();
114    fContext = ctxInfo.directContext();
115    if (!fContext) {
116        return;
117    }
118    SkISize canvasSize = kPromiseImageSize;
119    canvasSize.fWidth *= kPromiseImagesPerDDL;
120    SkImageInfo ii = SkImageInfo::Make(canvasSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
121    fSurface = SkSurface::MakeRenderTarget(fContext, SkBudgeted::kNo, ii);
122    if (!fSurface || !fSurface->characterize(&fSurfaceCharacterization)) {
123        return;
124    }
125
126    testCtx->makeNotCurrent();
127    fGpuTaskGroup.add([&]{
128        testCtx->makeCurrent();
129        fGpuThread = SkGetThreadID();
130    });
131    fGpuTaskGroup.wait();
132    for (int i = 0; i < kPromiseImageCount; ++i) {
133        this->initPromiseImage(i);
134    }
135}
136
137sk_sp<SkPromiseImageTexture> DDLFuzzer::fulfillPromiseImage(PromiseImageInfo& promiseImage) {
138    using State = PromiseImageInfo::State;
139    if (!this->isOnGPUThread()) {
140        fFuzz->signalBug();
141    }
142    bool success = make_fuzz_t<bool>(fFuzz);
143    State prior = promiseImage.fState.exchange(State::kTriedToFulfill, std::memory_order_relaxed);
144    if (prior != State::kInitial || promiseImage.fTexture != nullptr) {
145        fFuzz->signalBug();
146    }
147    if (!success) {
148        return nullptr;
149    }
150
151    // Try reusing an existing texture if we can and if the fuzzer wills it.
152    if (!fReusableTextures.empty() && make_fuzz_t<bool>(fFuzz)) {
153        promiseImage.fTexture = std::move(fReusableTextures.front());
154        fReusableTextures.pop();
155        return promiseImage.fTexture;
156    }
157
158    bool finishedBECreate = false;
159    auto markFinished = [](void* context) {
160        *(bool*)context = true;
161    };
162
163    GrBackendTexture backendTex = fContext->createBackendTexture(kPromiseImageSize.width(),
164                                                                 kPromiseImageSize.height(),
165                                                                 kRGBA_8888_SkColorType,
166                                                                 SkColors::kRed,
167                                                                 GrMipMapped::kNo,
168                                                                 GrRenderable::kYes,
169                                                                 GrProtected::kNo,
170                                                                 markFinished,
171                                                                 &finishedBECreate);
172    SkASSERT_RELEASE(backendTex.isValid());
173    while (!finishedBECreate) {
174        fContext->checkAsyncWorkCompletion();
175    }
176
177    promiseImage.fTexture = SkPromiseImageTexture::Make(backendTex);
178
179    return promiseImage.fTexture;
180}
181
182void DDLFuzzer::releasePromiseImage(PromiseImageInfo& promiseImage) {
183    using State = PromiseImageInfo::State;
184    // TODO: This requirement will go away when we unref promise images off the GPU thread.
185    if (!this->isOnGPUThread()) {
186        fFuzz->signalBug();
187    }
188    State old = promiseImage.fState.exchange(State::kInitial, std::memory_order_relaxed);
189    if (old != State::kTriedToFulfill) {
190        fFuzz->signalBug();
191    }
192
193    // If we failed to fulfill, then nothing to be done.
194    if (!promiseImage.fTexture) {
195        return;
196    }
197
198    bool reuse = make_fuzz_t<bool>(fFuzz);
199    if (reuse) {
200        fReusableTextures.push(std::move(promiseImage.fTexture));
201    } else {
202        fContext->deleteBackendTexture(promiseImage.fTexture->backendTexture());
203    }
204    promiseImage.fTexture = nullptr;
205}
206
207static sk_sp<SkPromiseImageTexture> fuzz_promise_image_fulfill(void* ctxIn) {
208    PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
209    return fuzzPromiseImage.fFuzzer->fulfillPromiseImage(fuzzPromiseImage);
210}
211
212static void fuzz_promise_image_release(void* ctxIn) {
213    PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
214    fuzzPromiseImage.fFuzzer->releasePromiseImage(fuzzPromiseImage);
215}
216
217void DDLFuzzer::initPromiseImage(int index) {
218    PromiseImageInfo& promiseImage = fPromiseImages[index];
219    promiseImage.fFuzzer = this;
220    GrBackendFormat backendFmt = fContext->defaultBackendFormat(kRGBA_8888_SkColorType,
221                                                                GrRenderable::kYes);
222    promiseImage.fImage = SkImage::MakePromiseTexture(fContext->threadSafeProxy(),
223                                                      backendFmt,
224                                                      kPromiseImageSize,
225                                                      GrMipMapped::kNo,
226                                                      kTopLeft_GrSurfaceOrigin,
227                                                      kRGBA_8888_SkColorType,
228                                                      kUnpremul_SkAlphaType,
229                                                      SkColorSpace::MakeSRGB(),
230                                                      &fuzz_promise_image_fulfill,
231                                                      &fuzz_promise_image_release,
232                                                      &promiseImage);
233}
234
235void DDLFuzzer::recordAndPlayDDL() {
236    SkASSERT(!this->isOnGPUThread() && !this->isOnMainThread());
237    SkDeferredDisplayListRecorder recorder(fSurfaceCharacterization);
238    SkCanvas* canvas = recorder.getCanvas();
239    // Draw promise images in a strip
240    for (int i = 0; i < kPromiseImagesPerDDL; i++) {
241        int xOffset = i * kPromiseImageSize.width();
242        int j;
243        // Pick random promise images to draw.
244        fFuzz->nextRange(&j, 0, kPromiseImageCount - 1);
245        canvas->drawImage(fPromiseImages[j].fImage, xOffset, 0);
246    }
247    sk_sp<SkDeferredDisplayList> ddl = recorder.detach();
248    fGpuTaskGroup.add([=, ddl{std::move(ddl)}]{
249        bool success = fSurface->draw(std::move(ddl));
250        if (!success) {
251            fFuzz->signalBug();
252        }
253    });
254}
255
256void DDLFuzzer::run() {
257    if (!fSurface) {
258        return;
259    }
260    fRecordingTaskGroup.batch(kIterationCount, [=](int i) {
261        this->recordAndPlayDDL();
262    });
263    fRecordingTaskGroup.wait();
264    fGpuTaskGroup.add([=] {
265        while (!fReusableTextures.empty()) {
266            sk_sp<SkPromiseImageTexture> gpuTexture = std::move(fReusableTextures.front());
267            fContext->deleteBackendTexture(gpuTexture->backendTexture());
268            fReusableTextures.pop();
269        }
270        fContextFactory.destroyContexts();
271        // TODO: Release promise images not on the GPU thread.
272        fPromiseImages.reset(0);
273    });
274    fGpuTaskGroup.wait();
275}
276
277DEF_FUZZ(DDLThreadingGL, fuzz) {
278    DDLFuzzer(fuzz, ContextType::kGL_ContextType).run();
279}
280