Home | History | Annotate | Download | only in surface
      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 "ui/surface/accelerated_surface_mac.h"
      6 
      7 #include "base/logging.h"
      8 #include "base/mac/scoped_cftyperef.h"
      9 #include "ui/gfx/rect.h"
     10 #include "ui/gl/gl_bindings.h"
     11 #include "ui/gl/gl_context.h"
     12 #include "ui/gl/gl_implementation.h"
     13 #include "ui/gl/gl_surface.h"
     14 #include "ui/gl/scoped_make_current.h"
     15 
     16 // Note that this must be included after gl_bindings.h to avoid conflicts.
     17 #include <OpenGL/CGLIOSurface.h>
     18 
     19 AcceleratedSurface::AcceleratedSurface()
     20     : io_surface_id_(0),
     21       allocate_fbo_(false),
     22       texture_(0),
     23       fbo_(0) {
     24 }
     25 
     26 AcceleratedSurface::~AcceleratedSurface() {}
     27 
     28 bool AcceleratedSurface::Initialize(
     29     gfx::GLContext* share_context,
     30     bool allocate_fbo,
     31     gfx::GpuPreference gpu_preference) {
     32   allocate_fbo_ = allocate_fbo;
     33 
     34   // GL should be initialized by content::SupportsCoreAnimationPlugins().
     35   DCHECK_NE(gfx::GetGLImplementation(), gfx::kGLImplementationNone);
     36 
     37   // Drawing to IOSurfaces via OpenGL only works with Apple's GL and
     38   // not with the OSMesa software renderer.
     39   if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL &&
     40       gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL)
     41     return false;
     42 
     43   gl_surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1));
     44   if (!gl_surface_.get()) {
     45     Destroy();
     46     return false;
     47   }
     48 
     49   gfx::GLShareGroup* share_group =
     50       share_context ? share_context->share_group() : NULL;
     51 
     52   gl_context_ = gfx::GLContext::CreateGLContext(
     53       share_group,
     54       gl_surface_.get(),
     55       gpu_preference);
     56   if (!gl_context_.get()) {
     57     Destroy();
     58     return false;
     59   }
     60 
     61   // Now we're ready to handle SetSurfaceSize calls, which will
     62   // allocate and/or reallocate the IOSurface and associated offscreen
     63   // OpenGL structures for rendering.
     64   return true;
     65 }
     66 
     67 void AcceleratedSurface::Destroy() {
     68   // The FBO and texture objects will be destroyed when the OpenGL context,
     69   // and any other contexts sharing resources with it, is. We don't want to
     70   // make the context current one last time here just in order to delete
     71   // these objects.
     72   gl_context_ = NULL;
     73   gl_surface_ = NULL;
     74 }
     75 
     76 // Call after making changes to the surface which require a visual update.
     77 // Makes the rendering show up in other processes.
     78 void AcceleratedSurface::SwapBuffers() {
     79   if (io_surface_.get() != NULL) {
     80     if (allocate_fbo_) {
     81       // Bind and unbind the framebuffer to make changes to the
     82       // IOSurface show up in the other process.
     83       glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
     84       glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
     85       glFlush();
     86     } else {
     87       // Copy the current framebuffer's contents into our "live" texture.
     88       // Note that the current GL context might not be ours at this point!
     89       // This is deliberate, so that surrounding code using GL can produce
     90       // rendering results consumed by the AcceleratedSurface.
     91       // Need to save and restore OpenGL state around this call.
     92       GLint current_texture = 0;
     93       GLenum target_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB;
     94       GLenum target = GL_TEXTURE_RECTANGLE_ARB;
     95       glGetIntegerv(target_binding, &current_texture);
     96       glBindTexture(target, texture_);
     97       glCopyTexSubImage2D(target, 0,
     98                           0, 0,
     99                           0, 0,
    100                           real_surface_size_.width(),
    101                           real_surface_size_.height());
    102       glBindTexture(target, current_texture);
    103       // This flush is absolutely essential -- it guarantees that the
    104       // rendering results are seen by the other process.
    105       glFlush();
    106     }
    107   }
    108 }
    109 
    110 static void AddBooleanValue(CFMutableDictionaryRef dictionary,
    111                             const CFStringRef key,
    112                             bool value) {
    113   CFDictionaryAddValue(dictionary, key,
    114                        (value ? kCFBooleanTrue : kCFBooleanFalse));
    115 }
    116 
    117 static void AddIntegerValue(CFMutableDictionaryRef dictionary,
    118                             const CFStringRef key,
    119                             int32 value) {
    120   base::ScopedCFTypeRef<CFNumberRef> number(
    121       CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
    122   CFDictionaryAddValue(dictionary, key, number.get());
    123 }
    124 
    125 // Creates a new OpenGL texture object bound to the given texture target.
    126 // Caller owns the returned texture.
    127 static GLuint CreateTexture(GLenum target) {
    128   GLuint texture = 0;
    129   glGenTextures(1, &texture);
    130   glBindTexture(target, texture);
    131   glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    132   glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    133   glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    134   glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    135   return texture;
    136 }
    137 
    138 void AcceleratedSurface::AllocateRenderBuffers(GLenum target,
    139                                                const gfx::Size& size) {
    140   if (!texture_) {
    141     // Generate the texture object.
    142     texture_ = CreateTexture(target);
    143     // Generate and bind the framebuffer object.
    144     glGenFramebuffersEXT(1, &fbo_);
    145     glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
    146   }
    147 
    148   // Make sure that subsequent set-up code affects the render texture.
    149   glBindTexture(target, texture_);
    150 }
    151 
    152 bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) {
    153   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
    154   GLenum fbo_status;
    155   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
    156                             GL_COLOR_ATTACHMENT0_EXT,
    157                             target,
    158                             texture_,
    159                             0);
    160   fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    161   return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT;
    162 }
    163 
    164 gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) {
    165   return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1));
    166 }
    167 
    168 bool AcceleratedSurface::MakeCurrent() {
    169   if (!gl_context_.get())
    170     return false;
    171   return gl_context_->MakeCurrent(gl_surface_.get());
    172 }
    173 
    174 void AcceleratedSurface::Clear(const gfx::Rect& rect) {
    175   DCHECK(gl_context_->IsCurrent(gl_surface_.get()));
    176   glClearColor(0, 0, 0, 0);
    177   glViewport(0, 0, rect.width(), rect.height());
    178   glMatrixMode(GL_PROJECTION);
    179   glLoadIdentity();
    180   glOrtho(0, rect.width(), 0, rect.height(), -1, 1);
    181   glClear(GL_COLOR_BUFFER_BIT);
    182 }
    183 
    184 uint32 AcceleratedSurface::SetSurfaceSize(const gfx::Size& size) {
    185   if (surface_size_ == size) {
    186     // Return 0 to indicate to the caller that no new backing store
    187     // allocation occurred.
    188     return 0;
    189   }
    190 
    191   // Only support IO surfaces if the GL implementation is the native desktop GL.
    192   // IO surfaces will not work with, for example, OSMesa software renderer
    193   // GL contexts.
    194   if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL)
    195     return 0;
    196 
    197   ui::ScopedMakeCurrent make_current(gl_context_.get(), gl_surface_.get());
    198   if (!make_current.Succeeded())
    199     return 0;
    200 
    201   gfx::Size clamped_size = ClampToValidDimensions(size);
    202 
    203   // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on
    204   // Mac OS X and is required for IOSurface interoperability.
    205   GLenum target = GL_TEXTURE_RECTANGLE_ARB;
    206   if (allocate_fbo_) {
    207     AllocateRenderBuffers(target, clamped_size);
    208   } else if (!texture_) {
    209     // Generate the texture object.
    210     texture_ = CreateTexture(target);
    211   }
    212 
    213   // Allocate a new IOSurface, which is the GPU resource that can be
    214   // shared across processes.
    215   base::ScopedCFTypeRef<CFMutableDictionaryRef> properties;
    216   properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault,
    217                                              0,
    218                                              &kCFTypeDictionaryKeyCallBacks,
    219                                              &kCFTypeDictionaryValueCallBacks));
    220   AddIntegerValue(properties, kIOSurfaceWidth, clamped_size.width());
    221   AddIntegerValue(properties, kIOSurfaceHeight, clamped_size.height());
    222   AddIntegerValue(properties, kIOSurfaceBytesPerElement, 4);
    223   AddBooleanValue(properties, kIOSurfaceIsGlobal, true);
    224   // I believe we should be able to unreference the IOSurfaces without
    225   // synchronizing with the browser process because they are
    226   // ultimately reference counted by the operating system.
    227   io_surface_.reset(IOSurfaceCreate(properties));
    228 
    229   // Don't think we need to identify a plane.
    230   GLuint plane = 0;
    231   CGLError error = CGLTexImageIOSurface2D(
    232       static_cast<CGLContextObj>(gl_context_->GetHandle()),
    233       target,
    234       GL_RGBA,
    235       clamped_size.width(),
    236       clamped_size.height(),
    237       GL_BGRA,
    238       GL_UNSIGNED_INT_8_8_8_8_REV,
    239       io_surface_.get(),
    240       plane);
    241   if (error != kCGLNoError) {
    242     DLOG(ERROR) << "CGL error " << error << " during CGLTexImageIOSurface2D";
    243   }
    244   if (allocate_fbo_) {
    245     // Set up the frame buffer object.
    246     if (!SetupFrameBufferObject(target)) {
    247       DLOG(ERROR) << "Failed to set up frame buffer object";
    248     }
    249   }
    250   surface_size_ = size;
    251   real_surface_size_ = clamped_size;
    252 
    253   // Now send back an identifier for the IOSurface. We originally
    254   // intended to send back a mach port from IOSurfaceCreateMachPort
    255   // but it looks like Chrome IPC would need to be modified to
    256   // properly send mach ports between processes. For the time being we
    257   // make our IOSurfaces global and send back their identifiers. On
    258   // the browser process side the identifier is reconstituted into an
    259   // IOSurface for on-screen rendering.
    260   io_surface_id_ = IOSurfaceGetID(io_surface_);
    261   return io_surface_id_;
    262 }
    263 
    264 uint32 AcceleratedSurface::GetSurfaceId() {
    265   return io_surface_id_;
    266 }
    267