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

#ifdef XP_MACOSX
#  include "GLContextCGL.h"
#else
#  include "GLContextEAGL.h"
#endif

#include "mozilla/gfx/Logging.h"
#include "mozilla/layers/GpuFence.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/TimeStamp.h"
#include "ScopedGLHelpers.h"

namespace mozilla {
namespace wr {

static bool CreateTextureForPlane(uint8_t aPlaneID, gl::GLContext* aGL,
                                  MacIOSurface* aSurface, GLuint* aTexture) {
  MOZ_ASSERT(aGL && aSurface && aTexture);

  const GLenum target = aGL->GetPreferredMacIOSurfaceTextureTarget();

  aGL->fGenTextures(1, aTexture);
  ActivateBindAndTexParameteri(aGL, LOCAL_GL_TEXTURE0, target, *aTexture);
  aGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
  aGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);

  gfx::SurfaceFormat readFormat = gfx::SurfaceFormat::UNKNOWN;
  bool result = aSurface->BindTexImage(aGL, aPlaneID, &readFormat);
  // If this is a yuv format, the Webrender only supports YUY2 interleaving
  // format.
  MOZ_ASSERT(aSurface->GetFormat() != gfx::SurfaceFormat::YUY2 ||
             readFormat == gfx::SurfaceFormat::YUY2);

  return result;
}

RenderMacIOSurfaceTextureHost::RenderMacIOSurfaceTextureHost(
    MacIOSurface* aSurface, layers::GpuFence* aGpuFence)
    : mSurface(aSurface), mGpuFence(aGpuFence), mTextureHandles{0, 0, 0} {
  MOZ_COUNT_CTOR_INHERITED(RenderMacIOSurfaceTextureHost, RenderTextureHost);
}

RenderMacIOSurfaceTextureHost::~RenderMacIOSurfaceTextureHost() {
  MOZ_COUNT_DTOR_INHERITED(RenderMacIOSurfaceTextureHost, RenderTextureHost);
  DeleteTextureHandle();
}

GLuint RenderMacIOSurfaceTextureHost::GetGLHandle(uint8_t aChannelIndex) const {
  MOZ_ASSERT(mSurface);
  MOZ_ASSERT((mSurface->GetPlaneCount() == 0)
                 ? (aChannelIndex == mSurface->GetPlaneCount())
                 : (aChannelIndex < mSurface->GetPlaneCount()));
  return mTextureHandles[aChannelIndex];
}

gfx::IntSize RenderMacIOSurfaceTextureHost::GetSize(
    uint8_t aChannelIndex) const {
  MOZ_ASSERT(mSurface);
  MOZ_ASSERT((mSurface->GetPlaneCount() == 0)
                 ? (aChannelIndex == mSurface->GetPlaneCount())
                 : (aChannelIndex < mSurface->GetPlaneCount()));

  if (!mSurface) {
    return gfx::IntSize();
  }
  return gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
                      mSurface->GetDevicePixelHeight(aChannelIndex));
}

size_t RenderMacIOSurfaceTextureHost::Bytes() {
  return mSurface->GetAllocSize();
}

wr::WrExternalImage RenderMacIOSurfaceTextureHost::Lock(uint8_t aChannelIndex,
                                                        gl::GLContext* aGL) {
  if (mGL.get() != aGL) {
    // release the texture handle in the previous gl context
    DeleteTextureHandle();
    mGL = aGL;
    mGL->MakeCurrent();
  }

  if (!mSurface || !mGL || !mGL->MakeCurrent()) {
    return InvalidToWrExternalImage();
  }

  if (!mTextureHandles[0]) {
#ifdef XP_MACOSX
    if (mGL->GetContextType() == gl::GLContextType::CGL) {
      MOZ_ASSERT(gl::GLContextCGL::Cast(mGL.get())->GetCGLContext());
    }
#else
    MOZ_ASSERT(gl::GLContextEAGL::Cast(mGL.get())->GetEAGLContext());
#endif

    // The result of GetPlaneCount() is 0 for single plane format, but it will
    // be 2 if the format has 2 planar data.
    CreateTextureForPlane(0, mGL, mSurface, &(mTextureHandles[0]));
    for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
      CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i]));
    }
  }

  if (mGpuFence) {
    // This timeout matches the acquisition timeout for the keyed mutex
    // in the D3D11 texture host.
    auto timeout = TimeDuration::FromMilliseconds(10000);
    auto start = TimeStamp::Now();
    AUTO_PROFILER_MARKER("Lock MacIOSurfaceTexture", GRAPHICS);
    while (!mGpuFence->HasCompleted() && (TimeStamp::Now() - start) < timeout) {
      PR_Sleep(PR_MillisecondsToInterval(1));
    }
  } else {
    PROFILER_MARKER_UNTYPED("No GpuFence", GRAPHICS);
  }

  const auto size = GetSize(aChannelIndex);
  return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0.0, 0.0,
                                        static_cast<float>(size.width),
                                        static_cast<float>(size.height));
}

void RenderMacIOSurfaceTextureHost::Unlock() {}

void RenderMacIOSurfaceTextureHost::DeleteTextureHandle() {
  if (mTextureHandles[0] != 0 && mGL && mGL->MakeCurrent()) {
    // Calling glDeleteTextures on 0 isn't an error. So, just make them a single
    // call.
    mGL->fDeleteTextures(3, mTextureHandles);
    for (size_t i = 0; i < 3; ++i) {
      mTextureHandles[i] = 0;
    }
  }
}

size_t RenderMacIOSurfaceTextureHost::GetPlaneCount() const {
  size_t planeCount = mSurface->GetPlaneCount();
  return planeCount > 0 ? planeCount : 1;
}

gfx::SurfaceFormat RenderMacIOSurfaceTextureHost::GetFormat() const {
  return mSurface->GetFormat();
}

gfx::ColorDepth RenderMacIOSurfaceTextureHost::GetColorDepth() const {
  return mSurface->GetColorDepth();
}

gfx::YUVRangedColorSpace RenderMacIOSurfaceTextureHost::GetYUVColorSpace()
    const {
  return ToYUVRangedColorSpace(mSurface->GetYUVColorSpace(),
                               mSurface->GetColorRange(),
                               mSurface->GetTransferFunction());
}

gfx::TransferFunction RenderMacIOSurfaceTextureHost::GetTransferFunction()
    const {
  return mSurface->GetTransferFunction();
};

bool RenderMacIOSurfaceTextureHost::MapPlane(RenderCompositor* aCompositor,
                                             uint8_t aChannelIndex,
                                             PlaneInfo& aPlaneInfo) {
  if (!aChannelIndex) {
    if (NS_WARN_IF(!mSurface->Lock())) {
      return false;
    }
  }
  aPlaneInfo.mData = mSurface->GetBaseAddressOfPlane(aChannelIndex);
  aPlaneInfo.mStride = mSurface->GetBytesPerRow(aChannelIndex);
  aPlaneInfo.mSize =
      gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
                   mSurface->GetDevicePixelHeight(aChannelIndex));
  return true;
}

void RenderMacIOSurfaceTextureHost::UnmapPlanes() { mSurface->Unlock(); }

}  // namespace wr
}  // namespace mozilla
