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

#include "RenderTextureHostSWGL.h"

#include "mozilla/gfx/Logging.h"
#include "mozilla/layers/TextureHost.h"
#include "RenderThread.h"

namespace mozilla {
namespace wr {

bool RenderTextureHostSWGL::UpdatePlanes(RenderCompositor* aCompositor) {
  wr_swgl_make_current(mContext);
  size_t planeCount = GetPlaneCount();
  bool texInit = false;
  if (mPlanes.size() < planeCount) {
    mPlanes.reserve(planeCount);
    while (mPlanes.size() < planeCount) {
      mPlanes.push_back(PlaneInfo(wr_swgl_gen_texture(mContext)));
    }
    texInit = true;
  }
  gfx::SurfaceFormat format = GetFormat();
  gfx::ColorDepth colorDepth = GetColorDepth();
  for (size_t i = 0; i < planeCount; i++) {
    PlaneInfo& plane = mPlanes[i];
    if (!MapPlane(aCompositor, i, plane)) {
      if (i > 0) {
        UnmapPlanes();
      }
      return false;
    }
    GLenum internalFormat = 0;
    switch (format) {
      case gfx::SurfaceFormat::B8G8R8A8:
      case gfx::SurfaceFormat::B8G8R8X8:
        MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_8);
        internalFormat = LOCAL_GL_RGBA8;
        break;
      case gfx::SurfaceFormat::YUV:
        switch (colorDepth) {
          case gfx::ColorDepth::COLOR_8:
            internalFormat = LOCAL_GL_R8;
            break;
          case gfx::ColorDepth::COLOR_10:
          case gfx::ColorDepth::COLOR_12:
          case gfx::ColorDepth::COLOR_16:
            internalFormat = LOCAL_GL_R16;
            break;
        }
        break;
      case gfx::SurfaceFormat::NV12:
        switch (colorDepth) {
          case gfx::ColorDepth::COLOR_8:
            internalFormat = i > 0 ? LOCAL_GL_RG8 : LOCAL_GL_R8;
            break;
          case gfx::ColorDepth::COLOR_10:
          case gfx::ColorDepth::COLOR_12:
          case gfx::ColorDepth::COLOR_16:
            internalFormat = i > 0 ? LOCAL_GL_RG16 : LOCAL_GL_R16;
            break;
        }
        break;
      case gfx::SurfaceFormat::P010:
        MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_10);
        internalFormat = i > 0 ? LOCAL_GL_RG16 : LOCAL_GL_R16;
        break;
      case gfx::SurfaceFormat::YUV422:
        MOZ_ASSERT(colorDepth == gfx::ColorDepth::COLOR_8);
        internalFormat = LOCAL_GL_RGB_RAW_422_APPLE;
        break;
      default:
        MOZ_RELEASE_ASSERT(false, "Unhandled external image format");
        break;
    }
    wr_swgl_set_texture_buffer(mContext, plane.mTexture, internalFormat,
                               plane.mSize.width, plane.mSize.height,
                               plane.mStride, plane.mData, 0, 0);
  }
  if (texInit) {
    // Initialize the mip filters to linear by default.
    for (const auto& plane : mPlanes) {
      wr_swgl_set_texture_parameter(mContext, plane.mTexture,
                                    LOCAL_GL_TEXTURE_MIN_FILTER,
                                    LOCAL_GL_LINEAR);
      wr_swgl_set_texture_parameter(mContext, plane.mTexture,
                                    LOCAL_GL_TEXTURE_MAG_FILTER,
                                    LOCAL_GL_LINEAR);
    }
  }
  return true;
}

bool RenderTextureHostSWGL::SetContext(void* aContext) {
  if (mContext != aContext) {
    CleanupPlanes();
    mContext = aContext;
    wr_swgl_reference_context(mContext);
  }
  return mContext != nullptr;
}

wr::WrExternalImage RenderTextureHostSWGL::LockSWGL(
    uint8_t aChannelIndex, void* aContext, RenderCompositor* aCompositor) {
  if (!SetContext(aContext)) {
    return InvalidToWrExternalImage();
  }
  if (!mLocked) {
    if (!UpdatePlanes(aCompositor)) {
      return InvalidToWrExternalImage();
    }
    mLocked = true;
  }
  if (aChannelIndex >= mPlanes.size()) {
    return InvalidToWrExternalImage();
  }
  const PlaneInfo& plane = mPlanes[aChannelIndex];

  const auto uvs = GetUvCoords(plane.mSize);

  // Prefer native textures, unless our backend forbids it.
  // If the GetUvCoords call above returned anything other than the default,
  // for example if this is a RenderAndroidSurfaceTextureHost, then this won't
  // be handled correctly in the RawDataToWrExternalImage path. But we shouldn't
  // hit this path in practice with a RenderAndroidSurfaceTextureHost.
  layers::TextureHost::NativeTexturePolicy policy =
      layers::TextureHost::BackendNativeTexturePolicy(
          layers::WebRenderBackend::SOFTWARE, plane.mSize);
  return policy == layers::TextureHost::NativeTexturePolicy::FORBID
             ? RawDataToWrExternalImage((uint8_t*)plane.mData,
                                        plane.mStride * plane.mSize.height)
             : NativeTextureToWrExternalImage(plane.mTexture, uvs.first.x,
                                              uvs.first.y, uvs.second.x,
                                              uvs.second.y);
}

void RenderTextureHostSWGL::UnlockSWGL() {
  if (mLocked) {
    mLocked = false;
    UnmapPlanes();
  }
}

void RenderTextureHostSWGL::CleanupPlanes() {
  if (!mContext) {
    return;
  }
  if (!mPlanes.empty()) {
    wr_swgl_make_current(mContext);
    for (const auto& plane : mPlanes) {
      wr_swgl_delete_texture(mContext, plane.mTexture);
    }
    mPlanes.clear();
  }
  wr_swgl_destroy_context(mContext);
  mContext = nullptr;
}

RenderTextureHostSWGL::~RenderTextureHostSWGL() { CleanupPlanes(); }

bool RenderTextureHostSWGL::LockSWGLCompositeSurface(
    void* aContext, wr::SWGLCompositeSurfaceInfo* aInfo) {
  if (!SetContext(aContext)) {
    return false;
  }
  if (!mLocked) {
    if (!UpdatePlanes(nullptr)) {
      return false;
    }
    mLocked = true;
  }
  MOZ_ASSERT(mPlanes.size() <= 3);
  for (size_t i = 0; i < mPlanes.size(); i++) {
    aInfo->textures[i] = mPlanes[i].mTexture;
  }
  switch (GetFormat()) {
    case gfx::SurfaceFormat::YUV:
    case gfx::SurfaceFormat::NV12:
    case gfx::SurfaceFormat::P010:
    case gfx::SurfaceFormat::YUV422: {
      aInfo->yuv_planes = mPlanes.size();
      auto colorSpace = GetYUVColorSpace();
      aInfo->color_space = ToWrYuvRangedColorSpace(colorSpace);
      auto colorDepth = GetColorDepth();
      aInfo->color_depth = ToWrColorDepth(colorDepth);
      break;
    }
    case gfx::SurfaceFormat::B8G8R8A8:
    case gfx::SurfaceFormat::B8G8R8X8:
      break;
    default:
      gfxCriticalNote << "Unhandled external image format: " << GetFormat();
      MOZ_RELEASE_ASSERT(false, "Unhandled external image format");
      break;
  }
  aInfo->size.width = mPlanes[0].mSize.width;
  aInfo->size.height = mPlanes[0].mSize.height;
  return true;
}

bool wr_swgl_lock_composite_surface(void* aContext, wr::ExternalImageId aId,
                                    wr::SWGLCompositeSurfaceInfo* aInfo) {
  RenderTextureHost* texture = RenderThread::Get()->GetRenderTexture(aId);
  if (!texture) {
    return false;
  }
  RenderTextureHostSWGL* swglTex = texture->AsRenderTextureHostSWGL();
  if (!swglTex) {
    return false;
  }
  return swglTex->LockSWGLCompositeSurface(aContext, aInfo);
}

void wr_swgl_unlock_composite_surface(void* aContext, wr::ExternalImageId aId) {
  RenderTextureHost* texture = RenderThread::Get()->GetRenderTexture(aId);
  if (!texture) {
    return;
  }
  RenderTextureHostSWGL* swglTex = texture->AsRenderTextureHostSWGL();
  if (!swglTex) {
    return;
  }
  swglTex->UnlockSWGL();
}

}  // namespace wr
}  // namespace mozilla
