普通文本  |  267行  |  9.43 KB

// Copyright (c) 2012 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 "ui/surface/accelerated_surface_mac.h"

#include "base/logging.h"
#include "base/mac/scoped_cftyperef.h"
#include "ui/gfx/rect.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/scoped_make_current.h"

// Note that this must be included after gl_bindings.h to avoid conflicts.
#include <OpenGL/CGLIOSurface.h>

AcceleratedSurface::AcceleratedSurface()
    : io_surface_id_(0),
      allocate_fbo_(false),
      texture_(0),
      fbo_(0) {
}

AcceleratedSurface::~AcceleratedSurface() {}

bool AcceleratedSurface::Initialize(
    gfx::GLContext* share_context,
    bool allocate_fbo,
    gfx::GpuPreference gpu_preference) {
  allocate_fbo_ = allocate_fbo;

  // GL should be initialized by content::SupportsCoreAnimationPlugins().
  DCHECK_NE(gfx::GetGLImplementation(), gfx::kGLImplementationNone);

  // Drawing to IOSurfaces via OpenGL only works with Apple's GL and
  // not with the OSMesa software renderer.
  if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL &&
      gfx::GetGLImplementation() != gfx::kGLImplementationAppleGL)
    return false;

  gl_surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1));
  if (!gl_surface_.get()) {
    Destroy();
    return false;
  }

  gfx::GLShareGroup* share_group =
      share_context ? share_context->share_group() : NULL;

  gl_context_ = gfx::GLContext::CreateGLContext(
      share_group,
      gl_surface_.get(),
      gpu_preference);
  if (!gl_context_.get()) {
    Destroy();
    return false;
  }

  // Now we're ready to handle SetSurfaceSize calls, which will
  // allocate and/or reallocate the IOSurface and associated offscreen
  // OpenGL structures for rendering.
  return true;
}

void AcceleratedSurface::Destroy() {
  // The FBO and texture objects will be destroyed when the OpenGL context,
  // and any other contexts sharing resources with it, is. We don't want to
  // make the context current one last time here just in order to delete
  // these objects.
  gl_context_ = NULL;
  gl_surface_ = NULL;
}

// Call after making changes to the surface which require a visual update.
// Makes the rendering show up in other processes.
void AcceleratedSurface::SwapBuffers() {
  if (io_surface_.get() != NULL) {
    if (allocate_fbo_) {
      // Bind and unbind the framebuffer to make changes to the
      // IOSurface show up in the other process.
      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
      glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
      glFlush();
    } else {
      // Copy the current framebuffer's contents into our "live" texture.
      // Note that the current GL context might not be ours at this point!
      // This is deliberate, so that surrounding code using GL can produce
      // rendering results consumed by the AcceleratedSurface.
      // Need to save and restore OpenGL state around this call.
      GLint current_texture = 0;
      GLenum target_binding = GL_TEXTURE_BINDING_RECTANGLE_ARB;
      GLenum target = GL_TEXTURE_RECTANGLE_ARB;
      glGetIntegerv(target_binding, &current_texture);
      glBindTexture(target, texture_);
      glCopyTexSubImage2D(target, 0,
                          0, 0,
                          0, 0,
                          real_surface_size_.width(),
                          real_surface_size_.height());
      glBindTexture(target, current_texture);
      // This flush is absolutely essential -- it guarantees that the
      // rendering results are seen by the other process.
      glFlush();
    }
  }
}

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) {
  base::ScopedCFTypeRef<CFNumberRef> number(
      CFNumberCreate(NULL, kCFNumberSInt32Type, &value));
  CFDictionaryAddValue(dictionary, key, number.get());
}

// Creates a new OpenGL texture object bound to the given texture target.
// Caller owns the returned texture.
static GLuint CreateTexture(GLenum target) {
  GLuint texture = 0;
  glGenTextures(1, &texture);
  glBindTexture(target, texture);
  glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  return texture;
}

void AcceleratedSurface::AllocateRenderBuffers(GLenum target,
                                               const gfx::Size& size) {
  if (!texture_) {
    // Generate the texture object.
    texture_ = CreateTexture(target);
    // Generate and bind the framebuffer object.
    glGenFramebuffersEXT(1, &fbo_);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo_);
  }

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

bool AcceleratedSurface::SetupFrameBufferObject(GLenum target) {
  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);
  return fbo_status == GL_FRAMEBUFFER_COMPLETE_EXT;
}

gfx::Size AcceleratedSurface::ClampToValidDimensions(const gfx::Size& size) {
  return gfx::Size(std::max(size.width(), 1), std::max(size.height(), 1));
}

bool AcceleratedSurface::MakeCurrent() {
  if (!gl_context_.get())
    return false;
  return gl_context_->MakeCurrent(gl_surface_.get());
}

void AcceleratedSurface::Clear(const gfx::Rect& rect) {
  DCHECK(gl_context_->IsCurrent(gl_surface_.get()));
  glClearColor(0, 0, 0, 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);
}

uint32 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;
  }

  // Only support IO surfaces if the GL implementation is the native desktop GL.
  // IO surfaces will not work with, for example, OSMesa software renderer
  // GL contexts.
  if (gfx::GetGLImplementation() != gfx::kGLImplementationDesktopGL)
    return 0;

  ui::ScopedMakeCurrent make_current(gl_context_.get(), gl_surface_.get());
  if (!make_current.Succeeded())
    return 0;

  gfx::Size clamped_size = ClampToValidDimensions(size);

  // 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;
  if (allocate_fbo_) {
    AllocateRenderBuffers(target, clamped_size);
  } else if (!texture_) {
    // Generate the texture object.
    texture_ = CreateTexture(target);
  }

  // Allocate a new IOSurface, which is the GPU resource that can be
  // shared across processes.
  base::ScopedCFTypeRef<CFMutableDictionaryRef> properties;
  properties.reset(CFDictionaryCreateMutable(kCFAllocatorDefault,
                                             0,
                                             &kCFTypeDictionaryKeyCallBacks,
                                             &kCFTypeDictionaryValueCallBacks));
  AddIntegerValue(properties, kIOSurfaceWidth, clamped_size.width());
  AddIntegerValue(properties, kIOSurfaceHeight, clamped_size.height());
  AddIntegerValue(properties, kIOSurfaceBytesPerElement, 4);
  AddBooleanValue(properties, kIOSurfaceIsGlobal, 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(IOSurfaceCreate(properties));

  // Don't think we need to identify a plane.
  GLuint plane = 0;
  CGLError error = CGLTexImageIOSurface2D(
      static_cast<CGLContextObj>(gl_context_->GetHandle()),
      target,
      GL_RGBA,
      clamped_size.width(),
      clamped_size.height(),
      GL_BGRA,
      GL_UNSIGNED_INT_8_8_8_8_REV,
      io_surface_.get(),
      plane);
  if (error != kCGLNoError) {
    DLOG(ERROR) << "CGL error " << error << " during CGLTexImageIOSurface2D";
  }
  if (allocate_fbo_) {
    // Set up the frame buffer object.
    if (!SetupFrameBufferObject(target)) {
      DLOG(ERROR) << "Failed to set up frame buffer object";
    }
  }
  surface_size_ = size;
  real_surface_size_ = clamped_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.
  io_surface_id_ = IOSurfaceGetID(io_surface_);
  return io_surface_id_;
}

uint32 AcceleratedSurface::GetSurfaceId() {
  return io_surface_id_;
}