Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(856)

Unified Diff: third_party/WebKit/Source/modules/vr/VRDisplay.cpp

Issue 2584343002: WIP: working copy-no-compositor path
Patch Set: StatTracker destructor, delete old magic numbers, mojo export Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/modules/vr/VRDisplay.cpp
diff --git a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
index c35e7f133671d7e0e39b8124b7fbd0ee82798014..5fe8300e78a359b12437c928a75ae2adfacc3c65 100644
--- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
+++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp
@@ -8,8 +8,8 @@
#include "core/dom/DOMException.h"
#include "core/dom/DocumentUserGestureToken.h"
#include "core/dom/FrameRequestCallback.h"
-#include "core/dom/Fullscreen.h"
#include "core/dom/ScriptedAnimationController.h"
+#include "core/frame/FrameView.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/loader/DocumentLoader.h"
@@ -26,19 +26,16 @@
#include "modules/webgl/WebGLRenderingContextBase.h"
#include "platform/Histogram.h"
#include "platform/UserGestureIndicator.h"
+#include "platform/instrumentation/tracing/TraceEvent.h"
#include "public/platform/Platform.h"
#include "wtf/AutoReset.h"
-#include <array>
+#include <time.h>
namespace blink {
namespace {
-// Magic numbers used to mark valid pose index values encoded in frame
-// data. Must match the magic numbers used in vr_shell.cc.
-static constexpr std::array<uint8_t, 2> kWebVrPosePixelMagicNumbers{{42, 142}};
-
VREye stringToVREye(const String& whichEye) {
if (whichEye == "left")
return VREyeLeft;
@@ -58,11 +55,7 @@ class VRDisplayFrameRequestCallback : public FrameRequestCallback {
if (!doc)
return;
- // Need to divide by 1000 here because serviceScriptedAnimations expects
- // time to be given in seconds.
- m_vrDisplay->serviceScriptedAnimations(
- doc->loader()->timing().pseudoWallTimeToMonotonicTime(highResTimeMs /
- 1000.0));
+ m_vrDisplay->frameTick(highResTimeMs);
}
DEFINE_INLINE_VIRTUAL_TRACE() {
@@ -76,6 +69,62 @@ class VRDisplayFrameRequestCallback : public FrameRequestCallback {
} // namespace
+StatTracker::StatTracker(unsigned int capacity) : m_capacity(capacity) {}
+
+StatTracker::~StatTracker() = default;
+
+void StatTracker::add(double item) {
+ if (m_items.size() >= m_capacity) {
+ m_items.pop_front();
+ }
+ m_items.push_back(item);
+}
+
+void StatTracker::clear() {
+ m_items.clear();
+}
+
+bool StatTracker::hasPrediction() {
+ return m_items.size() > 0;
+}
+
+double StatTracker::getPrediction() {
+ assert(hasPrediction());
+
+ // If we have 3 or more items, ignore min and max outliers and
+ // average the rest. For 2 or less, minmax.first and minmax.second
+ // will both be m_items.end(), so it's just a plain average.
+ auto minmax = m_items.size() > 2 ?
+ std::minmax_element(m_items.begin(), m_items.end()) :
+ std::minmax_element(m_items.end(), m_items.end());
+
+ double sum = 0.0;
+ int count = 0;
+ //VLOG(2) << __FUNCTION__ << ": stat start";
+ for (auto it = m_items.begin(); it != m_items.end(); ++it) {
+ //VLOG(2) << __FUNCTION__ << ": val=" << *it;
+ if (it == minmax.first || it == minmax.second) continue;
+ sum += *it;
+ ++count;
+ }
+ //VLOG(2) << __FUNCTION__ << ": stat return " << sum / count;
+ return sum / count;
+}
+
+static double getMonotonicTimestampMs() {
+#if defined(OS_ANDROID)
+ // Android surfacetexture timestamp compatible timer? See:
+ // http://androidxref.com/7.0.0_r1/xref/frameworks/native/libs/gui/Surface.cpp#370
+ // http://androidxref.com/7.0.0_r1/xref/frameworks/rs/rsCppUtils.h#162
+ struct timespec t;
+ t.tv_sec = t.tv_nsec = 0;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return t.tv_sec * 1e3 + t.tv_nsec * 1e-6;
+#else
+ return 0.0;
+#endif
+}
+
VRDisplay::VRDisplay(NavigatorVR* navigatorVR,
device::mojom::blink::VRDisplayPtr display,
device::mojom::blink::VRDisplayClientRequest request)
@@ -84,26 +133,34 @@ VRDisplay::VRDisplay(NavigatorVR* navigatorVR,
m_isConnected(false),
m_isPresenting(false),
m_isValidDeviceForPresenting(true),
+ m_framesPending(0),
m_canUpdateFramePose(true),
+ m_canSubmitFramePose(false),
m_capabilities(new VRDisplayCapabilities()),
m_eyeParametersLeft(new VREyeParameters()),
m_eyeParametersRight(new VREyeParameters()),
m_depthNear(0.01),
m_depthFar(10000.0),
- m_fullscreenCheckTimer(this, &VRDisplay::onFullscreenCheck),
m_contextGL(nullptr),
m_animationCallbackRequested(false),
m_inAnimationFrame(false),
+ m_surfaceHandle(0),
m_display(std::move(display)),
- m_binding(this, std::move(request)) {}
+ m_binding(this, std::move(request)),
+ m_pose_client_binding(this) {
+ VLOG(1) << __FUNCTION__ << ": CONSTRUCTOR this=" << (void*)this << " m_display=" << m_display << " ***********************************************************************************************************";
+}
-VRDisplay::~VRDisplay() {}
+VRDisplay::~VRDisplay() {
+ VLOG(1) << __FUNCTION__ << ": DESTRUCTOR this=" << (void*)this << " m_display=" << m_display << " ***********************************************************************************************************";
+}
VRController* VRDisplay::controller() {
return m_navigatorVR->controller();
}
void VRDisplay::update(const device::mojom::blink::VRDisplayInfoPtr& display) {
+ VLOG(1) << __FUNCTION__ << ": displayName=" << display->displayName;
m_displayId = display->index;
m_displayName = display->displayName;
m_isConnected = true;
@@ -118,6 +175,7 @@ void VRDisplay::update(const device::mojom::blink::VRDisplayInfoPtr& display) {
// Ignore non presenting delegate
bool isValid = display->leftEye->renderWidth > 0;
bool needOnPresentChange = false;
+ VLOG(1) << __FUNCTION__ << ": m_isPresenting=" << m_isPresenting << " isValid=" << isValid << " m_isValidDeviceForPresenting=" << m_isValidDeviceForPresenting;
if (m_isPresenting && isValid && !m_isValidDeviceForPresenting) {
needOnPresentChange = true;
}
@@ -136,15 +194,27 @@ void VRDisplay::update(const device::mojom::blink::VRDisplayInfoPtr& display) {
if (needOnPresentChange) {
OnPresentChange();
}
+ VLOG(1) << __FUNCTION__ << ": done";
}
void VRDisplay::disconnected() {
+ VLOG(1) << __FUNCTION__;
if (m_isConnected)
m_isConnected = !m_isConnected;
}
bool VRDisplay::getFrameData(VRFrameData* frameData) {
+ TRACE_EVENT1("media", "klausw:getFrameData", "frame", m_framePose ? m_framePose->poseIndex + 1 : -1);
+ double get_pose_ms = getMonotonicTimestampMs();
updatePose();
+ if (m_framePose) {
+ double got_pose_ms = getMonotonicTimestampMs();
+ // TODO(klausw): why do these show 0.1ms deltas while updateFrame shows 1ms+?
+ if (m_framePose->ts_getPose == 0.0) {
+ m_framePose->ts_getPose = get_pose_ms;
+ m_framePose->ts_gotPose = got_pose_ms;
+ }
+ }
if (!m_framePose)
return false;
@@ -155,35 +225,88 @@ bool VRDisplay::getFrameData(VRFrameData* frameData) {
if (m_depthNear == m_depthFar)
return false;
- return frameData->update(m_framePose, m_eyeParametersLeft,
- m_eyeParametersRight, m_depthNear, m_depthFar);
+ bool ret = frameData->update(m_framePose, m_eyeParametersLeft,
+ m_eyeParametersRight, m_depthNear, m_depthFar);
+ VLOG_IF(2, m_framePose) << __FUNCTION__ << ": frame " << m_framePose->poseIndex << ", recommended eye renderWidth/Height=" << m_eyeParametersLeft->renderWidth() << "x" << m_eyeParametersLeft->renderHeight();
+
+ return ret;
}
VRPose* VRDisplay::getPose() {
+ TRACE_EVENT1("media", "klausw:getPose", "next frame ", m_framePose ? m_framePose->poseIndex + 1 : -1);
+ double get_pose_ms = getMonotonicTimestampMs();
updatePose();
-
- if (!m_framePose)
+ double got_pose_ms = getMonotonicTimestampMs();
+ if (!m_framePose) {
+ VLOG(2) << __FUNCTION__ << ": no pose for next frame ";
return nullptr;
+ }
+
+ if (m_framePose->ts_getPose == 0.0) {
+ m_framePose->ts_getPose = get_pose_ms;
+ m_framePose->ts_gotPose = got_pose_ms;
+ }
VRPose* pose = VRPose::create();
pose->setPose(m_framePose);
+ VLOG(2) << __FUNCTION__ << ": next frame " << m_framePose->poseIndex;
return pose;
}
+void VRDisplay::requestPose() {
+ VLOG(2) << __FUNCTION__;
+ m_poseCallbackPending = true;
+ m_display->GetPose(m_pose_client_binding.CreateInterfacePtrAndBind());
+}
+
+void VRDisplay::OnPoseReceived(device::mojom::blink::VRPosePtr pose) {
+ VLOG(2) << __FUNCTION__;
+ m_framePose = std::move(pose);
+ m_poseCallbackPending = false;
+}
+
void VRDisplay::updatePose() {
+ VLOG(2) << __FUNCTION__ << ": display=" << m_display << " canUpdateFramePose=" << m_canUpdateFramePose;
if (m_displayBlurred) {
// WebVR spec says to return a null pose when the display is blurred.
m_framePose = nullptr;
return;
}
- if (m_canUpdateFramePose) {
+ // If a pose was prefetched and is complete, do nothing.
+ // If prefetch is still pending, wait for it to complete.
+ // If no prefetch was started, start it and wait for result.
+ if (m_canUpdateFramePose || m_poseCallbackPending) {
if (!m_display)
return;
device::mojom::blink::VRPosePtr pose;
- m_display->GetPose(&pose);
- m_framePose = std::move(pose);
- if (m_isPresenting)
+ TRACE_EVENT1("media", "klausw:updatePose", "frame", m_framePose ? m_framePose->poseIndex + 1 : -1);
+ double get_pose_ms = getMonotonicTimestampMs();
+ if (!m_poseCallbackPending) {
+ requestPose();
+ }
+ while (m_poseCallbackPending) {
+ {VLOG(2) << __FUNCTION__ << ": wait for pose";}
+ if (!m_pose_client_binding.WaitForIncomingMethodCall()) {
+ LOG(ERROR) << __FUNCTION__ << ": failed to receive a pose";
+ }
+ }
+ if (!m_framePose) {
+ VLOG(2) << __FUNCTION__ << ": did not get a pose";
+ return;
+ }
+ double got_pose_ms = getMonotonicTimestampMs();
+ m_canSubmitFramePose = true;
+ VLOG(2) << __FUNCTION__ << ": updatePose to " << (m_framePose ? m_framePose->poseIndex : -1) << " took " << got_pose_ms - get_pose_ms << "ms";
+ // For this newly fetched frame, zero the timers and let getPose
+ // or getFrameData update it. This way we can ensure that we
+ // measure the actual JS pause time one time, and don't overwrite
+ // it for redundant calls. If we prefetch the pose, the underlying
+ // fetch time wouldn't matter.
+ m_framePose->ts_getPose = 0.0;
+ m_framePose->ts_gotPose = 0.0;
+ if (m_isPresenting) {
m_canUpdateFramePose = false;
+ }
}
}
@@ -197,8 +320,10 @@ void VRDisplay::resetPose() {
VREyeParameters* VRDisplay::getEyeParameters(const String& whichEye) {
switch (stringToVREye(whichEye)) {
case VREyeLeft:
+ VLOG(1) << __FUNCTION__ << ": left renderWidth/Height=" << m_eyeParametersLeft->renderWidth() << "x" << m_eyeParametersLeft->renderHeight();
return m_eyeParametersLeft;
case VREyeRight:
+ VLOG(1) << __FUNCTION__ << ": right renderWidth/Height=" << m_eyeParametersRight->renderWidth() << "x" << m_eyeParametersRight->renderHeight();
return m_eyeParametersRight;
default:
return nullptr;
@@ -206,6 +331,7 @@ VREyeParameters* VRDisplay::getEyeParameters(const String& whichEye) {
}
int VRDisplay::requestAnimationFrame(FrameRequestCallback* callback) {
+ TRACE_EVENT1("media", "klausw:rAF", "frame", m_framePose ? m_framePose->poseIndex : -1);
Document* doc = this->document();
if (!doc)
return 0;
@@ -226,6 +352,7 @@ void VRDisplay::cancelAnimationFrame(int id) {
}
void VRDisplay::OnBlur() {
+ VLOG(1) << __FUNCTION__;
m_displayBlurred = true;
m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
@@ -233,6 +360,7 @@ void VRDisplay::OnBlur() {
}
void VRDisplay::OnFocus() {
+ VLOG(1) << __FUNCTION__;
m_displayBlurred = false;
// Restart our internal doc requestAnimationFrame callback, if it fired while
// the display was blurred.
@@ -249,9 +377,108 @@ void VRDisplay::OnFocus() {
EventTypeNames::vrdisplayfocus, true, false, this, ""));
}
-void VRDisplay::serviceScriptedAnimations(double monotonicAnimationStartTime) {
+#if 0
+static void delayTest() {
+ VLOG(2) << __FUNCTION__ << ": delay got executed";
+}
+#endif
+
+
+ // TODO(klausw): refactor:
+ // replace serviceScriptedAnimations with new callback chain sequence:
+ // | tick getPose service_rAF | tick
+ // | 0ms 4ms 8ms 16.7ms| 0ms
+ //
+ // // Save tickStart at VRDisplayFrameRequestCallback.handleEvent?
+ // timerTick
+ // tickStart=now()
+ // postDelayedTask(getPoseForFrame, poseOffset)
+ // getPoseForFrame
+ // getPose // TODO: getPoseAsync(gotPoseCallback), then next step in gotPoseCallback
+ // postDelayedTask(service_rAF_offset - (now() - tickStart))
+
+
+void VRDisplay::frameTick(double highResTimeMs) {
+ m_tickIsScheduled = false;
if (!m_scriptedAnimationController)
return;
+ TRACE_EVENT1("media", "frameTick", "frame", m_framePose ? m_framePose->poseIndex + 1 : -1);
+
+#if 0
+ Platform::current()->currentThread()->getWebTaskRunner()->postDelayedTask(
+ BLINK_FROM_HERE, WTF::bind(&delayTest), 8.0);
+#endif
+
+ double timeNow = getMonotonicTimestampMs();
+ m_frameTickStartMs = timeNow;
+ m_highResTimeMsAtLastFrameTick = highResTimeMs;
+
+ VLOG(2) << __FUNCTION__ << ": next frame " << (m_framePose ? m_framePose->poseIndex + 1 : -1) << " pending=" << m_framesPending;
+ bool skipThisTick = false;
+ if (m_framesPending > 1) {
+ LOG(ERROR) << __FUNCTION__ << ": SHOULD NOT HAPPEN: too many frames pending, want <=1, have " << m_framesPending;
+ skipThisTick = true;
+ } else if (m_frameWaitingToSubmit) {
+ VLOG(2) << __FUNCTION__ << ": have frame waiting to be submitted";
+ // Don't pump the timer loop, that will happen when the pending frame completes.
+ return;
+ } else if (m_framesPending > 0 &&
+ m_historyPreSubmitTimeMs.hasPrediction() &&
+ m_historyPostSubmitTimeMs.hasPrediction()) {
+#if 0
+ double expectedRenderTime = m_historyPostSubmitTimeMs.getPrediction();
+ double expectedFinishRender = m_submitExecuteMs + expectedRenderTime - timeNow;
+ // Simple logic - skip this frame if remaining rendering will take
+ // more than one frame.
+ if (expectedFinishRender > frameTime + 5.0) skipThisTick = true;
+ VLOG(2) << __FUNCTION__ << ": expectedRenderTime=" <<
+ expectedRenderTime << " expectedFinishRender=" <<
+ expectedFinishRender << " frameTime=" << frameTime << " skip=" <<
+ skipThisTick;
+#endif
+#if 0
+ // Alternate attempt - try to throttle to 30fps if not hitting 60fps.
+ // Doesn't work great at this point, needs further tuning and testing.
+ double allowedTime = frameTime + 5.0;
+ double expectedSubmit = m_historyPreSubmitTimeMs.getPrediction();
+ if (expectedFinishRender + expectedSubmit > allowedTime) skipThisTick = true;
+ VLOG(2) << __FUNCTION__ << ": expectedRenderTime=" << expectedRenderTime << " expectedFinishRender=" << expectedFinishRender << " expectedSubmit=" << expectedSubmit << " frameTime=" << frameTime << " skip=" << skipThisTick;
+#endif
+ }
+ {
+ double frameTimeMs = 1000.0 / 60; // TODO(klausw): measure instead of assuming 60fps.
+ if (timeNow - m_submitExecuteMs < frameTimeMs / 2) {
+ // Last submission was less than half a frame ago.
+ skipThisTick = true;
+ VLOG(2) << __FUNCTION__ << ": last submit was " << timeNow - m_submitExecuteMs << "ms ago, skip=" << skipThisTick;
+ }
+ }
+ if (skipThisTick) {
+ VLOG(2) << __FUNCTION__ << ": SKIP FRAME, pending=" << m_framesPending;
+ rescheduleAtNextTick();
+ return;
+ }
+
+ serviceScriptedAnimations();
+}
+
+void VRDisplay::rescheduleAtNextTick() {
+ if (m_tickIsScheduled) return;
+ m_tickIsScheduled = true;
+
+ Document* doc = m_navigatorVR->document();
+ if (!doc) {
+ VLOG(2) << __FUNCTION__ << ": cannot skip frame, no document?!";
+ return;
+ }
+ doc->requestAnimationFrame(new VRDisplayFrameRequestCallback(this));
+}
+
+void VRDisplay::serviceScriptedAnimations() {
+ if (!m_scriptedAnimationController)
+ return;
+ TRACE_EVENT1("media", "serviceScriptedAnimations", "frame", m_framePose ? m_framePose->poseIndex + 1 : -1);
+
AutoReset<bool> animating(&m_inAnimationFrame, true);
m_animationCallbackRequested = false;
@@ -261,8 +488,34 @@ void VRDisplay::serviceScriptedAnimations(double monotonicAnimationStartTime) {
// so we don't fire the user's callback until the display is focused.
if (m_displayBlurred)
return;
+
+ m_serviceStartMs = getMonotonicTimestampMs();
+
+ // Adjust high res timer since some time may have passed since the frame tick.
+ double highResTimeMs = m_highResTimeMsAtLastFrameTick +
+ m_serviceStartMs - m_frameTickStartMs;
+
+ if (m_isPresenting && m_canUpdateFramePose) {
+ // Prefetch pose asynchronously to avoid waiting for it.
+ // TODO(klausw): align its timing so that it arrives just
+ // in time depending on when JS usually asks for it.
+ m_canUpdateFramePose = false;
+ requestPose();
+ }
+
+ Document* doc = m_navigatorVR->document();
+ if (!doc || !doc->loader()) {
+ VLOG(2) << __FUNCTION__ << ": no document";
+ return;
+ }
+ // Need to divide by 1000 here because serviceScriptedAnimations expects
+ // time to be given in seconds.
+ double monotonicAnimationStartTime =
+ doc->loader()->timing().pseudoWallTimeToMonotonicTime(highResTimeMs /
+ 1000.0);
m_scriptedAnimationController->serviceScriptedAnimations(
monotonicAnimationStartTime);
+ m_tickIsScheduled = true;
}
void ReportPresentationResult(PresentationResult result) {
@@ -278,6 +531,7 @@ void ReportPresentationResult(PresentationResult result) {
ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
const HeapVector<VRLayer>& layers) {
+ VLOG(1) << __FUNCTION__;
ExecutionContext* executionContext = scriptState->getExecutionContext();
UseCounter::count(executionContext, UseCounter::VRRequestPresent);
if (!executionContext->isSecureContext()) {
@@ -293,6 +547,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
// If the VRDisplay does not advertise the ability to present reject the
// request.
if (!m_capabilities->canPresent()) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: VRDisplay cannot present";
DOMException* exception =
DOMException::create(InvalidStateError, "VRDisplay cannot present.");
resolver->reject(exception);
@@ -306,16 +561,21 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
// If the VRDisplay is already presenting, however, repeated calls are
// allowed outside a user gesture so that the presented content may be
// updated.
+#define HACK_DISABLE_USER_GESTURE_REQUIREMENT 0
+#if !HACK_DISABLE_USER_GESTURE_REQUIREMENT
if (firstPresent && !UserGestureIndicator::utilizeUserGesture()) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: API can only be initiated by a user gesture";
DOMException* exception = DOMException::create(
InvalidStateError, "API can only be initiated by a user gesture.");
resolver->reject(exception);
ReportPresentationResult(PresentationResult::NotInitiatedByUserGesture);
return promise;
}
+#endif
// A valid number of layers must be provided in order to present.
if (layers.size() == 0 || layers.size() > m_capabilities->maxLayers()) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: Invalid number of layers";
forceExitPresent();
DOMException* exception =
DOMException::create(InvalidStateError, "Invalid number of layers.");
@@ -327,6 +587,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
m_layer = layers[0];
if (!m_layer.source()) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: Invalid layer source";
forceExitPresent();
DOMException* exception =
DOMException::create(InvalidStateError, "Invalid layer source.");
@@ -337,8 +598,13 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
CanvasRenderingContext* renderingContext =
m_layer.source()->renderingContext();
+ if (firstPresent) {
+ m_sourceWidth = m_layer.source()->width();
+ m_sourceHeight = m_layer.source()->height();
+ }
if (!renderingContext || !renderingContext->is3d()) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: Layer source must have a WebGLRenderingContext";
forceExitPresent();
DOMException* exception = DOMException::create(
InvalidStateError, "Layer source must have a WebGLRenderingContext");
@@ -355,6 +621,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
if ((m_layer.leftBounds().size() != 0 && m_layer.leftBounds().size() != 4) ||
(m_layer.rightBounds().size() != 0 &&
m_layer.rightBounds().size() != 4)) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: Layer bounds must either be an empty array or have 4 values";
forceExitPresent();
DOMException* exception = DOMException::create(
InvalidStateError,
@@ -372,6 +639,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
} else if (firstPresent) {
bool secureContext = scriptState->getExecutionContext()->isSecureContext();
if (!m_display) {
+ VLOG(1) << __FUNCTION__ << ": REJECT: The service is no longer active";
forceExitPresent();
DOMException* exception = DOMException::create(
InvalidStateError, "The service is no longer active.");
@@ -384,6 +652,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
&VRDisplay::onPresentComplete,
wrapPersistent(this))));
} else {
+ m_isPresenting = true;
updateLayerBounds();
resolver->resolve();
ReportPresentationResult(PresentationResult::SuccessAlreadyPresenting);
@@ -393,6 +662,7 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState,
}
void VRDisplay::onPresentComplete(bool success) {
+ VLOG(1) << __FUNCTION__;
if (success) {
this->beginPresent();
} else {
@@ -408,6 +678,7 @@ void VRDisplay::onPresentComplete(bool success) {
}
ScriptPromise VRDisplay::exitPresent(ScriptState* scriptState) {
+ VLOG(1) << __FUNCTION__;
ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState);
ScriptPromise promise = resolver->promise();
@@ -435,6 +706,7 @@ ScriptPromise VRDisplay::exitPresent(ScriptState* scriptState) {
}
void VRDisplay::beginPresent() {
+ VLOG(1) << __FUNCTION__;
Document* doc = this->document();
std::unique_ptr<UserGestureIndicator> gestureIndicator;
if (m_capabilities->hasExternalDisplay()) {
@@ -450,48 +722,6 @@ void VRDisplay::beginPresent() {
PresentationResult::PresentationNotSupportedByDisplay);
return;
} else {
- // TODO(klausw,crbug.com/655722): Need a proper VR compositor, but
- // for the moment on mobile we'll just make the canvas fullscreen
- // so that VrShell can pick it up through the standard (high
- // latency) compositing path.
- auto canvas = m_layer.source();
- auto inlineStyle = canvas->inlineStyle();
- if (inlineStyle) {
- // THREE.js's VREffect sets explicit style.width/height on its rendering
- // canvas based on the non-fullscreen window dimensions, and it keeps
- // those unchanged when presenting. Unfortunately it appears that a
- // fullscreened canvas just gets centered if it has explicitly set a
- // size smaller than the fullscreen dimensions. Manually set size to
- // 100% in this case and restore it when exiting fullscreen. This is a
- // stopgap measure since THREE.js's usage appears legal according to the
- // WebVR API spec. This will no longer be necessary once we can get rid
- // of this fullscreen hack.
- m_fullscreenOrigWidth = inlineStyle->getPropertyValue(CSSPropertyWidth);
- if (!m_fullscreenOrigWidth.isNull()) {
- canvas->setInlineStyleProperty(CSSPropertyWidth, "100%");
- }
- m_fullscreenOrigHeight = inlineStyle->getPropertyValue(CSSPropertyHeight);
- if (!m_fullscreenOrigHeight.isNull()) {
- canvas->setInlineStyleProperty(CSSPropertyHeight, "100%");
- }
- } else {
- m_fullscreenOrigWidth = String();
- m_fullscreenOrigHeight = String();
- }
-
- if (doc) {
- // Since the callback for requestPresent is asynchronous, we've lost our
- // UserGestureToken, and need to create a new one to enter fullscreen.
- gestureIndicator = WTF::wrapUnique(
- new UserGestureIndicator(DocumentUserGestureToken::create(
- doc, UserGestureToken::Status::PossiblyExistingGesture)));
- }
- Fullscreen::requestFullscreen(*canvas);
-
- // Check to see if the canvas is still the current fullscreen
- // element once every 2 seconds.
- m_fullscreenCheckTimer.startRepeating(2.0, BLINK_FROM_HERE);
- m_reenteredFullscreen = false;
}
if (doc) {
@@ -499,6 +729,14 @@ void VRDisplay::beginPresent() {
WebURL(doc->url()));
}
+ // Stop unneeded compositor updates. We do so by hiding the view. We can't
+ // hide the page, doing so causes an assertion failure (!m_isHidden) in
+ // DrawingBuffer::prepareTextureMailboxInternal(). Do this only when we're
+ // actually presenting (m_isPresenting is true), see corresponding show()
+ // in forceExitPresent(). Otherwise the view may remain hidden for failing
+ // DON flow.
+ m_navigatorVR->document()->view()->hide();
+
m_isPresenting = true;
ReportPresentationResult(PresentationResult::Success);
@@ -512,29 +750,30 @@ void VRDisplay::beginPresent() {
}
void VRDisplay::forceExitPresent() {
+ VLOG(1) << __FUNCTION__;
+ if (m_surfaceHandle) {
+ m_renderingContext->setSurfaceHandle(0);
+ m_surfaceHandle = 0;
+ }
+
if (m_isPresenting) {
- if (!m_capabilities->hasExternalDisplay()) {
- auto canvas = m_layer.source();
- Fullscreen::fullyExitFullscreen(canvas->document());
- m_fullscreenCheckTimer.stop();
- if (!m_fullscreenOrigWidth.isNull()) {
- canvas->setInlineStyleProperty(CSSPropertyWidth, m_fullscreenOrigWidth);
- m_fullscreenOrigWidth = String();
- }
- if (!m_fullscreenOrigHeight.isNull()) {
- canvas->setInlineStyleProperty(CSSPropertyWidth,
- m_fullscreenOrigHeight);
- m_fullscreenOrigHeight = String();
- }
- } else {
- // Can't get into this presentation mode, so nothing to do here.
- }
m_isPresenting = false;
+ m_canUpdateFramePose = true;
OnPresentChange();
+ m_navigatorVR->document()->view()->show();
}
m_renderingContext = nullptr;
m_contextGL = nullptr;
+ m_contextProvider = nullptr;
+
+ VLOG(2) << __FUNCTION__ << ": lost surface, reset m_framesPending " << m_framesPending << " => " << 0;
+ m_framesPending = 0;
+ if (m_frameWaitingToSubmit) {
+ m_frameWaitingToSubmit = false;
+ // Make sure not to lose a rAF call if we ignore a pending frame.
+ serviceScriptedAnimations();
+ }
}
void VRDisplay::updateLayerBounds() {
@@ -547,6 +786,15 @@ void VRDisplay::updateLayerBounds() {
device::mojom::blink::VRLayerBoundsPtr rightBounds =
device::mojom::blink::VRLayerBounds::New();
+ // For which pose should these new bounds take effect?
+ // This depends on if the new layer bounds were applied
+ // before or after updating the pose.
+ uint32_t forPoseIndex = m_framePose ? m_framePose->poseIndex : 0;
+ if (m_canUpdateFramePose) ++forPoseIndex;
+
+ leftBounds->forPoseIndex = forPoseIndex;
+ rightBounds->forPoseIndex = forPoseIndex;
+
if (m_layer.leftBounds().size() == 4) {
leftBounds->left = m_layer.leftBounds()[0];
leftBounds->top = m_layer.leftBounds()[1];
@@ -573,6 +821,25 @@ void VRDisplay::updateLayerBounds() {
rightBounds->height = 1.0f;
}
+#ifdef HACK_PSEUDOSCALING
+ // TODO(klausw): this assumes that the eyes are arranged left to right with no gaps.
+ int recWidth = m_eyeParametersLeft->renderWidth() + m_eyeParametersRight->renderWidth();
+ int recHeight = m_eyeParametersLeft->renderHeight();
+ if (m_sourceWidth != recWidth || m_sourceHeight != recHeight) {
+ VLOG(1) << __FUNCTION__ << ": resize " << m_sourceWidth << "x" << m_sourceHeight << " to " << recWidth << "x" << recHeight;
+ double scaleX = (double)m_sourceWidth / recWidth;
+ double scaleY = (double)m_sourceHeight / recHeight;
+ leftBounds->left *= scaleX;
+ leftBounds->top *= scaleY;
+ leftBounds->width *= scaleX;
+ leftBounds->height *= scaleY;
+ rightBounds->left *= scaleX;
+ rightBounds->top *= scaleY;
+ rightBounds->width *= scaleX;
+ rightBounds->height *= scaleY;
+ }
+#endif
+
m_display->UpdateLayerBounds(std::move(leftBounds), std::move(rightBounds));
}
@@ -586,20 +853,26 @@ HeapVector<VRLayer> VRDisplay::getLayers() {
return layers;
}
-void VRDisplay::submitFrame() {
- if (!m_display)
- return;
-
- Document* doc = this->document();
- if (!m_isPresenting) {
- if (doc) {
- doc->addConsoleMessage(ConsoleMessage::create(
- RenderingMessageSource, WarningMessageLevel,
- "submitFrame has no effect when the VRDisplay is not presenting."));
- }
- return;
+gpu::gles2::GLES2Interface* VRDisplay::getCompositingContext() {
+ if (!m_contextProvider) {
+ m_contextProvider = WTF::wrapUnique(
+ Platform::current()->createSharedOffscreenGraphicsContext3DProvider());
}
+ gpu::gles2::GLES2Interface* sharedContext = nullptr;
+ if (m_contextProvider) {
+ sharedContext = m_contextProvider->contextGL();
+
+ if (!sharedContext)
+ return nullptr;
+ }
+
+ return sharedContext;
+}
+
+
+void VRDisplay::submitFrame() {
+ Document* doc = this->document();
if (!m_inAnimationFrame) {
if (doc) {
doc->addConsoleMessage(
@@ -610,44 +883,234 @@ void VRDisplay::submitFrame() {
return;
}
+ if (!m_isPresenting) {
+ if (doc) {
+ doc->addConsoleMessage(ConsoleMessage::create(
+ RenderingMessageSource, WarningMessageLevel,
+ "submitFrame has no effect when the VRDisplay is not presenting."));
+ }
+ return;
+ }
+
+ if (!m_canSubmitFramePose) {
+ if (doc) {
+ doc->addConsoleMessage(ConsoleMessage::create(
+ RenderingMessageSource, WarningMessageLevel,
+ "submitFrame rejected, this pose was already used."));
+ m_canUpdateFramePose = true;
+ }
+ return;
+ }
+
+ submitFrameAnyContext();
+}
+
+void VRDisplay::submitFrameAnyContext() {
+ TRACE_EVENT1("media", "klausw:submitFrame", "frame", m_framePose->poseIndex);
+
+ if (!m_display)
+ return;
+
if (!m_contextGL) {
// Something got confused, we can't submit frames without a GL context.
return;
}
- // Write the frame number for the pose used into a bottom left pixel block.
- // It is read by chrome/browser/android/vr_shell/vr_shell.cc to associate
- // the correct corresponding pose for submission.
+ if (m_framePose) {
+ m_framePose->ts_frameStart = m_serviceStartMs;
+ m_framePose->ts_submit = getMonotonicTimestampMs();
+ }
+
+ GLenum error;
+ (void)error;
+
+#define DRAW_FRAME_COUNTER_PIXEL_FOR_DEBUGGING 0
+#if DRAW_FRAME_COUNTER_PIXEL_FOR_DEBUGGING
auto gl = m_contextGL;
-
- // We must ensure that the WebGL app's GL state is preserved. We do this by
- // calling low-level GL commands directly so that the rendering context's
- // saved parameters don't get overwritten.
-
gl->Enable(GL_SCISSOR_TEST);
- // Use a few pixels to ensure we get a clean color. The resolution for the
- // WebGL buffer may not match the final rendered destination size, and
- // texture filtering could interfere for single pixels. This isn't visible
- // since the final rendering hides the edges via a vignette effect.
- gl->Scissor(0, 0, 4, 4);
+ gl->Scissor(0, 0, 200, 200);
gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
int idx = m_framePose->poseIndex;
// Careful with the arithmetic here. Float color 1.f is equivalent to int 255.
- // Use the low byte of the index as the red component, and store an arbitrary
- // magic number in green/blue. This number must match the reading code in
- // vr_shell.cc. Avoid all-black/all-white.
- gl->ClearColor((idx & 255) / 255.0f, kWebVrPosePixelMagicNumbers[0] / 255.0f,
- kWebVrPosePixelMagicNumbers[1] / 255.0f, 1.0f);
+ gl->ClearColor((idx & 255) / 255.0f, ((idx >> 8) & 255) / 255.0f,
+ ((idx >> 16) & 255) / 255.0f, 1.0f);
gl->Clear(GL_COLOR_BUFFER_BIT);
-
// Set the GL state back to what was set by the WebVR application.
m_renderingContext->restoreScissorEnabled();
m_renderingContext->restoreScissorBox();
m_renderingContext->restoreColorMask();
m_renderingContext->restoreClearColor();
+#endif
- m_display->SubmitFrame(m_framePose.Clone());
+ bool needSurfaceHandle = !m_surfaceHandle;
+
+ int currentWidth = m_layer.source()->width();
+ int currentHeight = m_layer.source()->height();
+ if (currentWidth != m_sourceWidth || currentHeight != m_sourceHeight) {
+ VLOG(1) << __FUNCTION__ << ": source size changed from " <<
+ m_sourceWidth << "x" << m_sourceHeight <<
+ " to " <<
+ currentWidth << "x" << currentHeight <<
+ ", m_surfaceHandleRequested=" << m_surfaceHandleRequested <<
+ ", m_surfaceHandle=" << m_surfaceHandle;
+ if (m_surfaceHandleRequested) {
+ // We can't currently request a new surface, a request is in flight.
+ // Just do nothing, try again next iteration.
+ VLOG(2) << __FUNCTION__ << ": do nothing, request in flight";
+ } else {
+ m_sourceWidth = currentWidth;
+ m_sourceHeight = currentHeight;
+ needSurfaceHandle = true;
+ }
+ }
+
+ if (needSurfaceHandle) {
+ if (!m_surfaceHandleRequested) {
+ auto callback = convertToBaseCallback(WTF::bind(
+ &VRDisplay::onGetSurfaceHandleComplete, wrapPersistent(this)));
+ m_surfaceHandleRequested = true;
+ m_display->GetSurfaceHandle(m_sourceWidth, m_sourceHeight, callback);
+ VLOG(2) << __FUNCTION__ << ": requesting surface handle for size " <<
+ m_sourceWidth << "x" << m_sourceHeight;
+ }
+ VLOG(2) << __FUNCTION__ << ": no surface available yet, discarding frame";
+ m_canUpdateFramePose = true;
+ return;
+ }
+
+ auto framePose = m_framePose.Clone();
+ if (!framePose) {
+ VLOG(2) << __FUNCTION__ << ": no pose!";
+ m_canUpdateFramePose = true;
+ return;
+ }
+
+ auto poseIdx = framePose->poseIndex;
+
+ assert(m_surfaceHandle != 0);
+ assert(poseIdx != 0);
+
+ if (m_framesPending > 0) {
+ VLOG(1) << __FUNCTION__ << ": m_framesPending=" << m_framesPending << ", deferring frame " << poseIdx;
+ m_frameWaitingToSubmit = true;
+ return;
+ }
+
+ // From this point on we're committed to calling SwapBuffers and submitting
+ // the frame. Don't do any early return from this point onward.
+ VLOG(2) << __FUNCTION__ << ": decided to submitFrame, have pose for frame " << poseIdx;
+
+ m_submitExecuteMs = getMonotonicTimestampMs();
+ m_historyPreSubmitTimeMs.add(m_submitExecuteMs - m_serviceStartMs);
+
+ // This is a load-bearing glFlush(). Removing it breaks rendering. WTF.
+ m_contextGL->Flush();
+ m_contextGL->SwapBuffers();
+ // Use glFinish here to avoid excessive wait-for-completion in
+ // the single-threaded CrBrowserMain message handler. TODO(klausw):
+ // use a fence instead?
+ //m_contextGL->Finish();
+
+ auto callback = convertToBaseCallback(WTF::bind(
+ &VRDisplay::onSubmitFrameComplete, wrapPersistent(this)));
+
+ // Update pending frames, we have a surface handle.
+ VLOG(2) << __FUNCTION__ << ": m_framesPending " << m_framesPending << " => " << m_framesPending + 1;
+ ++m_framesPending;
+
+ m_display->SubmitFrame(m_surfaceHandle, std::move(framePose), callback);
+ m_canSubmitFramePose = false; // Illegal to reuse the same pose twice.
m_canUpdateFramePose = true;
+ VLOG(2) << __FUNCTION__ << ": submit done for frame " << poseIdx;
+}
+
+void VRDisplay::onGetSurfaceHandleComplete(int32_t surfaceHandle) {
+ VLOG(1) << __FUNCTION__ << ": VRDisplay new surface handle=" << surfaceHandle;
+ m_surfaceHandleRequested = false;
+
+ if (m_surfaceHandle) {
+ // We have a surface, disconnect it so that the reconnect
+ // below resizes it.
+ //
+ // TODO(klausw): simplify this, would be nice to do a call
+ // to m_renderingContext->setSurfaceHandle(current_handle)
+ // would do an in-place resize. See
+ // SurfaceTexture.setDefaultBufferSize documentation:
+ //
+ // For OpenGL ES, the EGLSurface should be destroyed
+ // (via eglDestroySurface), made not-current (via
+ // eglMakeCurrent), and then recreated (via
+ // eglCreateWindowSurface) to ensure that the new
+ // default size has taken effect.
+ VLOG(2) << __FUNCTION__ << ": zero current surface handle to resize";
+ m_renderingContext->setSurfaceHandle(0);
+ m_surfaceHandle = 0;
+ }
+
+ // TODO(klausw): special-case in-place resize?
+ m_renderingContext->setSurfaceHandle(surfaceHandle);
+ m_surfaceHandle = surfaceHandle;
+
+ // Reset stats counters since a new resolution may have
+ // very different performance.
+ m_historyPreSubmitTimeMs.clear();
+ m_historyPostSubmitTimeMs.clear();
+ updateLayerBounds();
+}
+
+void VRDisplay::onSubmitFrameComplete(int32_t surfaceHandle, uint32_t poseIndex, double renderMs) {
+ TRACE_EVENT1("media", "klausw:onSubmitFrameComplete", "frame", poseIndex);
+ VLOG(2) << __FUNCTION__ << ": surface " << surfaceHandle << ", frame " << poseIndex << ", render time " << renderMs << "ms";
+ if (!surfaceHandle) {
+ VLOG(2) << __FUNCTION__ << ": Ignoring callback for invalid surface 0";
+ return;
+ }
+ if (surfaceHandle != m_surfaceHandle) {
+ VLOG(1) << __FUNCTION__ << ": Ignoring callback, was for surface " << surfaceHandle << " which is not the current surface " << m_surfaceHandle;
+ return;
+ }
+
+ // Update pending frames, the callback matches our active surface handle.
+ VLOG(2) << __FUNCTION__ << ": m_framesPending " << m_framesPending << " => " << m_framesPending - 1;
+ --m_framesPending; // should now be == 0
+
+ m_historyPostSubmitTimeMs.add(getMonotonicTimestampMs() - m_submitExecuteMs);
+
+ if (m_frameWaitingToSubmit) {
+ VLOG(2) << __FUNCTION__ << ": ready to submit deferred frame " << m_framePose->poseIndex;
+ submitFrameAnyContext();
+ // m_framesPending is now == 1
+ m_frameWaitingToSubmit = false;
+
+ // Run next rAF now, don't wait for next timer tick.
+ // TODO(klausw): try to stay tied to vsync and run at 30fps
+ // if not keeping up?
+ bool waitForNextTick = false;
+ if (m_historyPreSubmitTimeMs.hasPrediction()) {
+ // a . . b . . c . . d . . e . . f . . g
+ // | | | | | |
+ double avgSubmitDuration = m_historyPreSubmitTimeMs.getPrediction();
+ double frameTimeMs = 1000.0 / 60; // TODO(klausw): measure instead of assuming 60fps.
+ double nextTickWaitMs = m_frameTickStartMs + frameTimeMs - m_submitExecuteMs;
+ if (avgSubmitDuration < frameTimeMs && nextTickWaitMs > 0 /* && nextTickWaitMs < frameTimeMs / 2 */) {
+ // Last submit took less than a frame, and the next tick is soon. Wait for it to avoid exceeding 60fps.
+ VLOG(2) << __FUNCTION__ << ": avg submit took " << avgSubmitDuration <<
+ "ms, next tick in " << nextTickWaitMs <<
+ "ms. reschedule at next tick after frame " << m_framePose->poseIndex;
+ waitForNextTick = true;
+ } else {
+ VLOG(2) << __FUNCTION__ << ": avg submit took " << avgSubmitDuration <<
+ "ms, next tick in " << nextTickWaitMs <<
+ "ms. reschedule now after frame " << m_framePose->poseIndex;
+ }
+ }
+ if (waitForNextTick) {
+ rescheduleAtNextTick();
+ } else {
+ // We're backlogged. Run new frame immediately.
+ serviceScriptedAnimations();
+ }
+ }
}
Document* VRDisplay::document() {
@@ -655,6 +1118,7 @@ Document* VRDisplay::document() {
}
void VRDisplay::OnPresentChange() {
+ VLOG(1) << __FUNCTION__;
if (m_isPresenting && !m_isValidDeviceForPresenting) {
VLOG(1) << __FUNCTION__ << ": device not valid, not sending event";
return;
@@ -664,70 +1128,40 @@ void VRDisplay::OnPresentChange() {
}
void VRDisplay::OnChanged(device::mojom::blink::VRDisplayInfoPtr display) {
+ VLOG(1) << __FUNCTION__;
update(display);
}
void VRDisplay::OnExitPresent() {
+ VLOG(1) << __FUNCTION__;
forceExitPresent();
}
void VRDisplay::onConnected() {
+ VLOG(1) << __FUNCTION__;
m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
EventTypeNames::vrdisplayconnect, true, false, this, "connect"));
}
void VRDisplay::onDisconnected() {
+ VLOG(1) << __FUNCTION__;
m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
EventTypeNames::vrdisplaydisconnect, true, false, this, "disconnect"));
}
void VRDisplay::OnActivate(device::mojom::blink::VRDisplayEventReason reason) {
+ VLOG(1) << __FUNCTION__;
m_navigatorVR->dispatchVRGestureEvent(VRDisplayEvent::create(
EventTypeNames::vrdisplayactivate, true, false, this, reason));
}
void VRDisplay::OnDeactivate(
device::mojom::blink::VRDisplayEventReason reason) {
+ VLOG(1) << __FUNCTION__;
m_navigatorVR->enqueueVREvent(VRDisplayEvent::create(
EventTypeNames::vrdisplaydeactivate, true, false, this, reason));
}
-void VRDisplay::onFullscreenCheck(TimerBase*) {
- if (!m_isPresenting) {
- m_fullscreenCheckTimer.stop();
- return;
- }
- // TODO: This is a temporary measure to track if fullscreen mode has been
- // exited by the UA. If so we need to end VR presentation. Soon we won't
- // depend on the Fullscreen API to fake VR presentation, so this will
- // become unnessecary. Until that point, though, this seems preferable to
- // adding a bunch of notification plumbing to Fullscreen.
- if (!Fullscreen::isFullscreenElement(*m_layer.source())) {
- // TODO(mthiesse): Due to asynchronous resizing, we might get kicked out of
- // fullscreen when changing display parameters upon entering WebVR. So one
- // time only, we reenter fullscreen after having left it; otherwise we exit
- // presentation.
- if (m_reenteredFullscreen) {
- m_isPresenting = false;
- OnPresentChange();
- m_fullscreenCheckTimer.stop();
- if (m_display)
- m_display->ExitPresent();
- return;
- }
- m_reenteredFullscreen = true;
- auto canvas = m_layer.source();
- Document* doc = this->document();
- std::unique_ptr<UserGestureIndicator> gestureIndicator;
- if (doc) {
- gestureIndicator = WTF::wrapUnique(
- new UserGestureIndicator(DocumentUserGestureToken::create(
- doc, UserGestureToken::Status::PossiblyExistingGesture)));
- }
- Fullscreen::requestFullscreen(*canvas);
- }
-}
-
ScriptedAnimationController& VRDisplay::ensureScriptedAnimationController(
Document* doc) {
if (!m_scriptedAnimationController)

Powered by Google App Engine
This is Rietveld 408576698