1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h"
6
7#include "third_party/khronos/GLES2/gl2.h"
8#ifndef GL_GLEXT_PROTOTYPES
9#define GL_GLEXT_PROTOTYPES 1
10#endif
11#include "third_party/khronos/GLES2/gl2ext.h"
12
13#include <algorithm>
14#include <map>
15
16#include "base/atomicops.h"
17#include "base/bind.h"
18#include "base/command_line.h"
19#include "base/debug/trace_event.h"
20#include "base/lazy_instance.h"
21#include "base/logging.h"
22#include "base/message_loop/message_loop.h"
23#include "base/metrics/field_trial.h"
24#include "base/metrics/histogram.h"
25#include "content/common/gpu/client/gpu_channel_host.h"
26#include "content/public/common/content_constants.h"
27#include "content/public/common/content_switches.h"
28#include "gpu/GLES2/gl2extchromium.h"
29#include "gpu/command_buffer/client/gles2_cmd_helper.h"
30#include "gpu/command_buffer/client/gles2_implementation.h"
31#include "gpu/command_buffer/client/gles2_trace_implementation.h"
32#include "gpu/command_buffer/client/transfer_buffer.h"
33#include "gpu/command_buffer/common/constants.h"
34#include "gpu/command_buffer/common/gpu_memory_allocation.h"
35#include "gpu/command_buffer/common/mailbox.h"
36#include "gpu/skia_bindings/gl_bindings_skia_cmd_buffer.h"
37#include "third_party/skia/include/core/SkTypes.h"
38
39namespace content {
40
41namespace {
42
43static base::LazyInstance<base::Lock>::Leaky
44    g_default_share_groups_lock = LAZY_INSTANCE_INITIALIZER;
45
46typedef std::map<GpuChannelHost*,
47    scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup> >
48    ShareGroupMap;
49static base::LazyInstance<ShareGroupMap> g_default_share_groups =
50    LAZY_INSTANCE_INITIALIZER;
51
52scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup>
53    GetDefaultShareGroupForHost(GpuChannelHost* host) {
54  base::AutoLock lock(g_default_share_groups_lock.Get());
55
56  ShareGroupMap& share_groups = g_default_share_groups.Get();
57  ShareGroupMap::iterator it = share_groups.find(host);
58  if (it == share_groups.end()) {
59    scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup> group =
60        new WebGraphicsContext3DCommandBufferImpl::ShareGroup();
61    share_groups[host] = group;
62    return group;
63  }
64  return it->second;
65}
66
67} // namespace anonymous
68
69WebGraphicsContext3DCommandBufferImpl::SharedMemoryLimits::SharedMemoryLimits()
70    : command_buffer_size(kDefaultCommandBufferSize),
71      start_transfer_buffer_size(kDefaultStartTransferBufferSize),
72      min_transfer_buffer_size(kDefaultMinTransferBufferSize),
73      max_transfer_buffer_size(kDefaultMaxTransferBufferSize),
74      mapped_memory_reclaim_limit(gpu::gles2::GLES2Implementation::kNoLimit) {}
75
76WebGraphicsContext3DCommandBufferImpl::ShareGroup::ShareGroup() {
77}
78
79WebGraphicsContext3DCommandBufferImpl::ShareGroup::~ShareGroup() {
80  DCHECK(contexts_.empty());
81}
82
83WebGraphicsContext3DCommandBufferImpl::WebGraphicsContext3DCommandBufferImpl(
84    int surface_id,
85    const GURL& active_url,
86    GpuChannelHost* host,
87    const Attributes& attributes,
88    bool lose_context_when_out_of_memory,
89    const SharedMemoryLimits& limits,
90    WebGraphicsContext3DCommandBufferImpl* share_context)
91    : lose_context_when_out_of_memory_(lose_context_when_out_of_memory),
92      attributes_(attributes),
93      visible_(false),
94      host_(host),
95      surface_id_(surface_id),
96      active_url_(active_url),
97      gpu_preference_(attributes.preferDiscreteGPU ? gfx::PreferDiscreteGpu
98                                                   : gfx::PreferIntegratedGpu),
99      mem_limits_(limits),
100      weak_ptr_factory_(this) {
101  if (share_context) {
102    DCHECK(!attributes_.shareResources);
103    share_group_ = share_context->share_group_;
104  } else {
105    share_group_ = attributes_.shareResources
106        ? GetDefaultShareGroupForHost(host)
107        : scoped_refptr<WebGraphicsContext3DCommandBufferImpl::ShareGroup>(
108            new ShareGroup());
109  }
110}
111
112WebGraphicsContext3DCommandBufferImpl::
113    ~WebGraphicsContext3DCommandBufferImpl() {
114  if (real_gl_) {
115    real_gl_->SetErrorMessageCallback(NULL);
116  }
117
118  Destroy();
119}
120
121bool WebGraphicsContext3DCommandBufferImpl::MaybeInitializeGL() {
122  if (initialized_)
123    return true;
124
125  if (initialize_failed_)
126    return false;
127
128  TRACE_EVENT0("gpu", "WebGfxCtx3DCmdBfrImpl::MaybeInitializeGL");
129
130  if (!CreateContext(surface_id_ != 0)) {
131    Destroy();
132    initialize_failed_ = true;
133    return false;
134  }
135
136  if (gl_ && attributes_.webGL)
137    gl_->EnableFeatureCHROMIUM("webgl_enable_glsl_webgl_validation");
138
139  command_buffer_->SetChannelErrorCallback(
140      base::Bind(&WebGraphicsContext3DCommandBufferImpl::OnGpuChannelLost,
141                 weak_ptr_factory_.GetWeakPtr()));
142
143  command_buffer_->SetOnConsoleMessageCallback(
144      base::Bind(&WebGraphicsContext3DCommandBufferImpl::OnErrorMessage,
145                 weak_ptr_factory_.GetWeakPtr()));
146
147  real_gl_->SetErrorMessageCallback(getErrorMessageCallback());
148
149  visible_ = true;
150  initialized_ = true;
151  return true;
152}
153
154bool WebGraphicsContext3DCommandBufferImpl::InitializeCommandBuffer(
155    bool onscreen, WebGraphicsContext3DCommandBufferImpl* share_context) {
156  if (!host_.get())
157    return false;
158
159  CommandBufferProxyImpl* share_group_command_buffer = NULL;
160
161  if (share_context) {
162    share_group_command_buffer = share_context->command_buffer_.get();
163  }
164
165  ::gpu::gles2::ContextCreationAttribHelper attribs_for_gles2;
166  ConvertAttributes(attributes_, &attribs_for_gles2);
167  attribs_for_gles2.lose_context_when_out_of_memory =
168      lose_context_when_out_of_memory_;
169  DCHECK(attribs_for_gles2.buffer_preserved);
170  std::vector<int32> attribs;
171  attribs_for_gles2.Serialize(&attribs);
172
173  // Create a proxy to a command buffer in the GPU process.
174  if (onscreen) {
175    command_buffer_.reset(host_->CreateViewCommandBuffer(
176        surface_id_,
177        share_group_command_buffer,
178        attribs,
179        active_url_,
180        gpu_preference_));
181  } else {
182    command_buffer_.reset(host_->CreateOffscreenCommandBuffer(
183        gfx::Size(1, 1),
184        share_group_command_buffer,
185        attribs,
186        active_url_,
187        gpu_preference_));
188  }
189
190  if (!command_buffer_) {
191    DLOG(ERROR) << "GpuChannelHost failed to create command buffer.";
192    return false;
193  }
194
195  DVLOG_IF(1, gpu::error::IsError(command_buffer_->GetLastError()))
196      << "Context dead on arrival. Last error: "
197      << command_buffer_->GetLastError();
198  // Initialize the command buffer.
199  bool result = command_buffer_->Initialize();
200  LOG_IF(ERROR, !result) << "CommandBufferProxy::Initialize failed.";
201  return result;
202}
203
204bool WebGraphicsContext3DCommandBufferImpl::CreateContext(bool onscreen) {
205  TRACE_EVENT0("gpu", "WebGfxCtx3DCmdBfrImpl::CreateContext");
206  scoped_refptr<gpu::gles2::ShareGroup> gles2_share_group;
207
208  scoped_ptr<base::AutoLock> share_group_lock;
209  bool add_to_share_group = false;
210  if (!command_buffer_) {
211    WebGraphicsContext3DCommandBufferImpl* share_context = NULL;
212
213    share_group_lock.reset(new base::AutoLock(share_group_->lock()));
214    share_context = share_group_->GetAnyContextLocked();
215
216    if (!InitializeCommandBuffer(onscreen, share_context)) {
217      LOG(ERROR) << "Failed to initialize command buffer.";
218      return false;
219    }
220
221    if (share_context)
222      gles2_share_group = share_context->GetImplementation()->share_group();
223
224    add_to_share_group = true;
225  }
226
227  // Create the GLES2 helper, which writes the command buffer protocol.
228  gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer_.get()));
229  if (!gles2_helper_->Initialize(mem_limits_.command_buffer_size)) {
230    LOG(ERROR) << "Failed to initialize GLES2CmdHelper.";
231    return false;
232  }
233
234  if (attributes_.noAutomaticFlushes)
235    gles2_helper_->SetAutomaticFlushes(false);
236  // Create a transfer buffer used to copy resources between the renderer
237  // process and the GPU process.
238  transfer_buffer_ .reset(new gpu::TransferBuffer(gles2_helper_.get()));
239
240  DCHECK(host_.get());
241
242  // Create the object exposing the OpenGL API.
243  bool bind_generates_resources = false;
244  real_gl_.reset(
245      new gpu::gles2::GLES2Implementation(gles2_helper_.get(),
246                                          gles2_share_group.get(),
247                                          transfer_buffer_.get(),
248                                          bind_generates_resources,
249                                          lose_context_when_out_of_memory_,
250                                          command_buffer_.get()));
251  setGLInterface(real_gl_.get());
252
253  if (!real_gl_->Initialize(
254      mem_limits_.start_transfer_buffer_size,
255      mem_limits_.min_transfer_buffer_size,
256      mem_limits_.max_transfer_buffer_size,
257      mem_limits_.mapped_memory_reclaim_limit)) {
258    LOG(ERROR) << "Failed to initialize GLES2Implementation.";
259    return false;
260  }
261
262  if (add_to_share_group)
263    share_group_->AddContextLocked(this);
264
265  if (CommandLine::ForCurrentProcess()->HasSwitch(
266      switches::kEnableGpuClientTracing)) {
267    trace_gl_.reset(new gpu::gles2::GLES2TraceImplementation(GetGLInterface()));
268    setGLInterface(trace_gl_.get());
269  }
270  return true;
271}
272
273bool WebGraphicsContext3DCommandBufferImpl::InitializeOnCurrentThread() {
274  if (!MaybeInitializeGL()) {
275    DLOG(ERROR) << "Failed to initialize context.";
276    return false;
277  }
278  if (gpu::error::IsError(command_buffer_->GetLastError())) {
279    LOG(ERROR) << "Context dead on arrival. Last error: "
280               << command_buffer_->GetLastError();
281    return false;
282  }
283
284  return true;
285}
286
287void WebGraphicsContext3DCommandBufferImpl::Destroy() {
288  share_group_->RemoveContext(this);
289
290  gpu::gles2::GLES2Interface* gl = GetGLInterface();
291  if (gl) {
292    // First flush the context to ensure that any pending frees of resources
293    // are completed. Otherwise, if this context is part of a share group,
294    // those resources might leak. Also, any remaining side effects of commands
295    // issued on this context might not be visible to other contexts in the
296    // share group.
297    gl->Flush();
298    setGLInterface(NULL);
299  }
300
301  trace_gl_.reset();
302  real_gl_.reset();
303  transfer_buffer_.reset();
304  gles2_helper_.reset();
305  real_gl_.reset();
306
307  if (command_buffer_) {
308    if (host_.get())
309      host_->DestroyCommandBuffer(command_buffer_.release());
310    command_buffer_.reset();
311  }
312
313  host_ = NULL;
314}
315
316gpu::ContextSupport*
317WebGraphicsContext3DCommandBufferImpl::GetContextSupport() {
318  return real_gl_.get();
319}
320
321bool WebGraphicsContext3DCommandBufferImpl::isContextLost() {
322  return initialize_failed_ ||
323      (command_buffer_ && IsCommandBufferContextLost()) ||
324      context_lost_reason_ != GL_NO_ERROR;
325}
326
327WGC3Denum WebGraphicsContext3DCommandBufferImpl::getGraphicsResetStatusARB() {
328  if (IsCommandBufferContextLost() &&
329      context_lost_reason_ == GL_NO_ERROR) {
330    return GL_UNKNOWN_CONTEXT_RESET_ARB;
331  }
332
333  return context_lost_reason_;
334}
335
336bool WebGraphicsContext3DCommandBufferImpl::IsCommandBufferContextLost() {
337  // If the channel shut down unexpectedly, let that supersede the
338  // command buffer's state.
339  if (host_.get() && host_->IsLost())
340    return true;
341  gpu::CommandBuffer::State state = command_buffer_->GetLastState();
342  return state.error == gpu::error::kLostContext;
343}
344
345// static
346WebGraphicsContext3DCommandBufferImpl*
347WebGraphicsContext3DCommandBufferImpl::CreateOffscreenContext(
348    GpuChannelHost* host,
349    const WebGraphicsContext3D::Attributes& attributes,
350    bool lose_context_when_out_of_memory,
351    const GURL& active_url,
352    const SharedMemoryLimits& limits,
353    WebGraphicsContext3DCommandBufferImpl* share_context) {
354  if (!host)
355    return NULL;
356
357  if (share_context && share_context->IsCommandBufferContextLost())
358    return NULL;
359
360  return new WebGraphicsContext3DCommandBufferImpl(
361      0,
362      active_url,
363      host,
364      attributes,
365      lose_context_when_out_of_memory,
366      limits,
367      share_context);
368}
369
370namespace {
371
372WGC3Denum convertReason(gpu::error::ContextLostReason reason) {
373  switch (reason) {
374  case gpu::error::kGuilty:
375    return GL_GUILTY_CONTEXT_RESET_ARB;
376  case gpu::error::kInnocent:
377    return GL_INNOCENT_CONTEXT_RESET_ARB;
378  case gpu::error::kUnknown:
379    return GL_UNKNOWN_CONTEXT_RESET_ARB;
380  }
381
382  NOTREACHED();
383  return GL_UNKNOWN_CONTEXT_RESET_ARB;
384}
385
386}  // anonymous namespace
387
388void WebGraphicsContext3DCommandBufferImpl::OnGpuChannelLost() {
389  context_lost_reason_ = convertReason(
390      command_buffer_->GetLastState().context_lost_reason);
391  if (context_lost_callback_) {
392    context_lost_callback_->onContextLost();
393  }
394
395  share_group_->RemoveAllContexts();
396
397  DCHECK(host_.get());
398  {
399    base::AutoLock lock(g_default_share_groups_lock.Get());
400    g_default_share_groups.Get().erase(host_.get());
401  }
402}
403
404}  // namespace content
405