// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/gpu/client/gl_helper.h"

#include <queue>
#include <string>

#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "content/common/gpu/client/gl_helper_scaling.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "third_party/WebKit/public/platform/WebCString.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
#include "ui/gl/gl_bindings.h"

using blink::WebGLId;
using blink::WebGraphicsContext3D;

namespace {

// Helper class for allocating and holding an RGBA texture of a given
// size and an associated framebuffer.
class TextureFrameBufferPair {
 public:
  TextureFrameBufferPair(WebGraphicsContext3D* context,
                         gfx::Size size)
      : texture_(context, context->createTexture()),
        framebuffer_(context, context->createFramebuffer()),
        size_(size) {
    content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context,
                                                               texture_);
    context->texImage2D(GL_TEXTURE_2D,
                        0,
                        GL_RGBA,
                        size.width(),
                        size.height(),
                        0,
                        GL_RGBA,
                        GL_UNSIGNED_BYTE,
                        NULL);
    content::ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(
        context,
        framebuffer_);
    context->framebufferTexture2D(GL_FRAMEBUFFER,
                                  GL_COLOR_ATTACHMENT0,
                                  GL_TEXTURE_2D,
                                  texture_,
                                  0);
  }

  WebGLId texture() const { return texture_.id(); }
  WebGLId framebuffer() const { return framebuffer_.id(); }
  gfx::Size size() const { return size_; }

 private:
  content::ScopedTexture texture_;
  content::ScopedFramebuffer framebuffer_;
  gfx::Size size_;

  DISALLOW_COPY_AND_ASSIGN(TextureFrameBufferPair);
};

// Helper class for holding a scaler, a texture for the output of that
// scaler and an associated frame buffer. This is inteded to be used
// when the output of a scaler is to be sent to a readback.
class ScalerHolder {
 public:
  ScalerHolder(WebGraphicsContext3D* context,
               content::GLHelper::ScalerInterface *scaler)
      : texture_and_framebuffer_(context, scaler->DstSize()),
        scaler_(scaler) {
  }

  void Scale(blink::WebGLId src_texture) {
    scaler_->Scale(src_texture, texture_and_framebuffer_.texture());
  }

  content::GLHelper::ScalerInterface* scaler() const { return scaler_.get(); }
  TextureFrameBufferPair *texture_and_framebuffer() {
    return &texture_and_framebuffer_;
  }
  WebGLId texture() const { return texture_and_framebuffer_.texture(); }

 private:
  TextureFrameBufferPair texture_and_framebuffer_;
  scoped_ptr<content::GLHelper::ScalerInterface> scaler_;

  DISALLOW_COPY_AND_ASSIGN(ScalerHolder);
};

}  // namespace

namespace content {

// Implements GLHelper::CropScaleReadbackAndCleanTexture and encapsulates
// the data needed for it.
class GLHelper::CopyTextureToImpl :
      public base::SupportsWeakPtr<GLHelper::CopyTextureToImpl> {
 public:
  CopyTextureToImpl(WebGraphicsContext3D* context,
                    gpu::ContextSupport* context_support,
                    GLHelper* helper)
      : context_(context),
        context_support_(context_support),
        helper_(helper),
        flush_(context),
        max_draw_buffers_(0) {
    std::string extensions_string = " " +
        UTF16ToASCII(context_->getString(GL_EXTENSIONS)) + " ";
    if (extensions_string.find(" GL_EXT_draw_buffers ") != std::string::npos) {
      context_->getIntegerv(GL_MAX_DRAW_BUFFERS, &max_draw_buffers_);
    }
  }
  ~CopyTextureToImpl() {
    CancelRequests();
  }

  WebGLId ConsumeMailboxToTexture(const gpu::Mailbox& mailbox,
                                  uint32 sync_point) {
    return helper_->ConsumeMailboxToTexture(mailbox, sync_point);
  }

  void CropScaleReadbackAndCleanTexture(
      WebGLId src_texture,
      const gfx::Size& src_size,
      const gfx::Rect& src_subrect,
      const gfx::Size& dst_size,
      unsigned char* out,
      const base::Callback<void(bool)>& callback,
      GLHelper::ScalerQuality quality);

  void ReadbackTextureSync(WebGLId texture,
                           const gfx::Rect& src_rect,
                           unsigned char* out);

  // Reads back bytes from the currently bound frame buffer.
  // Note that dst_size is specified in bytes, not pixels.
  void ReadbackAsync(
      const gfx::Size& dst_size,
      int32 bytes_per_row,     // generally dst_size.width() * 4
      int32 row_stride_bytes,  // generally dst_size.width() * 4
      unsigned char* out,
      const base::Callback<void(bool)>& callback);

  void ReadbackPlane(TextureFrameBufferPair* source,
                     const scoped_refptr<media::VideoFrame>& target,
                     int plane,
                     int size_shift,
                     const gfx::Rect& dst_subrect,
                     const base::Callback<void(bool)>& callback);

  blink::WebGLId CopyAndScaleTexture(WebGLId texture,
                                      const gfx::Size& src_size,
                                      const gfx::Size& dst_size,
                                      bool vertically_flip_texture,
                                      GLHelper::ScalerQuality quality);

  ReadbackYUVInterface* CreateReadbackPipelineYUV(
      GLHelper::ScalerQuality quality,
      const gfx::Size& src_size,
      const gfx::Rect& src_subrect,
      const gfx::Size& dst_size,
      const gfx::Rect& dst_subrect,
      bool flip_vertically,
      bool use_mrt);

  // Returns the maximum number of draw buffers available,
  // 0 if GL_EXT_draw_buffers is not available.
  blink::WGC3Dint MaxDrawBuffers() const {
    return max_draw_buffers_;
  }

 private:
  // A single request to CropScaleReadbackAndCleanTexture.
  // The main thread can cancel the request, before it's handled by the helper
  // thread, by resetting the texture and pixels fields. Alternatively, the
  // thread marks that it handles the request by resetting the pixels field
  // (meaning it guarantees that the callback with be called).
  // In either case, the callback must be called exactly once, and the texture
  // must be deleted by the main thread context.
  struct Request {
    Request(const gfx::Size& size_,
            int32 bytes_per_row_,
            int32 row_stride_bytes_,
            unsigned char* pixels_,
            const base::Callback<void(bool)>& callback_)
        : done(false),
          size(size_),
          bytes_per_row(bytes_per_row_),
          row_stride_bytes(row_stride_bytes_),
          pixels(pixels_),
          callback(callback_),
          buffer(0),
          query(0) {
    }

    bool done;
    gfx::Size size;
    int bytes_per_row;
    int row_stride_bytes;
    unsigned char* pixels;
    base::Callback<void(bool)> callback;
    GLuint buffer;
    blink::WebGLId query;
  };

  // A readback pipeline that also converts the data to YUV before
  // reading it back.
  class ReadbackYUVImpl : public ReadbackYUVInterface {
   public:
    ReadbackYUVImpl(WebGraphicsContext3D* context,
                    CopyTextureToImpl* copy_impl,
                    GLHelperScaling* scaler_impl,
                    GLHelper::ScalerQuality quality,
                    const gfx::Size& src_size,
                    const gfx::Rect& src_subrect,
                    const gfx::Size& dst_size,
                    const gfx::Rect& dst_subrect,
                    bool flip_vertically);

    virtual void ReadbackYUV(
        const gpu::Mailbox& mailbox,
        uint32 sync_point,
        const scoped_refptr<media::VideoFrame>& target,
        const base::Callback<void(bool)>& callback) OVERRIDE;

    virtual ScalerInterface* scaler() OVERRIDE {
      return scaler_.scaler();
    }

   private:
    WebGraphicsContext3D* context_;
    CopyTextureToImpl* copy_impl_;
    gfx::Size dst_size_;
    gfx::Rect dst_subrect_;
    ScalerHolder scaler_;
    ScalerHolder y_;
    ScalerHolder u_;
    ScalerHolder v_;

    DISALLOW_COPY_AND_ASSIGN(ReadbackYUVImpl);
  };

  // A readback pipeline that also converts the data to YUV before
  // reading it back. This one uses Multiple Render Targets, which
  // may not be supported on all platforms.
  class ReadbackYUV_MRT : public ReadbackYUVInterface {
   public:
    ReadbackYUV_MRT(WebGraphicsContext3D* context,
                    CopyTextureToImpl* copy_impl,
                    GLHelperScaling* scaler_impl,
                    GLHelper::ScalerQuality quality,
                    const gfx::Size& src_size,
                    const gfx::Rect& src_subrect,
                    const gfx::Size& dst_size,
                    const gfx::Rect& dst_subrect,
                    bool flip_vertically);

    virtual void ReadbackYUV(
        const gpu::Mailbox& mailbox,
        uint32 sync_point,
        const scoped_refptr<media::VideoFrame>& target,
        const base::Callback<void(bool)>& callback) OVERRIDE;

    virtual ScalerInterface* scaler() OVERRIDE {
      return scaler_.scaler();
    }

   private:
    WebGraphicsContext3D* context_;
    CopyTextureToImpl* copy_impl_;
    gfx::Size dst_size_;
    gfx::Rect dst_subrect_;
    GLHelper::ScalerQuality quality_;
    ScalerHolder scaler_;
    scoped_ptr<content::GLHelperScaling::ShaderInterface> pass1_shader_;
    scoped_ptr<content::GLHelperScaling::ShaderInterface> pass2_shader_;
    TextureFrameBufferPair y_;
    ScopedTexture uv_;
    TextureFrameBufferPair u_;
    TextureFrameBufferPair v_;

    DISALLOW_COPY_AND_ASSIGN(ReadbackYUV_MRT);
  };

  // Copies the block of pixels specified with |src_subrect| from |src_texture|,
  // scales it to |dst_size|, writes it into a texture, and returns its ID.
  // |src_size| is the size of |src_texture|.
  WebGLId ScaleTexture(WebGLId src_texture,
                       const gfx::Size& src_size,
                       const gfx::Rect& src_subrect,
                       const gfx::Size& dst_size,
                       bool vertically_flip_texture,
                       bool swizzle,
                       GLHelper::ScalerQuality quality);

  static void nullcallback(bool success) {}
  void ReadbackDone(Request *request);
  void FinishRequest(Request* request, bool result);
  void CancelRequests();

  static const float kRGBtoYColorWeights[];
  static const float kRGBtoUColorWeights[];
  static const float kRGBtoVColorWeights[];

  WebGraphicsContext3D* context_;
  gpu::ContextSupport* context_support_;
  GLHelper* helper_;

  // A scoped flush that will ensure all resource deletions are flushed when
  // this object is destroyed. Must be declared before other Scoped* fields.
  ScopedFlush flush_;

  std::queue<Request*> request_queue_;
  blink::WGC3Dint max_draw_buffers_;
};

GLHelper::ScalerInterface* GLHelper::CreateScaler(
    ScalerQuality quality,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    bool vertically_flip_texture,
    bool swizzle) {
  InitScalerImpl();
  return scaler_impl_->CreateScaler(quality,
                                    src_size,
                                    src_subrect,
                                    dst_size,
                                    vertically_flip_texture,
                                    swizzle);
}

WebGLId GLHelper::CopyTextureToImpl::ScaleTexture(
    WebGLId src_texture,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    bool vertically_flip_texture,
    bool swizzle,
    GLHelper::ScalerQuality quality) {
  scoped_ptr<ScalerInterface> scaler(
      helper_->CreateScaler(quality,
                            src_size,
                            src_subrect,
                            dst_size,
                            vertically_flip_texture,
                            swizzle));

  WebGLId dst_texture = context_->createTexture();
  {
    ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, dst_texture);
    context_->texImage2D(GL_TEXTURE_2D,
                         0,
                         GL_RGBA,
                         dst_size.width(),
                         dst_size.height(),
                         0,
                         GL_RGBA,
                         GL_UNSIGNED_BYTE,
                         NULL);
  }
  scaler->Scale(src_texture, dst_texture);
  return dst_texture;
}

void GLHelper::CopyTextureToImpl::ReadbackAsync(
    const gfx::Size& dst_size,
    int32 bytes_per_row,
    int32 row_stride_bytes,
    unsigned char* out,
    const base::Callback<void(bool)>& callback) {
  Request* request = new Request(dst_size,
                                 bytes_per_row,
                                 row_stride_bytes,
                                 out,
                                 callback);
  request_queue_.push(request);
  request->buffer = context_->createBuffer();
  context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
                       request->buffer);
  context_->bufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
                       4 * dst_size.GetArea(),
                       NULL,
                       GL_STREAM_READ);

  request->query = context_->createQueryEXT();
  context_->beginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM,
                          request->query);
  context_->readPixels(0, 0, dst_size.width(), dst_size.height(),
                       GL_RGBA, GL_UNSIGNED_BYTE, NULL);
  context_->endQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
  context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
  context_support_->SignalQuery(
      request->query,
      base::Bind(&CopyTextureToImpl::ReadbackDone, AsWeakPtr(), request));
}


void GLHelper::CopyTextureToImpl::CropScaleReadbackAndCleanTexture(
    WebGLId src_texture,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    unsigned char* out,
    const base::Callback<void(bool)>& callback,
    GLHelper::ScalerQuality quality) {
  WebGLId texture = ScaleTexture(src_texture,
                                 src_size,
                                 src_subrect,
                                 dst_size,
                                 true,
#if (SK_R32_SHIFT == 16) && !SK_B32_SHIFT
                                 true,
#else
                                 false,
#endif
                                 quality);
  ScopedFramebuffer dst_framebuffer(context_, context_->createFramebuffer());
  ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(context_,
                                                             dst_framebuffer);
  ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
  context_->framebufferTexture2D(GL_FRAMEBUFFER,
                                 GL_COLOR_ATTACHMENT0,
                                 GL_TEXTURE_2D,
                                 texture,
                                 0);
  ReadbackAsync(dst_size,
                dst_size.width() * 4,
                dst_size.width() * 4,
                out,
                callback);
  context_->deleteTexture(texture);
}

void GLHelper::CopyTextureToImpl::ReadbackTextureSync(
    WebGLId texture,
    const gfx::Rect& src_rect,
    unsigned char* out) {
  ScopedFramebuffer dst_framebuffer(context_, context_->createFramebuffer());
  ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(context_,
                                                             dst_framebuffer);
  ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
  context_->framebufferTexture2D(GL_FRAMEBUFFER,
                                 GL_COLOR_ATTACHMENT0,
                                 GL_TEXTURE_2D,
                                 texture,
                                 0);
  context_->readPixels(src_rect.x(),
                       src_rect.y(),
                       src_rect.width(),
                       src_rect.height(),
                       GL_RGBA,
                       GL_UNSIGNED_BYTE,
                       out);
}

blink::WebGLId GLHelper::CopyTextureToImpl::CopyAndScaleTexture(
    WebGLId src_texture,
    const gfx::Size& src_size,
    const gfx::Size& dst_size,
    bool vertically_flip_texture,
    GLHelper::ScalerQuality quality) {
  return ScaleTexture(src_texture,
                      src_size,
                      gfx::Rect(src_size),
                      dst_size,
                      vertically_flip_texture,
                      false,
                      quality);
}

void GLHelper::CopyTextureToImpl::ReadbackDone(Request* finished_request) {
  TRACE_EVENT0("mirror",
               "GLHelper::CopyTextureToImpl::CheckReadbackFramebufferComplete");
  finished_request->done = true;

  // We process transfer requests in the order they were received, regardless
  // of the order we get the callbacks in.
  while (!request_queue_.empty()) {
    Request* request = request_queue_.front();
    if (!request->done) {
      break;
    }

    bool result = false;
    if (request->buffer != 0) {
      context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
                           request->buffer);
      unsigned char* data = static_cast<unsigned char *>(
          context_->mapBufferCHROMIUM(
              GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, GL_READ_ONLY));
      if (data) {
        result = true;
        if (request->bytes_per_row == request->size.width() * 4 &&
            request->bytes_per_row == request->row_stride_bytes) {
          memcpy(request->pixels, data, request->size.GetArea() * 4);
        } else {
          unsigned char* out = request->pixels;
          for (int y = 0; y < request->size.height(); y++) {
            memcpy(out, data, request->bytes_per_row);
            out += request->row_stride_bytes;
            data += request->size.width() * 4;
          }
        }
        context_->unmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
      }
      context_->bindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }

    FinishRequest(request, result);
  }
}

void GLHelper::CopyTextureToImpl::FinishRequest(Request* request,
                                                bool result) {
  TRACE_EVENT0("mirror", "GLHelper::CopyTextureToImpl::FinishRequest");
  DCHECK(request_queue_.front() == request);
  request_queue_.pop();
  request->callback.Run(result);
  ScopedFlush flush(context_);
  if (request->query != 0) {
    context_->deleteQueryEXT(request->query);
    request->query = 0;
  }
  if (request->buffer != 0) {
    context_->deleteBuffer(request->buffer);
    request->buffer = 0;
  }
  delete request;
}

void GLHelper::CopyTextureToImpl::CancelRequests() {
  while (!request_queue_.empty()) {
    Request* request = request_queue_.front();
    FinishRequest(request, false);
  }
}

GLHelper::GLHelper(blink::WebGraphicsContext3D* context,
                   gpu::ContextSupport* context_support)
    : context_(context),
      context_support_(context_support) {
}

GLHelper::~GLHelper() {
}

void GLHelper::CropScaleReadbackAndCleanTexture(
    WebGLId src_texture,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    unsigned char* out,
    const base::Callback<void(bool)>& callback) {
  InitCopyTextToImpl();
  copy_texture_to_impl_->CropScaleReadbackAndCleanTexture(
      src_texture,
      src_size,
      src_subrect,
      dst_size,
      out,
      callback,
      GLHelper::SCALER_QUALITY_FAST);
}

void GLHelper::CropScaleReadbackAndCleanMailbox(
    const gpu::Mailbox& src_mailbox,
    uint32 sync_point,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    unsigned char* out,
    const base::Callback<void(bool)>& callback) {
  WebGLId mailbox_texture = ConsumeMailboxToTexture(src_mailbox, sync_point);
  CropScaleReadbackAndCleanTexture(
      mailbox_texture, src_size, src_subrect, dst_size, out, callback);
  context_->deleteTexture(mailbox_texture);
}

void GLHelper::ReadbackTextureSync(blink::WebGLId texture,
                                   const gfx::Rect& src_rect,
                                   unsigned char* out) {
  InitCopyTextToImpl();
  copy_texture_to_impl_->ReadbackTextureSync(texture,
                                             src_rect,
                                             out);
}

blink::WebGLId GLHelper::CopyTexture(blink::WebGLId texture,
                                      const gfx::Size& size) {
  InitCopyTextToImpl();
  return copy_texture_to_impl_->CopyAndScaleTexture(
      texture,
      size,
      size,
      false,
      GLHelper::SCALER_QUALITY_FAST);
}

blink::WebGLId GLHelper::CopyAndScaleTexture(
    blink::WebGLId texture,
    const gfx::Size& src_size,
    const gfx::Size& dst_size,
    bool vertically_flip_texture,
    ScalerQuality quality) {
  InitCopyTextToImpl();
  return copy_texture_to_impl_->CopyAndScaleTexture(texture,
                                                    src_size,
                                                    dst_size,
                                                    vertically_flip_texture,
                                                    quality);
}

WebGLId GLHelper::CompileShaderFromSource(
    const blink::WGC3Dchar* source,
    blink::WGC3Denum type) {
  ScopedShader shader(context_, context_->createShader(type));
  context_->shaderSource(shader, source);
  context_->compileShader(shader);
  blink::WGC3Dint compile_status = 0;
  context_->getShaderiv(shader, GL_COMPILE_STATUS, &compile_status);
  if (!compile_status) {
    LOG(ERROR) << std::string(context_->getShaderInfoLog(shader).utf8());
    return 0;
  }
  return shader.Detach();
}

void GLHelper::InitCopyTextToImpl() {
  // Lazily initialize |copy_texture_to_impl_|
  if (!copy_texture_to_impl_)
    copy_texture_to_impl_.reset(
        new CopyTextureToImpl(context_, context_support_, this));
}

void GLHelper::InitScalerImpl() {
  // Lazily initialize |scaler_impl_|
  if (!scaler_impl_)
    scaler_impl_.reset(new GLHelperScaling(context_, this));
}

blink::WGC3Dint GLHelper::MaxDrawBuffers() {
  InitCopyTextToImpl();
  return copy_texture_to_impl_->MaxDrawBuffers();
}

void GLHelper::CopySubBufferDamage(blink::WebGLId texture,
                                   blink::WebGLId previous_texture,
                                   const SkRegion& new_damage,
                                   const SkRegion& old_damage) {
  SkRegion region(old_damage);
  if (region.op(new_damage, SkRegion::kDifference_Op)) {
    ScopedFramebuffer dst_framebuffer(context_,
                                      context_->createFramebuffer());
    ScopedFramebufferBinder<GL_FRAMEBUFFER> framebuffer_binder(context_,
                                                               dst_framebuffer);
    ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
    context_->framebufferTexture2D(GL_FRAMEBUFFER,
                                   GL_COLOR_ATTACHMENT0,
                                   GL_TEXTURE_2D,
                                   previous_texture,
                                   0);
    for (SkRegion::Iterator it(region); !it.done(); it.next()) {
      const SkIRect& rect = it.rect();
      context_->copyTexSubImage2D(GL_TEXTURE_2D, 0,
                                  rect.x(), rect.y(),
                                  rect.x(), rect.y(),
                                  rect.width(), rect.height());
    }
    context_->flush();
  }
}

blink::WebGLId GLHelper::CreateTexture() {
  blink::WebGLId texture = context_->createTexture();
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_,
                                                             texture);
  context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  context_->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  return texture;
}

void GLHelper::DeleteTexture(blink::WebGLId texture_id) {
  context_->deleteTexture(texture_id);
}

uint32 GLHelper::InsertSyncPoint() { return context_->insertSyncPoint(); }

void GLHelper::WaitSyncPoint(uint32 sync_point) {
  context_->waitSyncPoint(sync_point);
}

gpu::Mailbox GLHelper::ProduceMailboxFromTexture(blink::WebGLId texture_id,
                                                 uint32* sync_point) {
  gpu::Mailbox mailbox;
  context_->genMailboxCHROMIUM(mailbox.name);
  if (mailbox.IsZero()) {
    *sync_point = 0;
    return mailbox;
  }
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_,
                                                             texture_id);
  context_->produceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
  *sync_point = context_->insertSyncPoint();
  return mailbox;
}

blink::WebGLId GLHelper::ConsumeMailboxToTexture(const gpu::Mailbox& mailbox,
                                                  uint32 sync_point) {
  if (mailbox.IsZero())
    return 0;
  if (sync_point)
    context_->waitSyncPoint(sync_point);
  blink::WebGLId texture = CreateTexture();
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_,
                                                             texture);
  context_->consumeTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name);
  return texture;
}

void GLHelper::ResizeTexture(blink::WebGLId texture, const gfx::Size& size) {
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
  context_->texImage2D(GL_TEXTURE_2D, 0, GL_RGB,
                       size.width(), size.height(), 0,
                       GL_RGB, GL_UNSIGNED_BYTE, NULL);
}

void GLHelper::CopyTextureSubImage(blink::WebGLId texture,
                                   const gfx::Rect& rect) {
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
  context_->copyTexSubImage2D(GL_TEXTURE_2D, 0,
                              rect.x(), rect.y(),
                              rect.x(), rect.y(), rect.width(), rect.height());
}

void GLHelper::CopyTextureFullImage(blink::WebGLId texture,
                                    const gfx::Size& size) {
  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context_, texture);
  context_->copyTexImage2D(GL_TEXTURE_2D, 0,
                           GL_RGB,
                           0, 0,
                           size.width(), size.height(), 0);
}

void GLHelper::CopyTextureToImpl::ReadbackPlane(
    TextureFrameBufferPair* source,
    const scoped_refptr<media::VideoFrame>& target,
    int plane,
    int size_shift,
    const gfx::Rect& dst_subrect,
    const base::Callback<void(bool)>& callback) {
  context_->bindFramebuffer(GL_FRAMEBUFFER, source->framebuffer());
  size_t offset = target->stride(plane) * (dst_subrect.y() >> size_shift) +
      (dst_subrect.x() >> size_shift);
  ReadbackAsync(
      source->size(),
      dst_subrect.width() >> size_shift,
      target->stride(plane),
      target->data(plane) + offset,
      callback);
}

const float GLHelper::CopyTextureToImpl::kRGBtoYColorWeights[] = {
  0.257f, 0.504f, 0.098f, 0.0625f
};
const float GLHelper::CopyTextureToImpl::kRGBtoUColorWeights[] = {
  -0.148f, -0.291f, 0.439f, 0.5f
};
const float GLHelper::CopyTextureToImpl::kRGBtoVColorWeights[] = {
  0.439f, -0.368f, -0.071f, 0.5f
};

// YUV readback constructors. Initiates the main scaler pipeline and
// one planar scaler for each of the Y, U and V planes.
GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUVImpl(
    WebGraphicsContext3D* context,
    CopyTextureToImpl* copy_impl,
    GLHelperScaling* scaler_impl,
    GLHelper::ScalerQuality quality,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    const gfx::Rect& dst_subrect,
    bool flip_vertically)
    : context_(context),
      copy_impl_(copy_impl),
      dst_size_(dst_size),
      dst_subrect_(dst_subrect),
      scaler_(context, scaler_impl->CreateScaler(
          quality,
          src_size,
          src_subrect,
          dst_subrect.size(),
          flip_vertically,
          false)),
      y_(context, scaler_impl->CreatePlanarScaler(
          dst_subrect.size(),
          gfx::Rect(0, 0,
                    (dst_subrect.width() + 3) & ~3,
                    dst_subrect.height()),
          gfx::Size((dst_subrect.width() + 3) / 4,
                    dst_subrect.height()),
          false,
          kRGBtoYColorWeights)),
      u_(context, scaler_impl->CreatePlanarScaler(
          dst_subrect.size(),
          gfx::Rect(0, 0,
                    (dst_subrect.width() + 7) & ~7,
                    (dst_subrect.height() + 1) & ~1),
          gfx::Size((dst_subrect.width() + 7) / 8,
                    (dst_subrect.height() + 1) / 2),
          false,
          kRGBtoUColorWeights)),
      v_(context, scaler_impl->CreatePlanarScaler(
          dst_subrect.size(),
          gfx::Rect(0, 0,
                    (dst_subrect.width() + 7) & ~7,
                    (dst_subrect.height() + 1) & ~1),
          gfx::Size((dst_subrect.width() + 7) / 8,
                    (dst_subrect.height() + 1) / 2),
          false,
          kRGBtoVColorWeights)) {
  DCHECK(!(dst_size.width() & 1));
  DCHECK(!(dst_size.height() & 1));
  DCHECK(!(dst_subrect.width() & 1));
  DCHECK(!(dst_subrect.height() & 1));
  DCHECK(!(dst_subrect.x() & 1));
  DCHECK(!(dst_subrect.y() & 1));
}

static void CallbackKeepingVideoFrameAlive(
    scoped_refptr<media::VideoFrame> video_frame,
    const base::Callback<void(bool)>& callback,
    bool success) {
  callback.Run(success);
}

void GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUV(
    const gpu::Mailbox& mailbox,
    uint32 sync_point,
    const scoped_refptr<media::VideoFrame>& target,
    const base::Callback<void(bool)>& callback) {
  WebGLId mailbox_texture =
      copy_impl_->ConsumeMailboxToTexture(mailbox, sync_point);

  // Scale texture to right size.
  scaler_.Scale(mailbox_texture);
  context_->deleteTexture(mailbox_texture);

  // Convert the scaled texture in to Y, U and V planes.
  y_.Scale(scaler_.texture());
  u_.Scale(scaler_.texture());
  v_.Scale(scaler_.texture());

  if (target->coded_size() != dst_size_) {
    DCHECK(target->coded_size() == dst_size_);
    LOG(ERROR) << "ReadbackYUV size error!";
    callback.Run(false);
    return;
  }

  // Read back planes, one at a time. Keep the video frame alive while doing the
  // readback.
  copy_impl_->ReadbackPlane(y_.texture_and_framebuffer(),
                            target,
                            media::VideoFrame::kYPlane,
                            0,
                            dst_subrect_,
                            base::Bind(&nullcallback));
  copy_impl_->ReadbackPlane(u_.texture_and_framebuffer(),
                            target,
                            media::VideoFrame::kUPlane,
                            1,
                            dst_subrect_,
                            base::Bind(&nullcallback));
  copy_impl_->ReadbackPlane(v_.texture_and_framebuffer(),
                            target,
                            media::VideoFrame::kVPlane,
                            1,
                            dst_subrect_,
                            base::Bind(&CallbackKeepingVideoFrameAlive,
                                       target,
                                       callback));
  context_->bindFramebuffer(GL_FRAMEBUFFER, 0);
  media::LetterboxYUV(target, dst_subrect_);
}

// YUV readback constructors. Initiates the main scaler pipeline and
// one planar scaler for each of the Y, U and V planes.
GLHelper::CopyTextureToImpl::ReadbackYUV_MRT::ReadbackYUV_MRT(
    WebGraphicsContext3D* context,
    CopyTextureToImpl* copy_impl,
    GLHelperScaling* scaler_impl,
    GLHelper::ScalerQuality quality,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    const gfx::Rect& dst_subrect,
    bool flip_vertically)
    : context_(context),
      copy_impl_(copy_impl),
      dst_size_(dst_size),
      dst_subrect_(dst_subrect),
      quality_(quality),
      scaler_(context, scaler_impl->CreateScaler(
          quality,
          src_size,
          src_subrect,
          dst_subrect.size(),
          false,
          false)),
      pass1_shader_(scaler_impl->CreateYuvMrtShader(
          dst_subrect.size(),
          gfx::Rect(0, 0,
                    (dst_subrect.width() + 3) & ~3,
                    dst_subrect.height()),
          gfx::Size((dst_subrect.width() + 3) / 4,
                    dst_subrect.height()),
          flip_vertically,
          GLHelperScaling::SHADER_YUV_MRT_PASS1)),
      pass2_shader_(scaler_impl->CreateYuvMrtShader(
          gfx::Size((dst_subrect.width() + 3) / 4,
                    dst_subrect.height()),
          gfx::Rect(0, 0,
                    (dst_subrect.width() + 7) / 8 * 2,
                    dst_subrect.height()),
          gfx::Size((dst_subrect.width() + 7) / 8,
                    (dst_subrect.height() + 1) / 2),
          false,
          GLHelperScaling::SHADER_YUV_MRT_PASS2)),
      y_(context, gfx::Size((dst_subrect.width() + 3) / 4,
                            dst_subrect.height())),
      uv_(context, context->createTexture()),
      u_(context, gfx::Size((dst_subrect.width() + 7) / 8,
                            (dst_subrect.height() + 1) / 2)),
      v_(context, gfx::Size((dst_subrect.width() + 7) / 8,
                            (dst_subrect.height() + 1) / 2)) {

  content::ScopedTextureBinder<GL_TEXTURE_2D> texture_binder(context, uv_);
  context->texImage2D(GL_TEXTURE_2D,
                      0,
                      GL_RGBA,
                      (dst_subrect.width() + 3) / 4,
                      dst_subrect.height(),
                      0,
                      GL_RGBA,
                      GL_UNSIGNED_BYTE,
                      NULL);

  DCHECK(!(dst_size.width() & 1));
  DCHECK(!(dst_size.height() & 1));
  DCHECK(!(dst_subrect.width() & 1));
  DCHECK(!(dst_subrect.height() & 1));
  DCHECK(!(dst_subrect.x() & 1));
  DCHECK(!(dst_subrect.y() & 1));
}

void GLHelper::CopyTextureToImpl::ReadbackYUV_MRT::ReadbackYUV(
    const gpu::Mailbox& mailbox,
    uint32 sync_point,
    const scoped_refptr<media::VideoFrame>& target,
    const base::Callback<void(bool)>& callback) {
  WebGLId mailbox_texture =
      copy_impl_->ConsumeMailboxToTexture(mailbox, sync_point);

  WebGLId texture;
  if (quality_ == GLHelper::SCALER_QUALITY_FAST) {
    // Optimization: SCALER_QUALITY_FAST is just a single bilinear
    // pass, which pass1_shader_ can do just as well, so let's skip
    // the actual scaling in that case.
    texture = mailbox_texture;
  } else {
    // Scale texture to right size.
    scaler_.Scale(mailbox_texture);
    texture = scaler_.texture();
  }


  std::vector<blink::WebGLId> outputs(2);
  // Convert the scaled texture in to Y, U and V planes.
  outputs[0] = y_.texture();
  outputs[1] = uv_;
  pass1_shader_->Execute(texture, outputs);

  context_->deleteTexture(mailbox_texture);

  outputs[0] = u_.texture();
  outputs[1] = v_.texture();
  pass2_shader_->Execute(uv_, outputs);

  if (target->coded_size() != dst_size_) {
    DCHECK(target->coded_size() == dst_size_);
    LOG(ERROR) << "ReadbackYUV size error!";
    callback.Run(false);
    return;
  }

  // Read back planes, one at a time.
  copy_impl_->ReadbackPlane(&y_,
                            target,
                            media::VideoFrame::kYPlane,
                            0,
                            dst_subrect_,
                            base::Bind(&nullcallback));
  copy_impl_->ReadbackPlane(&u_,
                            target,
                            media::VideoFrame::kUPlane,
                            1,
                            dst_subrect_,
                            base::Bind(&nullcallback));
  copy_impl_->ReadbackPlane(&v_,
                            target,
                            media::VideoFrame::kVPlane,
                            1,
                            dst_subrect_,
                            base::Bind(&CallbackKeepingVideoFrameAlive,
                                       target,
                                       callback));
  context_->bindFramebuffer(GL_FRAMEBUFFER, 0);
  media::LetterboxYUV(target, dst_subrect_);
}

ReadbackYUVInterface*
GLHelper::CopyTextureToImpl::CreateReadbackPipelineYUV(
    GLHelper::ScalerQuality quality,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    const gfx::Rect& dst_subrect,
    bool flip_vertically,
    bool use_mrt) {
  helper_->InitScalerImpl();
  if (max_draw_buffers_ >= 2 && use_mrt) {
    return new ReadbackYUV_MRT(
        context_,
        this,
        helper_->scaler_impl_.get(),
        quality,
        src_size,
        src_subrect,
        dst_size,
        dst_subrect,
        flip_vertically);
  }
  return new ReadbackYUVImpl(
      context_,
      this,
      helper_->scaler_impl_.get(),
      quality,
      src_size,
      src_subrect,
      dst_size,
      dst_subrect,
      flip_vertically);
}

ReadbackYUVInterface*
GLHelper::CreateReadbackPipelineYUV(
    ScalerQuality quality,
    const gfx::Size& src_size,
    const gfx::Rect& src_subrect,
    const gfx::Size& dst_size,
    const gfx::Rect& dst_subrect,
    bool flip_vertically,
    bool use_mrt) {
  InitCopyTextToImpl();
  return copy_texture_to_impl_->CreateReadbackPipelineYUV(
      quality,
      src_size,
      src_subrect,
      dst_size,
      dst_subrect,
      flip_vertically,
      use_mrt);
}

}  // namespace content