/* 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 "VALibWrapper.h"

#include "FFmpegLog.h"
#include "PlatformDecoderModule.h"
#include "mozilla/DataMutex.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/widget/DMABufDevice.h"
#include "prlink.h"

namespace mozilla {

VALibWrapper VALibWrapper::sFuncs;
static int (*vaInitialize)(void* dpy, int* major_version, int* minor_version);
static int (*vaTerminate)(void* dpy);
static void* (*vaGetDisplayDRM)(int fd);

constinit static StaticDataMutex<ThreadSafeWeakPtr<VADisplayHolder>>
    sDisplayHolder("VADisplayHolder::sDisplayHolder");

void VALibWrapper::Link() {
#define VA_FUNC_OPTION_SILENT(func)                               \
  if (!((func) = (decltype(func))PR_FindSymbol(mVALib, #func))) { \
    (func) = (decltype(func))nullptr;                             \
  }

  // mVALib is optional and may not be present.
  if (mVALib) {
    VA_FUNC_OPTION_SILENT(vaExportSurfaceHandle)
    VA_FUNC_OPTION_SILENT(vaSyncSurface)
    VA_FUNC_OPTION_SILENT(vaInitialize)
    VA_FUNC_OPTION_SILENT(vaTerminate)
  }
#undef VA_FUNC_OPTION_SILENT

#define VAD_FUNC_OPTION_SILENT(func)                                 \
  if (!((func) = (decltype(func))PR_FindSymbol(mVALibDrm, #func))) { \
    FFMPEGP_LOG("Couldn't load function " #func);                    \
  }

  // mVALibDrm is optional and may not be present.
  if (mVALibDrm) {
    VAD_FUNC_OPTION_SILENT(vaGetDisplayDRM)
  }
#undef VAD_FUNC_OPTION_SILENT
}

bool VALibWrapper::LinkVAAPILibs() {
  if (!gfx::gfxVars::CanUseHardwareVideoDecoding() || !XRE_IsRDDProcess()) {
    return false;
  }

  PRLibSpec lspec;
  lspec.type = PR_LibSpec_Pathname;
  const char* libDrm = "libva-drm.so.2";
  lspec.value.pathname = libDrm;
  mVALibDrm = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
  if (!mVALibDrm) {
    FFMPEGP_LOG("VA-API support: Missing or old {} library.\n", libDrm);
    return false;
  }

  const char* lib = "libva.so.2";
  lspec.value.pathname = lib;
  mVALib = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
  // Don't use libva when it's missing vaExportSurfaceHandle.
  if (mVALib && !PR_FindSymbol(mVALib, "vaExportSurfaceHandle")) {
    PR_UnloadLibrary(mVALib);
    mVALib = nullptr;
  }
  if (!mVALib) {
    FFMPEGP_LOG("VA-API support: Missing or old {} library.\n", lib);
    return false;
  }

  Link();
  return true;
}

bool VALibWrapper::AreVAAPIFuncsAvailable() {
#define VA_FUNC_LOADED(func) ((func) != nullptr)
  return VA_FUNC_LOADED(vaExportSurfaceHandle) &&
         VA_FUNC_LOADED(vaSyncSurface) && VA_FUNC_LOADED(vaInitialize) &&
         VA_FUNC_LOADED(vaTerminate) && VA_FUNC_LOADED(vaGetDisplayDRM);
}

/* static */
bool VALibWrapper::IsVAAPIAvailable() {
  static bool once = sFuncs.LinkVAAPILibs();
  (void)once;

  return sFuncs.AreVAAPIFuncsAvailable();
}

VADisplayHolder::VADisplayHolder(UniqueVADisplay aDisplay,
                                 UniqueFileHandle aDRMFd)
    : mDRMFd(std::move(aDRMFd)), mDisplay(std::move(aDisplay)) {};

VADisplayHolder::~VADisplayHolder() = default;

/* static */
RefPtr<VADisplayHolder> VADisplayHolder::GetSingleton() {
  auto weakInstance = sDisplayHolder.Lock();

  RefPtr<VADisplayHolder> instance(*weakInstance);
  if (instance) {
    return instance;
  }

  widget::DMABufDeviceLock device;
  UniqueFileHandle drmFd{device.GetDMABufDevice()->OpenDRMFd()};
  UniqueVADisplay display{vaGetDisplayDRM(drmFd.get())};
  if (!display) {
    FFMPEGP_LOG("  Can't get DRM VA-API display.");
    return nullptr;
  }

  int major, minor;
  VAStatus status = vaInitialize(display.get(), &major, &minor);
  if (status != VA_STATUS_SUCCESS) {
    FFMPEGP_LOG("  vaInitialize failed.");
    // |display| is destroyed while sDisplayHolderMutex is still held, to
    // vaTerminate() before another VADisplay might be accessed.
    // See Bug 1850615.
    return nullptr;
  }

  instance = new VADisplayHolder(std::move(display), std::move(drmFd));
  *weakInstance = instance;
  return instance;
}

void VADisplayHolder::VADisplayDeleter::operator()(VADisplay aDisplay) {
  vaTerminate(aDisplay);
}

}  // namespace mozilla
