1/*
2 * Copyright 2019 Google Inc. and Adobe 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 "include/core/SkCanvas.h"
9#include "include/core/SkPaint.h"
10#include "include/core/SkSurface.h"
11#include "include/core/SkTypes.h"
12#include "include/gpu/GrDirectContext.h"
13#include "samplecode/Sample.h"
14#include "tools/timer/TimeUtils.h"
15
16/**
17 * This sample exercises heavy texture updates and uploads.
18 */
19class TextureUploadSample : public Sample {
20    inline static constexpr int kMinTileSize = 128;
21    inline static constexpr int kMaxTileSize = 2048;
22    inline static constexpr float kGridScale = 0.25f;
23
24    bool fDrawTexturesToScreen = true;
25    int fTileSize = 256;
26    int fTileRows = 8;
27    int fTileCols = 8;
28
29    sk_sp<SkSurface> fBlueSurface;
30    sk_sp<SkSurface> fGraySurface;
31
32    class RenderTargetTexture : public SkRefCnt {
33    public:
34        RenderTargetTexture(GrDirectContext* direct, int size) {
35            SkSurfaceProps surfaceProps(0, kRGB_H_SkPixelGeometry);
36            SkImageInfo imageInfo = SkImageInfo::Make(size, size, kRGBA_8888_SkColorType,
37                                                      kPremul_SkAlphaType);
38            fSurface = SkSurface::MakeRenderTarget(direct, SkBudgeted::kNo, imageInfo, 0,
39                                                   &surfaceProps);
40        }
41
42        sk_sp<SkImage> getImage() {
43            return fSurface->makeImageSnapshot();
44        }
45
46        void uploadRasterSurface(sk_sp<SkSurface> rasterSurface) {
47            SkPixmap pixmap;
48            rasterSurface->peekPixels(&pixmap);
49            fSurface->writePixels(pixmap, 0, 0);
50        }
51
52    private:
53        sk_sp<SkSurface> fSurface;
54        sk_sp<SkImage> fCachedImage;
55    };
56
57    SkTArray<sk_sp<RenderTargetTexture>> fTextures;
58    GrDirectContext* fCachedContext = nullptr;
59    SkScalar fActiveTileIndex = 0;
60
61    SkString name() override {
62        return SkString("TextureUpload");
63    }
64
65    bool onChar(SkUnichar uni) override {
66        if ('m' == uni) {
67            fDrawTexturesToScreen = !fDrawTexturesToScreen;
68            return true;
69        } else if ('>' == uni) {
70            fTileSize = std::min(kMaxTileSize, 2*fTileSize);
71            fTileRows = kMaxTileSize/fTileSize;
72            fTileCols = kMaxTileSize/fTileSize;
73            fCachedContext = nullptr;
74        } else if ('<' == uni) {
75            fTileSize = std::max(kMinTileSize, fTileSize/2);
76            fTileRows = kMaxTileSize/fTileSize;
77            fTileCols = kMaxTileSize/fTileSize;
78            fCachedContext = nullptr;
79        }
80        return false;
81    }
82
83    sk_sp<SkSurface> getFilledRasterSurface(SkColor color, int size) {
84        sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(size, size));
85        SkCanvas* canvas = surface->getCanvas();
86        canvas->clear(color);
87        return surface;
88    }
89
90    void onOnceBeforeDraw() override {
91        this->setBGColor(0xFFFFFFFF);
92        this->setSize(1024, 1024);
93    }
94
95    void initializeTextures(GrDirectContext* direct) {
96        fTextures.reset();
97        int textureCount = fTileRows * fTileCols;
98        for (int i = 0; i < textureCount; i++) {
99            fTextures.emplace_back(new RenderTargetTexture(direct, fTileSize));
100        }
101
102        // Construct two simple rasters of differing colors to serve
103        // as cpu rasterized data to refresh textures with.
104        fBlueSurface = this->getFilledRasterSurface(SK_ColorBLUE, fTileSize);
105        fGraySurface = this->getFilledRasterSurface(SK_ColorGRAY, fTileSize);
106    }
107
108    void onDrawContent(SkCanvas* canvas) override {
109#if SK_SUPPORT_GPU
110        auto direct = GrAsDirectContext(canvas->recordingContext());
111        if (direct) {
112            // One-time context-specific setup.
113            if (direct != fCachedContext) {
114                fCachedContext = direct;
115                this->initializeTextures(direct);
116            }
117
118            // Upload new texture data for all textures, simulating a full page of tiles
119            // needing refresh.
120            int textureCount = fTileRows * fTileCols;
121            for (int i = 0; i < textureCount; i++) {
122                fTextures[i]->uploadRasterSurface(i == fActiveTileIndex ? fBlueSurface
123                                                                        : fGraySurface);
124            }
125
126            // Scale grid.
127            canvas->scale(kGridScale, kGridScale);
128
129            if (fDrawTexturesToScreen) {
130                for (int y = 0; y < fTileRows; y++) {
131                    for (int x = 0; x < fTileCols; x++) {
132                        int currentIndex = y * fTileCols + x;
133                        canvas->drawImage(fTextures[currentIndex]->getImage(),
134                                          x * fTileSize, y * fTileSize);
135                    }
136                }
137            }
138        }
139#endif
140    }
141
142    bool onAnimate(double nanos) override {
143        constexpr SkScalar kDesiredDurationSecs = 16.0f;
144        float numTiles = fTileRows*fTileCols;
145        fActiveTileIndex = floorf(TimeUtils::Scaled(1e-9 * nanos,
146                                                    numTiles/kDesiredDurationSecs, numTiles));
147        return true;
148    }
149};
150
151
152DEF_SAMPLE( return new TextureUploadSample(); )
153
154