Commit 5e0d181b authored by scottmg@chromium.org's avatar scottmg@chromium.org

Add gamepad hardware data fetcher, and higher level thread container

DataFetcher is the hardware and platform-specific data getter that talks to OS
level interfaces for each platform and fill in a structure provided to it.
Currently only Windows is here.

Provider is a manager for the background thread that the DataFetcher runs on,
and interacts with the rest of browser.

This is part of a larger patch, the remainder of which is at
http://codereview.chromium.org/8345027/

BUG=79050


Review URL: http://codereview.chromium.org/8568029

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@111189 0039d316-1c4b-4281-b951-d872f2087c98
parent a7497876
// Copyright (c) 2011 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.
#ifndef CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_
#define CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_
namespace WebKit {
class WebGamepads;
}
namespace gamepad {
class DataFetcher {
public:
virtual ~DataFetcher() {}
virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) = 0;
};
} // namespace gamepad
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_H_
// Copyright (c) 2011 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 "content/browser/gamepad/data_fetcher_win.h"
#include "base/debug/trace_event.h"
#include "content/common/gamepad_messages.h"
#include "content/common/gamepad_hardware_buffer.h"
#pragma comment(lib, "xinput.lib")
namespace gamepad {
using namespace WebKit;
namespace {
// See http://goo.gl/5VSJR. These are not available in all versions of the
// header, but they can be returned from the driver, so we define our own
// versions here.
static const BYTE kDeviceSubTypeGamepad = 1;
static const BYTE kDeviceSubTypeWheel = 2;
static const BYTE kDeviceSubTypeArcadeStick = 3;
static const BYTE kDeviceSubTypeFlightStick = 4;
static const BYTE kDeviceSubTypeDancePad = 5;
static const BYTE kDeviceSubTypeGuitar = 6;
static const BYTE kDeviceSubTypeGuitarAlternate = 7;
static const BYTE kDeviceSubTypeDrumKit = 8;
static const BYTE kDeviceSubTypeGuitarBass = 11;
static const BYTE kDeviceSubTypeArcadePad = 19;
const WebUChar* const GamepadSubTypeName(BYTE sub_type) {
switch (sub_type) {
case kDeviceSubTypeGamepad: return L"GAMEPAD";
case kDeviceSubTypeWheel: return L"WHEEL";
case kDeviceSubTypeArcadeStick: return L"ARCADE_STICK";
case kDeviceSubTypeFlightStick: return L"FLIGHT_STICK";
case kDeviceSubTypeDancePad: return L"DANCE_PAD";
case kDeviceSubTypeGuitar: return L"GUITAR";
case kDeviceSubTypeGuitarAlternate: return L"GUITAR_ALTERNATE";
case kDeviceSubTypeDrumKit: return L"DRUM_KIT";
case kDeviceSubTypeGuitarBass: return L"GUITAR_BASS";
case kDeviceSubTypeArcadePad: return L"ARCADE_PAD";
default: return L"<UNKNOWN>";
}
}
}
void DataFetcherWindows::GetGamepadData(WebGamepads* pads,
bool devices_changed_hint) {
TRACE_EVENT0("GAMEPAD", "DataFetcherWindows::GetGamepadData");
pads->length = WebGamepads::itemsLengthCap;
// If we got notification that system devices have been updated, then
// run GetCapabilities to update the connected status and the device
// identifier. It can be slow to do to both GetCapabilities and
// GetState on unconnected devices, so we want to avoid a 2-5ms pause
// here by only doing this when the devices are updated (despite
// documentation claiming it's OK to call it any time).
if (devices_changed_hint) {
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
WebGamepad& pad = pads->items[i];
TRACE_EVENT1("GAMEPAD", "DataFetcherWindows::GetCapabilities", "id", i);
XINPUT_CAPABILITIES caps;
DWORD res = XInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &caps);
if (res == ERROR_DEVICE_NOT_CONNECTED) {
pad.connected = false;
} else {
pad.connected = true;
base::swprintf(pad.id,
WebGamepad::idLengthCap,
L"Xbox 360 Controller (XInput %ls)",
GamepadSubTypeName(caps.SubType));
}
}
}
// We've updated the connection state if necessary, now update the actual
// data for the devices that are connected.
for (unsigned i = 0; i < WebGamepads::itemsLengthCap; ++i) {
WebGamepad& pad = pads->items[i];
// We rely on device_changed and GetCapabilities to tell us that
// something's been connected, but we will mark as disconnected if
// GetState returns that we've lost the pad.
if (!pad.connected)
continue;
XINPUT_STATE state;
memset(&state, 0, sizeof(XINPUT_STATE));
TRACE_EVENT_BEGIN1("GAMEPAD", "XInputGetState", "id", i);
DWORD dwResult = XInputGetState(i, &state);
TRACE_EVENT_END1("GAMEPAD", "XInputGetState", "id", i);
if (dwResult == ERROR_SUCCESS) {
pad.timestamp = state.dwPacketNumber;
pad.buttonsLength = 0;
#define ADD(b) pad.buttons[pad.buttonsLength++] = \
((state.Gamepad.wButtons & (b)) ? 1.0 : 0.0);
ADD(XINPUT_GAMEPAD_A);
ADD(XINPUT_GAMEPAD_B);
ADD(XINPUT_GAMEPAD_X);
ADD(XINPUT_GAMEPAD_Y);
ADD(XINPUT_GAMEPAD_LEFT_SHOULDER);
ADD(XINPUT_GAMEPAD_RIGHT_SHOULDER);
pad.buttons[pad.buttonsLength++] = state.Gamepad.bLeftTrigger / 255.0;
pad.buttons[pad.buttonsLength++] = state.Gamepad.bRightTrigger / 255.0;
ADD(XINPUT_GAMEPAD_BACK);
ADD(XINPUT_GAMEPAD_START);
ADD(XINPUT_GAMEPAD_LEFT_THUMB);
ADD(XINPUT_GAMEPAD_RIGHT_THUMB);
ADD(XINPUT_GAMEPAD_DPAD_UP);
ADD(XINPUT_GAMEPAD_DPAD_DOWN);
ADD(XINPUT_GAMEPAD_DPAD_LEFT);
ADD(XINPUT_GAMEPAD_DPAD_RIGHT);
#undef ADD
pad.axesLength = 0;
// XInput are +up/+right, -down/-left, we want -up/-left.
pad.axes[pad.axesLength++] = state.Gamepad.sThumbLX / 32767.0;
pad.axes[pad.axesLength++] = -state.Gamepad.sThumbLY / 32767.0;
pad.axes[pad.axesLength++] = state.Gamepad.sThumbRX / 32767.0;
pad.axes[pad.axesLength++] = -state.Gamepad.sThumbRY / 32767.0;
} else {
pad.connected = false;
}
}
}
} // namespace gamepad
// Copyright (c) 2011 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.
#ifndef CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_
#define CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_
#include "build/build_config.h"
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <XInput.h>
#include "base/compiler_specific.h"
#include "content/browser/gamepad/data_fetcher.h"
namespace gamepad {
class DataFetcherWindows : public DataFetcher {
public:
virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) OVERRIDE;
};
} // namespace gamepad
#endif // CONTENT_BROWSER_GAMEPAD_DATA_FETCHER_WIN_H_
// Copyright (c) 2011 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 <cmath>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "base/threading/thread.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/browser/browser_thread.h"
#include "content/browser/gamepad/gamepad_provider.h"
#include "content/browser/gamepad/data_fetcher.h"
#include "content/common/gamepad_messages.h"
#if defined(OS_WIN)
#include "content/browser/gamepad/data_fetcher_win.h"
#endif
namespace gamepad {
Provider* Provider::instance_ = NULL;
using namespace content;
// Define the default data fetcher that Provider will use if none is supplied.
// (PlatformDataFetcher).
#if defined(OS_WIN)
typedef DataFetcherWindows PlatformDataFetcher;
#else
class EmptyDataFetcher : public DataFetcher {
public:
void GetGamepadData(WebKit::WebGamepads* pads, bool) {
pads->length = 0;
}
};
typedef EmptyDataFetcher PlatformDataFetcher;
#endif
Provider::Provider(DataFetcher* fetcher)
: creator_loop_(MessageLoop::current()->message_loop_proxy()),
provided_fetcher_(fetcher),
devices_changed_(true),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
size_t data_size = sizeof(GamepadHardwareBuffer);
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
monitor->AddDevicesChangedObserver(this);
gamepad_shared_memory_.CreateAndMapAnonymous(data_size);
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
memset(hwbuf, 0, sizeof(GamepadHardwareBuffer));
}
Provider::~Provider() {
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
monitor->RemoveDevicesChangedObserver(this);
Stop();
}
base::SharedMemoryHandle Provider::GetRendererSharedMemoryHandle(
base::ProcessHandle process) {
base::SharedMemoryHandle renderer_handle;
gamepad_shared_memory_.ShareToProcess(process, &renderer_handle);
return renderer_handle;
}
void Provider::OnDevicesChanged() {
devices_changed_ = true;
}
void Provider::Start() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
if (polling_thread_.get())
return;
polling_thread_.reset(new base::Thread("Gamepad polling thread"));
if (!polling_thread_->Start()) {
LOG(ERROR) << "Failed to start gamepad polling thread";
polling_thread_.reset();
return;
}
MessageLoop* polling_loop = polling_thread_->message_loop();
polling_loop->PostTask(
FROM_HERE,
base::Bind(&Provider::DoInitializePollingThread, this));
}
void Provider::Stop() {
DCHECK(MessageLoop::current()->message_loop_proxy() == creator_loop_);
polling_thread_.reset();
data_fetcher_.reset();
}
void Provider::DoInitializePollingThread() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
if (!provided_fetcher_.get())
provided_fetcher_.reset(new PlatformDataFetcher);
// Pass ownership of fetcher to provider_.
data_fetcher_.swap(provided_fetcher_);
// Start polling.
ScheduleDoPoll();
}
void Provider::DoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
GamepadHardwareBuffer* hwbuf = SharedMemoryAsHardwareBuffer();
base::subtle::Barrier_AtomicIncrement(&hwbuf->start_marker, 1);
data_fetcher_->GetGamepadData(&hwbuf->buffer, devices_changed_);
base::subtle::Barrier_AtomicIncrement(&hwbuf->end_marker, 1);
devices_changed_ = false;
// Schedule our next interval of polling.
ScheduleDoPoll();
}
void Provider::ScheduleDoPoll() {
DCHECK(MessageLoop::current() == polling_thread_->message_loop());
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&Provider::DoPoll, weak_factory_.GetWeakPtr()),
kDesiredSamplingIntervalMs);
}
GamepadHardwareBuffer* Provider::SharedMemoryAsHardwareBuffer() {
void* mem = gamepad_shared_memory_.memory();
DCHECK(mem);
return static_cast<GamepadHardwareBuffer*>(mem);
}
} // namespace gamepad
// Copyright (c) 2011 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.
#ifndef CONTENT_BROWSER_GAMEPAD_PROVIDER_H_
#define CONTENT_BROWSER_GAMEPAD_PROVIDER_H_
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/shared_memory.h"
#include "base/system_monitor/system_monitor.h"
#include "base/task.h"
#include "content/browser/gamepad/data_fetcher.h"
#include "content/common/gamepad_hardware_buffer.h"
namespace base {
class Thread;
}
struct GamepadMsg_Updated_Params;
namespace gamepad {
class Provider : public base::RefCountedThreadSafe<Provider>,
public base::SystemMonitor::DevicesChangedObserver {
public:
explicit Provider(DataFetcher* fetcher);
// Starts or Stops the provider. Called from creator_loop_.
void Start();
void Stop();
base::SharedMemoryHandle GetRendererSharedMemoryHandle(
base::ProcessHandle renderer_process);
private:
friend class base::RefCountedThreadSafe<Provider>;
virtual ~Provider();
// Method for starting the polling, runs on polling_thread_.
void DoInitializePollingThread();
// Method for polling a DataFetcher. Runs on the polling_thread_.
void DoPoll();
void ScheduleDoPoll();
virtual void OnDevicesChanged() OVERRIDE;
GamepadHardwareBuffer* SharedMemoryAsHardwareBuffer();
enum { kDesiredSamplingIntervalMs = 16 };
// The Message Loop on which this object was created.
// Typically the I/O loop, but may be something else during testing.
scoped_refptr<base::MessageLoopProxy> creator_loop_;
scoped_ptr<DataFetcher> provided_fetcher_;
// When polling_thread_ is running, members below are only to be used
// from that thread.
scoped_ptr<DataFetcher> data_fetcher_;
base::SharedMemory gamepad_shared_memory_;
bool devices_changed_;
// Polling is done on this background thread.
scoped_ptr<base::Thread> polling_thread_;
static Provider* instance_;
base::WeakPtrFactory<Provider> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Provider);
};
} // namespace gamepad
#endif // CONTENT_BROWSER_GAMEPAD_PROVIDER_H_
// Copyright (c) 2011 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/memory/scoped_ptr.h"
#include "base/process_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/system_monitor/system_monitor.h"
#include "content/browser/gamepad/gamepad_provider.h"
#include "content/common/gamepad_messages.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class MockDataFetcher : public gamepad::DataFetcher {
public:
MockDataFetcher() : read_data_(false, false) {
memset(&test_data, 0, sizeof(test_data));
}
virtual void GetGamepadData(WebKit::WebGamepads* pads,
bool devices_changed_hint) OVERRIDE {
*pads = test_data;
read_data_.Signal();
}
void SetData(WebKit::WebGamepads& data) {
test_data = data;
}
void WaitForDataRead() { return read_data_.Wait(); }
WebKit::WebGamepads test_data;
base::WaitableEvent read_data_;
};
// Main test fixture
class GamepadProviderTest : public testing::Test {
public:
gamepad::Provider* CreateProvider() {
#if defined(OS_MACOSX)
base::SystemMonitor::AllocateSystemIOPorts();
#endif
system_monitor_.reset(new base::SystemMonitor);
mock_data_fetcher_ = new MockDataFetcher;
provider_ = new gamepad::Provider(mock_data_fetcher_);
return provider_.get();
}
protected:
GamepadProviderTest() {
}
MessageLoop main_message_loop_;
scoped_ptr<base::SystemMonitor> system_monitor_;
MockDataFetcher* mock_data_fetcher_;
scoped_refptr<gamepad::Provider> provider_;
};
TEST_F(GamepadProviderTest, BasicStartStop) {
gamepad::Provider* provider = CreateProvider();
provider->Start();
provider->Stop();
// Just ensure that there's no asserts on startup, shutdown, or destroy.
}
TEST_F(GamepadProviderTest, PollingAccess) {
using namespace gamepad;
Provider* provider = CreateProvider();
provider->Start();
WebKit::WebGamepads test_data;
test_data.length = 1;
test_data.items[0].connected = true;
test_data.items[0].timestamp = 0;
test_data.items[0].buttonsLength = 1;
test_data.items[0].axesLength = 2;
test_data.items[0].buttons[0] = 1.f;
test_data.items[0].axes[0] = -1.f;
test_data.items[0].axes[1] = .5f;
mock_data_fetcher_->SetData(test_data);
main_message_loop_.RunAllPending();
mock_data_fetcher_->WaitForDataRead();
// Renderer-side, pull data out of poll buffer.
base::SharedMemoryHandle handle =
provider->GetRendererSharedMemoryHandle(base::GetCurrentProcessHandle());
base::SharedMemory* shared_memory = new base::SharedMemory(handle, true);
EXPECT_TRUE(shared_memory->Map(sizeof(GamepadHardwareBuffer)));
void* mem = shared_memory->memory();
GamepadHardwareBuffer* hwbuf = static_cast<GamepadHardwareBuffer*>(mem);
// See gamepad_hardware_buffer.h for details on the read discipline.
base::subtle::Atomic32 start, end;
WebKit::WebGamepads output;
int contention_count;
// Here we're attempting to test the read discipline during contention. If
// we fail to read this many times, then the read thread is starving, and we
// should fail the test.
for (contention_count = 0; contention_count < 10; ++contention_count) {
end = base::subtle::Acquire_Load(&hwbuf->end_marker);
memcpy(&output, &hwbuf->buffer, sizeof(output));
start = base::subtle::Acquire_Load(&hwbuf->start_marker);
if (start == end)
break;
base::PlatformThread::YieldCurrentThread();
}
EXPECT_GT(10, contention_count);
EXPECT_EQ(1u, output.length);
EXPECT_EQ(1u, output.items[0].buttonsLength);
EXPECT_EQ(1.f, output.items[0].buttons[0]);
EXPECT_EQ(2u, output.items[0].axesLength);
EXPECT_EQ(-1.f, output.items[0].axes[0]);
EXPECT_EQ(0.5f, output.items[0].axes[1]);
provider->Stop();
}
} // namespace
......@@ -193,6 +193,11 @@
'browser/find_pasteboard.mm',
'browser/font_list_async.cc',
'browser/font_list_async.h',
'browser/gamepad/data_fetcher.h',
'browser/gamepad/data_fetcher_win.cc',
'browser/gamepad/data_fetcher_win.h',
'browser/gamepad/gamepad_provider.cc',
'browser/gamepad/gamepad_provider.h',
'browser/geolocation/access_token_store.cc',
'browser/geolocation/access_token_store.h',
'browser/geolocation/arbitrator_dependency_factory.cc',
......
......@@ -176,6 +176,7 @@
'browser/download/download_id_unittest.cc',
'browser/download/download_status_updater_unittest.cc',
'browser/download/save_package_unittest.cc',
'browser/gamepad/gamepad_provider_unittest.cc',
'browser/geolocation/device_data_provider_unittest.cc',
'browser/geolocation/geolocation_provider_unittest.cc',
'browser/geolocation/gps_location_provider_unittest_linux.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment