/* 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/. */

#include "FenceD3D11.h"

#include <d3d11.h>
#include <d3d11_3.h>
#include <d3d11_4.h>
#include <dxgi1_6.h>

#include "mozilla/gfx/Logging.h"

namespace mozilla {
namespace layers {

constinit RefPtr<ID3D11Device> mDevice;

/* static */
RefPtr<FenceD3D11> FenceD3D11::Create(ID3D11Device* aDevice) {
  MOZ_ASSERT(aDevice);

  if (!aDevice) {
    return nullptr;
  }

  RefPtr<ID3D11Device5> d3d11_5;
  auto hr =
      aDevice->QueryInterface(__uuidof(ID3D11Device5), getter_AddRefs(d3d11_5));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get ID3D11Device5: " << gfx::hexa(hr);
    return nullptr;
  }

  RefPtr<ID3D11Fence> fenceD3D11;
  d3d11_5->CreateFence(0, D3D11_FENCE_FLAG_SHARED,
                       IID_PPV_ARGS((ID3D11Fence**)getter_AddRefs(fenceD3D11)));
  if (FAILED(hr) || !fenceD3D11) {
    gfxCriticalNoteOnce << "Fence creation failed: " << gfx::hexa(hr);
    return nullptr;
  }

  HANDLE sharedHandle = nullptr;
  hr = fenceD3D11->CreateSharedHandle(nullptr, GENERIC_ALL, nullptr,
                                      &sharedHandle);
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Fence shared handle creation failed "
                        << gfx::hexa(hr);
    return nullptr;
  }

  RefPtr handle =
      MakeRefPtr<gfx::FileHandleWrapper>(UniqueFileHandle(sharedHandle));
  RefPtr<FenceD3D11> fence =
      new FenceD3D11(OwnsFence::Yes, aDevice, fenceD3D11, handle);
  return fence;
}

/* static */
RefPtr<FenceD3D11> FenceD3D11::CreateFromHandle(
    RefPtr<gfx::FileHandleWrapper> aHandle,
    const RefPtr<ID3D11Device> aDevice) {
  MOZ_ASSERT(aHandle);

  if (!aHandle) {
    return nullptr;
  }
  // Opening shared handle is deferred.
  return new FenceD3D11(OwnsFence::No, aDevice, /* aSignalFence */ nullptr,
                        aHandle);
}

/* static */
bool FenceD3D11::IsSupported(ID3D11Device* aDevice) {
  MOZ_ASSERT(aDevice);

  if (!aDevice) {
    return false;
  }
  RefPtr<ID3D11Device5> d3d11_5;
  auto hr = aDevice->QueryInterface((ID3D11Device5**)getter_AddRefs(d3d11_5));
  if (FAILED(hr)) {
    return false;
  }

  // Check for IDXGIAdapter4:
  RefPtr<IDXGIDevice> dxgiDevice;
  aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(dxgiDevice));
  if (FAILED(hr)) {
    return false;
  }

  RefPtr<IDXGIAdapter> dxgiAdapter;
  hr = dxgiDevice->GetAdapter(getter_AddRefs(dxgiAdapter));
  if (FAILED(hr)) {
    return false;
  }

  RefPtr<IDXGIAdapter4> dxgiAdapter4;
  dxgiAdapter->QueryInterface((IDXGIAdapter4**)getter_AddRefs(dxgiAdapter4));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get IDXGIAdapter4: " << gfx::hexa(hr);
    return false;
  }

  DXGI_ADAPTER_DESC3 adapterDesc;
  hr = dxgiAdapter4->GetDesc3(&adapterDesc);
  if (FAILED(hr)) {
    return false;
  }

  // The adapter must support monitored fences.
  return adapterDesc.Flags & DXGI_ADAPTER_FLAG3_SUPPORT_MONITORED_FENCES;
}

FenceD3D11::FenceD3D11(const OwnsFence aOwnsFence,
                       const RefPtr<ID3D11Device> aDevice,
                       const RefPtr<ID3D11Fence> aSignalFence,
                       const RefPtr<gfx::FileHandleWrapper>& aHandle)
    : mOwnsFence(aOwnsFence),
      mDevice(aDevice),
      mSignalFence(aSignalFence),
      mHandle(aHandle) {
  MOZ_ASSERT(mHandle);
  MOZ_ASSERT_IF(mOwnsFence == OwnsFence::Yes, mDevice);
  MOZ_ASSERT_IF(mOwnsFence == OwnsFence::Yes, mSignalFence);
  MOZ_ASSERT_IF(mOwnsFence == OwnsFence::No, !mSignalFence);
}

FenceD3D11::~FenceD3D11() {}

RefPtr<FenceD3D11> FenceD3D11::CloneFromHandle() {
  RefPtr<FenceD3D11> fence = FenceD3D11::CreateFromHandle(mHandle, mDevice);
  if (fence) {
    fence->Update(mFenceValue);
  }
  return fence;
}

bool FenceD3D11::IncrementAndSignal() {
  MOZ_ASSERT(mOwnsFence == OwnsFence::Yes);

  if (mOwnsFence != OwnsFence::Yes) {
    return false;
  }

  RefPtr<ID3D11DeviceContext> context;
  mDevice->GetImmediateContext(getter_AddRefs(context));
  RefPtr<ID3D11DeviceContext4> context4;
  auto hr = context->QueryInterface(__uuidof(ID3D11DeviceContext4),
                                    getter_AddRefs(context4));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get D3D11DeviceContext4: "
                        << gfx::hexa(hr);
    return false;
  }

  hr = context4->Signal(mSignalFence, mFenceValue + 1);
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Signal fence failed: " << gfx::hexa(hr);
    return false;
  }

  mFenceValue++;
  return true;
}

void FenceD3D11::Update(uint64_t aFenceValue) {
  MOZ_ASSERT(mOwnsFence == OwnsFence::No);

  if (mOwnsFence != OwnsFence::No) {
    return;
  }

  if (mFenceValue > aFenceValue) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    return;
  }
  mFenceValue = aFenceValue;
}

bool FenceD3D11::Wait(ID3D11Device* aDevice) {
  MOZ_ASSERT(aDevice);

  if (!aDevice) {
    return false;
  }

  // Skip wait if passed device is the same as signaling device.
  if (mDevice == aDevice) {
    return true;
  }

  RefPtr<ID3D11Fence> fence;
  {
    MutexAutoLock lock(mMutex);
    auto it = mWaitFenceMap.find(aDevice);
    if (it == mWaitFenceMap.end()) {
      RefPtr<ID3D11Device5> d3d11_5;
      auto hr = aDevice->QueryInterface(__uuidof(ID3D11Device5),
                                        getter_AddRefs(d3d11_5));
      if (FAILED(hr)) {
        gfxCriticalNoteOnce << "Failed to get ID3D11Device5: " << gfx::hexa(hr);
        return false;
      }
      hr = d3d11_5->OpenSharedFence(
          mHandle->GetHandle(), __uuidof(ID3D11Fence),
          (void**)(ID3D11Fence**)getter_AddRefs(fence));
      if (FAILED(hr)) {
        gfxCriticalNoteOnce << "Opening fence shared handle failed "
                            << gfx::hexa(hr);
        return false;
      }
      mWaitFenceMap.emplace(aDevice, fence);
    } else {
      fence = it->second;
    }
  }

  if (!fence) {
    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    return false;
  }

  RefPtr<ID3D11DeviceContext> context;
  aDevice->GetImmediateContext(getter_AddRefs(context));
  RefPtr<ID3D11DeviceContext4> context4;
  auto hr = context->QueryInterface(__uuidof(ID3D11DeviceContext4),
                                    getter_AddRefs(context4));
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to get D3D11DeviceContext4: "
                        << gfx::hexa(hr);
    return false;
  }
  hr = context4->Wait(fence, mFenceValue);
  if (FAILED(hr)) {
    gfxCriticalNoteOnce << "Failed to wait fence: " << gfx::hexa(hr);
    return false;
  }

  return true;
}

}  // namespace layers
}  // namespace mozilla
