Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2010 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/hash.h" | |
| 10 #include "base/scoped_native_library.h" | |
| 11 #include "base/stringprintf.h" | |
| 12 #include "base/win/scoped_comptr.h" | |
| 13 #include "testing/gtest/include/gtest/gtest.h" | |
| 14 #include "ui/gfx/rect.h" | |
| 15 #include "ui/surface/accelerated_surface_transformer_win.h" | |
| 16 #include "ui/surface/accelerated_surface_win.h" | |
| 17 #include "ui/surface/d3d9_utils_win.h" | |
| 18 | |
| 19 namespace d3d_utils = ui_surface_d3d9_utils; | |
| 20 | |
| 21 using base::win::ScopedComPtr; | |
| 22 using std::uniform_int_distribution; | |
| 23 | |
| 24 // Provides a reference rasterizer (all rendering done by software emulation) | |
| 25 // Direct3D device, for use by unit tests. | |
| 26 class AcceleratedSurfaceTransformerTest : public testing::Test { | |
| 27 public: | |
| 28 AcceleratedSurfaceTransformerTest() { | |
| 29 | |
|
apatrick_chromium
2012/12/14 23:07:58
nit: extra newline
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 30 }; | |
| 31 | |
| 32 // Methods to be called by the test. | |
| 33 IDirect3DDevice9Ex* device() { return device_.get(); } | |
| 34 | |
| 35 // testing::Environment implementation. | |
| 36 virtual void SetUp() { | |
| 37 if (!d3d_module_.is_valid()) { | |
| 38 if (!d3d_utils::LoadD3D9(&d3d_module_)) { | |
| 39 GTEST_FAIL(); | |
| 40 return; | |
| 41 } | |
| 42 } | |
| 43 if (!d3d_utils::CreateDevice(d3d_module_, | |
| 44 D3DDEVTYPE_HAL, | |
| 45 D3DPRESENT_INTERVAL_IMMEDIATE, | |
| 46 device_.Receive())) { | |
| 47 GTEST_FAIL(); | |
| 48 return; | |
| 49 } | |
| 50 const ::testing::TestInfo* const test_info = | |
| 51 ::testing::UnitTest::GetInstance()->current_test_info(); | |
| 52 const std::string& test_name = | |
| 53 StringPrintf("%s.%s", test_info->test_case_name(), test_info->name()); | |
| 54 rng_.seed(base::Hash(test_name)); | |
| 55 random_dword_.reset(); | |
| 56 } | |
| 57 | |
| 58 virtual void TearDown() { | |
| 59 device_ = NULL; | |
| 60 } | |
| 61 | |
| 62 // Locks and fills a surface with a checkerboard pattern where the colors | |
| 63 // are random but the total image pattern is horizontally and vertically | |
| 64 // symmetric. | |
| 65 template<typename ColorType> | |
| 66 void FillSymmetricRandomCheckerboard( | |
| 67 IDirect3DSurface9* lockable_surface, | |
| 68 const gfx::Size& size, | |
| 69 int checker_square_size) { | |
| 70 | |
| 71 D3DLOCKED_RECT locked_rect; | |
| 72 ASSERT_TRUE(SUCCEEDED( | |
| 73 lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD))); | |
| 74 ColorType* surface = reinterpret_cast<ColorType*>(locked_rect.pBits); | |
| 75 ASSERT_EQ(0, locked_rect.Pitch % sizeof(ColorType)); | |
| 76 int pitch = locked_rect.Pitch / sizeof(ColorType); | |
| 77 | |
| 78 for (int y = 0; y <= size.height() / 2; y += checker_square_size) { | |
| 79 for (int x = 0; x <= size.width() / 2; x += checker_square_size) { | |
| 80 ColorType color = static_cast<ColorType>(RandomColor()); | |
| 81 int y_limit = std::min(size.height() / 2, y + checker_square_size - 1); | |
| 82 int x_limit = std::min(size.width() / 2, x + checker_square_size - 1); | |
| 83 for (int y_lo = y; y_lo <= y_limit; y_lo++) { | |
| 84 for (int x_lo = x; x_lo <= x_limit; x_lo++) { | |
| 85 int y_hi = size.height() - 1 - y_lo; | |
| 86 int x_hi = size.width() - 1 - x_lo; | |
| 87 surface[x_lo + y_lo*pitch] = color; | |
| 88 surface[x_lo + y_hi*pitch] = color; | |
| 89 surface[x_hi + y_lo*pitch] = color; | |
| 90 surface[x_hi + y_hi*pitch] = color; | |
| 91 } | |
| 92 } | |
| 93 } | |
| 94 } | |
| 95 | |
| 96 lockable_surface->UnlockRect(); | |
| 97 } | |
| 98 | |
| 99 template<typename ColorType> | |
| 100 void FillRandomCheckerboard( | |
| 101 IDirect3DSurface9* lockable_surface, | |
| 102 const gfx::Size& size, | |
| 103 int checker_square_size) { | |
| 104 | |
| 105 D3DLOCKED_RECT locked_rect; | |
| 106 ASSERT_TRUE(SUCCEEDED( | |
| 107 lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_DISCARD))); | |
| 108 ColorType* surface = reinterpret_cast<ColorType*>(locked_rect.pBits); | |
| 109 ASSERT_EQ(0, locked_rect.Pitch % sizeof(ColorType)); | |
| 110 int pitch = locked_rect.Pitch / sizeof(ColorType); | |
| 111 | |
| 112 for (int y = 0; y <= size.height(); y += checker_square_size) { | |
| 113 for (int x = 0; x <= size.width(); x += checker_square_size) { | |
| 114 ColorType color = static_cast<ColorType>(RandomColor()); | |
| 115 int y_limit = std::min(size.height(), y + checker_square_size); | |
| 116 int x_limit = std::min(size.width(), x + checker_square_size); | |
| 117 for (int square_y = y; square_y < y_limit; square_y++) { | |
| 118 for (int square_x = x; square_x < x_limit; square_x++) { | |
| 119 surface[square_x + square_y*pitch] = color; | |
| 120 } | |
| 121 } | |
| 122 } | |
| 123 } | |
| 124 | |
| 125 lockable_surface->UnlockRect(); | |
| 126 } | |
| 127 | |
| 128 // Approximate color-equality check. Allows for some rounding error. | |
| 129 bool AssertSameColor(DWORD color_a, DWORD color_b) { | |
| 130 if (color_a == color_b) | |
| 131 return true; | |
| 132 uint8* a = reinterpret_cast<uint8*>(&color_a); | |
| 133 uint8* b = reinterpret_cast<uint8*>(&color_b); | |
| 134 int max_error = 0; | |
| 135 for (int i = 0; i < 4; i++) | |
| 136 max_error = std::max(max_error, std::abs((int)a[i] - (int)b[i])); | |
|
apatrick_chromium
2012/12/14 23:07:58
nit: no c-style casts
ncarter (slow)
2012/12/17 18:58:59
Done. Looks like my vacation in Java-land rotted
| |
| 137 | |
| 138 if (max_error <= kAbsoluteColorErrorTolerance) | |
| 139 return true; | |
| 140 | |
| 141 EXPECT_EQ(StringPrintf("%d %d %d %d", a[0], a[1], a[2], a[3]), | |
| 142 StringPrintf("%d %d %d %d", b[0], b[1], b[2], b[3])); | |
| 143 | |
| 144 return false; | |
| 145 } | |
| 146 | |
| 147 // Asserts that an image is symmetric with respect to itself: both | |
| 148 // horizontally and vertically, within the tolerance of AssertSameColor. | |
| 149 template<typename ColorType> | |
| 150 void AssertSymmetry(IDirect3DSurface9* lockable_surface, | |
| 151 const gfx::Size& size) { | |
| 152 D3DLOCKED_RECT locked_rect; | |
| 153 ASSERT_TRUE(SUCCEEDED( | |
| 154 lockable_surface->LockRect(&locked_rect, NULL, D3DLOCK_READONLY))); | |
| 155 ASSERT_EQ(0, locked_rect.Pitch % sizeof(ColorType)); | |
| 156 int pitch = locked_rect.Pitch / sizeof(ColorType); | |
| 157 ColorType* surface = reinterpret_cast<ColorType*>(locked_rect.pBits); | |
| 158 for (int y_lo = 0; y_lo < size.height() / 2; y_lo++) { | |
| 159 int y_hi = size.height() - 1 - y_lo; | |
| 160 for (int x_lo = 0; x_lo < size.width() / 2; x_lo++) { | |
| 161 int x_hi = size.width() - 1 - x_lo; | |
| 162 if (!AssertSameColor(surface[x_lo + y_lo*pitch], | |
| 163 surface[x_hi + y_lo*pitch])) { | |
| 164 FAIL() << StringPrintf("Pixels (%d, %d) vs. (%d, %d)", | |
| 165 x_lo, y_lo, x_hi, y_lo); | |
| 166 } | |
| 167 if (!AssertSameColor(surface[x_hi + y_lo*pitch], | |
| 168 surface[x_hi + y_hi*pitch])) { | |
| 169 FAIL() << StringPrintf("Pixels (%d, %d) vs. (%d, %d)", | |
| 170 x_hi, y_lo, x_hi, y_hi); | |
| 171 } | |
| 172 if (!AssertSameColor(surface[x_hi + y_hi*pitch], | |
| 173 surface[x_lo + y_hi*pitch])) { | |
| 174 FAIL() << StringPrintf("Pixels (%d, %d) vs. (%d, %d)", | |
| 175 x_hi, y_hi, x_lo, y_hi); | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 lockable_surface->UnlockRect(); | |
| 180 } | |
| 181 | |
| 182 // Asserts that an image is symmetric with respect to itself: both | |
|
apatrick_chromium
2012/12/14 23:07:58
Comment is copy and paste of as AssertSymmetry?
| |
| 183 // horizontally and vertically, within the tolerance of AssertSameColor. | |
| 184 template<typename ColorType> | |
| 185 void AssertIsInvertedCopy(const gfx::Size& size, | |
| 186 IDirect3DSurface9* expected, | |
| 187 IDirect3DSurface9* actual) { | |
| 188 | |
| 189 D3DLOCKED_RECT locked_expected, locked_actual; | |
| 190 ASSERT_TRUE(SUCCEEDED( | |
| 191 expected->LockRect(&locked_expected, NULL, D3DLOCK_READONLY))); | |
| 192 ASSERT_TRUE(SUCCEEDED( | |
| 193 actual->LockRect(&locked_actual, NULL, D3DLOCK_READONLY))); | |
| 194 ASSERT_EQ(0, locked_expected.Pitch % sizeof(ColorType)); | |
| 195 int pitch = locked_expected.Pitch / sizeof(ColorType); | |
| 196 ColorType* expected_image = | |
| 197 reinterpret_cast<ColorType*>(locked_expected.pBits); | |
| 198 ColorType* actual_image = | |
| 199 reinterpret_cast<ColorType*>(locked_actual.pBits); | |
| 200 for (int y = 0; y < size.height(); y++) { | |
| 201 int y_actual = size.height() - 1 - y; | |
| 202 ASSERT_EQ(0, | |
| 203 memcmp(&expected_image[y*pitch], &actual_image[y_actual*pitch], | |
| 204 sizeof(ColorType) * size.width())) | |
| 205 << StringPrintf("Rows not equal: %d vs. %d", y, y_actual); | |
| 206 } | |
| 207 expected->UnlockRect(); | |
| 208 actual->UnlockRect(); | |
| 209 } | |
| 210 | |
| 211 | |
| 212 protected: | |
| 213 static const int kAbsoluteColorErrorTolerance = 4; | |
| 214 | |
| 215 DWORD RandomColor() { | |
| 216 return random_dword_(rng_); | |
| 217 } | |
| 218 | |
| 219 void DoResizeBilinearTest(AcceleratedSurfaceTransformer* gpu_ops, | |
| 220 const gfx::Size& src_size, | |
| 221 const gfx::Size& dst_size, | |
| 222 int checkerboard_size) { | |
| 223 | |
| 224 SCOPED_TRACE( | |
| 225 StringPrintf("Resizing %dx%d -> %dx%d at checkerboard size of %d", | |
| 226 src_size.width(), src_size.height(), | |
| 227 dst_size.width(), dst_size.height(), | |
| 228 checkerboard_size)); | |
| 229 | |
| 230 base::win::ScopedComPtr<IDirect3DSurface9> src, dst; | |
| 231 ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface( | |
| 232 device(), src_size, src.Receive())); | |
| 233 ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface( | |
| 234 device(), dst_size, dst.Receive())); | |
| 235 | |
| 236 FillSymmetricRandomCheckerboard<DWORD>(src, src_size, checkerboard_size); | |
| 237 | |
| 238 ASSERT_TRUE(gpu_ops->ResizeBilinear(src, gfx::Rect(src_size), dst)); | |
| 239 | |
| 240 AssertSymmetry<DWORD>(dst, dst_size); | |
| 241 } | |
| 242 | |
| 243 void DoCopyInvertedTest(AcceleratedSurfaceTransformer* gpu_ops, | |
| 244 const gfx::Size& size) { | |
| 245 | |
| 246 SCOPED_TRACE( | |
| 247 StringPrintf("CopyInverted @ %dx%d", size.width(), size.height())); | |
| 248 | |
| 249 base::win::ScopedComPtr<IDirect3DSurface9> checkerboard, src, dst; | |
| 250 base::win::ScopedComPtr<IDirect3DTexture9> src_texture; | |
| 251 ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface(device(), size, | |
| 252 checkerboard.Receive())); | |
| 253 ASSERT_TRUE(d3d_utils::CreateTemporaryRenderTargetTexture(device(), size, | |
| 254 src_texture.Receive(), src.Receive())); | |
| 255 ASSERT_TRUE(d3d_utils::CreateTemporaryLockableSurface(device(), size, | |
| 256 dst.Receive())); | |
| 257 | |
| 258 FillRandomCheckerboard<DWORD>(checkerboard, size, 1); | |
| 259 ASSERT_TRUE(SUCCEEDED( | |
| 260 device()->StretchRect(checkerboard, NULL, src, NULL, D3DTEXF_NONE))); | |
| 261 ASSERT_TRUE(gpu_ops->CopyInverted(src_texture, dst, size)); | |
| 262 AssertIsInvertedCopy<DWORD>(size, checkerboard, dst); | |
| 263 } | |
| 264 | |
| 265 uniform_int_distribution<DWORD> random_dword_; | |
| 266 std::mt19937 rng_; | |
| 267 base::ScopedNativeLibrary d3d_module_; | |
| 268 base::win::ScopedComPtr<IDirect3DDevice9Ex> device_; | |
| 269 }; | |
| 270 | |
| 271 | |
| 272 | |
| 273 #if defined(OS_WIN) | |
|
apatrick_chromium
2012/12/14 23:07:58
Won't this whole file need to be ifdefed out? Assu
ncarter (slow)
2012/12/15 00:04:15
I think it's unnecessary -- this was cruft from th
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 274 | |
| 275 TEST_F(AcceleratedSurfaceTransformerTest, Init) { | |
|
apatrick_chromium
2012/12/14 23:07:58
I think you'll need to disable these tests for XP.
ncarter (slow)
2012/12/15 00:04:15
Good point.
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 276 AcceleratedSurfaceTransformer gpu_ops; | |
| 277 ASSERT_TRUE(gpu_ops.Init(device())); | |
| 278 }; | |
| 279 | |
| 280 TEST_F(AcceleratedSurfaceTransformerTest, TestConsistentRandom) { | |
| 281 // The random generator is be seeded from the test name, so this | |
| 282 // should be the same for every execution. Otherwise tests might be flaky. | |
| 283 DWORD d1 = RandomColor(); | |
| 284 ASSERT_EQ(2922058934, d1); | |
|
apatrick_chromium
2012/12/14 23:07:58
Prefixing the test with FLAKY_ or FAILS_ would cha
ncarter (slow)
2012/12/15 00:04:15
Eek, gross. I guess I'll move to an explicit seed.
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 285 } | |
| 286 | |
| 287 TEST_F(AcceleratedSurfaceTransformerTest, MixedOperations) { | |
| 288 AcceleratedSurfaceTransformer t; | |
| 289 ASSERT_TRUE(t.Init(device())); | |
| 290 | |
| 291 DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 1); | |
| 292 DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 2); | |
| 293 DoCopyInvertedTest(&t, gfx::Size(20, 107)); | |
| 294 DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(255, 255), 5); | |
| 295 DoResizeBilinearTest(&t, gfx::Size(256, 256), gfx::Size(64, 64), 5); | |
| 296 DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(3, 3), 1); | |
| 297 DoCopyInvertedTest(&t, gfx::Size(1412, 124)); | |
| 298 DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 1); | |
| 299 DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 2); | |
| 300 | |
| 301 DoCopyInvertedTest(&t, gfx::Size(1512, 7)); | |
| 302 DoResizeBilinearTest(&t, gfx::Size(255, 255), gfx::Size(257, 257), 5); | |
| 303 DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 8); | |
| 304 DoCopyInvertedTest(&t, gfx::Size(1521, 3)); | |
| 305 DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 256), 1); | |
| 306 DoCopyInvertedTest(&t, gfx::Size(33, 712)); | |
| 307 DoResizeBilinearTest(&t, gfx::Size(150, 256), gfx::Size(126, 8), 8); | |
| 308 DoCopyInvertedTest(&t, gfx::Size(33, 2)); | |
| 309 DoResizeBilinearTest(&t, gfx::Size(200, 256), gfx::Size(126, 8), 8); | |
| 310 } | |
| 311 | |
| 312 // Tests ResizeBilinear with 16K wide/hight src and dst surfaces. | |
| 313 TEST_F(AcceleratedSurfaceTransformerTest, LargeSurfaces) { | |
| 314 AcceleratedSurfaceTransformer gpu_ops; | |
| 315 ASSERT_TRUE(gpu_ops.Init(device())); | |
| 316 | |
| 317 const int lo = 256; | |
| 318 const int hi = 16384; | |
| 319 | |
| 320 DoResizeBilinearTest(&gpu_ops, gfx::Size(hi, lo), gfx::Size(lo, lo), 1); | |
| 321 DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, hi), gfx::Size(lo, lo), 1); | |
| 322 DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(hi, lo), lo); | |
| 323 DoResizeBilinearTest(&gpu_ops, gfx::Size(lo, lo), gfx::Size(lo, hi), lo); | |
| 324 DoCopyInvertedTest(&gpu_ops, gfx::Size(hi, lo)); | |
| 325 DoCopyInvertedTest(&gpu_ops, gfx::Size(lo, hi)); | |
| 326 } | |
| 327 | |
| 328 // Exercises ResizeBilinear with random minification cases where the | |
| 329 // aspect ratio does not change. | |
| 330 TEST_F(AcceleratedSurfaceTransformerTest, MinifyUniform) { | |
| 331 AcceleratedSurfaceTransformer gpu_ops; | |
| 332 ASSERT_TRUE(gpu_ops.Init(device())); | |
| 333 | |
| 334 int dims[] = { 21, 63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257}; | |
| 335 int checkerboards[] = {1, 2, 3, 9}; | |
| 336 uniform_int_distribution<int> dim(0, arraysize(dims) - 1); | |
| 337 uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1); | |
| 338 | |
| 339 for (int i = 0; i < 300; i++) { | |
| 340 // Widths are picked so that dst is smaller than src. | |
| 341 int dst_width = dims[dim(rng_)]; | |
| 342 int src_width = dims[dim(rng_)]; | |
| 343 if (src_width < dst_width) | |
| 344 std::swap(dst_width, src_width); | |
| 345 | |
| 346 // src_width is picked to preserve aspect ratio. | |
| 347 int dst_height = dims[dim(rng_)]; | |
| 348 int src_height = (int) (((int64) src_width) * dst_height / dst_width); | |
|
apatrick_chromium
2012/12/14 23:07:58
nit: no c-style casts
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 349 | |
| 350 int checkerboard_size = checkerboards[checkerboard(rng_)]; | |
| 351 | |
| 352 DoResizeBilinearTest(&gpu_ops, | |
| 353 gfx::Size(src_width, src_height), // Src size (larger) | |
| 354 gfx::Size(dst_width, dst_height), // Dst size (smaller) | |
| 355 checkerboard_size); | |
| 356 } | |
| 357 }; | |
| 358 | |
| 359 // Exercises ResizeBilinear with random magnification cases where the | |
| 360 // aspect ratio does not change. | |
| 361 // | |
| 362 // Disabled. This test relies on an assertion that resizing preserves | |
| 363 // symmetry in the image, but for the current implementation of ResizeBilinear, | |
| 364 // this does not seem to be true. | |
| 365 TEST_F(AcceleratedSurfaceTransformerTest, DISABLED_MagnifyUniform) { | |
| 366 AcceleratedSurfaceTransformer gpu_ops; | |
| 367 ASSERT_TRUE(gpu_ops.Init(device())); | |
| 368 | |
| 369 int dims[] = {63, 64, 65, 99, 127, 128, 129, 192, 255, 256, 257}; | |
| 370 int checkerboards[] = {1, 2, 3, 9}; | |
| 371 uniform_int_distribution<int> dim(0, arraysize(dims) - 1); | |
| 372 uniform_int_distribution<int> checkerboard(0, arraysize(checkerboards) - 1); | |
| 373 | |
| 374 for (int i = 0; i < 50; i++) { | |
| 375 // Widths are picked so that b is smaller than a. | |
| 376 int dst_width = dims[dim(rng_)]; | |
| 377 int src_width = dims[dim(rng_)]; | |
| 378 if (dst_width < src_width) | |
| 379 std::swap(src_width, dst_width); | |
| 380 | |
| 381 int dst_height = dims[dim(rng_)]; | |
| 382 int src_height = (int) (((int64) src_width) * dst_height / dst_width); | |
|
apatrick_chromium
2012/12/14 23:07:58
nit: no c-style casts
ncarter (slow)
2012/12/17 18:58:59
Done.
| |
| 383 | |
| 384 int checkerboard_size = checkerboards[checkerboard(rng_)]; | |
| 385 | |
| 386 DoResizeBilinearTest(&gpu_ops, | |
| 387 gfx::Size(src_width, src_height), // Src size (smaller) | |
| 388 gfx::Size(dst_width, dst_height), // Dst size (larger) | |
| 389 checkerboard_size); | |
| 390 } | |
| 391 }; | |
| 392 | |
| 393 #endif | |
| OLD | NEW |