1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
|
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <vector>
#include "app/app_switches.h"
#include "app/gfx/gl/gl_implementation.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/gpu_process_host.h"
#include "chrome/browser/gpu_process_host_ui_shim.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/gpu_info.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/test_launcher_utils.h"
#include "chrome/test/ui_test_utils.h"
#include "gfx/codec/png_codec.h"
#include "gfx/size.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
namespace {
// Command line flag for forcing the machine's GPU to be used instead of OSMesa.
const char kUseGpuInTests[] = "use-gpu-in-tests";
// Command line flag for overriding the default location for putting generated
// test images that do not match references.
const char kGeneratedDir[] = "generated-dir";
// Reads and decodes a PNG image to a bitmap. Returns true on success. The PNG
// should have been encoded using |gfx::PNGCodec::Encode|.
bool ReadPNGFile(const FilePath& file_path, SkBitmap* bitmap) {
DCHECK(bitmap);
std::string png_data;
return file_util::ReadFileToString(file_path, &png_data) &&
gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&png_data[0]),
png_data.length(),
bitmap);
}
// Encodes a bitmap into a PNG and write to disk. Returns true on success. The
// parent directory does not have to exist.
bool WritePNGFile(const SkBitmap& bitmap, const FilePath& file_path) {
std::vector<unsigned char> png_data;
if (gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, true, &png_data) &&
file_util::CreateDirectory(file_path.DirName())) {
int bytes_written = file_util::WriteFile(
file_path, reinterpret_cast<char*>(&png_data[0]), png_data.size());
if (bytes_written == static_cast<int>(png_data.size()))
return true;
}
return false;
}
// Resizes the browser window so that the tab's contents are at a given size.
void ResizeTabContainer(Browser* browser, const gfx::Size& desired_size) {
gfx::Rect container_rect;
browser->GetSelectedTabContents()->GetContainerBounds(&container_rect);
// Size cannot be negative, so use a point.
gfx::Point correction(desired_size.width() - container_rect.size().width(),
desired_size.height() - container_rect.size().height());
gfx::Rect window_rect = browser->window()->GetRestoredBounds();
gfx::Size new_size = window_rect.size();
new_size.Enlarge(correction.x(), correction.y());
window_rect.set_size(new_size);
browser->window()->SetBounds(window_rect);
}
// Observes when any GPU info has been collected and quits the current message
// loop.
class GPUInfoCollectedObserver {
public:
explicit GPUInfoCollectedObserver(GpuProcessHostUIShim* gpu_host_shim)
: gpu_host_shim_(gpu_host_shim),
gpu_info_collected_(false) {
gpu_host_shim_->set_gpu_info_collected_callback(
NewCallback(this, &GPUInfoCollectedObserver::OnGpuInfoCollected));
}
void OnGpuInfoCollected() {
gpu_info_collected_ = true;
gpu_host_shim_->set_gpu_info_collected_callback(NULL);
MessageLoopForUI::current()->Quit();
}
bool gpu_info_collected() { return gpu_info_collected_; }
private:
GpuProcessHostUIShim* gpu_host_shim_;
bool gpu_info_collected_;
};
// Collects info about the GPU. Iff the info is collected, |client_info| will be
// set and true will be returned. This info may be partial or complete. This
// will return false if we are running in a virtualized environment.
bool CollectGPUInfo(GPUInfo* client_info) {
CHECK(client_info);
GpuProcessHostUIShim* gpu_host_shim = GpuProcessHostUIShim::GetInstance();
if (!gpu_host_shim)
return false;
GPUInfo info = gpu_host_shim->gpu_info();
if (info.level() == GPUInfo::kUninitialized) {
GPUInfoCollectedObserver observer(gpu_host_shim);
gpu_host_shim->CollectGraphicsInfoAsynchronously(GPUInfo::kPartial);
ui_test_utils::RunMessageLoop();
if (!observer.gpu_info_collected())
return false;
info = gpu_host_shim->gpu_info();
}
*client_info = info;
return true;
}
} // namespace
// Test fixture for GPU image comparison tests.
// TODO(kkania): Document how to add to/modify these tests.
class GpuPixelBrowserTest : public InProcessBrowserTest {
public:
GpuPixelBrowserTest() {}
virtual void SetUpCommandLine(CommandLine* command_line) {
// This enables DOM automation for tab contents.
EnableDOMAutomation();
// These tests by default use OSMesa. This can be changed if the |kUseGL|
// switch is explicitly set to something else or if |kUseGpuInTests| is
// present.
if (command_line->HasSwitch(switches::kUseGL)) {
using_gpu_ = command_line->GetSwitchValueASCII(switches::kUseGL) !=
gfx::kGLImplementationOSMesaName;
} else if (command_line->HasSwitch(kUseGpuInTests)) {
using_gpu_ = true;
} else {
// OSMesa will be used by default.
EXPECT_TRUE(test_launcher_utils::OverrideGLImplementation(
command_line,
gfx::kGLImplementationOSMesaName));
#if defined(OS_MACOSX)
// Accelerated compositing does not work with OSMesa. AcceleratedSurface
// assumes GL contexts are native.
command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
#endif
using_gpu_ = false;
}
}
virtual void SetUpInProcessBrowserTestFixture() {
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_));
test_data_dir_ = test_data_dir_.AppendASCII("gpu");
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kGeneratedDir))
generated_img_dir_ = command_line->GetSwitchValuePath(kGeneratedDir);
else
generated_img_dir_ = test_data_dir_.AppendASCII("generated");
if (using_gpu_)
reference_img_dir_ = test_data_dir_.AppendASCII("gpu_reference");
else
reference_img_dir_ = test_data_dir_.AppendASCII("sw_reference");
test_name_ = testing::UnitTest::GetInstance()->current_test_info()->name();
const char* test_status_prefixes[] = {"DISABLED_", "FLAKY_", "FAILS_"};
for (size_t i = 0; i < arraysize(test_status_prefixes); ++i) {
ReplaceFirstSubstringAfterOffset(
&test_name_, 0, test_status_prefixes[i], "");
}
}
// Compares the generated bitmap with the appropriate reference image on disk.
// The reference image is determined by the name of the test, the |postfix|,
// and the vendor and device ID. Returns true iff the images were the same.
//
// |postfix|, is an optional name that can be appended onto the images name to
// distinguish between images from a single test.
//
// On failure, the image will be written to disk, and can be collected as a
// baseline. The image format is:
// <test_name>_<postfix>_<os>_<vendor_id>-<device_id>.png
// E.g.,
// WebGLFishTank_FirstTab_120981-1201.png
bool CompareImages(const SkBitmap& gen_bmp, const std::string& postfix) {
// Determine the name of the image.
std::string img_name = test_name_;
if (postfix.length())
img_name += "_" + postfix;
#if defined(OS_WIN)
const char* os_label = "win";
#elif defined(OS_MACOSX)
const char* os_label = "mac";
#elif defined(OS_LINUX)
const char* os_label = "linux";
#else
#error "Not implemented for this platform"
#endif
if (using_gpu_) {
GPUInfo info;
if (!CollectGPUInfo(&info)) {
LOG(ERROR) << "Could not get gpu info";
return false;
}
img_name = base::StringPrintf("%s_%s_%04x-%04x.png",
img_name.c_str(), os_label, info.vendor_id(), info.device_id());
} else {
img_name = base::StringPrintf("%s_%s_mesa.png",
img_name.c_str(), os_label);
}
// Read the reference image and verify the images' dimensions are equal.
FilePath ref_img_path = reference_img_dir_.AppendASCII(img_name);
SkBitmap ref_bmp;
bool should_compare = true;
if (!ReadPNGFile(ref_img_path, &ref_bmp)) {
LOG(ERROR) << "Cannot read reference image: " << ref_img_path.value();
should_compare = false;
}
if (should_compare &&
(ref_bmp.width() != gen_bmp.width() ||
ref_bmp.height() != gen_bmp.height())) {
LOG(ERROR)
<< "Dimensions do not match (Expected) vs (Actual):"
<< "(" << ref_bmp.width() << "x" << ref_bmp.height()
<< ") vs. "
<< "(" << gen_bmp.width() << "x" << gen_bmp.height() << ")";
should_compare = false;
}
// Compare pixels and create a simple diff image.
int diff_pixels_count = 0;
SkBitmap diff_bmp;
if (should_compare) {
diff_bmp.setConfig(SkBitmap::kARGB_8888_Config,
gen_bmp.width(), gen_bmp.height());
diff_bmp.allocPixels();
diff_bmp.eraseColor(SK_ColorWHITE);
SkAutoLockPixels lock_bmp(gen_bmp);
SkAutoLockPixels lock_ref_bmp(ref_bmp);
SkAutoLockPixels lock_diff_bmp(diff_bmp);
// The reference images were saved with no alpha channel. Use the mask to
// set alpha to 0.
uint32_t kAlphaMask = 0x00FFFFFF;
for (int x = 0; x < gen_bmp.width(); ++x) {
for (int y = 0; y < gen_bmp.height(); ++y) {
if ((*gen_bmp.getAddr32(x, y) & kAlphaMask) !=
(*ref_bmp.getAddr32(x, y) & kAlphaMask)) {
++diff_pixels_count;
*diff_bmp.getAddr32(x, y) = 192 << 16; // red
}
}
}
}
// Write the generated and diff images if the comparison failed.
if (!should_compare || diff_pixels_count != 0) {
FilePath gen_img_path = generated_img_dir_.AppendASCII(img_name);
if (WritePNGFile(gen_bmp, gen_img_path)) {
// Output the generated image path to the log for easy parsing later.
std::cout << "*GEN_IMG=" << gen_img_path.value() << std::endl;
} else {
LOG(WARNING) << "Could not write generated image: "
<< gen_img_path.value();
}
if (should_compare) {
WritePNGFile(diff_bmp,
generated_img_dir_.AppendASCII("diff-" + img_name));
LOG(ERROR) << "Images differ by pixel count: " << diff_pixels_count;
}
return false;
}
return true;
}
protected:
FilePath test_data_dir_;
private:
FilePath reference_img_dir_;
FilePath generated_img_dir_;
// The name of the test, with any special prefixes dropped.
std::string test_name_;
// Whether the gpu, or OSMesa is being used for rendering.
bool using_gpu_;
DISALLOW_COPY_AND_ASSIGN(GpuPixelBrowserTest);
};
IN_PROC_BROWSER_TEST_F(GpuPixelBrowserTest, WebGLTeapot) {
ui_test_utils::DOMMessageQueue message_queue;
ui_test_utils::NavigateToURL(
browser(),
net::FilePathToFileURL(test_data_dir_.AppendASCII("webgl_teapot").
AppendASCII("teapot.html")));
// Wait for message from teapot page indicating the GL calls have been issued.
ASSERT_TRUE(message_queue.WaitForMessage(NULL));
SkBitmap bitmap;
gfx::Size container_size(500, 500);
ResizeTabContainer(browser(), container_size);
ASSERT_TRUE(ui_test_utils::TakeRenderWidgetSnapshot(
browser()->GetSelectedTabContents()->render_view_host(),
container_size, &bitmap));
ASSERT_TRUE(CompareImages(bitmap, ""));
}
|