1// Copyright (c) 2013 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 <d3d9.h>
6#include <random>
7
8#include "base/basictypes.h"
9#include "base/file_util.h"
10#include "base/hash.h"
11#include "base/scoped_native_library.h"
12#include "base/strings/stringprintf.h"
13#include "base/time/time.h"
14#include "base/win/scoped_comptr.h"
15#include "base/win/windows_version.h"
16#include "media/base/simd/convert_rgb_to_yuv.h"
17#include "media/base/yuv_convert.h"
18#include "skia/ext/image_operations.h"
19#include "testing/gtest/include/gtest/gtest-param-test.h"
20#include "testing/gtest/include/gtest/gtest.h"
21#include "third_party/skia/include/core/SkBitmap.h"
22#include "third_party/skia/include/core/SkColor.h"
23#include "ui/gfx/codec/png_codec.h"
24#include "ui/gfx/rect.h"
25#include "ui/surface/accelerated_surface_transformer_win.h"
26#include "ui/surface/accelerated_surface_win.h"
27#include "ui/surface/d3d9_utils_win.h"
28
29namespace d3d_utils = ui_surface_d3d9_utils;
30
31using base::win::ScopedComPtr;
32using std::uniform_int_distribution;
33
34namespace {
35
36// Debug flag, useful when hacking on tests.
37const bool kDumpImagesOnFailure = false;
38
39SkBitmap ToSkBitmap(IDirect3DSurface9* surface, bool is_single_channel) {
40  D3DLOCKED_RECT locked_rect;
41  EXPECT_HRESULT_SUCCEEDED(
42      surface->LockRect(&locked_rect, NULL, D3DLOCK_READONLY));
43
44  SkBitmap result;
45  gfx::Size size = d3d_utils::GetSize(surface);
46  if (is_single_channel)
47    size = gfx::Size(size.width() * 4, size.height());
48  result.setConfig(SkBitmap::kARGB_8888_Config, size.width(), size.height(),
49                   0, kOpaque_SkAlphaType);
50  result.allocPixels();
51  result.lockPixels();
52  for (int y = 0; y < size.height(); ++y) {
53    uint8* row8 = reinterpret_cast<uint8*>(locked_rect.pBits) +
54        (y * locked_rect.Pitch);
55    if (is_single_channel) {
56      for (int x = 0; x < size.width(); ++x) {
57        *result.getAddr32(x, y) = SkColorSetRGB(row8[x], row8[x], row8[x]);
58      }
59    } else {
60      uint32* row32 = reinterpret_cast<uint32*>(row8);
61      for (int x = 0; x < size.width(); ++x) {
62        *result.getAddr32(x, y) = row32[x] | 0xFF000000;
63      }
64    }
65  }
66  result.unlockPixels();
67  result.setImmutable();
68  surface->UnlockRect();
69  return result;
70}
71
72bool WritePNGFile(const SkBitmap& bitmap, const base::FilePath& file_path) {
73  std::vector<unsigned char> png_data;
74  const bool discard_transparency = true;
75  if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap,
76                                        discard_transparency,
77                                        &png_data) &&
78      base::CreateDirectory(file_path.DirName())) {
79    char* data = reinterpret_cast<char*>(&png_data[0]);
80    int size = static_cast<int>(png_data.size());
81    return file_util::WriteFile(file_path, data, size) == size;
82  }
83  return false;
84}
85
86}  // namespace
87
88// Test fixture for AcceleratedSurfaceTransformer.
89//
90// This class is parameterized so that it runs only on Vista+. See
91// WindowsVersionIfVistaOrBetter() for details on this works.
92class AcceleratedSurfaceTransformerTest : public testing::TestWithParam<int> {
93 public:
94  AcceleratedSurfaceTransformerTest() : color_error_tolerance_(0) {};
95
96  IDirect3DDevice9Ex* device() { return device_.get(); }
97
98  virtual void SetUp() {
99    if (!d3d_module_.is_valid()) {
100      if (!d3d_utils::LoadD3D9(&d3d_module_)) {
101        GTEST_FAIL() << "Could not load d3d9.dll";
102        return;
103      }
104    }
105    if (!d3d_utils::CreateDevice(d3d_module_,
106                                 D3DDEVTYPE_HAL,
107                                 D3DPRESENT_INTERVAL_IMMEDIATE,
108                                 device_.Receive())) {
109      GTEST_FAIL() << "Could not create Direct3D device.";
110      return;
111    }
112
113    SeedRandom("default");
114  }
115
116  virtual void TearDown() {
117    device_ = NULL;
118  }
119
120  // Gets a human-readable identifier of the graphics hardware being used,
121  // intended for use inside of SCOPED_TRACE().
122  std::string GetAdapterInfo() {
123    ScopedComPtr<IDirect3D9> d3d;
124    EXPECT_HRESULT_SUCCEEDED(device()->GetDirect3D(d3d.Receive()));
125    D3DADAPTER_IDENTIFIER9 info;
126    EXPECT_HRESULT_SUCCEEDED(d3d->GetAdapterIdentifier(0, 0, &info));
127    return base::StringPrintf(
128        "Running on graphics hardware: %s", info.Description);
129  }
130
131  void SeedRandom(const char* seed) {
132    rng_.seed(base::Hash(seed));
133    random_dword_.reset();
134  }
135
136  // Driver workaround: on an Intel GPU (Mobile Intel 965 Express), it seems
137  // necessary to flush between drawing and locking, for the synchronization
138  // to behave properly.
139  void BeforeLockWorkaround() {
140    EXPECT_HRESULT_SUCCEEDED(
141        device()->Present(0, 0, 0, 0));
142  }
143
144  void WarnOnMissingFeatures(AcceleratedSurfaceTransformer* gpu_ops) {
145    // Prints a single warning line if some tests are feature-dependent
146    // and the feature is not supported by the current GPU.
147    if (!gpu_ops->device_supports_multiple_render_targets()) {
148      LOG(WARNING) << "MRT not supported, some tests will be skipped. "
149                   << GetAdapterInfo();
150    }
151  }
152
153  // Locks and fills a surface with a checkerboard pattern where the colors
154  // are random but the total image pattern is horizontally and vertically
155  // symmetric.
156  void FillSymmetricRandomCheckerboard(
157      IDirect3DSurface9* lockable_surface,
158      const gfx::Size& size,
159      int checker_square_size) {
160
161    D3DLOCKED_RECT locked_rect;
162    ASSERT_HRESULT_SUCCEEDED(
163        lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD));
164    DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits);
165    ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD));
166    int pitch = locked_rect.Pitch / sizeof(DWORD);
167
168    for (int y = 0; y < (size.height() + 1) / 2; y += checker_square_size) {
169      for (int x = 0; x < (size.width() + 1) / 2; x += checker_square_size) {
170        DWORD color = RandomColor();
171        int y_limit = std::min(size.height() / 2, y + checker_square_size - 1);
172        int x_limit = std::min(size.width() / 2, x + checker_square_size - 1);
173        for (int y_lo = y; y_lo <= y_limit; y_lo++) {
174          for (int x_lo = x; x_lo <= x_limit; x_lo++) {
175            int y_hi = size.height() - 1 - y_lo;
176            int x_hi = size.width() - 1 - x_lo;
177            surface[x_lo + y_lo*pitch] = color;
178            surface[x_lo + y_hi*pitch] = color;
179            surface[x_hi + y_lo*pitch] = color;
180            surface[x_hi + y_hi*pitch] = color;
181          }
182        }
183      }
184    }
185
186    lockable_surface->UnlockRect();
187  }
188
189  void FillRandomCheckerboard(
190      IDirect3DSurface9* lockable_surface,
191      const gfx::Size& size,
192      int checker_square_size) {
193
194    D3DLOCKED_RECT locked_rect;
195    ASSERT_HRESULT_SUCCEEDED(
196        lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD));
197    DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits);
198    ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD));
199    int pitch = locked_rect.Pitch / sizeof(DWORD);
200
201    for (int y = 0; y <= size.height(); y += checker_square_size) {
202      for (int x = 0; x <= size.width(); x += checker_square_size) {
203        DWORD color = RandomColor();
204        int y_limit = std::min(size.height(), y + checker_square_size);
205        int x_limit = std::min(size.width(), x + checker_square_size);
206        for (int square_y = y; square_y < y_limit; square_y++) {
207          for (int square_x = x; square_x < x_limit; square_x++) {
208            surface[square_x + square_y*pitch] = color;
209          }
210        }
211      }
212    }
213
214    lockable_surface->UnlockRect();
215  }
216
217  // Approximate color-equality check. Allows for some rounding error.
218  bool AssertSameColor(DWORD color_a, DWORD color_b) {
219    if (color_a == color_b)
220      return true;
221    uint8* a = reinterpret_cast<uint8*>(&color_a);
222    uint8* b = reinterpret_cast<uint8*>(&color_b);
223    int max_error = 0;
224    for (int i = 0; i < 4; i++)
225      max_error = std::max(max_error,
226          std::abs(static_cast<int>(a[i]) - b[i]));
227
228    if (max_error <= color_error_tolerance())
229      return true;
230
231    std::string expected_color =
232        base::StringPrintf("%3d, %3d, %3d, %3d", a[0], a[1], a[2], a[3]);
233    std::string actual_color =
234        base::StringPrintf("%3d, %3d, %3d, %3d", b[0], b[1], b[2], b[3]);
235    EXPECT_EQ(expected_color, actual_color)
236        << "Componentwise color difference was "
237        << max_error << "; max allowed is " << color_error_tolerance();
238
239    return false;
240  }
241
242bool AssertSameColor(uint8 color_a, uint8 color_b) {
243    if (color_a == color_b)
244      return true;
245    int max_error = std::abs((int) color_a - (int) color_b);
246    if (max_error <= color_error_tolerance())
247      return true;
248    ADD_FAILURE() << "Colors not equal: "
249                  << base::StringPrintf("0x%x", color_a)
250                  << " vs. " << base::StringPrintf("0x%x", color_b);
251    return false;
252  }
253
254  // Asserts that an image is symmetric with respect to itself: both
255  // horizontally and vertically, within the tolerance of AssertSameColor.
256  void AssertSymmetry(IDirect3DSurface9* lockable_surface,
257                      const gfx::Size& size) {
258    BeforeLockWorkaround();
259
260    D3DLOCKED_RECT locked_rect;
261    ASSERT_HRESULT_SUCCEEDED(
262        lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_READONLY));
263    ASSERT_EQ(0, locked_rect.Pitch % sizeof(DWORD));
264    int pitch = locked_rect.Pitch / sizeof(DWORD);
265    DWORD* surface = reinterpret_cast<DWORD*>(locked_rect.pBits);
266    for (int y_lo = 0; y_lo < size.height() / 2; y_lo++) {
267      int y_hi = size.height() - 1 - y_lo;
268      for (int x_lo = 0; x_lo < size.width() / 2; x_lo++) {
269        int x_hi = size.width() - 1 - x_lo;
270        if (!AssertSameColor(surface[x_lo + y_lo*pitch],
271                             surface[x_hi + y_lo*pitch])) {
272          lockable_surface->UnlockRect();
273          GTEST_FAIL() << "Pixels (" << x_lo << ", " << y_lo << ") vs. "
274                       << "(" << x_hi << ", " << y_lo << ")";
275        }
276        if (!AssertSameColor(surface[x_hi + y_lo*pitch],
277                             surface[x_hi + y_hi*pitch])) {
278          lockable_surface->UnlockRect();
279          GTEST_FAIL() << "Pixels (" << x_hi << ", " << y_lo << ") vs. "
280                       << "(" << x_hi << ", " << y_hi << ")";
281        }
282        if (!AssertSameColor(surface[x_hi + y_hi*pitch],
283                             surface[x_lo + y_hi*pitch])) {
284          lockable_surface->UnlockRect();
285          GTEST_FAIL() << "Pixels (" << x_hi << ", " << y_hi << ") vs. "
286                       << "(" << x_lo << ", " << y_hi << ")";
287        }
288      }
289    }
290    lockable_surface->UnlockRect();
291  }
292
293  // Asserts that the actual image is a bit-identical, vertically mirrored
294  // copy of the expected image.
295  void AssertIsInvertedCopy(const gfx::Size& size,
296                            IDirect3DSurface9* expected,
297                            IDirect3DSurface9* actual) {
298    BeforeLockWorkaround();
299
300    D3DLOCKED_RECT locked_expected, locked_actual;
301    ASSERT_HRESULT_SUCCEEDED(
302        expected->LockRect(&locked_expected, NULL, D3DLOCK_READONLY));
303    ASSERT_HRESULT_SUCCEEDED(
304        actual->LockRect(&locked_actual, NULL, D3DLOCK_READONLY));
305    ASSERT_EQ(0, locked_expected.Pitch % sizeof(DWORD));
306    int pitch = locked_expected.Pitch / sizeof(DWORD);
307    DWORD* expected_image = reinterpret_cast<DWORD*>(locked_expected.pBits);
308    DWORD* actual_image = reinterpret_cast<DWORD*>(locked_actual.pBits);
309    for (int y = 0; y < size.height(); y++) {
310      int y_actual = size.height() - 1 - y;
311      for (int x = 0; x < size.width(); ++x)
312        if (!AssertSameColor(expected_image[y*pitch + x],
313                             actual_image[y_actual*pitch + x])) {
314          expected->UnlockRect();
315          actual->UnlockRect();
316          GTEST_FAIL() << "Pixels (" << x << ", " << y << ") vs. "
317                       << "(" << x << ", " << y_actual << ")";
318        }
319    }
320    expected->UnlockRect();
321    actual->UnlockRect();
322  }
323
324 protected:
325  DWORD RandomColor() {
326    return random_dword_(rng_);
327  }
328
329  void set_color_error_tolerance(int value) {
330    color_error_tolerance_ = value;
331  }
332
333  int color_error_tolerance() {
334    return color_error_tolerance_;
335  }
336
337  void DoResizeBilinearTest(AcceleratedSurfaceTransformer* gpu_ops,
338                            const gfx::Size& src_size,
339                            const gfx::Size& dst_size,
340                            int checkerboard_size) {
341
342    SCOPED_TRACE(
343        base::StringPrintf(
344            "Resizing %dx%d -> %dx%d at checkerboard size of %d",
345            src_size.width(), src_size.height(),
346            dst_size.width(), dst_size.height(),
347            checkerboard_size));
348
349    set_color_error_tolerance(4);
350
351    base::win::ScopedComPtr<IDirect3DSurface9> src, dst;
352    ASSERT_TRUE(d3d_utils::CreateOrReuseLockableSurface(
353        device(), src_size, &src))
354            << "Could not create src render target";
355    ASSERT_TRUE(d3d_utils::CreateOrReuseLockableSurface(
356        device(), dst_size, &dst))
357            << "Could not create dst render target";
358
359    FillSymmetricRandomCheckerboard(src, src_size, checkerboard_size);
360
361    ASSERT_TRUE(gpu_ops->ResizeBilinear(src, gfx::Rect(src_size), dst,
362                                        gfx::Rect(dst_size)));
363
364    AssertSymmetry(dst, dst_size);
365  }
366
367  void CreateRandomCheckerboardTexture(
368      const gfx::Size& size,
369      int checkerboard_size,
370      base::win::ScopedComPtr<IDirect3DSurface9>* reference_surface,
371      base::win::ScopedComPtr<IDirect3DTexture9>* result) {
372    base::win::ScopedComPtr<IDirect3DSurface9> dst;
373    ASSERT_TRUE(d3d_utils::CreateOrReuseLockableSurface(device(), size,
374        reference_surface));
375    ASSERT_TRUE(d3d_utils::CreateOrReuseRenderTargetTexture(device(), size,
376        result, dst.Receive()));
377    FillRandomCheckerboard(*reference_surface, size, checkerboard_size);
378    ASSERT_HRESULT_SUCCEEDED(
379        device()->StretchRect(
380            *reference_surface, NULL, dst, NULL, D3DTEXF_NONE));
381  }
382
383  void AssertSame(int width_in_bytes, int height, uint8* reference,
384                  IDirect3DSurface9* lockable) {
385    BeforeLockWorkaround();
386
387    D3DLOCKED_RECT locked_rect;
388    ASSERT_HRESULT_SUCCEEDED(
389        lockable->LockRect(&locked_rect, NULL, D3DLOCK_READONLY));
390    uint8* actual = reinterpret_cast<uint8*>(locked_rect.pBits);
391    for (int y = 0; y < height; ++y) {
392      for (int x = 0; x < width_in_bytes; ++x) {
393        if (!AssertSameColor(reference[y * width_in_bytes + x],
394                             actual[y * locked_rect.Pitch + x])) {
395          lockable->UnlockRect();
396          GTEST_FAIL() << "At pixel (" << x << ", " << y << ")";
397        }
398      }
399    }
400    lockable->UnlockRect();
401  }
402
403  void DoCopyInvertedTest(AcceleratedSurfaceTransformer* gpu_ops,
404                          const gfx::Size& size) {
405
406    SCOPED_TRACE(base::StringPrintf(
407        "CopyInverted @ %dx%d", size.width(), size.height()));
408
409    set_color_error_tolerance(0);
410
411    base::win::ScopedComPtr<IDirect3DSurface9> dst, reference_pattern;
412    base::win::ScopedComPtr<IDirect3DTexture9> src;
413
414    CreateRandomCheckerboardTexture(size, 1, &reference_pattern, &src);
415
416    // Alloc a slightly larger image 75% of the time, to test that the
417    // viewport is set properly.
418    const int kAlign = 4;
419    gfx::Size alloc_size((size.width() + kAlign - 1) / kAlign * kAlign,
420                         (size.height() + kAlign - 1) / kAlign * kAlign);
421
422    ASSERT_TRUE(d3d_utils::CreateOrReuseLockableSurface(device(), alloc_size,
423        &dst)) << "Could not create dst render target.";
424
425    ASSERT_TRUE(gpu_ops->CopyInverted(src, dst, size));
426    AssertIsInvertedCopy(size, reference_pattern, dst);
427  }
428
429
430  void DoYUVConversionTest(AcceleratedSurfaceTransformer* gpu_ops,
431                           const gfx::Size& src_size,
432                           int checkerboard_size) {
433    // Test the non-MRT implementation, and the MRT implementation as well
434    // (if supported by the device).
435    ASSERT_NO_FATAL_FAILURE(
436        DoYUVConversionTest(gpu_ops, src_size, src_size,
437                            checkerboard_size, false));
438    if (gpu_ops->device_supports_multiple_render_targets()) {
439      ASSERT_NO_FATAL_FAILURE(
440          DoYUVConversionTest(gpu_ops, src_size, src_size,
441                              checkerboard_size, true));
442    }
443  }
444
445  void DoYUVConversionScaleTest(AcceleratedSurfaceTransformer* gpu_ops,
446                                const gfx::Size& src_size,
447                                const gfx::Size& dst_size) {
448    // Test the non-MRT implementation, and the MRT implementation as well
449    // (if supported by the device).
450    if (gpu_ops->device_supports_multiple_render_targets()) {
451      ASSERT_NO_FATAL_FAILURE(
452          DoYUVConversionTest(gpu_ops, src_size, dst_size, 4, true));
453    }
454    ASSERT_NO_FATAL_FAILURE(
455        DoYUVConversionTest(gpu_ops, src_size, dst_size, 4, false));
456  }
457
458  void DoYUVConversionTest(AcceleratedSurfaceTransformer* gpu_ops,
459                           const gfx::Size& src_size,
460                           const gfx::Size& dst_size,
461                           int checkerboard_size,
462                           boolean use_multi_render_targets) {
463    SCOPED_TRACE(
464        base::StringPrintf(
465            "YUV Converting %dx%d at checkerboard size of %d; MRT %s",
466            src_size.width(), src_size.height(),
467            checkerboard_size,
468            use_multi_render_targets ? "enabled" : "disabled"));
469
470
471    base::win::ScopedComPtr<IDirect3DTexture9> src;
472    base::win::ScopedComPtr<IDirect3DSurface9> reference;
473    base::win::ScopedComPtr<IDirect3DSurface9> dst_y, dst_u, dst_v;
474
475    // TODO(ncarter): Use a better error metric that measures aggregate error
476    // rather than simply max error. There seems to be slightly more error at
477    // higher resolutions, maybe due to precision issues during rasterization
478    // (or maybe more pixels = more test trials). Results are usually to an
479    // error of 1, but we must use a tolerance of 3.
480    set_color_error_tolerance(3);
481    CreateRandomCheckerboardTexture(src_size, checkerboard_size, &reference,
482                                    &src);
483
484    gfx::Size packed_y_size, packed_uv_size;
485
486    ASSERT_TRUE(gpu_ops->AllocYUVBuffers(dst_size,
487                                         &packed_y_size,
488                                         &packed_uv_size,
489                                         dst_y.Receive(),
490                                         dst_u.Receive(),
491                                         dst_v.Receive()));
492
493    // Actually do the conversion.
494    if (use_multi_render_targets) {
495      ASSERT_TRUE(gpu_ops->TransformRGBToYV12_MRT(src,
496                                                  dst_size,
497                                                  packed_y_size,
498                                                  packed_uv_size,
499                                                  dst_y,
500                                                  dst_u,
501                                                  dst_v));
502    } else {
503      ASSERT_TRUE(gpu_ops->TransformRGBToYV12_WithoutMRT(src,
504                                                         dst_size,
505                                                         packed_y_size,
506                                                         packed_uv_size,
507                                                         dst_y,
508                                                         dst_u,
509                                                         dst_v));
510    }
511
512    // UV size (in bytes/samples) is half, rounded up.
513    gfx::Size uv_size((dst_size.width() + 1) / 2,
514                      (dst_size.height() + 1) / 2);
515
516    // Generate a reference bitmap by calling a software implementation.
517    SkBitmap reference_rgb = ToSkBitmap(reference, false);
518    SkBitmap reference_rgb_scaled;
519    if (dst_size == src_size) {
520      reference_rgb_scaled = reference_rgb;
521    } else {
522      // We'll call Copy to do the bilinear scaling if needed.
523      base::win::ScopedComPtr<IDirect3DSurface9> reference_scaled;
524      ASSERT_TRUE(
525          d3d_utils::CreateOrReuseLockableSurface(
526              device(), dst_size, &reference_scaled));
527      ASSERT_TRUE(gpu_ops->Copy(src, reference_scaled, dst_size));
528      BeforeLockWorkaround();
529      reference_rgb_scaled = ToSkBitmap(reference_scaled, false);
530    }
531
532    scoped_ptr<uint8[]> reference_y(new uint8[dst_size.GetArea()]);
533    scoped_ptr<uint8[]> reference_u(new uint8[uv_size.GetArea()]);
534    scoped_ptr<uint8[]> reference_v(new uint8[uv_size.GetArea()]);
535    reference_rgb_scaled.lockPixels();
536    media::ConvertRGB32ToYUV_SSE2_Reference(
537        reinterpret_cast<uint8*>(reference_rgb_scaled.getAddr32(0, 0)),
538        &reference_y[0],
539        &reference_u[0],
540        &reference_v[0],
541        dst_size.width(),
542        dst_size.height(),
543        reference_rgb_scaled.rowBytes(),
544        dst_size.width(),
545        uv_size.width());
546    reference_rgb_scaled.unlockPixels();
547
548    // Check for equality of the reference and the actual.
549    AssertSame(dst_size.width(), dst_size.height(), &reference_y[0], dst_y);
550    AssertSame(uv_size.width(), uv_size.height(), &reference_u[0], dst_u);
551    AssertSame(uv_size.width(), uv_size.height(), &reference_v[0], dst_v);
552
553    if (kDumpImagesOnFailure && HasFatalFailure()) {
554      // Note that this will dump the full u and v buffers, including
555      // extra columns added due to packing. That means up to 7 extra
556      // columns for uv, and up to 3 extra columns for y.
557      WritePNGFile(reference_rgb,
558                   base::FilePath(FILE_PATH_LITERAL("test_fail_src.png")));
559      WritePNGFile(reference_rgb_scaled,
560                   base::FilePath(
561                       FILE_PATH_LITERAL("test_fail_src_scaled.png")));
562      WritePNGFile(ToSkBitmap(dst_y, true),
563                   base::FilePath(FILE_PATH_LITERAL("test_fail_y.png")));
564      WritePNGFile(ToSkBitmap(dst_u, true),
565                   base::FilePath(FILE_PATH_LITERAL("test_fail_u.png")));
566      WritePNGFile(ToSkBitmap(dst_v, true),
567                   base::FilePath(FILE_PATH_LITERAL("test_fail_v.png")));
568    }
569  }
570
571  int color_error_tolerance_;
572  uniform_int_distribution<DWORD> random_dword_;
573  std::mt19937 rng_;
574  base::ScopedNativeLibrary d3d_module_;
575  base::win::ScopedComPtr<IDirect3DDevice9Ex> device_;
576};
577
578// Fails on some bots because Direct3D isn't allowed.
579TEST_P(AcceleratedSurfaceTransformerTest, Init) {
580  SCOPED_TRACE(GetAdapterInfo());
581  AcceleratedSurfaceTransformer gpu_ops;
582  ASSERT_TRUE(gpu_ops.Init(device()));
583
584  WarnOnMissingFeatures(&gpu_ops);
585};
586
587// Fails on some bots because Direct3D isn't allowed.
588TEST_P(AcceleratedSurfaceTransformerTest, TestConsistentRandom) {
589  // This behavior should be the same for every execution on every machine.
590  // Otherwise tests might be flaky and impossible to debug.
591  SeedRandom("AcceleratedSurfaceTransformerTest.TestConsistentRandom");
592  ASSERT_EQ(2922058934, RandomColor());
593
594  SeedRandom("AcceleratedSurfaceTransformerTest.TestConsistentRandom");
595  ASSERT_EQ(2922058934, RandomColor());
596  ASSERT_EQ(4050239976, RandomColor());
597
598  SeedRandom("DifferentSeed");
599  ASSERT_EQ(3904108833, RandomColor());
600}
601
602// Fails on some bots because Direct3D isn't allowed.
603TEST_P(AcceleratedSurfaceTransformerTest, CopyInverted) {
604  // This behavior should be the same for every execution on every machine.
605  // Otherwise tests might be flaky and impossible to debug.
606  SCOPED_TRACE(GetAdapterInfo());
607  SeedRandom("CopyInverted");
608
609  AcceleratedSurfaceTransformer t;
610  ASSERT_TRUE(t.Init(device()));
611
612  uniform_int_distribution<int> size(1, 512);
613
614  for (int i = 0; i < 100; ++i) {
615    ASSERT_NO_FATAL_FAILURE(
616        DoCopyInvertedTest(&t, gfx::Size(size(rng_), size(rng_))))
617            << "At iteration " << i;
618  }
619}
620
621// Fails on some bots because Direct3D isn't allowed.
622// Fails on other bots because of ResizeBilinear symmetry failures.
623// Should pass, at least, on NVIDIA Quadro 600.
624TEST_P(AcceleratedSurfaceTransformerTest, MixedOperations) {
625  SCOPED_TRACE(GetAdapterInfo());
626  SeedRandom("MixedOperations");
627
628  AcceleratedSurfaceTransformer t;
629  ASSERT_TRUE(t.Init(device()));
630
631  ASSERT_NO_FATAL_FAILURE(
632      DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 1));
633  ASSERT_NO_FATAL_FAILURE(
634      DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 2));
635  ASSERT_NO_FATAL_FAILURE(
636      DoCopyInvertedTest(&t, gfx::Size(20, 107)));
637  ASSERT_NO_FATAL_FAILURE(
638      DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 5));
639  ASSERT_NO_FATAL_FAILURE(
640      DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(64, 64), 5));
641  ASSERT_NO_FATAL_FAILURE(
642      DoYUVConversionTest(&t, gfx::Size(128, 128), 1));
643  ASSERT_NO_FATAL_FAILURE(
644      DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(3, 3), 1));
645  ASSERT_NO_FATAL_FAILURE(
646      DoCopyInvertedTest(&t, gfx::Size(1412, 124)));
647  ASSERT_NO_FATAL_FAILURE(
648      DoYUVConversionTest(&t, gfx::Size(100, 200), 1));
649  ASSERT_NO_FATAL_FAILURE(
650      DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 1));
651  ASSERT_NO_FATAL_FAILURE(
652      DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 2));
653
654  ASSERT_NO_FATAL_FAILURE(
655      DoCopyInvertedTest(&t, gfx::Size(1512, 7)));
656  ASSERT_NO_FATAL_FAILURE(
657      DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 5));
658  ASSERT_NO_FATAL_FAILURE(
659      DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 8));
660  ASSERT_NO_FATAL_FAILURE(
661      DoCopyInvertedTest(&t, gfx::Size(1521, 3)));
662  ASSERT_NO_FATAL_FAILURE(
663      DoYUVConversionTest(&t, gfx::Size(140, 181), 1));
664  ASSERT_NO_FATAL_FAILURE(
665      DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 1));
666  ASSERT_NO_FATAL_FAILURE(
667      DoCopyInvertedTest(&t, gfx::Size(33, 712)));
668  ASSERT_NO_FATAL_FAILURE(
669      DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 8), 8));
670  ASSERT_NO_FATAL_FAILURE(
671      DoCopyInvertedTest(&t, gfx::Size(33, 2)));
672  ASSERT_NO_FATAL_FAILURE(
673      DoResizeBilinearTest(&t, gfx::Size(200, 256), gfx::Size(126, 8), 8));
674}
675
676// Tests ResizeBilinear with 16K wide/hight src and dst surfaces.
677//
678// Fails on some bots because Direct3D isn't allowed.
679// Should pass, at least, on NVIDIA Quadro 600.
680TEST_P(AcceleratedSurfaceTransformerTest, LargeSurfaces) {
681  SCOPED_TRACE(GetAdapterInfo());
682  SeedRandom("LargeSurfaces");
683
684  AcceleratedSurfaceTransformer gpu_ops;
685  ASSERT_TRUE(gpu_ops.Init(device()));
686
687  D3DCAPS9 caps;
688  ASSERT_HRESULT_SUCCEEDED(
689      device()->GetDeviceCaps(&caps));
690
691  SCOPED_TRACE(base::StringPrintf(
692     "max texture size: %dx%d, max texture aspect: %d",
693      caps.MaxTextureWidth, caps.MaxTextureHeight, caps.MaxTextureAspectRatio));
694
695  const int w = caps.MaxTextureWidth;
696  const int h = caps.MaxTextureHeight;
697  const int lo = 256;
698
699  ASSERT_NO_FATAL_FAILURE(
700      DoResizeBilinearTest(&gpu_ops, gfx::Size(w, lo), gfx::Size(lo, lo), 1));
701  ASSERT_NO_FATAL_FAILURE(
702      DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, h), gfx::Size(lo, lo), 1));
703  ASSERT_NO_FATAL_FAILURE(
704      DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(w, lo), lo));
705  ASSERT_NO_FATAL_FAILURE(
706      DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(lo, h), lo));
707  ASSERT_NO_FATAL_FAILURE(
708      DoCopyInvertedTest(&gpu_ops, gfx::Size(w, lo)));
709  ASSERT_NO_FATAL_FAILURE(
710      DoCopyInvertedTest(&gpu_ops, gfx::Size(lo, h)));
711
712  ASSERT_NO_FATAL_FAILURE(
713      DoYUVConversionTest(&gpu_ops, gfx::Size(w, lo), 1));
714  ASSERT_NO_FATAL_FAILURE(
715      DoYUVConversionTest(&gpu_ops, gfx::Size(lo, h), 1));
716
717}
718
719// Exercises ResizeBilinear with random minification cases where the
720// aspect ratio does not change.
721//
722// Fails on some bots because Direct3D isn't allowed.
723// Fails on other bots because of StretchRect symmetry failures.
724// Should pass, at least, on NVIDIA Quadro 600.
725TEST_P(AcceleratedSurfaceTransformerTest, MinifyUniform) {
726  SCOPED_TRACE(GetAdapterInfo());
727  SeedRandom("MinifyUniform");
728
729  AcceleratedSurfaceTransformer gpu_ops;
730  ASSERT_TRUE(gpu_ops.Init(device()));
731
732  const int dims[] = {21, 63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257};
733  const int checkerboards[] = {1, 2, 3, 9};
734  uniform_int_distribution<int> dim(0, arraysize(dims) - 1);
735  uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1);
736
737  for (int i = 0; i < 300; i++) {
738    // Widths are picked so that dst is smaller than src.
739    int dst_width = dims[dim(rng_)];
740    int src_width = dims[dim(rng_)];
741    if (src_width < dst_width)
742      std::swap(dst_width, src_width);
743
744    // src_height is picked to preserve aspect ratio.
745    int dst_height = dims[dim(rng_)];
746    int src_height = static_cast<int>(
747        static_cast<int64>(src_width) * dst_height / dst_width);
748
749    int checkerboard_size = checkerboards[checkerboard(rng_)];
750
751    ASSERT_NO_FATAL_FAILURE(
752        DoResizeBilinearTest(&gpu_ops,
753            gfx::Size(src_width, src_height),  // Src size (larger)
754            gfx::Size(dst_width, dst_height),  // Dst size (smaller)
755            checkerboard_size)) << "Failed on iteration " << i;
756  }
757};
758
759// Exercises ResizeBilinear with random magnification cases where the
760// aspect ratio does not change.
761//
762// This test relies on an assertion that resizing preserves symmetry in the
763// image, but for the current implementation of ResizeBilinear, this does not
764// seem to be true (fails on NVIDIA Quadro 600; passes on
765// Intel Mobile 965 Express)
766TEST_P(AcceleratedSurfaceTransformerTest, DISABLED_MagnifyUniform) {
767  SCOPED_TRACE(GetAdapterInfo());
768  SeedRandom("MagnifyUniform");
769
770  AcceleratedSurfaceTransformer gpu_ops;
771  ASSERT_TRUE(gpu_ops.Init(device()));
772
773  const int dims[] = {63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257};
774  const int checkerboards[] = {1, 2, 3, 9};
775  uniform_int_distribution<int> dim(0, arraysize(dims) - 1);
776  uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1);
777
778  for (int i = 0; i < 50; i++) {
779    // Widths are picked so that src is smaller than dst.
780    int dst_width = dims[dim(rng_)];
781    int src_width = dims[dim(rng_)];
782    if (dst_width < src_width)
783      std::swap(src_width, dst_width);
784
785    int dst_height = dims[dim(rng_)];
786    int src_height = static_cast<int>(
787        static_cast<int64>(src_width) * dst_height / dst_width);
788
789    int checkerboard_size = checkerboards[checkerboard(rng_)];
790
791    ASSERT_NO_FATAL_FAILURE(
792        DoResizeBilinearTest(&gpu_ops,
793            gfx::Size(src_width, src_height),  // Src size (smaller)
794            gfx::Size(dst_width, dst_height),  // Dst size (larger)
795            checkerboard_size)) << "Failed on iteration " << i;
796  }
797};
798
799TEST_P(AcceleratedSurfaceTransformerTest, RGBtoYUV) {
800  SeedRandom("RGBtoYUV");
801
802  AcceleratedSurfaceTransformer gpu_ops;
803  ASSERT_TRUE(gpu_ops.Init(device()));
804
805  // Start with some easy-to-debug cases. A checkerboard size of 1 is the
806  // best test, but larger checkerboard sizes give more insight into where
807  // a bug might be.
808  ASSERT_NO_FATAL_FAILURE(
809      DoYUVConversionTest(&gpu_ops, gfx::Size(32, 32), 4));
810  ASSERT_NO_FATAL_FAILURE(
811      DoYUVConversionTest(&gpu_ops, gfx::Size(32, 32), 2));
812  ASSERT_NO_FATAL_FAILURE(
813      DoYUVConversionTest(&gpu_ops, gfx::Size(32, 32), 3));
814
815  // All cases of width (mod 8) and height (mod 8), using 1x1 checkerboard.
816  for (int w = 32; w < 40; ++w) {
817    for (int h = 32; h < 40; ++h) {
818      ASSERT_NO_FATAL_FAILURE(
819          DoYUVConversionTest(&gpu_ops, gfx::Size(w, h), 1));
820    }
821  }
822
823  // All the very small sizes which require the most shifting in the
824  // texture coordinates when doing alignment.
825  for (int w = 1; w <= 9; ++w) {
826    for (int h = 1; h <= 9; ++h) {
827      ASSERT_NO_FATAL_FAILURE(
828          DoYUVConversionTest(&gpu_ops, gfx::Size(w, h), 1));
829    }
830  }
831
832  // Random medium dimensions.
833  ASSERT_NO_FATAL_FAILURE(
834      DoYUVConversionTest(&gpu_ops, gfx::Size(10, 142), 1));
835  ASSERT_NO_FATAL_FAILURE(
836      DoYUVConversionTest(&gpu_ops, gfx::Size(124, 333), 1));
837  ASSERT_NO_FATAL_FAILURE(
838      DoYUVConversionTest(&gpu_ops, gfx::Size(853, 225), 1));
839  ASSERT_NO_FATAL_FAILURE(
840      DoYUVConversionTest(&gpu_ops, gfx::Size(231, 412), 1));
841  ASSERT_NO_FATAL_FAILURE(
842      DoYUVConversionTest(&gpu_ops, gfx::Size(512, 128), 1));
843  ASSERT_NO_FATAL_FAILURE(
844      DoYUVConversionTest(&gpu_ops, gfx::Size(1024, 768), 1));
845
846  // Common video/monitor resolutions
847  ASSERT_NO_FATAL_FAILURE(
848      DoYUVConversionTest(&gpu_ops, gfx::Size(800, 768), 1));
849  ASSERT_NO_FATAL_FAILURE(
850      DoYUVConversionTest(&gpu_ops, gfx::Size(1024, 768), 1));
851  ASSERT_NO_FATAL_FAILURE(
852      DoYUVConversionTest(&gpu_ops, gfx::Size(1280, 720), 1));
853  ASSERT_NO_FATAL_FAILURE(
854      DoYUVConversionTest(&gpu_ops, gfx::Size(1280, 720), 2));
855  ASSERT_NO_FATAL_FAILURE(
856      DoYUVConversionTest(&gpu_ops, gfx::Size(1920, 1080), 1));
857  ASSERT_NO_FATAL_FAILURE(
858      DoYUVConversionTest(&gpu_ops, gfx::Size(1920, 1080), 2));
859  ASSERT_NO_FATAL_FAILURE(
860      DoYUVConversionTest(&gpu_ops, gfx::Size(2048, 1536), 1));
861}
862
863TEST_P(AcceleratedSurfaceTransformerTest, RGBtoYUVScaled) {
864  SeedRandom("RGBtoYUVScaled");
865
866  AcceleratedSurfaceTransformer gpu_ops;
867  ASSERT_TRUE(gpu_ops.Init(device()));
868
869  ASSERT_NO_FATAL_FAILURE(
870      DoYUVConversionScaleTest(&gpu_ops, gfx::Size(32, 32), gfx::Size(64, 64)));
871
872  ASSERT_NO_FATAL_FAILURE(
873      DoYUVConversionScaleTest(&gpu_ops, gfx::Size(32, 32), gfx::Size(16, 16)));
874  ASSERT_NO_FATAL_FAILURE(
875      DoYUVConversionScaleTest(&gpu_ops, gfx::Size(32, 32), gfx::Size(24, 24)));
876  ASSERT_NO_FATAL_FAILURE(
877      DoYUVConversionScaleTest(&gpu_ops, gfx::Size(32, 32), gfx::Size(48, 48)));
878}
879
880namespace {
881
882// Used to suppress test on Windows versions prior to Vista.
883std::vector<int> WindowsVersionIfVistaOrBetter() {
884  std::vector<int> result;
885  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
886    result.push_back(base::win::GetVersion());
887  }
888  return result;
889}
890
891}  // namespace
892
893INSTANTIATE_TEST_CASE_P(VistaAndUp,
894                        AcceleratedSurfaceTransformerTest,
895                        ::testing::ValuesIn(WindowsVersionIfVistaOrBetter()));
896