// 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 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 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 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 enter_graphics(graphics3d_.get(), false); if (enter_graphics.failed()) return PP_ERROR_BADRESOURCE; PPB_Graphics3D_Shared* ppb_graphics3d_shared = static_cast(enter_graphics.object()); gles2_impl_ = ppb_graphics3d_shared->gles2_impl(); host_resource = ppb_graphics3d_shared->host_resource(); } initialize_callback_ = callback; Call( 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 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::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(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(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 shm( new base::SharedMemory(shm_handle, false /* read_only */)); scoped_ptr 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( 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 callback) { get_picture_0_1_ = picture; return GetPicture(NULL, callback); } int32_t VideoDecoderResource::GetPicture( PP_VideoPicture* picture, scoped_refptr 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 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( RENDERER, PpapiHostMsg_VideoDecoder_Flush(), base::Bind(&VideoDecoderResource::OnPluginMsgFlushComplete, this)); return PP_OK_COMPLETIONPENDING; } int32_t VideoDecoderResource::Reset(scoped_refptr 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( 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& mailboxes) { DCHECK(num_textures); DCHECK(mailboxes.empty() || mailboxes.size() == num_textures); std::vector 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(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 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 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 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 callback; callback.swap(get_picture_callback_); callback->Abort(); } scoped_refptr 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 callback; callback.swap(reset_callback_); callback->Run(params.result()); } void VideoDecoderResource::RunCallbackWithError( scoped_refptr* callback) { if (TrackedCallback::IsPending(*callback)) { scoped_refptr 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