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