|  | // 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 "content/common/gpu/client/command_buffer_proxy_impl.h" | 
|  |  | 
|  | #include "base/callback.h" | 
|  | #include "base/debug/trace_event.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/process_util.h" | 
|  | #include "base/shared_memory.h" | 
|  | #include "base/stl_util.h" | 
|  | #include "content/common/child_process_messages.h" | 
|  | #include "content/common/gpu/gpu_memory_allocation.h" | 
|  | #include "content/common/gpu/client/gpu_channel_host.h" | 
|  | #include "content/common/gpu/gpu_messages.h" | 
|  | #include "content/common/plugin_messages.h" | 
|  | #include "content/common/view_messages.h" | 
|  | #include "gpu/command_buffer/common/cmd_buffer_common.h" | 
|  | #include "gpu/command_buffer/common/command_buffer_shared.h" | 
|  | #include "ui/gfx/size.h" | 
|  |  | 
|  | #if defined(OS_WIN) | 
|  | #include "content/public/common/sandbox_init.h" | 
|  | #endif | 
|  |  | 
|  | using gpu::Buffer; | 
|  |  | 
|  | CommandBufferProxyImpl::CommandBufferProxyImpl( | 
|  | GpuChannelHost* channel, | 
|  | int route_id) | 
|  | : shared_state_(NULL), | 
|  | channel_(channel), | 
|  | route_id_(route_id), | 
|  | flush_count_(0), | 
|  | last_put_offset_(-1), | 
|  | next_signal_id_(0), | 
|  | state_buffer_(-1) { | 
|  | } | 
|  |  | 
|  | CommandBufferProxyImpl::~CommandBufferProxyImpl() { | 
|  | if (state_buffer_ != -1) | 
|  | DestroyTransferBuffer(state_buffer_); | 
|  | // Delete all the locally cached shared memory objects, closing the handle | 
|  | // in this process. | 
|  | for (TransferBufferMap::iterator it = transfer_buffers_.begin(); | 
|  | it != transfer_buffers_.end(); | 
|  | ++it) { | 
|  | delete it->second.shared_memory; | 
|  | it->second.shared_memory = NULL; | 
|  | } | 
|  | for (Decoders::iterator it = video_decoder_hosts_.begin(); | 
|  | it != video_decoder_hosts_.end(); ++it) { | 
|  | if (it->second) | 
|  | it->second->OnChannelError(); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::OnMessageReceived(const IPC::Message& message) { | 
|  | bool handled = true; | 
|  | IPC_BEGIN_MESSAGE_MAP(CommandBufferProxyImpl, message) | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_Destroyed, OnDestroyed); | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_NotifyRepaint, | 
|  | OnNotifyRepaint); | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_EchoAck, OnEchoAck); | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_ConsoleMsg, OnConsoleMessage); | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_SetMemoryAllocation, | 
|  | OnSetMemoryAllocation); | 
|  | IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_SignalSyncPointAck, | 
|  | OnSignalSyncPointAck); | 
|  | IPC_MESSAGE_UNHANDLED(handled = false) | 
|  | IPC_END_MESSAGE_MAP() | 
|  |  | 
|  | DCHECK(handled); | 
|  | return handled; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnChannelError() { | 
|  | OnDestroyed(gpu::error::kUnknown); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnDestroyed(gpu::error::ContextLostReason reason) { | 
|  | // Prevent any further messages from being sent. | 
|  | channel_ = NULL; | 
|  |  | 
|  | // When the client sees that the context is lost, they should delete this | 
|  | // CommandBufferProxyImpl and create a new one. | 
|  | last_state_.error = gpu::error::kLostContext; | 
|  | last_state_.context_lost_reason = reason; | 
|  |  | 
|  | if (!channel_error_callback_.is_null()) { | 
|  | channel_error_callback_.Run(); | 
|  | // Avoid calling the error callback more than once. | 
|  | channel_error_callback_.Reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnEchoAck() { | 
|  | DCHECK(!echo_tasks_.empty()); | 
|  | base::Closure callback = echo_tasks_.front(); | 
|  | echo_tasks_.pop(); | 
|  | callback.Run(); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnConsoleMessage( | 
|  | const GPUCommandBufferConsoleMessage& message) { | 
|  | if (!console_message_callback_.is_null()) { | 
|  | console_message_callback_.Run(message.message, message.id); | 
|  | } | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetMemoryAllocationChangedCallback( | 
|  | const base::Callback<void(const GpuMemoryAllocationForRenderer&)>& | 
|  | callback) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return; | 
|  |  | 
|  | memory_allocation_changed_callback_ = callback; | 
|  | Send(new GpuCommandBufferMsg_SetClientHasMemoryAllocationChangedCallback( | 
|  | route_id_, !memory_allocation_changed_callback_.is_null())); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnSetMemoryAllocation( | 
|  | const GpuMemoryAllocationForRenderer& allocation) { | 
|  | if (!memory_allocation_changed_callback_.is_null()) | 
|  | memory_allocation_changed_callback_.Run(allocation); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnSignalSyncPointAck(uint32 id) { | 
|  | SignalTaskMap::iterator it = signal_tasks_.find(id); | 
|  | DCHECK(it != signal_tasks_.end()); | 
|  | base::Closure callback = it->second; | 
|  | signal_tasks_.erase(it); | 
|  | callback.Run(); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetChannelErrorCallback( | 
|  | const base::Closure& callback) { | 
|  | channel_error_callback_ = callback; | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::Initialize() { | 
|  | bool result; | 
|  | if (!Send(new GpuCommandBufferMsg_Initialize(route_id_, &result))) { | 
|  | LOG(ERROR) << "Could not send GpuCommandBufferMsg_Initialize."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!result) { | 
|  | LOG(ERROR) << "Failed to initialize command buffer service."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | state_buffer_ = CreateTransferBuffer(sizeof *shared_state_, -1); | 
|  |  | 
|  | if (state_buffer_ == -1) { | 
|  | LOG(ERROR) << "Failed to create shared state transfer buffer."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | gpu::Buffer buffer = GetTransferBuffer(state_buffer_); | 
|  | if (!buffer.ptr) { | 
|  | LOG(ERROR) << "Failed to get shared state transfer buffer"; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | shared_state_ = reinterpret_cast<gpu::CommandBufferSharedState*>(buffer.ptr); | 
|  | shared_state_->Initialize(); | 
|  |  | 
|  | if (!Send(new GpuCommandBufferMsg_SetSharedStateBuffer(route_id_, | 
|  | state_buffer_))) { | 
|  | LOG(ERROR) << "Failed to initialize shared command buffer state."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | gpu::CommandBuffer::State CommandBufferProxyImpl::GetState() { | 
|  | // Send will flag state with lost context if IPC fails. | 
|  | if (last_state_.error == gpu::error::kNoError) { | 
|  | gpu::CommandBuffer::State state; | 
|  | if (Send(new GpuCommandBufferMsg_GetState(route_id_, &state))) | 
|  | OnUpdateState(state); | 
|  | } | 
|  |  | 
|  | TryUpdateState(); | 
|  | return last_state_; | 
|  | } | 
|  |  | 
|  | gpu::CommandBuffer::State CommandBufferProxyImpl::GetLastState() { | 
|  | return last_state_; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::Flush(int32 put_offset) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return; | 
|  |  | 
|  | TRACE_EVENT1("gpu", | 
|  | "CommandBufferProxyImpl::Flush", | 
|  | "put_offset", | 
|  | put_offset); | 
|  |  | 
|  | if (last_put_offset_ == put_offset) | 
|  | return; | 
|  |  | 
|  | last_put_offset_ = put_offset; | 
|  |  | 
|  | Send(new GpuCommandBufferMsg_AsyncFlush(route_id_, | 
|  | put_offset, | 
|  | ++flush_count_)); | 
|  | } | 
|  |  | 
|  | gpu::CommandBuffer::State CommandBufferProxyImpl::FlushSync( | 
|  | int32 put_offset, | 
|  | int32 last_known_get) { | 
|  | TRACE_EVENT1("gpu", "CommandBufferProxyImpl::FlushSync", "put_offset", | 
|  | put_offset); | 
|  | Flush(put_offset); | 
|  | TryUpdateState(); | 
|  | if (last_known_get == last_state_.get_offset) { | 
|  | // Send will flag state with lost context if IPC fails. | 
|  | if (last_state_.error == gpu::error::kNoError) { | 
|  | gpu::CommandBuffer::State state; | 
|  | if (Send(new GpuCommandBufferMsg_GetStateFast(route_id_, | 
|  | &state))) | 
|  | OnUpdateState(state); | 
|  | } | 
|  | TryUpdateState(); | 
|  | } | 
|  |  | 
|  | return last_state_; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetGetBuffer(int32 shm_id) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return; | 
|  |  | 
|  | Send(new GpuCommandBufferMsg_SetGetBuffer(route_id_, shm_id)); | 
|  | last_put_offset_ = -1; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetGetOffset(int32 get_offset) { | 
|  | // Not implemented in proxy. | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | int32 CommandBufferProxyImpl::CreateTransferBuffer( | 
|  | size_t size, int32 id_request) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return -1; | 
|  |  | 
|  | // Take ownership of shared memory. This will close the handle if Send below | 
|  | // fails. Otherwise, callee takes ownership before this variable | 
|  | // goes out of scope by duping the handle. | 
|  | scoped_ptr<base::SharedMemory> shm( | 
|  | channel_->factory()->AllocateSharedMemory(size)); | 
|  | if (!shm.get()) | 
|  | return -1; | 
|  |  | 
|  | base::SharedMemoryHandle handle = shm->handle(); | 
|  | #if defined(OS_WIN) | 
|  | // Windows needs to explicitly duplicate the handle out to another process. | 
|  | if (!content::BrokerDuplicateHandle(handle, channel_->gpu_pid(), | 
|  | &handle, FILE_MAP_WRITE, 0)) { | 
|  | return -1; | 
|  | } | 
|  | #elif defined(OS_POSIX) | 
|  | DCHECK(!handle.auto_close); | 
|  | #endif | 
|  |  | 
|  | int32 id; | 
|  | if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer(route_id_, | 
|  | handle, | 
|  | size, | 
|  | id_request, | 
|  | &id))) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return id; | 
|  | } | 
|  |  | 
|  | int32 CommandBufferProxyImpl::RegisterTransferBuffer( | 
|  | base::SharedMemory* shared_memory, | 
|  | size_t size, | 
|  | int32 id_request) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return -1; | 
|  |  | 
|  | // Returns FileDescriptor with auto_close off. | 
|  | base::SharedMemoryHandle handle = shared_memory->handle(); | 
|  | #if defined(OS_WIN) | 
|  | // Windows needs to explicitly duplicate the handle out to another process. | 
|  | if (!content::BrokerDuplicateHandle(handle, channel_->gpu_pid(), | 
|  | &handle, FILE_MAP_WRITE, 0)) { | 
|  | return -1; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | int32 id; | 
|  | if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer( | 
|  | route_id_, | 
|  | handle, | 
|  | size, | 
|  | id_request, | 
|  | &id))) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return id; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::DestroyTransferBuffer(int32 id) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return; | 
|  |  | 
|  | // Remove the transfer buffer from the client side cache. | 
|  | TransferBufferMap::iterator it = transfer_buffers_.find(id); | 
|  | if (it != transfer_buffers_.end()) { | 
|  | delete it->second.shared_memory; | 
|  | transfer_buffers_.erase(it); | 
|  | } | 
|  |  | 
|  | Send(new GpuCommandBufferMsg_DestroyTransferBuffer(route_id_, id)); | 
|  | } | 
|  |  | 
|  | Buffer CommandBufferProxyImpl::GetTransferBuffer(int32 id) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return Buffer(); | 
|  |  | 
|  | // Check local cache to see if there is already a client side shared memory | 
|  | // object for this id. | 
|  | TransferBufferMap::iterator it = transfer_buffers_.find(id); | 
|  | if (it != transfer_buffers_.end()) { | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | // Assuming we are in the renderer process, the service is responsible for | 
|  | // duplicating the handle. This might not be true for NaCl. | 
|  | base::SharedMemoryHandle handle; | 
|  | uint32 size; | 
|  | if (!Send(new GpuCommandBufferMsg_GetTransferBuffer(route_id_, | 
|  | id, | 
|  | &handle, | 
|  | &size))) { | 
|  | return Buffer(); | 
|  | } | 
|  |  | 
|  | // Cache the transfer buffer shared memory object client side. | 
|  | base::SharedMemory* shared_memory = new base::SharedMemory(handle, false); | 
|  |  | 
|  | // Map the shared memory on demand. | 
|  | if (!shared_memory->memory()) { | 
|  | if (!shared_memory->Map(size)) { | 
|  | delete shared_memory; | 
|  | return Buffer(); | 
|  | } | 
|  | } | 
|  |  | 
|  | Buffer buffer; | 
|  | buffer.ptr = shared_memory->memory(); | 
|  | buffer.size = size; | 
|  | buffer.shared_memory = shared_memory; | 
|  | transfer_buffers_[id] = buffer; | 
|  |  | 
|  | return buffer; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetToken(int32 token) { | 
|  | // Not implemented in proxy. | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnNotifyRepaint() { | 
|  | if (!notify_repaint_task_.is_null()) | 
|  | MessageLoop::current()->PostNonNestableTask( | 
|  | FROM_HERE, notify_repaint_task_); | 
|  | notify_repaint_task_.Reset(); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetParseError( | 
|  | gpu::error::Error error) { | 
|  | // Not implemented in proxy. | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetContextLostReason( | 
|  | gpu::error::ContextLostReason reason) { | 
|  | // Not implemented in proxy. | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | int CommandBufferProxyImpl::GetRouteID() const { | 
|  | return route_id_; | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::Echo(const base::Closure& callback) { | 
|  | if (last_state_.error != gpu::error::kNoError) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!Send(new GpuCommandBufferMsg_Echo(route_id_, | 
|  | GpuCommandBufferMsg_EchoAck(route_id_)))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | echo_tasks_.push(callback); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::SetSurfaceVisible(bool visible) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return false; | 
|  |  | 
|  | return Send(new GpuCommandBufferMsg_SetSurfaceVisible(route_id_, visible)); | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::DiscardBackbuffer() { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return false; | 
|  |  | 
|  | return Send(new GpuCommandBufferMsg_DiscardBackbuffer(route_id_)); | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::EnsureBackbuffer() { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return false; | 
|  |  | 
|  | return Send(new GpuCommandBufferMsg_EnsureBackbuffer(route_id_)); | 
|  | } | 
|  |  | 
|  | uint32 CommandBufferProxyImpl::InsertSyncPoint() { | 
|  | uint32 sync_point = 0; | 
|  | Send(new GpuCommandBufferMsg_InsertSyncPoint(route_id_, &sync_point)); | 
|  | return sync_point; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::WaitSyncPoint(uint32 sync_point) { | 
|  | Send(new GpuCommandBufferMsg_WaitSyncPoint(route_id_, sync_point)); | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::SignalSyncPoint(uint32 sync_point, | 
|  | const base::Closure& callback) { | 
|  | if (last_state_.error != gpu::error::kNoError) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint32 signal_id = next_signal_id_++; | 
|  | if (!Send(new GpuCommandBufferMsg_SignalSyncPoint(route_id_, | 
|  | sync_point, | 
|  | signal_id))) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | signal_tasks_.insert(std::make_pair(signal_id, callback)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::SetParent( | 
|  | CommandBufferProxy* parent_command_buffer, | 
|  | uint32 parent_texture_id) { | 
|  | if (last_state_.error != gpu::error::kNoError) | 
|  | return false; | 
|  |  | 
|  | bool result; | 
|  | if (parent_command_buffer) { | 
|  | if (!Send(new GpuCommandBufferMsg_SetParent( | 
|  | route_id_, | 
|  | parent_command_buffer->GetRouteID(), | 
|  | parent_texture_id, | 
|  | &result))) { | 
|  | return false; | 
|  | } | 
|  | } else { | 
|  | if (!Send(new GpuCommandBufferMsg_SetParent( | 
|  | route_id_, | 
|  | MSG_ROUTING_NONE, | 
|  | 0, | 
|  | &result))) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetNotifyRepaintTask(const base::Closure& task) { | 
|  | notify_repaint_task_ = task; | 
|  | } | 
|  |  | 
|  | GpuVideoDecodeAcceleratorHost* | 
|  | CommandBufferProxyImpl::CreateVideoDecoder( | 
|  | media::VideoCodecProfile profile, | 
|  | media::VideoDecodeAccelerator::Client* client) { | 
|  | int decoder_route_id; | 
|  | if (!Send(new GpuCommandBufferMsg_CreateVideoDecoder(route_id_, profile, | 
|  | &decoder_route_id))) { | 
|  | LOG(ERROR) << "Send(GpuCommandBufferMsg_CreateVideoDecoder) failed"; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (decoder_route_id < 0) { | 
|  | DLOG(ERROR) << "Failed to Initialize GPU decoder on profile: " << profile; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | GpuVideoDecodeAcceleratorHost* decoder_host = | 
|  | new GpuVideoDecodeAcceleratorHost(channel_, decoder_route_id, client); | 
|  | bool inserted = video_decoder_hosts_.insert(std::make_pair( | 
|  | decoder_route_id, base::AsWeakPtr(decoder_host))).second; | 
|  | DCHECK(inserted); | 
|  |  | 
|  | channel_->AddRoute(decoder_route_id, base::AsWeakPtr(decoder_host)); | 
|  |  | 
|  | return decoder_host; | 
|  | } | 
|  |  | 
|  | gpu::error::Error CommandBufferProxyImpl::GetLastError() { | 
|  | return last_state_.error; | 
|  | } | 
|  |  | 
|  | bool CommandBufferProxyImpl::Send(IPC::Message* msg) { | 
|  | // Caller should not intentionally send a message if the context is lost. | 
|  | DCHECK(last_state_.error == gpu::error::kNoError); | 
|  |  | 
|  | if (channel_) { | 
|  | if (channel_->Send(msg)) { | 
|  | return true; | 
|  | } else { | 
|  | // Flag the command buffer as lost. Defer deleting the channel until | 
|  | // OnChannelError is called after returning to the message loop in case | 
|  | // it is referenced elsewhere. | 
|  | last_state_.error = gpu::error::kLostContext; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Callee takes ownership of message, regardless of whether Send is | 
|  | // successful. See IPC::Sender. | 
|  | delete msg; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::OnUpdateState( | 
|  | const gpu::CommandBuffer::State& state) { | 
|  | // Handle wraparound. It works as long as we don't have more than 2B state | 
|  | // updates in flight across which reordering occurs. | 
|  | if (state.generation - last_state_.generation < 0x80000000U) | 
|  | last_state_ = state; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::SetOnConsoleMessageCallback( | 
|  | const GpuConsoleMessageCallback& callback) { | 
|  | console_message_callback_ = callback; | 
|  | } | 
|  |  | 
|  | void CommandBufferProxyImpl::TryUpdateState() { | 
|  | if (last_state_.error == gpu::error::kNoError) | 
|  | shared_state_->Read(&last_state_); | 
|  | } |