Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

accelerated_surface_mac.cc

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "app/surface/accelerated_surface_mac.h"

#include "app/surface/io_surface_support_mac.h"
#include "base/logging.h"
#include "gfx/rect.h"

AcceleratedSurface::AcceleratedSurface()
    : gl_context_(NULL),
      pbuffer_(NULL),
      texture_(0),
      fbo_(0),
      depth_stencil_renderbuffer_(0),
      bound_fbo_(0),
      bound_renderbuffer_(0) {
}

bool AcceleratedSurface::Initialize() {
  // Create a 1x1 pbuffer and associated context to bootstrap things
  static const CGLPixelFormatAttribute attribs[] = {
    (CGLPixelFormatAttribute) kCGLPFAPBuffer,
    (CGLPixelFormatAttribute) 0
  };
  CGLPixelFormatObj pixel_format;
  GLint num_pixel_formats;
  if (CGLChoosePixelFormat(attribs,
                           &pixel_format,
                           &num_pixel_formats) != kCGLNoError) {
    DLOG(ERROR) << "Error choosing pixel format.";
    return false;
  }
  if (!pixel_format) {
    return false;
  }
  CGLContextObj context;
  CGLError res = CGLCreateContext(pixel_format, 0, &context);
  CGLDestroyPixelFormat(pixel_format);
  if (res != kCGLNoError) {
    DLOG(ERROR) << "Error creating context.";
    return false;
  }
  CGLPBufferObj pbuffer;
  if (CGLCreatePBuffer(1, 1,
                       GL_TEXTURE_2D, GL_RGBA,
                       0, &pbuffer) != kCGLNoError) {
    CGLDestroyContext(context);
    DLOG(ERROR) << "Error creating pbuffer.";
    return false;
  }
  if (CGLSetPBuffer(context, pbuffer, 0, 0, 0) != kCGLNoError) {
    CGLDestroyContext(context);
    CGLDestroyPBuffer(pbuffer);
    DLOG(ERROR) << "Error attaching pbuffer to context.";
    return false;
  }
  gl_context_ = context;
  pbuffer_ = pbuffer;
  // Now we're ready to handle SetWindowSize calls, which will
  // allocate and/or reallocate the IOSurface and associated offscreen
  // OpenGL structures for rendering.
  return true;
}

void AcceleratedSurface::Destroy() {
  // Release the old TransportDIB in the browser.
  if (dib_free_callback_.get() && transport_dib_.get()) {
    dib_free_callback_->Run(transport_dib_->id());
  }
  transport_dib_.reset();
  if (gl_context_)
    CGLDestroyContext(gl_context_);
  if (pbuffer_)
    CGLDestroyPBuffer(pbuffer_);
}

// Call after making changes to the surface which require a visual update.
// Makes the rendering show up in other processes.
void AcceleratedSurface::SwapBuffers() {
  if (bound_fbo_ != fbo_) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
  }
  if (io_surface_.get() != NULL) {
    // Bind and unbind the framebuffer to make changes to the
    // IOSurface show up in the other process.
    glFlush();
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
  } else if (transport_dib_.get() != NULL) {
    // Pre-Mac OS X 10.6, fetch the rendered image from the FBO and copy it
    // into the TransportDIB.
    // TODO(dspringer): There are a couple of options that can speed this up.
    // First is to use async reads into a PBO, second is to use SPI that
    // allows many tasks to access the same CGSSurface.
    void* pixel_memory = transport_dib_->memory();
    if (pixel_memory) {
      // Note that glReadPixels does an implicit glFlush().
      glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
      glReadPixels(0,
                   0,
                   surface_size_.width(),
                   surface_size_.height(),
                   GL_BGRA,  // This pixel format should have no conversion.
                   GL_UNSIGNED_INT_8_8_8_8_REV,
                   pixel_memory);
    }
  }
  if (bound_fbo_ != fbo_) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bound_fbo_);
  }
}

static void AddBooleanValue(CFMutableDictionaryRef dictionary,
                            const CFStringRef key,
                            bool value) {
  CFDictionaryAddValue(dictionary, key,
                       (value ? kCFBooleanTrue : kCFBooleanFalse));
}

static void AddIntegerValue(CFMutableDictionaryRef dictionary,
                            const CFStringRef key,
                            int32 value) {
  CFNumberRef number = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
  CFDictionaryAddValue(dictionary, key, number);
}

void AcceleratedSurface::AllocateRenderBuffers(GLenum target,
                                               const gfx::Size& size) {
  if (!texture_) {
    // Generate the texture object.
    glGenTextures(1, &texture_);
    glBindTexture(target, texture_);
    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    // Generate and bind the framebuffer object.
    glGenFramebuffersEXT(1, &fbo_);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
    bound_fbo_ = fbo_;
    // Generate (but don't bind) the depth buffer -- we don't need
    // this bound in order to do offscreen rendering.
    glGenRenderbuffersEXT(1, &depth_stencil_renderbuffer_);
  }

  // Reallocate the depth buffer.
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth_stencil_renderbuffer_);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT,
                           GL_DEPTH24_STENCIL8_EXT,
                           size.width(),
                           size.height());

  // Unbind the renderbuffers.
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, bound_renderbuffer_);

  // Make sure that subsequent set-up code affects the render texture.
  glBindTexture(target, texture_);
}

bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) {
  if (bound_fbo_ != fbo_) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
  }
  GLenum fbo_status;
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
                            GL_COLOR_ATTACHMENT0_EXT,
                            target,
                            texture_,
                            0);
  fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  if (fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT) {
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                 GL_DEPTH_ATTACHMENT_EXT,
                                 GL_RENDERBUFFER_EXT,
                                 depth_stencil_renderbuffer_);
    fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  }
  // Attach the depth and stencil buffer.
  if (fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT) {
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
                                 0x8D20, // GL_STENCIL_ATTACHMENT,
                                 GL_RENDERBUFFER_EXT,
                                 depth_stencil_renderbuffer_);
    fbo_status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  }
  if (bound_fbo_ != fbo_) {
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bound_fbo_);
  }
  return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT;
}

bool AcceleratedSurface::MakeCurrent() {
  if (CGLGetCurrentContext() != gl_context_) {
    if (CGLSetCurrentContext(gl_context_) != kCGLNoError) {
      DLOG(ERROR) << "Unable to make gl context current.";
      return false;
    }
  }
  return true;
}

void AcceleratedSurface::Clear(const gfx::Rect& rect) {
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glViewport(0, 0, rect.width(), rect.height());
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0, rect.width(), 0, rect.height(), -1, 1);
  glClear(GL_COLOR_BUFFER_BIT);
}

uint64 AcceleratedSurface::SetSurfaceSize(const gfx::Size& size) {
  if (surface_size_ == size) {
    // Return 0 to indicate to the caller that no new backing store
    // allocation occurred.
    return 0;
  }

  IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
  if (!io_surface_support)
    return 0;  // Caller can try using SetWindowSizeForTransportDIB().

  if (!MakeCurrent())
    return 0;

  // GL_TEXTURE_RECTANGLE_ARB is the best supported render target on
  // Mac OS X and is required for IOSurface interoperability.
  GLenum target = GL_TEXTURE_RECTANGLE_ARB;
  AllocateRenderBuffers(target, size);

  // Allocate a new IOSurface, which is the GPU resource that can be
  // shared across processes.
  scoped_cftyperef<CFMutableDictionaryRef> properties;
  properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault,
                                             0,
                                             &kCFTypeDictionaryKeyCallBacks,
                                             &kCFTypeDictionaryValueCallBacks));
  AddIntegerValue(properties,
                  io_surface_support->GetKIOSurfaceWidth(), size.width());
  AddIntegerValue(properties,
                  io_surface_support->GetKIOSurfaceHeight(), size.height());
  AddIntegerValue(properties,
                  io_surface_support->GetKIOSurfaceBytesPerElement(), 4);
  AddBooleanValue(properties,
                  io_surface_support->GetKIOSurfaceIsGlobal(), true);
  // I believe we should be able to unreference the IOSurfaces without
  // synchronizing with the browser process because they are
  // ultimately reference counted by the operating system.
  io_surface_.reset(io_surface_support->IOSurfaceCreate(properties));

  // Don't think we need to identify a plane.
  GLuint plane = 0;
  io_surface_support->CGLTexImageIOSurface2D(gl_context_,
                                             target,
                                             GL_RGBA,
                                             size.width(),
                                             size.height(),
                                             GL_BGRA,
                                             GL_UNSIGNED_INT_8_8_8_8_REV,
                                             io_surface_.get(),
                                             plane);
  // Set up the frame buffer object.
  SetupFrameBufferObject(target);
  surface_size_ = size;

  // Now send back an identifier for the IOSurface. We originally
  // intended to send back a mach port from IOSurfaceCreateMachPort
  // but it looks like Chrome IPC would need to be modified to
  // properly send mach ports between processes. For the time being we
  // make our IOSurfaces global and send back their identifiers. On
  // the browser process side the identifier is reconstituted into an
  // IOSurface for on-screen rendering.
  return io_surface_support->IOSurfaceGetID(io_surface_);
}

TransportDIB::Handle AcceleratedSurface::SetTransportDIBSize(
    const gfx::Size& size) {
  if (surface_size_ == size) {
    // Return an invalid handle to indicate to the caller that no new backing
    // store allocation occurred.
    return TransportDIB::DefaultHandleValue();
  }
  surface_size_ = size;

  // Release the old TransportDIB in the browser.
  if (dib_free_callback_.get() && transport_dib_.get()) {
    dib_free_callback_->Run(transport_dib_->id());
  }
  transport_dib_.reset();

  // Ask the renderer to create a TransportDIB.
  size_t dib_size = size.width() * 4 * size.height();  // 4 bytes per pixel.
  TransportDIB::Handle dib_handle;
  if (dib_alloc_callback_.get()) {
    dib_alloc_callback_->Run(dib_size, &dib_handle);
  }
  if (!TransportDIB::is_valid(dib_handle)) {
    // If the allocator fails, it means the DIB was not created in the browser,
    // so there is no need to run the deallocator here.
    return TransportDIB::DefaultHandleValue();
  }
  transport_dib_.reset(TransportDIB::Map(dib_handle));
  if (transport_dib_.get() == NULL) {
    // TODO(dspringer): if the Map() fails, should the deallocator be run so
    // that the DIB is deallocated in the browser?
    return TransportDIB::DefaultHandleValue();
  }

  // Set up the render buffers and reserve enough space on the card for the
  // framebuffer texture.
  GLenum target = GL_TEXTURE_RECTANGLE_ARB;
  AllocateRenderBuffers(target, size);
  glTexImage2D(target,
               0,  // mipmap level 0
               GL_RGBA8,  // internal pixel format
               size.width(),
               size.height(),
               0,  // 0 border
               GL_BGRA,  // Used for consistency
               GL_UNSIGNED_INT_8_8_8_8_REV,
               NULL);  // No data, just reserve room on the card.
  SetupFrameBufferObject(target);
  return transport_dib_->handle();
}

void AcceleratedSurface::SetTransportDIBAllocAndFree(
    Callback2<size_t, TransportDIB::Handle*>::Type* allocator,
    Callback1<TransportDIB::Id>::Type* deallocator) {
  dib_alloc_callback_.reset(allocator);
  dib_free_callback_.reset(deallocator);
}

Generated by  Doxygen 1.6.0   Back to index