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