Commit 19bd94e2 authored by Brandon Jones's avatar Brandon Jones Committed by Commit Bot

Enabled frame submission with WebXR

Allows exclusive XRSessions to submit frames to the XR compositor, which
enables the most basic end-to-end usage of the API.

Bug: 670510
Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: I704aaac3325538e2db8f8b6d6f8d341492835a14
Reviewed-on: https://chromium-review.googlesource.com/871790
Commit-Queue: Brandon Jones <bajones@chromium.org>
Reviewed-by: default avatarKenneth Russell <kbr@chromium.org>
Reviewed-by: default avatarKlaus Weidner <klausw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#530379}
parent f372bbfa
......@@ -39,6 +39,10 @@ class MockVRDisplay {
});
}
getSubmitFrameCount() {
return this.presentation_provider_.submit_frame_count_;
}
forceActivate(reason) {
this.displayClient_.onActivate(reason);
}
......@@ -64,6 +68,7 @@ class MockVRPresentationProvider {
constructor() {
this.binding_ = new mojo.Binding(device.mojom.VRPresentationProvider, this);
this.pose_ = null;
this.submit_frame_count_ = 0;
}
bind(client, request) {
......@@ -73,6 +78,8 @@ class MockVRPresentationProvider {
}
submitFrame(frameId, mailboxHolder, timeWaited) {
this.submit_frame_count_++;
// Trigger the submit completion callbacks here. WARNING: The
// Javascript-based mojo mocks are *not* re-entrant. It's OK to
// wait for these notifications on the next frame, but waiting
......
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../vr/resources/fake-vr-displays.js"></script>
<script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="file:///gen/device/vr/vr_service.mojom.js"></script>
<script src="../vr/resources/mock-vr-service.js"></script>
<script src="../vr/resources/test-constants.js"></script>
<canvas id="webgl-canvas"></canvas>
<script src="../vr/resources/presentation-setup.js"></script>
<script>
let fakeDisplays = fakeVRDisplays();
xr_session_test( (t, session, mockService) => {
// Session must have a baseLayer or else frame requests will be ignored.
let webglLayer = new XRWebGLLayer(session, gl);
session.baseLayer = webglLayer;
let mockDisplay = mockService.mockVRDisplays_[0];
function onSkipFrame(time, xrFrame) {
// No GL commands issued
session.requestAnimationFrame(onDrawToCanvas);
}
function onDrawToCanvas(time, xrFrame) {
// Ensure the previous step did not submit a frame
t.step( () => {
assert_equals(mockDisplay.getSubmitFrameCount(), 0);
}, "Expecting no frame was submitted during onSkipFrame");
// Clear the canvas
gl.clear(gl.COLOR_BUFFER_BIT);
session.requestAnimationFrame(onDrawToFramebuffer);
}
function onDrawToFramebuffer(time, xrFrame) {
// Ensure the previous step did not submit a frame
t.step( () => {
assert_equals(mockDisplay.getSubmitFrameCount(), 0);
}, "Expecting no frame was submitted during onDrawToCanvas");
// Clear the VRWebGLLayer framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, webglLayer.framebuffer);
gl.clear(gl.COLOR_BUFFER_BIT);
// After the function returns ensure the frame was submitted.
window.setTimeout(() => {
t.step( () => {
assert_equals(mockDisplay.getSubmitFrameCount(), 1);
}, "Expecting one frame was submitted during onDrawToFramebuffer");
t.done();
}, 100);
}
session.requestAnimationFrame(onSkipFrame);
}, fakeDisplays["Pixel"], { exclusive: true },
"A frame should be submitted if the base layer was written to during requestAnimationFrame");
</script>
......@@ -101,6 +101,9 @@ class WebGLFramebuffer final : public WebGLContextObject {
bool HasStencilBuffer() const;
bool HaveContentsChanged() { return contents_changed_; }
void SetContentsChanged(bool changed) { contents_changed_ = changed; }
bool Opaque() const { return opaque_; }
void MarkOpaqueBufferComplete(bool complete) { opaque_complete_ = complete; }
......@@ -156,6 +159,7 @@ class WebGLFramebuffer final : public WebGLContextObject {
bool has_ever_been_bound_;
bool web_gl1_depth_stencil_consistent_;
bool contents_changed_ = false;
const bool opaque_;
bool opaque_complete_ = false;
......
......@@ -1293,9 +1293,14 @@ void WebGLRenderingContextBase::DestroyContext() {
void WebGLRenderingContextBase::MarkContextChanged(
ContentChangeType change_type) {
if (framebuffer_binding_ || isContextLost())
if (isContextLost())
return;
if (framebuffer_binding_) {
framebuffer_binding_->SetContentsChanged(true);
return;
}
if (!GetDrawingBuffer()->MarkContentsChanged() && marked_canvas_dirty_) {
return;
}
......
......@@ -5,14 +5,19 @@
#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/WebTaskRunner.h"
#include "platform/graphics/gpu/XRFrameTransport.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/transforms/TransformationMatrix.h"
#include "platform/wtf/Time.h"
......@@ -29,12 +34,7 @@ class XRFrameProviderRequestCallback
: frame_provider_(frame_provider) {}
~XRFrameProviderRequestCallback() override = default;
void Invoke(double high_res_time_ms) override {
// TODO(bajones): Eventually exclusive vsyncs won't be handled here.
if (frame_provider_->exclusive_session()) {
frame_provider_->OnExclusiveVSync(high_res_time_ms / 1000.0);
} else {
frame_provider_->OnNonExclusiveVSync(high_res_time_ms / 1000.0);
}
frame_provider_->OnNonExclusiveVSync(high_res_time_ms / 1000.0);
}
virtual void Trace(blink::Visitor* visitor) {
......@@ -98,13 +98,32 @@ void XRFrameProvider::BeginExclusiveSession(XRSession* session,
pending_exclusive_session_resolver_ = resolver;
// TODO(bajones): Request a XRPresentationProviderPtr to use for presenting
// frames, delay call to OnPresentComplete till the connection is established.
OnPresentComplete(true);
// 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) {
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();
......@@ -119,17 +138,39 @@ void XRFrameProvider::OnPresentComplete(bool success) {
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;
// TODO(bajones): Call device_->xrDisplayHostPtr()->ExitPresent();
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.
......@@ -161,21 +202,10 @@ void XRFrameProvider::ScheduleExclusiveFrame() {
if (pending_exclusive_vsync_)
return;
// TODO(bajones): This should acquire frames through a XRPresentationProvider
// instead of duplicating the non-exclusive path.
LocalFrame* frame = device_->xr()->GetFrame();
if (!frame)
return;
Document* doc = frame->GetDocument();
if (!doc)
return;
pending_exclusive_vsync_ = true;
device_->xrMagicWindowProviderPtr()->GetPose(WTF::Bind(
&XRFrameProvider::OnNonExclusivePose, WrapWeakPersistent(this)));
doc->RequestAnimationFrame(new XRFrameProviderRequestCallback(this));
presentation_provider_->GetVSync(
WTF::Bind(&XRFrameProvider::OnExclusiveVSync, WrapWeakPersistent(this)));
}
void XRFrameProvider::ScheduleNonExclusiveFrame() {
......@@ -197,14 +227,28 @@ void XRFrameProvider::ScheduleNonExclusiveFrame() {
doc->RequestAnimationFrame(new XRFrameProviderRequestCallback(this));
}
void XRFrameProvider::OnExclusiveVSync(double timestamp) {
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__;
pending_exclusive_vsync_ = false;
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_)
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
......@@ -213,7 +257,7 @@ void XRFrameProvider::OnExclusiveVSync(double timestamp) {
// multiple frames without yielding, see crbug.com/701444.
Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&XRFrameProvider::ProcessScheduledFrame,
WrapWeakPersistent(this), timestamp));
WrapWeakPersistent(this), time_delta.InSecondsF()));
}
void XRFrameProvider::OnNonExclusiveVSync(double timestamp) {
......@@ -262,9 +306,76 @@ void XRFrameProvider::ProcessScheduledFrame(double timestamp) {
}
}
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());
scoped_refptr<Image> image_ref = layer->TransferToStaticBitmapImage();
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), 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_);
}
......
......@@ -16,6 +16,8 @@ namespace blink {
class ScriptPromiseResolver;
class XRDevice;
class XRSession;
class XRFrameTransport;
class XRWebGLLayer;
// This class manages requesting and dispatching frame updates, which includes
// pose information for a given XRDevice.
......@@ -31,29 +33,41 @@ class XRFrameProvider final
void RequestFrame(XRSession*);
void OnExclusiveVSync(double timestamp);
void OnNonExclusiveVSync(double timestamp);
void SubmitFrame(gpu::MailboxHolder);
void SubmitWebGLLayer(XRWebGLLayer*);
void UpdateWebGLLayerViewports(XRWebGLLayer*);
void Dispose();
virtual void Trace(blink::Visitor*);
private:
void OnExclusiveVSync(
device::mojom::blink::VRPosePtr,
WTF::TimeDelta,
int16_t frame_id,
device::mojom::blink::VRPresentationProvider::VSyncStatus);
void OnNonExclusivePose(device::mojom::blink::VRPosePtr);
void ScheduleExclusiveFrame();
void ScheduleNonExclusiveFrame();
void OnPresentComplete(bool success);
void OnPresentComplete(
bool success,
device::mojom::blink::VRDisplayFrameTransportOptionsPtr);
void OnPresentationProviderConnectionError();
void ProcessScheduledFrame(double timestamp);
const Member<XRDevice> device_;
Member<XRSession> exclusive_session_;
Member<ScriptPromiseResolver> pending_exclusive_session_resolver_;
Member<XRFrameTransport> frame_transport_;
// Non-exclusive Sessions which have requested a frame update.
HeapVector<Member<XRSession>> requesting_sessions_;
device::mojom::blink::VRPresentationProviderPtr presentation_provider_;
device::mojom::blink::VRMagicWindowProviderPtr magic_window_provider_;
device::mojom::blink::VRPosePtr frame_pose_;
......@@ -64,10 +78,6 @@ class XRFrameProvider final
bool pending_exclusive_vsync_ = false;
bool pending_non_exclusive_vsync_ = false;
bool vsync_connection_failed_ = false;
double timebase_ = -1;
bool pending_submit_frame_ = false;
bool pending_previous_frame_render_ = false;
};
} // namespace blink
......
......@@ -11,6 +11,7 @@
#include "modules/webgl/WebGLFramebuffer.h"
#include "modules/webgl/WebGLRenderingContext.h"
#include "modules/xr/XRDevice.h"
#include "modules/xr/XRFrameProvider.h"
#include "modules/xr/XRSession.h"
#include "modules/xr/XRView.h"
#include "modules/xr/XRViewport.h"
......@@ -170,6 +171,8 @@ void XRWebGLLayer::UpdateViewports() {
new XRViewport(framebuffer_width * 0.5 * viewport_scale_, 0,
framebuffer_width * 0.5 * viewport_scale_,
framebuffer_height * viewport_scale_);
session()->device()->frameProvider()->UpdateWebGLLayerViewports(this);
} else {
left_viewport_ = new XRViewport(0, 0, framebuffer_width * viewport_scale_,
framebuffer_height * viewport_scale_);
......@@ -178,10 +181,19 @@ void XRWebGLLayer::UpdateViewports() {
void XRWebGLLayer::OnFrameStart() {
framebuffer_->MarkOpaqueBufferComplete(true);
framebuffer_->SetContentsChanged(false);
}
void XRWebGLLayer::OnFrameEnd() {
framebuffer_->MarkOpaqueBufferComplete(false);
// Exit early if the framebuffer contents have not changed.
if (!framebuffer_->HaveContentsChanged())
return;
// Submit the frame to the XR compositor.
if (session()->exclusive()) {
session()->device()->frameProvider()->SubmitWebGLLayer(this);
}
}
void XRWebGLLayer::OnResize() {
......
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