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/renderer/pepper/ppb_graphics_3d_impl.h"
6
7#include "base/bind.h"
8#include "base/command_line.h"
9#include "base/message_loop/message_loop.h"
10#include "base/strings/utf_string_conversions.h"
11#include "content/common/gpu/client/command_buffer_proxy_impl.h"
12#include "content/common/gpu/client/gpu_channel_host.h"
13#include "content/public/common/content_switches.h"
14#include "content/public/common/web_preferences.h"
15#include "content/renderer/pepper/host_globals.h"
16#include "content/renderer/pepper/pepper_plugin_instance_impl.h"
17#include "content/renderer/pepper/plugin_module.h"
18#include "content/renderer/render_thread_impl.h"
19#include "content/renderer/render_view_impl.h"
20#include "gpu/command_buffer/client/gles2_implementation.h"
21#include "ppapi/c/ppp_graphics_3d.h"
22#include "ppapi/thunk/enter.h"
23#include "third_party/WebKit/public/platform/WebString.h"
24#include "third_party/WebKit/public/web/WebConsoleMessage.h"
25#include "third_party/WebKit/public/web/WebDocument.h"
26#include "third_party/WebKit/public/web/WebElement.h"
27#include "third_party/WebKit/public/web/WebLocalFrame.h"
28#include "third_party/WebKit/public/web/WebPluginContainer.h"
29
30using ppapi::thunk::EnterResourceNoLock;
31using ppapi::thunk::PPB_Graphics3D_API;
32using blink::WebConsoleMessage;
33using blink::WebLocalFrame;
34using blink::WebPluginContainer;
35using blink::WebString;
36
37namespace content {
38
39namespace {
40const int32 kCommandBufferSize = 1024 * 1024;
41const int32 kTransferBufferSize = 1024 * 1024;
42
43}  // namespace.
44
45PPB_Graphics3D_Impl::PPB_Graphics3D_Impl(PP_Instance instance)
46    : PPB_Graphics3D_Shared(instance),
47      bound_to_instance_(false),
48      commit_pending_(false),
49      sync_point_(0),
50      has_alpha_(false),
51      command_buffer_(NULL),
52      weak_ptr_factory_(this) {}
53
54PPB_Graphics3D_Impl::~PPB_Graphics3D_Impl() {
55  DestroyGLES2Impl();
56  if (command_buffer_) {
57    DCHECK(channel_.get());
58    channel_->DestroyCommandBuffer(command_buffer_);
59    command_buffer_ = NULL;
60  }
61
62  channel_ = NULL;
63}
64
65// static
66PP_Resource PPB_Graphics3D_Impl::Create(PP_Instance instance,
67                                        PP_Resource share_context,
68                                        const int32_t* attrib_list) {
69  PPB_Graphics3D_API* share_api = NULL;
70  if (share_context) {
71    EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
72    if (enter.failed())
73      return 0;
74    share_api = enter.object();
75  }
76  scoped_refptr<PPB_Graphics3D_Impl> graphics_3d(
77      new PPB_Graphics3D_Impl(instance));
78  if (!graphics_3d->Init(share_api, attrib_list))
79    return 0;
80  return graphics_3d->GetReference();
81}
82
83// static
84PP_Resource PPB_Graphics3D_Impl::CreateRaw(
85    PP_Instance instance,
86    PP_Resource share_context,
87    const int32_t* attrib_list,
88    base::SharedMemoryHandle* shared_state_handle) {
89  PPB_Graphics3D_API* share_api = NULL;
90  if (share_context) {
91    EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
92    if (enter.failed())
93      return 0;
94    share_api = enter.object();
95  }
96  scoped_refptr<PPB_Graphics3D_Impl> graphics_3d(
97      new PPB_Graphics3D_Impl(instance));
98  if (!graphics_3d->InitRaw(share_api, attrib_list, shared_state_handle))
99    return 0;
100  return graphics_3d->GetReference();
101}
102
103PP_Bool PPB_Graphics3D_Impl::SetGetBuffer(int32_t transfer_buffer_id) {
104  GetCommandBuffer()->SetGetBuffer(transfer_buffer_id);
105  return PP_TRUE;
106}
107
108scoped_refptr<gpu::Buffer> PPB_Graphics3D_Impl::CreateTransferBuffer(
109    uint32_t size,
110    int32_t* id) {
111  return GetCommandBuffer()->CreateTransferBuffer(size, id);
112}
113
114PP_Bool PPB_Graphics3D_Impl::DestroyTransferBuffer(int32_t id) {
115  GetCommandBuffer()->DestroyTransferBuffer(id);
116  return PP_TRUE;
117}
118
119PP_Bool PPB_Graphics3D_Impl::Flush(int32_t put_offset) {
120  GetCommandBuffer()->Flush(put_offset);
121  return PP_TRUE;
122}
123
124gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForTokenInRange(
125    int32_t start,
126    int32_t end) {
127  GetCommandBuffer()->WaitForTokenInRange(start, end);
128  return GetCommandBuffer()->GetLastState();
129}
130
131gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForGetOffsetInRange(
132    int32_t start,
133    int32_t end) {
134  GetCommandBuffer()->WaitForGetOffsetInRange(start, end);
135  return GetCommandBuffer()->GetLastState();
136}
137
138uint32_t PPB_Graphics3D_Impl::InsertSyncPoint() {
139  return command_buffer_->InsertSyncPoint();
140}
141
142uint32_t PPB_Graphics3D_Impl::InsertFutureSyncPoint() {
143  return command_buffer_->InsertFutureSyncPoint();
144}
145
146void PPB_Graphics3D_Impl::RetireSyncPoint(uint32_t sync_point) {
147  return command_buffer_->RetireSyncPoint(sync_point);
148}
149
150bool PPB_Graphics3D_Impl::BindToInstance(bool bind) {
151  bound_to_instance_ = bind;
152  return true;
153}
154
155bool PPB_Graphics3D_Impl::IsOpaque() { return !has_alpha_; }
156
157void PPB_Graphics3D_Impl::ViewInitiatedPaint() {
158  commit_pending_ = false;
159
160  if (HasPendingSwap())
161    SwapBuffersACK(PP_OK);
162}
163
164void PPB_Graphics3D_Impl::ViewFlushedPaint() {}
165
166int PPB_Graphics3D_Impl::GetCommandBufferRouteId() {
167  DCHECK(command_buffer_);
168  return command_buffer_->GetRouteID();
169}
170
171gpu::CommandBuffer* PPB_Graphics3D_Impl::GetCommandBuffer() {
172  return command_buffer_;
173}
174
175gpu::GpuControl* PPB_Graphics3D_Impl::GetGpuControl() {
176  return command_buffer_;
177}
178
179int32 PPB_Graphics3D_Impl::DoSwapBuffers() {
180  DCHECK(command_buffer_);
181  // We do not have a GLES2 implementation when using an OOP proxy.
182  // The plugin-side proxy is responsible for adding the SwapBuffers command
183  // to the command buffer in that case.
184  if (gles2_impl())
185    gles2_impl()->SwapBuffers();
186
187  // Since the backing texture has been updated, a new sync point should be
188  // inserted.
189  sync_point_ = command_buffer_->InsertSyncPoint();
190
191  if (bound_to_instance_) {
192    // If we are bound to the instance, we need to ask the compositor
193    // to commit our backing texture so that the graphics appears on the page.
194    // When the backing texture will be committed we get notified via
195    // ViewFlushedPaint().
196    //
197    // Don't need to check for NULL from GetPluginInstance since when we're
198    // bound, we know our instance is valid.
199    HostGlobals::Get()->GetInstance(pp_instance())->CommitBackingTexture();
200    commit_pending_ = true;
201  } else {
202    // Wait for the command to complete on the GPU to allow for throttling.
203    command_buffer_->Echo(base::Bind(&PPB_Graphics3D_Impl::OnSwapBuffers,
204                                     weak_ptr_factory_.GetWeakPtr()));
205  }
206
207  return PP_OK_COMPLETIONPENDING;
208}
209
210bool PPB_Graphics3D_Impl::Init(PPB_Graphics3D_API* share_context,
211                               const int32_t* attrib_list) {
212  if (!InitRaw(share_context, attrib_list, NULL))
213    return false;
214
215  gpu::gles2::GLES2Implementation* share_gles2 = NULL;
216  if (share_context) {
217    share_gles2 =
218        static_cast<PPB_Graphics3D_Shared*>(share_context)->gles2_impl();
219  }
220
221  return CreateGLES2Impl(kCommandBufferSize, kTransferBufferSize, share_gles2);
222}
223
224bool PPB_Graphics3D_Impl::InitRaw(
225    PPB_Graphics3D_API* share_context,
226    const int32_t* attrib_list,
227    base::SharedMemoryHandle* shared_state_handle) {
228  PepperPluginInstanceImpl* plugin_instance =
229      HostGlobals::Get()->GetInstance(pp_instance());
230  if (!plugin_instance)
231    return false;
232
233  const WebPreferences& prefs =
234      static_cast<RenderViewImpl*>(plugin_instance->GetRenderView())
235          ->webkit_preferences();
236  // 3D access might be disabled or blacklisted.
237  if (!prefs.pepper_3d_enabled)
238    return false;
239
240  RenderThreadImpl* render_thread = RenderThreadImpl::current();
241  if (!render_thread)
242    return false;
243
244  channel_ = render_thread->EstablishGpuChannelSync(
245      CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE);
246  if (!channel_.get())
247    return false;
248
249  gfx::Size surface_size;
250  std::vector<int32> attribs;
251  gfx::GpuPreference gpu_preference = gfx::PreferDiscreteGpu;
252  // TODO(alokp): Change GpuChannelHost::CreateOffscreenCommandBuffer()
253  // interface to accept width and height in the attrib_list so that
254  // we do not need to filter for width and height here.
255  if (attrib_list) {
256    for (const int32_t* attr = attrib_list; attr[0] != PP_GRAPHICS3DATTRIB_NONE;
257         attr += 2) {
258      switch (attr[0]) {
259        case PP_GRAPHICS3DATTRIB_WIDTH:
260          surface_size.set_width(attr[1]);
261          break;
262        case PP_GRAPHICS3DATTRIB_HEIGHT:
263          surface_size.set_height(attr[1]);
264          break;
265        case PP_GRAPHICS3DATTRIB_GPU_PREFERENCE:
266          gpu_preference =
267              (attr[1] == PP_GRAPHICS3DATTRIB_GPU_PREFERENCE_LOW_POWER)
268                  ? gfx::PreferIntegratedGpu
269                  : gfx::PreferDiscreteGpu;
270          break;
271        case PP_GRAPHICS3DATTRIB_ALPHA_SIZE:
272          has_alpha_ = attr[1] > 0;
273        // fall-through
274        default:
275          attribs.push_back(attr[0]);
276          attribs.push_back(attr[1]);
277          break;
278      }
279    }
280    attribs.push_back(PP_GRAPHICS3DATTRIB_NONE);
281  }
282
283  CommandBufferProxyImpl* share_buffer = NULL;
284  if (share_context) {
285    PPB_Graphics3D_Impl* share_graphics =
286        static_cast<PPB_Graphics3D_Impl*>(share_context);
287    share_buffer = share_graphics->command_buffer_;
288  }
289
290  command_buffer_ = channel_->CreateOffscreenCommandBuffer(
291      surface_size, share_buffer, attribs, GURL::EmptyGURL(), gpu_preference);
292  if (!command_buffer_)
293    return false;
294  if (!command_buffer_->Initialize())
295    return false;
296  if (shared_state_handle)
297    *shared_state_handle = command_buffer_->GetSharedStateHandle();
298  mailbox_ = gpu::Mailbox::Generate();
299  if (!command_buffer_->ProduceFrontBuffer(mailbox_))
300    return false;
301  sync_point_ = command_buffer_->InsertSyncPoint();
302
303  command_buffer_->SetChannelErrorCallback(base::Bind(
304      &PPB_Graphics3D_Impl::OnContextLost, weak_ptr_factory_.GetWeakPtr()));
305
306  command_buffer_->SetOnConsoleMessageCallback(base::Bind(
307      &PPB_Graphics3D_Impl::OnConsoleMessage, weak_ptr_factory_.GetWeakPtr()));
308  return true;
309}
310
311void PPB_Graphics3D_Impl::OnConsoleMessage(const std::string& message, int id) {
312  if (!bound_to_instance_)
313    return;
314  WebPluginContainer* container =
315      HostGlobals::Get()->GetInstance(pp_instance())->container();
316  if (!container)
317    return;
318  WebLocalFrame* frame = container->element().document().frame();
319  if (!frame)
320    return;
321  WebConsoleMessage console_message = WebConsoleMessage(
322      WebConsoleMessage::LevelError, WebString(base::UTF8ToUTF16(message)));
323  frame->addMessageToConsole(console_message);
324}
325
326void PPB_Graphics3D_Impl::OnSwapBuffers() {
327  if (HasPendingSwap()) {
328    // If we're off-screen, no need to trigger and wait for compositing.
329    // Just send the swap-buffers ACK to the plugin immediately.
330    commit_pending_ = false;
331    SwapBuffersACK(PP_OK);
332  }
333}
334
335void PPB_Graphics3D_Impl::OnContextLost() {
336  // Don't need to check for NULL from GetPluginInstance since when we're
337  // bound, we know our instance is valid.
338  if (bound_to_instance_) {
339    HostGlobals::Get()->GetInstance(pp_instance())->BindGraphics(pp_instance(),
340                                                                 0);
341  }
342
343  // Send context lost to plugin. This may have been caused by a PPAPI call, so
344  // avoid re-entering.
345  base::MessageLoop::current()->PostTask(
346      FROM_HERE,
347      base::Bind(&PPB_Graphics3D_Impl::SendContextLost,
348                 weak_ptr_factory_.GetWeakPtr()));
349}
350
351void PPB_Graphics3D_Impl::SendContextLost() {
352  // By the time we run this, the instance may have been deleted, or in the
353  // process of being deleted. Even in the latter case, we don't want to send a
354  // callback after DidDestroy.
355  PepperPluginInstanceImpl* instance =
356      HostGlobals::Get()->GetInstance(pp_instance());
357  if (!instance || !instance->container())
358    return;
359
360  // This PPB_Graphics3D_Impl could be deleted during the call to
361  // GetPluginInterface (which sends a sync message in some cases). We still
362  // send the Graphics3DContextLost to the plugin; the instance may care about
363  // that event even though this context has been destroyed.
364  PP_Instance this_pp_instance = pp_instance();
365  const PPP_Graphics3D* ppp_graphics_3d = static_cast<const PPP_Graphics3D*>(
366      instance->module()->GetPluginInterface(PPP_GRAPHICS_3D_INTERFACE));
367  // We have to check *again* that the instance exists, because it could have
368  // been deleted during GetPluginInterface(). Even the PluginModule could be
369  // deleted, but in that case, the instance should also be gone, so the
370  // GetInstance check covers both cases.
371  if (ppp_graphics_3d && HostGlobals::Get()->GetInstance(this_pp_instance))
372    ppp_graphics_3d->Graphics3DContextLost(this_pp_instance);
373}
374
375}  // namespace content
376