Bug 1343730 - Part 1: Support submitFrame and encode the frame as a base64 image in VRPuppet; r?kip draft
authorDaosheng Mu <daoshengmu@gmail.com>
Tue, 23 May 2017 16:55:30 +0800
changeset 583416 2cb9d263607ada53dbb080e5c4754482d0cd181c
parent 582257 9851fcb0bf4d855c36729d7de19f0fa5c9f69776
child 583417 804c4cd811822b1fa89267f9b122126fc94964b9
child 583573 2f4e59665f5d8d15025cb7633ccda63b9cb0ab8c
push id60387
push userbmo:dmu@mozilla.com
push dateWed, 24 May 2017 01:53:59 +0000
reviewerskip
bugs1343730
milestone55.0a1
Bug 1343730 - Part 1: Support submitFrame and encode the frame as a base64 image in VRPuppet; r?kip MozReview-Commit-ID: jHKHSoNo6X
gfx/thebes/gfxPrefs.h
gfx/vr/VRDisplayClient.cpp
gfx/vr/VRDisplayClient.h
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/gfxVR.h
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
gfx/vr/ipc/VRMessageUtils.h
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -352,16 +352,17 @@ private:
   DECL_GFX_PREF(Live, "dom.vr.controller_trigger_threshold",   VRControllerTriggerThreshold, float, 0.1f);
   DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.require-gesture",                VRRequireGesture, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.puppet.submitframe",             VRPuppetSubmitFrame, uint32_t, 0);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.w3c_touch_events.enabled",          TouchEventsEnabled, int32_t, 0);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
   DECL_GFX_PREF(Live, "general.smoothScroll.currentVelocityWeighting",
                 SmoothScrollCurrentVelocityWeighting, float, 0.25);
   DECL_GFX_PREF(Live, "general.smoothScroll.durationToIntervalRatio",
                 SmoothScrollDurationToIntervalRatio, int32_t, 200);
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -143,8 +143,20 @@ VRDisplayClient::GetIsPresenting() const
   return mDisplayInfo.GetIsPresenting();
 }
 
 void
 VRDisplayClient::NotifyDisconnected()
 {
   mDisplayInfo.mIsConnected = false;
 }
+
+void
+VRDisplayClient::UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult)
+{
+  mSubmitFrameResult = aResult;
+}
+
+void
+VRDisplayClient::GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult)
+{
+  aResult = mSubmitFrameResult;
+}
\ No newline at end of file
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -21,19 +21,21 @@ class VRManagerChild;
 class VRDisplayClient
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayClient)
 
   explicit VRDisplayClient(const VRDisplayInfo& aDisplayInfo);
 
   void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo);
+  void UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult);
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
   virtual VRHMDSensorState GetSensorState();
+  void GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult);
 
   virtual void ZeroSensor();
 
   already_AddRefed<VRDisplayPresentation> BeginPresentation(const nsTArray<dom::VRLayer>& aLayers);
   void PresentationDestroyed();
 
   void NotifyVsync();
   void NotifyVRVsync();
@@ -48,14 +50,17 @@ protected:
 
   VRDisplayInfo mDisplayInfo;
 
   bool bLastEventWasMounted;
   bool bLastEventWasPresenting;
 
   TimeStamp mLastVSyncTime;
   int mPresentationCount;
+
+private:
+  VRSubmitFrameResultInfo mSubmitFrameResult;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_CLIENT_H */
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -463,10 +463,18 @@ VRManager::StopVibrateHaptic(uint32_t aC
 void
 VRManager::NotifyVibrateHapticCompleted(uint32_t aPromiseID)
 {
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     Unused << iter.Get()->GetKey()->SendReplyGamepadVibrateHaptic(aPromiseID);
   }
 }
 
+void
+VRManager::DispatchSubmitFrameResult(uint32_t aDisplayID, const VRSubmitFrameResultInfo& aResult)
+{
+  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
+    Unused << iter.Get()->GetKey()->SendDispatchSubmitFrameResult(aDisplayID, aResult);
+  }
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -48,16 +48,17 @@ public:
                    const gfx::Rect& aRightEyeRect);
   RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
   void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
   void CreateVRTestSystem();
   void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                      double aIntensity, double aDuration, uint32_t aPromiseID);
   void StopVibrateHaptic(uint32_t aControllerIdx);
   void NotifyVibrateHapticCompleted(uint32_t aPromiseID);
+  void DispatchSubmitFrameResult(uint32_t aDisplayID, const VRSubmitFrameResultInfo& aResult);
 
 protected:
   VRManager();
   ~VRManager();
 
 private:
   RefPtr<layers::TextureHost> mLastFrame;
 
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -204,16 +204,31 @@ struct VRHMDSensorState {
   float linearVelocity[3];
   float linearAcceleration[3];
 
   void Clear() {
     memset(this, 0, sizeof(VRHMDSensorState));
   }
 };
 
+struct VRSubmitFrameResultInfo
+{
+  VRSubmitFrameResultInfo()
+   : mFrameNum(0),
+     mWidth(0),
+     mHeight(0)
+  {}
+
+  nsCString mBase64Image;
+  SurfaceFormat mFormat;
+  uint32_t mFrameNum;
+  uint32_t mWidth;
+  uint32_t mHeight;
+};
+
 struct VRControllerInfo
 {
   VRDeviceType GetType() const { return mType; }
   uint32_t GetControllerID() const { return mControllerID; }
   const nsCString& GetControllerName() const { return mControllerName; }
   dom::GamepadMappingType GetMappingType() const { return mMappingType; }
   dom::GamepadHand GetHand() const { return mHand; }
   uint32_t GetNumButtons() const { return mNumButtons; }
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -1,29 +1,41 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if defined(XP_WIN)
 #include "CompositorD3D11.h"
 #include "TextureD3D11.h"
+#include "mozilla/gfx/DeviceManagerDx.h"
 #endif // XP_WIN
 
+#include "mozilla/Base64.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "gfxUtils.h"
 #include "gfxVRPuppet.h"
 
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/dom/GamepadBinding.h"
 
+// See CompositorD3D11Shaders.h
+namespace mozilla {
+namespace layers {
+struct ShaderBytes { const void* mData; size_t mLength; };
+extern ShaderBytes sRGBShader;
+extern ShaderBytes sLayerQuadVS;
+} // namespace layers
+} // namespace mozilla
+
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::gfx::impl;
 using namespace mozilla::layers;
 
-
 // Reminder: changing the order of these buttons may break web content
 static const uint64_t kPuppetButtonMask[] = {
   1,
   2,
   4,
   8
 };
 
@@ -39,16 +51,17 @@ static const uint32_t kPuppetAxes[] = {
 static const uint32_t kNumPuppetAxis = sizeof(kPuppetAxes) /
                                        sizeof(uint32_t);
 
 static const uint32_t kNumPuppetHaptcs = 1;
 
 VRDisplayPuppet::VRDisplayPuppet()
  : VRDisplayHost(VRDeviceType::Puppet)
  , mIsPresenting(false)
+ , mFrameNum(0)
 {
   MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
 
   mDisplayInfo.mDisplayName.AssignLiteral("Puppet HMD");
   mDisplayInfo.mIsConnected = true;
   mDisplayInfo.mIsMounted = false;
   mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
                                   VRDisplayCapabilityFlags::Cap_Orientation |
@@ -158,50 +171,300 @@ VRDisplayPuppet::SetSensorState(const VR
 
 void
 VRDisplayPuppet::StartPresentation()
 {
   if (mIsPresenting) {
     return;
   }
   mIsPresenting = true;
+
+#if defined(XP_WIN)
+  if (!mDevice) {
+    mDevice = gfx::DeviceManagerDx::Get()->GetCompositorDevice();
+    if (!mDevice) {
+      NS_WARNING("Failed to get a D3D11Device for Puppet");
+      return;
+    }
+  }
+
+  mDevice->GetImmediateContext(getter_AddRefs(mContext));
+  if (!mContext) {
+    NS_WARNING("Failed to get immediate context for Puppet");
+    return;
+  }
+
+  if (FAILED(mDevice->CreateVertexShader(sLayerQuadVS.mData,
+                      sLayerQuadVS.mLength, nullptr, &mQuadVS))) {
+    NS_WARNING("Failed to create vertex shader for Puppet");
+    return;
+  }
+
+  if (FAILED(mDevice->CreatePixelShader(sRGBShader.mData,
+                      sRGBShader.mLength, nullptr, &mQuadPS))) {
+    NS_WARNING("Failed to create pixel shader for Puppet");
+    return;
+  }
+
+  CD3D11_BUFFER_DESC cBufferDesc(sizeof(layers::VertexShaderConstants),
+                                 D3D11_BIND_CONSTANT_BUFFER,
+                                 D3D11_USAGE_DYNAMIC,
+                                 D3D11_CPU_ACCESS_WRITE);
+
+  if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mVSConstantBuffer)))) {
+    NS_WARNING("Failed to vertex shader constant buffer for Puppet");
+    return;
+  }
+
+  cBufferDesc.ByteWidth = sizeof(layers::PixelShaderConstants);
+  if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, getter_AddRefs(mPSConstantBuffer)))) {
+    NS_WARNING("Failed to pixel shader constant buffer for Puppet");
+    return;
+  }
+
+  CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT);
+  if (FAILED(mDevice->CreateSamplerState(&samplerDesc, getter_AddRefs(mLinearSamplerState)))) {
+    NS_WARNING("Failed to create sampler state for Puppet");
+    return;
+  }
+
+  D3D11_INPUT_ELEMENT_DESC layout[] =
+  {
+    { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
+  };
+
+  if (FAILED(mDevice->CreateInputLayout(layout,
+                                        sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC),
+                                        sLayerQuadVS.mData,
+                                        sLayerQuadVS.mLength,
+                                        getter_AddRefs(mInputLayout)))) {
+    NS_WARNING("Failed to create input layout for Puppet");
+    return;
+  }
+
+  Vertex vertices[] = { { { 0.0, 0.0 } },{ { 1.0, 0.0 } },{ { 0.0, 1.0 } },{ { 1.0, 1.0 } } };
+  CD3D11_BUFFER_DESC bufferDesc(sizeof(vertices), D3D11_BIND_VERTEX_BUFFER);
+  D3D11_SUBRESOURCE_DATA data;
+  data.pSysMem = (void*)vertices;
+
+  if (FAILED(mDevice->CreateBuffer(&bufferDesc, &data, getter_AddRefs(mVertexBuffer)))) {
+    NS_WARNING("Failed to create vertex buffer for Puppet");
+    return;
+  }
+
+  memset(&mVSConstants, 0, sizeof(mVSConstants));
+  memset(&mPSConstants, 0, sizeof(mPSConstants));
+#endif // XP_WIN
 }
 
 void
 VRDisplayPuppet::StopPresentation()
 {
   if (!mIsPresenting) {
     return;
   }
 
   mIsPresenting = false;
 }
 
 #if defined(XP_WIN)
+bool
+VRDisplayPuppet::UpdateConstantBuffers()
+{
+  HRESULT hr;
+  D3D11_MAPPED_SUBRESOURCE resource;
+  resource.pData = nullptr;
+
+  hr = mContext->Map(mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
+  if (FAILED(hr) || !resource.pData) {
+    return false;
+  }
+  *(VertexShaderConstants*)resource.pData = mVSConstants;
+  mContext->Unmap(mVSConstantBuffer, 0);
+  resource.pData = nullptr;
+
+  hr = mContext->Map(mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource);
+  if (FAILED(hr) || !resource.pData) {
+    return false;
+  }
+  *(PixelShaderConstants*)resource.pData = mPSConstants;
+  mContext->Unmap(mPSConstantBuffer, 0);
+
+  ID3D11Buffer *buffer = mVSConstantBuffer;
+  mContext->VSSetConstantBuffers(0, 1, &buffer);
+  buffer = mPSConstantBuffer;
+  mContext->PSSetConstantBuffers(0, 1, &buffer);
+  return true;
+}
+
 void
 VRDisplayPuppet::SubmitFrame(TextureSourceD3D11* aSource,
                              const IntSize& aSize,
                              const VRHMDSensorState& aSensorState,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
   if (!mIsPresenting) {
     return;
   }
 
-  ID3D11Texture2D* tex = aSource->GetD3D11Texture();
-  MOZ_ASSERT(tex);
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+
+  switch (gfxPrefs::VRPuppetSubmitFrame()) {
+    case 0:
+      // The VR frame is not displayed.
+      break;
+    case 1:
+    {
+      // The frames are submitted to VR compositor are decoded
+      // into a base64Image and dispatched to the DOM side.
+      D3D11_TEXTURE2D_DESC desc;
+      ID3D11Texture2D* texture = aSource->GetD3D11Texture();
+      texture->GetDesc(&desc);
+      DXGI_FORMAT format = desc.Format;
+      // Map the staging resource
+      ID3D11Texture2D* mappedTexture = nullptr;
+      D3D11_MAPPED_SUBRESOURCE mapInfo;
+      HRESULT hr = mContext->Map(texture,
+                                 0,  // Subsource
+                                 D3D11_MAP_READ,
+                                 0,  // MapFlags
+                                 &mapInfo);
+
+      if (FAILED(hr)) {
+        // If we can't map this texture, copy it to a staging resource.
+        if (hr == E_INVALIDARG) {
+          D3D11_TEXTURE2D_DESC desc2;
+          desc2.Width = desc.Width;
+          desc2.Height = desc.Height;
+          desc2.MipLevels = desc.MipLevels;
+          desc2.ArraySize = desc.ArraySize;
+          desc2.Format = desc.Format;
+          desc2.SampleDesc = desc.SampleDesc;
+          desc2.Usage = D3D11_USAGE_STAGING;
+          desc2.BindFlags = 0;
+          desc2.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+          desc2.MiscFlags = 0;
 
-  // TODO: Bug 1343730, Need to block until the next simulated
-  // vblank interval and capture frames for use in reftests.
+          ID3D11Texture2D* stagingTexture = nullptr;
+          hr = mDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
+          if (FAILED(hr)) {
+            MOZ_ASSERT(false, "Failed to create a staging texture");
+            return;
+          }
+          // Copy the texture to a staging resource
+          mContext->CopyResource(stagingTexture, texture);
+          // Map the staging resource
+          hr = mContext->Map(stagingTexture,
+                             0,  // Subsource
+                             D3D11_MAP_READ,
+                             0,  // MapFlags
+                             &mapInfo);
+          if (FAILED(hr)) {
+            MOZ_ASSERT(false, "Failed to map staging texture");
+          }
+          mappedTexture = stagingTexture;
+        }
+        else {
+          MOZ_ASSERT(false, "Failed to map staging texture");
+          return;
+        }
+      } else {
+        mappedTexture = texture;
+      }
+      // Ideally, we should convert the srcData to a PNG image and decode it
+      // to a Base64 string here, but the GPU process does not have the privilege to
+      // access the image library. So, we have to convert the RAW image data
+      // to a base64 string and forward it to let the content process to
+      // do the image conversion.
+      char* srcData = static_cast<char*>(mapInfo.pData);
+      VRSubmitFrameResultInfo result;
+      result.mFormat = SurfaceFormat::B8G8R8A8;
+      result.mWidth = desc.Width;
+      result.mHeight = desc.Height;
+      result.mFrameNum = mFrameNum;
+      nsCString rawString(Substring((char*)srcData, mapInfo.RowPitch * desc.Height));
+
+      if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
+        MOZ_ASSERT(false, "Failed to encode base64 images.");
+      }
+      mContext->Unmap(mappedTexture, 0);
+      // Dispatch the base64 encoded string to the DOM side, and it will be decoded
+      // and convert to a PNG image there.
+      vm->DispatchSubmitFrameResult(mDisplayInfo.mDisplayID, result);
+      break;
+    }
+    case 2:
+    {
+      // The VR compositor sumbmit frame to the screen window,
+      // the current coordinate is at (0, 0, width, height).
+      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);
+
+      const float posX = 0.0f, posY = 0.0f;
+      D3D11_VIEWPORT viewport;
+      viewport.MinDepth = 0.0f;
+      viewport.MaxDepth = 1.0f;
+      viewport.Width = aSize.width;
+      viewport.Height = aSize.height;
+      viewport.TopLeftX = posX;
+      viewport.TopLeftY = posY;
+
+      D3D11_RECT scissor;
+      scissor.left = posX;
+      scissor.right = aSize.width + posX;
+      scissor.top = posY;
+      scissor.bottom = aSize.height + posY;
+
+      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 Puppet");
+        return;
+      }
+      mContext->Draw(4, 0);
+      break;
+    }
+  }
 
   // Trigger the next VSync immediately
-  VRManager *vm = VRManager::Get();
-  MOZ_ASSERT(vm);
   vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+  ++mFrameNum;
 }
 #else
 void
 VRDisplayPuppet::SubmitFrame(TextureSourceOGL* aSource,
                              const IntSize& aSize,
                              const VRHMDSensorState& aSensorState,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
@@ -212,16 +475,17 @@ VRDisplayPuppet::SubmitFrame(TextureSour
 
   // 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);
+  ++mFrameNum;
 }
 #endif
 
 void
 VRDisplayPuppet::NotifyVSync()
 {
   // We update mIsConneced once per frame.
   mDisplayInfo.mIsConnected = true;
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -48,17 +48,34 @@ protected:
   virtual ~VRDisplayPuppet();
   void Destroy();
 
   VRHMDSensorState GetSensorState(double timeOffset);
 
   bool mIsPresenting;
 
 private:
+#if defined(XP_WIN)
+  bool UpdateConstantBuffers();
+
+  RefPtr<ID3D11Device> mDevice;
+  RefPtr<ID3D11DeviceContext> mContext;
+  ID3D11VertexShader* mQuadVS;
+  ID3D11PixelShader* mQuadPS;
+  RefPtr<ID3D11SamplerState> mLinearSamplerState;
+  layers::VertexShaderConstants mVSConstants;
+  layers::PixelShaderConstants mPSConstants;
+  RefPtr<ID3D11Buffer> mVSConstantBuffer;
+  RefPtr<ID3D11Buffer> mPSConstantBuffer;
+  RefPtr<ID3D11Buffer> mVertexBuffer;
+  RefPtr<ID3D11InputLayout> mInputLayout;
+#endif
+
   VRHMDSensorState mSensorState;
+  uint32_t mFrameNum;
 };
 
 class VRControllerPuppet : public VRControllerHost
 {
 public:
   explicit VRControllerPuppet(dom::GamepadHand aHand);
   void SetButtonPressState(uint32_t aButton, bool aPressed);
   uint64_t GetButtonPressState();
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -13,16 +13,17 @@ include GamepadEventTypes;
 
 include "VRMessageUtils.h";
 
 using struct mozilla::gfx::VRFieldOfView from "gfxVR.h";
 using struct mozilla::gfx::VRDisplayInfo from "gfxVR.h";
 using struct mozilla::gfx::VRSensorUpdate from "gfxVR.h";
 using struct mozilla::gfx::VRHMDSensorState from "gfxVR.h";
 using struct mozilla::gfx::VRControllerInfo from "gfxVR.h";
+using struct mozilla::gfx::VRSubmitFrameResultInfo from "gfxVR.h";
 using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h";
 
 
 namespace mozilla {
 namespace gfx {
 
 /**
@@ -77,16 +78,17 @@ child:
   // Notify children of updated VR display enumeration and details.  This will
   // be sent to all children when the parent receives RefreshDisplays, even
   // if no changes have been detected.  This ensures that Promises exposed
   // through DOM calls are always resolved.
   async UpdateDisplayInfo(VRDisplayInfo[] aDisplayUpdates);
 
   async NotifyVSync();
   async NotifyVRVSync(uint32_t aDisplayID);
+  async DispatchSubmitFrameResult(uint32_t aDisplayID, VRSubmitFrameResultInfo aResult);
   async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
   async ReplyGamepadVibrateHaptic(uint32_t aPromiseID);
 
   async ReplyCreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID,
                                         uint32_t aDeviceID);
   async ReplyCreateVRServiceTestController(nsCString aID, uint32_t aPromiseID,
                                            uint32_t aDeviceID);
 
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -713,10 +713,23 @@ VRManagerChild::RecvReplyGamepadVibrateH
     MOZ_CRASH("We should always have a promise.");
   }
 
   p->MaybeResolve(true);
   mGamepadPromiseList.Remove(aPromiseID);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+VRManagerChild::RecvDispatchSubmitFrameResult(const uint32_t& aDisplayID,
+                                              const VRSubmitFrameResultInfo& aResult)
+{
+   for (auto& display : mDisplays) {
+    if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
+      display->UpdateSubmitFrameResult(aResult);
+    }
+  }
+
+  return IPC_OK();
+}
+
 } // namespace gfx
 } // namespace mozilla
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -123,16 +123,17 @@ protected:
   virtual bool DeallocPVRLayerChild(PVRLayerChild* actor) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDisplayInfo(nsTArray<VRDisplayInfo>&& aDisplayUpdates) override;
 
   virtual mozilla::ipc::IPCResult RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override;
 
   virtual mozilla::ipc::IPCResult RecvNotifyVSync() override;
   virtual mozilla::ipc::IPCResult RecvNotifyVRVSync(const uint32_t& aDisplayID) override;
+  virtual mozilla::ipc::IPCResult RecvDispatchSubmitFrameResult(const uint32_t& aDisplayID, const VRSubmitFrameResultInfo& aResult) override;
   virtual mozilla::ipc::IPCResult RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
   virtual mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID) override;
 
   virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestDisplay(const nsCString& aID,
                                                                       const uint32_t& aPromiseID,
                                                                       const uint32_t& aDeviceID) override;
   virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestController(const nsCString& aID,
                                                                          const uint32_t& aPromiseID,
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -185,11 +185,40 @@ struct ParamTraits<mozilla::gfx::VRContr
         !ReadParam(aMsg, aIter, &(aResult->mNumButtons)) ||
         !ReadParam(aMsg, aIter, &(aResult->mNumAxes))) {
       return false;
     }
 
     return true;
   }
 };
+
+template <>
+struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo>
+{
+  typedef mozilla::gfx::VRSubmitFrameResultInfo paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mBase64Image);
+    WriteParam(aMsg, aParam.mFormat);
+    WriteParam(aMsg, aParam.mWidth);
+    WriteParam(aMsg, aParam.mHeight);
+    WriteParam(aMsg, aParam.mFrameNum);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mBase64Image)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mFormat)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mWidth)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mHeight)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mFrameNum))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
 } // namespace IPC
 
 #endif // mozilla_gfx_vr_VRMessageUtils_h
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5128,16 +5128,20 @@ pref("dom.vr.poseprediction.enabled", tr
 pref("dom.vr.require-gesture", true);
 // path to OSVR DLLs
 pref("gfx.vr.osvr.utilLibPath", "");
 pref("gfx.vr.osvr.commonLibPath", "");
 pref("gfx.vr.osvr.clientLibPath", "");
 pref("gfx.vr.osvr.clientKitLibPath", "");
 // Puppet device, used for simulating VR hardware within tests and dev tools
 pref("dom.vr.puppet.enabled", false);
+// Allow displaying the result of vr submitframe (0: disable, 1: store the
+// result as a base64 image, 2: show it on the screen).
+pref("dom.vr.puppet.submitframe", 0);
+// VR test system.
 pref("dom.vr.test.enabled", false);
 // MMS UA Profile settings
 pref("wap.UAProf.url", "");
 pref("wap.UAProf.tagname", "x-wap-profile");
 
 // MMS version 1.1 = 0x11 (or decimal 17)
 // MMS version 1.3 = 0x13 (or decimal 19)
 // @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.34