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