/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * 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 "CompositorOGL.h"
#include <sstream>                      // for ostringstream
#include <stddef.h>                     // for size_t
#include <stdint.h>                     // for uint32_t, uint8_t
#include <stdlib.h>                     // for free, malloc
#include "mozilla/layers/LayerManagerComposite.h"  // for LayerComposite, etc
#include "mozilla/layers/Effects.h"     // for EffectChain, TexturedEffect, etc
#include "mozilla/layers/CompositingRenderTargetOGL.h"
#include "mozilla/layers/TextureHost.h"  // for TextureSource, etc
#include "mozilla/layers/TextureHostOGL.h"  // for TextureSourceOGL, etc
#include "GLContextProvider.h"          // for GLContextProvider
#include "GLContext.h"                  // for GLContext
#include "GLUploadHelpers.h"
#include "Layers.h"                     // for WriteSnapshotToDumpFile
#include "LayerScope.h"                 // for LayerScope
#include "gfxCrashReporterUtils.h"      // for ScopedGfxFeatureReporter
#include "gfxMatrix.h"                  // for gfxMatrix
#include "gfxPlatform.h"                // for gfxPlatform
#include "gfxPrefs.h"                   // for gfxPrefs
#include "gfxRect.h"                    // for gfxRect
#include "gfxUtils.h"                   // for NextPowerOfTwo, gfxUtils, etc
#include "gfxVR.h"

namespace mozilla {

using namespace std;
using namespace gfx;

namespace layers {

using namespace mozilla::gl;

bool
CompositorOGL::InitializeVR()
{
  gl()->fGenBuffers(2, mVR.mDistortionVertices);
  gl()->fGenBuffers(2, mVR.mDistortionIndices);

  // these are explicitly bound later
  mVR.mAPosition = 0;
  mVR.mATexCoord0 = 1;
  mVR.mATexCoord1 = 2;
  mVR.mATexCoord2 = 3;
  mVR.mAGenericAttribs = 4;

  ostringstream vs;

  vs << "uniform vec4 uVREyeToSource;\n";
  vs << "uniform vec4 uVRDestinationScaleAndOffset;\n";
  vs << "uniform float uHeight;\n";
  vs << "attribute vec2 aPosition;\n";
  vs << "attribute vec2 aTexCoord0;\n";
  vs << "attribute vec2 aTexCoord1;\n";
  vs << "attribute vec2 aTexCoord2;\n";
  vs << "attribute vec4 aGenericAttribs;\n";
  vs << "varying vec2 vTexCoord0;\n";
  vs << "varying vec2 vTexCoord1;\n";
  vs << "varying vec2 vTexCoord2;\n";
  vs << "varying vec4 vGenericAttribs;\n";
  vs << "void main() {\n";
  vs << "  gl_Position = vec4(aPosition.xy * uVRDestinationScaleAndOffset.zw + uVRDestinationScaleAndOffset.xy, 0.5, 1.0);\n";
  vs << "  vTexCoord0 = uVREyeToSource.zw * aTexCoord0 + uVREyeToSource.xy;\n";
  vs << "  vTexCoord1 = uVREyeToSource.zw * aTexCoord1 + uVREyeToSource.xy;\n";
  vs << "  vTexCoord2 = uVREyeToSource.zw * aTexCoord2 + uVREyeToSource.xy;\n";
  vs << "  vTexCoord0.y = uHeight - vTexCoord0.y;\n";
  vs << "  vTexCoord1.y = uHeight - vTexCoord1.y;\n";
  vs << "  vTexCoord2.y = uHeight - vTexCoord2.y;\n";
  vs << "  vGenericAttribs = aGenericAttribs;\n";
  vs << "}\n";

  std::string vsSrcString(vs.str());
  const char *vsSrc = vsSrcString.c_str();

  for (int programIndex = 0; programIndex < 2; programIndex++) {
    GLenum textureTarget = programIndex == 0 ? LOCAL_GL_TEXTURE_2D : LOCAL_GL_TEXTURE_RECTANGLE;

    const char *sampler2D = "sampler2D";
    const char *texture2D = "texture2D";

    ostringstream fs;

    if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) {
      fs << "#extension GL_ARB_texture_rectangle : require\n";
      sampler2D = "sampler2DRect";
      texture2D = "texture2DRect";
    }

    if (gl()->IsGLES()) {
      fs << "precision highp float;\n";
    }

    fs << "uniform " << sampler2D << " uTexture;\n";
    fs << "varying vec2 vTexCoord0;\n";
    fs << "varying vec2 vTexCoord1;\n";
    fs << "varying vec2 vTexCoord2;\n";
    fs << "varying vec4 vGenericAttribs;\n";
    fs << "void main() {\n";
    fs << "  float resR = " << texture2D << "(uTexture, vTexCoord0.xy).r;\n";
    fs << "  float resG = " << texture2D << "(uTexture, vTexCoord1.xy).g;\n";
    fs << "  float resB = " << texture2D << "(uTexture, vTexCoord2.xy).b;\n";
    fs << "  gl_FragColor = vec4(resR * vGenericAttribs.r, resG * vGenericAttribs.r, resB * vGenericAttribs.r, 1.0);\n";
    fs << "}\n";

    std::string fsSrcString(fs.str());
    const char *fsSrc = fsSrcString.c_str();

    GLint status;
#ifdef DEBUG
    GLint length;
    nsCString logStr;
#endif

    GLuint shaders[2];
    shaders[0] = gl()->fCreateShader(LOCAL_GL_VERTEX_SHADER);
    shaders[1] = gl()->fCreateShader(LOCAL_GL_FRAGMENT_SHADER);

    GLint shaderLen[2] = { (GLint) strlen(vsSrc), (GLint) strlen(fsSrc) };
    gl()->fShaderSource(shaders[0], 1, &vsSrc, &shaderLen[0]);
    gl()->fShaderSource(shaders[1], 1, &fsSrc, &shaderLen[1]);

    for (int k = 0; k < 2; ++k) {
      gl()->fCompileShader(shaders[k]);
      gl()->fGetShaderiv(shaders[k], LOCAL_GL_COMPILE_STATUS, &status);
      if (!status) {
        NS_WARNING("VR GL shader failed to compile!");

#ifdef DEBUG
        gl()->fGetShaderiv(shaders[k], LOCAL_GL_INFO_LOG_LENGTH, &length);
        logStr.SetLength(length);
        gl()->fGetShaderInfoLog(shaders[k], length, &length, logStr.BeginWriting());
        printf_stderr("GL %s shader failed to compile:\n%s\n", k == 0 ? "vertex" : "fragment", logStr.BeginReading());
#endif

        gl()->fDeleteShader(shaders[0]);
        gl()->fDeleteShader(shaders[1]);

        return false;
      }
    }

    GLuint prog = gl()->fCreateProgram();
    gl()->fAttachShader(prog, shaders[0]);
    gl()->fAttachShader(prog, shaders[1]);

    gl()->fBindAttribLocation(prog, mVR.mAPosition, "aPosition");
    gl()->fBindAttribLocation(prog, mVR.mATexCoord0, "aTexCoord0");
    gl()->fBindAttribLocation(prog, mVR.mATexCoord1, "aTexCoord1");
    gl()->fBindAttribLocation(prog, mVR.mATexCoord2, "aTexCoord2");
    gl()->fBindAttribLocation(prog, mVR.mAGenericAttribs, "aGenericAttribs");

    gl()->fLinkProgram(prog);

    gl()->fGetProgramiv(prog, LOCAL_GL_LINK_STATUS, &status);
    if (!status) {
      NS_WARNING("VR GL program failed to link!");

#ifdef DEBUG
      gl()->fGetProgramiv(prog, LOCAL_GL_INFO_LOG_LENGTH, &length);
      logStr.SetLength(length);
      gl()->fGetProgramInfoLog(prog, length, &length, logStr.BeginWriting());
      printf_stderr("GL shader failed to link:\n%s\n", logStr.BeginReading());
#endif

      gl()->fDeleteProgram(prog);
      gl()->fDeleteShader(shaders[0]);
      gl()->fDeleteShader(shaders[1]);

      return false;
    }

    mVR.mUTexture[programIndex] = gl()->fGetUniformLocation(prog, "uTexture");
    mVR.mUVREyeToSource[programIndex] = gl()->fGetUniformLocation(prog, "uVREyeToSource");
    mVR.mUVRDestionatinScaleAndOffset[programIndex] = gl()->fGetUniformLocation(prog, "uVRDestinationScaleAndOffset");
    mVR.mUHeight[programIndex] = gl()->fGetUniformLocation(prog, "uHeight");

    mVR.mDistortionProgram[programIndex] = prog;

    gl()->fDeleteShader(shaders[0]);
    gl()->fDeleteShader(shaders[1]);
  }

  mVR.mInitialized = true;
  return true;
}

void
CompositorOGL::DestroyVR(GLContext *gl)
{
  if (!mVR.mInitialized)
    return;

  gl->fDeleteBuffers(2, mVR.mDistortionVertices);
  gl->fDeleteBuffers(2, mVR.mDistortionIndices);
  gl->fDeleteProgram(mVR.mDistortionProgram[0]);
  gl->fDeleteProgram(mVR.mDistortionProgram[1]);

  mVR.mInitialized = false;
}

void
CompositorOGL::DrawVRDistortion(const gfx::Rect& aRect,
                                const gfx::Rect& aClipRect,
                                const EffectChain& aEffectChain,
                                gfx::Float aOpacity,
                                const gfx::Matrix4x4& aTransform)
{
  MOZ_ASSERT(aEffectChain.mPrimaryEffect->mType == EffectTypes::VR_DISTORTION);
  MOZ_ASSERT(mVR.mInitialized);

  if (aEffectChain.mSecondaryEffects[EffectTypes::MASK] ||
      aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE])
  {
    NS_WARNING("DrawVRDistortion: ignoring secondary effect!");
  }

  EffectVRDistortion* vrEffect =
    static_cast<EffectVRDistortion*>(aEffectChain.mPrimaryEffect.get());

  GLenum textureTarget = LOCAL_GL_TEXTURE_2D;
  if (vrEffect->mRenderTarget)
    textureTarget = mFBOTextureTarget;

  RefPtr<CompositingRenderTargetOGL> surface =
    static_cast<CompositingRenderTargetOGL*>(vrEffect->mRenderTarget.get());

  VRHMDInfo* hmdInfo = vrEffect->mHMD;
  VRDistortionConstants shaderConstants;

  if (hmdInfo->GetConfiguration() != mVR.mConfiguration) {
    for (uint32_t eye = 0; eye < 2; eye++) {
      const gfx::VRDistortionMesh& mesh = hmdInfo->GetDistortionMesh(eye);
      gl()->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mVR.mDistortionVertices[eye]);
      gl()->fBufferData(LOCAL_GL_ARRAY_BUFFER,
                       mesh.mVertices.Length() * sizeof(gfx::VRDistortionVertex),
                       mesh.mVertices.Elements(),
                       LOCAL_GL_STATIC_DRAW);

      gl()->fBindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, mVR.mDistortionIndices[eye]);
      gl()->fBufferData(LOCAL_GL_ELEMENT_ARRAY_BUFFER,
                       mesh.mIndices.Length() * sizeof(uint16_t),
                       mesh.mIndices.Elements(),
                       LOCAL_GL_STATIC_DRAW);

      mVR.mDistortionIndexCount[eye] = mesh.mIndices.Length();
    }

    mVR.mConfiguration = hmdInfo->GetConfiguration();
  }

  int programIndex = textureTarget == LOCAL_GL_TEXTURE_2D ? 0 : 1;

  gl()->fScissor(aClipRect.x, FlipY(aClipRect.y + aClipRect.height),
                 aClipRect.width, aClipRect.height);

  // Clear out the entire area that we want to render; this ensures that
  // the layer will be opaque, even though the mesh geometry we'll be
  // drawing below won't cover the full rectangle.
  gl()->fClearColor(0.0, 0.0, 0.0, 1.0);
  gl()->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);

  // Make sure that the cached current program is reset for the
  // rest of the compositor, since we're using a custom program here
  ResetProgram();

  gl()->fUseProgram(mVR.mDistortionProgram[programIndex]);

  gl()->fEnableVertexAttribArray(mVR.mAPosition);
  gl()->fEnableVertexAttribArray(mVR.mATexCoord0);
  gl()->fEnableVertexAttribArray(mVR.mATexCoord1);
  gl()->fEnableVertexAttribArray(mVR.mATexCoord2);
  gl()->fEnableVertexAttribArray(mVR.mAGenericAttribs);

  surface->BindTexture(LOCAL_GL_TEXTURE0, mFBOTextureTarget);
  gl()->fUniform1i(mVR.mUTexture[programIndex], 0);

  Rect destRect = aTransform.TransformBounds(aRect);
  gfx::IntSize preDistortionSize = surface->GetInitSize(); // XXX source->GetSize()
  gfx::Size vpSize = destRect.Size();

  for (uint32_t eye = 0; eye < 2; eye++) {
    gfx::IntRect eyeViewport;
    eyeViewport.x = eye * preDistortionSize.width / 2;
    eyeViewport.y = 0;
    eyeViewport.width = preDistortionSize.width / 2;
    eyeViewport.height = preDistortionSize.height;

    hmdInfo->FillDistortionConstants(eye,
                                     preDistortionSize, eyeViewport,
                                     vpSize, destRect,
                                     shaderConstants);

    float height = 1.0f;
    float texScaleAndOffset[4] = { shaderConstants.eyeToSourceScaleAndOffset[0],
                                   shaderConstants.eyeToSourceScaleAndOffset[1],
                                   shaderConstants.eyeToSourceScaleAndOffset[2],
                                   shaderConstants.eyeToSourceScaleAndOffset[3] };
    if (textureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB) {
      texScaleAndOffset[0] *= preDistortionSize.width;
      texScaleAndOffset[1] *= preDistortionSize.height;
      texScaleAndOffset[2] *= preDistortionSize.width;
      texScaleAndOffset[3] *= preDistortionSize.height;
      height = preDistortionSize.height;
    }

    gl()->fUniform4fv(mVR.mUVRDestionatinScaleAndOffset[programIndex], 1, shaderConstants.destinationScaleAndOffset);
    gl()->fUniform4fv(mVR.mUVREyeToSource[programIndex], 1, texScaleAndOffset);
    gl()->fUniform1f(mVR.mUHeight[programIndex], height);

    gl()->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mVR.mDistortionVertices[eye]);

    /* This is for Oculus DistortionVertex */

    gl()->fVertexAttribPointer(mVR.mAPosition,  2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, sizeof(gfx::VRDistortionVertex), (void*) (sizeof(float) * 0));
    gl()->fVertexAttribPointer(mVR.mATexCoord0, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, sizeof(gfx::VRDistortionVertex), (void*) (sizeof(float) * 2));
    gl()->fVertexAttribPointer(mVR.mATexCoord1, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, sizeof(gfx::VRDistortionVertex), (void*) (sizeof(float) * 4));
    gl()->fVertexAttribPointer(mVR.mATexCoord2, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, sizeof(gfx::VRDistortionVertex), (void*) (sizeof(float) * 6));
    gl()->fVertexAttribPointer(mVR.mAGenericAttribs, 4, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, sizeof(gfx::VRDistortionVertex),  (void*) (sizeof(float) * 8));

    gl()->fBindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, mVR.mDistortionIndices[eye]);
    gl()->fDrawElements(LOCAL_GL_TRIANGLES, mVR.mDistortionIndexCount[eye], LOCAL_GL_UNSIGNED_SHORT, 0);
  }

  // Not clear if I should disable all of this; but going to do it and hope that
  // any later code will enable what it needs.
  gl()->fDisableVertexAttribArray(mVR.mAPosition);
  gl()->fDisableVertexAttribArray(mVR.mATexCoord0);
  gl()->fDisableVertexAttribArray(mVR.mATexCoord1);
  gl()->fDisableVertexAttribArray(mVR.mATexCoord2);
  gl()->fDisableVertexAttribArray(mVR.mAGenericAttribs);
}

} // namespace layers
} // namespace mozilla
