blob: 35eca57b4f97de90cf6901d0118a3f31f043180c [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
#include "content/public/test/xr_test_utils.h"
#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#if BUILDFLAG(IS_ANDROID)
#include "components/webxr/android/openxr_device_provider.h"
#endif
// TODO(crbug.com/41418750): Remove these conversion functions as part of
// the switch to only mojom types.
device_test::mojom::ControllerRole DeviceToMojoControllerRole(
device::ControllerRole role) {
switch (role) {
case device::kControllerRoleInvalid:
return device_test::mojom::ControllerRole::kControllerRoleInvalid;
case device::kControllerRoleRight:
return device_test::mojom::ControllerRole::kControllerRoleRight;
case device::kControllerRoleLeft:
return device_test::mojom::ControllerRole::kControllerRoleLeft;
case device::kControllerRoleVoice:
return device_test::mojom::ControllerRole::kControllerRoleVoice;
}
}
device_test::mojom::ControllerFrameDataPtr DeviceToMojoControllerFrameData(
const device::ControllerFrameData& data) {
device_test::mojom::ControllerFrameDataPtr ret =
device_test::mojom::ControllerFrameData::New();
ret->packet_number = data.packet_number;
ret->buttons_pressed = data.buttons_pressed;
ret->buttons_touched = data.buttons_touched;
ret->supported_buttons = data.supported_buttons;
for (unsigned int i = 0; i < device::kMaxNumAxes; ++i) {
ret->axis_data.emplace_back(device_test::mojom::ControllerAxisData::New());
ret->axis_data[i]->x = data.axis_data[i].x;
ret->axis_data[i]->y = data.axis_data[i].y;
ret->axis_data[i]->axis_type = data.axis_data[i].axis_type;
}
ret->role = DeviceToMojoControllerRole(data.role);
ret->is_valid = data.is_valid;
ret->pose_data = device_test::mojom::PoseFrameData::New();
ret->pose_data->device_to_origin = gfx::Transform();
for (int col = 0; col < 4; ++col) {
for (int row = 0; row < 4; ++row) {
ret->pose_data->device_to_origin->set_rc(
row, col, data.pose_data.device_to_origin[row + col * 4]);
}
}
if (data.has_hand_data) {
device::mojom::XRHandTrackingDataPtr hand_tracking_data =
device::mojom::XRHandTrackingData::New();
hand_tracking_data->hand_joint_data =
std::vector<device::mojom::XRHandJointDataPtr>{};
auto& joint_data = hand_tracking_data->hand_joint_data;
// We need to use `resize` here to create default data fields so we can use
// [] indexing to ensure things are added to the right spot.
joint_data.resize(std::size(data.hand_data));
for (const auto& joint_entry : data.hand_data) {
uint32_t joint_index = static_cast<uint32_t>(joint_entry.joint);
joint_data[joint_index] = device::mojom::XRHandJointData::New(
joint_entry.joint, joint_entry.mojo_from_joint, joint_entry.radius);
}
ret->hand_data = std::move(hand_tracking_data);
}
return ret;
}
MockXRDeviceHookBase::MockXRDeviceHookBase()
: tracked_classes_{
device_test::mojom::TrackedDeviceClass::kTrackedDeviceHmd} {
thread_ = std::make_unique<base::Thread>("MockXRDeviceHookThread");
thread_->Start();
// By default, `mock_device_sequence_` is bound to the constructing thread
// (i.e. the main test thread). We must detach it so it can be bound to
// our internal `thread_` the first time a checked method is called.
DETACH_FROM_SEQUENCE(mock_device_sequence_);
// TODO(https://crbug.com/381913614): Instead of this pattern, consider
// spinning up/holding onto and setting the test hook on the XrRuntimeManager,
// which could pass on to providers.
#if BUILDFLAG(IS_WIN)
content::GetXRDeviceServiceForTesting()->BindTestHook(
service_test_hook_.BindNewPipeAndPassReceiver());
mojo::ScopedAllowSyncCallForTesting scoped_allow_sync;
service_test_hook_->SetTestHook(
receiver_.BindNewPipeAndPassRemote(thread_->task_runner()));
#elif BUILDFLAG(IS_ANDROID)
mojo::ScopedAllowSyncCallForTesting scoped_allow_sync;
webxr::OpenXrDeviceProvider::SetTestHook(
receiver_.BindNewPipeAndPassRemote(thread_->task_runner()));
#endif
}
MockXRDeviceHookBase::~MockXRDeviceHookBase() {
StopHooking();
if (thread_->IsRunning()) {
thread_->Stop();
}
}
void MockXRDeviceHookBase::StopHooking() {
// Ensure that this is being called from our main thread, and not the mock
// device thread.
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
// We don't call service_test_hook_->SetTestHook(mojo::NullRemote()), since
// that will potentially deadlock with reentrant or crossing synchronous mojo
// calls.
service_test_hook_.reset();
// Unretained is safe here because we are going to block until this message
// has been processed.
thread_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&mojo::Receiver<device_test::mojom::XRTestHook>::reset,
base::Unretained(&receiver_)));
// Mojo messages and this destruction task are the only thing that should be
// posted to the thread. Since we're destroying the mojo pipe, we can safely
// block here.
thread_->FlushForTesting();
}
void MockXRDeviceHookBase::WaitNumFrames(uint32_t num_frames) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
WaitForTotalFrameCount(frame_count_ + num_frames);
}
void MockXRDeviceHookBase::WaitForTotalFrameCount(uint32_t total_count) {
DCHECK(!can_signal_wait_loop_);
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
target_frame_count_ = total_count;
// No need to wait if we've already had at least the requested number of
// frames submitted.
if (frame_count_ >= target_frame_count_) {
return;
}
wait_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
can_signal_wait_loop_ = true;
wait_loop_->Run();
can_signal_wait_loop_ = false;
wait_loop_.reset();
}
void MockXRDeviceHookBase::OnFrameSubmitted(
std::vector<device_test::mojom::ViewDataPtr> views,
device_test::mojom::XRTestHook::OnFrameSubmittedCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
frame_count_++;
ProcessSubmittedFrameUnlocked(std::move(views));
if (can_signal_wait_loop_ && frame_count_ >= target_frame_count_) {
wait_loop_->Quit();
can_signal_wait_loop_ = false;
}
std::move(callback).Run();
}
void MockXRDeviceHookBase::WaitGetDeviceConfig(
device_test::mojom::XRTestHook::WaitGetDeviceConfigCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
device_test::mojom::DeviceConfigPtr ret =
device_test::mojom::DeviceConfig::New();
ret->interpupillary_distance = 0.1f;
ret->projection_left = device_test::mojom::ProjectionRaw::New(1, 1, 1, 1);
ret->projection_right = device_test::mojom::ProjectionRaw::New(1, 1, 1, 1);
std::move(callback).Run(std::move(ret));
}
void MockXRDeviceHookBase::WaitGetPresentingPose(
device_test::mojom::XRTestHook::WaitGetPresentingPoseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
auto pose = device_test::mojom::PoseFrameData::New();
pose->device_to_origin = gfx::Transform();
std::move(callback).Run(std::move(pose));
}
void MockXRDeviceHookBase::WaitGetMagicWindowPose(
device_test::mojom::XRTestHook::WaitGetMagicWindowPoseCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
auto pose = device_test::mojom::PoseFrameData::New();
pose->device_to_origin = gfx::Transform();
std::move(callback).Run(std::move(pose));
}
void MockXRDeviceHookBase::WaitGetControllerRoleForTrackedDeviceIndex(
unsigned int index,
device_test::mojom::XRTestHook::
WaitGetControllerRoleForTrackedDeviceIndexCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
device_test::mojom::ControllerRole role =
device_test::mojom::ControllerRole::kControllerRoleInvalid;
{
base::AutoLock lock(lock_);
auto iter = controller_data_map_.find(index);
if (iter != controller_data_map_.end()) {
role = DeviceToMojoControllerRole(iter->second.role);
}
}
std::move(callback).Run(role);
}
void MockXRDeviceHookBase::WaitGetTrackedDeviceClass(
unsigned int index,
device_test::mojom::XRTestHook::WaitGetTrackedDeviceClassCallback
callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
DCHECK(index < device::kMaxTrackedDevices);
device_test::mojom::TrackedDeviceClass tracked_class =
device_test::mojom::TrackedDeviceClass::kTrackedDeviceInvalid;
{
base::AutoLock lock(lock_);
tracked_class = tracked_classes_[index];
}
std::move(callback).Run(tracked_class);
}
void MockXRDeviceHookBase::WaitGetControllerData(
unsigned int index,
device_test::mojom::XRTestHook::WaitGetControllerDataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
device::ControllerFrameData data;
{
base::AutoLock lock(lock_);
if (tracked_classes_[index] ==
device_test::mojom::TrackedDeviceClass::kTrackedDeviceController) {
auto iter = controller_data_map_.find(index);
CHECK(iter != controller_data_map_.end());
data = iter->second;
} else {
// Default to not being valid so that controllers aren't connected unless
// a test specifically enables it.
data =
CreateValidController(device::ControllerRole::kControllerRoleInvalid);
data.is_valid = false;
}
}
std::move(callback).Run(DeviceToMojoControllerFrameData(data));
}
void MockXRDeviceHookBase::WaitGetEventData(
device_test::mojom::XRTestHook::WaitGetEventDataCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
device_test::mojom::EventDataPtr ret = device_test::mojom::EventData::New();
ret->type = device_test::mojom::EventType::kNoEvent;
{
base::AutoLock lock(lock_);
if (!event_data_queue_.empty()) {
ret = device_test::mojom::EventData::New(event_data_queue_.front());
event_data_queue_.pop();
}
}
std::move(callback).Run(std::move(ret));
}
unsigned int MockXRDeviceHookBase::ConnectController(
const device::ControllerFrameData& initial_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
base::AutoLock lock(lock_);
// Find the first open tracked device slot and fill that.
for (unsigned int i = 0; i < device::kMaxTrackedDevices; ++i) {
if (tracked_classes_[i] ==
device_test::mojom::TrackedDeviceClass::kTrackedDeviceInvalid) {
tracked_classes_[i] =
device_test::mojom::TrackedDeviceClass::kTrackedDeviceController;
controller_data_map_.insert(std::make_pair(i, initial_data));
return i;
}
}
// We shouldn't be running out of slots during a test.
NOTREACHED();
}
void MockXRDeviceHookBase::TerminateDeviceServiceProcessForTesting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
mojo::ScopedAllowSyncCallForTesting scoped_allow_sync;
service_test_hook_->TerminateDeviceServiceProcessForTesting();
}
void MockXRDeviceHookBase::UpdateController(
unsigned int index,
const device::ControllerFrameData& updated_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
base::AutoLock lock(lock_);
auto iter = controller_data_map_.find(index);
CHECK(iter != controller_data_map_.end());
iter->second = updated_data;
}
void MockXRDeviceHookBase::DisconnectController(unsigned int index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
base::AutoLock lock(lock_);
DCHECK(tracked_classes_[index] ==
device_test::mojom::TrackedDeviceClass::kTrackedDeviceController);
auto iter = controller_data_map_.find(index);
CHECK(iter != controller_data_map_.end());
controller_data_map_.erase(iter);
tracked_classes_[index] =
device_test::mojom::TrackedDeviceClass::kTrackedDeviceInvalid;
}
device::ControllerFrameData MockXRDeviceHookBase::CreateValidController(
device::ControllerRole role) {
// Stateless helper may be called on any sequence.
device::ControllerFrameData ret;
// Because why shouldn't a 64 button controller exist?
ret.supported_buttons = UINT64_MAX;
std::ranges::fill(ret.axis_data, device::ControllerAxisData{});
ret.role = role;
ret.is_valid = true;
// Identity matrix.
ret.pose_data.device_to_origin[0] = 1;
ret.pose_data.device_to_origin[5] = 1;
ret.pose_data.device_to_origin[10] = 1;
ret.pose_data.device_to_origin[15] = 1;
return ret;
}
void MockXRDeviceHookBase::PopulateEvent(device_test::mojom::EventData data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
base::AutoLock lock(lock_);
event_data_queue_.push(data);
}
void MockXRDeviceHookBase::WaitGetCanCreateSession(
device_test::mojom::XRTestHook::WaitGetCanCreateSessionCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
std::move(callback).Run(can_create_session_);
}
void MockXRDeviceHookBase::SetCanCreateSession(bool can_create_session) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
can_create_session_ = can_create_session;
}