summaryrefslogtreecommitdiffstats
path: root/content/browser/gpu/gpu_memory_test.cc
blob: 36584c39305be4275651aef97c48a6140b9ef621 (plain)
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
// Copyright (c) 2012 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 "base/callback.h"
#include "base/command_line.h"
#include "base/path_service.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/shell.h"
#include "content/test/content_browser_test.h"
#include "content/test/content_browser_test_utils.h"
#include "content/test/gpu/gpu_test_config.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "net/base/net_util.h"

namespace content {

// Run the tests with a memory limit of 256MB, and give
// and extra 4MB of wiggle-room for over-allocation.
const char* kMemoryLimitMBSwitch = "256";
const size_t kMemoryLimitMB = 256;
const size_t kSingleTabLimitMB = 128;
const size_t kWiggleRoomMB = 4;

// Observer to report GPU memory usage when requested.
class GpuMemoryBytesAllocatedObserver : public GpuDataManagerObserver {
 public:
  GpuMemoryBytesAllocatedObserver()
      : bytes_allocated_(0) {
  }

  virtual ~GpuMemoryBytesAllocatedObserver() {
  }

  virtual void OnVideoMemoryUsageStatsUpdate(
      const GPUVideoMemoryUsageStats& video_memory_usage_stats) OVERRIDE {
    bytes_allocated_ = video_memory_usage_stats.bytes_allocated;
    message_loop_runner_->Quit();
  }

  size_t GetBytesAllocated() {
    message_loop_runner_ = new MessageLoopRunner;
    GpuDataManager::GetInstance()->AddObserver(this);
    GpuDataManager::GetInstance()->RequestVideoMemoryUsageStatsUpdate();
    message_loop_runner_->Run();
    GpuDataManager::GetInstance()->RemoveObserver(this);
    message_loop_runner_ = NULL;
    return bytes_allocated_;
  }

 private:
  size_t bytes_allocated_;
  scoped_refptr<MessageLoopRunner> message_loop_runner_;
};

class GpuMemoryTest : public ContentBrowserTest {
 public:
  GpuMemoryTest()
      : allow_tests_to_run_(false),
        has_used_first_shell_(false) {
  }
  virtual ~GpuMemoryTest() {
  }

  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
    base::FilePath test_dir;
    ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &test_dir));
    gpu_test_dir_ = test_dir.AppendASCII("gpu");
  }

  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    command_line->AppendSwitch(switches::kEnableLogging);
    command_line->AppendSwitch(switches::kForceCompositingMode);
    command_line->AppendSwitchASCII(switches::kForceGpuMemAvailableMb,
                                    kMemoryLimitMBSwitch);
    // Only run this on GPU bots for now. These tests should work with
    // any GPU process, but may be slow.
    if (command_line->HasSwitch(switches::kUseGpuInTests)) {
      allow_tests_to_run_ = true;
    }
    // Don't enable these tests on Android just yet (they use lots of memory and
    // may not be stable).
#if defined(OS_ANDROID)
    allow_tests_to_run_ = false;
#endif
  }

  enum PageType {
    PAGE_CSS3D,
    PAGE_WEBGL,
  };

  // Load a page and consume a specified amount of GPU memory.
  void LoadPage(Shell* tab_to_load,
                PageType page_type,
                size_t mb_to_use) {
    base::FilePath url;
    switch (page_type) {
      case PAGE_CSS3D:
        url = gpu_test_dir_.AppendASCII("mem_css3d.html");
        break;
      case PAGE_WEBGL:
        url = gpu_test_dir_.AppendASCII("mem_webgl.html");
        break;
    }

    NavigateToURL(tab_to_load, net::FilePathToFileURL(url));
    std::ostringstream js_call;
    js_call << "useGpuMemory(";
    js_call << mb_to_use;
    js_call << ");";
    std::string message;
    ASSERT_TRUE(
        ExecuteScriptInFrameAndExtractString(
        tab_to_load->web_contents(),
        "",
        js_call.str(),
        &message));
    EXPECT_EQ("DONE_USE_GPU_MEMORY", message);
  }

  // Create a new tab.
  Shell* CreateNewTab() {
    // The ContentBrowserTest will create one shell by default, use that one
    // first so that we don't confuse the memory manager into thinking there
    // are more windows than there are.
    Shell* new_tab = has_used_first_shell_ ? CreateBrowser() : shell();
    has_used_first_shell_ = true;
    tabs_.insert(new_tab);
    visible_tabs_.insert(new_tab);
    return new_tab;
  }

  void SetTabBackgrounded(Shell* tab_to_background) {
    ASSERT_TRUE(
        visible_tabs_.find(tab_to_background) != visible_tabs_.end());
    visible_tabs_.erase(tab_to_background);
    tab_to_background->web_contents()->WasHidden();
  }

  bool MemoryUsageInRange(size_t low, size_t high) {
    FinishGpuMemoryChanges();
    size_t memory_usage_bytes = GetMemoryUsageMbytes();

    // If it's not immediately the case that low <= usage <= high, then
    // allow
    // Because we haven't implemented the full delay in FinishGpuMemoryChanges,
    // keep re-reading the GPU memory usage for 2 seconds before declaring
    // failure.
    base::Time start_time = base::Time::Now();
    while (low > memory_usage_bytes || memory_usage_bytes > high) {
      memory_usage_bytes = GetMemoryUsageMbytes();
      base::TimeDelta delta = base::Time::Now() - start_time;
      if (delta.InMilliseconds() >= 2000)
        break;
    }

    return (low <= memory_usage_bytes && memory_usage_bytes <= high);
  }

  bool AllowTestsToRun() const {
    return allow_tests_to_run_;
  }

 private:
  void FinishGpuMemoryChanges() {
    // This should wait until all effects of memory management complete.
    // We will need to wait until all
    // 1. pending commits from the main thread to the impl thread in the
    //    compositor complete (for visible compositors).
    // 2. allocations that the renderer's impl thread will make due to the
    //    compositor and WebGL are completed.
    // 3. pending GpuMemoryManager::Manage() calls to manage are made.
    // 4. renderers' OnMemoryAllocationChanged callbacks in response to
    //    manager are made.
    // Each step in this sequence can cause trigger the next (as a 1-2-3-4-1
    // cycle), so we will need to pump this cycle until it stabilizes.

    // Pump the cycle 8 times (in principle it could take an infinite number
    // of iterations to settle).
    for (size_t pump_it = 0; pump_it < 8; ++pump_it) {
      // Wait for a RequestAnimationFrame to complete from all visible tabs
      // for stage 1 of the cycle.
      for (std::set<Shell*>::iterator it = visible_tabs_.begin();
           it != visible_tabs_.end();
           ++it) {
        std::string js_call(
            "window.webkitRequestAnimationFrame(function() {"
            "  domAutomationController.setAutomationId(1);"
            "  domAutomationController.send(\"DONE_RAF\");"
            "})");
        std::string message;
        ASSERT_TRUE(
            ExecuteScriptInFrameAndExtractString(
            (*it)->web_contents(),
            "",
            js_call,
            &message));
        EXPECT_EQ("DONE_RAF", message);
      }
      // TODO(ccameron): send an IPC from Browser -> Renderer (delay it until
      // painting finishes) -> GPU process (delay it until any pending manages
      // happen) -> All Renderers -> Browser to flush parts 2, 3, and 4.
    }
  }

  size_t GetMemoryUsageMbytes() {
    GpuMemoryBytesAllocatedObserver observer;
    observer.GetBytesAllocated();
    return observer.GetBytesAllocated() / 1048576;
  }

  bool allow_tests_to_run_;
  std::set<Shell*> tabs_;
  std::set<Shell*> visible_tabs_;
  bool has_used_first_shell_;
  base::FilePath gpu_test_dir_;
};

// When trying to load something that doesn't fit into our total GPU memory
// limit, we shouldn't exceed that limit.
IN_PROC_BROWSER_TEST_F(GpuMemoryTest, SingleWindowDoesNotExceedLimit) {
  if (!AllowTestsToRun())
    return;

  Shell* tab = CreateNewTab();
  LoadPage(tab, PAGE_CSS3D, kMemoryLimitMB);
  // Make sure that the CSS3D page maxes out a single tab's budget (otherwise
  // the test doesn't test anything) but still stays under the limit.
  EXPECT_TRUE(MemoryUsageInRange(
     kSingleTabLimitMB - kWiggleRoomMB,
     kMemoryLimitMB + kWiggleRoomMB));
}

}  // namespace content