blob: 180f349e87965d69245a67022121500146a2f75e [file] [log] [blame]
// Copyright 2019 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 "device/vr/windows_mixed_reality/mixed_reality_device.h"
#include <math.h>
#include <utility>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/numerics/math_constants.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "device/vr/windows_mixed_reality/mixed_reality_renderloop.h"
#include "ui/gfx/geometry/angle_conversions.h"
namespace device {
namespace {
// Windows Mixed Reality doesn't give out display info until you start a
// presentation session and "Holographic Cameras" are added to the scene.
// However our mojo interface expects display info right away to support WebVR.
// We create a fake display info to use, then notify the client that the display
// info changed when we get real data.
mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) {
mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
display_info->id = id;
display_info->displayName = "Windows Mixed Reality";
display_info->capabilities = mojom::VRDisplayCapabilities::New();
display_info->capabilities->hasPosition = true;
display_info->capabilities->hasExternalDisplay = true;
display_info->capabilities->canPresent = true;
display_info->webvr_default_framebuffer_scale = 1.0;
display_info->webxr_default_framebuffer_scale = 1.0;
display_info->leftEye = mojom::VREyeParameters::New();
display_info->rightEye = mojom::VREyeParameters::New();
mojom::VREyeParametersPtr& left_eye = display_info->leftEye;
mojom::VREyeParametersPtr& right_eye = display_info->rightEye;
left_eye->fieldOfView = mojom::VRFieldOfView::New(45, 45, 45, 45);
right_eye->fieldOfView = mojom::VRFieldOfView::New(45, 45, 45, 45);
constexpr float interpupillary_distance = 0.1f; // 10cm
left_eye->offset = {-interpupillary_distance * 0.5, 0, 0};
right_eye->offset = {interpupillary_distance * 0.5, 0, 0};
constexpr uint32_t width = 1024;
constexpr uint32_t height = 1024;
left_eye->renderWidth = width;
left_eye->renderHeight = height;
right_eye->renderWidth = left_eye->renderWidth;
right_eye->renderHeight = left_eye->renderHeight;
return display_info;
}
} // namespace
MixedRealityDevice::MixedRealityDevice()
: VRDeviceBase(device::mojom::XRDeviceId::WINDOWS_MIXED_REALITY_ID),
gamepad_provider_factory_binding_(this),
compositor_host_binding_(this),
exclusive_controller_binding_(this),
weak_ptr_factory_(this) {
SetVRDisplayInfo(CreateFakeVRDisplayInfo(GetId()));
}
MixedRealityDevice::~MixedRealityDevice() {
Shutdown();
}
mojom::IsolatedXRGamepadProviderFactoryPtr
MixedRealityDevice::BindGamepadFactory() {
mojom::IsolatedXRGamepadProviderFactoryPtr ret;
gamepad_provider_factory_binding_.Bind(mojo::MakeRequest(&ret));
return ret;
}
mojom::XRCompositorHostPtr MixedRealityDevice::BindCompositorHost() {
mojom::XRCompositorHostPtr ret;
compositor_host_binding_.Bind(mojo::MakeRequest(&ret));
return ret;
}
void MixedRealityDevice::RequestSession(
mojom::XRRuntimeSessionOptionsPtr options,
mojom::XRRuntime::RequestSessionCallback callback) {
DCHECK(options->immersive);
if (!render_loop_)
CreateRenderLoop();
if (!render_loop_->IsRunning()) {
// We need to start a UI message loop or we will not receive input events
// on 1809 or newer.
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_UI;
render_loop_->StartWithOptions(options);
// IsRunning() should be true here unless the thread failed to start (likely
// memory exhaustion). If the thread fails to start, then we fail to create
// a session.
if (!render_loop_->IsRunning()) {
std::move(callback).Run(nullptr, nullptr);
return;
}
if (provider_request_) {
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestGamepadProvider,
base::Unretained(render_loop_.get()),
std::move(provider_request_)));
}
if (overlay_request_) {
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
base::Unretained(render_loop_.get()),
std::move(overlay_request_)));
}
}
auto my_callback =
base::BindOnce(&MixedRealityDevice::OnRequestSessionResult,
weak_ptr_factory_.GetWeakPtr(), std::move(callback));
auto on_presentation_ended = base::BindOnce(
&MixedRealityDevice::OnPresentationEnded, weak_ptr_factory_.GetWeakPtr());
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestSession,
base::Unretained(render_loop_.get()),
std::move(on_presentation_ended),
std::move(options), std::move(my_callback)));
}
void MixedRealityDevice::Shutdown() {
// Wait for the render loop to stop before completing destruction.
if (render_loop_ && render_loop_->IsRunning())
render_loop_->Stop();
}
void MixedRealityDevice::CreateRenderLoop() {
auto on_info_changed = base::BindRepeating(
&MixedRealityDevice::SetVRDisplayInfo, weak_ptr_factory_.GetWeakPtr());
render_loop_ =
std::make_unique<MixedRealityRenderLoop>(std::move(on_info_changed));
}
void MixedRealityDevice::OnPresentationEnded() {}
void MixedRealityDevice::OnRequestSessionResult(
mojom::XRRuntime::RequestSessionCallback callback,
bool result,
mojom::XRSessionPtr session) {
if (!result) {
OnPresentationEnded();
std::move(callback).Run(nullptr, nullptr);
return;
}
OnStartPresenting();
mojom::XRSessionControllerPtr session_controller;
exclusive_controller_binding_.Bind(mojo::MakeRequest(&session_controller));
// Use of Unretained is safe because the callback will only occur if the
// binding is not destroyed.
exclusive_controller_binding_.set_connection_error_handler(base::BindOnce(
&MixedRealityDevice::OnPresentingControllerMojoConnectionError,
base::Unretained(this)));
session->display_info = display_info_.Clone();
std::move(callback).Run(std::move(session), std::move(session_controller));
}
void MixedRealityDevice::GetIsolatedXRGamepadProvider(
mojom::IsolatedXRGamepadProviderRequest provider_request) {
if (!render_loop_)
CreateRenderLoop();
if (render_loop_->IsRunning()) {
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestGamepadProvider,
base::Unretained(render_loop_.get()),
std::move(provider_request)));
} else {
provider_request_ = std::move(provider_request);
}
}
void MixedRealityDevice::CreateImmersiveOverlay(
mojom::ImmersiveOverlayRequest overlay_request) {
if (!render_loop_)
CreateRenderLoop();
if (render_loop_->IsRunning()) {
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
base::Unretained(render_loop_.get()),
std::move(overlay_request)));
} else {
overlay_request_ = std::move(overlay_request);
}
}
// XRSessionController
void MixedRealityDevice::SetFrameDataRestricted(bool restricted) {
// Presentation sessions can not currently be restricted.
DCHECK(false);
}
void MixedRealityDevice::OnPresentingControllerMojoConnectionError() {
if (!render_loop_)
CreateRenderLoop();
render_loop_->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&XRCompositorCommon::ExitPresent,
base::Unretained(render_loop_.get())));
// Don't stop the render loop here. We need to keep the gamepad provider alive
// so that we don't lose a pending mojo gamepad_callback_.
// TODO(https://crbug.com/875187): Alternatively, we could recreate the
// provider on the next session, or look into why the callback gets lost.
OnExitPresent();
exclusive_controller_binding_.Close();
}
} // namespace device