1/*
2 * Copyright 2019 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 "include/core/SkCanvas.h"
9#include "include/gpu/GrDirectContext.h"
10#include "include/gpu/GrRecordingContext.h"
11#include "src/core/SkAutoPixmapStorage.h"
12#include "src/core/SkCompressedDataUtils.h"
13#include "src/core/SkMipmap.h"
14#include "src/core/SkPaintPriv.h"
15#include "src/gpu/GrBackendUtils.h"
16#include "src/gpu/GrDirectContextPriv.h"
17#include "src/image/SkImage_Base.h"
18#include "tests/Test.h"
19#include "tests/TestUtils.h"
20#include "tools/ToolUtils.h"
21
22// Just verify that 'actual' is entirely 'expected'
23static void check_solid_pixmap(skiatest::Reporter* reporter,
24                               const SkColor4f& expected, const SkPixmap& actual,
25                               const char* label0, const char* label1, const char* label2) {
26    const float tols[4] = { 0.01f, 0.01f, 0.01f, 0.01f };
27
28    auto error = std::function<ComparePixmapsErrorReporter>(
29        [reporter, label0, label1, label2](int x, int y, const float diffs[4]) {
30            SkASSERT(x >= 0 && y >= 0);
31            ERRORF(reporter, "%s %s %s - mismatch at %d, %d (%f, %f, %f %f)",
32                   label0, label1, label2, x, y,
33                   diffs[0], diffs[1], diffs[2], diffs[3]);
34        });
35
36    CheckSolidPixels(expected, actual, tols, error);
37}
38
39// Create an SkImage to wrap 'backendTex'
40sk_sp<SkImage> create_image(GrDirectContext* dContext, const GrBackendTexture& backendTex) {
41    SkImage::CompressionType compression =
42            GrBackendFormatToCompressionType(backendTex.getBackendFormat());
43
44    SkAlphaType at = SkCompressionTypeIsOpaque(compression) ? kOpaque_SkAlphaType
45                                                            : kPremul_SkAlphaType;
46
47    return SkImage::MakeFromCompressedTexture(dContext,
48                                              backendTex,
49                                              kTopLeft_GrSurfaceOrigin,
50                                              at,
51                                              nullptr);
52}
53
54// Draw the compressed backend texture (wrapped in an SkImage) into an RGBA surface, attempting
55// to access all the mipMap levels.
56static void check_compressed_mipmaps(GrRecordingContext* rContext, sk_sp<SkImage> img,
57                                     SkImage::CompressionType compressionType,
58                                     const SkColor4f expectedColors[6],
59                                     GrMipmapped mipMapped,
60                                     skiatest::Reporter* reporter, const char* label) {
61
62    SkImageInfo readbackSurfaceII = SkImageInfo::Make(32, 32, kRGBA_8888_SkColorType,
63                                                      kPremul_SkAlphaType);
64
65    sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(rContext,
66                                                        SkBudgeted::kNo,
67                                                        readbackSurfaceII, 1,
68                                                        kTopLeft_GrSurfaceOrigin,
69                                                        nullptr);
70    if (!surf) {
71        return;
72    }
73
74    SkCanvas* canvas = surf->getCanvas();
75
76    const SkSamplingOptions sampling(SkFilterMode::kLinear,
77                                     SkMipmapMode::kLinear);
78    SkPaint p;
79    p.setBlendMode(SkBlendMode::kSrc);
80
81    int numMipLevels = 1;
82    if (mipMapped == GrMipmapped::kYes) {
83        numMipLevels = SkMipmap::ComputeLevelCount(32, 32)+1;
84    }
85
86    for (int i = 0, rectSize = 32; i < numMipLevels; ++i, rectSize /= 2) {
87        SkASSERT(rectSize >= 1);
88
89        canvas->clear(SK_ColorTRANSPARENT);
90
91        SkRect r = SkRect::MakeWH(rectSize, rectSize);
92        canvas->drawImageRect(img, r, sampling, &p);
93
94        SkImageInfo readbackII = SkImageInfo::Make(rectSize, rectSize,
95                                                   kRGBA_8888_SkColorType,
96                                                   kUnpremul_SkAlphaType);
97        SkAutoPixmapStorage actual2;
98        SkAssertResult(actual2.tryAlloc(readbackII));
99        actual2.erase(SkColors::kTransparent);
100
101        bool result = surf->readPixels(actual2, 0, 0);
102        REPORTER_ASSERT(reporter, result);
103
104        SkString str;
105        str.appendf("mip-level %d", i);
106
107        check_solid_pixmap(reporter, expectedColors[i], actual2,
108                           GrCompressionTypeToStr(compressionType), label, str.c_str());
109    }
110}
111
112// Verify that we can readback from a compressed texture
113static void check_readback(GrDirectContext* dContext, sk_sp<SkImage> img,
114                           SkImage::CompressionType compressionType,
115                           const SkColor4f& expectedColor,
116                           skiatest::Reporter* reporter, const char* label) {
117#ifdef SK_BUILD_FOR_IOS
118    // reading back ETC2 is broken on Metal/iOS (skbug.com/9839)
119    if (dContext->backend() == GrBackendApi::kMetal) {
120      return;
121    }
122#endif
123
124    SkAutoPixmapStorage actual;
125
126    SkImageInfo readBackII = SkImageInfo::Make(img->width(), img->height(),
127                                               kRGBA_8888_SkColorType,
128                                               kUnpremul_SkAlphaType);
129
130    SkAssertResult(actual.tryAlloc(readBackII));
131    actual.erase(SkColors::kTransparent);
132
133    bool result = img->readPixels(dContext, actual, 0, 0);
134    REPORTER_ASSERT(reporter, result);
135
136    check_solid_pixmap(reporter, expectedColor, actual,
137                       GrCompressionTypeToStr(compressionType), label, "");
138}
139
140// Test initialization of compressed GrBackendTextures to a specific color
141static void test_compressed_color_init(GrDirectContext* dContext,
142                                       skiatest::Reporter* reporter,
143                                       std::function<GrBackendTexture (GrDirectContext*,
144                                                                       const SkColor4f&,
145                                                                       GrMipmapped)> create,
146                                       const SkColor4f& color,
147                                       SkImage::CompressionType compression,
148                                       GrMipmapped mipMapped) {
149    GrBackendTexture backendTex = create(dContext, color, mipMapped);
150    if (!backendTex.isValid()) {
151        return;
152    }
153
154    sk_sp<SkImage> img = create_image(dContext, backendTex);
155    if (!img) {
156        return;
157    }
158
159    SkColor4f expectedColors[6] = { color, color, color, color, color, color };
160
161    check_compressed_mipmaps(dContext, img, compression, expectedColors, mipMapped,
162                             reporter, "colorinit");
163    check_readback(dContext, img, compression, color, reporter, "solid readback");
164
165    SkColor4f newColor;
166    newColor.fR = color.fB;
167    newColor.fG = color.fR;
168    newColor.fB = color.fG;
169    newColor.fA = color.fA;
170
171    bool result = dContext->updateCompressedBackendTexture(backendTex, newColor, nullptr, nullptr);
172    // Since we were able to create the compressed texture we should be able to update it.
173    REPORTER_ASSERT(reporter, result);
174
175    SkColor4f expectedNewColors[6] = {newColor, newColor, newColor, newColor, newColor, newColor};
176
177    check_compressed_mipmaps(dContext, img, compression, expectedNewColors, mipMapped, reporter,
178                             "colorinit");
179    check_readback(dContext, std::move(img), compression, newColor, reporter, "solid readback");
180
181    dContext->deleteBackendTexture(backendTex);
182}
183
184// Create compressed data pulling the color for each mipmap level from 'levelColors'.
185static std::unique_ptr<const char[]> make_compressed_data(SkImage::CompressionType compression,
186                                                          SkColor4f levelColors[6],
187                                                          GrMipmapped mipMapped) {
188    SkISize dimensions { 32, 32 };
189
190    int numMipLevels = 1;
191    if (mipMapped == GrMipmapped::kYes) {
192        numMipLevels = SkMipmap::ComputeLevelCount(dimensions.width(), dimensions.height()) + 1;
193    }
194
195    SkTArray<size_t> mipMapOffsets(numMipLevels);
196
197    size_t dataSize = SkCompressedDataSize(compression, dimensions, &mipMapOffsets,
198                                           mipMapped == GrMipmapped::kYes);
199    char* data = new char[dataSize];
200
201    for (int level = 0; level < numMipLevels; ++level) {
202        // We have to do this a level at a time bc we might have a different color for
203        // each level
204        GrFillInCompressedData(compression, dimensions,
205                               GrMipmapped::kNo, &data[mipMapOffsets[level]], levelColors[level]);
206
207        dimensions = {std::max(1, dimensions.width() /2), std::max(1, dimensions.height()/2)};
208    }
209
210    return std::unique_ptr<const char[]>(data);
211}
212
213// Verify that we can initialize a compressed backend texture with data (esp.
214// the mipmap levels).
215static void test_compressed_data_init(GrDirectContext* dContext,
216                                      skiatest::Reporter* reporter,
217                                      std::function<GrBackendTexture (GrDirectContext*,
218                                                                      const char* data,
219                                                                      size_t dataSize,
220                                                                      GrMipmapped)> create,
221                                      SkImage::CompressionType compression,
222                                      GrMipmapped mipMapped) {
223
224    SkColor4f expectedColors[6] = {
225        { 1.0f, 0.0f, 0.0f, 1.0f }, // R
226        { 0.0f, 1.0f, 0.0f, 1.0f }, // G
227        { 0.0f, 0.0f, 1.0f, 1.0f }, // B
228        { 0.0f, 1.0f, 1.0f, 1.0f }, // C
229        { 1.0f, 0.0f, 1.0f, 1.0f }, // M
230        { 1.0f, 1.0f, 0.0f, 1.0f }, // Y
231    };
232
233    std::unique_ptr<const char[]> data(make_compressed_data(compression, expectedColors,
234                                                            mipMapped));
235    size_t dataSize = SkCompressedDataSize(compression, { 32, 32 }, nullptr,
236                                           mipMapped == GrMipmapped::kYes);
237
238    GrBackendTexture backendTex = create(dContext, data.get(), dataSize, mipMapped);
239    if (!backendTex.isValid()) {
240        return;
241    }
242
243    sk_sp<SkImage> img = create_image(dContext, backendTex);
244    if (!img) {
245        return;
246    }
247
248    check_compressed_mipmaps(dContext, img, compression, expectedColors,
249                             mipMapped, reporter, "pixmap");
250    check_readback(dContext, img, compression, expectedColors[0], reporter, "data readback");
251
252    SkColor4f expectedColorsNew[6] = {
253        {1.0f, 1.0f, 0.0f, 1.0f},  // Y
254        {1.0f, 0.0f, 0.0f, 1.0f},  // R
255        {0.0f, 1.0f, 0.0f, 1.0f},  // G
256        {0.0f, 0.0f, 1.0f, 1.0f},  // B
257        {0.0f, 1.0f, 1.0f, 1.0f},  // C
258        {1.0f, 0.0f, 1.0f, 1.0f},  // M
259    };
260
261    std::unique_ptr<const char[]> dataNew(
262            make_compressed_data(compression, expectedColorsNew, mipMapped));
263    size_t dataNewSize =
264            SkCompressedDataSize(compression, {32, 32}, nullptr, mipMapped == GrMipMapped::kYes);
265
266    bool result = dContext->updateCompressedBackendTexture(backendTex, dataNew.get(), dataNewSize,
267                                                           nullptr, nullptr);
268    // Since we were able to create the compressed texture we should be able to update it.
269    REPORTER_ASSERT(reporter, result);
270
271    check_compressed_mipmaps(dContext, img, compression, expectedColorsNew, mipMapped, reporter,
272                             "pixmap");
273    check_readback(dContext, std::move(img), compression, expectedColorsNew[0], reporter,
274                   "data readback");
275
276    dContext->deleteBackendTexture(backendTex);
277}
278
279DEF_GPUTEST_FOR_RENDERING_CONTEXTS(CompressedBackendAllocationTest, reporter, ctxInfo) {
280    auto dContext = ctxInfo.directContext();
281    const GrCaps* caps = dContext->priv().caps();
282
283    struct {
284        SkImage::CompressionType fCompression;
285        SkColor4f                fColor;
286    } combinations[] = {
287        { SkImage::CompressionType::kETC2_RGB8_UNORM, SkColors::kRed },
288        { SkImage::CompressionType::kBC1_RGB8_UNORM,  SkColors::kBlue },
289        { SkImage::CompressionType::kBC1_RGBA8_UNORM, SkColors::kTransparent },
290    };
291
292    for (auto combo : combinations) {
293        GrBackendFormat format = dContext->compressedBackendFormat(combo.fCompression);
294        if (!format.isValid()) {
295            continue;
296        }
297
298        if (!caps->isFormatTexturable(format, GrTextureType::k2D)) {
299            continue;
300        }
301
302        for (auto mipMapped : { GrMipmapped::kNo, GrMipmapped::kYes }) {
303            if (GrMipmapped::kYes == mipMapped && !caps->mipmapSupport()) {
304                continue;
305            }
306
307            // color initialized
308            {
309                auto createWithColorMtd = [format](GrDirectContext* dContext,
310                                                   const SkColor4f& color,
311                                                   GrMipmapped mipMapped) {
312                    return dContext->createCompressedBackendTexture(32, 32, format, color,
313                                                                    mipMapped, GrProtected::kNo);
314                };
315
316                test_compressed_color_init(dContext, reporter, createWithColorMtd,
317                                           combo.fColor, combo.fCompression, mipMapped);
318            }
319
320            // data initialized
321            {
322                auto createWithDataMtd = [format](GrDirectContext* dContext,
323                                                  const char* data, size_t dataSize,
324                                                  GrMipmapped mipMapped) {
325                    return dContext->createCompressedBackendTexture(32, 32, format, data, dataSize,
326                                                                    mipMapped, GrProtected::kNo);
327                };
328
329                test_compressed_data_init(dContext, reporter, createWithDataMtd,
330                                          combo.fCompression, mipMapped);
331            }
332
333        }
334    }
335}
336