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

backing_store_mac.mm

// 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.

#import <Cocoa/Cocoa.h>

#include "chrome/browser/renderer_host/backing_store_mac.h"

#include "app/surface/transport_dib.h"
#include "base/logging.h"
#include "base/mac_util.h"
#include "base/sys_info.h"
#include "chrome/browser/renderer_host/render_process_host.h"
#include "chrome/browser/renderer_host/render_widget_host.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"

// Mac Backing Stores:
//
// Since backing stores are only ever written to or drawn into windows, we keep
// our backing store in a CGLayer that can get cached in GPU memory.  This
// allows acclerated drawing into the layer and lets scrolling and such happen
// all or mostly on the GPU, which is good for performance.

namespace {

// Returns whether this version of OS X has broken CGLayers, see
// http://crbug.com/45553 , comments 5 and 6.
bool NeedsLayerWorkaround() {
  int32 os_major, os_minor, os_bugfix;
  base::SysInfo::OperatingSystemVersionNumbers(
      &os_major, &os_minor, &os_bugfix);
  return os_major == 10 && os_minor == 5;
}

}  // namespace

BackingStoreMac::BackingStoreMac(RenderWidgetHost* widget,
                                 const gfx::Size& size)
    : BackingStore(widget, size) {
  cg_layer_.reset(CreateCGLayer());
  if (!cg_layer_) {
    // The view isn't in a window yet.  Use a CGBitmapContext for now.
    cg_bitmap_.reset(CreateCGBitmapContext());
  }
}

BackingStoreMac::~BackingStoreMac() {
}

void BackingStoreMac::PaintToBackingStore(
    RenderProcessHost* process,
    TransportDIB::Id bitmap,
    const gfx::Rect& bitmap_rect,
    const std::vector<gfx::Rect>& copy_rects,
    bool* painted_synchronously) {
  // Our paints are always synchronous and the caller can free the TransportDIB,
  // even on failure.
  *painted_synchronously = true;

  DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));

  TransportDIB* dib = process->GetTransportDIB(bitmap);
  if (!dib)
    return;

  scoped_cftyperef<CGDataProviderRef> data_provider(
      CGDataProviderCreateWithData(NULL, dib->memory(),
      bitmap_rect.width() * bitmap_rect.height() * 4, NULL));

  scoped_cftyperef<CGImageRef> bitmap_image(
      CGImageCreate(bitmap_rect.width(), bitmap_rect.height(), 8, 32,
          4 * bitmap_rect.width(), mac_util::GetSystemColorSpace(),
          kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
          data_provider, NULL, false, kCGRenderingIntentDefault));

  for (size_t i = 0; i < copy_rects.size(); i++) {
    const gfx::Rect& copy_rect = copy_rects;

    // Only the subpixels given by copy_rect have pixels to copy.
    scoped_cftyperef<CGImageRef> image(
        CGImageCreateWithImageInRect(bitmap_image, CGRectMake(
            copy_rect.x() - bitmap_rect.x(),
            copy_rect.y() - bitmap_rect.y(),
            copy_rect.width(),
            copy_rect.height())));

    if (!cg_layer()) {
      // The view may have moved to a window.  Try to get a CGLayer.
      cg_layer_.reset(CreateCGLayer());
      if (cg_layer()) {
        // now that we have a layer, copy the cached image into it
        scoped_cftyperef<CGImageRef> bitmap_image(
            CGBitmapContextCreateImage(cg_bitmap_));
        CGContextDrawImage(CGLayerGetContext(cg_layer()),
                           CGRectMake(0, 0, size().width(), size().height()),
                           bitmap_image);
        // Discard the cache bitmap, since we no longer need it.
        cg_bitmap_.reset(NULL);
      }
    }

    DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));

    if (cg_layer()) {
      // The CGLayer's origin is in the lower left, but flipping the CTM would
      // cause the image to get drawn upside down.  So we move the rectangle
      // to the right position before drawing the image.
      CGContextRef layer = CGLayerGetContext(cg_layer());
      gfx::Rect paint_rect = copy_rect;
      paint_rect.set_y(size().height() - copy_rect.bottom());
      CGContextDrawImage(layer, paint_rect.ToCGRect(), image);
    } else {
      // The layer hasn't been created yet, so draw into the cache bitmap.
      gfx::Rect paint_rect = copy_rect;
      paint_rect.set_y(size().height() - copy_rect.bottom());
      CGContextDrawImage(cg_bitmap_, paint_rect.ToCGRect(), image);
    }
  }
}

bool BackingStoreMac::CopyFromBackingStore(const gfx::Rect& rect,
                                           skia::PlatformCanvas* output) {
  if (!output->initialize(rect.width(), rect.height(), true))
    return false;

  CGContextRef temp_context = output->beginPlatformPaint();
  CGContextSaveGState(temp_context);
  CGContextTranslateCTM(temp_context, 0.0, size().height());
  CGContextScaleCTM(temp_context, 1.0, -1.0);
  CGContextDrawLayerAtPoint(temp_context, CGPointMake(rect.x(), rect.y()),
                            cg_layer());
  CGContextRestoreGState(temp_context);
  output->endPlatformPaint();
  return true;
}

// Scroll the contents of our CGLayer
void BackingStoreMac::ScrollBackingStore(int dx, int dy,
                                         const gfx::Rect& clip_rect,
                                         const gfx::Size& view_size) {
  DCHECK_NE(static_cast<bool>(cg_layer()), static_cast<bool>(cg_bitmap()));

  // "Scroll" the contents of the layer by creating a new CGLayer,
  // copying the contents of the old one into the new one offset by the scroll
  // amount, swapping in the new CGLayer, and then painting in the new data.
  //
  // The Windows code always sets the whole backing store as the source of the
  // scroll. Thus, we only have to worry about pixels which will end up inside
  // the clipping rectangle. (Note that the clipping rectangle is not
  // translated by the scroll.)

  // We assume |clip_rect| is contained within the backing store.
  DCHECK(clip_rect.bottom() <= size().height());
  DCHECK(clip_rect.right() <= size().width());

  if ((dx || dy) && abs(dx) < size().width() && abs(dy) < size().height()) {
    if (cg_layer()) {
      // See http://crbug.com/45553 , comments 5 and 6.
      static bool needs_layer_workaround = NeedsLayerWorkaround();

      scoped_cftyperef<CGLayerRef> new_layer;
      CGContextRef layer;

      if (needs_layer_workaround) {
        new_layer.reset(CreateCGLayer());
        // If the current view is in a window, the replacement must be too.
        DCHECK(new_layer);

        layer = CGLayerGetContext(new_layer);
        CGContextDrawLayerAtPoint(layer, CGPointMake(0, 0), cg_layer());
      } else {
        layer = CGLayerGetContext(cg_layer());
      }

      CGContextSaveGState(layer);
      CGContextClipToRect(layer,
                          CGRectMake(clip_rect.x(),
                                     size().height() - clip_rect.bottom(),
                                     clip_rect.width(),
                                     clip_rect.height()));
      CGContextDrawLayerAtPoint(layer, CGPointMake(dx, -dy), cg_layer());
      CGContextRestoreGState(layer);

      if (needs_layer_workaround)
        cg_layer_.swap(new_layer);
    } else {
      // We don't have a layer, so scroll the contents of the CGBitmapContext.
      scoped_cftyperef<CGImageRef> bitmap_image(
          CGBitmapContextCreateImage(cg_bitmap_));
      CGContextSaveGState(cg_bitmap_);
      CGContextClipToRect(cg_bitmap_,
                          CGRectMake(clip_rect.x(),
                                     size().height() - clip_rect.bottom(),
                                     clip_rect.width(),
                                     clip_rect.height()));
      CGContextDrawImage(cg_bitmap_,
                         CGRectMake(dx, -dy, size().width(), size().height()),
                         bitmap_image);
      CGContextRestoreGState(cg_bitmap_);
    }
  }
}

CGLayerRef BackingStoreMac::CreateCGLayer() {
  // The CGLayer should be optimized for drawing into the containing window,
  // so extract a CGContext corresponding to the window to be passed to
  // CGLayerCreateWithContext.
  NSWindow* window = [render_widget_host()->view()->GetNativeView() window];
  if ([window windowNumber] <= 0) {
    // This catches a nil |window|, as well as windows that exist but that
    // aren't yet connected to WindowServer.
    return NULL;
  }

  NSGraphicsContext* ns_context = [window graphicsContext];
  DCHECK(ns_context);

  CGContextRef cg_context = static_cast<CGContextRef>(
      [ns_context graphicsPort]);
  DCHECK(cg_context);

  CGLayerRef layer = CGLayerCreateWithContext(cg_context,
                                              size().ToCGSize(),
                                              NULL);
  DCHECK(layer);

  return layer;
}

CGContextRef BackingStoreMac::CreateCGBitmapContext() {
  // A CGBitmapContext serves as a stand-in for the layer before the view is
  // in a containing window.
  CGContextRef context = CGBitmapContextCreate(NULL,
                                               size().width(), size().height(),
                                               8, size().width() * 4,
                                               mac_util::GetSystemColorSpace(),
                                               kCGImageAlphaPremultipliedFirst |
                                                   kCGBitmapByteOrder32Host);
  DCHECK(context);

  return context;
}

Generated by  Doxygen 1.6.0   Back to index