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/image_transport_surface.h"
6
7#include "base/mac/scoped_cftyperef.h"
8#include "base/memory/scoped_ptr.h"
9#include "content/common/gpu/gpu_command_buffer_stub.h"
10#include "content/common/gpu/gpu_messages.h"
11#include "ui/gfx/native_widget_types.h"
12#include "ui/gl/gl_bindings.h"
13#include "ui/gl/gl_context.h"
14#include "ui/gl/gl_implementation.h"
15#include "ui/gl/gl_surface_cgl.h"
16#include "ui/gl/gl_surface_osmesa.h"
17#include "ui/gl/io_surface_support_mac.h"
18
19namespace content {
20namespace {
21
22// IOSurface dimensions will be rounded up to a multiple of this value in order
23// to reduce memory thrashing during resize. This must be a power of 2.
24const uint32 kIOSurfaceDimensionRoundup = 64;
25
26int RoundUpSurfaceDimension(int number) {
27  DCHECK(number >= 0);
28  // Cast into unsigned space for portable bitwise ops.
29  uint32 unsigned_number = static_cast<uint32>(number);
30  uint32 roundup_sub_1 = kIOSurfaceDimensionRoundup - 1;
31  unsigned_number = (unsigned_number + roundup_sub_1) & ~roundup_sub_1;
32  return static_cast<int>(unsigned_number);
33}
34
35// We are backed by an offscreen surface for the purposes of creating
36// a context, but use FBOs to render to texture backed IOSurface
37class IOSurfaceImageTransportSurface
38    : public gfx::NoOpGLSurfaceCGL,
39      public ImageTransportSurface,
40      public GpuCommandBufferStub::DestructionObserver {
41 public:
42  IOSurfaceImageTransportSurface(GpuChannelManager* manager,
43                                 GpuCommandBufferStub* stub,
44                                 gfx::PluginWindowHandle handle);
45
46  // GLSurface implementation
47  virtual bool Initialize() OVERRIDE;
48  virtual void Destroy() OVERRIDE;
49  virtual bool DeferDraws() OVERRIDE;
50  virtual bool IsOffscreen() OVERRIDE;
51  virtual bool SwapBuffers() OVERRIDE;
52  virtual bool PostSubBuffer(int x, int y, int width, int height) OVERRIDE;
53  virtual std::string GetExtensions() OVERRIDE;
54  virtual gfx::Size GetSize() OVERRIDE;
55  virtual bool OnMakeCurrent(gfx::GLContext* context) OVERRIDE;
56  virtual unsigned int GetBackingFrameBufferObject() OVERRIDE;
57  virtual bool SetBackbufferAllocation(bool allocated) OVERRIDE;
58  virtual void SetFrontbufferAllocation(bool allocated) OVERRIDE;
59
60 protected:
61  // ImageTransportSurface implementation
62  virtual void OnBufferPresented(
63      const AcceleratedSurfaceMsg_BufferPresented_Params& params) OVERRIDE;
64  virtual void OnResizeViewACK() OVERRIDE;
65  virtual void OnResize(gfx::Size size, float scale_factor) OVERRIDE;
66  virtual void SetLatencyInfo(const ui::LatencyInfo&) OVERRIDE;
67  virtual void WakeUpGpu() OVERRIDE;
68
69  // GpuCommandBufferStub::DestructionObserver implementation.
70  virtual void OnWillDestroyStub() OVERRIDE;
71
72 private:
73  virtual ~IOSurfaceImageTransportSurface() OVERRIDE;
74
75  void AdjustBufferAllocation();
76  void UnrefIOSurface();
77  void CreateIOSurface();
78
79  // Tracks the current buffer allocation state.
80  bool backbuffer_suggested_allocation_;
81  bool frontbuffer_suggested_allocation_;
82
83  uint32 fbo_id_;
84  GLuint texture_id_;
85  GLuint depth_stencil_renderbuffer_id_;
86
87  base::ScopedCFTypeRef<CFTypeRef> io_surface_;
88
89  // The id of |io_surface_| or 0 if that's NULL.
90  uint64 io_surface_handle_;
91
92  // Weak pointer to the context that this was last made current to.
93  gfx::GLContext* context_;
94
95  gfx::Size size_;
96  gfx::Size rounded_size_;
97  float scale_factor_;
98
99  // Whether or not we've successfully made the surface current once.
100  bool made_current_;
101
102  // Whether a SwapBuffers is pending.
103  bool is_swap_buffers_pending_;
104
105  // Whether we unscheduled command buffer because of pending SwapBuffers.
106  bool did_unschedule_;
107
108  ui::LatencyInfo latency_info_;
109
110  scoped_ptr<ImageTransportHelper> helper_;
111
112  DISALLOW_COPY_AND_ASSIGN(IOSurfaceImageTransportSurface);
113};
114
115void AddBooleanValue(CFMutableDictionaryRef dictionary,
116                     const CFStringRef key,
117                     bool value) {
118  CFDictionaryAddValue(dictionary, key,
119                       (value ? kCFBooleanTrue : kCFBooleanFalse));
120}
121
122void AddIntegerValue(CFMutableDictionaryRef dictionary,
123                     const CFStringRef key,
124                     int32 value) {
125  base::ScopedCFTypeRef<CFNumberRef> number(
126      CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
127  CFDictionaryAddValue(dictionary, key, number.get());
128}
129
130IOSurfaceImageTransportSurface::IOSurfaceImageTransportSurface(
131    GpuChannelManager* manager,
132    GpuCommandBufferStub* stub,
133    gfx::PluginWindowHandle handle)
134    : gfx::NoOpGLSurfaceCGL(gfx::Size(1, 1)),
135      backbuffer_suggested_allocation_(true),
136      frontbuffer_suggested_allocation_(true),
137      fbo_id_(0),
138      texture_id_(0),
139      depth_stencil_renderbuffer_id_(0),
140      io_surface_handle_(0),
141      context_(NULL),
142      scale_factor_(1.f),
143      made_current_(false),
144      is_swap_buffers_pending_(false),
145      did_unschedule_(false) {
146  helper_.reset(new ImageTransportHelper(this, manager, stub, handle));
147}
148
149IOSurfaceImageTransportSurface::~IOSurfaceImageTransportSurface() {
150}
151
152bool IOSurfaceImageTransportSurface::Initialize() {
153  // Only support IOSurfaces if the GL implementation is the native desktop GL.
154  // IO surfaces will not work with, for example, OSMesa software renderer
155  // GL contexts.
156  if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL &&
157      gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL)
158    return false;
159
160  if (!helper_->Initialize())
161    return false;
162
163  if (!NoOpGLSurfaceCGL::Initialize()) {
164    helper_->Destroy();
165    return false;
166  }
167
168  helper_->stub()->AddDestructionObserver(this);
169  return true;
170}
171
172void IOSurfaceImageTransportSurface::Destroy() {
173  UnrefIOSurface();
174
175  helper_->Destroy();
176  NoOpGLSurfaceCGL::Destroy();
177}
178
179bool IOSurfaceImageTransportSurface::DeferDraws() {
180  // The command buffer hit a draw/clear command that could clobber the
181  // IOSurface in use by an earlier SwapBuffers. If a Swap is pending, abort
182  // processing of the command by returning true and unschedule until the Swap
183  // Ack arrives.
184  if(did_unschedule_)
185    return true;  // Still unscheduled, so just return true.
186  if (is_swap_buffers_pending_) {
187    did_unschedule_ = true;
188    helper_->SetScheduled(false);
189    return true;
190  }
191  return false;
192}
193
194bool IOSurfaceImageTransportSurface::IsOffscreen() {
195  return false;
196}
197
198bool IOSurfaceImageTransportSurface::OnMakeCurrent(gfx::GLContext* context) {
199  context_ = context;
200
201  if (made_current_)
202    return true;
203
204  OnResize(gfx::Size(1, 1), 1.f);
205
206  made_current_ = true;
207  return true;
208}
209
210unsigned int IOSurfaceImageTransportSurface::GetBackingFrameBufferObject() {
211  return fbo_id_;
212}
213
214bool IOSurfaceImageTransportSurface::SetBackbufferAllocation(bool allocation) {
215  if (backbuffer_suggested_allocation_ == allocation)
216    return true;
217  backbuffer_suggested_allocation_ = allocation;
218  AdjustBufferAllocation();
219  return true;
220}
221
222void IOSurfaceImageTransportSurface::SetFrontbufferAllocation(bool allocation) {
223  if (frontbuffer_suggested_allocation_ == allocation)
224    return;
225  frontbuffer_suggested_allocation_ = allocation;
226  AdjustBufferAllocation();
227}
228
229void IOSurfaceImageTransportSurface::AdjustBufferAllocation() {
230  // On mac, the frontbuffer and backbuffer are the same buffer. The buffer is
231  // free'd when both the browser and gpu processes have Unref'd the IOSurface.
232  if (!backbuffer_suggested_allocation_ &&
233      !frontbuffer_suggested_allocation_ &&
234      io_surface_.get()) {
235    UnrefIOSurface();
236    helper_->Suspend();
237  } else if (backbuffer_suggested_allocation_ && !io_surface_) {
238    CreateIOSurface();
239  }
240}
241
242bool IOSurfaceImageTransportSurface::SwapBuffers() {
243  DCHECK(backbuffer_suggested_allocation_);
244  if (!frontbuffer_suggested_allocation_)
245    return true;
246  glFlush();
247
248  GpuHostMsg_AcceleratedSurfaceBuffersSwapped_Params params;
249  params.surface_handle = io_surface_handle_;
250  params.size = GetSize();
251  params.scale_factor = scale_factor_;
252  params.latency_info = latency_info_;
253  helper_->SendAcceleratedSurfaceBuffersSwapped(params);
254
255  DCHECK(!is_swap_buffers_pending_);
256  is_swap_buffers_pending_ = true;
257  return true;
258}
259
260bool IOSurfaceImageTransportSurface::PostSubBuffer(
261    int x, int y, int width, int height) {
262  DCHECK(backbuffer_suggested_allocation_);
263  if (!frontbuffer_suggested_allocation_)
264    return true;
265  glFlush();
266
267  GpuHostMsg_AcceleratedSurfacePostSubBuffer_Params params;
268  params.surface_handle = io_surface_handle_;
269  params.x = x;
270  params.y = y;
271  params.width = width;
272  params.height = height;
273  params.surface_size = GetSize();
274  params.surface_scale_factor = scale_factor_;
275  params.latency_info = latency_info_;
276  helper_->SendAcceleratedSurfacePostSubBuffer(params);
277
278  DCHECK(!is_swap_buffers_pending_);
279  is_swap_buffers_pending_ = true;
280  return true;
281}
282
283std::string IOSurfaceImageTransportSurface::GetExtensions() {
284  std::string extensions = gfx::GLSurface::GetExtensions();
285  extensions += extensions.empty() ? "" : " ";
286  extensions += "GL_CHROMIUM_front_buffer_cached ";
287  extensions += "GL_CHROMIUM_post_sub_buffer";
288  return extensions;
289}
290
291gfx::Size IOSurfaceImageTransportSurface::GetSize() {
292  return size_;
293}
294
295void IOSurfaceImageTransportSurface::OnBufferPresented(
296    const AcceleratedSurfaceMsg_BufferPresented_Params& params) {
297  DCHECK(is_swap_buffers_pending_);
298
299  context_->share_group()->SetRendererID(params.renderer_id);
300  is_swap_buffers_pending_ = false;
301  if (did_unschedule_) {
302    did_unschedule_ = false;
303    helper_->SetScheduled(true);
304  }
305}
306
307void IOSurfaceImageTransportSurface::OnResizeViewACK() {
308  NOTREACHED();
309}
310
311void IOSurfaceImageTransportSurface::OnResize(gfx::Size size,
312                                              float scale_factor) {
313  // This trace event is used in gpu_feature_browsertest.cc - the test will need
314  // to be updated if this event is changed or moved.
315  TRACE_EVENT2("gpu", "IOSurfaceImageTransportSurface::OnResize",
316               "old_width", size_.width(), "new_width", size.width());
317  // Caching |context_| from OnMakeCurrent. It should still be current.
318  DCHECK(context_->IsCurrent(this));
319
320  size_ = size;
321  scale_factor_ = scale_factor;
322
323  CreateIOSurface();
324}
325
326void IOSurfaceImageTransportSurface::SetLatencyInfo(
327    const ui::LatencyInfo& latency_info) {
328  latency_info_ = latency_info;
329}
330
331void IOSurfaceImageTransportSurface::WakeUpGpu() {
332  NOTIMPLEMENTED();
333}
334
335void IOSurfaceImageTransportSurface::OnWillDestroyStub() {
336  helper_->stub()->RemoveDestructionObserver(this);
337  Destroy();
338}
339
340void IOSurfaceImageTransportSurface::UnrefIOSurface() {
341  // If we have resources to destroy, then make sure that we have a current
342  // context which we can use to delete the resources.
343  if (context_ || fbo_id_ || texture_id_ || depth_stencil_renderbuffer_id_) {
344    DCHECK(gfx::GLContext::GetCurrent() == context_);
345    DCHECK(context_->IsCurrent(this));
346    DCHECK(CGLGetCurrentContext());
347  }
348
349  if (fbo_id_) {
350    glDeleteFramebuffersEXT(1, &fbo_id_);
351    fbo_id_ = 0;
352  }
353
354  if (texture_id_) {
355    glDeleteTextures(1, &texture_id_);
356    texture_id_ = 0;
357  }
358
359  if (depth_stencil_renderbuffer_id_) {
360    glDeleteRenderbuffersEXT(1, &depth_stencil_renderbuffer_id_);
361    depth_stencil_renderbuffer_id_ = 0;
362  }
363
364  io_surface_.reset();
365  io_surface_handle_ = 0;
366}
367
368void IOSurfaceImageTransportSurface::CreateIOSurface() {
369  gfx::Size new_rounded_size(RoundUpSurfaceDimension(size_.width()),
370                             RoundUpSurfaceDimension(size_.height()));
371
372  // Only recreate surface when the rounded up size has changed.
373  if (io_surface_.get() && new_rounded_size == rounded_size_)
374    return;
375
376  // This trace event is used in gpu_feature_browsertest.cc - the test will need
377  // to be updated if this event is changed or moved.
378  TRACE_EVENT2("gpu", "IOSurfaceImageTransportSurface::CreateIOSurface",
379               "width", new_rounded_size.width(),
380               "height", new_rounded_size.height());
381
382  rounded_size_ = new_rounded_size;
383
384  GLint previous_texture_id = 0;
385  glGetIntegerv(GL_TEXTURE_BINDING_RECTANGLE_ARB, &previous_texture_id);
386
387  // Free the old IO Surface first to reduce memory fragmentation.
388  UnrefIOSurface();
389
390  glGenFramebuffersEXT(1, &fbo_id_);
391  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_id_);
392
393  IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
394
395  glGenTextures(1, &texture_id_);
396
397  // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on
398  // Mac OS X and is required for IOSurface interoperability.
399  GLenum target = GL_TEXTURE_RECTANGLE_ARB;
400  glBindTexture(target, texture_id_);
401  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
402  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
403  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
404  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
405
406  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
407                            GL_COLOR_ATTACHMENT0_EXT,
408                            target,
409                            texture_id_,
410                            0);
411
412
413  // Search through the provided attributes; if the caller has
414  // requested a stencil buffer, try to get one.
415
416  int32 stencil_bits =
417      helper_->stub()->GetRequestedAttribute(EGL_STENCIL_SIZE);
418  if (stencil_bits > 0) {
419    // Create and bind the stencil buffer
420    bool has_packed_depth_stencil =
421         GLSurface::ExtensionsContain(
422             reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
423                                            "GL_EXT_packed_depth_stencil");
424
425    if (has_packed_depth_stencil) {
426      glGenRenderbuffersEXT(1, &depth_stencil_renderbuffer_id_);
427      glBindRenderbufferEXT(GL_RENDERBUFFER_EXT,
428                            depth_stencil_renderbuffer_id_);
429      glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT,
430                              rounded_size_.width(), rounded_size_.height());
431      glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
432                                  GL_STENCIL_ATTACHMENT_EXT,
433                                  GL_RENDERBUFFER_EXT,
434                                  depth_stencil_renderbuffer_id_);
435    }
436
437    // If we asked for stencil but the extension isn't present,
438    // it's OK to silently fail; subsequent code will/must check
439    // for the presence of a stencil buffer before attempting to
440    // do stencil-based operations.
441  }
442
443  // Allocate a new IOSurface, which is the GPU resource that can be
444  // shared across processes.
445  base::ScopedCFTypeRef<CFMutableDictionaryRef> properties;
446  properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault,
447                                             0,
448                                             &kCFTypeDictionaryKeyCallBacks,
449                                             &kCFTypeDictionaryValueCallBacks));
450  AddIntegerValue(properties,
451                  io_surface_support->GetKIOSurfaceWidth(),
452                  rounded_size_.width());
453  AddIntegerValue(properties,
454                  io_surface_support->GetKIOSurfaceHeight(),
455                  rounded_size_.height());
456  AddIntegerValue(properties,
457                  io_surface_support->GetKIOSurfaceBytesPerElement(), 4);
458  AddBooleanValue(properties,
459                  io_surface_support->GetKIOSurfaceIsGlobal(), true);
460  // I believe we should be able to unreference the IOSurfaces without
461  // synchronizing with the browser process because they are
462  // ultimately reference counted by the operating system.
463  io_surface_.reset(io_surface_support->IOSurfaceCreate(properties));
464  io_surface_handle_ = io_surface_support->IOSurfaceGetID(io_surface_);
465
466  // Don't think we need to identify a plane.
467  GLuint plane = 0;
468  CGLError cglerror =
469      io_surface_support->CGLTexImageIOSurface2D(
470          static_cast<CGLContextObj>(context_->GetHandle()),
471          target,
472          GL_RGBA,
473          rounded_size_.width(),
474          rounded_size_.height(),
475          GL_BGRA,
476          GL_UNSIGNED_INT_8_8_8_8_REV,
477          io_surface_.get(),
478          plane);
479  if (cglerror != kCGLNoError) {
480    UnrefIOSurface();
481    return;
482  }
483
484  glFlush();
485
486  GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
487  if (status != GL_FRAMEBUFFER_COMPLETE_EXT) {
488    DLOG(ERROR) << "Framebuffer was incomplete: " << status;
489    UnrefIOSurface();
490    return;
491  }
492
493  glBindTexture(target, previous_texture_id);
494  // The FBO remains bound for this GL context.
495}
496
497// A subclass of GLSurfaceOSMesa that doesn't print an error message when
498// SwapBuffers() is called.
499class DRTSurfaceOSMesa : public gfx::GLSurfaceOSMesa {
500 public:
501  // Size doesn't matter, the surface is resized to the right size later.
502  DRTSurfaceOSMesa() : GLSurfaceOSMesa(GL_RGBA, gfx::Size(1, 1)) {}
503
504  // Implement a subset of GLSurface.
505  virtual bool SwapBuffers() OVERRIDE;
506
507 private:
508  virtual ~DRTSurfaceOSMesa() {}
509  DISALLOW_COPY_AND_ASSIGN(DRTSurfaceOSMesa);
510};
511
512bool DRTSurfaceOSMesa::SwapBuffers() {
513  return true;
514}
515
516bool g_allow_os_mesa = false;
517
518}  // namespace
519
520// static
521scoped_refptr<gfx::GLSurface> ImageTransportSurface::CreateNativeSurface(
522    GpuChannelManager* manager,
523    GpuCommandBufferStub* stub,
524    const gfx::GLSurfaceHandle& surface_handle) {
525  DCHECK(surface_handle.transport_type == gfx::NATIVE_TRANSPORT);
526  IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
527
528  switch (gfx::GetGLImplementation()) {
529    case gfx::kGLImplementationDesktopGL:
530    case gfx::kGLImplementationAppleGL:
531      if (!io_surface_support) {
532        DLOG(WARNING) << "No IOSurface support";
533        return scoped_refptr<gfx::GLSurface>();
534      }
535      return scoped_refptr<gfx::GLSurface>(new IOSurfaceImageTransportSurface(
536          manager, stub, surface_handle.handle));
537
538    default:
539      // Content shell in DRT mode spins up a gpu process which needs an
540      // image transport surface, but that surface isn't used to read pixel
541      // baselines. So this is mostly a dummy surface.
542      if (!g_allow_os_mesa) {
543        NOTREACHED();
544        return scoped_refptr<gfx::GLSurface>();
545      }
546      scoped_refptr<gfx::GLSurface> surface(new DRTSurfaceOSMesa());
547      if (!surface.get() || !surface->Initialize())
548        return surface;
549      return scoped_refptr<gfx::GLSurface>(new PassThroughImageTransportSurface(
550          manager, stub, surface.get(), false));
551  }
552}
553
554// static
555void ImageTransportSurface::SetAllowOSMesaForTesting(bool allow) {
556  g_allow_os_mesa = allow;
557}
558
559}  // namespace content
560