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