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