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