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 |