1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/viz/common/gl_scaler.h"
6 
7 #include <sstream>
8 
9 #include "base/strings/pattern.h"
10 #include "build/build_config.h"
11 #include "cc/test/pixel_test.h"
12 #include "cc/test/pixel_test_utils.h"
13 #include "components/viz/common/gl_scaler_test_util.h"
14 #include "components/viz/common/gpu/context_provider.h"
15 #include "gpu/GLES2/gl2chromium.h"
16 #include "gpu/GLES2/gl2extchromium.h"
17 #include "gpu/command_buffer/client/gles2_implementation.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "third_party/skia/include/core/SkBitmap.h"
20 #include "third_party/skia/include/core/SkColor.h"
21 #include "ui/gfx/color_space.h"
22 
23 #if defined(OS_ANDROID)
24 #include "base/android/build_info.h"
25 #endif
26 
27 namespace viz {
28 
29 #define EXPECT_STRING_MATCHES(expected, actual)                     \
30   if (!base::MatchPattern(actual, expected)) {                      \
31     ADD_FAILURE() << "\nActual: " << (actual)                       \
32                   << "\nExpected to match pattern: " << (expected); \
33   }
34 
35 class GLScalerPixelTest : public cc::PixelTest, public GLScalerTestUtil {
36  public:
scaler() const37   GLScaler* scaler() const { return scaler_.get(); }
38 
GetScalerString() const39   std::string GetScalerString() const {
40     std::ostringstream oss;
41     oss << *scaler_;
42     return oss.str();
43   }
44 
CreateTexture(const gfx::Size & size)45   GLuint CreateTexture(const gfx::Size& size) {
46     return texture_helper_->CreateTexture(size);
47   }
48 
UploadTexture(const SkBitmap & bitmap)49   GLuint UploadTexture(const SkBitmap& bitmap) {
50     return texture_helper_->UploadTexture(bitmap);
51   }
52 
DownloadTexture(GLuint texture,const gfx::Size & size)53   SkBitmap DownloadTexture(GLuint texture, const gfx::Size& size) {
54     return texture_helper_->DownloadTexture(texture, size);
55   }
56 
57   // Test convenience to upload |src_bitmap| to the GPU, execute the scaling,
58   // then download the result from the GPU and return it as a SkBitmap.
Scale(const SkBitmap & src_bitmap,const gfx::Vector2d & src_offset,const gfx::Rect & output_rect)59   SkBitmap Scale(const SkBitmap& src_bitmap,
60                  const gfx::Vector2d& src_offset,
61                  const gfx::Rect& output_rect) {
62     const GLuint src_texture = UploadTexture(src_bitmap);
63     const GLuint dest_texture = CreateTexture(output_rect.size());
64     if (!scaler()->Scale(src_texture,
65                          gfx::Size(src_bitmap.width(), src_bitmap.height()),
66                          src_offset, dest_texture, output_rect)) {
67       return SkBitmap();
68     }
69     return DownloadTexture(dest_texture, output_rect.size());
70   }
71 
72   // Returns the amount of color error expected due to bugs in the current
73   // platform's bilinear texture sampler.
GetBaselineColorDifference() const74   int GetBaselineColorDifference() const {
75 #if defined(OS_ANDROID)
76     // Android seems to have texture sampling problems that are not at all seen
77     // on any of the desktop platforms. Also, versions before Marshmallow seem
78     // to have a much larger accuracy issues.
79     if (base::android::BuildInfo::GetInstance()->sdk_int() <
80         base::android::SDK_VERSION_MARSHMALLOW) {
81       return 12;
82     }
83     return 2;
84 #else
85     return 0;
86 #endif
87   }
88 
89  protected:
SetUp()90   void SetUp() final {
91     cc::PixelTest::SetUpGLWithoutRenderer(gfx::SurfaceOrigin::kBottomLeft);
92 
93     scaler_ = std::make_unique<GLScaler>(context_provider());
94     gl_ = context_provider()->ContextGL();
95     CHECK(gl_);
96     texture_helper_ = std::make_unique<GLScalerTestTextureHelper>(gl_);
97   }
98 
IsAndroidMarshmallow()99   bool IsAndroidMarshmallow() {
100 #if defined(OS_ANDROID)
101     return base::android::BuildInfo::GetInstance()->sdk_int() ==
102            base::android::SDK_VERSION_MARSHMALLOW;
103 #else
104     return false;
105 #endif
106   }
107 
TearDown()108   void TearDown() final {
109     texture_helper_.reset();
110     gl_ = nullptr;
111     scaler_.reset();
112 
113     cc::PixelTest::TearDown();
114   }
115 
116  private:
117   std::unique_ptr<GLScaler> scaler_;
118   gpu::gles2::GLES2Interface* gl_ = nullptr;
119   std::unique_ptr<GLScalerTestTextureHelper> texture_helper_;
120 };
121 
122 // Tests that the default GLScaler::Parameters produces an unscaled copy.
TEST_F(GLScalerPixelTest,CopiesByDefault)123 TEST_F(GLScalerPixelTest, CopiesByDefault) {
124   // Disabled on Marshmallow. See crbug.com/933080
125   if (IsAndroidMarshmallow())
126     return;
127 
128   ASSERT_TRUE(scaler()->Configure(GLScaler::Parameters()));
129   EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
130   const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
131   const SkBitmap actual =
132       Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize));
133   int max_color_diff = GetBaselineColorDifference();
134   EXPECT_TRUE(LooksLikeSMPTETestImage(
135       actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 0, &max_color_diff))
136       << "max_color_diff measured was " << max_color_diff
137       << "\nActual: " << cc::GetPNGDataUrl(actual);
138 }
139 
140 // Tests a FAST quality scaling of 2→1 in X and 3→2 in Y.
TEST_F(GLScalerPixelTest,ScalesAtFastQuality)141 TEST_F(GLScalerPixelTest, ScalesAtFastQuality) {
142   GLScaler::Parameters params;
143   params.scale_from = gfx::Vector2d(2, 3);
144   params.scale_to = gfx::Vector2d(1, 2);
145   params.quality = GLScaler::Parameters::Quality::FAST;
146   params.is_flipped_source = false;
147   ASSERT_TRUE(scaler()->Configure(params));
148   EXPECT_EQ(u8"Output ← {BILINEAR/lowp [2 3] to [1 2]} ← Source",
149             GetScalerString());
150   const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
151   static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
152   static_assert(kSMPTEFullSize.height() % 3 == 0, "Fix kSMPTEFullSize.");
153   const SkBitmap actual = Scale(source, gfx::Vector2d(),
154                                 gfx::Rect(0, 0, kSMPTEFullSize.width() / 2,
155                                           kSMPTEFullSize.height() * 2 / 3));
156   int max_color_diff = GetBaselineColorDifference();
157   EXPECT_TRUE(LooksLikeSMPTETestImage(
158       actual, kSMPTEFullSize, gfx::Rect(kSMPTEFullSize), 2, &max_color_diff))
159       << "max_color_diff measured was " << max_color_diff
160       << "\nActual: " << cc::GetPNGDataUrl(actual);
161 }
162 
163 // Tests a GOOD quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest,ScalesALittleAtGoodQuality)164 TEST_F(GLScalerPixelTest, ScalesALittleAtGoodQuality) {
165   GLScaler::Parameters params;
166   params.scale_from = gfx::Vector2d(1280, 720);
167   params.scale_to = gfx::Vector2d(1024, 700);
168   params.quality = GLScaler::Parameters::Quality::GOOD;
169   params.is_flipped_source = false;
170   ASSERT_TRUE(scaler()->Configure(params));
171   EXPECT_EQ(u8"Output ← {BILINEAR2X2/lowp [1280 720] to [1024 700]} ← Source",
172             GetScalerString());
173   constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
174   const SkBitmap source = CreateSMPTETestImage(kSourceSize);
175   const SkBitmap actual =
176       Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
177   int max_color_diff = GetBaselineColorDifference();
178   EXPECT_TRUE(LooksLikeSMPTETestImage(
179       actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
180       << "max_color_diff measured was " << max_color_diff
181       << "\nActual: " << cc::GetPNGDataUrl(actual);
182 }
183 
184 // Tests a large, skewed reduction at GOOD quality: 3840x720 → 128x256.
TEST_F(GLScalerPixelTest,ScalesALotHorizontallyAtGoodQuality)185 TEST_F(GLScalerPixelTest, ScalesALotHorizontallyAtGoodQuality) {
186   GLScaler::Parameters params;
187   params.scale_from = gfx::Vector2d(3840, 720);
188   params.scale_to = gfx::Vector2d(128, 256);
189   params.quality = GLScaler::Parameters::Quality::GOOD;
190   params.is_flipped_source = false;
191   ASSERT_TRUE(scaler()->Configure(params));
192   EXPECT_EQ(
193       u8"Output "
194       u8"← {BILINEAR/lowp [256 256] to [128 256]} "
195       u8"← {BILINEAR4/lowp [2048 512] to [256 256]} "
196       u8"← {BILINEAR2X2/lowp [3840 720] to [2048 512]} "
197       u8"← Source",
198       GetScalerString());
199   constexpr gfx::Size kSourceSize = gfx::Size(3840, 720);
200   const SkBitmap source = CreateSMPTETestImage(kSourceSize);
201   const SkBitmap actual =
202       Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 128, 256));
203   int max_color_diff = GetBaselineColorDifference();
204   EXPECT_TRUE(LooksLikeSMPTETestImage(
205       actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
206       << "max_color_diff measured was " << max_color_diff
207       << "\nActual: " << cc::GetPNGDataUrl(actual);
208 }
209 
210 // Tests a large, skewed reduction at GOOD quality: 640x2160 → 256x128.
TEST_F(GLScalerPixelTest,ScalesALotVerticallyAtGoodQuality)211 TEST_F(GLScalerPixelTest, ScalesALotVerticallyAtGoodQuality) {
212   GLScaler::Parameters params;
213   params.scale_from = gfx::Vector2d(640, 2160);
214   params.scale_to = gfx::Vector2d(256, 128);
215   params.quality = GLScaler::Parameters::Quality::GOOD;
216   params.is_flipped_source = false;
217   ASSERT_TRUE(scaler()->Configure(params));
218   EXPECT_EQ(
219       u8"Output "
220       u8"← {BILINEAR/lowp [256 256] to [256 128]} "
221       u8"← {BILINEAR4/lowp [512 2048] to [256 256]} "
222       u8"← {BILINEAR2X2/lowp [640 2160] to [512 2048]} "
223       u8"← Source",
224       GetScalerString());
225   constexpr gfx::Size kSourceSize = gfx::Size(640, 2160);
226   const SkBitmap source = CreateSMPTETestImage(kSourceSize);
227   const SkBitmap actual =
228       Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 256, 128));
229   int max_color_diff = GetBaselineColorDifference();
230   EXPECT_TRUE(LooksLikeSMPTETestImage(
231       actual, kSourceSize, gfx::Rect(kSourceSize), 2, &max_color_diff))
232       << "max_color_diff measured was " << max_color_diff
233       << "\nActual: " << cc::GetPNGDataUrl(actual);
234 }
235 
236 // Tests a BEST quality scaling of 1280x720 → 1024x700.
TEST_F(GLScalerPixelTest,ScalesAtBestQuality)237 TEST_F(GLScalerPixelTest, ScalesAtBestQuality) {
238   GLScaler::Parameters params;
239   params.scale_from = gfx::Vector2d(1280, 720);
240   params.scale_to = gfx::Vector2d(1024, 700);
241   params.quality = GLScaler::Parameters::Quality::BEST;
242   params.is_flipped_source = false;
243   ASSERT_TRUE(scaler()->Configure(params));
244   EXPECT_EQ(
245       u8"Output "
246       u8"← {BICUBIC_HALF_1D/lowp [2048 700] to [1024 700]} "
247       u8"← {BICUBIC_UPSCALE/lowp [1280 700] to [2048 700]} "
248       u8"← {BICUBIC_HALF_1D/lowp [1280 1400] to [1280 700]} "
249       u8"← {BICUBIC_UPSCALE/lowp [1280 720] to [1280 1400]} "
250       u8"← Source",
251       GetScalerString());
252   constexpr gfx::Size kSourceSize = gfx::Size(1280, 720);
253   const SkBitmap source = CreateSMPTETestImage(kSourceSize);
254   const SkBitmap actual =
255       Scale(source, gfx::Vector2d(), gfx::Rect(0, 0, 1024, 700));
256   int max_color_diff = GetBaselineColorDifference();
257   EXPECT_TRUE(LooksLikeSMPTETestImage(
258       actual, kSourceSize, gfx::Rect(kSourceSize), 4, &max_color_diff))
259       << "max_color_diff measured was " << max_color_diff
260       << "\nActual: " << cc::GetPNGDataUrl(actual);
261 }
262 
263 // Tests that a source offset can be provided to sample the source starting at a
264 // different location.
TEST_F(GLScalerPixelTest,TranslatesWithSourceOffset)265 TEST_F(GLScalerPixelTest, TranslatesWithSourceOffset) {
266   // Disabled on Marshmallow. See crbug.com/933080
267   if (IsAndroidMarshmallow())
268     return;
269 
270   GLScaler::Parameters params;
271   params.is_flipped_source = false;
272   ASSERT_TRUE(scaler()->Configure(params));
273   EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
274   const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
275   static_assert(kSMPTEFullSize.width() % 2 == 0, "Fix kSMPTEFullSize.");
276   static_assert(kSMPTEFullSize.height() % 4 == 0, "Fix kSMPTEFullSize.");
277   const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
278                              kSMPTEFullSize.height() / 4);
279   const gfx::Rect src_rect(offset.x(), offset.y(),
280                            kSMPTEFullSize.width() - offset.x(),
281                            kSMPTEFullSize.height() - offset.y());
282   const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
283                               kSMPTEFullSize.height() - offset.y());
284   const SkBitmap actual = Scale(source, offset, output_rect);
285   int max_color_diff = GetBaselineColorDifference();
286   EXPECT_TRUE(LooksLikeSMPTETestImage(actual, kSMPTEFullSize, src_rect, 0,
287                                       &max_color_diff))
288       << "max_color_diff measured was " << max_color_diff
289       << "\nActual: " << cc::GetPNGDataUrl(actual);
290 }
291 
292 // Tests that the source offset works when the source content is vertically
293 // flipped.
TEST_F(GLScalerPixelTest,TranslatesVerticallyFlippedSourceWithSourceOffset)294 TEST_F(GLScalerPixelTest, TranslatesVerticallyFlippedSourceWithSourceOffset) {
295   // Disabled on Marshmallow. See crbug.com/933080
296   if (IsAndroidMarshmallow())
297     return;
298 
299   GLScaler::Parameters params;
300   params.is_flipped_source = true;
301   ASSERT_TRUE(scaler()->Configure(params));
302   EXPECT_EQ(u8"Output ← {BILINEAR/lowp copy} ← Source", GetScalerString());
303   const SkBitmap flipped_source =
304       CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSMPTEFullSize));
305   const gfx::Vector2d offset(kSMPTEFullSize.width() / 2,
306                              kSMPTEFullSize.height() / 4);
307   const gfx::Rect src_rect(offset.x(), offset.y(),
308                            kSMPTEFullSize.width() - offset.x(),
309                            kSMPTEFullSize.height() - offset.y());
310   const gfx::Rect output_rect(0, 0, kSMPTEFullSize.width() - offset.x(),
311                               kSMPTEFullSize.height() - offset.y());
312   const SkBitmap flipped_back_actual =
313       CreateVerticallyFlippedBitmap(Scale(flipped_source, offset, output_rect));
314   int max_color_diff = GetBaselineColorDifference();
315   EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
316                                       src_rect, 0, &max_color_diff))
317       << "max_color_diff measured was " << max_color_diff
318       << "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
319 }
320 
321 // Tests that the correct source selection is made when both translating the
322 // source and then scaling. Scale "from" and "to" values are chosen such that a
323 // multi-stage scaler will be configured (to test that offsets are correcty
324 // calculated and passed between multiple stages).
TEST_F(GLScalerPixelTest,ScalesWithTranslatedSourceOffset)325 TEST_F(GLScalerPixelTest, ScalesWithTranslatedSourceOffset) {
326   GLScaler::Parameters params;
327   params.scale_from = gfx::Vector2d(640, 2160);
328   params.scale_to = gfx::Vector2d(256, 128);
329   params.quality = GLScaler::Parameters::Quality::GOOD;
330   params.is_flipped_source = false;
331   ASSERT_TRUE(scaler()->Configure(params));
332   EXPECT_EQ(
333       u8"Output "
334       u8"← {BILINEAR/lowp [256 256] to [256 128]} "
335       u8"← {BILINEAR4/lowp [512 2048] to [256 256]} "
336       u8"← {BILINEAR2X2/lowp [640 2160] to [512 2048]} "
337       u8"← Source",
338       GetScalerString());
339   constexpr gfx::Size kSourceSize = gfx::Size(640, 2160);
340   const SkBitmap source = CreateSMPTETestImage(kSourceSize);
341   const gfx::Vector2d offset(kSourceSize.width() / 2, kSourceSize.height() / 4);
342   const gfx::Rect output_rect(0, 0, 128, 64);
343   const SkBitmap actual = Scale(source, offset, output_rect);
344   const gfx::Rect expected_copy_rect(
345       offset.x(), offset.y(),
346       output_rect.width() * params.scale_from.x() / params.scale_to.x(),
347       output_rect.height() * params.scale_from.y() / params.scale_to.y());
348   int max_color_diff = GetBaselineColorDifference();
349   EXPECT_TRUE(LooksLikeSMPTETestImage(actual, kSourceSize, expected_copy_rect,
350                                       2, &max_color_diff))
351       << "max_color_diff measured was " << max_color_diff
352       << "\nExpected crop region of source: " << expected_copy_rect.ToString()
353       << "\nFull (uncropped) Source: " << cc::GetPNGDataUrl(source)
354       << "\nActual: " << cc::GetPNGDataUrl(actual);
355 }
356 
357 // Tests that the output is vertically flipped, if requested in the parameters.
TEST_F(GLScalerPixelTest,VerticallyFlipsOutput)358 TEST_F(GLScalerPixelTest, VerticallyFlipsOutput) {
359   // Disabled on Marshmallow. See crbug.com/933080
360   if (IsAndroidMarshmallow())
361     return;
362 
363   GLScaler::Parameters params;
364   params.is_flipped_source = false;
365   params.flip_output = true;
366   ASSERT_TRUE(scaler()->Configure(params));
367   EXPECT_EQ(u8"Output ← {BILINEAR/lowp+flip_y copy} ← Source",
368             GetScalerString());
369   const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
370   const SkBitmap flipped_back_actual = CreateVerticallyFlippedBitmap(
371       Scale(source, gfx::Vector2d(), gfx::Rect(kSMPTEFullSize)));
372   int max_color_diff = GetBaselineColorDifference();
373   EXPECT_TRUE(LooksLikeSMPTETestImage(flipped_back_actual, kSMPTEFullSize,
374                                       gfx::Rect(kSMPTEFullSize), 0,
375                                       &max_color_diff))
376       << "max_color_diff measured was " << max_color_diff
377       << "\nActual (flipped-back): " << cc::GetPNGDataUrl(flipped_back_actual);
378 }
379 
380 // Tests that the single-channel export ScalerStage works by executing a red
381 // channel export.
TEST_F(GLScalerPixelTest,ExportsTheRedColorChannel)382 TEST_F(GLScalerPixelTest, ExportsTheRedColorChannel) {
383   // Disabled on Marshmallow. See crbug.com/933080
384   if (IsAndroidMarshmallow())
385     return;
386 
387   GLScaler::Parameters params;
388   params.is_flipped_source = false;
389   params.export_format = GLScaler::Parameters::ExportFormat::CHANNEL_0;
390   ASSERT_TRUE(scaler()->Configure(params));
391   EXPECT_EQ(u8"Output ← {PLANAR_CHANNEL_0/lowp [4 1] to [1 1]} ← Source",
392             GetScalerString());
393   const SkBitmap source = CreateSMPTETestImage(kSMPTEFullSize);
394   const SkBitmap expected = CreatePackedPlanarBitmap(source, 0);
395   const gfx::Size output_size(expected.width(), expected.height());
396   const SkBitmap actual =
397       Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
398   constexpr float kAvgAbsoluteErrorLimit = 1.f;
399   constexpr int kMaxAbsoluteErrorLimit = 2;
400   EXPECT_TRUE(cc::FuzzyPixelComparator(
401                   false, 100.f, 0.f,
402                   GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
403                   GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
404                   .Compare(expected, actual))
405       << "\nActual: " << cc::GetPNGDataUrl(actual)
406       << "\Expected: " << cc::GetPNGDataUrl(expected);
407 }
408 
409 // A test that also stands as an example for how to use the GLScaler to scale a
410 // screen-sized RGB source (2160x1440, 16:10 aspect ratio) to a typical video
411 // resolution (720p, 16:9). The end-goal is to produce three textures, which
412 // contain the three YUV planes in I420 format.
413 //
414 // This is a two step process: First, the source is scaled and color space
415 // converted, with the final result exported as NV61 format (a full size luma
416 // plane + a half-width interleaved UV image). Second, the interleaved UV image
417 // is scaled by half in the vertical direction and then separated into one U and
418 // one V plane.
TEST_F(GLScalerPixelTest,Example_ScaleAndExportForScreenVideoCapture)419 TEST_F(GLScalerPixelTest, Example_ScaleAndExportForScreenVideoCapture) {
420   if (scaler()->GetMaxDrawBuffersSupported() < 2) {
421     LOG(WARNING) << "Skipping test due to lack of MRT support.";
422     return;
423   }
424 
425   // Step 1: Produce a scaled NV61-format result.
426   GLScaler::Parameters params;
427   params.scale_from = gfx::Vector2d(2160, 1440);
428   params.scale_to = gfx::Vector2d(1280, 720);
429   params.source_color_space = DefaultRGBColorSpace();
430   params.output_color_space = DefaultYUVColorSpace();
431   params.enable_precise_color_management = true;
432   params.quality = GLScaler::Parameters::Quality::GOOD;
433   params.is_flipped_source = true;
434   params.flip_output = true;
435   params.export_format = GLScaler::Parameters::ExportFormat::NV61;
436   params.swizzle[0] = GL_BGRA_EXT;  // Swizzle for readback.
437   params.swizzle[1] = GL_RGBA;      // Don't swizzle output for Step 2.
438   ASSERT_TRUE(scaler()->Configure(params));
439   EXPECT_STRING_MATCHES(
440       u8"Output "
441       u8"← {I422_NV61_MRT/mediump [5120 720] to [1280 720], with color x-form "
442       u8"to *BT709*, with swizzle(0)} "
443       u8"← {BILINEAR2/mediump [2160 1440] to [1280 720]} "
444       u8"← {BILINEAR/mediump+flip_y copy, with color x-form *BT709* to "
445       u8"*transfer:1.0000\\*x*} "
446       u8"← Source",
447       GetScalerString());
448 
449   constexpr gfx::Size kSourceSize = gfx::Size(2160, 1440);
450   const GLuint src_texture = UploadTexture(
451       CreateVerticallyFlippedBitmap(CreateSMPTETestImage(kSourceSize)));
452   constexpr gfx::Size kOutputSize = gfx::Size(1280, 720);
453   SkBitmap expected = CreateSMPTETestImage(kOutputSize);
454   ConvertBitmapToYUV(&expected);
455 
456   // While the output size is 1280x720, the packing of 4 pixels into one RGBA
457   // quad means that the texture width must be divided by 4, and that size
458   // passed in the output_rect argument in the call to ScaleToMultipleOutputs().
459   const gfx::Size y_plane_size(kOutputSize.width() / 4, kOutputSize.height());
460   const GLuint y_plane_texture = CreateTexture(y_plane_size);
461   const GLuint uv_interleaved_texture = CreateTexture(y_plane_size);
462 
463   ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
464       src_texture, kSourceSize, gfx::Vector2d(), y_plane_texture,
465       uv_interleaved_texture, gfx::Rect(y_plane_size)));
466 
467   // Step 2: Run the scaler again with the deinterleaver exporter, to produce
468   // the I420 U and V planes from the NV61 UV interleaved image.
469   params = GLScaler::Parameters();  // Reset params.
470   params.scale_from = gfx::Vector2d(1, 2);
471   params.scale_to = gfx::Vector2d(1, 1);
472   params.source_color_space = DefaultYUVColorSpace();
473   params.quality = GLScaler::Parameters::Quality::GOOD;
474   params.is_flipped_source = false;  // Output was already flipped in Step 1.
475   params.export_format =
476       GLScaler::Parameters::ExportFormat::DEINTERLEAVE_PAIRWISE;
477   params.swizzle[0] = GL_BGRA_EXT;  // Swizzle for readback.
478   params.swizzle[1] = GL_BGRA_EXT;  // Swizzle for readback.
479   ASSERT_TRUE(scaler()->Configure(params));
480   EXPECT_EQ(
481       u8"Output "
482       u8"← {DEINTERLEAVE_PAIRWISE_MRT/lowp [2 2] to [1 1], with swizzle(0), "
483       u8"with swizzle(1)} "
484       u8"← Source",
485       GetScalerString());
486 
487   const gfx::Size uv_plane_size(y_plane_size.width() / 2,
488                                 y_plane_size.height() / 2);
489   const GLuint u_plane_texture = CreateTexture(uv_plane_size);
490   const GLuint v_plane_texture = CreateTexture(uv_plane_size);
491   ASSERT_TRUE(scaler()->ScaleToMultipleOutputs(
492       uv_interleaved_texture, y_plane_size, gfx::Vector2d(), u_plane_texture,
493       v_plane_texture, gfx::Rect(uv_plane_size)));
494 
495   // Download the textures, and unpack them into an interleaved YUV bitmap, for
496   // comparison against the |expected| rendition.
497   SkBitmap actual = AllocateRGBABitmap(kOutputSize);
498   actual.eraseColor(SkColorSetARGB(0xff, 0x00, 0x80, 0x80));
499   SkBitmap y_plane = DownloadTexture(y_plane_texture, y_plane_size);
500   SwizzleBitmap(&y_plane);
501   UnpackPlanarBitmap(y_plane, 0, &actual);
502   SkBitmap u_plane = DownloadTexture(u_plane_texture, uv_plane_size);
503   SwizzleBitmap(&u_plane);
504   UnpackPlanarBitmap(u_plane, 1, &actual);
505   SkBitmap v_plane = DownloadTexture(v_plane_texture, uv_plane_size);
506   SwizzleBitmap(&v_plane);
507   UnpackPlanarBitmap(v_plane, 2, &actual);
508 
509   // Provide generous error limits to account for the chroma subsampling in the
510   // |actual| result when compared to the perfect |expected| rendition.
511   constexpr float kAvgAbsoluteErrorLimit = 16.f;
512   constexpr int kMaxAbsoluteErrorLimit = 0x80;
513   EXPECT_TRUE(cc::FuzzyPixelComparator(false, 100.f, 0.f,
514                                        kAvgAbsoluteErrorLimit,
515                                        kMaxAbsoluteErrorLimit, 0)
516                   .Compare(expected, actual))
517       << "\nActual: " << cc::GetPNGDataUrl(actual)
518       << "\nExpected: " << cc::GetPNGDataUrl(expected);
519 }
520 
521 // Performs a scaling-with-gamma-correction experiment to test GLScaler's
522 // "precise color management" feature. A 50% scale is executed on the same
523 // source image, once with color management turned on, and once with it turned
524 // off. The results, each of which should be different, are then examined.
TEST_F(GLScalerPixelTest,ScalesWithColorManagement)525 TEST_F(GLScalerPixelTest, ScalesWithColorManagement) {
526   if (!scaler()->SupportsPreciseColorManagement()) {
527     LOG(WARNING) << "Skipping test due to lack of 16-bit float support.";
528     return;
529   }
530 
531   // An image of a raspberry (source:
532   // https://commons.wikimedia.org/wiki/File:Framboise_Margy_3.jpg) has been
533   // transformed in such a way that scaling it by half in both directions will
534   // reveal whether scaling is occurring on linearized color values. When scaled
535   // correctly, the output image should contain a visible raspberry blended
536   // heavily with solid gray. However, if done naively, the output will be a
537   // solid 50% gray. For details, see: http://www.ericbrasseur.org/gamma.html
538   //
539   // Note that the |source| and |expected| images both use the sRGB color space.
540   const SkBitmap source = LoadPNGTestImage("rasp-grayator.png");
541   ASSERT_FALSE(source.isNull());
542   const SkBitmap expected = LoadPNGTestImage("rasp-grayator-half.png");
543   ASSERT_FALSE(expected.isNull());
544   const gfx::Size output_size =
545       gfx::Size(source.width() / 2, source.height() / 2);
546   ASSERT_EQ(gfx::Size(expected.width(), expected.height()), output_size);
547   const SkBitmap expected_naive = AllocateRGBABitmap(output_size);
548   expected_naive.eraseColor(SkColorSetARGB(0xff, 0x7f, 0x7f, 0x7f));
549 
550   // Scale the right way: With color management enabled, the raspberry should be
551   // visible in the downscaled result.
552   GLScaler::Parameters params;
553   params.scale_from = gfx::Vector2d(2, 2);
554   params.scale_to = gfx::Vector2d(1, 1);
555   params.source_color_space = gfx::ColorSpace::CreateSRGB();
556   params.enable_precise_color_management = true;
557   params.quality = GLScaler::Parameters::Quality::GOOD;
558   params.is_flipped_source = false;
559   ASSERT_TRUE(scaler()->Configure(params));
560   EXPECT_STRING_MATCHES(
561       u8"Output "
562       u8"← {BILINEAR/mediump [2 2] to [1 1], with color x-form to *BT709*} "
563       u8"← {BILINEAR/mediump copy, with color x-form *BT709* to "
564       u8"*transfer:1.0000\\*x*} "
565       u8"← Source",
566       GetScalerString());
567   const SkBitmap actual =
568       Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
569   constexpr float kAvgAbsoluteErrorLimit = 1.f;
570   constexpr int kMaxAbsoluteErrorLimit = 2;
571   EXPECT_TRUE(cc::FuzzyPixelComparator(
572                   false, 100.f, 0.f,
573                   GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
574                   GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
575                   .Compare(expected, actual))
576       << "\nActual: " << cc::GetPNGDataUrl(actual)
577       << "\nExpected (half size): " << cc::GetPNGDataUrl(expected)
578       << "\nOriginal: " << cc::GetPNGDataUrl(source);
579 
580   // Scale the naive way: Without color management, expect a solid gray result.
581   params.enable_precise_color_management = false;
582   ASSERT_TRUE(scaler()->Configure(params));
583   EXPECT_EQ(u8"Output ← {BILINEAR/lowp [2 2] to [1 1]} ← Source",
584             GetScalerString());
585   const SkBitmap actual_naive =
586       Scale(source, gfx::Vector2d(), gfx::Rect(output_size));
587   EXPECT_TRUE(cc::FuzzyPixelComparator(
588                   false, 100.f, 0.f,
589                   GetBaselineColorDifference() + kAvgAbsoluteErrorLimit,
590                   GetBaselineColorDifference() + kMaxAbsoluteErrorLimit, 0)
591                   .Compare(expected_naive, actual_naive))
592       << "\nActual: " << cc::GetPNGDataUrl(actual_naive)
593       << "\nExpected (half size): " << cc::GetPNGDataUrl(expected_naive)
594       << "\nOriginal: " << cc::GetPNGDataUrl(source);
595 }
596 
597 #undef EXPECT_STRING_MATCHES
598 
599 }  // namespace viz
600