blob: 865f90fe6e50fa2bf58553d7d616bbc2c9957de4 [file] [log] [blame]
// Copyright (c) 2009 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/renderer/command_buffer_proxy.h"
#include "base/logging.h"
#include "base/process_util.h"
#include "base/shared_memory.h"
#include "base/task.h"
#include "chrome/renderer/render_thread.h"
#include "content/common/gpu_messages.h"
#include "content/common/plugin_messages.h"
#include "content/common/view_messages.h"
#include "content/renderer/plugin_channel_host.h"
#include "gpu/command_buffer/common/cmd_buffer_common.h"
#include "ui/gfx/size.h"
using gpu::Buffer;
CommandBufferProxy::CommandBufferProxy(
IPC::Channel::Sender* channel,
int route_id)
: num_entries_(0),
channel_(channel),
route_id_(route_id) {
}
CommandBufferProxy::~CommandBufferProxy() {
// 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;
}
}
bool CommandBufferProxy::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(CommandBufferProxy, message)
IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_UpdateState, OnUpdateState);
IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_SwapBuffers, OnSwapBuffers);
IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_NotifyRepaint,
OnNotifyRepaint);
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
DCHECK(handled);
return handled;
}
void CommandBufferProxy::OnChannelError() {
// Prevent any further messages from being sent.
channel_ = NULL;
// When the client sees that the context is lost, they should delete this
// CommandBufferProxy and create a new one.
last_state_.error = gpu::error::kLostContext;
if (channel_error_callback_.get())
channel_error_callback_->Run();
}
void CommandBufferProxy::SetChannelErrorCallback(Callback0::Type* callback) {
channel_error_callback_.reset(callback);
}
bool CommandBufferProxy::Initialize(int32 size) {
DCHECK(!ring_buffer_.get());
RenderThread* render_thread = RenderThread::current();
if (!render_thread)
return false;
base::SharedMemoryHandle handle;
if (!render_thread->Send(new ViewHostMsg_AllocateSharedMemoryBuffer(
size,
&handle))) {
return false;
}
if (!base::SharedMemory::IsHandleValid(handle))
return false;
#if defined(OS_POSIX)
handle.auto_close = false;
#endif
// 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.
base::SharedMemory shared_memory(handle, false);
return Initialize(&shared_memory, size);
}
bool CommandBufferProxy::Initialize(base::SharedMemory* buffer, int32 size) {
bool result;
if (!Send(new GpuCommandBufferMsg_Initialize(route_id_,
buffer->handle(),
size,
&result))) {
LOG(ERROR) << "Could not send GpuCommandBufferMsg_Initialize.";
return false;
}
if (!result) {
LOG(ERROR) << "Failed to initialize command buffer service.";
return false;
}
base::SharedMemoryHandle handle;
if (!buffer->GiveToProcess(base::GetCurrentProcessHandle(), &handle)) {
LOG(ERROR) << "Failed to duplicate command buffer handle.";
return false;
}
ring_buffer_.reset(new base::SharedMemory(handle, false));
if (!ring_buffer_->Map(size)) {
LOG(ERROR) << "Failed to map shared memory for command buffer.";
ring_buffer_.reset();
return false;
}
num_entries_ = size / sizeof(gpu::CommandBufferEntry);
return true;
}
Buffer CommandBufferProxy::GetRingBuffer() {
DCHECK(ring_buffer_.get());
// Return locally cached ring buffer.
Buffer buffer;
buffer.ptr = ring_buffer_->memory();
buffer.size = num_entries_ * sizeof(gpu::CommandBufferEntry);
buffer.shared_memory = ring_buffer_.get();
return buffer;
}
gpu::CommandBuffer::State CommandBufferProxy::GetState() {
// Send will flag state with lost context if IPC fails.
if (last_state_.error == gpu::error::kNoError)
Send(new GpuCommandBufferMsg_GetState(route_id_, &last_state_));
return last_state_;
}
void CommandBufferProxy::Flush(int32 put_offset) {
AsyncFlush(put_offset, NULL);
}
gpu::CommandBuffer::State CommandBufferProxy::FlushSync(int32 put_offset) {
// Send will flag state with lost context if IPC fails.
if (last_state_.error == gpu::error::kNoError) {
Send(new GpuCommandBufferMsg_Flush(route_id_,
put_offset,
&last_state_));
}
return last_state_;
}
void CommandBufferProxy::SetGetOffset(int32 get_offset) {
// Not implemented in proxy.
NOTREACHED();
}
int32 CommandBufferProxy::CreateTransferBuffer(size_t size) {
if (last_state_.error != gpu::error::kNoError)
return -1;
RenderThread* render_thread = RenderThread::current();
if (!render_thread)
return -1;
base::SharedMemoryHandle handle;
if (!render_thread->Send(new ViewHostMsg_AllocateSharedMemoryBuffer(
size,
&handle))) {
return -1;
}
if (!base::SharedMemory::IsHandleValid(handle))
return -1;
// Handle is closed by the SharedMemory object below. This stops
// base::FileDescriptor from closing it as well.
#if defined(OS_POSIX)
handle.auto_close = false;
#endif
// 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.
base::SharedMemory shared_memory(handle, false);
int32 id;
if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer(route_id_,
handle,
size,
&id))) {
return -1;
}
return id;
}
int32 CommandBufferProxy::RegisterTransferBuffer(
base::SharedMemory* shared_memory,
size_t size) {
if (last_state_.error != gpu::error::kNoError)
return -1;
int32 id;
if (!Send(new GpuCommandBufferMsg_RegisterTransferBuffer(
route_id_,
shared_memory->handle(), // Returns FileDescriptor with auto_close off.
size,
&id))) {
return -1;
}
return id;
}
void CommandBufferProxy::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 CommandBufferProxy::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 CommandBufferProxy::SetToken(int32 token) {
// Not implemented in proxy.
NOTREACHED();
}
void CommandBufferProxy::OnNotifyRepaint() {
if (notify_repaint_task_.get())
MessageLoop::current()->PostNonNestableTask(
FROM_HERE, notify_repaint_task_.release());
}
void CommandBufferProxy::SetParseError(
gpu::error::Error error) {
// Not implemented in proxy.
NOTREACHED();
}
void CommandBufferProxy::OnSwapBuffers() {
if (swap_buffers_callback_.get())
swap_buffers_callback_->Run();
}
void CommandBufferProxy::SetSwapBuffersCallback(Callback0::Type* callback) {
swap_buffers_callback_.reset(callback);
}
void CommandBufferProxy::ResizeOffscreenFrameBuffer(const gfx::Size& size) {
if (last_state_.error != gpu::error::kNoError)
return;
IPC::Message* message =
new GpuCommandBufferMsg_ResizeOffscreenFrameBuffer(route_id_, size);
// We need to set the unblock flag on this message to guarantee the
// order in which it is processed in the GPU process. Ordinarily in
// certain situations, namely if a synchronous message is being
// processed, other synchronous messages may be processed before
// asynchronous messages. During some page reloads WebGL seems to
// send three messages (sync, async, sync) in rapid succession in
// that order, and the sync message (GpuCommandBufferMsg_Flush, on
// behalf of SwapBuffers) is sometimes processed before the async
// message (GpuCommandBufferMsg_ResizeOffscreenFrameBuffer). This
// causes the WebGL content to disappear because the back buffer is
// not correctly resized.
message->set_unblock(true);
Send(message);
}
void CommandBufferProxy::SetNotifyRepaintTask(Task* task) {
notify_repaint_task_.reset(task);
}
#if defined(OS_MACOSX)
void CommandBufferProxy::SetWindowSize(const gfx::Size& size) {
if (last_state_.error != gpu::error::kNoError)
return;
Send(new GpuCommandBufferMsg_SetWindowSize(route_id_, size));
}
#endif
void CommandBufferProxy::AsyncGetState(Task* completion_task) {
if (last_state_.error != gpu::error::kNoError)
return;
IPC::Message* message = new GpuCommandBufferMsg_AsyncGetState(route_id_);
// Do not let a synchronous flush hold up this message. If this handler is
// deferred until after the synchronous flush completes, it will overwrite the
// cached last_state_ with out-of-date data.
message->set_unblock(true);
if (Send(message))
pending_async_flush_tasks_.push(linked_ptr<Task>(completion_task));
}
void CommandBufferProxy::AsyncFlush(int32 put_offset, Task* completion_task) {
if (last_state_.error != gpu::error::kNoError)
return;
IPC::Message* message = new GpuCommandBufferMsg_AsyncFlush(route_id_,
put_offset);
// Do not let a synchronous flush hold up this message. If this handler is
// deferred until after the synchronous flush completes, it will overwrite the
// cached last_state_ with out-of-date data.
message->set_unblock(true);
if (Send(message))
pending_async_flush_tasks_.push(linked_ptr<Task>(completion_task));
}
bool CommandBufferProxy::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::Message::Sender.
delete msg;
return false;
}
void CommandBufferProxy::OnUpdateState(const gpu::CommandBuffer::State& state) {
last_state_ = state;
linked_ptr<Task> task = pending_async_flush_tasks_.front();
pending_async_flush_tasks_.pop();
if (task.get()) {
// Although we need need to update last_state_ while potentially waiting
// for a synchronous flush to complete, we do not need to invoke the
// callback synchonously. Also, post it as a non nestable task so it is
// always invoked by the outermost message loop.
MessageLoop::current()->PostNonNestableTask(FROM_HERE, task.release());
}
}