text: // 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 "ppapi/proxy/video_decoder_resource.h"
  
  #include "base/bind.h"
  #include "gpu/command_buffer/client/gles2_cmd_helper.h"
  #include "gpu/command_buffer/client/gles2_implementation.h"
  #include "gpu/command_buffer/common/mailbox.h"
  #include "ipc/ipc_message.h"
  #include "ppapi/c/pp_errors.h"
  #include "ppapi/c/ppb_opengles2.h"
  #include "ppapi/proxy/plugin_dispatcher.h"
  #include "ppapi/proxy/ppapi_messages.h"
  #include "ppapi/proxy/ppb_graphics_3d_proxy.h"
  #include "ppapi/proxy/serialized_handle.h"
  #include "ppapi/proxy/video_decoder_constants.h"
  #include "ppapi/shared_impl/ppapi_globals.h"
  #include "ppapi/shared_impl/ppb_graphics_3d_shared.h"
  #include "ppapi/shared_impl/proxy_lock.h"
  #include "ppapi/shared_impl/resource_tracker.h"
  #include "ppapi/thunk/enter.h"
  
  using ppapi::thunk::EnterResourceNoLock;
  using ppapi::thunk::PPB_Graphics3D_API;
  using ppapi::thunk::PPB_VideoDecoder_API;
  
  namespace ppapi {
  namespace proxy {
  
  VideoDecoderResource::ShmBuffer::ShmBuffer(
      scoped_ptr<base::SharedMemory> shm_ptr,
      uint32_t size,
      uint32_t shm_id)
      : shm(shm_ptr.Pass()), addr(NULL), shm_id(shm_id) {
    if (shm->Map(size))
      addr = shm->memory();
  }
  
  VideoDecoderResource::ShmBuffer::~ShmBuffer() {
  }
  
  VideoDecoderResource::Texture::Texture(uint32_t texture_target,
                                         const PP_Size& size)
      : texture_target(texture_target), size(size) {
  }
  
  VideoDecoderResource::Texture::~Texture() {
  }
  
  VideoDecoderResource::Picture::Picture(int32_t decode_id,
                                         uint32_t texture_id,
                                         const PP_Rect& visible_rect)
      : decode_id(decode_id), texture_id(texture_id), visible_rect(visible_rect) {
  }
  
  VideoDecoderResource::Picture::~Picture() {
  }
  
  VideoDecoderResource::VideoDecoderResource(Connection connection,
                                             PP_Instance instance)
      : PluginResource(connection, instance),
        num_decodes_(0),
        get_picture_(NULL),
        get_picture_0_1_(NULL),
        gles2_impl_(NULL),
        initialized_(false),
        testing_(false),
        // Set |decoder_last_error_| to PP_OK after successful initialization.
        // This makes error checking a little more concise, since we can check
        // that the decoder has been initialized and hasn't returned an error by
        // just testing |decoder_last_error_|.
        decoder_last_error_(PP_ERROR_FAILED) {
    // Clear the decode_ids_ array.
    memset(decode_ids_, 0, arraysize(decode_ids_));
    SendCreate(RENDERER, PpapiHostMsg_VideoDecoder_Create());
  }
  
  VideoDecoderResource::~VideoDecoderResource() {
    // Destroy any textures which haven't been dismissed.
    TextureMap::iterator it = textures_.begin();
    for (; it != textures_.end(); ++it)
      DeleteGLTexture(it->first);
  }
  
  PPB_VideoDecoder_API* VideoDecoderResource::AsPPB_VideoDecoder_API() {
    return this;
  }
  
  int32_t VideoDecoderResource::Initialize0_1(
      PP_Resource graphics_context,
      PP_VideoProfile profile,
      PP_Bool allow_software_fallback,
      scoped_refptr<TrackedCallback> callback) {
    return Initialize(graphics_context,
                      profile,
                      allow_software_fallback
                          ? PP_HARDWAREACCELERATION_WITHFALLBACK
                          : PP_HARDWAREACCELERATION_ONLY,
                      callback);
  }
  
  int32_t VideoDecoderResource::Initialize(
      PP_Resource graphics_context,
      PP_VideoProfile profile,
      PP_HardwareAcceleration acceleration,
      scoped_refptr<TrackedCallback> callback) {
    if (initialized_)
      return PP_ERROR_FAILED;
    if (profile < 0 || profile > PP_VIDEOPROFILE_MAX)
      return PP_ERROR_BADARGUMENT;
    if (initialize_callback_.get())
      return PP_ERROR_INPROGRESS;
    if (!graphics_context)
      return PP_ERROR_BADRESOURCE;
  
    HostResource host_resource;
    if (!testing_) {
      // Create a new Graphics3D resource that can create texture resources to
      // share with the plugin. We can't use the plugin's Graphics3D, since we
      // create textures on a proxy thread, and would interfere with the plugin.
      thunk::EnterResourceCreationNoLock enter_create(pp_instance());
      if (enter_create.failed())
        return PP_ERROR_FAILED;
      int32_t attrib_list[] = {PP_GRAPHICS3DATTRIB_NONE};
      graphics3d_ =
          ScopedPPResource(ScopedPPResource::PassRef(),
                           enter_create.functions()->CreateGraphics3D(
                               pp_instance(), graphics_context, attrib_list));
      EnterResourceNoLock<PPB_Graphics3D_API> enter_graphics(graphics3d_.get(),
                                                             false);
      if (enter_graphics.failed())
        return PP_ERROR_BADRESOURCE;
  
      PPB_Graphics3D_Shared* ppb_graphics3d_shared =
          static_cast<PPB_Graphics3D_Shared*>(enter_graphics.object());
      gles2_impl_ = ppb_graphics3d_shared->gles2_impl();
      host_resource = ppb_graphics3d_shared->host_resource();
    }
  
    initialize_callback_ = callback;
  
    Call<PpapiPluginMsg_VideoDecoder_InitializeReply>(
        RENDERER,
        PpapiHostMsg_VideoDecoder_Initialize(
            host_resource, profile, acceleration),
        base::Bind(&VideoDecoderResource::OnPluginMsgInitializeComplete, this));
  
    return PP_OK_COMPLETIONPENDING;
  }
  
  int32_t VideoDecoderResource::Decode(uint32_t decode_id,
                                       uint32_t size,
                                       const void* buffer,
                                       scoped_refptr<TrackedCallback> callback) {
    if (decoder_last_error_)
      return decoder_last_error_;
    if (flush_callback_.get() || reset_callback_.get())
      return PP_ERROR_FAILED;
    if (decode_callback_.get())
      return PP_ERROR_INPROGRESS;
    if (size > kMaximumBitstreamBufferSize)
      return PP_ERROR_NOMEMORY;
  
    // If we allow the plugin to call Decode again, we must have somewhere to
    // copy their buffer.
    DCHECK(!available_shm_buffers_.empty() ||
           shm_buffers_.size() < kMaximumPendingDecodes);
  
    // Count up, wrapping back to 0 before overflowing.
    int32_t uid = ++num_decodes_;
    if (uid == std::numeric_limits<int32_t>::max())
      num_decodes_ = 0;
  
    // Save decode_id in a ring buffer. The ring buffer is sized to store
    // decode_id for the maximum picture delay.
    decode_ids_[uid % kMaximumPictureDelay] = decode_id;
  
    if (available_shm_buffers_.empty() ||
        available_shm_buffers_.back()->shm->mapped_size() < size) {
      uint32_t shm_id;
      if (shm_buffers_.size() < kMaximumPendingDecodes) {
        // Signal the host to create a new shm buffer by passing an index outside
        // the legal range.
        shm_id = static_cast<uint32_t>(shm_buffers_.size());
      } else {
        // Signal the host to grow a buffer by passing a legal index. Choose the
        // last available shm buffer for simplicity.
        shm_id = available_shm_buffers_.back()->shm_id;
        available_shm_buffers_.pop_back();
      }
  
      // Synchronously get shared memory. Use GenericSyncCall so we can get the
      // reply params, which contain the handle.
      uint32_t shm_size = 0;
      IPC::Message reply;
      ResourceMessageReplyParams reply_params;
      int32_t result =
          GenericSyncCall(RENDERER,
                          PpapiHostMsg_VideoDecoder_GetShm(shm_id, size),
                          &reply,
                          &reply_params);
      if (result != PP_OK)
        return PP_ERROR_FAILED;
      if (!UnpackMessage<PpapiPluginMsg_VideoDecoder_GetShmReply>(reply,
                                                                  &shm_size))
        return PP_ERROR_FAILED;
      base::SharedMemoryHandle shm_handle = base::SharedMemory::NULLHandle();
      if (!reply_params.TakeSharedMemoryHandleAtIndex(0, &shm_handle))
        return PP_ERROR_NOMEMORY;
      scoped_ptr<base::SharedMemory> shm(
          new base::SharedMemory(shm_handle, false /* read_only */));
      scoped_ptr<ShmBuffer> shm_buffer(
          new ShmBuffer(shm.Pass(), shm_size, shm_id));
      if (!shm_buffer->addr)
        return PP_ERROR_NOMEMORY;
  
      available_shm_buffers_.push_back(shm_buffer.get());
      if (shm_buffers_.size() < kMaximumPendingDecodes) {
        shm_buffers_.push_back(shm_buffer.release());
      } else {
        // Delete manually since ScopedVector won't delete the existing element if
        // we just assign it.
        delete shm_buffers_[shm_id];
        shm_buffers_[shm_id] = shm_buffer.release();
      }
    }
  
    // At this point we should have shared memory to hold the plugin's buffer.
    DCHECK(!available_shm_buffers_.empty() &&
           available_shm_buffers_.back()->shm->mapped_size() >= size);
  
    ShmBuffer* shm_buffer = available_shm_buffers_.back();
    available_shm_buffers_.pop_back();
    memcpy(shm_buffer->addr, buffer, size);
  
    Call<PpapiPluginMsg_VideoDecoder_DecodeReply>(
        RENDERER,
        PpapiHostMsg_VideoDecoder_Decode(shm_buffer->shm_id, size, uid),
        base::Bind(&VideoDecoderResource::OnPluginMsgDecodeComplete, this));
  
    // If we have another free buffer, or we can still create new buffers, let
    // the plugin call Decode again.
    if (!available_shm_buffers_.empty() ||
        shm_buffers_.size() < kMaximumPendingDecodes)
      return PP_OK;
  
    // All buffers are busy and we can't create more. Delay completion until a
    // buffer is available.
    decode_callback_ = callback;
    return PP_OK_COMPLETIONPENDING;
  }
  
  int32_t VideoDecoderResource::GetPicture0_1(
      PP_VideoPicture_0_1* picture,
      scoped_refptr<TrackedCallback> callback) {
    get_picture_0_1_ = picture;
    return GetPicture(NULL, callback);
  }
  
  int32_t VideoDecoderResource::GetPicture(
      PP_VideoPicture* picture,
      scoped_refptr<TrackedCallback> callback) {
    if (decoder_last_error_)
      return decoder_last_error_;
    if (reset_callback_.get())
      return PP_ERROR_FAILED;
    if (get_picture_callback_.get())
      return PP_ERROR_INPROGRESS;
  
    get_picture_ = picture;
  
    // If the next picture is ready, return it synchronously.
    if (!received_pictures_.empty()) {
      WriteNextPicture();
      return PP_OK;
    }
  
    get_picture_callback_ = callback;
  
    return PP_OK_COMPLETIONPENDING;
  }
  
  void VideoDecoderResource::RecyclePicture(const PP_VideoPicture* picture) {
    if (decoder_last_error_)
      return;
  
    Post(RENDERER, PpapiHostMsg_VideoDecoder_RecyclePicture(picture->texture_id));
  }
  
  int32_t VideoDecoderResource::Flush(scoped_refptr<TrackedCallback> callback) {
    if (decoder_last_error_)
      return decoder_last_error_;
    if (reset_callback_.get())
      return PP_ERROR_FAILED;
    if (flush_callback_.get())
      return PP_ERROR_INPROGRESS;
    flush_callback_ = callback;
  
    Call<PpapiPluginMsg_VideoDecoder_FlushReply>(
        RENDERER,
        PpapiHostMsg_VideoDecoder_Flush(),
        base::Bind(&VideoDecoderResource::OnPluginMsgFlushComplete, this));
  
    return PP_OK_COMPLETIONPENDING;
  }
  
  int32_t VideoDecoderResource::Reset(scoped_refptr<TrackedCallback> callback) {
    if (decoder_last_error_)
      return decoder_last_error_;
    if (flush_callback_.get())
      return PP_ERROR_FAILED;
    if (reset_callback_.get())
      return PP_ERROR_INPROGRESS;
    reset_callback_ = callback;
  
    // Cause any pending Decode or GetPicture callbacks to abort after we return,
    // to avoid reentering the plugin.
    if (TrackedCallback::IsPending(decode_callback_))
      decode_callback_->PostAbort();
    decode_callback_ = NULL;
    if (TrackedCallback::IsPending(get_picture_callback_))
      get_picture_callback_->PostAbort();
    get_picture_callback_ = NULL;
    Call<PpapiPluginMsg_VideoDecoder_ResetReply>(
        RENDERER,
        PpapiHostMsg_VideoDecoder_Reset(),
        base::Bind(&VideoDecoderResource::OnPluginMsgResetComplete, this));
  
    return PP_OK_COMPLETIONPENDING;
  }
  
  void VideoDecoderResource::OnReplyReceived(
      const ResourceMessageReplyParams& params,
      const IPC::Message& msg) {
    PPAPI_BEGIN_MESSAGE_MAP(VideoDecoderResource, msg)
      PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
          PpapiPluginMsg_VideoDecoder_RequestTextures, OnPluginMsgRequestTextures)
      PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
          PpapiPluginMsg_VideoDecoder_PictureReady, OnPluginMsgPictureReady)
      PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
          PpapiPluginMsg_VideoDecoder_DismissPicture, OnPluginMsgDismissPicture)
      PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
          PpapiPluginMsg_VideoDecoder_NotifyError, OnPluginMsgNotifyError)
      PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(
          PluginResource::OnReplyReceived(params, msg))
    PPAPI_END_MESSAGE_MAP()
  }
  
  void VideoDecoderResource::SetForTest() {
    testing_ = true;
  }
  
  void VideoDecoderResource::OnPluginMsgRequestTextures(
      const ResourceMessageReplyParams& params,
      uint32_t num_textures,
      const PP_Size& size,
      uint32_t texture_target,
      const std::vector<gpu::Mailbox>& mailboxes) {
    DCHECK(num_textures);
    DCHECK(mailboxes.empty() || mailboxes.size() == num_textures);
    std::vector<uint32_t> texture_ids(num_textures);
    if (gles2_impl_) {
      gles2_impl_->GenTextures(num_textures, &texture_ids.front());
      for (uint32_t i = 0; i < num_textures; ++i) {
        gles2_impl_->ActiveTexture(GL_TEXTURE0);
        gles2_impl_->BindTexture(texture_target, texture_ids[i]);
        gles2_impl_->TexParameteri(
            texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        gles2_impl_->TexParameteri(
            texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        gles2_impl_->TexParameterf(
            texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        gles2_impl_->TexParameterf(
            texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  
        if (texture_target == GL_TEXTURE_2D) {
          gles2_impl_->TexImage2D(texture_target,
                                  0,
                                  GL_RGBA,
                                  size.width,
                                  size.height,
                                  0,
                                  GL_RGBA,
                                  GL_UNSIGNED_BYTE,
                                  NULL);
        }
        if (!mailboxes.empty()) {
          gles2_impl_->ProduceTextureCHROMIUM(
              GL_TEXTURE_2D, reinterpret_cast<const GLbyte*>(mailboxes[i].name));
        }
  
        textures_.insert(
            std::make_pair(texture_ids[i], Texture(texture_target, size)));
      }
      gles2_impl_->Flush();
    } else {
      DCHECK(testing_);
      // Create some fake texture ids so we can test picture handling.
      for (uint32_t i = 0; i < num_textures; ++i) {
        texture_ids[i] = i + 1;
        textures_.insert(
            std::make_pair(texture_ids[i], Texture(texture_target, size)));
      }
    }
  
    Post(RENDERER, PpapiHostMsg_VideoDecoder_AssignTextures(size, texture_ids));
  }
  
  void VideoDecoderResource::OnPluginMsgPictureReady(
      const ResourceMessageReplyParams& params,
      int32_t decode_id,
      uint32_t texture_id,
      const PP_Rect& visible_rect) {
    received_pictures_.push(Picture(decode_id, texture_id, visible_rect));
  
    if (TrackedCallback::IsPending(get_picture_callback_)) {
      // The plugin may call GetPicture in its callback.
      scoped_refptr<TrackedCallback> callback;
      callback.swap(get_picture_callback_);
      WriteNextPicture();
      callback->Run(PP_OK);
    }
  }
  
  void VideoDecoderResource::OnPluginMsgDismissPicture(
      const ResourceMessageReplyParams& params,
      uint32_t texture_id) {
    DeleteGLTexture(texture_id);
    textures_.erase(texture_id);
  }
  
  void VideoDecoderResource::OnPluginMsgNotifyError(
      const ResourceMessageReplyParams& params,
      int32_t error) {
    decoder_last_error_ = error;
    // Cause any pending callbacks to run immediately. Reentrancy isn't a problem,
    // since the plugin wasn't calling us.
    RunCallbackWithError(&initialize_callback_);
    RunCallbackWithError(&decode_callback_);
    RunCallbackWithError(&get_picture_callback_);
    RunCallbackWithError(&flush_callback_);
    RunCallbackWithError(&reset_callback_);
  }
  
  void VideoDecoderResource::OnPluginMsgInitializeComplete(
      const ResourceMessageReplyParams& params) {
    decoder_last_error_ = params.result();
    if (decoder_last_error_ == PP_OK)
      initialized_ = true;
  
    // Let the plugin call Initialize again from its callback in case of failure.
    scoped_refptr<TrackedCallback> callback;
    callback.swap(initialize_callback_);
    callback->Run(decoder_last_error_);
  }
  
  void VideoDecoderResource::OnPluginMsgDecodeComplete(
      const ResourceMessageReplyParams& params,
      uint32_t shm_id) {
    if (shm_id >= shm_buffers_.size()) {
      NOTREACHED();
      return;
    }
    // Make the shm buffer available.
    available_shm_buffers_.push_back(shm_buffers_[shm_id]);
    // If the plugin is waiting, let it call Decode again.
    if (decode_callback_.get()) {
      scoped_refptr<TrackedCallback> callback;
      callback.swap(decode_callback_);
      callback->Run(PP_OK);
    }
  }
  
  void VideoDecoderResource::OnPluginMsgFlushComplete(
      const ResourceMessageReplyParams& params) {
    // All shm buffers should have been made available by now.
    DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size());
  
    if (get_picture_callback_.get()) {
      scoped_refptr<TrackedCallback> callback;
      callback.swap(get_picture_callback_);
      callback->Abort();
    }
  
    scoped_refptr<TrackedCallback> callback;
    callback.swap(flush_callback_);
    callback->Run(params.result());
  }
  
  void VideoDecoderResource::OnPluginMsgResetComplete(
      const ResourceMessageReplyParams& params) {
    // All shm buffers should have been made available by now.
    DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size());
    // Recycle any pictures which haven't been passed to the plugin.
    while (!received_pictures_.empty()) {
      Post(RENDERER, PpapiHostMsg_VideoDecoder_RecyclePicture(
          received_pictures_.front().texture_id));
      received_pictures_.pop();
    }
  
    scoped_refptr<TrackedCallback> callback;
    callback.swap(reset_callback_);
    callback->Run(params.result());
  }
  
  void VideoDecoderResource::RunCallbackWithError(
      scoped_refptr<TrackedCallback>* callback) {
    if (TrackedCallback::IsPending(*callback)) {
      scoped_refptr<TrackedCallback> temp;
      callback->swap(temp);
      temp->Run(decoder_last_error_);
    }
  }
  
  void VideoDecoderResource::DeleteGLTexture(uint32_t id) {
    if (gles2_impl_) {
      gles2_impl_->DeleteTextures(1, &id);
      gles2_impl_->Flush();
    }
  }
  
  void VideoDecoderResource::WriteNextPicture() {
    DCHECK(!received_pictures_.empty());
    Picture& picture = received_pictures_.front();
  
    // Internally, we identify decodes by a unique id, which the host returns
    // to us in the picture. Use this to get the plugin's decode_id.
    uint32_t decode_id = decode_ids_[picture.decode_id % kMaximumPictureDelay];
    uint32_t texture_id = picture.texture_id;
    uint32_t texture_target = 0;
    PP_Size texture_size = PP_MakeSize(0, 0);
    TextureMap::iterator it = textures_.find(picture.texture_id);
    if (it != textures_.end()) {
      texture_target = it->second.texture_target;
      texture_size = it->second.size;
    } else {
      NOTREACHED();
    }
  
    if (get_picture_) {
      DCHECK(!get_picture_0_1_);
      get_picture_->decode_id = decode_id;
      get_picture_->texture_id = texture_id;
      get_picture_->texture_target = texture_target;
      get_picture_->texture_size = texture_size;
      get_picture_->visible_rect = picture.visible_rect;
      get_picture_ = NULL;
    } else {
      DCHECK(get_picture_0_1_);
      get_picture_0_1_->decode_id = decode_id;
      get_picture_0_1_->texture_id = texture_id;
      get_picture_0_1_->texture_target = texture_target;
      get_picture_0_1_->texture_size = texture_size;
      get_picture_0_1_ = NULL;
    }
  
    received_pictures_.pop();
  }
  
  }  // namespace proxy
  }  // namespace ppapi
  