void VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, PTextureParent* aTexture, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) { // Suppress layers hidden by the group mask return; } TextureHost* th = TextureHost::AsTextureHost(aTexture); // WebVR doesn't use the compositor to compose the frame, so use // AutoLockTextureHostWithoutCompositor here. AutoLockTextureHostWithoutCompositor autoLock(th); if (autoLock.Failed()) { NS_WARNING("Failed to lock the VR layer texture"); return; } CompositableTextureSourceRef source; if (!th->BindTextureSource(source)) { NS_WARNING("The TextureHost was successfully locked but can't provide a TextureSource"); return; } MOZ_ASSERT(source); IntSize texSize = source->GetSize(); TextureSourceD3D11* sourceD3D11 = source->AsSourceD3D11(); if (!sourceD3D11) { NS_WARNING("WebVR support currently only implemented for D3D11"); return; } if (!SubmitFrame(sourceD3D11, texSize, aLeftEyeRect, aRightEyeRect)) { return; } /** * Trigger the next VSync immediately after we are successfully * submitting frames. As SubmitFrame is responsible for throttling * the render loop, if we don't successfully call it, we shouldn't trigger * NotifyVRVsync immediately, as it will run unbounded. * If NotifyVRVsync is not called here due to SubmitFrame failing, the * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause * frames to continue at a lower refresh rate until frame submission * succeeds again. */ VRManager *vm = VRManager::Get(); MOZ_ASSERT(vm); vm->NotifyVRVsync(mDisplayInfo.mDisplayID); }
void VRDisplayOpenVR::SubmitFrame(TextureSourceD3D11* aSource, const IntSize& aSize, const VRHMDSensorState& aSensorState, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { if (!mIsPresenting) { return; } ::vr::Texture_t tex; tex.handle = (void *)aSource->GetD3D11Texture(); tex.eType = ::vr::EGraphicsAPIConvention::API_DirectX; tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto; ::vr::VRTextureBounds_t bounds; bounds.uMin = aLeftEyeRect.x; bounds.vMin = 1.0 - aLeftEyeRect.y; bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width; bounds.vMax = 1.0 - aLeftEyeRect.y - aLeftEyeRect.height; ::vr::EVRCompositorError err; err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds); if (err != ::vr::EVRCompositorError::VRCompositorError_None) { printf_stderr("OpenVR Compositor Submit() failed.\n"); } bounds.uMin = aRightEyeRect.x; bounds.vMin = 1.0 - aRightEyeRect.y; bounds.uMax = aRightEyeRect.x + aRightEyeRect.width; bounds.vMax = 1.0 - aRightEyeRect.y - aRightEyeRect.height; err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds); if (err != ::vr::EVRCompositorError::VRCompositorError_None) { printf_stderr("OpenVR Compositor Submit() failed.\n"); } mVRCompositor->PostPresentHandoff(); // Trigger the next VSync immediately VRManager *vm = VRManager::Get(); MOZ_ASSERT(vm); vm->NotifyVRVsync(mDisplayInfo.mDisplayID); }
void VRDisplayPuppet::SubmitFrame(TextureSourceOGL* aSource, const IntSize& aSize, const VRHMDSensorState& aSensorState, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { if (!mIsPresenting) { return; } // TODO: Bug 1343730, Need to block until the next simulated // vblank interval and capture frames for use in reftests. // Trigger the next VSync immediately VRManager *vm = VRManager::Get(); MOZ_ASSERT(vm); vm->NotifyVRVsync(mDisplayInfo.mDisplayID); }
void VRDisplayOculus::SubmitFrame(TextureSourceD3D11* aSource, const IntSize& aSize, const VRHMDSensorState& aSensorState, const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { if (!mIsPresenting) { return; } if (mRenderTargets.IsEmpty()) { /** * XXX - We should resolve fail the promise returned by * VRDisplay.requestPresent() when the DX11 resources fail allocation * in VRDisplayOculus::StartPresentation(). * Bailing out here prevents the crash but content should be aware * that frames are not being presented. * See Bug 1299309. **/ return; } MOZ_ASSERT(mDevice); MOZ_ASSERT(mContext); RefPtr<CompositingRenderTargetD3D11> surface = GetNextRenderTarget(); surface->BindRenderTarget(mContext); Matrix viewMatrix = Matrix::Translation(-1.0, 1.0); viewMatrix.PreScale(2.0f / float(aSize.width), 2.0f / float(aSize.height)); viewMatrix.PreScale(1.0f, -1.0f); Matrix4x4 projection = Matrix4x4::From2D(viewMatrix); projection._33 = 0.0f; Matrix transform2d; gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d); D3D11_VIEWPORT viewport; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.Width = aSize.width; viewport.Height = aSize.height; viewport.TopLeftX = 0; viewport.TopLeftY = 0; D3D11_RECT scissor; scissor.left = 0; scissor.right = aSize.width; scissor.top = 0; scissor.bottom = aSize.height; memcpy(&mVSConstants.layerTransform, &transform._11, sizeof(mVSConstants.layerTransform)); memcpy(&mVSConstants.projection, &projection._11, sizeof(mVSConstants.projection)); mVSConstants.renderTargetOffset[0] = 0.0f; mVSConstants.renderTargetOffset[1] = 0.0f; mVSConstants.layerQuad = Rect(0.0f, 0.0f, aSize.width, aSize.height); mVSConstants.textureCoords = Rect(0.0f, 1.0f, 1.0f, -1.0f); mPSConstants.layerOpacity[0] = 1.0f; ID3D11Buffer* vbuffer = mVertexBuffer; UINT vsize = sizeof(Vertex); UINT voffset = 0; mContext->IASetVertexBuffers(0, 1, &vbuffer, &vsize, &voffset); mContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0); mContext->IASetInputLayout(mInputLayout); mContext->RSSetViewports(1, &viewport); mContext->RSSetScissorRects(1, &scissor); mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); mContext->VSSetShader(mQuadVS, nullptr, 0); mContext->PSSetShader(mQuadPS, nullptr, 0); ID3D11ShaderResourceView* srView = aSource->GetShaderResourceView(); mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &srView); // XXX Use Constant from TexSlot in CompositorD3D11.cpp? ID3D11SamplerState *sampler = mLinearSamplerState; mContext->PSSetSamplers(0, 1, &sampler); if (!UpdateConstantBuffers()) { NS_WARNING("Failed to update constant buffers for Oculus"); return; } mContext->Draw(4, 0); ovrResult orv = ovr_CommitTextureSwapChain(mSession, mTextureSet); if (orv != ovrSuccess) { NS_WARNING("ovr_CommitTextureSwapChain failed.\n"); return; } ovrLayerEyeFov layer; memset(&layer, 0, sizeof(layer)); layer.Header.Type = ovrLayerType_EyeFov; layer.Header.Flags = 0; layer.ColorTexture[0] = mTextureSet; layer.ColorTexture[1] = nullptr; layer.Fov[0] = mFOVPort[0]; layer.Fov[1] = mFOVPort[1]; layer.Viewport[0].Pos.x = aSize.width * aLeftEyeRect.x; layer.Viewport[0].Pos.y = aSize.height * aLeftEyeRect.y; layer.Viewport[0].Size.w = aSize.width * aLeftEyeRect.width; layer.Viewport[0].Size.h = aSize.height * aLeftEyeRect.height; layer.Viewport[1].Pos.x = aSize.width * aRightEyeRect.x; layer.Viewport[1].Pos.y = aSize.height * aRightEyeRect.y; layer.Viewport[1].Size.w = aSize.width * aRightEyeRect.width; layer.Viewport[1].Size.h = aSize.height * aRightEyeRect.height; const Point3D& l = mDisplayInfo.mEyeTranslation[0]; const Point3D& r = mDisplayInfo.mEyeTranslation[1]; const ovrVector3f hmdToEyeViewOffset[2] = { { l.x, l.y, l.z }, { r.x, r.y, r.z } }; for (uint32_t i = 0; i < 2; ++i) { Quaternion o(aSensorState.orientation[0], aSensorState.orientation[1], aSensorState.orientation[2], aSensorState.orientation[3]); Point3D vo(hmdToEyeViewOffset[i].x, hmdToEyeViewOffset[i].y, hmdToEyeViewOffset[i].z); Point3D p = o.RotatePoint(vo); layer.RenderPose[i].Orientation.x = o.x; layer.RenderPose[i].Orientation.y = o.y; layer.RenderPose[i].Orientation.z = o.z; layer.RenderPose[i].Orientation.w = o.w; layer.RenderPose[i].Position.x = p.x + aSensorState.position[0]; layer.RenderPose[i].Position.y = p.y + aSensorState.position[1]; layer.RenderPose[i].Position.z = p.z + aSensorState.position[2]; } ovrLayerHeader *layers = &layer.Header; orv = ovr_SubmitFrame(mSession, aSensorState.inputFrameID, nullptr, &layers, 1); if (orv != ovrSuccess) { printf_stderr("ovr_SubmitFrame failed.\n"); } // Trigger the next VSync immediately VRManager *vm = VRManager::Get(); MOZ_ASSERT(vm); vm->NotifyVRVsync(mDisplayInfo.mDisplayID); }
void VRDisplayHost::NotifyVSync() { /** * We will trigger a new frame immediately after a successful frame texture * submission. If content fails to call VRDisplay.submitFrame after * kVRDisplayRAFMaxDuration milliseconds has elapsed since the last * VRDisplay.requestAnimationFrame, we act as a "watchdog" and kick-off * a new VRDisplay.requestAnimationFrame to avoid a render loop stall and * to give content a chance to recover. * * If the lower level VR platform API's are rejecting submitted frames, * such as when the Oculus "Health and Safety Warning" is displayed, * we will not kick off the next frame immediately after VRDisplay.submitFrame * as it would result in an unthrottled render loop that would free run at * potentially extreme frame rates. To ensure that content has a chance to * resume its presentation when the frames are accepted once again, we rely * on this "watchdog" to act as a VR refresh driver cycling at a rate defined * by kVRDisplayRAFMaxDuration. * * kVRDisplayRAFMaxDuration is the number of milliseconds since last frame * start before triggering a new frame. When content is failing to submit * frames on time or the lower level VR platform API's are rejecting frames, * kVRDisplayRAFMaxDuration determines the rate at which RAF callbacks * will be called. * * This number must be larger than the slowest expected frame time during * normal VR presentation, but small enough not to break content that * makes assumptions of reasonably minimal VSync rate. * * The slowest expected refresh rate for a VR display currently is an * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz. * A kVRDisplayRAFMaxDuration value of 50 milliseconds results in a 20hz * rate, which avoids inadvertent triggering of the watchdog during * Oculus ASW even if every second frame is dropped. */ const double kVRDisplayRAFMaxDuration = 50; bool bShouldStartFrame = false; if (mDisplayInfo.mPresentingGroups == 0) { // If this display isn't presenting, refresh the sensors and trigger // VRDisplay.requestAnimationFrame at the normal 2d display refresh rate. bShouldStartFrame = true; } else { // If content fails to call VRDisplay.submitFrame, we must eventually // time-out and trigger a new frame. if (mLastFrameStart.IsNull()) { bShouldStartFrame = true; } else { TimeDuration duration = TimeStamp::Now() - mLastFrameStart; if (duration.ToMilliseconds() > kVRDisplayRAFMaxDuration) { bShouldStartFrame = true; } } } if (bShouldStartFrame) { VRManager *vm = VRManager::Get(); MOZ_ASSERT(vm); vm->NotifyVRVsync(mDisplayInfo.mDisplayID); } }