blob: bf806e373da73d1a56bfae20cb398a1ae613d219 [file] [log] [blame]
// Copyright 2017 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 "modules/xr/XRFrameProvider.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "build/build_config.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/dom/FrameRequestCallbackCollection.h"
#include "core/frame/LocalFrame.h"
#include "core/imagebitmap/ImageBitmap.h"
#include "modules/xr/XR.h"
#include "modules/xr/XRDevice.h"
#include "modules/xr/XRSession.h"
#include "modules/xr/XRViewport.h"
#include "modules/xr/XRWebGLLayer.h"
#include "platform/graphics/gpu/XRFrameTransport.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/transforms/TransformationMatrix.h"
#include "platform/wtf/Time.h"
#include "public/platform/Platform.h"
namespace blink {
namespace {
class XRFrameProviderRequestCallback
: public FrameRequestCallbackCollection::FrameCallback {
public:
explicit XRFrameProviderRequestCallback(XRFrameProvider* frame_provider)
: frame_provider_(frame_provider) {}
~XRFrameProviderRequestCallback() override = default;
void Invoke(double high_res_time_ms) override {
frame_provider_->OnNonExclusiveVSync(high_res_time_ms / 1000.0);
}
virtual void Trace(blink::Visitor* visitor) {
visitor->Trace(frame_provider_);
FrameRequestCallbackCollection::FrameCallback::Trace(visitor);
}
Member<XRFrameProvider> frame_provider_;
};
std::unique_ptr<TransformationMatrix> getPoseMatrix(
const device::mojom::blink::VRPosePtr& pose) {
if (!pose)
return nullptr;
std::unique_ptr<TransformationMatrix> pose_matrix =
TransformationMatrix::Create();
TransformationMatrix::DecomposedType decomp;
memset(&decomp, 0, sizeof(decomp));
decomp.perspective_w = 1;
decomp.scale_x = 1;
decomp.scale_y = 1;
decomp.scale_z = 1;
if (pose->orientation) {
decomp.quaternion_x = -pose->orientation.value()[0];
decomp.quaternion_y = -pose->orientation.value()[1];
decomp.quaternion_z = -pose->orientation.value()[2];
decomp.quaternion_w = pose->orientation.value()[3];
} else {
decomp.quaternion_w = 1.0;
}
if (pose->position) {
decomp.translate_x = pose->position.value()[0];
decomp.translate_y = pose->position.value()[1];
decomp.translate_z = pose->position.value()[2];
}
pose_matrix->Recompose(decomp);
return pose_matrix;
}
} // namespace
XRFrameProvider::XRFrameProvider(XRDevice* device) : device_(device) {}
void XRFrameProvider::BeginExclusiveSession(XRSession* session,
ScriptPromiseResolver* resolver) {
// Make sure the session is indeed an exclusive one.
DCHECK(session && session->exclusive());
// Ensure we can only have one exclusive session at a time.
DCHECK(!exclusive_session_);
exclusive_session_ = session;
pending_exclusive_session_resolver_ = resolver;
// Establish the connection with the VSyncProvider if needed.
if (!presentation_provider_.is_bound()) {
frame_transport_ = new XRFrameTransport();
// Set up RequestPresentOptions based on canvas properties.
device::mojom::blink::VRRequestPresentOptionsPtr options =
device::mojom::blink::VRRequestPresentOptions::New();
options->preserve_drawing_buffer = false;
device_->xrDisplayHostPtr()->RequestPresent(
frame_transport_->GetSubmitFrameClient(),
mojo::MakeRequest(&presentation_provider_), std::move(options),
WTF::Bind(&XRFrameProvider::OnPresentComplete, WrapPersistent(this)));
presentation_provider_.set_connection_error_handler(
WTF::Bind(&XRFrameProvider::OnPresentationProviderConnectionError,
WrapWeakPersistent(this)));
}
}
void XRFrameProvider::OnPresentComplete(
bool success,
device::mojom::blink::VRDisplayFrameTransportOptionsPtr transport_options) {
if (success) {
frame_transport_->SetTransportOptions(std::move(transport_options));
frame_transport_->PresentChange();
pending_exclusive_session_resolver_->Resolve(exclusive_session_);
} else {
exclusive_session_->ForceEnd();
if (pending_exclusive_session_resolver_) {
DOMException* exception = DOMException::Create(
kNotAllowedError, "Request for exclusive XRSession was denied.");
pending_exclusive_session_resolver_->Reject(exception);
}
}
pending_exclusive_session_resolver_ = nullptr;
}
void XRFrameProvider::OnPresentationProviderConnectionError() {
if (pending_exclusive_session_resolver_) {
DOMException* exception = DOMException::Create(
kNotAllowedError,
"Error occured while requesting exclusive XRSession.");
pending_exclusive_session_resolver_->Reject(exception);
pending_exclusive_session_resolver_ = nullptr;
}
presentation_provider_.reset();
if (vsync_connection_failed_)
return;
exclusive_session_->ForceEnd();
vsync_connection_failed_ = true;
}
// Called by the exclusive session when it is ended.
void XRFrameProvider::OnExclusiveSessionEnded() {
if (!exclusive_session_)
return;
device_->xrDisplayHostPtr()->ExitPresent();
exclusive_session_ = nullptr;
pending_exclusive_vsync_ = false;
frame_id_ = -1;
if (presentation_provider_.is_bound()) {
presentation_provider_.reset();
}
frame_transport_ = nullptr;
// When we no longer have an active exclusive session schedule all the
// outstanding frames that were requested while the exclusive session was
// active.
if (requesting_sessions_.size() > 0)
ScheduleNonExclusiveFrame();
}
// Schedule a session to be notified when the next XR frame is available.
void XRFrameProvider::RequestFrame(XRSession* session) {
DVLOG(2) << __FUNCTION__;
// If a previous session has already requested a frame don't fire off another
// request. All requests will be fullfilled at once when OnVSync is called.
if (session->exclusive()) {
ScheduleExclusiveFrame();
} else {
requesting_sessions_.push_back(session);
// If there's an active exclusive session save the request but suppress
// processing it until the exclusive session is no longer active.
if (exclusive_session_)
return;
ScheduleNonExclusiveFrame();
}
}
void XRFrameProvider::ScheduleExclusiveFrame() {
if (pending_exclusive_vsync_)
return;
pending_exclusive_vsync_ = true;
presentation_provider_->GetVSync(
WTF::Bind(&XRFrameProvider::OnExclusiveVSync, WrapWeakPersistent(this)));
}
void XRFrameProvider::ScheduleNonExclusiveFrame() {
if (pending_non_exclusive_vsync_)
return;
LocalFrame* frame = device_->xr()->GetFrame();
if (!frame)
return;
Document* doc = frame->GetDocument();
if (!doc)
return;
pending_non_exclusive_vsync_ = true;
device_->xrMagicWindowProviderPtr()->GetPose(WTF::Bind(
&XRFrameProvider::OnNonExclusivePose, WrapWeakPersistent(this)));
doc->RequestAnimationFrame(new XRFrameProviderRequestCallback(this));
}
void XRFrameProvider::OnExclusiveVSync(
device::mojom::blink::VRPosePtr pose,
WTF::TimeDelta time_delta,
int16_t frame_id,
device::mojom::blink::VRPresentationProvider::VSyncStatus status) {
DVLOG(2) << __FUNCTION__;
vsync_connection_failed_ = false;
switch (status) {
case device::mojom::blink::VRPresentationProvider::VSyncStatus::SUCCESS:
break;
case device::mojom::blink::VRPresentationProvider::VSyncStatus::CLOSING:
return;
}
// We may have lost the exclusive session since the last VSync request.
if (!exclusive_session_) {
return;
}
frame_pose_ = std::move(pose);
frame_id_ = frame_id;
pending_exclusive_vsync_ = false;
// Post a task to handle scheduled animations after the current
// execution context finishes, so that we yield to non-mojo tasks in
// between frames. Executing mojo tasks back to back within the same
// execution context caused extreme input delay due to processing
// multiple frames without yielding, see crbug.com/701444.
Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&XRFrameProvider::ProcessScheduledFrame,
WrapWeakPersistent(this), time_delta.InSecondsF()));
}
void XRFrameProvider::OnNonExclusiveVSync(double timestamp) {
DVLOG(2) << __FUNCTION__;
pending_non_exclusive_vsync_ = false;
// Suppress non-exclusive vsyncs when there's an exclusive session active.
if (exclusive_session_)
return;
Platform::Current()->CurrentThread()->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&XRFrameProvider::ProcessScheduledFrame,
WrapWeakPersistent(this), timestamp));
}
void XRFrameProvider::OnNonExclusivePose(device::mojom::blink::VRPosePtr pose) {
frame_pose_ = std::move(pose);
}
void XRFrameProvider::ProcessScheduledFrame(double timestamp) {
DVLOG(2) << __FUNCTION__;
TRACE_EVENT1("gpu", "XRFrameProvider::ProcessScheduledFrame", "frame",
frame_id_);
if (exclusive_session_) {
// If there's an exclusive session active only process it's frame.
std::unique_ptr<TransformationMatrix> pose_matrix =
getPoseMatrix(frame_pose_);
exclusive_session_->OnFrame(std::move(pose_matrix));
} else {
// In the process of fulfilling the frame requests for each session they are
// extremely likely to request another frame. Work off of a separate list
// from the requests to prevent infinite loops.
HeapVector<Member<XRSession>> processing_sessions;
swap(requesting_sessions_, processing_sessions);
// Inform sessions with a pending request of the new frame
for (unsigned i = 0; i < processing_sessions.size(); ++i) {
XRSession* session = processing_sessions.at(i).Get();
std::unique_ptr<TransformationMatrix> pose_matrix =
getPoseMatrix(frame_pose_);
session->OnFrame(std::move(pose_matrix));
}
}
}
void XRFrameProvider::SubmitWebGLLayer(XRWebGLLayer* layer) {
DCHECK(layer);
DCHECK(layer->session() == exclusive_session_);
DCHECK(presentation_provider_);
TRACE_EVENT1("gpu", "XRFrameProvider::SubmitWebGLLayer", "frame", frame_id_);
WebGLRenderingContextBase* webgl_context = layer->context();
frame_transport_->FramePreImage(webgl_context->ContextGL());
std::unique_ptr<viz::SingleReleaseCallback> image_release_callback;
scoped_refptr<Image> image_ref =
layer->TransferToStaticBitmapImage(&image_release_callback);
if (!image_ref)
return;
// Hardware-accelerated rendering should always be texture backed. Ensure this
// is the case, don't attempt to render if using an unexpected drawing path.
if (!image_ref->IsTextureBacked()) {
NOTREACHED() << "WebXR requires hardware-accelerated rendering to texture";
return;
}
// TODO(bajones): Remove this when the Windows path has been updated to no
// longer require a texture copy.
bool needs_copy = device_->external();
frame_transport_->FrameSubmit(
presentation_provider_.get(), webgl_context->ContextGL(), webgl_context,
std::move(image_ref), std::move(image_release_callback), frame_id_,
needs_copy);
// Reset our frame id, since anything we'd want to do (resizing/etc) can
// no-longer happen to this frame.
frame_id_ = -1;
}
// TODO(bajones): This only works because we're restricted to a single layer at
// the moment. Will need an overhaul when we get more robust layering support.
void XRFrameProvider::UpdateWebGLLayerViewports(XRWebGLLayer* layer) {
DCHECK(layer->session() == exclusive_session_);
DCHECK(presentation_provider_);
XRViewport* left = layer->GetViewport(XRView::kEyeLeft);
XRViewport* right = layer->GetViewport(XRView::kEyeRight);
float width = layer->framebufferWidth();
float height = layer->framebufferHeight();
WebFloatRect left_coords(
static_cast<float>(left->x()) / width,
static_cast<float>(height - (left->y() + left->height())) / height,
static_cast<float>(left->width()) / width,
static_cast<float>(left->height()) / height);
WebFloatRect right_coords(
static_cast<float>(right->x()) / width,
static_cast<float>(height - (right->y() + right->height())) / height,
static_cast<float>(right->width()) / width,
static_cast<float>(right->height()) / height);
presentation_provider_->UpdateLayerBounds(
frame_id_, left_coords, right_coords, WebSize(width, height));
}
void XRFrameProvider::Dispose() {
presentation_provider_.reset();
// TODO(bajones): Do something for outstanding frame requests?
}
void XRFrameProvider::Trace(blink::Visitor* visitor) {
visitor->Trace(device_);
visitor->Trace(pending_exclusive_session_resolver_);
visitor->Trace(frame_transport_);
visitor->Trace(exclusive_session_);
visitor->Trace(requesting_sessions_);
}
} // namespace blink