Commit f5bbb138 authored by Klaus Weidner's avatar Klaus Weidner Committed by Commit Bot

Add SubmitFrameMissing mojo call for WebVR/WebXR

Goal is that we get a clean lifecycle for a functioning WebVR/WebXR
presentation render loop. It's started by a presenting SendVSync, calls
GetVSync to schedule the next frame, and is ended by a SubmitFrame
call. If there was nothing drawn, it uses SubmitFrameMissing instead of the
usual SubmitFrame/SubmitFrameWithTextureHandle.

In WebVR 1.1, submitFrame is a JS call, and the app can exit its animation
loop without calling it. WebXR had an analogous feature where SubmitFrame
was skipped if the framebuffer wasn't touched by drawing calls. This
made it hard to tell for the device side if a frame is done or not.

WebVR 1.1 worked around this by deferring a GetVSync call until after
SubmitFrame, but this was complex:

-  // The logic here is a bit subtle. We get called from one of the following
-  // four contexts:
-  //
-  // (a) from requestAnimationFrame if outside an animating context (i.e. the
-  //     first rAF call from inside a getVRDisplays() promise)
-  //
-  // (b) from requestAnimationFrame in an animating context if the JS code
-  //     calls rAF after submitFrame.
-  //
-  // (c) from submitFrame if that is called after rAF.
-  //
-  // (d) from ProcessScheduledAnimations if a rAF callback finishes without
-  //     submitting a frame.
-  //
-  // These cases are mutually exclusive which prevents duplicate GetVSync
-  // calls. Case (a) only applies outside an animating context
-  // (in_animation_frame_ is false), and (b,c,d) all require an animating
-  // context. While in an animating context, submitFrame is called either
-  // before rAF (b), after rAF (c), or not at all (d). If rAF isn't called at
-  // all, there won't be future frames.

This CL removes those special cases and just always calls RequestVSync from
requestAnimationFrame, collapsing cases (a) and (b) into an unconditional call.
Cases (c) and (d) are now no longer needed and removed.

The layout tests now check the SubmitFrameMissing call count.

Also added a check for a WebXR exclusive session ending in the middle of a
frame. (The layout tests revealed this since OnEndFrame no longer exits early
on a clean framebuffer.)

Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: I5722097d421ca9448760e696ea379895a1320199
Reviewed-on: https://chromium-review.googlesource.com/996614Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Reviewed-by: default avatarBrandon Jones <bajones@chromium.org>
Reviewed-by: default avatarMartin Barbella <mbarbella@chromium.org>
Reviewed-by: default avatarBill Orr <billorr@chromium.org>
Commit-Queue: Klaus Weidner <klausw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548518}
parent 521940b7
...@@ -304,6 +304,12 @@ void MailboxToSurfaceBridge::GenSyncToken(gpu::SyncToken* out_sync_token) { ...@@ -304,6 +304,12 @@ void MailboxToSurfaceBridge::GenSyncToken(gpu::SyncToken* out_sync_token) {
gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData()); gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
} }
void MailboxToSurfaceBridge::WaitSyncToken(const gpu::SyncToken& sync_token) {
TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected());
gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
}
void MailboxToSurfaceBridge::WaitForClientGpuFence(gfx::GpuFence* gpu_fence) { void MailboxToSurfaceBridge::WaitForClientGpuFence(gfx::GpuFence* gpu_fence) {
TRACE_EVENT0("gpu", __FUNCTION__); TRACE_EVENT0("gpu", __FUNCTION__);
DCHECK(IsConnected()); DCHECK(IsConnected());
......
...@@ -61,6 +61,8 @@ class MailboxToSurfaceBridge { ...@@ -61,6 +61,8 @@ class MailboxToSurfaceBridge {
void GenSyncToken(gpu::SyncToken* out_sync_token); void GenSyncToken(gpu::SyncToken* out_sync_token);
void WaitSyncToken(const gpu::SyncToken& sync_token);
// Copies a GpuFence from the local context to the GPU process, // Copies a GpuFence from the local context to the GPU process,
// and issues a server wait for it. // and issues a server wait for it.
void WaitForClientGpuFence(gfx::GpuFence*); void WaitForClientGpuFence(gfx::GpuFence*);
......
...@@ -390,9 +390,7 @@ void VrShellGl::CreateOrResizeWebVRSurface(const gfx::Size& size) { ...@@ -390,9 +390,7 @@ void VrShellGl::CreateOrResizeWebVRSurface(const gfx::Size& size) {
} }
} }
void VrShellGl::SubmitFrame(int16_t frame_index, bool VrShellGl::IsSubmitFrameExpected(int16_t frame_index) {
const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) {
TRACE_EVENT0("gpu", "VrShellGl::SubmitWebVRFrame"); TRACE_EVENT0("gpu", "VrShellGl::SubmitWebVRFrame");
// submit_client_ could be null when we exit presentation, if there were // submit_client_ could be null when we exit presentation, if there were
...@@ -400,15 +398,39 @@ void VrShellGl::SubmitFrame(int16_t frame_index, ...@@ -400,15 +398,39 @@ void VrShellGl::SubmitFrame(int16_t frame_index,
// will clean up state in blink, so it doesn't wait for // will clean up state in blink, so it doesn't wait for
// OnSubmitFrameTransferred or OnSubmitFrameRendered. // OnSubmitFrameTransferred or OnSubmitFrameRendered.
if (!submit_client_.get()) if (!submit_client_.get())
return; return false;
if (frame_index < 0 || if (frame_index < 0 ||
!webvr_frame_oustanding_[frame_index % kPoseRingBufferSize]) { !webvr_frame_oustanding_[frame_index % kPoseRingBufferSize]) {
mojo::ReportBadMessage("SubmitFrame called with an invalid frame_index"); mojo::ReportBadMessage("SubmitFrame called with an invalid frame_index");
binding_.Close(); binding_.Close();
return; return false;
} }
// Frame looks valid.
return true;
}
void VrShellGl::SubmitFrameMissing(int16_t frame_index,
const gpu::SyncToken& sync_token) {
if (!IsSubmitFrameExpected(frame_index))
return;
// Renderer didn't submit a frame. Wait for the sync token to ensure
// that any mailbox_bridge_ operations for the next frame happen after
// whatever drawing the Renderer may have done before exiting.
if (mailbox_bridge_ready_)
mailbox_bridge_->WaitSyncToken(sync_token);
DVLOG(2) << __FUNCTION__ << ": recycle unused animating frame";
webvr_frame_oustanding_[frame_index % kPoseRingBufferSize] = false;
}
void VrShellGl::SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) {
if (!IsSubmitFrameExpected(frame_index))
return;
// The JavaScript wait time is supplied externally and not trustworthy. Clamp // The JavaScript wait time is supplied externally and not trustworthy. Clamp
// to a reasonable range to avoid math errors. // to a reasonable range to avoid math errors.
......
...@@ -175,8 +175,11 @@ class VrShellGl : public device::mojom::VRPresentationProvider { ...@@ -175,8 +175,11 @@ class VrShellGl : public device::mojom::VRPresentationProvider {
void OnVSync(base::TimeTicks frame_time); void OnVSync(base::TimeTicks frame_time);
bool IsSubmitFrameExpected(int16_t frame_index);
// VRPresentationProvider // VRPresentationProvider
void GetVSync(GetVSyncCallback callback) override; void GetVSync(GetVSyncCallback callback) override;
void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
void SubmitFrame(int16_t frame_index, void SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox, const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) override; base::TimeDelta time_waited) override;
......
...@@ -60,6 +60,12 @@ void OculusRenderLoop::CleanUp() { ...@@ -60,6 +60,12 @@ void OculusRenderLoop::CleanUp() {
binding_.Close(); binding_.Close();
} }
void OpenVRRenderLoop::SubmitFrameMissing(int16_t frame_index,
const gpu::SyncToken& sync_token) {
// Nothing to do. It's OK to start the next frame even if the current
// one didn't get sent to the ovrSession.
}
void OculusRenderLoop::SubmitFrame(int16_t frame_index, void OculusRenderLoop::SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox, const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) { base::TimeDelta time_waited) {
......
...@@ -36,6 +36,7 @@ class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider { ...@@ -36,6 +36,7 @@ class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider {
base::WeakPtr<OculusRenderLoop> GetWeakPtr(); base::WeakPtr<OculusRenderLoop> GetWeakPtr();
// VRPresentationProvider overrides: // VRPresentationProvider overrides:
void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
void SubmitFrame(int16_t frame_index, void SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox, const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) override; base::TimeDelta time_waited) override;
......
...@@ -55,6 +55,12 @@ OpenVRRenderLoop::~OpenVRRenderLoop() { ...@@ -55,6 +55,12 @@ OpenVRRenderLoop::~OpenVRRenderLoop() {
Stop(); Stop();
} }
void OpenVRRenderLoop::SubmitFrameMissing(int16_t frame_index,
const gpu::SyncToken& sync_token) {
// Nothing to do. It's OK to start the next frame even if the current
// one didn't get sent to OpenVR.
}
void OpenVRRenderLoop::SubmitFrame(int16_t frame_index, void OpenVRRenderLoop::SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox, const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) { base::TimeDelta time_waited) {
......
...@@ -35,6 +35,7 @@ class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider { ...@@ -35,6 +35,7 @@ class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider {
base::WeakPtr<OpenVRRenderLoop> GetWeakPtr(); base::WeakPtr<OpenVRRenderLoop> GetWeakPtr();
// VRPresentationProvider overrides: // VRPresentationProvider overrides:
void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
void SubmitFrame(int16_t frame_index, void SubmitFrame(int16_t frame_index,
const gpu::MailboxHolder& mailbox, const gpu::MailboxHolder& mailbox,
base::TimeDelta time_waited) override; base::TimeDelta time_waited) override;
......
...@@ -245,6 +245,16 @@ interface VRPresentationProvider { ...@@ -245,6 +245,16 @@ interface VRPresentationProvider {
UpdateLayerBounds(int16 frame_id, gfx.mojom.RectF left_bounds, UpdateLayerBounds(int16 frame_id, gfx.mojom.RectF left_bounds,
gfx.mojom.RectF right_bounds, gfx.mojom.Size source_size); gfx.mojom.RectF right_bounds, gfx.mojom.Size source_size);
// Call this if the animation loop exited without submitting a frame to
// ensure that every GetVSync has a matching Submit call. This happens for
// WebXR if there were no drawing operations to the opaque framebuffer, and
// for WebVR 1.1 if the application didn't call SubmitFrame. Usable with any
// VRDisplayFrameTransportMethod. This path does *not* call the
// SubmitFrameClient methods such as OnSubmitFrameTransferred. This is
// intended to help separate frames while presenting, it may or may not
// be called for the last animating frame when presentation ends.
SubmitFrameMissing(int16 frame_id, gpu.mojom.SyncToken sync_token);
// VRDisplayFrameTransportMethod SUBMIT_AS_MAILBOX_HOLDER // VRDisplayFrameTransportMethod SUBMIT_AS_MAILBOX_HOLDER
SubmitFrame(int16 frame_id, gpu.mojom.MailboxHolder mailbox_holder, SubmitFrame(int16 frame_id, gpu.mojom.MailboxHolder mailbox_holder,
mojo_base.mojom.TimeDelta time_waited); mojo_base.mojom.TimeDelta time_waited);
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<script> <script>
let fakeDisplays = fakeVRDisplays(); let fakeDisplays = fakeVRDisplays();
vr_test( (t) => { vr_test( (t, mock_service) => {
return navigator.getVRDisplays().then( (displays) => { return navigator.getVRDisplays().then( (displays) => {
let display = displays[0]; let display = displays[0];
...@@ -33,31 +33,51 @@ vr_test( (t) => { ...@@ -33,31 +33,51 @@ vr_test( (t) => {
} }
} }
function getSubmitFrameCount() {
return mock_service.mockVRDisplays_[0].getSubmitFrameCount();
}
function getMissingFrameCount() {
return mock_service.mockVRDisplays_[0].getMissingFrameCount();
}
function onFrame1() { function onFrame1() {
assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 0);
// case (b): submit frame first, then rAF // case (b): submit frame first, then rAF
display.submitFrame(); display.submitFrame();
display.requestAnimationFrame(onFrame2); display.requestAnimationFrame(onFrame2);
} }
function onFrame2() { function onFrame2() {
assert_equals(getSubmitFrameCount(), 1);
assert_equals(getMissingFrameCount(), 0);
// case (c): rAF first, then submit frame // case (c): rAF first, then submit frame
display.requestAnimationFrame(onFrame3); display.requestAnimationFrame(onFrame3);
display.submitFrame(); display.submitFrame();
} }
function onFrame3(time) { function onFrame3(time) {
assert_equals(getSubmitFrameCount(), 2);
assert_equals(getMissingFrameCount(), 0);
// case (d): don't submit a frame. // case (d): don't submit a frame.
display.requestAnimationFrame(onFrame4); display.requestAnimationFrame(onFrame4);
} }
function onFrame4(time) { function onFrame4(time) {
// If we get here, we're done. // If we get here, we're done.
assert_equals(getSubmitFrameCount(), 2);
assert_equals(getMissingFrameCount(), 1);
t.done(); t.done();
} }
function startPresentation() { function startPresentation() {
assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 0);
display.requestPresent([{ source : webglCanvas }]).then( () => { display.requestPresent([{ source : webglCanvas }]).then( () => {
t.step( () => { t.step( () => {
assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 0);
// case (a): in requestPresent promise, outside animating context. // case (a): in requestPresent promise, outside animating context.
assert_true(display.isPresenting); assert_true(display.isPresenting);
display.requestAnimationFrame(onFrame1); display.requestAnimationFrame(onFrame1);
......
...@@ -43,6 +43,10 @@ class MockVRDisplay { ...@@ -43,6 +43,10 @@ class MockVRDisplay {
return this.presentation_provider_.submit_frame_count_; return this.presentation_provider_.submit_frame_count_;
} }
getMissingFrameCount() {
return this.presentation_provider_.missing_frame_count_;
}
forceActivate(reason) { forceActivate(reason) {
this.displayClient_.onActivate(reason); this.displayClient_.onActivate(reason);
} }
...@@ -69,6 +73,7 @@ class MockVRPresentationProvider { ...@@ -69,6 +73,7 @@ class MockVRPresentationProvider {
this.binding_ = new mojo.Binding(device.mojom.VRPresentationProvider, this); this.binding_ = new mojo.Binding(device.mojom.VRPresentationProvider, this);
this.pose_ = null; this.pose_ = null;
this.submit_frame_count_ = 0; this.submit_frame_count_ = 0;
this.missing_frame_count_ = 0;
} }
bind(client, request) { bind(client, request) {
...@@ -77,6 +82,10 @@ class MockVRPresentationProvider { ...@@ -77,6 +82,10 @@ class MockVRPresentationProvider {
this.binding_.bind(request); this.binding_.bind(request);
} }
submitFrameMissing(frameId, syncToken) {
this.missing_frame_count_++;
}
submitFrame(frameId, mailboxHolder, timeWaited) { submitFrame(frameId, mailboxHolder, timeWaited) {
this.submit_frame_count_++; this.submit_frame_count_++;
......
...@@ -36,6 +36,11 @@ function getSubmitFrameCount() { ...@@ -36,6 +36,11 @@ function getSubmitFrameCount() {
return mockVRService.mockVRDisplays_[0].getSubmitFrameCount(); return mockVRService.mockVRDisplays_[0].getSubmitFrameCount();
} }
// Returns the missing (not submitted) frame count for the first display
function getMissingFrameCount() {
return mockVRService.mockVRDisplays_[0].getMissingFrameCount();
}
function addInputSource(input_source) { function addInputSource(input_source) {
return mockVRService.mockVRDisplays_[0].addInputSource(input_source); return mockVRService.mockVRDisplays_[0].addInputSource(input_source);
} }
...@@ -293,6 +298,10 @@ class MockDevice { ...@@ -293,6 +298,10 @@ class MockDevice {
return this.presentation_provider_.submit_frame_count_; return this.presentation_provider_.submit_frame_count_;
} }
getMissingFrameCount() {
return this.presentation_provider_.missing_frame_count_;
}
forceActivate(reason) { forceActivate(reason) {
this.displayClient_.onActivate(reason); this.displayClient_.onActivate(reason);
} }
...@@ -329,6 +338,7 @@ class MockVRPresentationProvider { ...@@ -329,6 +338,7 @@ class MockVRPresentationProvider {
this.pose_ = null; this.pose_ = null;
this.next_frame_id_ = 0; this.next_frame_id_ = 0;
this.submit_frame_count_ = 0; this.submit_frame_count_ = 0;
this.missing_frame_count_ = 0;
this.input_sources_ = []; this.input_sources_ = [];
this.next_input_source_index_ = 1; this.next_input_source_index_ = 1;
...@@ -340,6 +350,10 @@ class MockVRPresentationProvider { ...@@ -340,6 +350,10 @@ class MockVRPresentationProvider {
this.binding_.bind(request); this.binding_.bind(request);
} }
submitFrameMissing(frameId, mailboxHolder, timeWaited) {
this.missing_frame_count_++;
}
submitFrame(frameId, mailboxHolder, timeWaited) { submitFrame(frameId, mailboxHolder, timeWaited) {
this.submit_frame_count_++; this.submit_frame_count_++;
......
...@@ -17,6 +17,8 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => { ...@@ -17,6 +17,8 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => {
session.baseLayer = webglLayer; session.baseLayer = webglLayer;
function onSkipFrame(time, xrFrame) { function onSkipFrame(time, xrFrame) {
assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 0);
// No GL commands issued. // No GL commands issued.
session.requestAnimationFrame(onDrawToCanvas); session.requestAnimationFrame(onDrawToCanvas);
} }
...@@ -24,15 +26,17 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => { ...@@ -24,15 +26,17 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => {
function onDrawToCanvas(time, xrFrame) { function onDrawToCanvas(time, xrFrame) {
// Ensure the previous step did not submit a frame. // Ensure the previous step did not submit a frame.
assert_equals(getSubmitFrameCount(), 0); assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 1);
// Clear the canvas. // Clear the canvas, but don't touch the framebuffer.
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
session.requestAnimationFrame(onDrawToFramebuffer); session.requestAnimationFrame(onDrawToFramebuffer);
} }
function onDrawToFramebuffer(time, xrFrame) { function onDrawToFramebuffer(time, xrFrame) {
// Ensure the previous step did not submit a frame. // Ensure both previous steps did not submit frames.
assert_equals(getSubmitFrameCount(), 0); assert_equals(getSubmitFrameCount(), 0);
assert_equals(getMissingFrameCount(), 2);
// Clear the VRWebGLLayer framebuffer. // Clear the VRWebGLLayer framebuffer.
gl.bindFramebuffer(gl.FRAMEBUFFER, webglLayer.framebuffer); gl.bindFramebuffer(gl.FRAMEBUFFER, webglLayer.framebuffer);
...@@ -41,6 +45,7 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => { ...@@ -41,6 +45,7 @@ xr_session_promise_test( (session) => new Promise((resolve, reject) => {
// After the function returns ensure the frame was submitted. // After the function returns ensure the frame was submitted.
window.setTimeout(() => { window.setTimeout(() => {
assert_equals(getSubmitFrameCount(), 1); assert_equals(getSubmitFrameCount(), 1);
assert_equals(getMissingFrameCount(), 2);
// Finished test. // Finished test.
resolve(); resolve();
}, 100); }, 100);
......
...@@ -244,27 +244,6 @@ void VRDisplay::RequestVSync() { ...@@ -244,27 +244,6 @@ void VRDisplay::RequestVSync() {
if (pending_presenting_vsync_) if (pending_presenting_vsync_)
return; return;
// The logic here is a bit subtle. We get called from one of the following
// four contexts:
//
// (a) from requestAnimationFrame if outside an animating context (i.e. the
// first rAF call from inside a getVRDisplays() promise)
//
// (b) from requestAnimationFrame in an animating context if the JS code
// calls rAF after submitFrame.
//
// (c) from submitFrame if that is called after rAF.
//
// (d) from ProcessScheduledAnimations if a rAF callback finishes without
// submitting a frame.
//
// These cases are mutually exclusive which prevents duplicate GetVSync
// calls. Case (a) only applies outside an animating context
// (in_animation_frame_ is false), and (b,c,d) all require an animating
// context. While in an animating context, submitFrame is called either
// before rAF (b), after rAF (c), or not at all (d). If rAF isn't called at
// all, there won't be future frames.
pending_magic_window_vsync_ = false; pending_magic_window_vsync_ = false;
pending_presenting_vsync_ = true; pending_presenting_vsync_ = true;
vr_presentation_provider_->GetVSync( vr_presentation_provider_->GetVSync(
...@@ -281,13 +260,8 @@ int VRDisplay::requestAnimationFrame(V8FrameRequestCallback* callback) { ...@@ -281,13 +260,8 @@ int VRDisplay::requestAnimationFrame(V8FrameRequestCallback* callback) {
return 0; return 0;
pending_vrdisplay_raf_ = true; pending_vrdisplay_raf_ = true;
// We want to delay the GetVSync call while presenting to ensure it doesn't
// arrive earlier than frame submission, but other than that we want to call
// it as early as possible. See comments inside RequestVSync() for more
// details on the applicable cases.
if (!is_presenting_ || !in_animation_frame_ || did_submit_this_frame_) {
RequestVSync(); RequestVSync();
}
FrameRequestCallbackCollection::V8FrameCallback* frame_callback = FrameRequestCallbackCollection::V8FrameCallback* frame_callback =
FrameRequestCallbackCollection::V8FrameCallback::Create(callback); FrameRequestCallbackCollection::V8FrameCallback::Create(callback);
frame_callback->SetUseLegacyTimeBase(false); frame_callback->SetUseLegacyTimeBase(false);
...@@ -756,8 +730,6 @@ void VRDisplay::submitFrame() { ...@@ -756,8 +730,6 @@ void VRDisplay::submitFrame() {
// Reset our frame id, since anything we'd want to do (resizing/etc) can // Reset our frame id, since anything we'd want to do (resizing/etc) can
// no-longer happen to this frame. // no-longer happen to this frame.
vr_frame_id_ = -1; vr_frame_id_ = -1;
// If we were deferring a rAF-triggered vsync request, do this now.
RequestVSync();
// If preserveDrawingBuffer is false, must clear now. Normally this // If preserveDrawingBuffer is false, must clear now. Normally this
// happens as part of compositing, but that's not active while // happens as part of compositing, but that's not active while
...@@ -908,10 +880,19 @@ void VRDisplay::ProcessScheduledAnimations(double timestamp) { ...@@ -908,10 +880,19 @@ void VRDisplay::ProcessScheduledAnimations(double timestamp) {
pending_vrdisplay_raf_ = false; pending_vrdisplay_raf_ = false;
did_submit_this_frame_ = false; did_submit_this_frame_ = false;
scripted_animation_controller_->ServiceScriptedAnimations(timestamp); scripted_animation_controller_->ServiceScriptedAnimations(timestamp);
// requestAnimationFrame may have deferred RequestVSync, call it now to // If presenting and the script didn't call SubmitFrame, let the device
// cover the case where no frame was submitted, or where presentation ended // side know so that it can cleanly reuse resources and make appropriate
// while servicing the scripted animation. // timing decisions. Note that is_presenting_ could become false during
RequestVSync(); // an animation loop due to reentrant mojo processing in SubmitFrame,
// so there's no guarantee that this is called for the last animating
// frame. That's OK since the sync token placed by FrameSubmitMissing
// is only intended to separate frames while presenting.
if (is_presenting_ && !did_submit_this_frame_) {
DCHECK(frame_transport_);
DCHECK(context_gl_);
frame_transport_->FrameSubmitMissing(vr_presentation_provider_.get(),
context_gl_, vr_frame_id_);
}
} }
if (pending_pose_) if (pending_pose_)
frame_pose_ = std::move(pending_pose_); frame_pose_ = std::move(pending_pose_);
......
...@@ -150,7 +150,6 @@ void XRFrameProvider::OnPresentationProviderConnectionError() { ...@@ -150,7 +150,6 @@ void XRFrameProvider::OnPresentationProviderConnectionError() {
pending_exclusive_session_resolver_->Reject(exception); pending_exclusive_session_resolver_->Reject(exception);
pending_exclusive_session_resolver_ = nullptr; pending_exclusive_session_resolver_ = nullptr;
} }
presentation_provider_.reset(); presentation_provider_.reset();
if (vsync_connection_failed_) if (vsync_connection_failed_)
return; return;
...@@ -321,7 +320,7 @@ void XRFrameProvider::ProcessScheduledFrame(double timestamp) { ...@@ -321,7 +320,7 @@ void XRFrameProvider::ProcessScheduledFrame(double timestamp) {
} }
} }
void XRFrameProvider::SubmitWebGLLayer(XRWebGLLayer* layer) { void XRFrameProvider::SubmitWebGLLayer(XRWebGLLayer* layer, bool was_changed) {
DCHECK(layer); DCHECK(layer);
DCHECK(layer->session() == exclusive_session_); DCHECK(layer->session() == exclusive_session_);
DCHECK(presentation_provider_); DCHECK(presentation_provider_);
...@@ -330,6 +329,14 @@ void XRFrameProvider::SubmitWebGLLayer(XRWebGLLayer* layer) { ...@@ -330,6 +329,14 @@ void XRFrameProvider::SubmitWebGLLayer(XRWebGLLayer* layer) {
WebGLRenderingContextBase* webgl_context = layer->context(); WebGLRenderingContextBase* webgl_context = layer->context();
if (!was_changed) {
// Just tell the device side that there was no submitted frame instead
// of executing the implicit end-of-frame submit.
frame_transport_->FrameSubmitMissing(presentation_provider_.get(),
webgl_context->ContextGL(), frame_id_);
return;
}
frame_transport_->FramePreImage(webgl_context->ContextGL()); frame_transport_->FramePreImage(webgl_context->ContextGL());
std::unique_ptr<viz::SingleReleaseCallback> image_release_callback; std::unique_ptr<viz::SingleReleaseCallback> image_release_callback;
......
...@@ -35,7 +35,7 @@ class XRFrameProvider final ...@@ -35,7 +35,7 @@ class XRFrameProvider final
void OnNonExclusiveVSync(double timestamp); void OnNonExclusiveVSync(double timestamp);
void SubmitWebGLLayer(XRWebGLLayer*); void SubmitWebGLLayer(XRWebGLLayer*, bool was_changed);
void UpdateWebGLLayerViewports(XRWebGLLayer*); void UpdateWebGLLayerViewports(XRWebGLLayer*);
void Dispose(); void Dispose();
......
...@@ -329,6 +329,9 @@ void XRSession::OnFrame( ...@@ -329,6 +329,9 @@ void XRSession::OnFrame(
AutoReset<bool> resolving(&resolving_frame_, true); AutoReset<bool> resolving(&resolving_frame_, true);
callback_collection_.ExecuteCallbacks(this, presentation_frame); callback_collection_.ExecuteCallbacks(this, presentation_frame);
// The session might have ended in the middle of the frame. Only call
// OnFrameEnd if it's still valid.
if (!ended_)
frame_base_layer->OnFrameEnd(); frame_base_layer->OnFrameEnd();
} }
} }
......
...@@ -254,18 +254,20 @@ void XRWebGLLayer::OnFrameStart() { ...@@ -254,18 +254,20 @@ void XRWebGLLayer::OnFrameStart() {
void XRWebGLLayer::OnFrameEnd() { void XRWebGLLayer::OnFrameEnd() {
framebuffer_->MarkOpaqueBufferComplete(false); framebuffer_->MarkOpaqueBufferComplete(false);
// Exit early if the framebuffer contents have not changed.
if (!framebuffer_->HaveContentsChanged())
return;
// Submit the frame to the XR compositor. // Submit the frame to the XR compositor.
if (session()->exclusive()) { if (session()->exclusive()) {
session()->device()->frameProvider()->SubmitWebGLLayer(this); // Always call submit, but notify if the contents were changed or not.
session()->device()->frameProvider()->SubmitWebGLLayer(
this, framebuffer_->HaveContentsChanged());
} else if (session()->outputContext()) { } else if (session()->outputContext()) {
// Nothing to do if the framebuffer contents have not changed.
if (framebuffer_->HaveContentsChanged()) {
ImageBitmap* image_bitmap = ImageBitmap* image_bitmap =
ImageBitmap::Create(TransferToStaticBitmapImage(nullptr)); ImageBitmap::Create(TransferToStaticBitmapImage(nullptr));
session()->outputContext()->SetImage(image_bitmap); session()->outputContext()->SetImage(image_bitmap);
} }
}
} }
void XRWebGLLayer::OnResize() { void XRWebGLLayer::OnResize() {
......
...@@ -66,6 +66,16 @@ void XRFrameTransport::CallPreviousFrameCallback() { ...@@ -66,6 +66,16 @@ void XRFrameTransport::CallPreviousFrameCallback() {
} }
} }
void XRFrameTransport::FrameSubmitMissing(
device::mojom::blink::VRPresentationProvider* vr_presentation_provider,
gpu::gles2::GLES2Interface* gl,
int16_t vr_frame_id) {
TRACE_EVENT0("gpu", __FUNCTION__);
gpu::SyncToken sync_token;
gl->GenSyncTokenCHROMIUM(sync_token.GetData());
vr_presentation_provider->SubmitFrameMissing(vr_frame_id, sync_token);
}
void XRFrameTransport::FrameSubmit( void XRFrameTransport::FrameSubmit(
device::mojom::blink::VRPresentationProvider* vr_presentation_provider, device::mojom::blink::VRPresentationProvider* vr_presentation_provider,
gpu::gles2::GLES2Interface* gl, gpu::gles2::GLES2Interface* gl,
......
...@@ -53,6 +53,10 @@ class PLATFORM_EXPORT XRFrameTransport final ...@@ -53,6 +53,10 @@ class PLATFORM_EXPORT XRFrameTransport final
int16_t vr_frame_id, int16_t vr_frame_id,
bool needs_copy); bool needs_copy);
void FrameSubmitMissing(device::mojom::blink::VRPresentationProvider*,
gpu::gles2::GLES2Interface*,
int16_t vr_frame_id);
virtual void Trace(blink::Visitor*); virtual void Trace(blink::Visitor*);
private: private:
......
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