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'
check_solid_pixmap(skiatest::Reporter * reporter,const SkColor4f & expected,const SkPixmap & actual,const char * label0,const char * label1,const char * label2)23 static 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'
create_image(GrDirectContext * dContext,const GrBackendTexture & backendTex)40 sk_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.
check_compressed_mipmaps(GrRecordingContext * rContext,sk_sp<SkImage> img,SkImage::CompressionType compressionType,const SkColor4f expectedColors[6],GrMipmapped mipMapped,skiatest::Reporter * reporter,const char * label)56 static 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
check_readback(GrDirectContext * dContext,sk_sp<SkImage> img,SkImage::CompressionType compressionType,const SkColor4f & expectedColor,skiatest::Reporter * reporter,const char * label)113 static 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
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)141 static 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'.
make_compressed_data(SkImage::CompressionType compression,SkColor4f levelColors[6],GrMipmapped mipMapped)185 static 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).
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)215 static 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
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(CompressedBackendAllocationTest,reporter,ctxInfo)279 DEF_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