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