1 // Copyright (c) 2012 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.
5 #include "base/callback.h"
6 #include "base/command_line.h"
7 #include "base/path_service.h"
8 #include "content/public/browser/gpu_data_manager.h"
9 #include "content/public/browser/gpu_data_manager_observer.h"
10 #include "content/public/browser/web_contents.h"
11 #include "content/public/common/content_paths.h"
12 #include "content/public/common/content_switches.h"
13 #include "content/public/test/browser_test_utils.h"
14 #include "content/public/test/test_utils.h"
15 #include "content/shell/shell.h"
16 #include "content/test/content_browser_test.h"
17 #include "content/test/content_browser_test_utils.h"
18 #include "gpu/command_buffer/service/gpu_switches.h"
19 #include "gpu/config/gpu_test_config.h"
20 #include "net/base/net_util.h"
24 // Run the tests with a memory limit of 256MB, and give
25 // and extra 4MB of wiggle-room for over-allocation.
26 const char* kMemoryLimitMBSwitch
= "256";
27 const size_t kMemoryLimitMB
= 256;
28 const size_t kSingleTabLimitMB
= 128;
29 const size_t kWiggleRoomMB
= 4;
31 // Observer to report GPU memory usage when requested.
32 class GpuMemoryBytesAllocatedObserver
: public GpuDataManagerObserver
{
34 GpuMemoryBytesAllocatedObserver()
35 : bytes_allocated_(0) {
38 virtual ~GpuMemoryBytesAllocatedObserver() {
41 virtual void OnVideoMemoryUsageStatsUpdate(
42 const GPUVideoMemoryUsageStats
& video_memory_usage_stats
) OVERRIDE
{
43 bytes_allocated_
= video_memory_usage_stats
.bytes_allocated
;
44 message_loop_runner_
->Quit();
47 size_t GetBytesAllocated() {
48 message_loop_runner_
= new MessageLoopRunner
;
49 GpuDataManager::GetInstance()->AddObserver(this);
50 GpuDataManager::GetInstance()->RequestVideoMemoryUsageStatsUpdate();
51 message_loop_runner_
->Run();
52 GpuDataManager::GetInstance()->RemoveObserver(this);
53 message_loop_runner_
= NULL
;
54 return bytes_allocated_
;
58 size_t bytes_allocated_
;
59 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
62 class GpuMemoryTest
: public ContentBrowserTest
{
65 : allow_tests_to_run_(false),
66 has_used_first_shell_(false) {
68 virtual ~GpuMemoryTest() {
71 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE
{
72 base::FilePath test_dir
;
73 ASSERT_TRUE(PathService::Get(DIR_TEST_DATA
, &test_dir
));
74 gpu_test_dir_
= test_dir
.AppendASCII("gpu");
77 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
78 command_line
->AppendSwitch(switches::kEnableLogging
);
79 command_line
->AppendSwitch(switches::kForceCompositingMode
);
80 command_line
->AppendSwitchASCII(switches::kForceGpuMemAvailableMb
,
81 kMemoryLimitMBSwitch
);
82 // Only run this on GPU bots for now. These tests should work with
83 // any GPU process, but may be slow.
84 if (command_line
->HasSwitch(switches::kUseGpuInTests
)) {
85 allow_tests_to_run_
= true;
87 // Don't enable these tests on Android just yet (they use lots of memory and
88 // may not be stable).
89 #if defined(OS_ANDROID)
90 allow_tests_to_run_
= false;
99 // Load a page and consume a specified amount of GPU memory.
100 void LoadPage(Shell
* tab_to_load
,
106 url
= gpu_test_dir_
.AppendASCII("mem_css3d.html");
109 url
= gpu_test_dir_
.AppendASCII("mem_webgl.html");
113 NavigateToURL(tab_to_load
, net::FilePathToFileURL(url
));
114 std::ostringstream js_call
;
115 js_call
<< "useGpuMemory(";
116 js_call
<< mb_to_use
;
119 ASSERT_TRUE(ExecuteScriptInFrameAndExtractString(
120 tab_to_load
->web_contents(), std::string(), js_call
.str(), &message
));
121 EXPECT_EQ("DONE_USE_GPU_MEMORY", message
);
125 Shell
* CreateNewTab() {
126 // The ContentBrowserTest will create one shell by default, use that one
127 // first so that we don't confuse the memory manager into thinking there
128 // are more windows than there are.
129 Shell
* new_tab
= has_used_first_shell_
? CreateBrowser() : shell();
130 has_used_first_shell_
= true;
131 tabs_
.insert(new_tab
);
132 visible_tabs_
.insert(new_tab
);
136 void SetTabBackgrounded(Shell
* tab_to_background
) {
138 visible_tabs_
.find(tab_to_background
) != visible_tabs_
.end());
139 visible_tabs_
.erase(tab_to_background
);
140 tab_to_background
->web_contents()->WasHidden();
143 bool MemoryUsageInRange(size_t low
, size_t high
) {
144 FinishGpuMemoryChanges();
145 size_t memory_usage_bytes
= GetMemoryUsageMbytes();
147 // If it's not immediately the case that low <= usage <= high, then
149 // Because we haven't implemented the full delay in FinishGpuMemoryChanges,
150 // keep re-reading the GPU memory usage for 2 seconds before declaring
152 base::Time start_time
= base::Time::Now();
153 while (low
> memory_usage_bytes
|| memory_usage_bytes
> high
) {
154 memory_usage_bytes
= GetMemoryUsageMbytes();
155 base::TimeDelta delta
= base::Time::Now() - start_time
;
156 if (delta
.InMilliseconds() >= 2000)
160 return (low
<= memory_usage_bytes
&& memory_usage_bytes
<= high
);
163 bool AllowTestsToRun() const {
164 return allow_tests_to_run_
;
168 void FinishGpuMemoryChanges() {
169 // This should wait until all effects of memory management complete.
170 // We will need to wait until all
171 // 1. pending commits from the main thread to the impl thread in the
172 // compositor complete (for visible compositors).
173 // 2. allocations that the renderer's impl thread will make due to the
174 // compositor and WebGL are completed.
175 // 3. pending GpuMemoryManager::Manage() calls to manage are made.
176 // 4. renderers' OnMemoryAllocationChanged callbacks in response to
178 // Each step in this sequence can cause trigger the next (as a 1-2-3-4-1
179 // cycle), so we will need to pump this cycle until it stabilizes.
181 // Pump the cycle 8 times (in principle it could take an infinite number
182 // of iterations to settle).
183 for (size_t pump_it
= 0; pump_it
< 8; ++pump_it
) {
184 // Wait for a RequestAnimationFrame to complete from all visible tabs
185 // for stage 1 of the cycle.
186 for (std::set
<Shell
*>::iterator it
= visible_tabs_
.begin();
187 it
!= visible_tabs_
.end();
190 "window.webkitRequestAnimationFrame(function() {"
191 " domAutomationController.setAutomationId(1);"
192 " domAutomationController.send(\"DONE_RAF\");"
195 ASSERT_TRUE(ExecuteScriptInFrameAndExtractString(
196 (*it
)->web_contents(), std::string(), js_call
, &message
));
197 EXPECT_EQ("DONE_RAF", message
);
199 // TODO(ccameron): send an IPC from Browser -> Renderer (delay it until
200 // painting finishes) -> GPU process (delay it until any pending manages
201 // happen) -> All Renderers -> Browser to flush parts 2, 3, and 4.
205 size_t GetMemoryUsageMbytes() {
206 GpuMemoryBytesAllocatedObserver observer
;
207 observer
.GetBytesAllocated();
208 return observer
.GetBytesAllocated() / 1048576;
211 bool allow_tests_to_run_
;
212 std::set
<Shell
*> tabs_
;
213 std::set
<Shell
*> visible_tabs_
;
214 bool has_used_first_shell_
;
215 base::FilePath gpu_test_dir_
;
218 // When trying to load something that doesn't fit into our total GPU memory
219 // limit, we shouldn't exceed that limit.
220 IN_PROC_BROWSER_TEST_F(GpuMemoryTest
, SingleWindowDoesNotExceedLimit
) {
221 if (!AllowTestsToRun())
224 Shell
* tab
= CreateNewTab();
225 LoadPage(tab
, PAGE_CSS3D
, kMemoryLimitMB
);
226 // Make sure that the CSS3D page maxes out a single tab's budget (otherwise
227 // the test doesn't test anything) but still stays under the limit.
228 EXPECT_TRUE(MemoryUsageInRange(
229 kSingleTabLimitMB
- kWiggleRoomMB
,
230 kMemoryLimitMB
+ kWiggleRoomMB
));
233 } // namespace content