// Copyright (c) 2011 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. #if defined(ENABLE_GPU) #include "base/bind.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/shared_memory.h" #include "build/build_config.h" #include "content/common/gpu/gpu_channel.h" #include "content/common/gpu/gpu_channel_manager.h" #include "content/common/gpu/gpu_command_buffer_stub.h" #include "content/common/gpu/gpu_messages.h" #include "content/common/gpu/gpu_watchdog.h" #include "gpu/command_buffer/common/constants.h" #include "ui/gfx/gl/gl_switches.h" #if defined(OS_MACOSX) || defined(UI_COMPOSITOR_IMAGE_TRANSPORT) #include "content/common/gpu/image_transport_surface.h" #endif GpuCommandBufferStub::GpuCommandBufferStub( GpuChannel* channel, GpuCommandBufferStub* share_group, gfx::PluginWindowHandle handle, const gfx::Size& size, const gpu::gles2::DisallowedFeatures& disallowed_features, const std::string& allowed_extensions, const std::vector& attribs, gfx::GpuPreference gpu_preference, int32 route_id, int32 renderer_id, int32 render_view_id, GpuWatchdog* watchdog, bool software) : channel_(channel), handle_(handle), initial_size_(size), disallowed_features_(disallowed_features), allowed_extensions_(allowed_extensions), requested_attribs_(attribs), gpu_preference_(gpu_preference), route_id_(route_id), software_(software), last_flush_count_(0), renderer_id_(renderer_id), render_view_id_(render_view_id), parent_stub_for_initialization_(), parent_texture_for_initialization_(0), watchdog_(watchdog) { if (share_group) { context_group_ = share_group->context_group_; } else { // TODO(gman): this needs to be false for everything but Pepper. bool bind_generates_resource = true; context_group_ = new gpu::gles2::ContextGroup(bind_generates_resource); } } GpuCommandBufferStub::~GpuCommandBufferStub() { Destroy(); GpuChannelManager* gpu_channel_manager = channel_->gpu_channel_manager(); gpu_channel_manager->Send(new GpuHostMsg_DestroyCommandBuffer( handle_, renderer_id_, render_view_id_)); } bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { // Ensure the appropriate GL context is current before handling any IPC // messages directed at the command buffer. This ensures that the message // handler can assume that the context is current. if (decoder_.get()) { if (!decoder_->MakeCurrent()) { DLOG(ERROR) << "Context lost because MakeCurrent failed."; command_buffer_->SetContextLostReason(decoder_->GetContextLostReason()); command_buffer_->SetParseError(gpu::error::kLostContext); if (gfx::GLContext::LosesAllContextsOnContextLost()) channel_->LoseAllContexts(); return false; } } // Always use IPC_MESSAGE_HANDLER_DELAY_REPLY for synchronous message handlers // here. This is so the reply can be delayed if the scheduler is unscheduled. bool handled = true; IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message) IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_Initialize, OnInitialize); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_SetParent, OnSetParent); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_GetState, OnGetState); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_GetStateFast, OnGetStateFast); IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_AsyncFlush, OnAsyncFlush); IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_Rescheduled, OnRescheduled); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_CreateTransferBuffer, OnCreateTransferBuffer); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_RegisterTransferBuffer, OnRegisterTransferBuffer); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_DestroyTransferBuffer, OnDestroyTransferBuffer); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_GetTransferBuffer, OnGetTransferBuffer); IPC_MESSAGE_HANDLER_DELAY_REPLY(GpuCommandBufferMsg_CreateVideoDecoder, OnCreateVideoDecoder) IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_DestroyVideoDecoder, OnDestroyVideoDecoder) IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_SetSurfaceVisible, OnSetSurfaceVisible) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() DCHECK(handled); return handled; } bool GpuCommandBufferStub::Send(IPC::Message* message) { return channel_->Send(message); } bool GpuCommandBufferStub::IsScheduled() { return !scheduler_.get() || scheduler_->IsScheduled(); } void GpuCommandBufferStub::Destroy() { // The scheduler has raw references to the decoder and the command buffer so // destroy it before those. scheduler_.reset(); if (decoder_.get()) { decoder_->Destroy(); decoder_.reset(); } command_buffer_.reset(); context_ = NULL; surface_ = NULL; } void GpuCommandBufferStub::OnInitializeFailed(IPC::Message* reply_message) { Destroy(); GpuCommandBufferMsg_Initialize::WriteReplyParams(reply_message, false); Send(reply_message); } void GpuCommandBufferStub::OnInitialize( base::SharedMemoryHandle ring_buffer, int32 size, IPC::Message* reply_message) { DCHECK(!command_buffer_.get()); command_buffer_.reset(new gpu::CommandBufferService); #if defined(OS_WIN) // Windows dups the shared memory handle it receives into the current process // and closes it when this variable goes out of scope. base::SharedMemory shared_memory(ring_buffer, false, channel_->renderer_process()); #else // POSIX receives a dup of the shared memory handle and closes the dup when // this variable goes out of scope. base::SharedMemory shared_memory(ring_buffer, false); #endif if (!command_buffer_->Initialize(&shared_memory, size)) { OnInitializeFailed(reply_message); return; } decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group_.get())); scheduler_.reset(new gpu::GpuScheduler(command_buffer_.get(), decoder_.get(), NULL)); decoder_->set_engine(scheduler_.get()); if (handle_) { #if defined(OS_MACOSX) || defined(UI_COMPOSITOR_IMAGE_TRANSPORT) if (software_) { OnInitializeFailed(reply_message); return; } surface_ = ImageTransportSurface::CreateSurface( channel_->gpu_channel_manager(), render_view_id_, renderer_id_, route_id_, handle_); #elif defined(OS_WIN) || defined(OS_LINUX) || defined(OS_OPENBSD) surface_ = gfx::GLSurface::CreateViewGLSurface(software_, handle_); #endif } else { surface_ = gfx::GLSurface::CreateOffscreenGLSurface(software_, gfx::Size(1, 1)); } if (!surface_.get()) { // Ensure the decoder is not destroyed if it is not initialized. decoder_.reset(); DLOG(ERROR) << "Failed to create surface.\n"; OnInitializeFailed(reply_message); return; } gfx::GpuPreference gpu_preference = channel_->ShouldPreferDiscreteGpu() ? gfx::PreferDiscreteGpu : gpu_preference_; context_ = gfx::GLContext::CreateGLContext( channel_->share_group(), surface_.get(), gpu_preference); if (!context_.get()) { // Ensure the decoder is not destroyed if it is not initialized. decoder_.reset(); DLOG(ERROR) << "Failed to create context.\n"; OnInitializeFailed(reply_message); return; } // Initialize the decoder with either the view or pbuffer GLContext. if (!decoder_->Initialize(surface_.get(), context_.get(), initial_size_, disallowed_features_, allowed_extensions_.c_str(), requested_attribs_)) { DLOG(ERROR) << "Failed to initialize decoder."; OnInitializeFailed(reply_message); return; } if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableGPUServiceLogging)) { decoder_->set_debug(true); } SetSwapInterval(); command_buffer_->SetPutOffsetChangeCallback( NewCallback(scheduler_.get(), &gpu::GpuScheduler::PutChanged)); command_buffer_->SetParseErrorCallback( NewCallback(this, &GpuCommandBufferStub::OnParseError)); scheduler_->SetScheduledCallback( NewCallback(channel_, &GpuChannel::OnScheduled)); // On platforms that use an ImageTransportSurface, the surface // handles co-ordinating the resize with the browser process. The // surface sets it's own resize callback, so we shouldn't do it here. #if !defined(OS_MACOSX) && !defined(UI_COMPOSITOR_IMAGE_TRANSPORT) if (handle_ != gfx::kNullPluginWindow) { decoder_->SetResizeCallback( NewCallback(this, &GpuCommandBufferStub::OnResize)); } #endif if (watchdog_) { scheduler_->SetCommandProcessedCallback( NewCallback(this, &GpuCommandBufferStub::OnCommandProcessed)); } if (parent_stub_for_initialization_) { decoder_->SetParent(parent_stub_for_initialization_->decoder_.get(), parent_texture_for_initialization_); parent_stub_for_initialization_.reset(); parent_texture_for_initialization_ = 0; } GpuCommandBufferMsg_Initialize::WriteReplyParams(reply_message, true); Send(reply_message); } void GpuCommandBufferStub::OnSetParent(int32 parent_route_id, uint32 parent_texture_id, IPC::Message* reply_message) { GpuCommandBufferStub* parent_stub = NULL; if (parent_route_id != MSG_ROUTING_NONE) { parent_stub = channel_->LookupCommandBuffer(parent_route_id); } bool result = true; if (scheduler_.get()) { gpu::gles2::GLES2Decoder* parent_decoder = parent_stub ? parent_stub->decoder_.get() : NULL; result = decoder_->SetParent(parent_decoder, parent_texture_id); } else { // If we don't have a scheduler, it means that Initialize hasn't been called // yet. Keep around the requested parent stub and texture so that we can set // it in Initialize(). parent_stub_for_initialization_ = parent_stub ? parent_stub->AsWeakPtr() : base::WeakPtr(); parent_texture_for_initialization_ = parent_texture_id; } GpuCommandBufferMsg_SetParent::WriteReplyParams(reply_message, result); Send(reply_message); } void GpuCommandBufferStub::OnGetState(IPC::Message* reply_message) { gpu::CommandBuffer::State state = command_buffer_->GetState(); if (state.error == gpu::error::kLostContext && gfx::GLContext::LosesAllContextsOnContextLost()) channel_->LoseAllContexts(); GpuCommandBufferMsg_GetState::WriteReplyParams(reply_message, state); Send(reply_message); } void GpuCommandBufferStub::OnParseError() { TRACE_EVENT0("gpu", "GpuCommandBufferStub::OnParseError"); gpu::CommandBuffer::State state = command_buffer_->GetState(); IPC::Message* msg = new GpuCommandBufferMsg_Destroyed( route_id_, state.context_lost_reason); msg->set_unblock(true); Send(msg); } void GpuCommandBufferStub::OnGetStateFast(IPC::Message* reply_message) { TRACE_EVENT0("gpu", "GpuCommandBufferStub::OnGetStateFast"); gpu::CommandBuffer::State state = command_buffer_->GetState(); if (state.error == gpu::error::kLostContext && gfx::GLContext::LosesAllContextsOnContextLost()) channel_->LoseAllContexts(); GpuCommandBufferMsg_GetStateFast::WriteReplyParams(reply_message, state); Send(reply_message); } void GpuCommandBufferStub::OnAsyncFlush(int32 put_offset, uint32 flush_count) { TRACE_EVENT1("gpu", "GpuCommandBufferStub::OnAsyncFlush", "put_offset", put_offset); if (flush_count - last_flush_count_ < 0x8000000U) { last_flush_count_ = flush_count; command_buffer_->Flush(put_offset); } else { // We received this message out-of-order. This should not happen but is here // to catch regressions. Ignore the message. NOTREACHED() << "Received a Flush message out-of-order"; } ReportState(); } void GpuCommandBufferStub::OnRescheduled() { gpu::CommandBuffer::State state = command_buffer_->GetLastState(); command_buffer_->Flush(state.put_offset); ReportState(); } void GpuCommandBufferStub::OnCreateTransferBuffer(int32 size, int32 id_request, IPC::Message* reply_message) { int32 id = command_buffer_->CreateTransferBuffer(size, id_request); GpuCommandBufferMsg_CreateTransferBuffer::WriteReplyParams(reply_message, id); Send(reply_message); } void GpuCommandBufferStub::OnRegisterTransferBuffer( base::SharedMemoryHandle transfer_buffer, size_t size, int32 id_request, IPC::Message* reply_message) { #if defined(OS_WIN) // Windows dups the shared memory handle it receives into the current process // and closes it when this variable goes out of scope. base::SharedMemory shared_memory(transfer_buffer, false, channel_->renderer_process()); #else // POSIX receives a dup of the shared memory handle and closes the dup when // this variable goes out of scope. base::SharedMemory shared_memory(transfer_buffer, false); #endif int32 id = command_buffer_->RegisterTransferBuffer(&shared_memory, size, id_request); GpuCommandBufferMsg_RegisterTransferBuffer::WriteReplyParams(reply_message, id); Send(reply_message); } void GpuCommandBufferStub::OnDestroyTransferBuffer( int32 id, IPC::Message* reply_message) { command_buffer_->DestroyTransferBuffer(id); Send(reply_message); } void GpuCommandBufferStub::OnGetTransferBuffer( int32 id, IPC::Message* reply_message) { base::SharedMemoryHandle transfer_buffer = base::SharedMemoryHandle(); uint32 size = 0; // Fail if the renderer process has not provided its process handle. if (!channel_->renderer_process()) return; gpu::Buffer buffer = command_buffer_->GetTransferBuffer(id); if (buffer.shared_memory) { // Assume service is responsible for duplicating the handle to the calling // process. buffer.shared_memory->ShareToProcess(channel_->renderer_process(), &transfer_buffer); size = buffer.size; } GpuCommandBufferMsg_GetTransferBuffer::WriteReplyParams(reply_message, transfer_buffer, size); Send(reply_message); } void GpuCommandBufferStub::OnCommandProcessed() { if (watchdog_) watchdog_->CheckArmed(); } void GpuCommandBufferStub::OnResize(gfx::Size size) { if (handle_ == gfx::kNullPluginWindow) return; #if defined(TOOLKIT_USES_GTK) && !defined(UI_COMPOSITOR_IMAGE_TRANSPORT) || \ defined(OS_WIN) GpuChannelManager* gpu_channel_manager = channel_->gpu_channel_manager(); // On Windows, Linux, we need to coordinate resizing of onscreen // contexts with the resizing of the actual OS-level window. We do this by // sending a resize message to the browser process and descheduling the // context until the ViewResized message comes back in reply. // Send the resize message if needed gpu_channel_manager->Send( new GpuHostMsg_ResizeView(renderer_id_, render_view_id_, route_id_, size)); scheduler_->SetScheduled(false); #endif } void GpuCommandBufferStub::ViewResized() { #if defined(TOOLKIT_USES_GTK) && !defined(UI_COMPOSITOR_IMAGE_TRANSPORT) || \ defined(OS_WIN) DCHECK(handle_ != gfx::kNullPluginWindow); scheduler_->SetScheduled(true); #endif #if defined(OS_WIN) // Recreate the view surface to match the window size. Swap chains do not // automatically resize with window size with D3D. context_->ReleaseCurrent(surface_.get()); if (surface_.get()) { surface_->Destroy(); surface_->Initialize(); SetSwapInterval(); } #endif } void GpuCommandBufferStub::ReportState() { gpu::CommandBuffer::State state = command_buffer_->GetState(); if (state.error == gpu::error::kLostContext && gfx::GLContext::LosesAllContextsOnContextLost()) { channel_->LoseAllContexts(); } else { IPC::Message* msg = new GpuCommandBufferMsg_UpdateState(route_id_, state); msg->set_unblock(true); Send(msg); } } void GpuCommandBufferStub::SetSwapInterval() { #if !defined(OS_MACOSX) && !defined(UI_COMPOSITOR_IMAGE_TRANSPORT) // Set up swap interval for onscreen contexts. if (!surface_->IsOffscreen()) { decoder_->MakeCurrent(); if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableGpuVsync)) context_->SetSwapInterval(0); else context_->SetSwapInterval(1); } #endif } void GpuCommandBufferStub::OnCreateVideoDecoder( media::VideoDecodeAccelerator::Profile profile, IPC::Message* reply_message) { int decoder_route_id = channel_->GenerateRouteID(); GpuCommandBufferMsg_CreateVideoDecoder::WriteReplyParams( reply_message, decoder_route_id); GpuVideoDecodeAccelerator* decoder = new GpuVideoDecodeAccelerator(this, decoder_route_id, this); video_decoders_.AddWithID(decoder, decoder_route_id); channel_->AddRoute(decoder_route_id, decoder); decoder->Initialize(profile, reply_message); } void GpuCommandBufferStub::OnDestroyVideoDecoder(int decoder_route_id) { channel_->RemoveRoute(decoder_route_id); video_decoders_.Remove(decoder_route_id); } void GpuCommandBufferStub::OnSetSurfaceVisible(bool visible) { surface_->SetVisible(visible); } #endif // defined(ENABLE_GPU)