gfx/vr/gfxVRPuppet.cpp
author Gurzau Raul <rgurzau@mozilla.com>
Sat, 25 May 2019 09:07:49 +0300
changeset 475564 af54b2de7028db03f42207598f7a0b4ba81e262f
parent 475527 75b04de7e99cab7bed5cc8b0f2f6d52ddf174bf3
child 475614 37fd585d6c8ffd435e422eec6f3e77fca4dcc798
permissions -rw-r--r--
Backed out 31 changesets (bug 1552643, bug 1550422) for xpcshell crash on a CLOSED TREE. Backed out changeset e30c1aa75529 (bug 1552643) Backed out changeset caadcd7e02d3 (bug 1552643) Backed out changeset aa7086ab09be (bug 1552643) Backed out changeset 0b4029671710 (bug 1550422) Backed out changeset a16295296035 (bug 1550422) Backed out changeset 3b70307c0db5 (bug 1550422) Backed out changeset 69df7818d4a3 (bug 1550422) Backed out changeset d98dfc565927 (bug 1550422) Backed out changeset 6f0997976944 (bug 1550422) Backed out changeset 0edd264464c2 (bug 1550422) Backed out changeset 9ea6da7a74ec (bug 1550422) Backed out changeset f855f9309c8b (bug 1550422) Backed out changeset 1033546224a7 (bug 1550422) Backed out changeset ade7384c6186 (bug 1550422) Backed out changeset 75b04de7e99c (bug 1550422) Backed out changeset 91c3acdb2454 (bug 1550422) Backed out changeset 77d2f80257d1 (bug 1550422) Backed out changeset e0cd10d35327 (bug 1550422) Backed out changeset 097091082423 (bug 1550422) Backed out changeset 2f328853c1ab (bug 1550422) Backed out changeset f92f2cc29cb1 (bug 1550422) Backed out changeset 6dc82f88333d (bug 1550422) Backed out changeset c20f66494d69 (bug 1550422) Backed out changeset 2ba22cddeb6f (bug 1550422) Backed out changeset 3aa72f89e295 (bug 1550422) Backed out changeset ab4c4e806977 (bug 1550422) Backed out changeset 72e5de040dda (bug 1550422) Backed out changeset 7d3c2d486706 (bug 1550422) Backed out changeset 132e0b8d8468 (bug 1550422) Backed out changeset 54c85ac75dd0 (bug 1550422) Backed out changeset d7ba4a18dd54 (bug 1550422)

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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"
#elif defined(XP_MACOSX)
#  include "mozilla/gfx/MacIOSurface.h"
#endif

#include "mozilla/Base64.h"
#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/layers/CompositorThread.h"  // for CompositorThreadHolder
#include "gfxPrefs.h"
#include "gfxUtils.h"
#include "gfxVRPuppet.h"
#include "VRManager.h"
#include "VRThread.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};
static const uint32_t kNumPuppetButtonMask =
    sizeof(kPuppetButtonMask) / sizeof(uint64_t);
static const uint32_t kNumPuppetAxis = 3;
static const uint32_t kNumPuppetHaptcs = 1;

VRDisplayPuppet::VRDisplayPuppet()
    : VRDisplayLocal(VRDeviceType::Puppet),
      mIsPresenting(false),
      mSensorState{} {
  MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayLocal);

  VRDisplayState& state = mDisplayInfo.mDisplayState;
  strncpy(state.displayName, "Puppet HMD", kVRDisplayNameMaxLen);
  state.isConnected = true;
  state.isMounted = false;
  state.capabilityFlags = VRDisplayCapabilityFlags::Cap_None |
                          VRDisplayCapabilityFlags::Cap_Orientation |
                          VRDisplayCapabilityFlags::Cap_Position |
                          VRDisplayCapabilityFlags::Cap_External |
                          VRDisplayCapabilityFlags::Cap_Present |
                          VRDisplayCapabilityFlags::Cap_StageParameters;
  state.eyeResolution.width = 1836;   // 1080 * 1.7
  state.eyeResolution.height = 2040;  // 1200 * 1.7

  // SteamVR gives the application a single FOV to use; it's not configurable as
  // with Oculus
  for (uint32_t eye = 0; eye < 2; ++eye) {
    state.eyeTranslation[eye].x = 0.0f;
    state.eyeTranslation[eye].y = 0.0f;
    state.eyeTranslation[eye].z = 0.0f;
    state.eyeFOV[eye] = VRFieldOfView(45.0, 45.0, 45.0, 45.0);
  }

  // default: 1m x 1m space, 0.75m high in seated position
  state.stageSize.width = 1.0f;
  state.stageSize.height = 1.0f;

  state.sittingToStandingTransform[0] = 1.0f;
  state.sittingToStandingTransform[1] = 0.0f;
  state.sittingToStandingTransform[2] = 0.0f;
  state.sittingToStandingTransform[3] = 0.0f;

  state.sittingToStandingTransform[4] = 0.0f;
  state.sittingToStandingTransform[5] = 1.0f;
  state.sittingToStandingTransform[6] = 0.0f;
  state.sittingToStandingTransform[7] = 0.0f;

  state.sittingToStandingTransform[8] = 0.0f;
  state.sittingToStandingTransform[9] = 0.0f;
  state.sittingToStandingTransform[10] = 1.0f;
  state.sittingToStandingTransform[11] = 0.0f;

  state.sittingToStandingTransform[12] = 0.0f;
  state.sittingToStandingTransform[13] = 0.75f;
  state.sittingToStandingTransform[14] = 0.0f;
  state.sittingToStandingTransform[15] = 1.0f;

  gfx::Quaternion rot;

  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
  mSensorState.pose.orientation[0] = rot.x;
  mSensorState.pose.orientation[1] = rot.y;
  mSensorState.pose.orientation[2] = rot.z;
  mSensorState.pose.orientation[3] = rot.w;
  mSensorState.pose.angularVelocity[0] = 0.0f;
  mSensorState.pose.angularVelocity[1] = 0.0f;
  mSensorState.pose.angularVelocity[2] = 0.0f;

  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
  mSensorState.pose.position[0] = 0.0f;
  mSensorState.pose.position[1] = 0.0f;
  mSensorState.pose.position[2] = 0.0f;
  mSensorState.pose.linearVelocity[0] = 0.0f;
  mSensorState.pose.linearVelocity[1] = 0.0f;
  mSensorState.pose.linearVelocity[2] = 0.0f;
}

VRDisplayPuppet::~VRDisplayPuppet() {
  MOZ_COUNT_DTOR_INHERITED(VRDisplayPuppet, VRDisplayLocal);
}

void VRDisplayPuppet::SetDisplayInfo(const VRDisplayInfo& aDisplayInfo) {
  // We are only interested in the eye and mount info of the display info.
  VRDisplayState& state = mDisplayInfo.mDisplayState;
  state.eyeResolution = aDisplayInfo.mDisplayState.eyeResolution;
  state.isMounted = aDisplayInfo.mDisplayState.isMounted;
  memcpy(&state.eyeFOV, &aDisplayInfo.mDisplayState.eyeFOV,
         sizeof(state.eyeFOV[0]) * VRDisplayState::NumEyes);
  memcpy(&state.eyeTranslation, &aDisplayInfo.mDisplayState.eyeTranslation,
         sizeof(state.eyeTranslation[0]) * VRDisplayState::NumEyes);
}

void VRDisplayPuppet::Destroy() { StopPresentation(); }

void VRDisplayPuppet::ZeroSensor() {}

VRHMDSensorState& VRDisplayPuppet::GetSensorState() {
  mSensorState.inputFrameID = mDisplayInfo.mFrameId;

  Matrix4x4 matHeadToEye[2];
  for (uint32_t eye = 0; eye < 2; ++eye) {
    matHeadToEye[eye].PreTranslate(mDisplayInfo.GetEyeTranslation(eye));
  }
  mSensorState.CalcViewMatrices(matHeadToEye);

  return mSensorState;
}

void VRDisplayPuppet::SetSensorState(const VRHMDSensorState& aSensorState) {
  memcpy(&mSensorState, &aSensorState, sizeof(mSensorState));
}

void VRDisplayPuppet::StartPresentation() {
  if (mIsPresenting) {
    return;
  }
  mIsPresenting = true;

#if defined(XP_WIN)
  if (!CreateD3DObjects()) {
    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;
}

bool VRDisplayPuppet::SubmitFrame(ID3D11Texture2D* aSource,
                                  const IntSize& aSize,
                                  const gfx::Rect& aLeftEyeRect,
                                  const gfx::Rect& aRightEyeRect) {
  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
  if (!mIsPresenting) {
    return false;
  }

  if (!CreateD3DObjects()) {
    return false;
  }
  AutoRestoreRenderState restoreState(this);
  if (!restoreState.IsSuccess()) {
    return false;
  }

  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;
      aSource->GetDesc(&desc);
      MOZ_ASSERT(desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM,
                 "Only support B8G8R8A8_UNORM format.");
      // Map the staging resource
      ID3D11Texture2D* mappedTexture = nullptr;
      D3D11_MAPPED_SUBRESOURCE mapInfo;
      HRESULT hr = mContext->Map(aSource,
                                 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;

          ID3D11Texture2D* stagingTexture = nullptr;
          hr = mDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
          if (FAILED(hr)) {
            MOZ_ASSERT(false, "Failed to create a staging texture");
            return false;
          }
          // Copy the texture to a staging resource
          mContext->CopyResource(stagingTexture, aSource);
          // 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 false;
        }
      } else {
        mappedTexture = aSource;
      }
      // 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.
      const char* srcData = static_cast<const char*>(mapInfo.pData);
      VRSubmitFrameResultInfo result;
      result.mFormat = SurfaceFormat::B8G8R8A8;
      result.mWidth = desc.Width;
      result.mHeight = desc.Height;
      result.mFrameNum = mDisplayInfo.mFrameId;
      // If the original texture size is not pow of 2, the data will not be
      // tightly strided. We have to copy the pixels by rows.
      nsCString rawString;
      for (uint32_t i = 0; i < desc.Height; i++) {
        rawString += Substring(srcData + i * mapInfo.RowPitch, desc.Width * 4);
      }
      mContext->Unmap(mappedTexture, 0);

      if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
        MOZ_ASSERT(false, "Failed to encode base64 images.");
      }
      // Dispatch the base64 encoded string to the DOM side. Then, it will be
      // decoded and convert to a PNG image there.
      MessageLoop* loop = CompositorThreadHolder::Loop();
      loop->PostTask(NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
          "VRManager::DispatchSubmitFrameResult", vm,
          &VRManager::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);

      RefPtr<ID3D11ShaderResourceView> srView;
      HRESULT hr = mDevice->CreateShaderResourceView(aSource, nullptr,
                                                     getter_AddRefs(srView));
      if (FAILED(hr) || !srView) {
        gfxWarning() << "Could not create shader resource view for Puppet: "
                     << hexa(hr);
        return false;
      }
      ID3D11ShaderResourceView* viewPtr = srView.get();
      mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &viewPtr);
      // 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 false;
      }
      mContext->Draw(4, 0);
      break;
    }
  }

  // We will always return false for gfxVRPuppet to ensure that the fallback
  // "watchdog" code in VRDisplayHost::NotifyVSync() throttles the render loop.
  // This "watchdog" will result in a refresh rate that is quite low compared to
  // real hardware, but should be sufficient for non-performance oriented tests.
  // If we wish to simulate realistic frame rates with VRDisplayPuppet, we
  // should block here for the appropriate amount of time and return true to
  // indicate that we have blocked.
  return false;
}

#elif defined(XP_MACOSX)

bool VRDisplayPuppet::SubmitFrame(MacIOSurface* aMacIOSurface,
                                  const IntSize& aSize,
                                  const gfx::Rect& aLeftEyeRect,
                                  const gfx::Rect& aRightEyeRect) {
  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
  if (!mIsPresenting || !aMacIOSurface) {
    return false;
  }

  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.
      RefPtr<SourceSurface> surf = aMacIOSurface->GetAsSurface();
      RefPtr<DataSourceSurface> dataSurf =
          surf ? surf->GetDataSurface() : nullptr;
      if (dataSurf) {
        // 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.
        DataSourceSurface::MappedSurface map;
        if (!dataSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
          MOZ_ASSERT(false, "Read DataSourceSurface fail.");
          return false;
        }
        const uint8_t* srcData = map.mData;
        const auto& surfSize = dataSurf->GetSize();
        VRSubmitFrameResultInfo result;
        result.mFormat = SurfaceFormat::B8G8R8A8;
        result.mWidth = surfSize.width;
        result.mHeight = surfSize.height;
        result.mFrameNum = mDisplayInfo.mFrameId;
        // If the original texture size is not pow of 2, the data will not be
        // tightly strided. We have to copy the pixels by rows.
        nsCString rawString;
        for (int32_t i = 0; i < surfSize.height; i++) {
          rawString += Substring((const char*)(srcData) + i * map.mStride,
                                 surfSize.width * 4);
        }
        dataSurf->Unmap();

        if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
          MOZ_ASSERT(false, "Failed to encode base64 images.");
        }
        // Dispatch the base64 encoded string to the DOM side. Then, it will be
        // decoded and convert to a PNG image there.
        MessageLoop* loop = CompositorThreadHolder::Loop();
        loop->PostTask(
            NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
                "VRManager::DispatchSubmitFrameResult", vm,
                &VRManager::DispatchSubmitFrameResult, mDisplayInfo.mDisplayID,
                result));
      }
      break;
    }
    case 2: {
      MOZ_ASSERT(false, "No support for showing VR frames on MacOSX yet.");
      break;
    }
  }

  return false;
}

#elif defined(MOZ_WIDGET_ANDROID)

bool VRDisplayPuppet::SubmitFrame(
    const mozilla::layers::SurfaceTextureDescriptor& aDescriptor,
    const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) {
  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
  return false;
}

#endif

void VRDisplayPuppet::Refresh() {
  // We update mIsConneced once per refresh.
  mDisplayInfo.mDisplayState.isConnected = true;
}

VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand,
                                       uint32_t aDisplayID)
    : VRControllerHost(VRDeviceType::Puppet, aHand, aDisplayID),
      mButtonPressState(0),
      mButtonTouchState(0) {
  MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
  VRControllerState& state = mControllerInfo.mControllerState;
  strncpy(state.controllerName, "Puppet Gamepad", kVRControllerNameMaxLen);
  state.numButtons = kNumPuppetButtonMask;
  state.numAxes = kNumPuppetAxis;
  state.numHaptics = kNumPuppetHaptcs;
}

VRControllerPuppet::~VRControllerPuppet() {
  MOZ_COUNT_DTOR_INHERITED(VRControllerPuppet, VRControllerHost);
}

void VRControllerPuppet::SetButtonPressState(uint32_t aButton, bool aPressed) {
  const uint64_t buttonMask = kPuppetButtonMask[aButton];
  uint64_t pressedBit = GetButtonPressed();

  if (aPressed) {
    pressedBit |= kPuppetButtonMask[aButton];
  } else if (pressedBit & buttonMask) {
    // this button was pressed but is released now.
    uint64_t mask = 0xff ^ buttonMask;
    pressedBit &= mask;
  }

  mButtonPressState = pressedBit;
}

uint64_t VRControllerPuppet::GetButtonPressState() { return mButtonPressState; }

void VRControllerPuppet::SetButtonTouchState(uint32_t aButton, bool aTouched) {
  const uint64_t buttonMask = kPuppetButtonMask[aButton];
  uint64_t touchedBit = GetButtonTouched();

  if (aTouched) {
    touchedBit |= kPuppetButtonMask[aButton];
  } else if (touchedBit & buttonMask) {
    // this button was touched but is released now.
    uint64_t mask = 0xff ^ buttonMask;
    touchedBit &= mask;
  }

  mButtonTouchState = touchedBit;
}

uint64_t VRControllerPuppet::GetButtonTouchState() { return mButtonTouchState; }

void VRControllerPuppet::SetAxisMoveState(uint32_t aAxis, double aValue) {
  MOZ_ASSERT((sizeof(mAxisMoveState) / sizeof(float)) == kNumPuppetAxis);
  MOZ_ASSERT(aAxis <= kNumPuppetAxis);

  mAxisMoveState[aAxis] = aValue;
}

double VRControllerPuppet::GetAxisMoveState(uint32_t aAxis) {
  return mAxisMoveState[aAxis];
}

void VRControllerPuppet::SetPoseMoveState(const dom::GamepadPoseState& aPose) {
  mPoseState = aPose;
}

const dom::GamepadPoseState& VRControllerPuppet::GetPoseMoveState() {
  return mPoseState;
}

float VRControllerPuppet::GetAxisMove(uint32_t aAxis) {
  return mControllerInfo.mControllerState.axisValue[aAxis];
}

void VRControllerPuppet::SetAxisMove(uint32_t aAxis, float aValue) {
  mControllerInfo.mControllerState.axisValue[aAxis] = aValue;
}

VRSystemManagerPuppet::VRSystemManagerPuppet()
    : mPuppetDisplayCount(0),
      mPuppetDisplayInfo{},
      mPuppetDisplaySensorState{} {}

/*static*/
already_AddRefed<VRSystemManagerPuppet> VRSystemManagerPuppet::Create() {
  if (!gfxPrefs::VREnabled() || !gfxPrefs::VRPuppetEnabled()) {
    return nullptr;
  }

  RefPtr<VRSystemManagerPuppet> manager = new VRSystemManagerPuppet();
  return manager.forget();
}

void VRSystemManagerPuppet::Destroy() { Shutdown(); }

void VRSystemManagerPuppet::Shutdown() { mPuppetHMDs.Clear(); }

void VRSystemManagerPuppet::Run10msTasks() {
  VRSystemManager::Run10msTasks();

  /**
   * When running headless mochitests on some of our automated test
   * infrastructure, 2d display vsyncs are not always generated.
   * To workaround, we produce a vsync manually.
   */
  VRManager* vm = VRManager::Get();
  MOZ_ASSERT(vm);
  vm->NotifyVsync(TimeStamp::Now());
}

void VRSystemManagerPuppet::NotifyVSync() {
  VRSystemManager::NotifyVSync();

  for (const auto& display : mPuppetHMDs) {
    display->Refresh();
  }
}

uint32_t VRSystemManagerPuppet::CreateTestDisplay() {
  if (mPuppetDisplayCount >= kMaxPuppetDisplays) {
    MOZ_ASSERT(false);
    return mPuppetDisplayCount;
  }
  return mPuppetDisplayCount++;
}

void VRSystemManagerPuppet::ClearTestDisplays() { mPuppetDisplayCount = 0; }

void VRSystemManagerPuppet::Enumerate() {
  while (mPuppetHMDs.Length() < mPuppetDisplayCount) {
    VRDisplayPuppet* puppetDisplay = new VRDisplayPuppet();
    uint32_t deviceID = mPuppetHMDs.Length();
    puppetDisplay->SetDisplayInfo(mPuppetDisplayInfo[deviceID]);
    puppetDisplay->SetSensorState(mPuppetDisplaySensorState[deviceID]);
    mPuppetHMDs.AppendElement(puppetDisplay);
  }
  while (mPuppetHMDs.Length() > mPuppetDisplayCount) {
    mPuppetHMDs.RemoveLastElement();
  }
}

void VRSystemManagerPuppet::SetPuppetDisplayInfo(
    const uint32_t& aDeviceID, const VRDisplayInfo& aDisplayInfo) {
  if (aDeviceID >= mPuppetDisplayCount) {
    MOZ_ASSERT(false);
    return;
  }
  /**
   * Even if mPuppetHMDs.Length() <= aDeviceID, we need to
   * update mPuppetDisplayInfo[aDeviceID].  In the case that
   * a puppet display is added and SetPuppetDisplayInfo is
   * immediately called, mPuppetHMDs may not be populated yet.
   * VRSystemManagerPuppet::Enumerate() will initialize
   * the VRDisplayPuppet later using mPuppetDisplayInfo.
   */
  mPuppetDisplayInfo[aDeviceID] = aDisplayInfo;
  if (mPuppetHMDs.Length() > aDeviceID) {
    /**
     * In the event that the VRDisplayPuppet has already been
     * created, we update it directly.
     */
    mPuppetHMDs[aDeviceID]->SetDisplayInfo(aDisplayInfo);
  }
}

void VRSystemManagerPuppet::SetPuppetDisplaySensorState(
    const uint32_t& aDeviceID, const VRHMDSensorState& aSensorState) {
  if (aDeviceID >= mPuppetDisplayCount) {
    MOZ_ASSERT(false);
    return;
  }
  /**
   * Even if mPuppetHMDs.Length() <= aDeviceID, we need to
   * update mPuppetDisplaySensorState[aDeviceID].  In the case that
   * a puppet display is added and SetPuppetDisplaySensorState is
   * immediately called, mPuppetHMDs may not be populated yet.
   * VRSystemManagerPuppet::Enumerate() will initialize
   * the VRDisplayPuppet later using mPuppetDisplaySensorState.
   */
  mPuppetDisplaySensorState[aDeviceID] = aSensorState;
  if (mPuppetHMDs.Length() > aDeviceID) {
    /**
     * In the event that the VRDisplayPuppet has already been
     * created, we update it directly.
     */
    mPuppetHMDs[aDeviceID]->SetSensorState(aSensorState);
  }
}

void VRSystemManagerPuppet::GetHMDs(
    nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) {
  for (auto display : mPuppetHMDs) {
    aHMDResult.AppendElement(display);
  }
}

bool VRSystemManagerPuppet::GetIsPresenting() {
  for (const auto& display : mPuppetHMDs) {
    const VRDisplayInfo& displayInfo(display->GetDisplayInfo());
    if (displayInfo.GetPresentingGroups() != kVRGroupNone) {
      return true;
    }
  }
  return false;
}

void VRSystemManagerPuppet::HandleInput() {
  RefPtr<impl::VRControllerPuppet> controller;
  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
    controller = mPuppetController[i];
    for (uint32_t j = 0; j < kNumPuppetButtonMask; ++j) {
      HandleButtonPress(i, j, kPuppetButtonMask[j],
                        controller->GetButtonPressState(),
                        controller->GetButtonTouchState());
    }
    controller->SetButtonPressed(controller->GetButtonPressState());
    controller->SetButtonTouched(controller->GetButtonTouchState());

    for (uint32_t j = 0; j < kNumPuppetAxis; ++j) {
      HandleAxisMove(i, j, controller->GetAxisMoveState(j));
    }
    HandlePoseTracking(i, controller->GetPoseMoveState(), controller);
  }
}

void VRSystemManagerPuppet::HandleButtonPress(uint32_t aControllerIdx,
                                              uint32_t aButton,
                                              uint64_t aButtonMask,
                                              uint64_t aButtonPressed,
                                              uint64_t aButtonTouched) {
  RefPtr<impl::VRControllerPuppet> controller(
      mPuppetController[aControllerIdx]);
  MOZ_ASSERT(controller);
  const uint64_t pressedDiff =
      (controller->GetButtonPressed() ^ aButtonPressed);
  const uint64_t touchedDiff =
      (controller->GetButtonTouched() ^ aButtonTouched);

  if (!pressedDiff && !touchedDiff) {
    return;
  }

  if (pressedDiff & aButtonMask || touchedDiff & aButtonMask) {
    // diff & (aButtonPressed, aButtonTouched) would be true while a new button
    // pressed or touched event, otherwise it is an old event and needs to
    // notify the button has been released.
    NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
                   aButtonMask & aButtonPressed,
                   (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
  }
}

void VRSystemManagerPuppet::HandleAxisMove(uint32_t aControllerIdx,
                                           uint32_t aAxis, float aValue) {
  RefPtr<impl::VRControllerPuppet> controller(
      mPuppetController[aControllerIdx]);
  MOZ_ASSERT(controller);

  if (controller->GetAxisMove(aAxis) != aValue) {
    NewAxisMove(aControllerIdx, aAxis, aValue);
    controller->SetAxisMove(aAxis, aValue);
  }
}

void VRSystemManagerPuppet::HandlePoseTracking(
    uint32_t aControllerIdx, const dom::GamepadPoseState& aPose,
    VRControllerHost* aController) {
  MOZ_ASSERT(aController);
  if (aPose != aController->GetPose()) {
    aController->SetPose(aPose);
    NewPoseState(aControllerIdx, aPose);
  }
}

void VRSystemManagerPuppet::VibrateHaptic(uint32_t aControllerIdx,
                                          uint32_t aHapticIndex,
                                          double aIntensity, double aDuration,
                                          const VRManagerPromise& aPromise) {}

void VRSystemManagerPuppet::StopVibrateHaptic(uint32_t aControllerIdx) {}

void VRSystemManagerPuppet::GetControllers(
    nsTArray<RefPtr<VRControllerHost>>& aControllerResult) {
  aControllerResult.Clear();
  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
    aControllerResult.AppendElement(mPuppetController[i]);
  }
}

void VRSystemManagerPuppet::ScanForControllers() {
  // We make sure VRSystemManagerPuppet has two controllers
  // for each display
  const uint32_t newControllerCount = mPuppetHMDs.Length() * 2;

  if (newControllerCount != mControllerCount) {
    RemoveControllers();

    // Re-adding controllers to VRControllerManager.
    for (const auto& display : mPuppetHMDs) {
      uint32_t displayID = display->GetDisplayInfo().GetDisplayID();
      for (uint32_t i = 0; i < 2; i++) {
        dom::GamepadHand hand =
            (i % 2) ? dom::GamepadHand::Right : dom::GamepadHand::Left;
        RefPtr<VRControllerPuppet> puppetController;
        puppetController = new VRControllerPuppet(hand, displayID);
        mPuppetController.AppendElement(puppetController);

        // Not already present, add it.
        AddGamepad(puppetController->GetControllerInfo());
        ++mControllerCount;
      }
    }
  }
}

void VRSystemManagerPuppet::RemoveControllers() {
  // controller count is changed, removing the existing gamepads first.
  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
    RemoveGamepad(i);
  }
  mPuppetController.Clear();
  mControllerCount = 0;
}