/* -*- 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 "mozilla/layers/Compositor.h"
#include "base/message_loop.h"          // for MessageLoop
#include "mozilla/layers/CompositorBridgeParent.h"  // for CompositorBridgeParent
#include "mozilla/layers/Diagnostics.h"
#include "mozilla/layers/Effects.h"     // for Effect, EffectChain, etc
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/TextureHost.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/mozalloc.h"           // for operator delete, etc
#include "gfx2DGlue.h"
#include "nsAppRunner.h"
#include "LayersHelpers.h"

namespace mozilla {

namespace layers {

Compositor::Compositor(widget::CompositorWidget* aWidget,
                      CompositorBridgeParent* aParent)
  : mDiagnosticTypes(DiagnosticTypes::NO_DIAGNOSTIC)
  , mParent(aParent)
  , mPixelsPerFrame(0)
  , mPixelsFilled(0)
  , mScreenRotation(ROTATION_0)
  , mWidget(aWidget)
  , mIsDestroyed(false)
#if defined(MOZ_WIDGET_ANDROID)
  // If the default color isn't white for Fennec, there is a black
  // flash before the first page of a tab is loaded.
  , mClearColor(1.0, 1.0, 1.0, 1.0)
  , mDefaultClearColor(1.0, 1.0, 1.0, 1.0)
#else
  , mClearColor(0.0, 0.0, 0.0, 0.0)
  , mDefaultClearColor(0.0, 0.0, 0.0, 0.0)
#endif
{
}

Compositor::~Compositor()
{
  ReadUnlockTextures();
}

void
Compositor::Destroy()
{
  TextureSourceProvider::Destroy();
  mIsDestroyed = true;
}

void
Compositor::EndFrame()
{
  ReadUnlockTextures();
  mLastCompositionEndTime = TimeStamp::Now();
}

/* static */ void
Compositor::AssertOnCompositorThread()
{
  MOZ_ASSERT(!CompositorThreadHolder::Loop() ||
             CompositorThreadHolder::Loop() == MessageLoop::current(),
             "Can only call this from the compositor thread!");
}

bool
Compositor::ShouldDrawDiagnostics(DiagnosticFlags aFlags)
{
  if ((aFlags & DiagnosticFlags::TILE) && !(mDiagnosticTypes & DiagnosticTypes::TILE_BORDERS)) {
    return false;
  }
  if ((aFlags & DiagnosticFlags::BIGIMAGE) &&
      !(mDiagnosticTypes & DiagnosticTypes::BIGIMAGE_BORDERS)) {
    return false;
  }
  if (mDiagnosticTypes == DiagnosticTypes::NO_DIAGNOSTIC) {
    return false;
  }
  return true;
}

void
Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
                            const nsIntRegion& aVisibleRegion,
                            const gfx::IntRect& aClipRect,
                            const gfx::Matrix4x4& aTransform,
                            uint32_t aFlashCounter)
{
  if (!ShouldDrawDiagnostics(aFlags)) {
    return;
  }

  if (aVisibleRegion.GetNumRects() > 1) {
    for (auto iter = aVisibleRegion.RectIter(); !iter.Done(); iter.Next()) {
      DrawDiagnostics(aFlags | DiagnosticFlags::REGION_RECT,
                      IntRectToRect(iter.Get()), aClipRect, aTransform,
                      aFlashCounter);
    }
  }

  DrawDiagnostics(aFlags, IntRectToRect(aVisibleRegion.GetBounds()),
                  aClipRect, aTransform, aFlashCounter);
}

void
Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
                            const gfx::Rect& aVisibleRect,
                            const gfx::IntRect& aClipRect,
                            const gfx::Matrix4x4& aTransform,
                            uint32_t aFlashCounter)
{
  if (!ShouldDrawDiagnostics(aFlags)) {
    return;
  }

  DrawDiagnosticsInternal(aFlags, aVisibleRect, aClipRect, aTransform,
                          aFlashCounter);
}

void
Compositor::DrawDiagnosticsInternal(DiagnosticFlags aFlags,
                                    const gfx::Rect& aVisibleRect,
                                    const gfx::IntRect& aClipRect,
                                    const gfx::Matrix4x4& aTransform,
                                    uint32_t aFlashCounter)
{
#ifdef ANDROID
  int lWidth = 10;
#else
  int lWidth = 2;
#endif

  gfx::Color color;
  if (aFlags & DiagnosticFlags::CONTENT) {
    color = gfx::Color(0.0f, 1.0f, 0.0f, 1.0f); // green
    if (aFlags & DiagnosticFlags::COMPONENT_ALPHA) {
      color = gfx::Color(0.0f, 1.0f, 1.0f, 1.0f); // greenish blue
    }
  } else if (aFlags & DiagnosticFlags::IMAGE) {
    if (aFlags & DiagnosticFlags::NV12) {
      color = gfx::Color(1.0f, 1.0f, 0.0f, 1.0f); // yellow
    } else if (aFlags & DiagnosticFlags::YCBCR) {
      color = gfx::Color(1.0f, 0.55f, 0.0f, 1.0f); // orange
    } else {
      color = gfx::Color(1.0f, 0.0f, 0.0f, 1.0f); // red
    }
  } else if (aFlags & DiagnosticFlags::COLOR) {
    color = gfx::Color(0.0f, 0.0f, 1.0f, 1.0f); // blue
  } else if (aFlags & DiagnosticFlags::CONTAINER) {
    color = gfx::Color(0.8f, 0.0f, 0.8f, 1.0f); // purple
  }

  // make tile borders a bit more transparent to keep layer borders readable.
  if (aFlags & DiagnosticFlags::TILE ||
      aFlags & DiagnosticFlags::BIGIMAGE ||
      aFlags & DiagnosticFlags::REGION_RECT) {
    lWidth = 1;
    color.r *= 0.7f;
    color.g *= 0.7f;
    color.b *= 0.7f;
    color.a = color.a * 0.5f;
  } else {
    color.a = color.a * 0.7f;
  }


  if (mDiagnosticTypes & DiagnosticTypes::FLASH_BORDERS) {
    float flash = (float)aFlashCounter / (float)DIAGNOSTIC_FLASH_COUNTER_MAX;
    color.r *= flash;
    color.g *= flash;
    color.b *= flash;
  }

  SlowDrawRect(aVisibleRect, color, aClipRect, aTransform, lWidth);
}

static void
UpdateTextureCoordinates(gfx::TexturedTriangle& aTriangle,
                         const gfx::Rect& aRect,
                         const gfx::Rect& aIntersection,
                         const gfx::Rect& aTextureCoords)
{
  // Calculate the relative offset of the intersection within the layer.
  float dx = (aIntersection.X() - aRect.X()) / aRect.Width();
  float dy = (aIntersection.Y() - aRect.Y()) / aRect.Height();

  // Update the texture offset.
  float x = aTextureCoords.X() + dx * aTextureCoords.Width();
  float y = aTextureCoords.Y() + dy * aTextureCoords.Height();

  // Scale the texture width and height.
  float w = aTextureCoords.Width() * aIntersection.Width() / aRect.Width();
  float h = aTextureCoords.Height() * aIntersection.Height() / aRect.Height();

  static const auto Clamp = [](float& f)
  {
    if (f >= 1.0f) f = 1.0f;
    if (f <= 0.0f) f = 0.0f;
  };

  auto UpdatePoint = [&](const gfx::Point& p, gfx::Point& t)
  {
    t.x = x + (p.x - aIntersection.X()) / aIntersection.Width() * w;
    t.y = y + (p.y - aIntersection.Y()) / aIntersection.Height() * h;

    Clamp(t.x);
    Clamp(t.y);
  };

  UpdatePoint(aTriangle.p1, aTriangle.textureCoords.p1);
  UpdatePoint(aTriangle.p2, aTriangle.textureCoords.p2);
  UpdatePoint(aTriangle.p3, aTriangle.textureCoords.p3);
}

void
Compositor::DrawGeometry(const gfx::Rect& aRect,
                         const gfx::IntRect& aClipRect,
                         const EffectChain& aEffectChain,
                         gfx::Float aOpacity,
                         const gfx::Matrix4x4& aTransform,
                         const gfx::Rect& aVisibleRect,
                         const Maybe<gfx::Polygon>& aGeometry)
{
  if (aRect.IsEmpty()) {
    return;
  }

  if (!aGeometry || !SupportsLayerGeometry()) {
    DrawQuad(aRect, aClipRect, aEffectChain,
             aOpacity, aTransform, aVisibleRect);
    return;
  }

  // Cull completely invisible polygons.
  if (aRect.Intersect(aGeometry->BoundingBox()).IsEmpty()) {
    return;
  }

  const gfx::Polygon clipped = aGeometry->ClipPolygon(aRect);

  // Cull polygons with no area.
  if (clipped.IsEmpty()) {
    return;
  }

  DrawPolygon(clipped, aRect, aClipRect, aEffectChain,
              aOpacity, aTransform, aVisibleRect);
}

void
Compositor::DrawTriangles(const nsTArray<gfx::TexturedTriangle>& aTriangles,
                          const gfx::Rect& aRect,
                          const gfx::IntRect& aClipRect,
                          const EffectChain& aEffectChain,
                          gfx::Float aOpacity,
                          const gfx::Matrix4x4& aTransform,
                          const gfx::Rect& aVisibleRect)
{
  for (const gfx::TexturedTriangle& triangle : aTriangles) {
    DrawTriangle(triangle, aClipRect, aEffectChain,
                 aOpacity, aTransform, aVisibleRect);
  }
}

nsTArray<gfx::TexturedTriangle>
GenerateTexturedTriangles(const gfx::Polygon& aPolygon,
                          const gfx::Rect& aRect,
                          const gfx::Rect& aTexRect)
{
  nsTArray<gfx::TexturedTriangle> texturedTriangles;

  gfx::Rect layerRects[4];
  gfx::Rect textureRects[4];
  size_t rects = DecomposeIntoNoRepeatRects(aRect, aTexRect,
                                            &layerRects, &textureRects);
  for (size_t i = 0; i < rects; ++i) {
    const gfx::Rect& rect = layerRects[i];
    const gfx::Rect& texRect = textureRects[i];
    const gfx::Polygon clipped = aPolygon.ClipPolygon(rect);

    if (clipped.IsEmpty()) {
      continue;
    }

    for (const gfx::Triangle& triangle : clipped.ToTriangles()) {
      const gfx::Rect intersection = rect.Intersect(triangle.BoundingBox());

      // Cull completely invisible triangles.
      if (intersection.IsEmpty()) {
        continue;
      }

      MOZ_ASSERT(rect.Width() > 0.0f && rect.Height() > 0.0f);
      MOZ_ASSERT(intersection.Width() > 0.0f && intersection.Height() > 0.0f);

      // Since the texture was created for non-split geometry, we need to
      // update the texture coordinates to account for the split.
      gfx::TexturedTriangle t(triangle);
      UpdateTextureCoordinates(t, rect, intersection, texRect);
      texturedTriangles.AppendElement(Move(t));
    }
  }

  return texturedTriangles;
}

nsTArray<TexturedVertex>
TexturedTrianglesToVertexArray(const nsTArray<gfx::TexturedTriangle>& aTriangles)
{
  const auto VertexFromPoints = [](const gfx::Point& p, const gfx::Point& t) {
    return TexturedVertex { { p.x, p.y }, { t.x, t.y } };
  };

  nsTArray<TexturedVertex> vertices;

  for (const gfx::TexturedTriangle& t : aTriangles) {
    vertices.AppendElement(VertexFromPoints(t.p1, t.textureCoords.p1));
    vertices.AppendElement(VertexFromPoints(t.p2, t.textureCoords.p2));
    vertices.AppendElement(VertexFromPoints(t.p3, t.textureCoords.p3));
  }

  return vertices;
}

void
Compositor::DrawPolygon(const gfx::Polygon& aPolygon,
                        const gfx::Rect& aRect,
                        const gfx::IntRect& aClipRect,
                        const EffectChain& aEffectChain,
                        gfx::Float aOpacity,
                        const gfx::Matrix4x4& aTransform,
                        const gfx::Rect& aVisibleRect)
{
  nsTArray<gfx::TexturedTriangle> texturedTriangles;

  TexturedEffect* texturedEffect =
    aEffectChain.mPrimaryEffect->AsTexturedEffect();

  if (texturedEffect) {
    texturedTriangles =
      GenerateTexturedTriangles(aPolygon, aRect, texturedEffect->mTextureCoords);
  } else {
    for (const gfx::Triangle& triangle : aPolygon.ToTriangles()) {
      texturedTriangles.AppendElement(gfx::TexturedTriangle(triangle));
    }
  }

  if (texturedTriangles.IsEmpty()) {
    // Nothing to render.
    return;
  }

  DrawTriangles(texturedTriangles, aRect, aClipRect, aEffectChain,
                aOpacity, aTransform, aVisibleRect);
}

void
Compositor::SlowDrawRect(const gfx::Rect& aRect, const gfx::Color& aColor,
                     const gfx::IntRect& aClipRect,
                     const gfx::Matrix4x4& aTransform, int aStrokeWidth)
{
  // TODO This should draw a rect using a single draw call but since
  // this is only used for debugging overlays it's not worth optimizing ATM.
  float opacity = 1.0f;
  EffectChain effects;

  effects.mPrimaryEffect = new EffectSolidColor(aColor);
  // left
  this->DrawQuad(gfx::Rect(aRect.X(), aRect.Y(),
                           aStrokeWidth, aRect.Height()),
                 aClipRect, effects, opacity,
                 aTransform);
  // top
  this->DrawQuad(gfx::Rect(aRect.X() + aStrokeWidth, aRect.Y(),
                           aRect.Width() - 2 * aStrokeWidth, aStrokeWidth),
                 aClipRect, effects, opacity,
                 aTransform);
  // right
  this->DrawQuad(gfx::Rect(aRect.XMost() - aStrokeWidth, aRect.Y(),
                           aStrokeWidth, aRect.Height()),
                 aClipRect, effects, opacity,
                 aTransform);
  // bottom
  this->DrawQuad(gfx::Rect(aRect.X() + aStrokeWidth, aRect.YMost() - aStrokeWidth,
                           aRect.Width() - 2 * aStrokeWidth, aStrokeWidth),
                 aClipRect, effects, opacity,
                 aTransform);
}

void
Compositor::FillRect(const gfx::Rect& aRect, const gfx::Color& aColor,
                     const gfx::IntRect& aClipRect,
                     const gfx::Matrix4x4& aTransform)
{
  float opacity = 1.0f;
  EffectChain effects;

  effects.mPrimaryEffect = new EffectSolidColor(aColor);
  this->DrawQuad(aRect,
                 aClipRect, effects, opacity,
                 aTransform);
}


static float
WrapTexCoord(float v)
{
    // This should return values in range [0, 1.0)
    return v - floorf(v);
}

static void
SetRects(size_t n,
         decomposedRectArrayT* aLayerRects,
         decomposedRectArrayT* aTextureRects,
         float x0, float y0, float x1, float y1,
         float tx0, float ty0, float tx1, float ty1,
         bool flip_y)
{
  if (flip_y) {
    std::swap(ty0, ty1);
  }
  (*aLayerRects)[n] = gfx::Rect(x0, y0, x1 - x0, y1 - y0);
  (*aTextureRects)[n] = gfx::Rect(tx0, ty0, tx1 - tx0, ty1 - ty0);
}

#ifdef DEBUG
static inline bool
FuzzyEqual(float a, float b)
{
	return fabs(a - b) < 0.0001f;
}
static inline bool
FuzzyLTE(float a, float b)
{
	return a <= b + 0.0001f;
}
#endif

size_t
DecomposeIntoNoRepeatRects(const gfx::Rect& aRect,
                           const gfx::Rect& aTexCoordRect,
                           decomposedRectArrayT* aLayerRects,
                           decomposedRectArrayT* aTextureRects)
{
  gfx::Rect texCoordRect = aTexCoordRect;

  // If the texture should be flipped, it will have negative height. Detect that
  // here and compensate for it. We will flip each rect as we emit it.
  bool flipped = false;
  if (texCoordRect.Height() < 0) {
    flipped = true;
    texCoordRect.MoveByY(texCoordRect.Height());
    texCoordRect.SetHeight(-texCoordRect.Height());
  }

  // Wrap the texture coordinates so they are within [0,1] and cap width/height
  // at 1. We rely on this below.
  texCoordRect = gfx::Rect(gfx::Point(WrapTexCoord(texCoordRect.X()),
                                      WrapTexCoord(texCoordRect.Y())),
                           gfx::Size(std::min(texCoordRect.Width(), 1.0f),
                                     std::min(texCoordRect.Height(), 1.0f)));

  NS_ASSERTION(texCoordRect.X() >= 0.0f && texCoordRect.X() <= 1.0f &&
               texCoordRect.Y() >= 0.0f && texCoordRect.Y() <= 1.0f &&
               texCoordRect.Width() >= 0.0f && texCoordRect.Width() <= 1.0f &&
               texCoordRect.Height() >= 0.0f && texCoordRect.Height() <= 1.0f &&
               texCoordRect.XMost() >= 0.0f && texCoordRect.XMost() <= 2.0f &&
               texCoordRect.YMost() >= 0.0f && texCoordRect.YMost() <= 2.0f,
               "We just wrapped the texture coordinates, didn't we?");

  // Get the top left and bottom right points of the rectangle. Note that
  // tl.x/tl.y are within [0,1] but br.x/br.y are within [0,2].
  gfx::Point tl = texCoordRect.TopLeft();
  gfx::Point br = texCoordRect.BottomRight();

  NS_ASSERTION(tl.x >= 0.0f && tl.x <= 1.0f &&
               tl.y >= 0.0f && tl.y <= 1.0f &&
               br.x >= tl.x && br.x <= 2.0f &&
               br.y >= tl.y && br.y <= 2.0f &&
               FuzzyLTE(br.x - tl.x, 1.0f) &&
               FuzzyLTE(br.y - tl.y, 1.0f),
               "Somehow generated invalid texture coordinates");

  // Then check if we wrap in either the x or y axis.
  bool xwrap = br.x > 1.0f;
  bool ywrap = br.y > 1.0f;

  // If xwrap is false, the texture will be sampled from tl.x .. br.x.
  // If xwrap is true, then it will be split into tl.x .. 1.0, and
  // 0.0 .. WrapTexCoord(br.x). Same for the Y axis. The destination
  // rectangle is also split appropriately, according to the calculated
  // xmid/ymid values.
  if (!xwrap && !ywrap) {
    SetRects(0, aLayerRects, aTextureRects,
             aRect.X(), aRect.Y(), aRect.XMost(), aRect.YMost(),
             tl.x, tl.y, br.x, br.y,
             flipped);
    return 1;
  }

  // If we are dealing with wrapping br.x and br.y are greater than 1.0 so
  // wrap them here as well.
  br = gfx::Point(xwrap ? WrapTexCoord(br.x) : br.x,
                  ywrap ? WrapTexCoord(br.y) : br.y);

  // If we wrap around along the x axis, we will draw first from
  // tl.x .. 1.0 and then from 0.0 .. br.x (which we just wrapped above).
  // The same applies for the Y axis. The midpoints we calculate here are
  // only valid if we actually wrap around.
  GLfloat xmid = aRect.X() + (1.0f - tl.x) / texCoordRect.Width() * aRect.Width();
  GLfloat ymid = aRect.Y() + (1.0f - tl.y) / texCoordRect.Height() * aRect.Height();

  // Due to floating-point inaccuracy, we have to use XMost()-x and YMost()-y
  // to calculate width and height, respectively, to ensure that size will
  // remain consistent going from absolute to relative and back again.
  NS_ASSERTION(!xwrap ||
               (xmid >= aRect.X() &&
                xmid <= aRect.XMost() &&
                FuzzyEqual((xmid - aRect.X()) + (aRect.XMost() - xmid), aRect.XMost() - aRect.X())),
               "xmid should be within [x,XMost()] and the wrapped rect should have the same width");
  NS_ASSERTION(!ywrap ||
               (ymid >= aRect.Y() &&
                ymid <= aRect.YMost() &&
                FuzzyEqual((ymid - aRect.Y()) + (aRect.YMost() - ymid), aRect.YMost() - aRect.Y())),
               "ymid should be within [y,YMost()] and the wrapped rect should have the same height");

  if (!xwrap && ywrap) {
    SetRects(0, aLayerRects, aTextureRects,
             aRect.X(), aRect.Y(), aRect.XMost(), ymid,
             tl.x, tl.y, br.x, 1.0f,
             flipped);
    SetRects(1, aLayerRects, aTextureRects,
             aRect.X(), ymid, aRect.XMost(), aRect.YMost(),
             tl.x, 0.0f, br.x, br.y,
             flipped);
    return 2;
  }

  if (xwrap && !ywrap) {
    SetRects(0, aLayerRects, aTextureRects,
             aRect.X(), aRect.Y(), xmid, aRect.YMost(),
             tl.x, tl.y, 1.0f, br.y,
             flipped);
    SetRects(1, aLayerRects, aTextureRects,
             xmid, aRect.Y(), aRect.XMost(), aRect.YMost(),
             0.0f, tl.y, br.x, br.y,
             flipped);
    return 2;
  }

  SetRects(0, aLayerRects, aTextureRects,
           aRect.X(), aRect.Y(), xmid, ymid,
           tl.x, tl.y, 1.0f, 1.0f,
           flipped);
  SetRects(1, aLayerRects, aTextureRects,
           xmid, aRect.Y(), aRect.XMost(), ymid,
           0.0f, tl.y, br.x, 1.0f,
           flipped);
  SetRects(2, aLayerRects, aTextureRects,
           aRect.X(), ymid, xmid, aRect.YMost(),
           tl.x, 0.0f, 1.0f, br.y,
           flipped);
  SetRects(3, aLayerRects, aTextureRects,
           xmid, ymid, aRect.XMost(), aRect.YMost(),
           0.0f, 0.0f, br.x, br.y,
           flipped);
  return 4;
}

gfx::IntRect
Compositor::ComputeBackdropCopyRect(const gfx::Rect& aRect,
                                    const gfx::IntRect& aClipRect,
                                    const gfx::Matrix4x4& aTransform,
                                    gfx::Matrix4x4* aOutTransform,
                                    gfx::Rect* aOutLayerQuad)
{
  // Compute the clip.
  gfx::IntPoint rtOffset = GetCurrentRenderTarget()->GetOrigin();
  gfx::IntSize rtSize = GetCurrentRenderTarget()->GetSize();

  return layers::ComputeBackdropCopyRect(
    aRect,
    aClipRect,
    aTransform,
    gfx::IntRect(rtOffset, rtSize),
    aOutTransform,
    aOutLayerQuad);
}

gfx::IntRect
Compositor::ComputeBackdropCopyRect(const gfx::Triangle& aTriangle,
                                    const gfx::IntRect& aClipRect,
                                    const gfx::Matrix4x4& aTransform,
                                    gfx::Matrix4x4* aOutTransform,
                                    gfx::Rect* aOutLayerQuad)
{
  gfx::Rect boundingBox = aTriangle.BoundingBox();
  return ComputeBackdropCopyRect(boundingBox, aClipRect, aTransform,
                                 aOutTransform, aOutLayerQuad);
}

void
Compositor::SetInvalid()
{
  mParent = nullptr;
}

bool
Compositor::IsValid() const
{
  return !!mParent;
}

void
Compositor::SetDispAcquireFence(Layer* aLayer)
{
}

void
Compositor::UnlockAfterComposition(TextureHost* aTexture)
{
  TextureSourceProvider::UnlockAfterComposition(aTexture);

  // If this is being called after we shutdown the compositor, we must finish
  // read unlocking now to prevent a cycle.
  if (IsDestroyed()) {
    ReadUnlockTextures();
  }
}

bool
Compositor::NotifyNotUsedAfterComposition(TextureHost* aTextureHost)
{
  if (IsDestroyed() || AsBasicCompositor()) {
    return false;
  }
  return TextureSourceProvider::NotifyNotUsedAfterComposition(aTextureHost);
}

void
Compositor::GetFrameStats(GPUStats* aStats)
{
  aStats->mInvalidPixels = mPixelsPerFrame;
  aStats->mPixelsFilled = mPixelsFilled;
}

} // namespace layers
} // namespace mozilla
