xref: /aosp_15_r20/external/skia/tests/CompressedBackendAllocationTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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