// Copyright 2014 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/renderer/pepper/video_decoder_shim.h"

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES2/gl2extchromium.h>
#include <utility>

#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/common/gpu/client/context_provider_command_buffer.h"
#include "content/public/renderer/render_thread.h"
#include "content/renderer/pepper/pepper_video_decoder_host.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "media/base/cdm_context.h"
#include "media/base/decoder_buffer.h"
#include "media/base/limits.h"
#include "media/base/media_util.h"
#include "media/base/video_decoder.h"
#include "media/filters/ffmpeg_video_decoder.h"
#include "media/filters/vpx_video_decoder.h"
#include "media/renderers/skcanvas_video_renderer.h"
#include "media/video/picture.h"
#include "media/video/video_decode_accelerator.h"
#include "ppapi/c/pp_errors.h"
#include "third_party/skia/include/gpu/GrTypes.h"

namespace content {

static const uint32_t kGrInvalidateState =
    kRenderTarget_GrGLBackendState | kTextureBinding_GrGLBackendState |
    kView_GrGLBackendState | kVertex_GrGLBackendState |
    kProgram_GrGLBackendState | kPixelStore_GrGLBackendState;

namespace {

bool IsCodecSupported(media::VideoCodec codec) {
#if !defined(MEDIA_DISABLE_LIBVPX)
  if (codec == media::kCodecVP9)
    return true;
#endif

#if !defined(MEDIA_DISABLE_FFMPEG) && !defined(DISABLE_FFMPEG_VIDEO_DECODERS)
  return media::FFmpegVideoDecoder::IsCodecSupported(codec);
#else
  return false;
#endif
}

}  // namespace

// YUV->RGB converter class using a shader and FBO.
class VideoDecoderShim::YUVConverter {
 public:
  YUVConverter(scoped_refptr<ContextProviderCommandBuffer>);
  ~YUVConverter();
  bool Initialize();
  void Convert(const scoped_refptr<media::VideoFrame>& frame, GLuint tex_out);

 private:
  GLuint CreateShader();
  GLuint CompileShader(const char* name, GLuint type, const char* code);
  GLuint CreateProgram(const char* name, GLuint vshader, GLuint fshader);
  GLuint CreateTexture();

  scoped_refptr<ContextProviderCommandBuffer> context_provider_;
  gpu::gles2::GLES2Interface* gl_;
  GLuint frame_buffer_;
  GLuint vertex_buffer_;
  GLuint program_;

  GLuint y_texture_;
  GLuint u_texture_;
  GLuint v_texture_;
  GLuint a_texture_;

  GLuint internal_format_;
  GLuint format_;
  media::VideoPixelFormat video_format_;

  GLuint y_width_;
  GLuint y_height_;

  GLuint uv_width_;
  GLuint uv_height_;
  uint32_t uv_height_divisor_;
  uint32_t uv_width_divisor_;

  GLint yuv_matrix_loc_;
  GLint yuv_adjust_loc_;

  DISALLOW_COPY_AND_ASSIGN(YUVConverter);
};

VideoDecoderShim::YUVConverter::YUVConverter(
    scoped_refptr<ContextProviderCommandBuffer> context_provider)
    : context_provider_(std::move(context_provider)),
      gl_(context_provider_->ContextGL()),
      frame_buffer_(0),
      vertex_buffer_(0),
      program_(0),
      y_texture_(0),
      u_texture_(0),
      v_texture_(0),
      a_texture_(0),
      internal_format_(0),
      format_(0),
      video_format_(media::PIXEL_FORMAT_UNKNOWN),
      y_width_(2),
      y_height_(2),
      uv_width_(2),
      uv_height_(2),
      uv_height_divisor_(1),
      uv_width_divisor_(1),
      yuv_matrix_loc_(0),
      yuv_adjust_loc_(0) {
  DCHECK(gl_);
}

VideoDecoderShim::YUVConverter::~YUVConverter() {
  if (y_texture_)
    gl_->DeleteTextures(1, &y_texture_);

  if (u_texture_)
    gl_->DeleteTextures(1, &u_texture_);

  if (v_texture_)
    gl_->DeleteTextures(1, &v_texture_);

  if (a_texture_)
    gl_->DeleteTextures(1, &a_texture_);

  if (frame_buffer_)
    gl_->DeleteFramebuffers(1, &frame_buffer_);

  if (vertex_buffer_)
    gl_->DeleteBuffers(1, &vertex_buffer_);

  if (program_)
    gl_->DeleteProgram(program_);
}

GLuint VideoDecoderShim::YUVConverter::CreateTexture() {
  GLuint tex = 0;

  gl_->GenTextures(1, &tex);
  gl_->BindTexture(GL_TEXTURE_2D, tex);

  // Create texture with default size - will be resized upon first frame.
  gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, 2, 2, 0, format_,
                  GL_UNSIGNED_BYTE, NULL);

  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  gl_->BindTexture(GL_TEXTURE_2D, 0);

  return tex;
}

GLuint VideoDecoderShim::YUVConverter::CompileShader(const char* name,
                                                     GLuint type,
                                                     const char* code) {
  GLuint shader = gl_->CreateShader(type);

  gl_->ShaderSource(shader, 1, (const GLchar**)&code, NULL);
  gl_->CompileShader(shader);

#ifndef NDEBUG
  GLint status = 0;

  gl_->GetShaderiv(shader, GL_COMPILE_STATUS, &status);
  if (status != GL_TRUE) {
    GLint max_length = 0;
    GLint actual_length = 0;
    gl_->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length);

    // The max_length includes the NULL character.
    std::string error_log(max_length, 0);
    gl_->GetShaderInfoLog(shader, max_length, &actual_length, &error_log[0]);

    LOG(ERROR) << name << " shader compilation failed: " << error_log.c_str();
    gl_->DeleteShader(shader);
    return 0;
  }
#endif

  return shader;
}

GLuint VideoDecoderShim::YUVConverter::CreateProgram(const char* name,
                                                     GLuint vshader,
                                                     GLuint fshader) {
  GLuint program = gl_->CreateProgram();
  gl_->AttachShader(program, vshader);
  gl_->AttachShader(program, fshader);

  gl_->BindAttribLocation(program, 0, "position");

  gl_->LinkProgram(program);

#ifndef NDEBUG
  GLint status = 0;

  gl_->GetProgramiv(program, GL_LINK_STATUS, &status);
  if (status != GL_TRUE) {
    GLint max_length = 0;
    GLint actual_length = 0;
    gl_->GetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length);

    // The max_length includes the NULL character.
    std::string error_log(max_length, 0);
    gl_->GetProgramInfoLog(program, max_length, &actual_length, &error_log[0]);

    LOG(ERROR) << name << " program linking failed: " << error_log.c_str();
    return 0;
  }
#endif

  return program;
}

GLuint VideoDecoderShim::YUVConverter::CreateShader() {
  const char* vert_shader =
      "precision mediump float;\n"
      "attribute vec2 position;\n"
      "varying vec2 texcoord;\n"
      "void main()\n"
      "{\n"
      "    gl_Position = vec4( position.xy, 0, 1 );\n"
      "    texcoord = position*0.5+0.5;\n"
      "}";

  const char* frag_shader =
      "precision mediump float;\n"
      "varying vec2 texcoord;\n"
      "uniform sampler2D y_sampler;\n"
      "uniform sampler2D u_sampler;\n"
      "uniform sampler2D v_sampler;\n"
      "uniform sampler2D a_sampler;\n"
      "uniform mat3 yuv_matrix;\n"
      "uniform vec3 yuv_adjust;\n"
      "void main()\n"
      "{\n"
      "  vec3 yuv = vec3(texture2D(y_sampler, texcoord).x,\n"
      "                  texture2D(u_sampler, texcoord).x,\n"
      "                  texture2D(v_sampler, texcoord).x) +\n"
      "                  yuv_adjust;\n"
      "  gl_FragColor = vec4(yuv_matrix * yuv, texture2D(a_sampler, "
      "texcoord).x);\n"
      "}";

  GLuint vertex_shader =
      CompileShader("Vertex Shader", GL_VERTEX_SHADER, vert_shader);
  if (!vertex_shader) {
    return 0;
  }

  GLuint fragment_shader =
      CompileShader("Fragment Shader", GL_FRAGMENT_SHADER, frag_shader);
  if (!fragment_shader) {
    gl_->DeleteShader(vertex_shader);
    return 0;
  }

  GLuint program =
      CreateProgram("YUVConverter Program", vertex_shader, fragment_shader);

  gl_->DeleteShader(vertex_shader);
  gl_->DeleteShader(fragment_shader);

  if (!program) {
    return 0;
  }

  gl_->UseProgram(program);

  GLint uniform_location;
  uniform_location = gl_->GetUniformLocation(program, "y_sampler");
  DCHECK(uniform_location != -1);
  gl_->Uniform1i(uniform_location, 0);

  uniform_location = gl_->GetUniformLocation(program, "u_sampler");
  DCHECK(uniform_location != -1);
  gl_->Uniform1i(uniform_location, 1);

  uniform_location = gl_->GetUniformLocation(program, "v_sampler");
  DCHECK(uniform_location != -1);
  gl_->Uniform1i(uniform_location, 2);

  uniform_location = gl_->GetUniformLocation(program, "a_sampler");
  DCHECK(uniform_location != -1);
  gl_->Uniform1i(uniform_location, 3);

  gl_->UseProgram(0);

  yuv_matrix_loc_ = gl_->GetUniformLocation(program, "yuv_matrix");
  DCHECK(yuv_matrix_loc_ != -1);

  yuv_adjust_loc_ = gl_->GetUniformLocation(program, "yuv_adjust");
  DCHECK(yuv_adjust_loc_ != -1);

  return program;
}

bool VideoDecoderShim::YUVConverter::Initialize() {
  // If texture_rg extension is not available, use slower GL_LUMINANCE.
  if (context_provider_->ContextCapabilities().texture_rg) {
    internal_format_ = GL_RED_EXT;
    format_ = GL_RED_EXT;
  } else {
    internal_format_ = GL_LUMINANCE;
    format_ = GL_LUMINANCE;
  }

  if (context_provider_->ContextCapabilities().max_texture_image_units < 4) {
    // We support YUVA textures and require 4 texture units in the fragment
    // stage.
    return false;
  }

  gl_->TraceBeginCHROMIUM("YUVConverter", "YUVConverterContext");
  gl_->GenFramebuffers(1, &frame_buffer_);

  y_texture_ = CreateTexture();
  u_texture_ = CreateTexture();
  v_texture_ = CreateTexture();
  a_texture_ = CreateTexture();

  // Vertex positions.  Also converted to texcoords in vertex shader.
  GLfloat vertex_positions[] = {-1.f, -1.f, 1.f, -1.f, -1.f, 1.f, 1.f, 1.f};

  gl_->GenBuffers(1, &vertex_buffer_);
  gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
  gl_->BufferData(GL_ARRAY_BUFFER, 2 * sizeof(GLfloat) * 4, vertex_positions,
                  GL_STATIC_DRAW);
  gl_->BindBuffer(GL_ARRAY_BUFFER, 0);

  program_ = CreateShader();

  gl_->TraceEndCHROMIUM();

  context_provider_->InvalidateGrContext(kGrInvalidateState);

  return (program_ != 0);
}

void VideoDecoderShim::YUVConverter::Convert(
    const scoped_refptr<media::VideoFrame>& frame,
    GLuint tex_out) {
  const float* yuv_matrix = 0;
  const float* yuv_adjust = 0;

  if (video_format_ != frame->format()) {
    // The constants below were taken from cc/output/gl_renderer.cc.
    // These values are magic numbers that are used in the transformation from
    // YUV to RGB color values.  They are taken from the following webpage:
    // http://www.fourcc.org/fccyvrgb.php
    const float yuv_to_rgb_rec601[9] = {
        1.164f, 1.164f, 1.164f, 0.0f, -.391f, 2.018f, 1.596f, -.813f, 0.0f,
    };
    const float yuv_to_rgb_jpeg[9] = {
        1.f, 1.f, 1.f, 0.0f, -.34414f, 1.772f, 1.402f, -.71414f, 0.0f,
    };
    const float yuv_to_rgb_rec709[9] = {
        1.164f, 1.164f, 1.164f, 0.0f, -0.213f, 2.112f, 1.793f, -0.533f, 0.0f,
    };

    // These values map to 16, 128, and 128 respectively, and are computed
    // as a fraction over 256 (e.g. 16 / 256 = 0.0625).
    // They are used in the YUV to RGBA conversion formula:
    //   Y - 16   : Gives 16 values of head and footroom for overshooting
    //   U - 128  : Turns unsigned U into signed U [-128,127]
    //   V - 128  : Turns unsigned V into signed V [-128,127]
    const float yuv_adjust_constrained[3] = {
        -0.0625f, -0.5f, -0.5f,
    };
    // Same as above, but without the head and footroom.
    const float yuv_adjust_full[3] = {
        0.0f, -0.5f, -0.5f,
    };

    yuv_adjust = yuv_adjust_constrained;
    yuv_matrix = yuv_to_rgb_rec601;

    int result;
    if (frame->metadata()->GetInteger(media::VideoFrameMetadata::COLOR_SPACE,
                                      &result)) {
      if (result == media::COLOR_SPACE_JPEG) {
        yuv_matrix = yuv_to_rgb_jpeg;
        yuv_adjust = yuv_adjust_full;
      } else if (result == media::COLOR_SPACE_HD_REC709) {
        yuv_matrix = yuv_to_rgb_rec709;
      }
    }

    switch (frame->format()) {
      case media::PIXEL_FORMAT_YV12:  // 420
      case media::PIXEL_FORMAT_YV12A:
      case media::PIXEL_FORMAT_I420:
        uv_height_divisor_ = 2;
        uv_width_divisor_ = 2;
        break;
      case media::PIXEL_FORMAT_YV16:  // 422
        uv_width_divisor_ = 2;
        uv_height_divisor_ = 1;
        break;
      case media::PIXEL_FORMAT_YV24:  // 444
        uv_width_divisor_ = 1;
        uv_height_divisor_ = 1;
        break;

      default:
        NOTREACHED();
    }

    video_format_ = frame->format();

    // Zero these so everything is reset below.
    y_width_ = y_height_ = 0;
  }

  gl_->TraceBeginCHROMIUM("YUVConverter", "YUVConverterContext");

  uint32_t ywidth = frame->coded_size().width();
  uint32_t yheight = frame->coded_size().height();

  DCHECK_EQ(frame->stride(media::VideoFrame::kUPlane),
            frame->stride(media::VideoFrame::kVPlane));

  uint32_t ystride = frame->stride(media::VideoFrame::kYPlane);
  uint32_t uvstride = frame->stride(media::VideoFrame::kUPlane);

  // The following code assumes that extended GLES 2.0 state like
  // UNPACK_SKIP* (if available) are set to defaults.
  gl_->PixelStorei(GL_UNPACK_ALIGNMENT, 1);

  if (ywidth != y_width_ || yheight != y_height_) {
    y_width_ = ywidth;
    y_height_ = yheight;

    uv_width_ = y_width_ / uv_width_divisor_;
    uv_height_ = y_height_ / uv_height_divisor_;

    // Re-create to resize the textures and upload data.
    gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, ystride);
    gl_->ActiveTexture(GL_TEXTURE0);
    gl_->BindTexture(GL_TEXTURE_2D, y_texture_);
    gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, y_width_, y_height_, 0,
                    format_, GL_UNSIGNED_BYTE,
                    frame->data(media::VideoFrame::kYPlane));

    if (video_format_ == media::PIXEL_FORMAT_YV12A) {
      DCHECK_EQ(frame->stride(media::VideoFrame::kYPlane),
                frame->stride(media::VideoFrame::kAPlane));
      gl_->ActiveTexture(GL_TEXTURE3);
      gl_->BindTexture(GL_TEXTURE_2D, a_texture_);
      gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, y_width_, y_height_,
                      0, format_, GL_UNSIGNED_BYTE,
                      frame->data(media::VideoFrame::kAPlane));
    } else {
      // if there is no alpha channel, then create a 2x2 texture with full
      // alpha.
      gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);
      const uint8_t alpha[4] = {0xff, 0xff, 0xff, 0xff};
      gl_->ActiveTexture(GL_TEXTURE3);
      gl_->BindTexture(GL_TEXTURE_2D, a_texture_);
      gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, 2, 2, 0, format_,
                      GL_UNSIGNED_BYTE, alpha);
    }

    gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, uvstride);
    gl_->ActiveTexture(GL_TEXTURE1);
    gl_->BindTexture(GL_TEXTURE_2D, u_texture_);
    gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, uv_width_, uv_height_,
                    0, format_, GL_UNSIGNED_BYTE,
                    frame->data(media::VideoFrame::kUPlane));

    gl_->ActiveTexture(GL_TEXTURE2);
    gl_->BindTexture(GL_TEXTURE_2D, v_texture_);
    gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format_, uv_width_, uv_height_,
                    0, format_, GL_UNSIGNED_BYTE,
                    frame->data(media::VideoFrame::kVPlane));
  } else {
    // Bind textures and upload texture data
    gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, ystride);
    gl_->ActiveTexture(GL_TEXTURE0);
    gl_->BindTexture(GL_TEXTURE_2D, y_texture_);
    gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, y_width_, y_height_, format_,
                       GL_UNSIGNED_BYTE,
                       frame->data(media::VideoFrame::kYPlane));

    if (video_format_ == media::PIXEL_FORMAT_YV12A) {
      DCHECK_EQ(frame->stride(media::VideoFrame::kYPlane),
                frame->stride(media::VideoFrame::kAPlane));
      gl_->ActiveTexture(GL_TEXTURE3);
      gl_->BindTexture(GL_TEXTURE_2D, a_texture_);
      gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, y_width_, y_height_, format_,
                         GL_UNSIGNED_BYTE,
                         frame->data(media::VideoFrame::kAPlane));
    } else {
      gl_->ActiveTexture(GL_TEXTURE3);
      gl_->BindTexture(GL_TEXTURE_2D, a_texture_);
    }

    gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, uvstride);
    gl_->ActiveTexture(GL_TEXTURE1);
    gl_->BindTexture(GL_TEXTURE_2D, u_texture_);
    gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, uv_width_, uv_height_, format_,
                       GL_UNSIGNED_BYTE,
                       frame->data(media::VideoFrame::kUPlane));

    gl_->ActiveTexture(GL_TEXTURE2);
    gl_->BindTexture(GL_TEXTURE_2D, v_texture_);
    gl_->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, uv_width_, uv_height_, format_,
                       GL_UNSIGNED_BYTE,
                       frame->data(media::VideoFrame::kVPlane));
  }

  gl_->BindFramebuffer(GL_FRAMEBUFFER, frame_buffer_);
  gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                            tex_out, 0);

#ifndef NDEBUG
  // We should probably check for framebuffer complete here, but that
  // will slow this method down so check only in debug mode.
  GLint status = gl_->CheckFramebufferStatus(GL_FRAMEBUFFER);
  if (status != GL_FRAMEBUFFER_COMPLETE) {
    return;
  }
#endif

  gl_->Viewport(0, 0, ywidth, yheight);

  gl_->UseProgram(program_);

  if (yuv_matrix) {
    gl_->UniformMatrix3fv(yuv_matrix_loc_, 1, 0, yuv_matrix);
    gl_->Uniform3fv(yuv_adjust_loc_, 1, yuv_adjust);
  }

  gl_->BindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
  gl_->EnableVertexAttribArray(0);
  gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat),
                           static_cast<const void*>(0));

  gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  // The YUVConverter shares the context with Skia and possibly other modules
  // that may make OpenGL calls.  To be a "good OpenGL citizen" for other
  // (non-Skia) modules that may share this context we restore
  // buffer/texture/state bindings to OpenGL defaults here.  If we were only
  // sharing the context with Skia this may not be necessary as we also
  // Invalidate the GrContext below so that Skia is aware that its state
  // caches need to be reset.

  gl_->BindBuffer(GL_ARRAY_BUFFER, 0);
  gl_->DisableVertexAttribArray(0);
  gl_->UseProgram(0);
  gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);

  gl_->BindTexture(GL_TEXTURE_2D, 0);

  gl_->ActiveTexture(GL_TEXTURE2);
  gl_->BindTexture(GL_TEXTURE_2D, 0);

  gl_->ActiveTexture(GL_TEXTURE1);
  gl_->BindTexture(GL_TEXTURE_2D, 0);

  gl_->ActiveTexture(GL_TEXTURE0);
  gl_->BindTexture(GL_TEXTURE_2D, 0);
  gl_->PixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0);

  gl_->TraceEndCHROMIUM();

  context_provider_->InvalidateGrContext(kGrInvalidateState);
}

struct VideoDecoderShim::PendingDecode {
  PendingDecode(uint32_t decode_id,
                const scoped_refptr<media::DecoderBuffer>& buffer);
  ~PendingDecode();

  const uint32_t decode_id;
  const scoped_refptr<media::DecoderBuffer> buffer;
};

VideoDecoderShim::PendingDecode::PendingDecode(
    uint32_t decode_id,
    const scoped_refptr<media::DecoderBuffer>& buffer)
    : decode_id(decode_id), buffer(buffer) {
}

VideoDecoderShim::PendingDecode::~PendingDecode() {
}

struct VideoDecoderShim::PendingFrame {
  explicit PendingFrame(uint32_t decode_id);
  PendingFrame(uint32_t decode_id,
               const scoped_refptr<media::VideoFrame>& frame);
  ~PendingFrame();

  const uint32_t decode_id;
  scoped_refptr<media::VideoFrame> video_frame;

 private:
  // This could be expensive to copy, so guard against that.
  DISALLOW_COPY_AND_ASSIGN(PendingFrame);
};

VideoDecoderShim::PendingFrame::PendingFrame(uint32_t decode_id)
    : decode_id(decode_id) {
}

VideoDecoderShim::PendingFrame::PendingFrame(
    uint32_t decode_id,
    const scoped_refptr<media::VideoFrame>& frame)
    : decode_id(decode_id), video_frame(frame) {
}

VideoDecoderShim::PendingFrame::~PendingFrame() {
}

// DecoderImpl runs the underlying VideoDecoder on the media thread, receiving
// calls from the VideoDecodeShim on the main thread and sending results back.
// This class is constructed on the main thread, but used and destructed on the
// media thread.
class VideoDecoderShim::DecoderImpl {
 public:
  explicit DecoderImpl(const base::WeakPtr<VideoDecoderShim>& proxy);
  ~DecoderImpl();

  void Initialize(media::VideoDecoderConfig config);
  void Decode(uint32_t decode_id, scoped_refptr<media::DecoderBuffer> buffer);
  void Reset();
  void Stop();

 private:
  void OnInitDone(bool success);
  void DoDecode();
  void OnDecodeComplete(media::DecodeStatus status);
  void OnOutputComplete(const scoped_refptr<media::VideoFrame>& frame);
  void OnResetComplete();

  // WeakPtr is bound to main_message_loop_. Use only in shim callbacks.
  base::WeakPtr<VideoDecoderShim> shim_;
  std::unique_ptr<media::VideoDecoder> decoder_;
  bool initialized_ = false;
  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
  // Queue of decodes waiting for the decoder.
  typedef std::queue<PendingDecode> PendingDecodeQueue;
  PendingDecodeQueue pending_decodes_;
  bool awaiting_decoder_ = false;
  // VideoDecoder returns pictures without information about the decode buffer
  // that generated it, but VideoDecoder implementations used in this class
  // (media::FFmpegVideoDecoder and media::VpxVideoDecoder) always generate
  // corresponding frames before decode is finished. |decode_id_| is used to
  // store id of the current buffer while Decode() call is pending.
  uint32_t decode_id_ = 0;

  base::WeakPtrFactory<DecoderImpl> weak_ptr_factory_;
};

VideoDecoderShim::DecoderImpl::DecoderImpl(
    const base::WeakPtr<VideoDecoderShim>& proxy)
    : shim_(proxy),
      main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
      weak_ptr_factory_(this) {
}

VideoDecoderShim::DecoderImpl::~DecoderImpl() {
  DCHECK(pending_decodes_.empty());
}

void VideoDecoderShim::DecoderImpl::Initialize(
    media::VideoDecoderConfig config) {
  DCHECK(!decoder_);
#if !defined(MEDIA_DISABLE_LIBVPX)
  if (config.codec() == media::kCodecVP9) {
    decoder_.reset(new media::VpxVideoDecoder());
  } else
#endif

#if !defined(MEDIA_DISABLE_FFMPEG) && !defined(DISABLE_FFMPEG_VIDEO_DECODERS)
  {
    std::unique_ptr<media::FFmpegVideoDecoder> ffmpeg_video_decoder(
        new media::FFmpegVideoDecoder());
    ffmpeg_video_decoder->set_decode_nalus(true);
    decoder_ = std::move(ffmpeg_video_decoder);
  }
#elif defined(MEDIA_DISABLE_LIBVPX)
  OnInitDone(false);
  return;
#endif

  // VpxVideoDecoder and FFmpegVideoDecoder support only one pending Decode()
  // request.
  DCHECK_EQ(decoder_->GetMaxDecodeRequests(), 1);

  decoder_->Initialize(
      config, true /* low_delay */, nullptr,
      base::Bind(&VideoDecoderShim::DecoderImpl::OnInitDone,
                 weak_ptr_factory_.GetWeakPtr()),
      base::Bind(&VideoDecoderShim::DecoderImpl::OnOutputComplete,
                 weak_ptr_factory_.GetWeakPtr()));
}

void VideoDecoderShim::DecoderImpl::Decode(
    uint32_t decode_id,
    scoped_refptr<media::DecoderBuffer> buffer) {
  DCHECK(decoder_);
  pending_decodes_.push(PendingDecode(decode_id, buffer));
  DoDecode();
}

void VideoDecoderShim::DecoderImpl::Reset() {
  DCHECK(decoder_);
  // Abort all pending decodes.
  while (!pending_decodes_.empty()) {
    const PendingDecode& decode = pending_decodes_.front();
    std::unique_ptr<PendingFrame> pending_frame(
        new PendingFrame(decode.decode_id));
    main_task_runner_->PostTask(
        FROM_HERE, base::Bind(&VideoDecoderShim::OnDecodeComplete, shim_, PP_OK,
                              decode.decode_id));
    pending_decodes_.pop();
  }
  // Don't need to call Reset() if the |decoder_| hasn't been initialized.
  if (!initialized_) {
    OnResetComplete();
    return;
  }

  decoder_->Reset(base::Bind(&VideoDecoderShim::DecoderImpl::OnResetComplete,
                             weak_ptr_factory_.GetWeakPtr()));
}

void VideoDecoderShim::DecoderImpl::Stop() {
  DCHECK(decoder_);
  // Clear pending decodes now. We don't want OnDecodeComplete to call DoDecode
  // again.
  while (!pending_decodes_.empty())
    pending_decodes_.pop();
  decoder_.reset();
  // This instance is deleted once we exit this scope.
}

void VideoDecoderShim::DecoderImpl::OnInitDone(bool success) {
  if (!success) {
    main_task_runner_->PostTask(
        FROM_HERE, base::Bind(&VideoDecoderShim::OnInitializeFailed, shim_));
    return;
  }

  initialized_ = true;
  DoDecode();
}

void VideoDecoderShim::DecoderImpl::DoDecode() {
  if (!initialized_ || pending_decodes_.empty() || awaiting_decoder_)
    return;

  awaiting_decoder_ = true;
  const PendingDecode& decode = pending_decodes_.front();
  decode_id_ = decode.decode_id;
  decoder_->Decode(decode.buffer,
                   base::Bind(&VideoDecoderShim::DecoderImpl::OnDecodeComplete,
                              weak_ptr_factory_.GetWeakPtr()));
  pending_decodes_.pop();
}

void VideoDecoderShim::DecoderImpl::OnDecodeComplete(
    media::DecodeStatus status) {
  DCHECK(awaiting_decoder_);
  awaiting_decoder_ = false;

  int32_t result;
  switch (status) {
    case media::DecodeStatus::OK:
    case media::DecodeStatus::ABORTED:
      result = PP_OK;
      break;
    case media::DecodeStatus::DECODE_ERROR:
      result = PP_ERROR_RESOURCE_FAILED;
      break;
  }

  main_task_runner_->PostTask(
      FROM_HERE, base::Bind(&VideoDecoderShim::OnDecodeComplete, shim_, result,
                            decode_id_));

  DoDecode();
}

void VideoDecoderShim::DecoderImpl::OnOutputComplete(
    const scoped_refptr<media::VideoFrame>& frame) {
  // Software decoders are expected to generated frames only when a Decode()
  // call is pending.
  DCHECK(awaiting_decoder_);

  std::unique_ptr<PendingFrame> pending_frame;
  if (!frame->metadata()->IsTrue(media::VideoFrameMetadata::END_OF_STREAM))
    pending_frame.reset(new PendingFrame(decode_id_, frame));
  else
    pending_frame.reset(new PendingFrame(decode_id_));

  main_task_runner_->PostTask(
      FROM_HERE, base::Bind(&VideoDecoderShim::OnOutputComplete, shim_,
                            base::Passed(&pending_frame)));
}

void VideoDecoderShim::DecoderImpl::OnResetComplete() {
  main_task_runner_->PostTask(
      FROM_HERE, base::Bind(&VideoDecoderShim::OnResetComplete, shim_));
}

VideoDecoderShim::VideoDecoderShim(
    PepperVideoDecoderHost* host, uint32_t texture_pool_size)
    : state_(UNINITIALIZED),
      host_(host),
      media_task_runner_(
          RenderThreadImpl::current()->GetMediaThreadTaskRunner()),
      context_provider_(
          RenderThreadImpl::current()->SharedMainThreadContextProvider()),
      texture_pool_size_(texture_pool_size),
      num_pending_decodes_(0),
      yuv_converter_(new YUVConverter(context_provider_)),
      weak_ptr_factory_(this) {
  DCHECK(host_);
  DCHECK(media_task_runner_.get());
  DCHECK(context_provider_.get());
  decoder_impl_.reset(new DecoderImpl(weak_ptr_factory_.GetWeakPtr()));
}

VideoDecoderShim::~VideoDecoderShim() {
  DCHECK(RenderThreadImpl::current());
  // Delete any remaining textures.
  TextureIdMap::iterator it = texture_id_map_.begin();
  for (; it != texture_id_map_.end(); ++it)
    DeleteTexture(it->second);
  texture_id_map_.clear();

  FlushCommandBuffer();

  weak_ptr_factory_.InvalidateWeakPtrs();
  // No more callbacks from the delegate will be received now.

  // The callback now holds the only reference to the DecoderImpl, which will be
  // deleted when Stop completes.
  media_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&VideoDecoderShim::DecoderImpl::Stop,
                 base::Owned(decoder_impl_.release())));
}

bool VideoDecoderShim::Initialize(const Config& vda_config, Client* client) {
  DCHECK_EQ(client, host_);
  DCHECK(RenderThreadImpl::current());
  DCHECK_EQ(state_, UNINITIALIZED);

  if (vda_config.is_encrypted) {
    NOTREACHED() << "Encrypted streams are not supported";
    return false;
  }

  media::VideoCodec codec = media::kUnknownVideoCodec;
  if (vda_config.profile <= media::H264PROFILE_MAX)
    codec = media::kCodecH264;
  else if (vda_config.profile <= media::VP8PROFILE_MAX)
    codec = media::kCodecVP8;
  else if (vda_config.profile <= media::VP9PROFILE_MAX)
    codec = media::kCodecVP9;
  DCHECK_NE(codec, media::kUnknownVideoCodec);

  if (!IsCodecSupported(codec))
    return false;

  if (!yuv_converter_->Initialize())
    return false;

  media::VideoDecoderConfig video_decoder_config(
      codec, vda_config.profile, media::PIXEL_FORMAT_YV12,
      media::COLOR_SPACE_UNSPECIFIED,
      gfx::Size(32, 24),  // Small sizes that won't fail.
      gfx::Rect(32, 24), gfx::Size(32, 24),
      // TODO(bbudge): Verify extra data isn't needed.
      media::EmptyExtraData(), media::Unencrypted());

  media_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&VideoDecoderShim::DecoderImpl::Initialize,
                 base::Unretained(decoder_impl_.get()), video_decoder_config));

  state_ = DECODING;

  // Return success, even though we are asynchronous, to mimic
  // media::VideoDecodeAccelerator.
  return true;
}

void VideoDecoderShim::Decode(const media::BitstreamBuffer& bitstream_buffer) {
  DCHECK(RenderThreadImpl::current());
  DCHECK_EQ(state_, DECODING);

  // We need the address of the shared memory, so we can copy the buffer.
  const uint8_t* buffer = host_->DecodeIdToAddress(bitstream_buffer.id());
  DCHECK(buffer);

  media_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(
          &VideoDecoderShim::DecoderImpl::Decode,
          base::Unretained(decoder_impl_.get()),
          bitstream_buffer.id(),
          media::DecoderBuffer::CopyFrom(buffer, bitstream_buffer.size())));
  num_pending_decodes_++;
}

void VideoDecoderShim::AssignPictureBuffers(
    const std::vector<media::PictureBuffer>& buffers) {
  DCHECK(RenderThreadImpl::current());
  DCHECK_NE(state_, UNINITIALIZED);
  if (buffers.empty()) {
    NOTREACHED();
    return;
  }
  DCHECK_EQ(buffers.size(), pending_texture_mailboxes_.size());
  GLuint num_textures = base::checked_cast<GLuint>(buffers.size());
  std::vector<uint32_t> local_texture_ids(num_textures);
  gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL();
  for (uint32_t i = 0; i < num_textures; i++) {
    DCHECK_EQ(1u, buffers[i].client_texture_ids().size());
    local_texture_ids[i] = gles2->CreateAndConsumeTextureCHROMIUM(
        GL_TEXTURE_2D, pending_texture_mailboxes_[i].name);
    // Map the plugin texture id to the local texture id.
    uint32_t plugin_texture_id = buffers[i].client_texture_ids()[0];
    texture_id_map_[plugin_texture_id] = local_texture_ids[i];
    available_textures_.insert(plugin_texture_id);
  }
  pending_texture_mailboxes_.clear();
  SendPictures();
}

void VideoDecoderShim::ReusePictureBuffer(int32_t picture_buffer_id) {
  DCHECK(RenderThreadImpl::current());
  uint32_t texture_id = static_cast<uint32_t>(picture_buffer_id);
  if (textures_to_dismiss_.find(texture_id) != textures_to_dismiss_.end()) {
    DismissTexture(texture_id);
  } else if (texture_id_map_.find(texture_id) != texture_id_map_.end()) {
    available_textures_.insert(texture_id);
    SendPictures();
  } else {
    NOTREACHED();
  }
}

void VideoDecoderShim::Flush() {
  DCHECK(RenderThreadImpl::current());
  DCHECK_EQ(state_, DECODING);
  state_ = FLUSHING;
}

void VideoDecoderShim::Reset() {
  DCHECK(RenderThreadImpl::current());
  DCHECK_EQ(state_, DECODING);
  state_ = RESETTING;
  media_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&VideoDecoderShim::DecoderImpl::Reset,
                 base::Unretained(decoder_impl_.get())));
}

void VideoDecoderShim::Destroy() {
  delete this;
}

void VideoDecoderShim::OnInitializeFailed() {
  DCHECK(RenderThreadImpl::current());
  DCHECK(host_);

  host_->NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE);
}

void VideoDecoderShim::OnDecodeComplete(int32_t result, uint32_t decode_id) {
  DCHECK(RenderThreadImpl::current());
  DCHECK(host_);

  if (result == PP_ERROR_RESOURCE_FAILED) {
    host_->NotifyError(media::VideoDecodeAccelerator::PLATFORM_FAILURE);
    return;
  }

  num_pending_decodes_--;
  completed_decodes_.push(decode_id);

  // If frames are being queued because we're out of textures, don't notify
  // the host that decode has completed. This exerts "back pressure" to keep
  // the host from sending buffers that will cause pending_frames_ to grow.
  if (pending_frames_.empty())
    NotifyCompletedDecodes();
}

void VideoDecoderShim::OnOutputComplete(std::unique_ptr<PendingFrame> frame) {
  DCHECK(RenderThreadImpl::current());
  DCHECK(host_);

  if (frame->video_frame) {
    if (texture_size_ != frame->video_frame->coded_size()) {
      // If the size has changed, all current textures must be dismissed. Add
      // all textures to |textures_to_dismiss_| and dismiss any that aren't in
      // use by the plugin. We will dismiss the rest as they are recycled.
      for (TextureIdMap::const_iterator it = texture_id_map_.begin();
           it != texture_id_map_.end();
           ++it) {
        textures_to_dismiss_.insert(it->first);
      }
      for (TextureIdSet::const_iterator it = available_textures_.begin();
           it != available_textures_.end();
           ++it) {
        DismissTexture(*it);
      }
      available_textures_.clear();
      FlushCommandBuffer();

      DCHECK(pending_texture_mailboxes_.empty());
      for (uint32_t i = 0; i < texture_pool_size_; i++)
        pending_texture_mailboxes_.push_back(gpu::Mailbox::Generate());

      host_->RequestTextures(texture_pool_size_,
                             frame->video_frame->coded_size(), GL_TEXTURE_2D,
                             pending_texture_mailboxes_);
      texture_size_ = frame->video_frame->coded_size();
    }

    pending_frames_.push(std::move(frame));
    SendPictures();
  }
}

void VideoDecoderShim::SendPictures() {
  DCHECK(RenderThreadImpl::current());
  DCHECK(host_);
  while (!pending_frames_.empty() && !available_textures_.empty()) {
    const std::unique_ptr<PendingFrame>& frame = pending_frames_.front();

    TextureIdSet::iterator it = available_textures_.begin();
    uint32_t texture_id = *it;
    available_textures_.erase(it);

    uint32_t local_texture_id = texture_id_map_[texture_id];

    yuv_converter_->Convert(frame->video_frame, local_texture_id);

    host_->PictureReady(media::Picture(texture_id, frame->decode_id,
                                       frame->video_frame->visible_rect(),
                                       gfx::ColorSpace(), false));
    pending_frames_.pop();
  }

  FlushCommandBuffer();

  if (pending_frames_.empty()) {
    // If frames aren't backing up, notify the host of any completed decodes so
    // it can send more buffers.
    NotifyCompletedDecodes();

    if (state_ == FLUSHING && !num_pending_decodes_) {
      state_ = DECODING;
      host_->NotifyFlushDone();
    }
  }
}

void VideoDecoderShim::OnResetComplete() {
  DCHECK(RenderThreadImpl::current());
  DCHECK(host_);

  while (!pending_frames_.empty())
    pending_frames_.pop();
  NotifyCompletedDecodes();

  // Dismiss any old textures now.
  while (!textures_to_dismiss_.empty())
    DismissTexture(*textures_to_dismiss_.begin());

  state_ = DECODING;
  host_->NotifyResetDone();
}

void VideoDecoderShim::NotifyCompletedDecodes() {
  while (!completed_decodes_.empty()) {
    host_->NotifyEndOfBitstreamBuffer(completed_decodes_.front());
    completed_decodes_.pop();
  }
}

void VideoDecoderShim::DismissTexture(uint32_t texture_id) {
  DCHECK(host_);
  textures_to_dismiss_.erase(texture_id);
  DCHECK(texture_id_map_.find(texture_id) != texture_id_map_.end());
  DeleteTexture(texture_id_map_[texture_id]);
  texture_id_map_.erase(texture_id);
  host_->DismissPictureBuffer(texture_id);
}

void VideoDecoderShim::DeleteTexture(uint32_t texture_id) {
  gpu::gles2::GLES2Interface* gles2 = context_provider_->ContextGL();
  gles2->DeleteTextures(1, &texture_id);
}

void VideoDecoderShim::FlushCommandBuffer() {
  context_provider_->ContextGL()->Flush();
}

}  // namespace content
