Chromium Code Reviews| 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 cc8f1038aae5d967f3865cc8b9a1881194dcb3d9..43cb8f5b41a92b9cb93f6818908c71c8eeb96cc8 100644 |
| --- a/third_party/WebKit/Source/modules/vr/VRDisplay.cpp |
| +++ b/third_party/WebKit/Source/modules/vr/VRDisplay.cpp |
| @@ -8,11 +8,14 @@ |
| #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/dom/TaskRunnerHelper.h" |
| +#include "core/frame/FrameView.h" |
| +#include "core/frame/ImageBitmap.h" |
| #include "core/frame/UseCounter.h" |
| #include "core/inspector/ConsoleMessage.h" |
| +#include "core/layout/LayoutView.h" |
| +#include "core/layout/compositing/PaintLayerCompositor.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "modules/EventTargetModules.h" |
| @@ -27,6 +30,7 @@ |
| #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 "wtf/Time.h" |
| @@ -37,10 +41,6 @@ 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; |
| @@ -59,12 +59,8 @@ VRDisplay::VRDisplay(NavigatorVR* navigatorVR, |
| m_capabilities(new VRDisplayCapabilities()), |
| m_eyeParametersLeft(new VREyeParameters()), |
| m_eyeParametersRight(new VREyeParameters()), |
| - m_fullscreenCheckTimer( |
| - TaskRunnerHelper::get(TaskType::UnspecedTimer, |
| - navigatorVR->document()->frame()), |
| - this, |
| - &VRDisplay::onFullscreenCheck), |
| m_display(std::move(display)), |
| + m_submit_frame_client_binding(this), |
| m_displayClientBinding(this, std::move(request)) {} |
| VRDisplay::~VRDisplay() {} |
| @@ -239,17 +235,6 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState, |
| return promise; |
| } |
| - // TODO(mthiesse): Remove fullscreen requirement for presentation. See |
| - // crbug.com/687369 |
| - Document* doc = this->document(); |
| - if (!doc || !Fullscreen::fullscreenEnabled(*doc)) { |
| - DOMException* exception = |
| - DOMException::create(InvalidStateError, "Fullscreen is not enabled."); |
| - resolver->reject(exception); |
| - ReportPresentationResult(PresentationResult::FullscreenNotEnabled); |
| - return promise; |
| - } |
| - |
| // A valid number of layers must be provided in order to present. |
| if (layers.size() == 0 || layers.size() > m_capabilities->maxLayers()) { |
| forceExitPresent(); |
| @@ -272,6 +257,9 @@ ScriptPromise VRDisplay::requestPresent(ScriptState* scriptState, |
| } |
| m_layer = layers[0]; |
| + if (firstPresent) { |
|
bajones
2017/03/07 00:48:07
if (firstPresent) { /* Do nothing. I just like bra
klausw
2017/03/07 02:55:55
Oops, removed.
|
| + } |
| + |
| CanvasRenderingContext* renderingContext; |
| if (m_layer.source().isHTMLCanvasElement()) { |
| renderingContext = |
| @@ -395,50 +383,15 @@ void VRDisplay::beginPresent() { |
| return; |
| } else { |
| if (m_layer.source().isHTMLCanvasElement()) { |
| - HTMLCanvasElement* canvas = m_layer.source().getAsHTMLCanvasElement(); |
| - // 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().getAsHTMLCanvasElement(); |
| - 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; |
| + // 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. |
| + auto doc = m_navigatorVR->document(); |
| + doc->view()->hide(); |
| + doc->layoutView()->compositor()->setCompositingModeEnabled(false); |
| } else { |
| DCHECK(m_layer.source().isOffscreenCanvas()); |
| // TODO(junov, crbug.com/695497): Implement OffscreenCanvas presentation |
| @@ -519,7 +472,8 @@ void VRDisplay::updateLayerBounds() { |
| } |
| m_display->UpdateLayerBounds(m_vrFrameId, std::move(leftBounds), |
| - std::move(rightBounds)); |
| + std::move(rightBounds), m_sourceWidth, |
| + m_sourceHeight); |
| } |
| HeapVector<VRLayer> VRDisplay::getLayers() { |
| @@ -535,6 +489,7 @@ HeapVector<VRLayer> VRDisplay::getLayers() { |
| void VRDisplay::submitFrame() { |
| if (!m_display) |
| return; |
| + TRACE_EVENT1("gpu", "submitFrame", "frame", m_vrFrameId); |
| Document* doc = this->document(); |
| if (!m_isPresenting) { |
| @@ -563,42 +518,129 @@ void VRDisplay::submitFrame() { |
| // No frame Id to write before submitting the frame. |
| if (m_vrFrameId < 0) { |
| - m_display->SubmitFrame(m_framePose.Clone()); |
| + // TODO(klausw): There used to be a submitFrame here, but we can't |
| + // submit without a frameId and associated pose data. Just drop it. |
| 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. |
| - auto gl = m_contextGL; |
| + m_contextGL->Flush(); |
| + auto elem = m_layer.source(); |
| - // 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. |
| + // Check if the canvas got resized, if yes send a bounds update. |
| + int currentWidth = m_renderingContext->drawingBufferWidth(); |
| + int currentHeight = m_renderingContext->drawingBufferHeight(); |
| + if ((currentWidth != m_sourceWidth || currentHeight != m_sourceHeight) && |
| + currentWidth != 0 && currentHeight != 0) { |
| + m_sourceWidth = currentWidth; |
| + m_sourceHeight = currentHeight; |
| + updateLayerBounds(); |
| + } |
| - 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->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| - // 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((m_vrFrameId & 255) / 255.0f, |
| - kWebVrPosePixelMagicNumbers[0] / 255.0f, |
| - kWebVrPosePixelMagicNumbers[1] / 255.0f, 1.0f); |
| - gl->Clear(GL_COLOR_BUFFER_BIT); |
| + // There's two types of synchronization needed for submitting frames: |
| + // |
| + // - Before submitting, need to wait for the previous frame to be |
| + // pulled off the transfer surface to avoid lost frames. This |
| + // is currently a compile-time option, normally we always want |
| + // to defer this wait to increase parallelism. |
| + // |
| + // - After submitting, need to wait for the mailbox to be consumed, |
| + // and must remain inside the execution context while waiting. |
| + // The waitForPreviousTransferToFinish option defers this wait |
| + // until the next frame. That's more efficient, but seems to |
| + // cause wobbly framerates. |
| + bool waitForPreviousTransferToFinish = |
| + RuntimeEnabledFeatures::webVRExperimentalRenderingEnabled(); |
| - // 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(); |
| + if (waitForPreviousTransferToFinish) { |
| + TRACE_EVENT0("gpu", "waitPrevXfer"); |
| + while (m_pendingSubmitFrame) { |
| + if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) { |
| + LOG(ERROR) << "Failed to receive SubmitFrame response"; |
| + break; |
| + } |
| + } |
| + } |
| - m_display->SubmitFrame(m_framePose.Clone()); |
| + auto image_ref = m_renderingContext->getImage( |
| + PreferAcceleration, SnapshotReasonCreateImageBitmap); |
| + |
| + // Hardware-accelerated rendering should always be texture backed. |
| + // I hope nobody is trying to do WebVR with software rendering. |
| + DCHECK(image_ref->isTextureBacked()); |
| + |
| + // The AcceleratedStaticBitmapImage must be kept alive until the |
| + // mailbox is used via createAndConsumeTextureCHROMIUM, the mailbox |
| + // itself does not keep it alive. We must keep a reference to the |
| + // image until the mailbox was consumed. |
| + StaticBitmapImage* staticImage = |
| + static_cast<StaticBitmapImage*>(image_ref.get()); |
| + staticImage->ensureMailbox(); |
| + auto mailbox = staticImage->mailbox(); |
| + |
| + if (waitForPreviousTransferToFinish) { |
| + // Save a reference to the image to keep it alive until next frame, |
| + // where we'll wait for the transfer to finish before overwriting |
| + // it. |
| + m_previousImage = std::move(image_ref); |
| + } |
| + |
| + // Wait for the previous render to finish, to avoid losing frames in the |
| + // Android Surface / GLConsumer pair. TODO(klausw): make this tunable? |
| + // Other devices may have different preferences. |
| + { |
| + TRACE_EVENT0("gpu", "waitPrevRender"); |
| + while (m_pendingPreviousFrameRender) { |
| + if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) { |
| + LOG(ERROR) << "Failed to receive SubmitFrame response"; |
| + break; |
| + } |
| + } |
| + TRACE_EVENT_END0("gpu", "waitPrevRender"); |
| + } |
| + |
| + m_pendingPreviousFrameRender = true; |
| + m_pendingSubmitFrame = true; |
| + |
| +#define USE_MAILBOX_TYPEMAP_WORKAROUND 1 |
| +#if USE_MAILBOX_TYPEMAP_WORKAROUND |
| + // Temporary workaround for being unable to use the mojo typemap |
| + // for the mailbox type. See added comment in blink_typemaps.gni. |
| + |
| + // Convert mailbox to a mojo type manually. |
| + auto mailbox_ptr = gpu::mojom::blink::Mailbox::New(); |
| + WTF::Vector<int8_t> tmpname(GL_MAILBOX_SIZE_CHROMIUM); |
| + memcpy(&tmpname[0], &mailbox.name[0], GL_MAILBOX_SIZE_CHROMIUM); |
| + mailbox_ptr->name = std::move(tmpname); |
| + |
| + m_display->SubmitFrame( |
| + m_vrFrameId, std::move(mailbox_ptr), staticImage->syncToken(), |
| + m_submit_frame_client_binding.CreateInterfacePtrAndBind()); |
| +#else |
| + m_display->SubmitFrame( |
| + m_vrFrameId, mailbox, staticImage->syncToken(), |
| + m_submit_frame_client_binding.CreateInterfacePtrAndBind()); |
| +#endif |
| + |
| + // If we're not deferring the wait for transferring the mailbox, |
| + // we need to wait for it now to prevent the image going out of |
| + // scope before its mailbox is retrieved. |
| + if (!waitForPreviousTransferToFinish) { |
| + TRACE_EVENT0("gpu", "waitCurrXfer"); |
| + while (m_pendingSubmitFrame) { |
| + if (!m_submit_frame_client_binding.WaitForIncomingMethodCall()) { |
| + LOG(ERROR) << "Failed to receive SubmitFrame response"; |
| + break; |
| + } |
| + } |
| + } |
| +} |
| + |
| +void VRDisplay::OnSubmitFrameTransferred() { |
| + m_pendingSubmitFrame = false; |
| +} |
| + |
| +void VRDisplay::OnSubmitFrameRendered() { |
| + m_pendingPreviousFrameRender = false; |
| } |
| Document* VRDisplay::document() { |
| @@ -636,19 +678,13 @@ void VRDisplay::stopPresenting() { |
| if (m_isPresenting) { |
| if (!m_capabilities->hasExternalDisplay()) { |
| if (m_layer.source().isHTMLCanvasElement()) { |
| - auto canvas = m_layer.source().getAsHTMLCanvasElement(); |
| - 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(); |
| - } |
| + // TODO(klausw): Is there a need to restore the original |
| + // hide/show state and compositing mode? AFAIK there's no way |
| + // to enter presentation without being visible and compositing, |
| + // so we'd always restore to these values. |
| + auto doc = m_navigatorVR->document(); |
| + doc->view()->show(); |
| + doc->layoutView()->compositor()->setCompositingModeEnabled(true); |
| } else { |
| // TODO(junov, crbug.com/695497): Implement for OffscreenCanvas |
| } |
| @@ -661,6 +697,8 @@ void VRDisplay::stopPresenting() { |
| m_renderingContext = nullptr; |
| m_contextGL = nullptr; |
| + m_pendingSubmitFrame = false; |
| + m_pendingPreviousFrameRender = false; |
| } |
| void VRDisplay::OnActivate(device::mojom::blink::VRDisplayEventReason reason) { |
| @@ -723,44 +761,6 @@ void VRDisplay::ConnectVSyncProvider() { |
| } |
| } |
| -void VRDisplay::onFullscreenCheck(TimerBase*) { |
| - DCHECK(m_layer.source().isHTMLCanvasElement()); |
| - 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::isCurrentFullScreenElement( |
| - *m_layer.source().getAsHTMLCanvasElement())) { |
| - // 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().getAsHTMLCanvasElement(); |
| - 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) |