blob: bc5db81cae097c18dcb320b245f62747bde081d9 [file] [log] [blame]
// Copyright 2016 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/android/gvr/gvr_device.h"
#include <math.h>
#include <algorithm>
#include <utility>
#include "base/android/android_hardware_buffer_compat.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "device/vr/android/gvr/gvr_delegate.h"
#include "device/vr/android/gvr/gvr_delegate_provider.h"
#include "device/vr/android/gvr/gvr_delegate_provider_factory.h"
#include "device/vr/android/gvr/gvr_device_provider.h"
#include "device/vr/vr_display_impl.h"
#include "jni/NonPresentingGvrContext_jni.h"
#include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/transform.h"
#include "ui/gfx/transform_util.h"
using base::android::JavaRef;
namespace device {
namespace {
// Default downscale factor for computing the recommended WebVR/WebXR
// renderWidth/Height from the 1:1 pixel mapped size. Using a rather
// aggressive downscale due to the high overhead of copying pixels
// twice before handing off to GVR. For comparison, the polyfill
// uses approximately 0.55 on a Pixel XL.
static constexpr float kWebVrRecommendedResolutionScale = 0.5;
static constexpr float kWebXrRecommendedResolutionScale = 0.7;
// The scale factor for WebXR on devices that don't have shared buffer
// support. (Android N and earlier.)
static constexpr float kWebXrNoSharedBufferResolutionScale = 0.5;
gfx::Size GetMaximumWebVrSize(gvr::GvrApi* gvr_api) {
// Get the default, unscaled size for the WebVR transfer surface
// based on the optimal 1:1 render resolution. A scalar will be applied to
// this value in the renderer to reduce the render load. This size will also
// be reported to the client via CreateVRDisplayInfo as the
// client-recommended renderWidth/renderHeight and for the GVR
// framebuffer. If the client chooses a different size or resizes it
// while presenting, we'll resize the transfer surface and GVR
// framebuffer to match.
gvr::Sizei render_target_size =
gvr_api->GetMaximumEffectiveRenderTargetSize();
gfx::Size webvr_size(render_target_size.width, render_target_size.height);
// Ensure that the width is an even number so that the eyes each
// get the same size, the recommended renderWidth is per eye
// and the client will use the sum of the left and right width.
//
// TODO(klausw,crbug.com/699350): should we round the recommended
// size to a multiple of 2^N pixels to be friendlier to the GPU? The
// exact size doesn't matter, and it might be more efficient.
webvr_size.set_width(webvr_size.width() & ~1);
return webvr_size;
}
mojom::VREyeParametersPtr CreateEyeParamater(
gvr::GvrApi* gvr_api,
gvr::Eye eye,
const gvr::BufferViewportList& buffers,
const gfx::Size& maximum_size) {
mojom::VREyeParametersPtr eye_params = mojom::VREyeParameters::New();
eye_params->fieldOfView = mojom::VRFieldOfView::New();
eye_params->offset.resize(3);
eye_params->renderWidth = maximum_size.width() / 2;
eye_params->renderHeight = maximum_size.height();
gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport();
buffers.GetBufferViewport(eye, &eye_viewport);
gvr::Rectf eye_fov = eye_viewport.GetSourceFov();
eye_params->fieldOfView->upDegrees = eye_fov.top;
eye_params->fieldOfView->downDegrees = eye_fov.bottom;
eye_params->fieldOfView->leftDegrees = eye_fov.left;
eye_params->fieldOfView->rightDegrees = eye_fov.right;
gvr::Mat4f eye_mat = gvr_api->GetEyeFromHeadMatrix(eye);
eye_params->offset[0] = -eye_mat.m[0][3];
eye_params->offset[1] = -eye_mat.m[1][3];
eye_params->offset[2] = -eye_mat.m[2][3];
return eye_params;
}
mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api,
uint32_t device_id) {
TRACE_EVENT0("input", "GvrDelegate::CreateVRDisplayInfo");
mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
device->index = device_id;
device->capabilities = mojom::VRDisplayCapabilities::New();
device->capabilities->hasPosition = false;
device->capabilities->hasExternalDisplay = false;
device->capabilities->canPresent = true;
std::string vendor = gvr_api->GetViewerVendor();
std::string model = gvr_api->GetViewerModel();
device->displayName = vendor + " " + model;
gvr::BufferViewportList gvr_buffer_viewports =
gvr_api->CreateEmptyBufferViewportList();
gvr_buffer_viewports.SetToRecommendedBufferViewports();
gfx::Size maximum_size = GetMaximumWebVrSize(gvr_api);
device->leftEye = CreateEyeParamater(gvr_api, GVR_LEFT_EYE,
gvr_buffer_viewports, maximum_size);
device->rightEye = CreateEyeParamater(gvr_api, GVR_RIGHT_EYE,
gvr_buffer_viewports, maximum_size);
// This scalar will be applied in the renderer to the recommended render
// target sizes. For WebVR it will always be applied, for WebXR it can be
// overridden.
if (base::AndroidHardwareBufferCompat::IsSupportAvailable()) {
device->webxr_default_framebuffer_scale = kWebXrRecommendedResolutionScale;
} else {
device->webxr_default_framebuffer_scale =
kWebXrNoSharedBufferResolutionScale;
}
device->webvr_default_framebuffer_scale = kWebVrRecommendedResolutionScale;
return device;
}
} // namespace
std::unique_ptr<GvrDevice> GvrDevice::Create() {
std::unique_ptr<GvrDevice> device = base::WrapUnique(new GvrDevice());
if (!device->gvr_api_)
return nullptr;
return device;
}
GvrDevice::GvrDevice() : weak_ptr_factory_(this) {
GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
if (!delegate_provider || delegate_provider->ShouldDisableGvrDevice())
return;
JNIEnv* env = base::android::AttachCurrentThread();
non_presenting_context_.Reset(
Java_NonPresentingGvrContext_create(env, reinterpret_cast<jlong>(this)));
if (!non_presenting_context_.obj())
return;
jlong context = Java_NonPresentingGvrContext_getNativeGvrContext(
env, non_presenting_context_);
gvr_api_ = gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context));
SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
}
GvrDevice::~GvrDevice() {
if (!non_presenting_context_.obj())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_NonPresentingGvrContext_shutdown(env, non_presenting_context_);
}
void GvrDevice::RequestPresent(
VRDisplayImpl* display,
mojom::VRSubmitFrameClientPtr submit_client,
mojom::VRPresentationProviderRequest request,
mojom::VRRequestPresentOptionsPtr present_options,
mojom::VRDisplayHost::RequestPresentCallback callback) {
GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
if (!delegate_provider) {
std::move(callback).Run(false, nullptr);
return;
}
// RequestWebVRPresent is async as we may trigger a DON (Device ON) flow that
// pauses Chrome.
delegate_provider->RequestWebVRPresent(
std::move(submit_client), std::move(request), GetVRDisplayInfo(),
std::move(present_options),
base::BindOnce(&GvrDevice::OnRequestPresentResult,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
base::Unretained(display)));
}
void GvrDevice::OnRequestPresentResult(
mojom::VRDisplayHost::RequestPresentCallback callback,
VRDisplayImpl* display,
bool result,
mojom::VRDisplayFrameTransportOptionsPtr transport_options) {
if (result)
SetPresentingDisplay(display);
std::move(callback).Run(result, std::move(transport_options));
}
void GvrDevice::ExitPresent() {
GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
if (delegate_provider)
delegate_provider->ExitWebVRPresent();
OnExitPresent();
}
void GvrDevice::OnMagicWindowPoseRequest(
mojom::VRMagicWindowProvider::GetPoseCallback callback) {
std::move(callback).Run(
GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), nullptr));
}
void GvrDevice::OnListeningForActivate(bool listening) {
GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider();
if (!delegate_provider)
return;
delegate_provider->OnListeningForActivateChanged(listening);
}
void GvrDevice::PauseTracking() {
gvr_api_->PauseTracking();
JNIEnv* env = base::android::AttachCurrentThread();
Java_NonPresentingGvrContext_pause(env, non_presenting_context_);
}
void GvrDevice::ResumeTracking() {
gvr_api_->ResumeTracking();
JNIEnv* env = base::android::AttachCurrentThread();
Java_NonPresentingGvrContext_resume(env, non_presenting_context_);
}
GvrDelegateProvider* GvrDevice::GetGvrDelegateProvider() {
// GvrDelegateProviderFactory::Create() may fail transiently, so every time we
// try to get it, set the device ID.
GvrDelegateProvider* delegate_provider = GvrDelegateProviderFactory::Create();
if (delegate_provider)
delegate_provider->SetDeviceId(GetId());
return delegate_provider;
}
void GvrDevice::OnDisplayConfigurationChanged(JNIEnv* env,
const JavaRef<jobject>& obj) {
SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
}
void GvrDevice::Activate(mojom::VRDisplayEventReason reason,
base::Callback<void(bool)> on_handled) {
OnActivate(reason, std::move(on_handled));
}
} // namespace device