blob: e7c945f3ceafbf059efc90d19cc6576516979369 [file] [log] [blame]
// 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/webgraphicscontext3d_command_buffer_impl.h"
#include "third_party/khronos/GLES2/gl2.h"
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES 1
#endif
#include "third_party/khronos/GLES2/gl2ext.h"
#include <algorithm>
#include <map>
#include "base/atomicops.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "content/common/gpu/client/gpu_channel_host.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/content_switches.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/gles2_implementation.h"
#include "gpu/command_buffer/client/gles2_lib.h"
#include "gpu/command_buffer/client/gles2_trace_implementation.h"
#include "gpu/command_buffer/client/transfer_buffer.h"
#include "gpu/command_buffer/common/constants.h"
#include "gpu/command_buffer/common/gpu_memory_allocation.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "gpu/skia_bindings/gl_bindings_skia_cmd_buffer.h"
#include "third_party/skia/include/core/SkTypes.h"
namespace content {
namespace {
static base::LazyInstance<base::Lock>::Leaky
g_default_share_groups_lock = LAZY_INSTANCE_INITIALIZER;
typedef std::map<GpuChannelHost*,
scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup> >
ShareGroupMap;
static base::LazyInstance<ShareGroupMap> g_default_share_groups =
LAZY_INSTANCE_INITIALIZER;
scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup>
GetDefaultShareGroupForHost(GpuChannelHost* host) {
base::AutoLock lock(g_default_share_groups_lock.Get());
ShareGroupMap& share_groups = g_default_share_groups.Get();
ShareGroupMap::iterator it = share_groups.find(host);
if (it == share_groups.end()) {
scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup> group =
new WebGraphicsContext3DCommandBufferImpl::ShareGroup();
share_groups[host] = group;
return group;
}
return it->second;
}
// Singleton used to initialize and terminate the gles2 library.
class GLES2Initializer {
public:
GLES2Initializer() {
gles2::Initialize();
}
~GLES2Initializer() {
gles2::Terminate();
}
private:
DISALLOW_COPY_AND_ASSIGN(GLES2Initializer);
};
////////////////////////////////////////////////////////////////////////////////
base::LazyInstance<GLES2Initializer> g_gles2_initializer =
LAZY_INSTANCE_INITIALIZER;
////////////////////////////////////////////////////////////////////////////////
} // namespace anonymous
WebGraphicsContext3DCommandBufferImpl::SharedMemoryLimits::SharedMemoryLimits()
: command_buffer_size(kDefaultCommandBufferSize),
start_transfer_buffer_size(kDefaultStartTransferBufferSize),
min_transfer_buffer_size(kDefaultMinTransferBufferSize),
max_transfer_buffer_size(kDefaultMaxTransferBufferSize),
mapped_memory_reclaim_limit(gpu::gles2::GLES2Implementation::kNoLimit) {}
WebGraphicsContext3DCommandBufferImpl::ShareGroup::ShareGroup() {
}
WebGraphicsContext3DCommandBufferImpl::ShareGroup::~ShareGroup() {
DCHECK(contexts_.empty());
}
WebGraphicsContext3DCommandBufferImpl::WebGraphicsContext3DCommandBufferImpl(
int surface_id,
const GURL& active_url,
GpuChannelHost* host,
const Attributes& attributes,
bool lose_context_when_out_of_memory,
const SharedMemoryLimits& limits,
WebGraphicsContext3DCommandBufferImpl* share_context)
: lose_context_when_out_of_memory_(lose_context_when_out_of_memory),
attributes_(attributes),
visible_(false),
host_(host),
surface_id_(surface_id),
active_url_(active_url),
gpu_preference_(attributes.preferDiscreteGPU ? gfx::PreferDiscreteGpu
: gfx::PreferIntegratedGpu),
weak_ptr_factory_(this),
mem_limits_(limits) {
if (share_context) {
DCHECK(!attributes_.shareResources);
share_group_ = share_context->share_group_;
} else {
share_group_ = attributes_.shareResources
? GetDefaultShareGroupForHost(host)
: scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup>(
new ShareGroup());
}
}
WebGraphicsContext3DCommandBufferImpl::
~WebGraphicsContext3DCommandBufferImpl() {
if (real_gl_) {
real_gl_->SetErrorMessageCallback(NULL);
}
Destroy();
}
bool WebGraphicsContext3DCommandBufferImpl::MaybeInitializeGL() {
if (initialized_)
return true;
if (initialize_failed_)
return false;
TRACE_EVENT0("gpu", "WebGfxCtx3DCmdBfrImpl::MaybeInitializeGL");
if (!CreateContext(surface_id_ != 0)) {
Destroy();
initialize_failed_ = true;
return false;
}
// TODO(twiz): This code is too fragile in that it assumes that only WebGL
// contexts will request noExtensions.
if (gl_ && attributes_.noExtensions)
gl_->EnableFeatureCHROMIUM("webgl_enable_glsl_webgl_validation");
command_buffer_->SetChannelErrorCallback(
base::Bind(&WebGraphicsContext3DCommandBufferImpl::OnGpuChannelLost,
weak_ptr_factory_.GetWeakPtr()));
command_buffer_->SetOnConsoleMessageCallback(
base::Bind(&WebGraphicsContext3DCommandBufferImpl::OnErrorMessage,
weak_ptr_factory_.GetWeakPtr()));
real_gl_->SetErrorMessageCallback(getErrorMessageCallback());
visible_ = true;
initialized_ = true;
return true;
}
bool WebGraphicsContext3DCommandBufferImpl::InitializeCommandBuffer(
bool onscreen, WebGraphicsContext3DCommandBufferImpl* share_context) {
if (!host_.get())
return false;
CommandBufferProxyImpl* share_group_command_buffer = NULL;
if (share_context) {
share_group_command_buffer = share_context->command_buffer_.get();
}
std::vector<int32> attribs;
attribs.push_back(ALPHA_SIZE);
attribs.push_back(attributes_.alpha ? 8 : 0);
attribs.push_back(DEPTH_SIZE);
attribs.push_back(attributes_.depth ? 24 : 0);
attribs.push_back(STENCIL_SIZE);
attribs.push_back(attributes_.stencil ? 8 : 0);
attribs.push_back(SAMPLES);
attribs.push_back(attributes_.antialias ? 4 : 0);
attribs.push_back(SAMPLE_BUFFERS);
attribs.push_back(attributes_.antialias ? 1 : 0);
attribs.push_back(FAIL_IF_MAJOR_PERF_CAVEAT);
attribs.push_back(attributes_.failIfMajorPerformanceCaveat ? 1 : 0);
attribs.push_back(LOSE_CONTEXT_WHEN_OUT_OF_MEMORY);
attribs.push_back(lose_context_when_out_of_memory_ ? 1 : 0);
attribs.push_back(BIND_GENERATES_RESOURCES);
attribs.push_back(0);
attribs.push_back(NONE);
// Create a proxy to a command buffer in the GPU process.
if (onscreen) {
command_buffer_.reset(host_->CreateViewCommandBuffer(
surface_id_,
share_group_command_buffer,
attribs,
active_url_,
gpu_preference_));
} else {
command_buffer_.reset(host_->CreateOffscreenCommandBuffer(
gfx::Size(1, 1),
share_group_command_buffer,
attribs,
active_url_,
gpu_preference_));
}
if (!command_buffer_) {
DLOG(ERROR) << "GpuChannelHost failed to create command buffer.";
return false;
}
DVLOG_IF(1, gpu::error::IsError(command_buffer_->GetLastError()))
<< "Context dead on arrival. Last error: "
<< command_buffer_->GetLastError();
// Initialize the command buffer.
bool result = command_buffer_->Initialize();
LOG_IF(ERROR, !result) << "CommandBufferProxy::Initialize failed.";
return result;
}
bool WebGraphicsContext3DCommandBufferImpl::CreateContext(bool onscreen) {
TRACE_EVENT0("gpu", "WebGfxCtx3DCmdBfrImpl::CreateContext");
// Ensure the gles2 library is initialized first in a thread safe way.
g_gles2_initializer.Get();
scoped_refptr<gpu::gles2::ShareGroup> gles2_share_group;
scoped_ptr<base::AutoLock> share_group_lock;
bool add_to_share_group = false;
if (!command_buffer_) {
WebGraphicsContext3DCommandBufferImpl* share_context = NULL;
share_group_lock.reset(new base::AutoLock(share_group_->lock()));
share_context = share_group_->GetAnyContextLocked();
if (!InitializeCommandBuffer(onscreen, share_context)) {
LOG(ERROR) << "Failed to initialize command buffer.";
return false;
}
if (share_context)
gles2_share_group = share_context->GetImplementation()->share_group();
add_to_share_group = true;
}
// Create the GLES2 helper, which writes the command buffer protocol.
gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer_.get()));
if (!gles2_helper_->Initialize(mem_limits_.command_buffer_size)) {
LOG(ERROR) << "Failed to initialize GLES2CmdHelper.";
return false;
}
if (attributes_.noAutomaticFlushes)
gles2_helper_->SetAutomaticFlushes(false);
// Create a transfer buffer used to copy resources between the renderer
// process and the GPU process.
transfer_buffer_ .reset(new gpu::TransferBuffer(gles2_helper_.get()));
DCHECK(host_.get());
// Create the object exposing the OpenGL API.
bool bind_generates_resources = false;
real_gl_.reset(
new gpu::gles2::GLES2Implementation(gles2_helper_.get(),
gles2_share_group,
transfer_buffer_.get(),
bind_generates_resources,
lose_context_when_out_of_memory_,
command_buffer_.get()));
setGLInterface(real_gl_.get());
if (!real_gl_->Initialize(
mem_limits_.start_transfer_buffer_size,
mem_limits_.min_transfer_buffer_size,
mem_limits_.max_transfer_buffer_size,
mem_limits_.mapped_memory_reclaim_limit)) {
LOG(ERROR) << "Failed to initialize GLES2Implementation.";
return false;
}
if (add_to_share_group)
share_group_->AddContextLocked(this);
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableGpuClientTracing)) {
trace_gl_.reset(new gpu::gles2::GLES2TraceImplementation(GetGLInterface()));
setGLInterface(trace_gl_.get());
}
return true;
}
bool WebGraphicsContext3DCommandBufferImpl::makeContextCurrent() {
if (!MaybeInitializeGL()) {
DLOG(ERROR) << "Failed to initialize context.";
return false;
}
gles2::SetGLContext(GetGLInterface());
if (gpu::error::IsError(command_buffer_->GetLastError())) {
LOG(ERROR) << "Context dead on arrival. Last error: "
<< command_buffer_->GetLastError();
return false;
}
return true;
}
void WebGraphicsContext3DCommandBufferImpl::Destroy() {
share_group_->RemoveContext(this);
gpu::gles2::GLES2Interface* gl = GetGLInterface();
if (gl) {
// First flush the context to ensure that any pending frees of resources
// are completed. Otherwise, if this context is part of a share group,
// those resources might leak. Also, any remaining side effects of commands
// issued on this context might not be visible to other contexts in the
// share group.
gl->Flush();
setGLInterface(NULL);
}
trace_gl_.reset();
real_gl_.reset();
transfer_buffer_.reset();
gles2_helper_.reset();
real_gl_.reset();
if (command_buffer_) {
if (host_.get())
host_->DestroyCommandBuffer(command_buffer_.release());
command_buffer_.reset();
}
host_ = NULL;
}
gpu::ContextSupport*
WebGraphicsContext3DCommandBufferImpl::GetContextSupport() {
return real_gl_.get();
}
bool WebGraphicsContext3DCommandBufferImpl::isContextLost() {
return initialize_failed_ ||
(command_buffer_ && IsCommandBufferContextLost()) ||
context_lost_reason_ != GL_NO_ERROR;
}
WGC3Denum WebGraphicsContext3DCommandBufferImpl::getGraphicsResetStatusARB() {
if (IsCommandBufferContextLost() &&
context_lost_reason_ == GL_NO_ERROR) {
return GL_UNKNOWN_CONTEXT_RESET_ARB;
}
return context_lost_reason_;
}
bool WebGraphicsContext3DCommandBufferImpl::IsCommandBufferContextLost() {
// If the channel shut down unexpectedly, let that supersede the
// command buffer's state.
if (host_.get() && host_->IsLost())
return true;
gpu::CommandBuffer::State state = command_buffer_->GetLastState();
return state.error == gpu::error::kLostContext;
}
// static
WebGraphicsContext3DCommandBufferImpl*
WebGraphicsContext3DCommandBufferImpl::CreateOffscreenContext(
GpuChannelHost* host,
const WebGraphicsContext3D::Attributes& attributes,
bool lose_context_when_out_of_memory,
const GURL& active_url,
const SharedMemoryLimits& limits,
WebGraphicsContext3DCommandBufferImpl* share_context) {
if (!host)
return NULL;
if (share_context && share_context->IsCommandBufferContextLost())
return NULL;
return new WebGraphicsContext3DCommandBufferImpl(
0,
active_url,
host,
attributes,
lose_context_when_out_of_memory,
limits,
share_context);
}
namespace {
WGC3Denum convertReason(gpu::error::ContextLostReason reason) {
switch (reason) {
case gpu::error::kGuilty:
return GL_GUILTY_CONTEXT_RESET_ARB;
case gpu::error::kInnocent:
return GL_INNOCENT_CONTEXT_RESET_ARB;
case gpu::error::kUnknown:
return GL_UNKNOWN_CONTEXT_RESET_ARB;
}
NOTREACHED();
return GL_UNKNOWN_CONTEXT_RESET_ARB;
}
} // anonymous namespace
void WebGraphicsContext3DCommandBufferImpl::OnGpuChannelLost() {
context_lost_reason_ = convertReason(
command_buffer_->GetLastState().context_lost_reason);
if (context_lost_callback_) {
context_lost_callback_->onContextLost();
}
share_group_->RemoveAllContexts();
DCHECK(host_.get());
{
base::AutoLock lock(g_default_share_groups_lock.Get());
g_default_share_groups.Get().erase(host_.get());
}
}
} // namespace content