blob: e1a63ea6b07b76580f888ba6eed0dbe517d8dd65 [file] [log] [blame]
// 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/browser/shell.h"
#include "content/test/content_browser_test.h"
#include "content/test/content_browser_test_utils.h"
#include "gpu/command_buffer/service/gpu_switches.h"
#include "gpu/config/gpu_test_config.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(), std::string(), 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(), std::string(), 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_;
};
#if defined(OS_LINUX) && !defined(NDEBUG)
// http://crbug.com/254724
#define MAYBE(x) DISABLED_ ## x
#elif defined(OS_WIN) && defined(USE_AURA)
// http://crbug.com/292882
#define MAYBE(x) DISABLED_ ## x
#else
#define MAYBE(x) x
#endif
// 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,
MAYBE(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