Home | History | Annotate | Download | only in proxy
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "ppapi/proxy/video_decoder_resource.h"
      6 
      7 #include "base/bind.h"
      8 #include "gpu/command_buffer/client/gles2_cmd_helper.h"
      9 #include "gpu/command_buffer/client/gles2_implementation.h"
     10 #include "gpu/command_buffer/common/mailbox.h"
     11 #include "ipc/ipc_message.h"
     12 #include "ppapi/c/pp_errors.h"
     13 #include "ppapi/c/ppb_opengles2.h"
     14 #include "ppapi/proxy/plugin_dispatcher.h"
     15 #include "ppapi/proxy/ppapi_messages.h"
     16 #include "ppapi/proxy/ppb_graphics_3d_proxy.h"
     17 #include "ppapi/proxy/serialized_handle.h"
     18 #include "ppapi/proxy/video_decoder_constants.h"
     19 #include "ppapi/shared_impl/ppapi_globals.h"
     20 #include "ppapi/shared_impl/ppb_graphics_3d_shared.h"
     21 #include "ppapi/shared_impl/proxy_lock.h"
     22 #include "ppapi/shared_impl/resource_tracker.h"
     23 #include "ppapi/thunk/enter.h"
     24 
     25 using ppapi::thunk::EnterResourceNoLock;
     26 using ppapi::thunk::PPB_Graphics3D_API;
     27 using ppapi::thunk::PPB_VideoDecoder_API;
     28 
     29 namespace ppapi {
     30 namespace proxy {
     31 
     32 VideoDecoderResource::ShmBuffer::ShmBuffer(
     33     scoped_ptr<base::SharedMemory> shm_ptr,
     34     uint32_t size,
     35     uint32_t shm_id)
     36     : shm(shm_ptr.Pass()), addr(NULL), shm_id(shm_id) {
     37   if (shm->Map(size))
     38     addr = shm->memory();
     39 }
     40 
     41 VideoDecoderResource::ShmBuffer::~ShmBuffer() {
     42 }
     43 
     44 VideoDecoderResource::Texture::Texture(uint32_t texture_target,
     45                                        const PP_Size& size)
     46     : texture_target(texture_target), size(size) {
     47 }
     48 
     49 VideoDecoderResource::Texture::~Texture() {
     50 }
     51 
     52 VideoDecoderResource::Picture::Picture(int32_t decode_id, uint32_t texture_id)
     53     : decode_id(decode_id), texture_id(texture_id) {
     54 }
     55 
     56 VideoDecoderResource::Picture::~Picture() {
     57 }
     58 
     59 VideoDecoderResource::VideoDecoderResource(Connection connection,
     60                                            PP_Instance instance)
     61     : PluginResource(connection, instance),
     62       num_decodes_(0),
     63       get_picture_(NULL),
     64       gles2_impl_(NULL),
     65       initialized_(false),
     66       testing_(false),
     67       // Set |decoder_last_error_| to PP_OK after successful initialization.
     68       // This makes error checking a little more concise, since we can check
     69       // that the decoder has been initialized and hasn't returned an error by
     70       // just testing |decoder_last_error_|.
     71       decoder_last_error_(PP_ERROR_FAILED) {
     72   // Clear the decode_ids_ array.
     73   memset(decode_ids_, 0, arraysize(decode_ids_));
     74   SendCreate(RENDERER, PpapiHostMsg_VideoDecoder_Create());
     75 }
     76 
     77 VideoDecoderResource::~VideoDecoderResource() {
     78   // Destroy any textures which haven't been dismissed.
     79   TextureMap::iterator it = textures_.begin();
     80   for (; it != textures_.end(); ++it)
     81     DeleteGLTexture(it->first);
     82 }
     83 
     84 PPB_VideoDecoder_API* VideoDecoderResource::AsPPB_VideoDecoder_API() {
     85   return this;
     86 }
     87 
     88 int32_t VideoDecoderResource::Initialize0_1(
     89     PP_Resource graphics_context,
     90     PP_VideoProfile profile,
     91     PP_Bool allow_software_fallback,
     92     scoped_refptr<TrackedCallback> callback) {
     93   return Initialize(graphics_context,
     94                     profile,
     95                     allow_software_fallback
     96                         ? PP_HARDWAREACCELERATION_WITHFALLBACK
     97                         : PP_HARDWAREACCELERATION_ONLY,
     98                     callback);
     99 }
    100 
    101 int32_t VideoDecoderResource::Initialize(
    102     PP_Resource graphics_context,
    103     PP_VideoProfile profile,
    104     PP_HardwareAcceleration acceleration,
    105     scoped_refptr<TrackedCallback> callback) {
    106   if (initialized_)
    107     return PP_ERROR_FAILED;
    108   if (profile < 0 || profile > PP_VIDEOPROFILE_MAX)
    109     return PP_ERROR_BADARGUMENT;
    110   if (initialize_callback_.get())
    111     return PP_ERROR_INPROGRESS;
    112   if (!graphics_context)
    113     return PP_ERROR_BADRESOURCE;
    114 
    115   HostResource host_resource;
    116   if (!testing_) {
    117     // Create a new Graphics3D resource that can create texture resources to
    118     // share with the plugin. We can't use the plugin's Graphics3D, since we
    119     // create textures on a proxy thread, and would interfere with the plugin.
    120     thunk::EnterResourceCreationNoLock enter_create(pp_instance());
    121     if (enter_create.failed())
    122       return PP_ERROR_FAILED;
    123     int32_t attrib_list[] = {PP_GRAPHICS3DATTRIB_NONE};
    124     graphics3d_ =
    125         ScopedPPResource(ScopedPPResource::PassRef(),
    126                          enter_create.functions()->CreateGraphics3D(
    127                              pp_instance(), graphics_context, attrib_list));
    128     EnterResourceNoLock<PPB_Graphics3D_API> enter_graphics(graphics3d_.get(),
    129                                                            false);
    130     if (enter_graphics.failed())
    131       return PP_ERROR_BADRESOURCE;
    132 
    133     PPB_Graphics3D_Shared* ppb_graphics3d_shared =
    134         static_cast<PPB_Graphics3D_Shared*>(enter_graphics.object());
    135     gles2_impl_ = ppb_graphics3d_shared->gles2_impl();
    136     host_resource = ppb_graphics3d_shared->host_resource();
    137   }
    138 
    139   initialize_callback_ = callback;
    140 
    141   Call<PpapiPluginMsg_VideoDecoder_InitializeReply>(
    142       RENDERER,
    143       PpapiHostMsg_VideoDecoder_Initialize(
    144           host_resource, profile, acceleration),
    145       base::Bind(&VideoDecoderResource::OnPluginMsgInitializeComplete, this));
    146 
    147   return PP_OK_COMPLETIONPENDING;
    148 }
    149 
    150 int32_t VideoDecoderResource::Decode(uint32_t decode_id,
    151                                      uint32_t size,
    152                                      const void* buffer,
    153                                      scoped_refptr<TrackedCallback> callback) {
    154   if (decoder_last_error_)
    155     return decoder_last_error_;
    156   if (flush_callback_.get() || reset_callback_.get())
    157     return PP_ERROR_FAILED;
    158   if (decode_callback_.get())
    159     return PP_ERROR_INPROGRESS;
    160   if (size > kMaximumBitstreamBufferSize)
    161     return PP_ERROR_NOMEMORY;
    162 
    163   // If we allow the plugin to call Decode again, we must have somewhere to
    164   // copy their buffer.
    165   DCHECK(!available_shm_buffers_.empty() ||
    166          shm_buffers_.size() < kMaximumPendingDecodes);
    167 
    168   // Count up, wrapping back to 0 before overflowing.
    169   int32_t uid = ++num_decodes_;
    170   if (uid == std::numeric_limits<int32_t>::max())
    171     num_decodes_ = 0;
    172 
    173   // Save decode_id in a ring buffer. The ring buffer is sized to store
    174   // decode_id for the maximum picture delay.
    175   decode_ids_[uid % kMaximumPictureDelay] = decode_id;
    176 
    177   if (available_shm_buffers_.empty() ||
    178       available_shm_buffers_.back()->shm->mapped_size() < size) {
    179     uint32_t shm_id;
    180     if (shm_buffers_.size() < kMaximumPendingDecodes) {
    181       // Signal the host to create a new shm buffer by passing an index outside
    182       // the legal range.
    183       shm_id = static_cast<uint32_t>(shm_buffers_.size());
    184     } else {
    185       // Signal the host to grow a buffer by passing a legal index. Choose the
    186       // last available shm buffer for simplicity.
    187       shm_id = available_shm_buffers_.back()->shm_id;
    188       available_shm_buffers_.pop_back();
    189     }
    190 
    191     // Synchronously get shared memory. Use GenericSyncCall so we can get the
    192     // reply params, which contain the handle.
    193     uint32_t shm_size = 0;
    194     IPC::Message reply;
    195     ResourceMessageReplyParams reply_params;
    196     int32_t result =
    197         GenericSyncCall(RENDERER,
    198                         PpapiHostMsg_VideoDecoder_GetShm(shm_id, size),
    199                         &reply,
    200                         &reply_params);
    201     if (result != PP_OK)
    202       return PP_ERROR_FAILED;
    203     if (!UnpackMessage<PpapiPluginMsg_VideoDecoder_GetShmReply>(reply,
    204                                                                 &shm_size))
    205       return PP_ERROR_FAILED;
    206     base::SharedMemoryHandle shm_handle = base::SharedMemory::NULLHandle();
    207     if (!reply_params.TakeSharedMemoryHandleAtIndex(0, &shm_handle))
    208       return PP_ERROR_NOMEMORY;
    209     scoped_ptr<base::SharedMemory> shm(
    210         new base::SharedMemory(shm_handle, false /* read_only */));
    211     scoped_ptr<ShmBuffer> shm_buffer(
    212         new ShmBuffer(shm.Pass(), shm_size, shm_id));
    213     if (!shm_buffer->addr)
    214       return PP_ERROR_NOMEMORY;
    215 
    216     available_shm_buffers_.push_back(shm_buffer.get());
    217     if (shm_buffers_.size() < kMaximumPendingDecodes) {
    218       shm_buffers_.push_back(shm_buffer.release());
    219     } else {
    220       // Delete manually since ScopedVector won't delete the existing element if
    221       // we just assign it.
    222       delete shm_buffers_[shm_id];
    223       shm_buffers_[shm_id] = shm_buffer.release();
    224     }
    225   }
    226 
    227   // At this point we should have shared memory to hold the plugin's buffer.
    228   DCHECK(!available_shm_buffers_.empty() &&
    229          available_shm_buffers_.back()->shm->mapped_size() >= size);
    230 
    231   ShmBuffer* shm_buffer = available_shm_buffers_.back();
    232   available_shm_buffers_.pop_back();
    233   memcpy(shm_buffer->addr, buffer, size);
    234 
    235   Call<PpapiPluginMsg_VideoDecoder_DecodeReply>(
    236       RENDERER,
    237       PpapiHostMsg_VideoDecoder_Decode(shm_buffer->shm_id, size, uid),
    238       base::Bind(&VideoDecoderResource::OnPluginMsgDecodeComplete, this));
    239 
    240   // If we have another free buffer, or we can still create new buffers, let
    241   // the plugin call Decode again.
    242   if (!available_shm_buffers_.empty() ||
    243       shm_buffers_.size() < kMaximumPendingDecodes)
    244     return PP_OK;
    245 
    246   // All buffers are busy and we can't create more. Delay completion until a
    247   // buffer is available.
    248   decode_callback_ = callback;
    249   return PP_OK_COMPLETIONPENDING;
    250 }
    251 
    252 int32_t VideoDecoderResource::GetPicture(
    253     PP_VideoPicture* picture,
    254     scoped_refptr<TrackedCallback> callback) {
    255   if (decoder_last_error_)
    256     return decoder_last_error_;
    257   if (reset_callback_.get())
    258     return PP_ERROR_FAILED;
    259   if (get_picture_callback_.get())
    260     return PP_ERROR_INPROGRESS;
    261 
    262   // If the next picture is ready, return it synchronously.
    263   if (!received_pictures_.empty()) {
    264     WriteNextPicture(picture);
    265     return PP_OK;
    266   }
    267 
    268   get_picture_callback_ = callback;
    269   get_picture_ = picture;
    270   return PP_OK_COMPLETIONPENDING;
    271 }
    272 
    273 void VideoDecoderResource::RecyclePicture(const PP_VideoPicture* picture) {
    274   if (decoder_last_error_)
    275     return;
    276 
    277   Post(RENDERER, PpapiHostMsg_VideoDecoder_RecyclePicture(picture->texture_id));
    278 }
    279 
    280 int32_t VideoDecoderResource::Flush(scoped_refptr<TrackedCallback> callback) {
    281   if (decoder_last_error_)
    282     return decoder_last_error_;
    283   if (reset_callback_.get())
    284     return PP_ERROR_FAILED;
    285   if (flush_callback_.get())
    286     return PP_ERROR_INPROGRESS;
    287   flush_callback_ = callback;
    288 
    289   Call<PpapiPluginMsg_VideoDecoder_FlushReply>(
    290       RENDERER,
    291       PpapiHostMsg_VideoDecoder_Flush(),
    292       base::Bind(&VideoDecoderResource::OnPluginMsgFlushComplete, this));
    293 
    294   return PP_OK_COMPLETIONPENDING;
    295 }
    296 
    297 int32_t VideoDecoderResource::Reset(scoped_refptr<TrackedCallback> callback) {
    298   if (decoder_last_error_)
    299     return decoder_last_error_;
    300   if (flush_callback_.get())
    301     return PP_ERROR_FAILED;
    302   if (reset_callback_.get())
    303     return PP_ERROR_INPROGRESS;
    304   reset_callback_ = callback;
    305 
    306   // Cause any pending Decode or GetPicture callbacks to abort after we return,
    307   // to avoid reentering the plugin.
    308   if (TrackedCallback::IsPending(decode_callback_))
    309     decode_callback_->PostAbort();
    310   decode_callback_ = NULL;
    311   if (TrackedCallback::IsPending(get_picture_callback_))
    312     get_picture_callback_->PostAbort();
    313   get_picture_callback_ = NULL;
    314   Call<PpapiPluginMsg_VideoDecoder_ResetReply>(
    315       RENDERER,
    316       PpapiHostMsg_VideoDecoder_Reset(),
    317       base::Bind(&VideoDecoderResource::OnPluginMsgResetComplete, this));
    318 
    319   return PP_OK_COMPLETIONPENDING;
    320 }
    321 
    322 void VideoDecoderResource::OnReplyReceived(
    323     const ResourceMessageReplyParams& params,
    324     const IPC::Message& msg) {
    325   PPAPI_BEGIN_MESSAGE_MAP(VideoDecoderResource, msg)
    326     PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
    327         PpapiPluginMsg_VideoDecoder_RequestTextures, OnPluginMsgRequestTextures)
    328     PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
    329         PpapiPluginMsg_VideoDecoder_PictureReady, OnPluginMsgPictureReady)
    330     PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
    331         PpapiPluginMsg_VideoDecoder_DismissPicture, OnPluginMsgDismissPicture)
    332     PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL(
    333         PpapiPluginMsg_VideoDecoder_NotifyError, OnPluginMsgNotifyError)
    334     PPAPI_DISPATCH_PLUGIN_RESOURCE_CALL_UNHANDLED(
    335         PluginResource::OnReplyReceived(params, msg))
    336   PPAPI_END_MESSAGE_MAP()
    337 }
    338 
    339 void VideoDecoderResource::SetForTest() {
    340   testing_ = true;
    341 }
    342 
    343 void VideoDecoderResource::OnPluginMsgRequestTextures(
    344     const ResourceMessageReplyParams& params,
    345     uint32_t num_textures,
    346     const PP_Size& size,
    347     uint32_t texture_target,
    348     const std::vector<gpu::Mailbox>& mailboxes) {
    349   DCHECK(num_textures);
    350   DCHECK(mailboxes.empty() || mailboxes.size() == num_textures);
    351   std::vector<uint32_t> texture_ids(num_textures);
    352   if (gles2_impl_) {
    353     gles2_impl_->GenTextures(num_textures, &texture_ids.front());
    354     for (uint32_t i = 0; i < num_textures; ++i) {
    355       gles2_impl_->ActiveTexture(GL_TEXTURE0);
    356       gles2_impl_->BindTexture(texture_target, texture_ids[i]);
    357       gles2_impl_->TexParameteri(
    358           texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    359       gles2_impl_->TexParameteri(
    360           texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    361       gles2_impl_->TexParameterf(
    362           texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    363       gles2_impl_->TexParameterf(
    364           texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    365 
    366       if (texture_target == GL_TEXTURE_2D) {
    367         gles2_impl_->TexImage2D(texture_target,
    368                                 0,
    369                                 GL_RGBA,
    370                                 size.width,
    371                                 size.height,
    372                                 0,
    373                                 GL_RGBA,
    374                                 GL_UNSIGNED_BYTE,
    375                                 NULL);
    376       }
    377       if (!mailboxes.empty()) {
    378         gles2_impl_->ProduceTextureCHROMIUM(
    379             GL_TEXTURE_2D, reinterpret_cast<const GLbyte*>(mailboxes[i].name));
    380       }
    381 
    382       textures_.insert(
    383           std::make_pair(texture_ids[i], Texture(texture_target, size)));
    384     }
    385     gles2_impl_->Flush();
    386   } else {
    387     DCHECK(testing_);
    388     // Create some fake texture ids so we can test picture handling.
    389     for (uint32_t i = 0; i < num_textures; ++i) {
    390       texture_ids[i] = i + 1;
    391       textures_.insert(
    392           std::make_pair(texture_ids[i], Texture(texture_target, size)));
    393     }
    394   }
    395 
    396   Post(RENDERER, PpapiHostMsg_VideoDecoder_AssignTextures(size, texture_ids));
    397 }
    398 
    399 void VideoDecoderResource::OnPluginMsgPictureReady(
    400     const ResourceMessageReplyParams& params,
    401     int32_t decode_id,
    402     uint32_t texture_id) {
    403   received_pictures_.push(Picture(decode_id, texture_id));
    404 
    405   if (TrackedCallback::IsPending(get_picture_callback_)) {
    406     // The plugin may call GetPicture in its callback.
    407     scoped_refptr<TrackedCallback> callback;
    408     callback.swap(get_picture_callback_);
    409     PP_VideoPicture* picture = get_picture_;
    410     get_picture_ = NULL;
    411     WriteNextPicture(picture);
    412     callback->Run(PP_OK);
    413   }
    414 }
    415 
    416 void VideoDecoderResource::OnPluginMsgDismissPicture(
    417     const ResourceMessageReplyParams& params,
    418     uint32_t texture_id) {
    419   DeleteGLTexture(texture_id);
    420   textures_.erase(texture_id);
    421 }
    422 
    423 void VideoDecoderResource::OnPluginMsgNotifyError(
    424     const ResourceMessageReplyParams& params,
    425     int32_t error) {
    426   decoder_last_error_ = error;
    427   // Cause any pending callbacks to run immediately. Reentrancy isn't a problem,
    428   // since the plugin wasn't calling us.
    429   RunCallbackWithError(&initialize_callback_);
    430   RunCallbackWithError(&decode_callback_);
    431   RunCallbackWithError(&get_picture_callback_);
    432   RunCallbackWithError(&flush_callback_);
    433   RunCallbackWithError(&reset_callback_);
    434 }
    435 
    436 void VideoDecoderResource::OnPluginMsgInitializeComplete(
    437     const ResourceMessageReplyParams& params) {
    438   decoder_last_error_ = params.result();
    439   if (decoder_last_error_ == PP_OK)
    440     initialized_ = true;
    441 
    442   // Let the plugin call Initialize again from its callback in case of failure.
    443   scoped_refptr<TrackedCallback> callback;
    444   callback.swap(initialize_callback_);
    445   callback->Run(decoder_last_error_);
    446 }
    447 
    448 void VideoDecoderResource::OnPluginMsgDecodeComplete(
    449     const ResourceMessageReplyParams& params,
    450     uint32_t shm_id) {
    451   if (shm_id >= shm_buffers_.size()) {
    452     NOTREACHED();
    453     return;
    454   }
    455   // Make the shm buffer available.
    456   available_shm_buffers_.push_back(shm_buffers_[shm_id]);
    457   // If the plugin is waiting, let it call Decode again.
    458   if (decode_callback_.get()) {
    459     scoped_refptr<TrackedCallback> callback;
    460     callback.swap(decode_callback_);
    461     callback->Run(PP_OK);
    462   }
    463 }
    464 
    465 void VideoDecoderResource::OnPluginMsgFlushComplete(
    466     const ResourceMessageReplyParams& params) {
    467   // All shm buffers should have been made available by now.
    468   DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size());
    469 
    470   if (get_picture_callback_.get()) {
    471     scoped_refptr<TrackedCallback> callback;
    472     callback.swap(get_picture_callback_);
    473     callback->Abort();
    474   }
    475 
    476   scoped_refptr<TrackedCallback> callback;
    477   callback.swap(flush_callback_);
    478   callback->Run(params.result());
    479 }
    480 
    481 void VideoDecoderResource::OnPluginMsgResetComplete(
    482     const ResourceMessageReplyParams& params) {
    483   // All shm buffers should have been made available by now.
    484   DCHECK_EQ(shm_buffers_.size(), available_shm_buffers_.size());
    485   // Recycle any pictures which haven't been passed to the plugin.
    486   while (!received_pictures_.empty()) {
    487     Post(RENDERER, PpapiHostMsg_VideoDecoder_RecyclePicture(
    488         received_pictures_.front().texture_id));
    489     received_pictures_.pop();
    490   }
    491 
    492   scoped_refptr<TrackedCallback> callback;
    493   callback.swap(reset_callback_);
    494   callback->Run(params.result());
    495 }
    496 
    497 void VideoDecoderResource::RunCallbackWithError(
    498     scoped_refptr<TrackedCallback>* callback) {
    499   if (TrackedCallback::IsPending(*callback)) {
    500     scoped_refptr<TrackedCallback> temp;
    501     callback->swap(temp);
    502     temp->Run(decoder_last_error_);
    503   }
    504 }
    505 
    506 void VideoDecoderResource::DeleteGLTexture(uint32_t id) {
    507   if (gles2_impl_) {
    508     gles2_impl_->DeleteTextures(1, &id);
    509     gles2_impl_->Flush();
    510   }
    511 }
    512 
    513 void VideoDecoderResource::WriteNextPicture(PP_VideoPicture* pp_picture) {
    514   DCHECK(!received_pictures_.empty());
    515   Picture& picture = received_pictures_.front();
    516   // Internally, we identify decodes by a unique id, which the host returns
    517   // to us in the picture. Use this to get the plugin's decode_id.
    518   pp_picture->decode_id = decode_ids_[picture.decode_id % kMaximumPictureDelay];
    519   pp_picture->texture_id = picture.texture_id;
    520   TextureMap::iterator it = textures_.find(picture.texture_id);
    521   if (it != textures_.end()) {
    522     pp_picture->texture_target = it->second.texture_target;
    523     pp_picture->texture_size = it->second.size;
    524   } else {
    525     NOTREACHED();
    526   }
    527   received_pictures_.pop();
    528 }
    529 
    530 }  // namespace proxy
    531 }  // namespace ppapi
    532